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