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