001 /*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2014 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 the set of attribute names for this RDN.
1161 *
1162 * @return The set of attribute names for this RDN.
1163 */
1164 public String[] getAttributeNames()
1165 {
1166 return attributeNames;
1167 }
1168
1169
1170
1171 /**
1172 * Retrieves the set of attribute values for this RDN.
1173 *
1174 * @return The set of attribute values for this RDN.
1175 */
1176 public String[] getAttributeValues()
1177 {
1178 final String[] stringValues = new String[attributeValues.length];
1179 for (int i=0; i < stringValues.length; i++)
1180 {
1181 stringValues[i] = attributeValues[i].stringValue();
1182 }
1183
1184 return stringValues;
1185 }
1186
1187
1188
1189 /**
1190 * Retrieves the set of attribute values for this RDN.
1191 *
1192 * @return The set of attribute values for this RDN.
1193 */
1194 public byte[][] getByteArrayAttributeValues()
1195 {
1196 final byte[][] byteValues = new byte[attributeValues.length][];
1197 for (int i=0; i < byteValues.length; i++)
1198 {
1199 byteValues[i] = attributeValues[i].getValue();
1200 }
1201
1202 return byteValues;
1203 }
1204
1205
1206
1207 /**
1208 * Retrieves the schema that will be used for this RDN, if any.
1209 *
1210 * @return The schema that will be used for this RDN, or {@code null} if none
1211 * has been provided.
1212 */
1213 Schema getSchema()
1214 {
1215 return schema;
1216 }
1217
1218
1219
1220 /**
1221 * Indicates whether this RDN contains the specified attribute.
1222 *
1223 * @param attributeName The name of the attribute for which to make the
1224 * determination.
1225 *
1226 * @return {@code true} if RDN contains the specified attribute, or
1227 * {@code false} if not.
1228 */
1229 public boolean hasAttribute(final String attributeName)
1230 {
1231 for (final String name : attributeNames)
1232 {
1233 if (name.equalsIgnoreCase(attributeName))
1234 {
1235 return true;
1236 }
1237 }
1238
1239 return false;
1240 }
1241
1242
1243
1244 /**
1245 * Indicates whether this RDN contains the specified attribute value.
1246 *
1247 * @param attributeName The name of the attribute for which to make the
1248 * determination.
1249 * @param attributeValue The attribute value for which to make the
1250 * determination.
1251 *
1252 * @return {@code true} if RDN contains the specified attribute, or
1253 * {@code false} if not.
1254 */
1255 public boolean hasAttributeValue(final String attributeName,
1256 final String attributeValue)
1257 {
1258 for (int i=0; i < attributeNames.length; i++)
1259 {
1260 if (attributeNames[i].equalsIgnoreCase(attributeName))
1261 {
1262 final Attribute a =
1263 new Attribute(attributeName, schema, attributeValue);
1264 final Attribute b = new Attribute(attributeName, schema,
1265 attributeValues[i].stringValue());
1266
1267 if (a.equals(b))
1268 {
1269 return true;
1270 }
1271 }
1272 }
1273
1274 return false;
1275 }
1276
1277
1278
1279 /**
1280 * Indicates whether this RDN contains the specified attribute value.
1281 *
1282 * @param attributeName The name of the attribute for which to make the
1283 * determination.
1284 * @param attributeValue The attribute value for which to make the
1285 * determination.
1286 *
1287 * @return {@code true} if RDN contains the specified attribute, or
1288 * {@code false} if not.
1289 */
1290 public boolean hasAttributeValue(final String attributeName,
1291 final byte[] attributeValue)
1292 {
1293 for (int i=0; i < attributeNames.length; i++)
1294 {
1295 if (attributeNames[i].equalsIgnoreCase(attributeName))
1296 {
1297 final Attribute a =
1298 new Attribute(attributeName, schema, attributeValue);
1299 final Attribute b = new Attribute(attributeName, schema,
1300 attributeValues[i].getValue());
1301
1302 if (a.equals(b))
1303 {
1304 return true;
1305 }
1306 }
1307 }
1308
1309 return false;
1310 }
1311
1312
1313
1314 /**
1315 * Retrieves a string representation of this RDN.
1316 *
1317 * @return A string representation of this RDN.
1318 */
1319 @Override()
1320 public String toString()
1321 {
1322 if (rdnString == null)
1323 {
1324 final StringBuilder buffer = new StringBuilder();
1325 toString(buffer, false);
1326 rdnString = buffer.toString();
1327 }
1328
1329 return rdnString;
1330 }
1331
1332
1333
1334 /**
1335 * Retrieves a string representation of this RDN with minimal encoding for
1336 * special characters. Only those characters specified in RFC 4514 section
1337 * 2.4 will be escaped. No escaping will be used for non-ASCII characters or
1338 * non-printable ASCII characters.
1339 *
1340 * @return A string representation of this RDN with minimal encoding for
1341 * special characters.
1342 */
1343 public String toMinimallyEncodedString()
1344 {
1345 final StringBuilder buffer = new StringBuilder();
1346 toString(buffer, true);
1347 return buffer.toString();
1348 }
1349
1350
1351
1352 /**
1353 * Appends a string representation of this RDN to the provided buffer.
1354 *
1355 * @param buffer The buffer to which the string representation is to be
1356 * appended.
1357 */
1358 public void toString(final StringBuilder buffer)
1359 {
1360 toString(buffer, false);
1361 }
1362
1363
1364
1365 /**
1366 * Appends a string representation of this RDN to the provided buffer.
1367 *
1368 * @param buffer The buffer to which the string representation is
1369 * to be appended.
1370 * @param minimizeEncoding Indicates whether to restrict the encoding of
1371 * special characters to the bare minimum required
1372 * by LDAP (as per RFC 4514 section 2.4). If this
1373 * is {@code true}, then only leading and trailing
1374 * spaces, double quotes, plus signs, commas,
1375 * semicolons, greater-than, less-than, and
1376 * backslash characters will be encoded.
1377 */
1378 public void toString(final StringBuilder buffer,
1379 final boolean minimizeEncoding)
1380 {
1381 if ((rdnString != null) && (! minimizeEncoding))
1382 {
1383 buffer.append(rdnString);
1384 return;
1385 }
1386
1387 for (int i=0; i < attributeNames.length; i++)
1388 {
1389 if (i > 0)
1390 {
1391 buffer.append('+');
1392 }
1393
1394 buffer.append(attributeNames[i]);
1395 buffer.append('=');
1396
1397 // Iterate through the value character-by-character and do any escaping
1398 // that may be necessary.
1399 final String valueString = attributeValues[i].stringValue();
1400 final int length = valueString.length();
1401 for (int j=0; j < length; j++)
1402 {
1403 final char c = valueString.charAt(j);
1404 switch (c)
1405 {
1406 case '\\':
1407 case '#':
1408 case '=':
1409 case '"':
1410 case '+':
1411 case ',':
1412 case ';':
1413 case '<':
1414 case '>':
1415 buffer.append('\\');
1416 buffer.append(c);
1417 break;
1418
1419 case ' ':
1420 // Escape this space only if it's the first character, the last
1421 // character, or if the next character is also a space.
1422 if ((j == 0) || ((j+1) == length) ||
1423 (((j+1) < length) && (valueString.charAt(j+1) == ' ')))
1424 {
1425 buffer.append("\\ ");
1426 }
1427 else
1428 {
1429 buffer.append(' ');
1430 }
1431 break;
1432
1433 case '\u0000':
1434 buffer.append("\\00");
1435 break;
1436
1437 default:
1438 // If it's not a printable ASCII character, then hex-encode it
1439 // unless we're using minimized encoding.
1440 if ((! minimizeEncoding) && ((c < ' ') || (c > '~')))
1441 {
1442 hexEncode(c, buffer);
1443 }
1444 else
1445 {
1446 buffer.append(c);
1447 }
1448 break;
1449 }
1450 }
1451 }
1452 }
1453
1454
1455
1456 /**
1457 * Retrieves a normalized string representation of this RDN.
1458 *
1459 * @return A normalized string representation of this RDN.
1460 */
1461 public String toNormalizedString()
1462 {
1463 if (normalizedString == null)
1464 {
1465 final StringBuilder buffer = new StringBuilder();
1466 toNormalizedString(buffer);
1467 normalizedString = buffer.toString();
1468 }
1469
1470 return normalizedString;
1471 }
1472
1473
1474
1475 /**
1476 * Appends a normalized string representation of this RDN to the provided
1477 * buffer.
1478 *
1479 * @param buffer The buffer to which the normalized string representation is
1480 * to be appended.
1481 */
1482 public void toNormalizedString(final StringBuilder buffer)
1483 {
1484 if (attributeNames.length == 1)
1485 {
1486 // It's a single-valued RDN, so there is no need to sort anything.
1487 final String name = normalizeAttrName(attributeNames[0]);
1488 buffer.append(name);
1489 buffer.append('=');
1490 buffer.append(normalizeValue(name, attributeValues[0]));
1491 }
1492 else
1493 {
1494 // It's a multivalued RDN, so we need to sort the components.
1495 final TreeMap<String,ASN1OctetString> valueMap =
1496 new TreeMap<String,ASN1OctetString>();
1497 for (int i=0; i < attributeNames.length; i++)
1498 {
1499 final String name = normalizeAttrName(attributeNames[i]);
1500 valueMap.put(name, attributeValues[i]);
1501 }
1502
1503 int i=0;
1504 for (final Map.Entry<String,ASN1OctetString> entry : valueMap.entrySet())
1505 {
1506 if (i++ > 0)
1507 {
1508 buffer.append('+');
1509 }
1510
1511 buffer.append(entry.getKey());
1512 buffer.append('=');
1513 buffer.append(normalizeValue(entry.getKey(), entry.getValue()));
1514 }
1515 }
1516 }
1517
1518
1519
1520 /**
1521 * Obtains a normalized representation of the provided attribute name.
1522 *
1523 * @param name The name of the attribute for which to create the normalized
1524 * representation.
1525 *
1526 * @return A normalized representation of the provided attribute name.
1527 */
1528 private String normalizeAttrName(final String name)
1529 {
1530 String n = name;
1531 if (schema != null)
1532 {
1533 final AttributeTypeDefinition at = schema.getAttributeType(name);
1534 if (at != null)
1535 {
1536 n = at.getNameOrOID();
1537 }
1538 }
1539 return toLowerCase(n);
1540 }
1541
1542
1543
1544 /**
1545 * Retrieves a normalized string representation of the RDN with the provided
1546 * string representation.
1547 *
1548 * @param s The string representation of the RDN to normalize. It must not
1549 * be {@code null}.
1550 *
1551 * @return The normalized string representation of the RDN with the provided
1552 * string representation.
1553 *
1554 * @throws LDAPException If the provided string cannot be parsed as an RDN.
1555 */
1556 public static String normalize(final String s)
1557 throws LDAPException
1558 {
1559 return normalize(s, null);
1560 }
1561
1562
1563
1564 /**
1565 * Retrieves a normalized string representation of the RDN with the provided
1566 * string representation.
1567 *
1568 * @param s The string representation of the RDN to normalize. It must
1569 * not be {@code null}.
1570 * @param schema The schema to use to generate the normalized string
1571 * representation of the RDN. It may be {@code null} if no
1572 * schema is available.
1573 *
1574 * @return The normalized string representation of the RDN with the provided
1575 * string representation.
1576 *
1577 * @throws LDAPException If the provided string cannot be parsed as an RDN.
1578 */
1579 public static String normalize(final String s, final Schema schema)
1580 throws LDAPException
1581 {
1582 return new RDN(s, schema).toNormalizedString();
1583 }
1584
1585
1586
1587 /**
1588 * Normalizes the provided attribute value for use in an RDN.
1589 *
1590 * @param attributeName The name of the attribute with which the value is
1591 * associated.
1592 * @param value The value to be normalized.
1593 *
1594 * @return A string builder containing a normalized representation of the
1595 * value in a suitable form for inclusion in an RDN.
1596 */
1597 private StringBuilder normalizeValue(final String attributeName,
1598 final ASN1OctetString value)
1599 {
1600 final MatchingRule matchingRule =
1601 MatchingRule.selectEqualityMatchingRule(attributeName, schema);
1602
1603 ASN1OctetString rawNormValue;
1604 try
1605 {
1606 rawNormValue = matchingRule.normalize(value);
1607 }
1608 catch (final Exception e)
1609 {
1610 debugException(e);
1611 rawNormValue =
1612 new ASN1OctetString(toLowerCase(value.stringValue()));
1613 }
1614
1615 final String valueString = rawNormValue.stringValue();
1616 final int length = valueString.length();
1617 final StringBuilder buffer = new StringBuilder(length);
1618
1619 for (int i=0; i < length; i++)
1620 {
1621 final char c = valueString.charAt(i);
1622
1623 switch (c)
1624 {
1625 case '\\':
1626 case '#':
1627 case '=':
1628 case '"':
1629 case '+':
1630 case ',':
1631 case ';':
1632 case '<':
1633 case '>':
1634 buffer.append('\\');
1635 buffer.append(c);
1636 break;
1637
1638 case ' ':
1639 // Escape this space only if it's the first character, the last
1640 // character, or if the next character is also a space.
1641 if ((i == 0) || ((i+1) == length) ||
1642 (((i+1) < length) && (valueString.charAt(i+1) == ' ')))
1643 {
1644 buffer.append("\\ ");
1645 }
1646 else
1647 {
1648 buffer.append(' ');
1649 }
1650 break;
1651
1652 default:
1653 // If it's not a printable ASCII character, then hex-encode it.
1654 if ((c < ' ') || (c > '~'))
1655 {
1656 hexEncode(c, buffer);
1657 }
1658 else
1659 {
1660 buffer.append(c);
1661 }
1662 break;
1663 }
1664 }
1665
1666 return buffer;
1667 }
1668
1669
1670
1671 /**
1672 * Retrieves a hash code for this RDN.
1673 *
1674 * @return The hash code for this RDN.
1675 */
1676 @Override()
1677 public int hashCode()
1678 {
1679 return toNormalizedString().hashCode();
1680 }
1681
1682
1683
1684 /**
1685 * Indicates whether this RDN is equal to the provided object. The given
1686 * object will only be considered equal to this RDN if it is also an RDN with
1687 * the same set of names and values.
1688 *
1689 * @param o The object for which to make the determination.
1690 *
1691 * @return {@code true} if the provided object can be considered equal to
1692 * this RDN, or {@code false} if not.
1693 */
1694 @Override()
1695 public boolean equals(final Object o)
1696 {
1697 if (o == null)
1698 {
1699 return false;
1700 }
1701
1702 if (o == this)
1703 {
1704 return true;
1705 }
1706
1707 if (! (o instanceof RDN))
1708 {
1709 return false;
1710 }
1711
1712 final RDN rdn = (RDN) o;
1713 return (toNormalizedString().equals(rdn.toNormalizedString()));
1714 }
1715
1716
1717
1718 /**
1719 * Indicates whether the RDN with the provided string representation is equal
1720 * to this RDN.
1721 *
1722 * @param s The string representation of the DN to compare with this RDN.
1723 *
1724 * @return {@code true} if the DN with the provided string representation is
1725 * equal to this RDN, or {@code false} if not.
1726 *
1727 * @throws LDAPException If the provided string cannot be parsed as an RDN.
1728 */
1729 public boolean equals(final String s)
1730 throws LDAPException
1731 {
1732 if (s == null)
1733 {
1734 return false;
1735 }
1736
1737 return equals(new RDN(s, schema));
1738 }
1739
1740
1741
1742 /**
1743 * Indicates whether the two provided strings represent the same RDN.
1744 *
1745 * @param s1 The string representation of the first RDN for which to make
1746 * the determination. It must not be {@code null}.
1747 * @param s2 The string representation of the second RDN for which to make
1748 * the determination. It must not be {@code null}.
1749 *
1750 * @return {@code true} if the provided strings represent the same RDN, or
1751 * {@code false} if not.
1752 *
1753 * @throws LDAPException If either of the provided strings cannot be parsed
1754 * as an RDN.
1755 */
1756 public static boolean equals(final String s1, final String s2)
1757 throws LDAPException
1758 {
1759 return new RDN(s1).equals(new RDN(s2));
1760 }
1761
1762
1763
1764 /**
1765 * Compares the provided RDN to this RDN to determine their relative order in
1766 * a sorted list.
1767 *
1768 * @param rdn The RDN to compare against this RDN. It must not be
1769 * {@code null}.
1770 *
1771 * @return A negative integer if this RDN should come before the provided RDN
1772 * in a sorted list, a positive integer if this RDN should come after
1773 * the provided RDN in a sorted list, or zero if the provided RDN
1774 * can be considered equal to this RDN.
1775 */
1776 public int compareTo(final RDN rdn)
1777 {
1778 return compare(this, rdn);
1779 }
1780
1781
1782
1783 /**
1784 * Compares the provided RDN values to determine their relative order in a
1785 * sorted list.
1786 *
1787 * @param rdn1 The first RDN to be compared. It must not be {@code null}.
1788 * @param rdn2 The second RDN to be compared. It must not be {@code null}.
1789 *
1790 * @return A negative integer if the first RDN should come before the second
1791 * RDN in a sorted list, a positive integer if the first RDN should
1792 * come after the second RDN in a sorted list, or zero if the two RDN
1793 * values can be considered equal.
1794 */
1795 public int compare(final RDN rdn1, final RDN rdn2)
1796 {
1797 ensureNotNull(rdn1, rdn2);
1798
1799 return(rdn1.toNormalizedString().compareTo(rdn2.toNormalizedString()));
1800 }
1801
1802
1803
1804 /**
1805 * Compares the RDN values with the provided string representations to
1806 * determine their relative order in a sorted list.
1807 *
1808 * @param s1 The string representation of the first RDN to be compared. It
1809 * must not be {@code null}.
1810 * @param s2 The string representation of the second RDN to be compared. It
1811 * must not be {@code null}.
1812 *
1813 * @return A negative integer if the first RDN should come before the second
1814 * RDN in a sorted list, a positive integer if the first RDN should
1815 * come after the second RDN in a sorted list, or zero if the two RDN
1816 * values can be considered equal.
1817 *
1818 * @throws LDAPException If either of the provided strings cannot be parsed
1819 * as an RDN.
1820 */
1821 public static int compare(final String s1, final String s2)
1822 throws LDAPException
1823 {
1824 return compare(s1, s2, null);
1825 }
1826
1827
1828
1829 /**
1830 * Compares the RDN values with the provided string representations to
1831 * determine their relative order in a sorted list.
1832 *
1833 * @param s1 The string representation of the first RDN to be compared.
1834 * It must not be {@code null}.
1835 * @param s2 The string representation of the second RDN to be compared.
1836 * It must not be {@code null}.
1837 * @param schema The schema to use to generate the normalized string
1838 * representations of the RDNs. It may be {@code null} if no
1839 * schema is available.
1840 *
1841 * @return A negative integer if the first RDN should come before the second
1842 * RDN in a sorted list, a positive integer if the first RDN should
1843 * come after the second RDN in a sorted list, or zero if the two RDN
1844 * values can be considered equal.
1845 *
1846 * @throws LDAPException If either of the provided strings cannot be parsed
1847 * as an RDN.
1848 */
1849 public static int compare(final String s1, final String s2,
1850 final Schema schema)
1851 throws LDAPException
1852 {
1853 return new RDN(s1, schema).compareTo(new RDN(s2, schema));
1854 }
1855 }