001 /*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2017 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 length = rdnString.length();
788 int pos = startPos;
789
790 boolean inQuotes = false;
791 valueLoop:
792 while (pos < length)
793 {
794 char c = rdnString.charAt(pos);
795 switch (c)
796 {
797 case '\\':
798 // It's an escaped value. It can either be followed by a single
799 // character (e.g., backslash, space, octothorpe, equals, double
800 // quote, plus sign, comma, semicolon, less than, or greater-than), or
801 // two hex digits. If it is followed by hex digits, then continue
802 // reading to see if there are more of them.
803 if ((pos+1) >= length)
804 {
805 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
806 ERR_RDN_ENDS_WITH_BACKSLASH.get());
807 }
808 else
809 {
810 pos++;
811 c = rdnString.charAt(pos);
812 if (isHex(c))
813 {
814 // We need to subtract one from the resulting position because
815 // it will be incremented later.
816 pos = readEscapedHexString(rdnString, pos, buffer) - 1;
817 }
818 else
819 {
820 buffer.append(c);
821 }
822 }
823 break;
824
825 case '"':
826 if (inQuotes)
827 {
828 // This should be the end of the value. If it's not, then fail.
829 pos++;
830 while (pos < length)
831 {
832 c = rdnString.charAt(pos);
833 if ((c == '+') || (c == ',') || (c == ';'))
834 {
835 break;
836 }
837 else if (c != ' ')
838 {
839 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
840 ERR_RDN_CHAR_OUTSIDE_QUOTES.get(c,
841 (pos-1)));
842 }
843
844 pos++;
845 }
846
847 inQuotes = false;
848 break valueLoop;
849 }
850 else
851 {
852 // This should be the first character of the value.
853 if (pos == startPos)
854 {
855 inQuotes = true;
856 }
857 else
858 {
859 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
860 ERR_RDN_UNEXPECTED_DOUBLE_QUOTE.get(pos));
861 }
862 }
863 break;
864
865 case ',':
866 case ';':
867 case '+':
868 // This denotes the end of the value, if it's not in quotes.
869 if (inQuotes)
870 {
871 buffer.append(c);
872 }
873 else
874 {
875 break valueLoop;
876 }
877 break;
878
879 default:
880 // This is a normal character that should be added to the buffer.
881 buffer.append(c);
882 break;
883 }
884
885 pos++;
886 }
887
888
889 // If the value started with a quotation mark, then make sure it was closed.
890 if (inQuotes)
891 {
892 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
893 ERR_RDN_UNCLOSED_DOUBLE_QUOTE.get());
894 }
895
896
897 // If the value ends with any unescaped trailing spaces, then trim them off.
898 int bufferPos = buffer.length() - 1;
899 int rdnStrPos = pos - 2;
900 while ((bufferPos > 0) && (buffer.charAt(bufferPos) == ' '))
901 {
902 if (rdnString.charAt(rdnStrPos) == '\\')
903 {
904 break;
905 }
906 else
907 {
908 buffer.deleteCharAt(bufferPos--);
909 rdnStrPos--;
910 }
911 }
912
913 return pos;
914 }
915
916
917
918 /**
919 * Reads one or more hex-encoded bytes from the specified portion of the RDN
920 * string.
921 *
922 * @param rdnString The string from which the data is to be read.
923 * @param startPos The position at which to start reading. This should be
924 * the first hex character immediately after the initial
925 * backslash.
926 * @param buffer The buffer to which the decoded string portion should be
927 * appended.
928 *
929 * @return The position at which the caller may resume parsing.
930 *
931 * @throws LDAPException If a problem occurs while reading hex-encoded
932 * bytes.
933 */
934 private static int readEscapedHexString(final String rdnString,
935 final int startPos,
936 final StringBuilder buffer)
937 throws LDAPException
938 {
939 final int length = rdnString.length();
940 int pos = startPos;
941
942 final ByteBuffer byteBuffer = ByteBuffer.allocate(length - pos);
943 while (pos < length)
944 {
945 byte b;
946 switch (rdnString.charAt(pos++))
947 {
948 case '0':
949 b = 0x00;
950 break;
951 case '1':
952 b = 0x10;
953 break;
954 case '2':
955 b = 0x20;
956 break;
957 case '3':
958 b = 0x30;
959 break;
960 case '4':
961 b = 0x40;
962 break;
963 case '5':
964 b = 0x50;
965 break;
966 case '6':
967 b = 0x60;
968 break;
969 case '7':
970 b = 0x70;
971 break;
972 case '8':
973 b = (byte) 0x80;
974 break;
975 case '9':
976 b = (byte) 0x90;
977 break;
978 case 'a':
979 case 'A':
980 b = (byte) 0xA0;
981 break;
982 case 'b':
983 case 'B':
984 b = (byte) 0xB0;
985 break;
986 case 'c':
987 case 'C':
988 b = (byte) 0xC0;
989 break;
990 case 'd':
991 case 'D':
992 b = (byte) 0xD0;
993 break;
994 case 'e':
995 case 'E':
996 b = (byte) 0xE0;
997 break;
998 case 'f':
999 case 'F':
1000 b = (byte) 0xF0;
1001 break;
1002 default:
1003 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1004 ERR_RDN_INVALID_HEX_CHAR.get(
1005 rdnString.charAt(pos-1), (pos-1)));
1006 }
1007
1008 if (pos >= length)
1009 {
1010 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1011 ERR_RDN_MISSING_HEX_CHAR.get());
1012 }
1013
1014 switch (rdnString.charAt(pos++))
1015 {
1016 case '0':
1017 // No action is required.
1018 break;
1019 case '1':
1020 b |= 0x01;
1021 break;
1022 case '2':
1023 b |= 0x02;
1024 break;
1025 case '3':
1026 b |= 0x03;
1027 break;
1028 case '4':
1029 b |= 0x04;
1030 break;
1031 case '5':
1032 b |= 0x05;
1033 break;
1034 case '6':
1035 b |= 0x06;
1036 break;
1037 case '7':
1038 b |= 0x07;
1039 break;
1040 case '8':
1041 b |= 0x08;
1042 break;
1043 case '9':
1044 b |= 0x09;
1045 break;
1046 case 'a':
1047 case 'A':
1048 b |= 0x0A;
1049 break;
1050 case 'b':
1051 case 'B':
1052 b |= 0x0B;
1053 break;
1054 case 'c':
1055 case 'C':
1056 b |= 0x0C;
1057 break;
1058 case 'd':
1059 case 'D':
1060 b |= 0x0D;
1061 break;
1062 case 'e':
1063 case 'E':
1064 b |= 0x0E;
1065 break;
1066 case 'f':
1067 case 'F':
1068 b |= 0x0F;
1069 break;
1070 default:
1071 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
1072 ERR_RDN_INVALID_HEX_CHAR.get(
1073 rdnString.charAt(pos-1), (pos-1)));
1074 }
1075
1076 byteBuffer.put(b);
1077 if (((pos+1) < length) && (rdnString.charAt(pos) == '\\') &&
1078 isHex(rdnString.charAt(pos+1)))
1079 {
1080 // It appears that there are more hex-encoded bytes to follow, so keep
1081 // reading.
1082 pos++;
1083 continue;
1084 }
1085 else
1086 {
1087 break;
1088 }
1089 }
1090
1091 byteBuffer.flip();
1092 final byte[] byteArray = new byte[byteBuffer.limit()];
1093 byteBuffer.get(byteArray);
1094
1095 try
1096 {
1097 buffer.append(toUTF8String(byteArray));
1098 }
1099 catch (final Exception e)
1100 {
1101 debugException(e);
1102 // This should never happen.
1103 buffer.append(new String(byteArray));
1104 }
1105
1106 return pos;
1107 }
1108
1109
1110
1111 /**
1112 * Indicates whether the provided string represents a valid RDN.
1113 *
1114 * @param s The string for which to make the determination. It must not be
1115 * {@code null}.
1116 *
1117 * @return {@code true} if the provided string represents a valid RDN, or
1118 * {@code false} if not.
1119 */
1120 public static boolean isValidRDN(final String s)
1121 {
1122 try
1123 {
1124 new RDN(s);
1125 return true;
1126 }
1127 catch (LDAPException le)
1128 {
1129 return false;
1130 }
1131 }
1132
1133
1134
1135 /**
1136 * Indicates whether this RDN contains multiple components.
1137 *
1138 * @return {@code true} if this RDN contains multiple components, or
1139 * {@code false} if not.
1140 */
1141 public boolean isMultiValued()
1142 {
1143 return (attributeNames.length != 1);
1144 }
1145
1146
1147
1148 /**
1149 * Retrieves an array of the attributes that comprise this RDN.
1150 *
1151 * @return An array of the attributes that comprise this RDN.
1152 */
1153 public Attribute[] getAttributes()
1154 {
1155 final Attribute[] attrs = new Attribute[attributeNames.length];
1156 for (int i=0; i < attrs.length; i++)
1157 {
1158 attrs[i] = new Attribute(attributeNames[i], schema,
1159 new ASN1OctetString[] { attributeValues[i] });
1160 }
1161
1162 return attrs;
1163 }
1164
1165
1166
1167 /**
1168 * Retrieves the set of attribute names for this RDN.
1169 *
1170 * @return The set of attribute names for this RDN.
1171 */
1172 public String[] getAttributeNames()
1173 {
1174 return attributeNames;
1175 }
1176
1177
1178
1179 /**
1180 * Retrieves the set of attribute values for this RDN.
1181 *
1182 * @return The set of attribute values for this RDN.
1183 */
1184 public String[] getAttributeValues()
1185 {
1186 final String[] stringValues = new String[attributeValues.length];
1187 for (int i=0; i < stringValues.length; i++)
1188 {
1189 stringValues[i] = attributeValues[i].stringValue();
1190 }
1191
1192 return stringValues;
1193 }
1194
1195
1196
1197 /**
1198 * Retrieves the set of attribute values for this RDN.
1199 *
1200 * @return The set of attribute values for this RDN.
1201 */
1202 public byte[][] getByteArrayAttributeValues()
1203 {
1204 final byte[][] byteValues = new byte[attributeValues.length][];
1205 for (int i=0; i < byteValues.length; i++)
1206 {
1207 byteValues[i] = attributeValues[i].getValue();
1208 }
1209
1210 return byteValues;
1211 }
1212
1213
1214
1215 /**
1216 * Retrieves the schema that will be used for this RDN, if any.
1217 *
1218 * @return The schema that will be used for this RDN, or {@code null} if none
1219 * has been provided.
1220 */
1221 Schema getSchema()
1222 {
1223 return schema;
1224 }
1225
1226
1227
1228 /**
1229 * Indicates whether this RDN contains the specified attribute.
1230 *
1231 * @param attributeName The name of the attribute for which to make the
1232 * determination.
1233 *
1234 * @return {@code true} if RDN contains the specified attribute, or
1235 * {@code false} if not.
1236 */
1237 public boolean hasAttribute(final String attributeName)
1238 {
1239 for (final String name : attributeNames)
1240 {
1241 if (name.equalsIgnoreCase(attributeName))
1242 {
1243 return true;
1244 }
1245 }
1246
1247 return false;
1248 }
1249
1250
1251
1252 /**
1253 * Indicates whether this RDN contains the specified attribute value.
1254 *
1255 * @param attributeName The name of the attribute for which to make the
1256 * determination.
1257 * @param attributeValue The attribute value for which to make the
1258 * determination.
1259 *
1260 * @return {@code true} if RDN contains the specified attribute, or
1261 * {@code false} if not.
1262 */
1263 public boolean hasAttributeValue(final String attributeName,
1264 final String attributeValue)
1265 {
1266 for (int i=0; i < attributeNames.length; i++)
1267 {
1268 if (attributeNames[i].equalsIgnoreCase(attributeName))
1269 {
1270 final Attribute a =
1271 new Attribute(attributeName, schema, attributeValue);
1272 final Attribute b = new Attribute(attributeName, schema,
1273 attributeValues[i].stringValue());
1274
1275 if (a.equals(b))
1276 {
1277 return true;
1278 }
1279 }
1280 }
1281
1282 return false;
1283 }
1284
1285
1286
1287 /**
1288 * Indicates whether this RDN contains the specified attribute value.
1289 *
1290 * @param attributeName The name of the attribute for which to make the
1291 * determination.
1292 * @param attributeValue The attribute value for which to make the
1293 * determination.
1294 *
1295 * @return {@code true} if RDN contains the specified attribute, or
1296 * {@code false} if not.
1297 */
1298 public boolean hasAttributeValue(final String attributeName,
1299 final byte[] attributeValue)
1300 {
1301 for (int i=0; i < attributeNames.length; i++)
1302 {
1303 if (attributeNames[i].equalsIgnoreCase(attributeName))
1304 {
1305 final Attribute a =
1306 new Attribute(attributeName, schema, attributeValue);
1307 final Attribute b = new Attribute(attributeName, schema,
1308 attributeValues[i].getValue());
1309
1310 if (a.equals(b))
1311 {
1312 return true;
1313 }
1314 }
1315 }
1316
1317 return false;
1318 }
1319
1320
1321
1322 /**
1323 * Retrieves a string representation of this RDN.
1324 *
1325 * @return A string representation of this RDN.
1326 */
1327 @Override()
1328 public String toString()
1329 {
1330 if (rdnString == null)
1331 {
1332 final StringBuilder buffer = new StringBuilder();
1333 toString(buffer, false);
1334 rdnString = buffer.toString();
1335 }
1336
1337 return rdnString;
1338 }
1339
1340
1341
1342 /**
1343 * Retrieves a string representation of this RDN with minimal encoding for
1344 * special characters. Only those characters specified in RFC 4514 section
1345 * 2.4 will be escaped. No escaping will be used for non-ASCII characters or
1346 * non-printable ASCII characters.
1347 *
1348 * @return A string representation of this RDN with minimal encoding for
1349 * special characters.
1350 */
1351 public String toMinimallyEncodedString()
1352 {
1353 final StringBuilder buffer = new StringBuilder();
1354 toString(buffer, true);
1355 return buffer.toString();
1356 }
1357
1358
1359
1360 /**
1361 * Appends a string representation of this RDN to the provided buffer.
1362 *
1363 * @param buffer The buffer to which the string representation is to be
1364 * appended.
1365 */
1366 public void toString(final StringBuilder buffer)
1367 {
1368 toString(buffer, false);
1369 }
1370
1371
1372
1373 /**
1374 * Appends a string representation of this RDN to the provided buffer.
1375 *
1376 * @param buffer The buffer to which the string representation is
1377 * to be appended.
1378 * @param minimizeEncoding Indicates whether to restrict the encoding of
1379 * special characters to the bare minimum required
1380 * by LDAP (as per RFC 4514 section 2.4). If this
1381 * is {@code true}, then only leading and trailing
1382 * spaces, double quotes, plus signs, commas,
1383 * semicolons, greater-than, less-than, and
1384 * backslash characters will be encoded.
1385 */
1386 public void toString(final StringBuilder buffer,
1387 final boolean minimizeEncoding)
1388 {
1389 if ((rdnString != null) && (! minimizeEncoding))
1390 {
1391 buffer.append(rdnString);
1392 return;
1393 }
1394
1395 for (int i=0; i < attributeNames.length; i++)
1396 {
1397 if (i > 0)
1398 {
1399 buffer.append('+');
1400 }
1401
1402 buffer.append(attributeNames[i]);
1403 buffer.append('=');
1404
1405 // Iterate through the value character-by-character and do any escaping
1406 // that may be necessary.
1407 final String valueString = attributeValues[i].stringValue();
1408 final int length = valueString.length();
1409 for (int j=0; j < length; j++)
1410 {
1411 final char c = valueString.charAt(j);
1412 switch (c)
1413 {
1414 case '\\':
1415 case '#':
1416 case '=':
1417 case '"':
1418 case '+':
1419 case ',':
1420 case ';':
1421 case '<':
1422 case '>':
1423 buffer.append('\\');
1424 buffer.append(c);
1425 break;
1426
1427 case ' ':
1428 // Escape this space only if it's the first character, the last
1429 // character, or if the next character is also a space.
1430 if ((j == 0) || ((j+1) == length) ||
1431 (((j+1) < length) && (valueString.charAt(j+1) == ' ')))
1432 {
1433 buffer.append("\\ ");
1434 }
1435 else
1436 {
1437 buffer.append(' ');
1438 }
1439 break;
1440
1441 case '\u0000':
1442 buffer.append("\\00");
1443 break;
1444
1445 default:
1446 // If it's not a printable ASCII character, then hex-encode it
1447 // unless we're using minimized encoding.
1448 if ((! minimizeEncoding) && ((c < ' ') || (c > '~')))
1449 {
1450 hexEncode(c, buffer);
1451 }
1452 else
1453 {
1454 buffer.append(c);
1455 }
1456 break;
1457 }
1458 }
1459 }
1460 }
1461
1462
1463
1464 /**
1465 * Retrieves a normalized string representation of this RDN.
1466 *
1467 * @return A normalized string representation of this RDN.
1468 */
1469 public String toNormalizedString()
1470 {
1471 if (normalizedString == null)
1472 {
1473 final StringBuilder buffer = new StringBuilder();
1474 toNormalizedString(buffer);
1475 normalizedString = buffer.toString();
1476 }
1477
1478 return normalizedString;
1479 }
1480
1481
1482
1483 /**
1484 * Appends a normalized string representation of this RDN to the provided
1485 * buffer.
1486 *
1487 * @param buffer The buffer to which the normalized string representation is
1488 * to be appended.
1489 */
1490 public void toNormalizedString(final StringBuilder buffer)
1491 {
1492 if (attributeNames.length == 1)
1493 {
1494 // It's a single-valued RDN, so there is no need to sort anything.
1495 final String name = normalizeAttrName(attributeNames[0]);
1496 buffer.append(name);
1497 buffer.append('=');
1498 buffer.append(normalizeValue(name, attributeValues[0]));
1499 }
1500 else
1501 {
1502 // It's a multivalued RDN, so we need to sort the components.
1503 final TreeMap<String,ASN1OctetString> valueMap =
1504 new TreeMap<String,ASN1OctetString>();
1505 for (int i=0; i < attributeNames.length; i++)
1506 {
1507 final String name = normalizeAttrName(attributeNames[i]);
1508 valueMap.put(name, attributeValues[i]);
1509 }
1510
1511 int i=0;
1512 for (final Map.Entry<String,ASN1OctetString> entry : valueMap.entrySet())
1513 {
1514 if (i++ > 0)
1515 {
1516 buffer.append('+');
1517 }
1518
1519 buffer.append(entry.getKey());
1520 buffer.append('=');
1521 buffer.append(normalizeValue(entry.getKey(), entry.getValue()));
1522 }
1523 }
1524 }
1525
1526
1527
1528 /**
1529 * Obtains a normalized representation of the provided attribute name.
1530 *
1531 * @param name The name of the attribute for which to create the normalized
1532 * representation.
1533 *
1534 * @return A normalized representation of the provided attribute name.
1535 */
1536 private String normalizeAttrName(final String name)
1537 {
1538 String n = name;
1539 if (schema != null)
1540 {
1541 final AttributeTypeDefinition at = schema.getAttributeType(name);
1542 if (at != null)
1543 {
1544 n = at.getNameOrOID();
1545 }
1546 }
1547 return toLowerCase(n);
1548 }
1549
1550
1551
1552 /**
1553 * Retrieves a normalized string representation of the RDN with the provided
1554 * string representation.
1555 *
1556 * @param s The string representation of the RDN to normalize. It must not
1557 * be {@code null}.
1558 *
1559 * @return The normalized string representation of the RDN with the provided
1560 * string representation.
1561 *
1562 * @throws LDAPException If the provided string cannot be parsed as an RDN.
1563 */
1564 public static String normalize(final String s)
1565 throws LDAPException
1566 {
1567 return normalize(s, null);
1568 }
1569
1570
1571
1572 /**
1573 * Retrieves a normalized string representation of the RDN with the provided
1574 * string representation.
1575 *
1576 * @param s The string representation of the RDN to normalize. It must
1577 * not be {@code null}.
1578 * @param schema The schema to use to generate the normalized string
1579 * representation of the RDN. It may be {@code null} if no
1580 * schema is available.
1581 *
1582 * @return The normalized string representation of the RDN with the provided
1583 * string representation.
1584 *
1585 * @throws LDAPException If the provided string cannot be parsed as an RDN.
1586 */
1587 public static String normalize(final String s, final Schema schema)
1588 throws LDAPException
1589 {
1590 return new RDN(s, schema).toNormalizedString();
1591 }
1592
1593
1594
1595 /**
1596 * Normalizes the provided attribute value for use in an RDN.
1597 *
1598 * @param attributeName The name of the attribute with which the value is
1599 * associated.
1600 * @param value The value to be normalized.
1601 *
1602 * @return A string builder containing a normalized representation of the
1603 * value in a suitable form for inclusion in an RDN.
1604 */
1605 private StringBuilder normalizeValue(final String attributeName,
1606 final ASN1OctetString value)
1607 {
1608 final MatchingRule matchingRule =
1609 MatchingRule.selectEqualityMatchingRule(attributeName, schema);
1610
1611 ASN1OctetString rawNormValue;
1612 try
1613 {
1614 rawNormValue = matchingRule.normalize(value);
1615 }
1616 catch (final Exception e)
1617 {
1618 debugException(e);
1619 rawNormValue =
1620 new ASN1OctetString(toLowerCase(value.stringValue()));
1621 }
1622
1623 final String valueString = rawNormValue.stringValue();
1624 final int length = valueString.length();
1625 final StringBuilder buffer = new StringBuilder(length);
1626
1627 for (int i=0; i < length; i++)
1628 {
1629 final char c = valueString.charAt(i);
1630
1631 switch (c)
1632 {
1633 case '\\':
1634 case '#':
1635 case '=':
1636 case '"':
1637 case '+':
1638 case ',':
1639 case ';':
1640 case '<':
1641 case '>':
1642 buffer.append('\\');
1643 buffer.append(c);
1644 break;
1645
1646 case ' ':
1647 // Escape this space only if it's the first character, the last
1648 // character, or if the next character is also a space.
1649 if ((i == 0) || ((i+1) == length) ||
1650 (((i+1) < length) && (valueString.charAt(i+1) == ' ')))
1651 {
1652 buffer.append("\\ ");
1653 }
1654 else
1655 {
1656 buffer.append(' ');
1657 }
1658 break;
1659
1660 default:
1661 // If it's not a printable ASCII character, then hex-encode it.
1662 if ((c < ' ') || (c > '~'))
1663 {
1664 hexEncode(c, buffer);
1665 }
1666 else
1667 {
1668 buffer.append(c);
1669 }
1670 break;
1671 }
1672 }
1673
1674 return buffer;
1675 }
1676
1677
1678
1679 /**
1680 * Retrieves a hash code for this RDN.
1681 *
1682 * @return The hash code for this RDN.
1683 */
1684 @Override()
1685 public int hashCode()
1686 {
1687 return toNormalizedString().hashCode();
1688 }
1689
1690
1691
1692 /**
1693 * Indicates whether this RDN is equal to the provided object. The given
1694 * object will only be considered equal to this RDN if it is also an RDN with
1695 * the same set of names and values.
1696 *
1697 * @param o The object for which to make the determination.
1698 *
1699 * @return {@code true} if the provided object can be considered equal to
1700 * this RDN, or {@code false} if not.
1701 */
1702 @Override()
1703 public boolean equals(final Object o)
1704 {
1705 if (o == null)
1706 {
1707 return false;
1708 }
1709
1710 if (o == this)
1711 {
1712 return true;
1713 }
1714
1715 if (! (o instanceof RDN))
1716 {
1717 return false;
1718 }
1719
1720 final RDN rdn = (RDN) o;
1721 return (toNormalizedString().equals(rdn.toNormalizedString()));
1722 }
1723
1724
1725
1726 /**
1727 * Indicates whether the RDN with the provided string representation is equal
1728 * to this RDN.
1729 *
1730 * @param s The string representation of the DN to compare with this RDN.
1731 *
1732 * @return {@code true} if the DN with the provided string representation is
1733 * equal to this RDN, or {@code false} if not.
1734 *
1735 * @throws LDAPException If the provided string cannot be parsed as an RDN.
1736 */
1737 public boolean equals(final String s)
1738 throws LDAPException
1739 {
1740 if (s == null)
1741 {
1742 return false;
1743 }
1744
1745 return equals(new RDN(s, schema));
1746 }
1747
1748
1749
1750 /**
1751 * Indicates whether the two provided strings represent the same RDN.
1752 *
1753 * @param s1 The string representation of the first RDN for which to make
1754 * the determination. It must not be {@code null}.
1755 * @param s2 The string representation of the second RDN for which to make
1756 * the determination. It must not be {@code null}.
1757 *
1758 * @return {@code true} if the provided strings represent the same RDN, or
1759 * {@code false} if not.
1760 *
1761 * @throws LDAPException If either of the provided strings cannot be parsed
1762 * as an RDN.
1763 */
1764 public static boolean equals(final String s1, final String s2)
1765 throws LDAPException
1766 {
1767 return new RDN(s1).equals(new RDN(s2));
1768 }
1769
1770
1771
1772 /**
1773 * Compares the provided RDN to this RDN to determine their relative order in
1774 * a sorted list.
1775 *
1776 * @param rdn The RDN to compare against this RDN. It must not be
1777 * {@code null}.
1778 *
1779 * @return A negative integer if this RDN should come before the provided RDN
1780 * in a sorted list, a positive integer if this RDN should come after
1781 * the provided RDN in a sorted list, or zero if the provided RDN
1782 * can be considered equal to this RDN.
1783 */
1784 public int compareTo(final RDN rdn)
1785 {
1786 return compare(this, rdn);
1787 }
1788
1789
1790
1791 /**
1792 * Compares the provided RDN values to determine their relative order in a
1793 * sorted list.
1794 *
1795 * @param rdn1 The first RDN to be compared. It must not be {@code null}.
1796 * @param rdn2 The second RDN to be compared. It must not be {@code null}.
1797 *
1798 * @return A negative integer if the first RDN should come before the second
1799 * RDN in a sorted list, a positive integer if the first RDN should
1800 * come after the second RDN in a sorted list, or zero if the two RDN
1801 * values can be considered equal.
1802 */
1803 public int compare(final RDN rdn1, final RDN rdn2)
1804 {
1805 ensureNotNull(rdn1, rdn2);
1806
1807 return(rdn1.toNormalizedString().compareTo(rdn2.toNormalizedString()));
1808 }
1809
1810
1811
1812 /**
1813 * Compares the RDN values with the provided string representations to
1814 * determine their relative order in a sorted list.
1815 *
1816 * @param s1 The string representation of the first RDN to be compared. It
1817 * must not be {@code null}.
1818 * @param s2 The string representation of the second RDN to be compared. It
1819 * must not be {@code null}.
1820 *
1821 * @return A negative integer if the first RDN should come before the second
1822 * RDN in a sorted list, a positive integer if the first RDN should
1823 * come after the second RDN in a sorted list, or zero if the two RDN
1824 * values can be considered equal.
1825 *
1826 * @throws LDAPException If either of the provided strings cannot be parsed
1827 * as an RDN.
1828 */
1829 public static int compare(final String s1, final String s2)
1830 throws LDAPException
1831 {
1832 return compare(s1, s2, null);
1833 }
1834
1835
1836
1837 /**
1838 * Compares the RDN values with the provided string representations to
1839 * determine their relative order in a sorted list.
1840 *
1841 * @param s1 The string representation of the first RDN to be compared.
1842 * It must not be {@code null}.
1843 * @param s2 The string representation of the second RDN to be compared.
1844 * It must not be {@code null}.
1845 * @param schema The schema to use to generate the normalized string
1846 * representations of the RDNs. It may be {@code null} if no
1847 * schema is available.
1848 *
1849 * @return A negative integer if the first RDN should come before the second
1850 * RDN in a sorted list, a positive integer if the first RDN should
1851 * come after the second RDN in a sorted list, or zero if the two RDN
1852 * values can be considered equal.
1853 *
1854 * @throws LDAPException If either of the provided strings cannot be parsed
1855 * as an RDN.
1856 */
1857 public static int compare(final String s1, final String s2,
1858 final Schema schema)
1859 throws LDAPException
1860 {
1861 return new RDN(s1, schema).compareTo(new RDN(s2, schema));
1862 }
1863 }