001 /*
002 * Copyright 2007-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2016 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.ldap.sdk;
022
023
024
025 import java.io.Serializable;
026 import java.nio.ByteBuffer;
027 import java.util.ArrayList;
028 import java.util.Comparator;
029 import java.util.Map;
030 import java.util.TreeMap;
031
032 import com.unboundid.asn1.ASN1OctetString;
033 import com.unboundid.ldap.matchingrules.MatchingRule;
034 import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
035 import com.unboundid.ldap.sdk.schema.Schema;
036
037 import static com.unboundid.ldap.sdk.LDAPMessages.*;
038 import static com.unboundid.util.Debug.*;
039 import static com.unboundid.util.StaticUtils.*;
040 import static com.unboundid.util.Validator.*;
041
042
043
044 /**
045 * This class provides a data structure for holding information about an LDAP
046 * relative distinguished name (RDN). An RDN consists of one or more
047 * attribute name-value pairs. See
048 * <A HREF="http://www.ietf.org/rfc/rfc4514.txt">RFC 4514</A> for more
049 * information about representing DNs and RDNs as strings. See the
050 * documentation in the {@code DN} class for more information about DNs and
051 * RDNs.
052 */
053 public final class RDN
054 implements Comparable<RDN>, Comparator<RDN>, Serializable
055 {
056 /**
057 * The serial version UID for this serializable class.
058 */
059 private static final long serialVersionUID = 2923419812807188487L;
060
061
062
063 // The set of attribute values for this RDN.
064 private final ASN1OctetString[] attributeValues;
065
066 // The schema to use to generate the normalized string representation of this
067 // RDN, if any.
068 private final Schema schema;
069
070 // The normalized string representation for this RDN.
071 private volatile String normalizedString;
072
073 // The user-defined string representation for this RDN.
074 private volatile String rdnString;
075
076 // The set of attribute names for this RDN.
077 private final String[] attributeNames;
078
079
080
081 /**
082 * Creates a new single-valued RDN with the provided information.
083 *
084 * @param attributeName The attribute name for this RDN. It must not be
085 * {@code null}.
086 * @param attributeValue The attribute value for this RDN. It must not be
087 * {@code null}.
088 */
089 public RDN(final String attributeName, final String attributeValue)
090 {
091 this(attributeName, attributeValue, null);
092 }
093
094
095
096 /**
097 * Creates a new single-valued RDN with the provided information.
098 *
099 * @param attributeName The attribute name for this RDN. It must not be
100 * {@code null}.
101 * @param attributeValue The attribute value for this RDN. It must not be
102 * {@code null}.
103 * @param schema The schema to use to generate the normalized string
104 * representation of this RDN. It may be {@code null}
105 * if no schema is available.
106 */
107 public RDN(final String attributeName, final String attributeValue,
108 final Schema schema)
109 {
110 ensureNotNull(attributeName, attributeValue);
111
112 this.schema = schema;
113
114 attributeNames = new String[] { attributeName };
115 attributeValues =
116 new ASN1OctetString[] { new ASN1OctetString(attributeValue) };
117 }
118
119
120
121 /**
122 * Creates a new single-valued RDN with the provided information.
123 *
124 * @param attributeName The attribute name for this RDN. It must not be
125 * {@code null}.
126 * @param attributeValue The attribute value for this RDN. It must not be
127 * {@code null}.
128 */
129 public RDN(final String attributeName, final byte[] attributeValue)
130 {
131 this(attributeName, attributeValue, null);
132 }
133
134
135
136 /**
137 * Creates a new single-valued RDN with the provided information.
138 *
139 * @param attributeName The attribute name for this RDN. It must not be
140 * {@code null}.
141 * @param attributeValue The attribute value for this RDN. It must not be
142 * {@code null}.
143 * @param schema The schema to use to generate the normalized string
144 * representation of this RDN. It may be {@code null}
145 * if no schema is available.
146 */
147 public RDN(final String attributeName, final byte[] attributeValue,
148 final Schema schema)
149 {
150 ensureNotNull(attributeName, attributeValue);
151
152 this.schema = schema;
153
154 attributeNames = new String[] { attributeName };
155 attributeValues =
156 new ASN1OctetString[] { new ASN1OctetString(attributeValue) };
157 }
158
159
160
161 /**
162 * Creates a new (potentially multivalued) RDN. The set of names must have
163 * the same number of elements as the set of values, and there must be at
164 * least one element in each array.
165 *
166 * @param attributeNames The set of attribute names for this RDN. It must
167 * not be {@code null} or empty.
168 * @param attributeValues The set of attribute values for this RDN. It must
169 * not be {@code null} or empty.
170 */
171 public RDN(final String[] attributeNames, final String[] attributeValues)
172 {
173 this(attributeNames, attributeValues, null);
174 }
175
176
177
178 /**
179 * Creates a new (potentially multivalued) RDN. The set of names must have
180 * the same number of elements as the set of values, and there must be at
181 * least one element in each array.
182 *
183 * @param attributeNames The set of attribute names for this RDN. It must
184 * not be {@code null} or empty.
185 * @param attributeValues The set of attribute values for this RDN. It must
186 * not be {@code null} or empty.
187 * @param schema The schema to use to generate the normalized
188 * string representation of this RDN. It may be
189 * {@code null} if no schema is available.
190 */
191 public RDN(final String[] attributeNames, final String[] attributeValues,
192 final Schema schema)
193 {
194 ensureNotNull(attributeNames, attributeValues);
195 ensureTrue(attributeNames.length == attributeValues.length,
196 "RDN.attributeNames and attributeValues must be the same size.");
197 ensureTrue(attributeNames.length > 0,
198 "RDN.attributeNames must not be empty.");
199
200 this.attributeNames = attributeNames;
201 this.schema = schema;
202
203 this.attributeValues = new ASN1OctetString[attributeValues.length];
204 for (int i=0; i < attributeValues.length; i++)
205 {
206 this.attributeValues[i] = new ASN1OctetString(attributeValues[i]);
207 }
208 }
209
210
211
212 /**
213 * Creates a new (potentially multivalued) RDN. The set of names must have
214 * the same number of elements as the set of values, and there must be at
215 * least one element in each array.
216 *
217 * @param attributeNames The set of attribute names for this RDN. It must
218 * not be {@code null} or empty.
219 * @param attributeValues The set of attribute values for this RDN. It must
220 * not be {@code null} or empty.
221 */
222 public RDN(final String[] attributeNames, final byte[][] attributeValues)
223 {
224 this(attributeNames, attributeValues, null);
225 }
226
227
228
229 /**
230 * Creates a new (potentially multivalued) RDN. The set of names must have
231 * the same number of elements as the set of values, and there must be at
232 * least one element in each array.
233 *
234 * @param attributeNames The set of attribute names for this RDN. It must
235 * not be {@code null} or empty.
236 * @param attributeValues The set of attribute values for this RDN. It must
237 * not be {@code null} or empty.
238 * @param schema The schema to use to generate the normalized
239 * string representation of this RDN. It may be
240 * {@code null} if no schema is available.
241 */
242 public RDN(final String[] attributeNames, final byte[][] attributeValues,
243 final Schema schema)
244 {
245 ensureNotNull(attributeNames, attributeValues);
246 ensureTrue(attributeNames.length == attributeValues.length,
247 "RDN.attributeNames and attributeValues must be the same size.");
248 ensureTrue(attributeNames.length > 0,
249 "RDN.attributeNames must not be empty.");
250
251 this.attributeNames = attributeNames;
252 this.schema = schema;
253
254 this.attributeValues = new ASN1OctetString[attributeValues.length];
255 for (int i=0; i < attributeValues.length; i++)
256 {
257 this.attributeValues[i] = new ASN1OctetString(attributeValues[i]);
258 }
259 }
260
261
262
263 /**
264 * Creates a new single-valued RDN with the provided information.
265 *
266 * @param attributeName The name to use for this RDN.
267 * @param attributeValue The value to use for this RDN.
268 * @param schema The schema to use to generate the normalized string
269 * representation of this RDN. It may be {@code null}
270 * if no schema is available.
271 * @param rdnString The string representation for this RDN.
272 */
273 RDN(final String attributeName, final ASN1OctetString attributeValue,
274 final Schema schema, final String rdnString)
275 {
276 this.rdnString = rdnString;
277 this.schema = schema;
278
279 attributeNames = new String[] { attributeName };
280 attributeValues = new ASN1OctetString[] { attributeValue };
281 }
282
283
284
285 /**
286 * Creates a new potentially multivalued RDN with the provided information.
287 *
288 * @param attributeNames The set of names to use for this RDN.
289 * @param attributeValues The set of values to use for this RDN.
290 * @param rdnString The string representation for this RDN.
291 * @param schema The schema to use to generate the normalized
292 * string representation of this RDN. It may be
293 * {@code null} if no schema is available.
294 */
295 RDN(final String[] attributeNames, final ASN1OctetString[] attributeValues,
296 final Schema schema, final String rdnString)
297 {
298 this.rdnString = rdnString;
299 this.schema = schema;
300
301 this.attributeNames = attributeNames;
302 this.attributeValues = attributeValues;
303 }
304
305
306
307 /**
308 * Creates a new RDN from the provided string representation.
309 *
310 * @param rdnString The string representation to use for this RDN. It must
311 * not be empty or {@code null}.
312 *
313 * @throws LDAPException If the provided string cannot be parsed as a valid
314 * RDN.
315 */
316 public RDN(final String rdnString)
317 throws LDAPException
318 {
319 this(rdnString, (Schema) null);
320 }
321
322
323
324 /**
325 * Creates a new RDN from the provided string representation.
326 *
327 * @param rdnString The string representation to use for this RDN. It must
328 * not be empty or {@code null}.
329 * @param schema The schema to use to generate the normalized string
330 * representation of this RDN. It may be {@code null} if
331 * no schema is available.
332 *
333 * @throws LDAPException If the provided string cannot be parsed as a valid
334 * RDN.
335 */
336 public RDN(final String rdnString, final Schema schema)
337 throws LDAPException
338 {
339 ensureNotNull(rdnString);
340
341 this.rdnString = rdnString;
342 this.schema = schema;
343
344 int pos = 0;
345 final int length = rdnString.length();
346
347 // First, skip over any leading spaces.
348 while ((pos < length) && (rdnString.charAt(pos) == ' '))
349 {
350 pos++;
351 }
352
353 // Read until we find a space or an equal sign. Technically, we should
354 // ensure that all characters before that point are ASCII letters, numeric
355 // digits, or dashes, or that it is a valid numeric OID, but since some
356 // directories allow technically invalid characters in attribute names,
357 // we'll just blindly take whatever is provided.
358 int attrStartPos = pos;
359 while (pos < length)
360 {
361 final char c = rdnString.charAt(pos);
362 if ((c == ' ') || (c == '='))
363 {
364 break;
365 }
366
367 pos++;
368 }
369
370 // Extract the attribute name, then skip over any spaces between the
371 // attribute name and the equal sign.
372 String attrName = rdnString.substring(attrStartPos, pos);
373 if (attrName.length() == 0)
374 {
375 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
376 ERR_RDN_NO_ATTR_NAME.get());
377 }
378
379 while ((pos < length) && (rdnString.charAt(pos) == ' '))
380 {
381 pos++;
382 }
383
384 if ((pos >= length) || (rdnString.charAt(pos) != '='))
385 {
386 // We didn't find an equal sign.
387 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
388 ERR_RDN_NO_EQUAL_SIGN.get(attrName));
389 }
390
391
392 // The next character is the equal sign. Skip it, and then skip over any
393 // spaces between it and the attribute value.
394 pos++;
395 while ((pos < length) && (rdnString.charAt(pos) == ' '))
396 {
397 pos++;
398 }
399
400
401 // Look at the next character. If it is an octothorpe (#), then the value
402 // must be hex-encoded. Otherwise, it's a regular string (although possibly
403 // containing escaped or quoted characters).
404 ASN1OctetString value;
405 if (pos >= length)
406 {
407 value = new ASN1OctetString();
408 }
409 else if (rdnString.charAt(pos) == '#')
410 {
411 // It is a hex-encoded value, so we'll read until we find the end of the
412 // string or the first non-hex character, which must be either a space or
413 // a plus sign.
414 final byte[] valueArray = readHexString(rdnString, ++pos);
415 value = new ASN1OctetString(valueArray);
416 pos += (valueArray.length * 2);
417 }
418 else
419 {
420 // It is a string value, which potentially includes escaped characters.
421 final StringBuilder buffer = new StringBuilder();
422 pos = readValueString(rdnString, pos, buffer);
423 value = new ASN1OctetString(buffer.toString());
424 }
425
426
427 // Skip over any spaces until we find a plus sign or the end of the value.
428 while ((pos < length) && (rdnString.charAt(pos) == ' '))
429 {
430 pos++;
431 }
432
433 if (pos >= length)
434 {
435 // It's a single-valued RDN, so we have everything that we need.
436 attributeNames = new String[] { attrName };
437 attributeValues = new ASN1OctetString[] { value };
438 return;
439 }
440
441 // It's a multivalued RDN, so create temporary lists to hold the names and
442 // values.
443 final ArrayList<String> nameList = new ArrayList<String>(5);
444 final ArrayList<ASN1OctetString> valueList =
445 new ArrayList<ASN1OctetString>(5);
446 nameList.add(attrName);
447 valueList.add(value);
448
449 if (rdnString.charAt(pos) == '+')
450 {
451 pos++;
452 }
453 else
454 {
455 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
456 ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get());
457 }
458
459 if (pos >= length)
460 {
461 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
462 ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get());
463 }
464
465 int numValues = 1;
466 while (pos < length)
467 {
468 // Skip over any spaces between the plus sign and the attribute name.
469 while ((pos < length) && (rdnString.charAt(pos) == ' '))
470 {
471 pos++;
472 }
473
474 attrStartPos = pos;
475 while (pos < length)
476 {
477 final char c = rdnString.charAt(pos);
478 if ((c == ' ') || (c == '='))
479 {
480 break;
481 }
482
483 pos++;
484 }
485
486 // Skip over any spaces between the attribute name and the equal sign.
487 attrName = rdnString.substring(attrStartPos, pos);
488 if (attrName.length() == 0)
489 {
490 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
491 ERR_RDN_NO_ATTR_NAME.get());
492 }
493
494 while ((pos < length) && (rdnString.charAt(pos) == ' '))
495 {
496 pos++;
497 }
498
499 if ((pos >= length) || (rdnString.charAt(pos) != '='))
500 {
501 // We didn't find an equal sign.
502 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
503 ERR_RDN_NO_EQUAL_SIGN.get(attrName));
504 }
505
506 // The next character is the equal sign. Skip it, and then skip over any
507 // spaces between it and the attribute value.
508 pos++;
509 while ((pos < length) && (rdnString.charAt(pos) == ' '))
510 {
511 pos++;
512 }
513
514 // Look at the next character. If it is an octothorpe (#), then the value
515 // must be hex-encoded. Otherwise, it's a regular string (although
516 // possibly containing escaped or quoted characters).
517 if (pos >= length)
518 {
519 value = new ASN1OctetString();
520 }
521 else if (rdnString.charAt(pos) == '#')
522 {
523 // It is a hex-encoded value, so we'll read until we find the end of the
524 // string or the first non-hex character, which must be either a space
525 // or a plus sign.
526 final byte[] valueArray = readHexString(rdnString, ++pos);
527 value = new ASN1OctetString(valueArray);
528 pos += (valueArray.length * 2);
529 }
530 else
531 {
532 // It is a string value, which potentially includes escaped characters.
533 final StringBuilder buffer = new StringBuilder();
534 pos = readValueString(rdnString, pos, buffer);
535 value = new ASN1OctetString(buffer.toString());
536 }
537
538
539 // Skip over any spaces until we find a plus sign or the end of the value.
540 while ((pos < length) && (rdnString.charAt(pos) == ' '))
541 {
542 pos++;
543 }
544
545 nameList.add(attrName);
546 valueList.add(value);
547 numValues++;
548
549 if (pos >= length)
550 {
551 // We're at the end of the value, so break out of the loop.
552 break;
553 }
554 else
555 {
556 // Skip over the plus sign and loop again to read another name-value
557 // pair.
558 if (rdnString.charAt(pos) == '+')
559 {
560 pos++;
561 }
562 else
563 {
564 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
565 ERR_RDN_VALUE_NOT_FOLLOWED_BY_PLUS.get());
566 }
567 }
568
569 if (pos >= length)
570 {
571 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
572 ERR_RDN_PLUS_NOT_FOLLOWED_BY_AVP.get());
573 }
574 }
575
576 attributeNames = new String[numValues];
577 attributeValues = new ASN1OctetString[numValues];
578 for (int i=0; i < numValues; i++)
579 {
580 attributeNames[i] = nameList.get(i);
581 attributeValues[i] = valueList.get(i);
582 }
583 }
584
585
586
587 /**
588 * Parses a hex-encoded RDN value from the provided string. Reading will
589 * continue until the end of the string is reached or a non-escaped plus sign
590 * is encountered. After returning, the caller should increment its position
591 * by two times the length of the value array.
592 *
593 * @param rdnString The string to be parsed. It should be the position
594 * immediately after the octothorpe at the start of the
595 * hex-encoded value.
596 * @param startPos The position at which to start reading the value.
597 *
598 * @return A byte array containing the parsed value.
599 *
600 * @throws LDAPException If an error occurs while reading the value (e.g.,
601 * if it contains non-hex characters, or has an odd
602 * number of characters.
603 */
604 static byte[] readHexString(final String rdnString, final int startPos)
605 throws LDAPException
606 {
607 final int length = rdnString.length();
608 int pos = startPos;
609
610 final ByteBuffer buffer = ByteBuffer.allocate(length-pos);
611 hexLoop:
612 while (pos < length)
613 {
614 byte hexByte;
615 switch (rdnString.charAt(pos++))
616 {
617 case '0':
618 hexByte = 0x00;
619 break;
620 case '1':
621 hexByte = 0x10;
622 break;
623 case '2':
624 hexByte = 0x20;
625 break;
626 case '3':
627 hexByte = 0x30;
628 break;
629 case '4':
630 hexByte = 0x40;
631 break;
632 case '5':
633 hexByte = 0x50;
634 break;
635 case '6':
636 hexByte = 0x60;
637 break;
638 case '7':
639 hexByte = 0x70;
640 break;
641 case '8':
642 hexByte = (byte) 0x80;
643 break;
644 case '9':
645 hexByte = (byte) 0x90;
646 break;
647 case 'a':
648 case 'A':
649 hexByte = (byte) 0xA0;
650 break;
651 case 'b':
652 case 'B':
653 hexByte = (byte) 0xB0;
654 break;
655 case 'c':
656 case 'C':
657 hexByte = (byte) 0xC0;
658 break;
659 case 'd':
660 case 'D':
661 hexByte = (byte) 0xD0;
662 break;
663 case 'e':
664 case 'E':
665 hexByte = (byte) 0xE0;
666 break;
667 case 'f':
668 case 'F':
669 hexByte = (byte) 0xF0;
670 break;
671 case ' ':
672 case '+':
673 case ',':
674 case ';':
675 // This indicates that we've reached the end of the hex string.
676 break hexLoop;
677 default:
678 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
679 ERR_RDN_INVALID_HEX_CHAR.get(
680 rdnString.charAt(pos-1), (pos-1)));
681 }
682
683 if (pos >= length)
684 {
685 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
686 ERR_RDN_MISSING_HEX_CHAR.get());
687 }
688
689 switch (rdnString.charAt(pos++))
690 {
691 case '0':
692 // No action is required.
693 break;
694 case '1':
695 hexByte |= 0x01;
696 break;
697 case '2':
698 hexByte |= 0x02;
699 break;
700 case '3':
701 hexByte |= 0x03;
702 break;
703 case '4':
704 hexByte |= 0x04;
705 break;
706 case '5':
707 hexByte |= 0x05;
708 break;
709 case '6':
710 hexByte |= 0x06;
711 break;
712 case '7':
713 hexByte |= 0x07;
714 break;
715 case '8':
716 hexByte |= 0x08;
717 break;
718 case '9':
719 hexByte |= 0x09;
720 break;
721 case 'a':
722 case 'A':
723 hexByte |= 0x0A;
724 break;
725 case 'b':
726 case 'B':
727 hexByte |= 0x0B;
728 break;
729 case 'c':
730 case 'C':
731 hexByte |= 0x0C;
732 break;
733 case 'd':
734 case 'D':
735 hexByte |= 0x0D;
736 break;
737 case 'e':
738 case 'E':
739 hexByte |= 0x0E;
740 break;
741 case 'f':
742 case 'F':
743 hexByte |= 0x0F;
744 break;
745 default:
746 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
747 ERR_RDN_INVALID_HEX_CHAR.get(
748 rdnString.charAt(pos-1), (pos-1)));
749 }
750
751 buffer.put(hexByte);
752 }
753
754 buffer.flip();
755 final byte[] valueArray = new byte[buffer.limit()];
756 buffer.get(valueArray);
757 return valueArray;
758 }
759
760
761
762 /**
763 * Reads a string value from the provided RDN string. Reading will continue
764 * until the end of the string is reached or until a non-escaped plus sign is
765 * encountered.
766 *
767 * @param rdnString The string from which to read the value.
768 * @param startPos The position in the RDN string at which to start reading
769 * the value.
770 * @param buffer The buffer into which the parsed value should be
771 * placed.
772 *
773 * @return The position at which the caller should continue reading when
774 * parsing the RDN.
775 *
776 * @throws LDAPException If a problem occurs while reading the value.
777 */
778 static int readValueString(final String rdnString, final int startPos,
779 final StringBuilder buffer)
780 throws LDAPException
781 {
782 final int bufferLength = buffer.length();
783 final int length = rdnString.length();
784 int pos = startPos;
785
786 boolean inQuotes = false;
787 valueLoop:
788 while (pos < length)
789 {
790 char c = rdnString.charAt(pos);
791 switch (c)
792 {
793 case '\\':
794 // It's an escaped value. It can either be followed by a single
795 // character (e.g., backslash, space, octothorpe, equals, double
796 // quote, plus sign, comma, semicolon, less than, or greater-than), or
797 // two hex digits. If it is followed by hex digits, then continue
798 // reading to see if there are more of them.
799 if ((pos+1) >= length)
800 {
801 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
802 ERR_RDN_ENDS_WITH_BACKSLASH.get());
803 }
804 else
805 {
806 pos++;
807 c = rdnString.charAt(pos);
808 if (isHex(c))
809 {
810 // We need to subtract one from the resulting position because
811 // it will be incremented later.
812 pos = readEscapedHexString(rdnString, pos, buffer) - 1;
813 }
814 else
815 {
816 buffer.append(c);
817 }
818 }
819 break;
820
821 case '"':
822 if (inQuotes)
823 {
824 // This should be the end of the value. If it's not, then fail.
825 pos++;
826 while (pos < length)
827 {
828 c = rdnString.charAt(pos);
829 if ((c == '+') || (c == ',') || (c == ';'))
830 {
831 break;
832 }
833 else if (c != ' ')
834 {
835 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
836 ERR_RDN_CHAR_OUTSIDE_QUOTES.get(c,
837 (pos-1)));
838 }
839
840 pos++;
841 }
842
843 inQuotes = false;
844 break valueLoop;
845 }
846 else
847 {
848 // This should be the first character of the value.
849 if (pos == startPos)
850 {
851 inQuotes = true;
852 }
853 else
854 {
855 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
856 ERR_RDN_UNEXPECTED_DOUBLE_QUOTE.get(pos));
857 }
858 }
859 break;
860
861 case ' ':
862 // We'll add this character if we're in quotes, or if the next
863 // character is not also a space.
864 if (inQuotes ||
865 (((pos+1) < length) && (rdnString.charAt(pos+1) != ' ')))
866 {
867 buffer.append(' ');
868 }
869 break;
870
871 case ',':
872 case ';':
873 case '+':
874 // This denotes the end of the value, if it's not in quotes.
875 if (inQuotes)
876 {
877 buffer.append(c);
878 }
879 else
880 {
881 break valueLoop;
882 }
883 break;
884
885 default:
886 // This is a normal character that should be added to the buffer.
887 buffer.append(c);
888 break;
889 }
890
891 pos++;
892 }
893
894
895 // If the value started with a quotation mark, then make sure it was closed.
896 if (inQuotes)
897 {
898 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
899 ERR_RDN_UNCLOSED_DOUBLE_QUOTE.get());
900 }
901
902
903 // If the value ends with any unescaped trailing spaces, then trim them off.
904 int bufferPos = buffer.length() - 1;
905 int rdnStrPos = pos - 2;
906 while ((bufferPos > 0) && (buffer.charAt(bufferPos) == ' '))
907 {
908 if (rdnString.charAt(rdnStrPos) == '\\')
909 {
910 break;
911 }
912 else
913 {
914 buffer.deleteCharAt(bufferPos--);
915 rdnStrPos--;
916 }
917 }
918
919 return pos;
920 }
921
922
923
924 /**
925 * Reads one or more hex-encoded bytes from the specified portion of the RDN
926 * string.
927 *
928 * @param rdnString The string from which the data is to be read.
929 * @param startPos The position at which to start reading. This should be
930 * the first hex character immediately after the initial
931 * backslash.
932 * @param buffer The buffer to which the decoded string portion should be
933 * appended.
934 *
935 * @return The position at which the caller may resume parsing.
936 *
937 * @throws LDAPException If a problem occurs while reading hex-encoded
938 * bytes.
939 */
940 private static int readEscapedHexString(final String rdnString,
941 final int startPos,
942 final StringBuilder buffer)
943 throws LDAPException
944 {
945 final int length = rdnString.length();
946 int pos = startPos;
947
948 final ByteBuffer byteBuffer = ByteBuffer.allocate(length - pos);
949 while (pos < length)
950 {
951 byte b;
952 switch (rdnString.charAt(pos++))
953 {
954 case '0':
955 b = 0x00;
956 break;
957 case '1':
958 b = 0x10;
959 break;
960 case '2':
961 b = 0x20;
962 break;
963 case '3':
964 b = 0x30;
965 break;
966 case '4':
967 b = 0x40;
968 break;
969 case '5':
970 b = 0x50;
971 break;
972 case '6':
973 b = 0x60;
974 break;
975 case '7':
976 b = 0x70;
977 break;
978 case '8':
979 b = (byte) 0x80;
980 break;
981 case '9':
982 b = (byte) 0x90;
983 break;
984 case 'a':
985 case 'A':
986 b = (byte) 0xA0;
987 break;
988 case 'b':
989 case 'B':
990 b = (byte) 0xB0;
991 break;
992 case 'c':
993 case 'C':
994 b = (byte) 0xC0;
995 break;
996 case 'd':
997 case 'D':
998 b = (byte) 0xD0;
999 break;
1000 case 'e':
1001 case 'E':
1002 b = (byte) 0xE0;
1003 break;
1004 case 'f':
1005 case 'F':
1006 b = (byte) 0xF0;
1007 break;
1008 default:
1009 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1010 ERR_RDN_INVALID_HEX_CHAR.get(
1011 rdnString.charAt(pos-1), (pos-1)));
1012 }
1013
1014 if (pos >= length)
1015 {
1016 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1017 ERR_RDN_MISSING_HEX_CHAR.get());
1018 }
1019
1020 switch (rdnString.charAt(pos++))
1021 {
1022 case '0':
1023 // No action is required.
1024 break;
1025 case '1':
1026 b |= 0x01;
1027 break;
1028 case '2':
1029 b |= 0x02;
1030 break;
1031 case '3':
1032 b |= 0x03;
1033 break;
1034 case '4':
1035 b |= 0x04;
1036 break;
1037 case '5':
1038 b |= 0x05;
1039 break;
1040 case '6':
1041 b |= 0x06;
1042 break;
1043 case '7':
1044 b |= 0x07;
1045 break;
1046 case '8':
1047 b |= 0x08;
1048 break;
1049 case '9':
1050 b |= 0x09;
1051 break;
1052 case 'a':
1053 case 'A':
1054 b |= 0x0A;
1055 break;
1056 case 'b':
1057 case 'B':
1058 b |= 0x0B;
1059 break;
1060 case 'c':
1061 case 'C':
1062 b |= 0x0C;
1063 break;
1064 case 'd':
1065 case 'D':
1066 b |= 0x0D;
1067 break;
1068 case 'e':
1069 case 'E':
1070 b |= 0x0E;
1071 break;
1072 case 'f':
1073 case 'F':
1074 b |= 0x0F;
1075 break;
1076 default:
1077 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1078 ERR_RDN_INVALID_HEX_CHAR.get(
1079 rdnString.charAt(pos-1), (pos-1)));
1080 }
1081
1082 byteBuffer.put(b);
1083 if (((pos+1) < length) && (rdnString.charAt(pos) == '\\') &&
1084 isHex(rdnString.charAt(pos+1)))
1085 {
1086 // It appears that there are more hex-encoded bytes to follow, so keep
1087 // reading.
1088 pos++;
1089 continue;
1090 }
1091 else
1092 {
1093 break;
1094 }
1095 }
1096
1097 byteBuffer.flip();
1098 final byte[] byteArray = new byte[byteBuffer.limit()];
1099 byteBuffer.get(byteArray);
1100
1101 try
1102 {
1103 buffer.append(toUTF8String(byteArray));
1104 }
1105 catch (final Exception e)
1106 {
1107 debugException(e);
1108 // This should never happen.
1109 buffer.append(new String(byteArray));
1110 }
1111
1112 return pos;
1113 }
1114
1115
1116
1117 /**
1118 * Indicates whether the provided string represents a valid RDN.
1119 *
1120 * @param s The string for which to make the determination. It must not be
1121 * {@code null}.
1122 *
1123 * @return {@code true} if the provided string represents a valid RDN, or
1124 * {@code false} if not.
1125 */
1126 public static boolean isValidRDN(final String s)
1127 {
1128 try
1129 {
1130 new RDN(s);
1131 return true;
1132 }
1133 catch (LDAPException le)
1134 {
1135 return false;
1136 }
1137 }
1138
1139
1140
1141 /**
1142 * Indicates whether this RDN contains multiple components.
1143 *
1144 * @return {@code true} if this RDN contains multiple components, or
1145 * {@code false} if not.
1146 */
1147 public boolean isMultiValued()
1148 {
1149 return (attributeNames.length != 1);
1150 }
1151
1152
1153
1154 /**
1155 * Retrieves an array of the attributes that comprise this RDN.
1156 *
1157 * @return An array of the attributes that comprise this RDN.
1158 */
1159 public Attribute[] getAttributes()
1160 {
1161 final Attribute[] attrs = new Attribute[attributeNames.length];
1162 for (int i=0; i < attrs.length; i++)
1163 {
1164 attrs[i] = new Attribute(attributeNames[i], schema,
1165 new ASN1OctetString[] { attributeValues[i] });
1166 }
1167
1168 return attrs;
1169 }
1170
1171
1172
1173 /**
1174 * Retrieves the set of attribute names for this RDN.
1175 *
1176 * @return The set of attribute names for this RDN.
1177 */
1178 public String[] getAttributeNames()
1179 {
1180 return attributeNames;
1181 }
1182
1183
1184
1185 /**
1186 * Retrieves the set of attribute values for this RDN.
1187 *
1188 * @return The set of attribute values for this RDN.
1189 */
1190 public String[] getAttributeValues()
1191 {
1192 final String[] stringValues = new String[attributeValues.length];
1193 for (int i=0; i < stringValues.length; i++)
1194 {
1195 stringValues[i] = attributeValues[i].stringValue();
1196 }
1197
1198 return stringValues;
1199 }
1200
1201
1202
1203 /**
1204 * Retrieves the set of attribute values for this RDN.
1205 *
1206 * @return The set of attribute values for this RDN.
1207 */
1208 public byte[][] getByteArrayAttributeValues()
1209 {
1210 final byte[][] byteValues = new byte[attributeValues.length][];
1211 for (int i=0; i < byteValues.length; i++)
1212 {
1213 byteValues[i] = attributeValues[i].getValue();
1214 }
1215
1216 return byteValues;
1217 }
1218
1219
1220
1221 /**
1222 * Retrieves the schema that will be used for this RDN, if any.
1223 *
1224 * @return The schema that will be used for this RDN, or {@code null} if none
1225 * has been provided.
1226 */
1227 Schema getSchema()
1228 {
1229 return schema;
1230 }
1231
1232
1233
1234 /**
1235 * Indicates whether this RDN contains the specified attribute.
1236 *
1237 * @param attributeName The name of the attribute for which to make the
1238 * determination.
1239 *
1240 * @return {@code true} if RDN contains the specified attribute, or
1241 * {@code false} if not.
1242 */
1243 public boolean hasAttribute(final String attributeName)
1244 {
1245 for (final String name : attributeNames)
1246 {
1247 if (name.equalsIgnoreCase(attributeName))
1248 {
1249 return true;
1250 }
1251 }
1252
1253 return false;
1254 }
1255
1256
1257
1258 /**
1259 * Indicates whether this RDN contains the specified attribute value.
1260 *
1261 * @param attributeName The name of the attribute for which to make the
1262 * determination.
1263 * @param attributeValue The attribute value for which to make the
1264 * determination.
1265 *
1266 * @return {@code true} if RDN contains the specified attribute, or
1267 * {@code false} if not.
1268 */
1269 public boolean hasAttributeValue(final String attributeName,
1270 final String attributeValue)
1271 {
1272 for (int i=0; i < attributeNames.length; i++)
1273 {
1274 if (attributeNames[i].equalsIgnoreCase(attributeName))
1275 {
1276 final Attribute a =
1277 new Attribute(attributeName, schema, attributeValue);
1278 final Attribute b = new Attribute(attributeName, schema,
1279 attributeValues[i].stringValue());
1280
1281 if (a.equals(b))
1282 {
1283 return true;
1284 }
1285 }
1286 }
1287
1288 return false;
1289 }
1290
1291
1292
1293 /**
1294 * Indicates whether this RDN contains the specified attribute value.
1295 *
1296 * @param attributeName The name of the attribute for which to make the
1297 * determination.
1298 * @param attributeValue The attribute value for which to make the
1299 * determination.
1300 *
1301 * @return {@code true} if RDN contains the specified attribute, or
1302 * {@code false} if not.
1303 */
1304 public boolean hasAttributeValue(final String attributeName,
1305 final byte[] attributeValue)
1306 {
1307 for (int i=0; i < attributeNames.length; i++)
1308 {
1309 if (attributeNames[i].equalsIgnoreCase(attributeName))
1310 {
1311 final Attribute a =
1312 new Attribute(attributeName, schema, attributeValue);
1313 final Attribute b = new Attribute(attributeName, schema,
1314 attributeValues[i].getValue());
1315
1316 if (a.equals(b))
1317 {
1318 return true;
1319 }
1320 }
1321 }
1322
1323 return false;
1324 }
1325
1326
1327
1328 /**
1329 * Retrieves a string representation of this RDN.
1330 *
1331 * @return A string representation of this RDN.
1332 */
1333 @Override()
1334 public String toString()
1335 {
1336 if (rdnString == null)
1337 {
1338 final StringBuilder buffer = new StringBuilder();
1339 toString(buffer, false);
1340 rdnString = buffer.toString();
1341 }
1342
1343 return rdnString;
1344 }
1345
1346
1347
1348 /**
1349 * Retrieves a string representation of this RDN with minimal encoding for
1350 * special characters. Only those characters specified in RFC 4514 section
1351 * 2.4 will be escaped. No escaping will be used for non-ASCII characters or
1352 * non-printable ASCII characters.
1353 *
1354 * @return A string representation of this RDN with minimal encoding for
1355 * special characters.
1356 */
1357 public String toMinimallyEncodedString()
1358 {
1359 final StringBuilder buffer = new StringBuilder();
1360 toString(buffer, true);
1361 return buffer.toString();
1362 }
1363
1364
1365
1366 /**
1367 * Appends a string representation of this RDN to the provided buffer.
1368 *
1369 * @param buffer The buffer to which the string representation is to be
1370 * appended.
1371 */
1372 public void toString(final StringBuilder buffer)
1373 {
1374 toString(buffer, false);
1375 }
1376
1377
1378
1379 /**
1380 * Appends a string representation of this RDN to the provided buffer.
1381 *
1382 * @param buffer The buffer to which the string representation is
1383 * to be appended.
1384 * @param minimizeEncoding Indicates whether to restrict the encoding of
1385 * special characters to the bare minimum required
1386 * by LDAP (as per RFC 4514 section 2.4). If this
1387 * is {@code true}, then only leading and trailing
1388 * spaces, double quotes, plus signs, commas,
1389 * semicolons, greater-than, less-than, and
1390 * backslash characters will be encoded.
1391 */
1392 public void toString(final StringBuilder buffer,
1393 final boolean minimizeEncoding)
1394 {
1395 if ((rdnString != null) && (! minimizeEncoding))
1396 {
1397 buffer.append(rdnString);
1398 return;
1399 }
1400
1401 for (int i=0; i < attributeNames.length; i++)
1402 {
1403 if (i > 0)
1404 {
1405 buffer.append('+');
1406 }
1407
1408 buffer.append(attributeNames[i]);
1409 buffer.append('=');
1410
1411 // Iterate through the value character-by-character and do any escaping
1412 // that may be necessary.
1413 final String valueString = attributeValues[i].stringValue();
1414 final int length = valueString.length();
1415 for (int j=0; j < length; j++)
1416 {
1417 final char c = valueString.charAt(j);
1418 switch (c)
1419 {
1420 case '\\':
1421 case '#':
1422 case '=':
1423 case '"':
1424 case '+':
1425 case ',':
1426 case ';':
1427 case '<':
1428 case '>':
1429 buffer.append('\\');
1430 buffer.append(c);
1431 break;
1432
1433 case ' ':
1434 // Escape this space only if it's the first character, the last
1435 // character, or if the next character is also a space.
1436 if ((j == 0) || ((j+1) == length) ||
1437 (((j+1) < length) && (valueString.charAt(j+1) == ' ')))
1438 {
1439 buffer.append("\\ ");
1440 }
1441 else
1442 {
1443 buffer.append(' ');
1444 }
1445 break;
1446
1447 case '\u0000':
1448 buffer.append("\\00");
1449 break;
1450
1451 default:
1452 // If it's not a printable ASCII character, then hex-encode it
1453 // unless we're using minimized encoding.
1454 if ((! minimizeEncoding) && ((c < ' ') || (c > '~')))
1455 {
1456 hexEncode(c, buffer);
1457 }
1458 else
1459 {
1460 buffer.append(c);
1461 }
1462 break;
1463 }
1464 }
1465 }
1466 }
1467
1468
1469
1470 /**
1471 * Retrieves a normalized string representation of this RDN.
1472 *
1473 * @return A normalized string representation of this RDN.
1474 */
1475 public String toNormalizedString()
1476 {
1477 if (normalizedString == null)
1478 {
1479 final StringBuilder buffer = new StringBuilder();
1480 toNormalizedString(buffer);
1481 normalizedString = buffer.toString();
1482 }
1483
1484 return normalizedString;
1485 }
1486
1487
1488
1489 /**
1490 * Appends a normalized string representation of this RDN to the provided
1491 * buffer.
1492 *
1493 * @param buffer The buffer to which the normalized string representation is
1494 * to be appended.
1495 */
1496 public void toNormalizedString(final StringBuilder buffer)
1497 {
1498 if (attributeNames.length == 1)
1499 {
1500 // It's a single-valued RDN, so there is no need to sort anything.
1501 final String name = normalizeAttrName(attributeNames[0]);
1502 buffer.append(name);
1503 buffer.append('=');
1504 buffer.append(normalizeValue(name, attributeValues[0]));
1505 }
1506 else
1507 {
1508 // It's a multivalued RDN, so we need to sort the components.
1509 final TreeMap<String,ASN1OctetString> valueMap =
1510 new TreeMap<String,ASN1OctetString>();
1511 for (int i=0; i < attributeNames.length; i++)
1512 {
1513 final String name = normalizeAttrName(attributeNames[i]);
1514 valueMap.put(name, attributeValues[i]);
1515 }
1516
1517 int i=0;
1518 for (final Map.Entry<String,ASN1OctetString> entry : valueMap.entrySet())
1519 {
1520 if (i++ > 0)
1521 {
1522 buffer.append('+');
1523 }
1524
1525 buffer.append(entry.getKey());
1526 buffer.append('=');
1527 buffer.append(normalizeValue(entry.getKey(), entry.getValue()));
1528 }
1529 }
1530 }
1531
1532
1533
1534 /**
1535 * Obtains a normalized representation of the provided attribute name.
1536 *
1537 * @param name The name of the attribute for which to create the normalized
1538 * representation.
1539 *
1540 * @return A normalized representation of the provided attribute name.
1541 */
1542 private String normalizeAttrName(final String name)
1543 {
1544 String n = name;
1545 if (schema != null)
1546 {
1547 final AttributeTypeDefinition at = schema.getAttributeType(name);
1548 if (at != null)
1549 {
1550 n = at.getNameOrOID();
1551 }
1552 }
1553 return toLowerCase(n);
1554 }
1555
1556
1557
1558 /**
1559 * Retrieves a normalized string representation of the RDN with the provided
1560 * string representation.
1561 *
1562 * @param s The string representation of the RDN to normalize. It must not
1563 * be {@code null}.
1564 *
1565 * @return The normalized string representation of the RDN with the provided
1566 * string representation.
1567 *
1568 * @throws LDAPException If the provided string cannot be parsed as an RDN.
1569 */
1570 public static String normalize(final String s)
1571 throws LDAPException
1572 {
1573 return normalize(s, null);
1574 }
1575
1576
1577
1578 /**
1579 * Retrieves a normalized string representation of the RDN with the provided
1580 * string representation.
1581 *
1582 * @param s The string representation of the RDN to normalize. It must
1583 * not be {@code null}.
1584 * @param schema The schema to use to generate the normalized string
1585 * representation of the RDN. It may be {@code null} if no
1586 * schema is available.
1587 *
1588 * @return The normalized string representation of the RDN with the provided
1589 * string representation.
1590 *
1591 * @throws LDAPException If the provided string cannot be parsed as an RDN.
1592 */
1593 public static String normalize(final String s, final Schema schema)
1594 throws LDAPException
1595 {
1596 return new RDN(s, schema).toNormalizedString();
1597 }
1598
1599
1600
1601 /**
1602 * Normalizes the provided attribute value for use in an RDN.
1603 *
1604 * @param attributeName The name of the attribute with which the value is
1605 * associated.
1606 * @param value The value to be normalized.
1607 *
1608 * @return A string builder containing a normalized representation of the
1609 * value in a suitable form for inclusion in an RDN.
1610 */
1611 private StringBuilder normalizeValue(final String attributeName,
1612 final ASN1OctetString value)
1613 {
1614 final MatchingRule matchingRule =
1615 MatchingRule.selectEqualityMatchingRule(attributeName, schema);
1616
1617 ASN1OctetString rawNormValue;
1618 try
1619 {
1620 rawNormValue = matchingRule.normalize(value);
1621 }
1622 catch (final Exception e)
1623 {
1624 debugException(e);
1625 rawNormValue =
1626 new ASN1OctetString(toLowerCase(value.stringValue()));
1627 }
1628
1629 final String valueString = rawNormValue.stringValue();
1630 final int length = valueString.length();
1631 final StringBuilder buffer = new StringBuilder(length);
1632
1633 for (int i=0; i < length; i++)
1634 {
1635 final char c = valueString.charAt(i);
1636
1637 switch (c)
1638 {
1639 case '\\':
1640 case '#':
1641 case '=':
1642 case '"':
1643 case '+':
1644 case ',':
1645 case ';':
1646 case '<':
1647 case '>':
1648 buffer.append('\\');
1649 buffer.append(c);
1650 break;
1651
1652 case ' ':
1653 // Escape this space only if it's the first character, the last
1654 // character, or if the next character is also a space.
1655 if ((i == 0) || ((i+1) == length) ||
1656 (((i+1) < length) && (valueString.charAt(i+1) == ' ')))
1657 {
1658 buffer.append("\\ ");
1659 }
1660 else
1661 {
1662 buffer.append(' ');
1663 }
1664 break;
1665
1666 default:
1667 // If it's not a printable ASCII character, then hex-encode it.
1668 if ((c < ' ') || (c > '~'))
1669 {
1670 hexEncode(c, buffer);
1671 }
1672 else
1673 {
1674 buffer.append(c);
1675 }
1676 break;
1677 }
1678 }
1679
1680 return buffer;
1681 }
1682
1683
1684
1685 /**
1686 * Retrieves a hash code for this RDN.
1687 *
1688 * @return The hash code for this RDN.
1689 */
1690 @Override()
1691 public int hashCode()
1692 {
1693 return toNormalizedString().hashCode();
1694 }
1695
1696
1697
1698 /**
1699 * Indicates whether this RDN is equal to the provided object. The given
1700 * object will only be considered equal to this RDN if it is also an RDN with
1701 * the same set of names and values.
1702 *
1703 * @param o The object for which to make the determination.
1704 *
1705 * @return {@code true} if the provided object can be considered equal to
1706 * this RDN, or {@code false} if not.
1707 */
1708 @Override()
1709 public boolean equals(final Object o)
1710 {
1711 if (o == null)
1712 {
1713 return false;
1714 }
1715
1716 if (o == this)
1717 {
1718 return true;
1719 }
1720
1721 if (! (o instanceof RDN))
1722 {
1723 return false;
1724 }
1725
1726 final RDN rdn = (RDN) o;
1727 return (toNormalizedString().equals(rdn.toNormalizedString()));
1728 }
1729
1730
1731
1732 /**
1733 * Indicates whether the RDN with the provided string representation is equal
1734 * to this RDN.
1735 *
1736 * @param s The string representation of the DN to compare with this RDN.
1737 *
1738 * @return {@code true} if the DN with the provided string representation is
1739 * equal to this RDN, or {@code false} if not.
1740 *
1741 * @throws LDAPException If the provided string cannot be parsed as an RDN.
1742 */
1743 public boolean equals(final String s)
1744 throws LDAPException
1745 {
1746 if (s == null)
1747 {
1748 return false;
1749 }
1750
1751 return equals(new RDN(s, schema));
1752 }
1753
1754
1755
1756 /**
1757 * Indicates whether the two provided strings represent the same RDN.
1758 *
1759 * @param s1 The string representation of the first RDN for which to make
1760 * the determination. It must not be {@code null}.
1761 * @param s2 The string representation of the second RDN for which to make
1762 * the determination. It must not be {@code null}.
1763 *
1764 * @return {@code true} if the provided strings represent the same RDN, or
1765 * {@code false} if not.
1766 *
1767 * @throws LDAPException If either of the provided strings cannot be parsed
1768 * as an RDN.
1769 */
1770 public static boolean equals(final String s1, final String s2)
1771 throws LDAPException
1772 {
1773 return new RDN(s1).equals(new RDN(s2));
1774 }
1775
1776
1777
1778 /**
1779 * Compares the provided RDN to this RDN to determine their relative order in
1780 * a sorted list.
1781 *
1782 * @param rdn The RDN to compare against this RDN. It must not be
1783 * {@code null}.
1784 *
1785 * @return A negative integer if this RDN should come before the provided RDN
1786 * in a sorted list, a positive integer if this RDN should come after
1787 * the provided RDN in a sorted list, or zero if the provided RDN
1788 * can be considered equal to this RDN.
1789 */
1790 public int compareTo(final RDN rdn)
1791 {
1792 return compare(this, rdn);
1793 }
1794
1795
1796
1797 /**
1798 * Compares the provided RDN values to determine their relative order in a
1799 * sorted list.
1800 *
1801 * @param rdn1 The first RDN to be compared. It must not be {@code null}.
1802 * @param rdn2 The second RDN to be compared. It must not be {@code null}.
1803 *
1804 * @return A negative integer if the first RDN should come before the second
1805 * RDN in a sorted list, a positive integer if the first RDN should
1806 * come after the second RDN in a sorted list, or zero if the two RDN
1807 * values can be considered equal.
1808 */
1809 public int compare(final RDN rdn1, final RDN rdn2)
1810 {
1811 ensureNotNull(rdn1, rdn2);
1812
1813 return(rdn1.toNormalizedString().compareTo(rdn2.toNormalizedString()));
1814 }
1815
1816
1817
1818 /**
1819 * Compares the RDN values with the provided string representations to
1820 * determine their relative order in a sorted list.
1821 *
1822 * @param s1 The string representation of the first RDN to be compared. It
1823 * must not be {@code null}.
1824 * @param s2 The string representation of the second RDN to be compared. It
1825 * must not be {@code null}.
1826 *
1827 * @return A negative integer if the first RDN should come before the second
1828 * RDN in a sorted list, a positive integer if the first RDN should
1829 * come after the second RDN in a sorted list, or zero if the two RDN
1830 * values can be considered equal.
1831 *
1832 * @throws LDAPException If either of the provided strings cannot be parsed
1833 * as an RDN.
1834 */
1835 public static int compare(final String s1, final String s2)
1836 throws LDAPException
1837 {
1838 return compare(s1, s2, null);
1839 }
1840
1841
1842
1843 /**
1844 * Compares the RDN values with the provided string representations to
1845 * determine their relative order in a sorted list.
1846 *
1847 * @param s1 The string representation of the first RDN to be compared.
1848 * It must not be {@code null}.
1849 * @param s2 The string representation of the second RDN to be compared.
1850 * It must not be {@code null}.
1851 * @param schema The schema to use to generate the normalized string
1852 * representations of the RDNs. It may be {@code null} if no
1853 * schema is available.
1854 *
1855 * @return A negative integer if the first RDN should come before the second
1856 * RDN in a sorted list, a positive integer if the first RDN should
1857 * come after the second RDN in a sorted list, or zero if the two RDN
1858 * values can be considered equal.
1859 *
1860 * @throws LDAPException If either of the provided strings cannot be parsed
1861 * as an RDN.
1862 */
1863 public static int compare(final String s1, final String s2,
1864 final Schema schema)
1865 throws LDAPException
1866 {
1867 return new RDN(s1, schema).compareTo(new RDN(s2, schema));
1868 }
1869 }