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