001 /*
002 * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.naming.ldap;
027
028 import java.util.Iterator;
029 import java.util.NoSuchElementException;
030 import java.util.ArrayList;
031 import java.util.Collections;
032
033 import javax.naming.InvalidNameException;
034 import javax.naming.directory.BasicAttributes;
035 import javax.naming.directory.Attributes;
036 import javax.naming.directory.Attribute;
037 import javax.naming.NamingEnumeration;
038 import javax.naming.NamingException;
039
040 import java.io.Serializable;
041 import java.io.ObjectOutputStream;
042 import java.io.ObjectInputStream;
043 import java.io.IOException;
044
045 /**
046 * This class represents a relative distinguished name, or RDN, which is a
047 * component of a distinguished name as specified by
048 * <a href="http://ietf.org/rfc/rfc2253.txt">RFC 2253</a>.
049 * An example of an RDN is "OU=Sales+CN=J.Smith". In this example,
050 * the RDN consist of multiple attribute type/value pairs. The
051 * RDN is parsed as described in the class description for
052 * {@link javax.naming.ldap.LdapName <tt>LdapName</tt>}.
053 * <p>
054 * The Rdn class represents an RDN as attribute type/value mappings,
055 * which can be viewed using
056 * {@link javax.naming.directory.Attributes Attributes}.
057 * In addition, it contains convenience methods that allow easy retrieval
058 * of type and value when the Rdn consist of a single type/value pair,
059 * which is how it appears in a typical usage.
060 * It also contains helper methods that allow escaping of the unformatted
061 * attribute value and unescaping of the value formatted according to the
062 * escaping syntax defined in RFC2253. For methods that take or return
063 * attribute value as an Object, the value is either a String
064 * (in unescaped form) or a byte array.
065 * <p>
066 * <code>Rdn</code> will properly parse all valid RDNs, but
067 * does not attempt to detect all possible violations when parsing
068 * invalid RDNs. It is "generous" in accepting invalid RDNs.
069 * The "validity" of a name is determined ultimately when it
070 * is supplied to an LDAP server, which may accept or
071 * reject the name based on factors such as its schema information
072 * and interoperability considerations.
073 *
074 * <p>
075 * The following code example shows how to construct an Rdn using the
076 * constructor that takes type and value as arguments:
077 * <pre>
078 * Rdn rdn = new Rdn("cn", "Juicy, Fruit");
079 * System.out.println(rdn.toString());
080 * </pre>
081 * The last line will print <tt>cn=Juicy\, Fruit</tt>. The
082 * {@link #unescapeValue(String) <tt>unescapeValue()</tt>} method can be
083 * used to unescape the escaped comma resulting in the original
084 * value <tt>"Juicy, Fruit"</tt>. The {@link #escapeValue(Object)
085 * <tt>escapeValue()</tt>} method adds the escape back preceding the comma.
086 * <p>
087 * This class can be instantiated by a string representation
088 * of the RDN defined in RFC 2253 as shown in the following code example:
089 * <pre>
090 * Rdn rdn = new Rdn("cn=Juicy\\, Fruit");
091 * System.out.println(rdn.toString());
092 * </pre>
093 * The last line will print <tt>cn=Juicy\, Fruit</tt>.
094 * <p>
095 * Concurrent multithreaded read-only access of an instance of
096 * <tt>Rdn</tt> need not be synchronized.
097 * <p>
098 * Unless otherwise noted, the behavior of passing a null argument
099 * to a constructor or method in this class will cause NullPointerException
100 * to be thrown.
101 *
102 * @version 1.15 07/05/05
103 * @since 1.5
104 */
105
106 public class Rdn implements Serializable, Comparable<Object> {
107
108 // private transient ArrayList<RdnEntry> entries;
109 private transient ArrayList entries;
110
111 // The common case.
112 private static final int DEFAULT_SIZE = 1;
113
114 private static final long serialVersionUID = -5994465067210009656L;
115
116 /**
117 * Constructs an Rdn from the given attribute set. See
118 * {@link javax.naming.directory.Attributes Attributes}.
119 * <p>
120 * The string attribute values are not interpretted as
121 * <a href="http://ietf.org/rfc/rfc2253.txt">RFC 2253</a>
122 * formatted RDN strings. That is, the values are used
123 * literally (not parsed) and assumed to be unescaped.
124 *
125 * @param attrSet The non-null and non-empty attributes containing
126 * type/value mappings.
127 * @throws InvalidNameException If contents of <tt>attrSet</tt> cannot
128 * be used to construct a valid RDN.
129 */
130 public Rdn(Attributes attrSet) throws InvalidNameException {
131 if (attrSet.size() == 0) {
132 throw new InvalidNameException("Attributes cannot be empty");
133 }
134 entries = new ArrayList(attrSet.size());
135 NamingEnumeration attrs = attrSet.getAll();
136 try {
137 for (int nEntries = 0; attrs.hasMore(); nEntries++) {
138 RdnEntry entry = new RdnEntry();
139 Attribute attr = (Attribute) attrs.next();
140 entry.type = attr.getID();
141 entry.value = attr.get();
142 entries.add(nEntries, entry);
143 }
144 } catch (NamingException e) {
145 InvalidNameException e2 = new InvalidNameException(e
146 .getMessage());
147 e2.initCause(e);
148 throw e2;
149 }
150 sort(); // arrange entries for comparison
151 }
152
153 /**
154 * Constructs an Rdn from the given string.
155 * This constructor takes a string formatted according to the rules
156 * defined in <a href="http://ietf.org//rfc/rfc2253.txt">RFC 2253</a>
157 * and described in the class description for
158 * {@link javax.naming.ldap.LdapName}.
159 *
160 * @param rdnString The non-null and non-empty RFC2253 formatted string.
161 * @throws InvalidNameException If a syntax error occurs during
162 * parsing of the rdnString.
163 */
164 public Rdn(String rdnString) throws InvalidNameException {
165 entries = new ArrayList(DEFAULT_SIZE);
166 (new Rfc2253Parser(rdnString)).parseRdn(this );
167 }
168
169 /**
170 * Constructs an Rdn from the given <tt>rdn</tt>.
171 * The contents of the <tt>rdn</tt> are simply copied into the newly
172 * created Rdn.
173 * @param rdn The non-null Rdn to be copied.
174 */
175 public Rdn(Rdn rdn) {
176 entries = new ArrayList(rdn.entries.size());
177 entries.addAll(rdn.entries);
178 }
179
180 /**
181 * Constructs an Rdn from the given attribute type and
182 * value.
183 * The string attribute values are not interpretted as
184 * <a href="http://ietf.org/rfc/rfc2253.txt">RFC 2253</a>
185 * formatted RDN strings. That is, the values are used
186 * literally (not parsed) and assumed to be unescaped.
187 *
188 * @param type The non-null and non-empty string attribute type.
189 * @param value The non-null and non-empty attribute value.
190 * @throws InvalidNameException If type/value cannot be used to
191 * construct a valid RDN.
192 * @see #toString()
193 */
194 public Rdn(String type, Object value) throws InvalidNameException {
195 if (value == null) {
196 throw new NullPointerException("Cannot set value to null");
197 }
198 if (type.equals("") || isEmptyValue(value)) {
199 throw new InvalidNameException(
200 "type or value cannot be empty, type:" + type
201 + " value:" + value);
202 }
203 entries = new ArrayList(DEFAULT_SIZE);
204 put(type, value);
205 }
206
207 private boolean isEmptyValue(Object val) {
208 return ((val instanceof String) && val.equals(""))
209 || ((val instanceof byte[]) && (((byte[]) val).length == 0));
210 }
211
212 // An empty constructor used by the parser
213 Rdn() {
214 entries = new ArrayList(DEFAULT_SIZE);
215 }
216
217 /*
218 * Adds the given attribute type and value to this Rdn.
219 * The string attribute values are not interpretted as
220 * <a href="http://ietf.org/rfc/rfc2253.txt">RFC 2253</a>
221 * formatted RDN strings. That is the values are used
222 * literally (not parsed) and assumed to be unescaped.
223 *
224 * @param type The non-null and non-empty string attribute type.
225 * @param value The non-null and non-empty attribute value.
226 * @return The updated Rdn, not a new one. Cannot be null.
227 * @see #toString()
228 */
229 Rdn put(String type, Object value) {
230
231 // create new Entry
232 RdnEntry newEntry = new RdnEntry();
233 newEntry.type = type;
234 if (value instanceof byte[]) { // clone the byte array
235 newEntry.value = ((byte[]) value).clone();
236 } else {
237 newEntry.value = value;
238 }
239 entries.add(newEntry);
240 return this ;
241 }
242
243 void sort() {
244 if (entries.size() > 1) {
245 Collections.sort(entries);
246 }
247 }
248
249 /**
250 * Retrieves one of this Rdn's value.
251 * This is a convenience method for obtaining the value,
252 * when the RDN contains a single type and value mapping,
253 * which is the common RDN usage.
254 * <p>
255 * For a multi-valued RDN, this method returns value corresponding
256 * to the type returned by {@link #getType() getType()} method.
257 *
258 * @return The non-null attribute value.
259 */
260 public Object getValue() {
261 return ((RdnEntry) entries.get(0)).getValue();
262 }
263
264 /**
265 * Retrieves one of this Rdn's type.
266 * This is a convenience method for obtaining the type,
267 * when the RDN contains a single type and value mapping,
268 * which is the common RDN usage.
269 * <p>
270 * For a multi-valued RDN, the type/value pairs have
271 * no specific order defined on them. In that case, this method
272 * returns type of one of the type/value pairs.
273 * The {@link #getValue() getValue()} method returns the
274 * value corresponding to the type returned by this method.
275 *
276 * @return The non-null attribute type.
277 */
278 public String getType() {
279 return ((RdnEntry) entries.get(0)).getType();
280 }
281
282 /**
283 * Returns this Rdn as a string represented in a format defined by
284 * <a href="http://ietf.org//rfc/rfc2253.txt">RFC 2253</a> and described
285 * in the class description for {@link javax.naming.ldap.LdapName LdapName}.
286 *
287 * @return The string representation of the Rdn.
288 */
289 public String toString() {
290 StringBuilder builder = new StringBuilder();
291 int size = entries.size();
292 if (size > 0) {
293 builder.append(entries.get(0));
294 }
295 for (int next = 1; next < size; next++) {
296 builder.append('+');
297 builder.append(entries.get(next));
298 }
299 return builder.toString();
300 }
301
302 /**
303 * Compares this Rdn with the specified Object for order.
304 * Returns a negative integer, zero, or a positive integer as this
305 * Rdn is less than, equal to, or greater than the given Object.
306 * <p>
307 * If obj is null or not an instance of Rdn, ClassCastException
308 * is thrown.
309 * <p>
310 * The attribute type and value pairs of the RDNs are lined up
311 * against each other and compared lexicographically. The order of
312 * components in multi-valued Rdns (such as "ou=Sales+cn=Bob") is not
313 * significant.
314 *
315 * @param obj The non-null object to compare against.
316 * @return A negative integer, zero, or a positive integer as this Rdn
317 * is less than, equal to, or greater than the given Object.
318 * @exception ClassCastException if obj is null or not a Rdn.
319 * <p>
320 */
321 public int compareTo(Object obj) {
322 if (!(obj instanceof Rdn)) {
323 throw new ClassCastException("The obj is not a Rdn");
324 }
325 if (obj == this ) {
326 return 0;
327 }
328 Rdn that = (Rdn) obj;
329 int minSize = Math.min(entries.size(), that.entries.size());
330 for (int i = 0; i < minSize; i++) {
331
332 // Compare a single pair of type/value pairs.
333 int diff = ((RdnEntry) entries.get(i))
334 .compareTo(that.entries.get(i));
335 if (diff != 0) {
336 return diff;
337 }
338 }
339 return (entries.size() - that.entries.size()); // longer RDN wins
340 }
341
342 /**
343 * Compares the specified Object with this Rdn for equality.
344 * Returns true if the given object is also a Rdn and the two Rdns
345 * represent the same attribute type and value mappings. The order of
346 * components in multi-valued Rdns (such as "ou=Sales+cn=Bob") is not
347 * significant.
348 * <p>
349 * Type and value equalilty matching is done as below:
350 * <ul>
351 * <li> The types are compared for equality with their case ignored.
352 * <li> String values with different but equivalent usage of quoting,
353 * escaping, or UTF8-hex-encoding are considered equal.
354 * The case of the values is ignored during the comparison.
355 * </ul>
356 * <p>
357 * If obj is null or not an instance of Rdn, false is returned.
358 * <p>
359 * @param obj object to be compared for equality with this Rdn.
360 * @return true if the specified object is equal to this Rdn.
361 * @see #hashCode()
362 */
363 public boolean equals(Object obj) {
364 if (obj == this ) {
365 return true;
366 }
367 if (!(obj instanceof Rdn)) {
368 return false;
369 }
370 Rdn that = (Rdn) obj;
371 if (entries.size() != that.size()) {
372 return false;
373 }
374 for (int i = 0; i < entries.size(); i++) {
375 if (!entries.get(i).equals(that.entries.get(i))) {
376 return false;
377 }
378 }
379 return true;
380 }
381
382 /**
383 * Returns the hash code of this RDN. Two RDNs that are
384 * equal (according to the equals method) will have the same
385 * hash code.
386 *
387 * @return An int representing the hash code of this Rdn.
388 * @see #equals
389 */
390 public int hashCode() {
391
392 // Sum up the hash codes of the components.
393 int hash = 0;
394
395 // For each type/value pair...
396 for (int i = 0; i < entries.size(); i++) {
397 hash += entries.get(i).hashCode();
398 }
399 return hash;
400 }
401
402 /**
403 * Retrieves the {@link javax.naming.directory.Attributes Attributes}
404 * view of the type/value mappings contained in this Rdn.
405 *
406 * @return The non-null attributes containing the type/value
407 * mappings of this Rdn.
408 */
409 public Attributes toAttributes() {
410 Attributes attrs = new BasicAttributes(true);
411 for (int i = 0; i < entries.size(); i++) {
412 RdnEntry entry = (RdnEntry) entries.get(i);
413 Attribute attr = attrs.put(entry.getType(), entry
414 .getValue());
415 if (attr != null) {
416 attr.add(entry.getValue());
417 attrs.put(attr);
418 }
419 }
420 return attrs;
421 }
422
423 private static class RdnEntry implements Comparable {
424 private String type;
425 private Object value;
426
427 // If non-null, a cannonical representation of the value suitable
428 // for comparison using String.compareTo()
429 private String comparable = null;
430
431 String getType() {
432 return type;
433 }
434
435 Object getValue() {
436 return value;
437 }
438
439 public int compareTo(Object obj) {
440
441 // Any change here affecting equality must be
442 // reflected in hashCode().
443 RdnEntry that = (RdnEntry) obj;
444
445 int diff = type.toUpperCase().compareTo(
446 that.type.toUpperCase());
447 if (diff != 0) {
448 return diff;
449 }
450 if (value.equals(that.value)) { // try shortcut
451 return 0;
452 }
453 return getValueComparable().compareTo(
454 that.getValueComparable());
455 }
456
457 public boolean equals(Object obj) {
458 if (obj == this ) {
459 return true;
460 }
461 if (!(obj instanceof RdnEntry)) {
462 return false;
463 }
464
465 // Any change here must be reflected in hashCode()
466 RdnEntry that = (RdnEntry) obj;
467 return (type.equalsIgnoreCase(that.type))
468 && (getValueComparable().equals(that
469 .getValueComparable()));
470 }
471
472 public int hashCode() {
473 return (type.toUpperCase().hashCode() + getValueComparable()
474 .hashCode());
475 }
476
477 public String toString() {
478 return type + "=" + escapeValue(value);
479 }
480
481 private String getValueComparable() {
482 if (comparable != null) {
483 return comparable; // return cached result
484 }
485
486 // cache result
487 if (value instanceof byte[]) {
488 comparable = escapeBinaryValue((byte[]) value);
489 } else {
490 comparable = ((String) value).toUpperCase();
491 }
492 return comparable;
493 }
494 }
495
496 /**
497 * Retrieves the number of attribute type/value pairs in this Rdn.
498 * @return The non-negative number of type/value pairs in this Rdn.
499 */
500 public int size() {
501 return entries.size();
502 }
503
504 /**
505 * Given the value of an attribute, returns a string escaped according
506 * to the rules specified in
507 * <a href="http://ietf.org/rfc/rfc2253.txt">RFC 2253</a>.
508 * <p>
509 * For example, if the val is "Sue, Grabbit and Runn", the escaped
510 * value returned by this method is "Sue\, Grabbit and Runn".
511 * <p>
512 * A string value is represented as a String and binary value
513 * as a byte array.
514 *
515 * @param val The non-null object to be escaped.
516 * @return Escaped string value.
517 * @throws ClassCastException if val is is not a String or byte array.
518 */
519 public static String escapeValue(Object val) {
520 return (val instanceof byte[]) ? escapeBinaryValue((byte[]) val)
521 : escapeStringValue((String) val);
522 }
523
524 /*
525 * Given the value of a string-valued attribute, returns a
526 * string suitable for inclusion in a DN. This is accomplished by
527 * using backslash (\) to escape the following characters:
528 * leading and trailing whitespace
529 * , = + < > # ; " \
530 */
531 private static final String escapees = ",=+<>#;\"\\";
532
533 private static String escapeStringValue(String val) {
534
535 char[] chars = val.toCharArray();
536 StringBuilder builder = new StringBuilder(2 * val.length());
537
538 // Find leading and trailing whitespace.
539 int lead; // index of first char that is not leading whitespace
540 for (lead = 0; lead < chars.length; lead++) {
541 if (!isWhitespace(chars[lead])) {
542 break;
543 }
544 }
545 int trail; // index of last char that is not trailing whitespace
546 for (trail = chars.length - 1; trail >= 0; trail--) {
547 if (!isWhitespace(chars[trail])) {
548 break;
549 }
550 }
551
552 for (int i = 0; i < chars.length; i++) {
553 char c = chars[i];
554 if ((i < lead) || (i > trail) || (escapees.indexOf(c) >= 0)) {
555 builder.append('\\');
556 }
557 builder.append(c);
558 }
559 return builder.toString();
560 }
561
562 /*
563 * Given the value of a binary attribute, returns a string
564 * suitable for inclusion in a DN (such as "#CEB1DF80").
565 * TBD: This method should actually generate the ber encoding
566 * of the binary value
567 */
568 private static String escapeBinaryValue(byte[] val) {
569
570 StringBuilder builder = new StringBuilder(1 + 2 * val.length);
571 builder.append("#");
572
573 for (int i = 0; i < val.length; i++) {
574 byte b = val[i];
575 builder.append(Character.forDigit(0xF & (b >>> 4), 16));
576 builder.append(Character.forDigit(0xF & b, 16));
577 }
578 return builder.toString();
579 // return builder.toString().toUpperCase();
580 }
581
582 /**
583 * Given an attribute value string formated according to the rules
584 * specified in
585 * <a href="http://ietf.org//rfc/rfc2253.txt">RFC 2253</a>,
586 * returns the unformated value. Escapes and quotes are
587 * stripped away, and hex-encoded UTF-8 is converted to equivalent
588 * UTF-16 characters. Returns a string value as a String, and a
589 * binary value as a byte array.
590 * <p>
591 * Legal and illegal values are defined in RFC 2253.
592 * This method is generous in accepting the values and does not
593 * catch all illegal values.
594 * Therefore, passing in an illegal value might not necessarily
595 * trigger an <tt>IllegalArgumentException</tt>.
596 *
597 * @param val The non-null string to be unescaped.
598 * @return Unescaped value.
599 * @throws IllegalArgumentException When an Illegal value
600 * is provided.
601 */
602 public static Object unescapeValue(String val) {
603
604 char[] chars = val.toCharArray();
605 int beg = 0;
606 int end = chars.length;
607
608 // Trim off leading and trailing whitespace.
609 while ((beg < end) && isWhitespace(chars[beg])) {
610 ++beg;
611 }
612
613 while ((beg < end) && isWhitespace(chars[end - 1])) {
614 --end;
615 }
616
617 // Add back the trailing whitespace with a preceeding '\'
618 // (escaped or unescaped) that was taken off in the above
619 // loop. Whether or not to retain this whitespace is decided below.
620 if (end != chars.length && (beg < end)
621 && chars[end - 1] == '\\') {
622 end++;
623 }
624 if (beg >= end) {
625 return "";
626 }
627
628 if (chars[beg] == '#') {
629 // Value is binary (eg: "#CEB1DF80").
630 return decodeHexPairs(chars, ++beg, end);
631 }
632
633 // Trim off quotes.
634 if ((chars[beg] == '\"') && (chars[end - 1] == '\"')) {
635 ++beg;
636 --end;
637 }
638
639 StringBuilder builder = new StringBuilder(end - beg);
640 int esc = -1; // index of the last escaped character
641
642 for (int i = beg; i < end; i++) {
643 if ((chars[i] == '\\') && (i + 1 < end)) {
644 if (!Character.isLetterOrDigit(chars[i + 1])) {
645 ++i; // skip backslash
646 builder.append(chars[i]); // snarf escaped char
647 esc = i;
648 } else {
649
650 // Convert hex-encoded UTF-8 to 16-bit chars.
651 byte[] utf8 = getUtf8Octets(chars, i, end);
652 if (utf8.length > 0) {
653 try {
654 builder.append(new String(utf8, "UTF8"));
655 } catch (java.io.UnsupportedEncodingException e) {
656 // shouldn't happen
657 }
658 i += utf8.length * 3 - 1;
659 } else { // no utf8 bytes available, invalid DN
660
661 // '/' has no meaning, throw exception
662 throw new IllegalArgumentException(
663 "Not a valid attribute string value:"
664 + val
665 + ",improper usage of backslash");
666 }
667 }
668 } else {
669 builder.append(chars[i]); // snarf unescaped char
670 }
671 }
672
673 // Get rid of the unescaped trailing whitespace with the
674 // preceeding '\' character that was previously added back.
675 int len = builder.length();
676 if (isWhitespace(builder.charAt(len - 1)) && esc != (end - 1)) {
677 builder.setLength(len - 1);
678 }
679 return builder.toString();
680 }
681
682 /*
683 * Given an array of chars (with starting and ending indexes into it)
684 * representing bytes encoded as hex-pairs (such as "CEB1DF80"),
685 * returns a byte array containing the decoded bytes.
686 */
687 private static byte[] decodeHexPairs(char[] chars, int beg, int end) {
688 byte[] bytes = new byte[(end - beg) / 2];
689 for (int i = 0; beg + 1 < end; i++) {
690 int hi = Character.digit(chars[beg], 16);
691 int lo = Character.digit(chars[beg + 1], 16);
692 if (hi < 0 || lo < 0) {
693 break;
694 }
695 bytes[i] = (byte) ((hi << 4) + lo);
696 beg += 2;
697 }
698 if (beg != end) {
699 throw new IllegalArgumentException(
700 "Illegal attribute value: " + new String(chars));
701 }
702 return bytes;
703 }
704
705 /*
706 * Given an array of chars (with starting and ending indexes into it),
707 * finds the largest prefix consisting of hex-encoded UTF-8 octets,
708 * and returns a byte array containing the corresponding UTF-8 octets.
709 *
710 * Hex-encoded UTF-8 octets look like this:
711 * \03\B1\DF\80
712 */
713 private static byte[] getUtf8Octets(char[] chars, int beg, int end) {
714 byte[] utf8 = new byte[(end - beg) / 3]; // allow enough room
715 int len = 0; // index of first unused byte in utf8
716
717 while ((beg + 2 < end) && (chars[beg++] == '\\')) {
718 int hi = Character.digit(chars[beg++], 16);
719 int lo = Character.digit(chars[beg++], 16);
720 if (hi < 0 || lo < 0) {
721 break;
722 }
723 utf8[len++] = (byte) ((hi << 4) + lo);
724 }
725 if (len == utf8.length) {
726 return utf8;
727 } else {
728 byte[] res = new byte[len];
729 System.arraycopy(utf8, 0, res, 0, len);
730 return res;
731 }
732 }
733
734 /*
735 * Best guess as to what RFC 2253 means by "whitespace".
736 */
737 private static boolean isWhitespace(char c) {
738 return (c == ' ' || c == '\r');
739 }
740
741 /**
742 * Serializes only the unparsed RDN, for compactness and to avoid
743 * any implementation dependency.
744 *
745 * @serialData The RDN string
746 */
747 private void writeObject(ObjectOutputStream s)
748 throws java.io.IOException {
749 s.defaultWriteObject();
750 s.writeObject(toString());
751 }
752
753 private void readObject(ObjectInputStream s) throws IOException,
754 ClassNotFoundException {
755 s.defaultReadObject();
756 entries = new ArrayList(DEFAULT_SIZE);
757 String unparsed = (String) s.readObject();
758 try {
759 (new Rfc2253Parser(unparsed)).parseRdn(this );
760 } catch (InvalidNameException e) {
761 // shouldn't happen
762 throw new java.io.StreamCorruptedException("Invalid name: "
763 + unparsed);
764 }
765 }
766 }
|