001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.types;
028 import org.opends.messages.Message;
029
030
031
032 import java.util.Iterator;
033 import java.util.List;
034 import java.util.TreeMap;
035 import java.util.TreeSet;
036
037 import org.opends.server.api.OrderingMatchingRule;
038 import org.opends.server.core.DirectoryServer;
039 import org.opends.server.protocols.asn1.ASN1OctetString;
040
041 import static org.opends.server.loggers.debug.DebugLogger.*;
042 import org.opends.server.loggers.debug.DebugTracer;
043 import static org.opends.messages.CoreMessages.*;
044 import static org.opends.server.util.StaticUtils.*;
045
046
047
048 /**
049 * This class defines a data structure for storing and interacting
050 * with the relative distinguished names associated with entries in
051 * the Directory Server.
052 */
053 @org.opends.server.types.PublicAPI(
054 stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
055 mayInstantiate=true,
056 mayExtend=false,
057 mayInvoke=true)
058 public final class RDN
059 implements Comparable<RDN>
060 {
061 /**
062 * The tracer object for the debug logger.
063 */
064 private static final DebugTracer TRACER = getTracer();
065
066
067
068
069 // The set of attribute types for the elements in this RDN.
070 private AttributeType[] attributeTypes;
071
072 // The set of values for the elements in this RDN.
073 private AttributeValue[] attributeValues;
074
075 // The number of values for this RDN.
076 private int numValues;
077
078 // The string representation of the normalized form of this RDN.
079 private String normalizedRDN;
080
081 // The string representation of this RDN.
082 private String rdnString;
083
084 // The set of user-provided names for the attributes in this RDN.
085 private String[] attributeNames;
086
087
088
089 /**
090 * Creates a new RDN with the provided information.
091 *
092 * @param attributeType The attribute type for this RDN. It must
093 * not be {@code null}.
094 * @param attributeValue The value for this RDN. It must not be
095 * {@code null}.
096 */
097 public RDN(AttributeType attributeType,
098 AttributeValue attributeValue)
099 {
100 attributeTypes = new AttributeType[] { attributeType };
101 attributeNames = new String[] { attributeType.getPrimaryName() };
102 attributeValues = new AttributeValue[] { attributeValue };
103
104 numValues = 1;
105 rdnString = null;
106 normalizedRDN = null;
107 }
108
109
110
111 /**
112 * Creates a new RDN with the provided information.
113 *
114 * @param attributeType The attribute type for this RDN. It must
115 * not be {@code null}.
116 * @param attributeName The user-provided name for this RDN. It
117 * must not be {@code null}.
118 * @param attributeValue The value for this RDN. It must not be
119 * {@code null}.
120 */
121 public RDN(AttributeType attributeType, String attributeName,
122 AttributeValue attributeValue)
123 {
124 attributeTypes = new AttributeType[] { attributeType };
125 attributeNames = new String[] { attributeName };
126 attributeValues = new AttributeValue[] { attributeValue };
127
128 numValues = 1;
129 rdnString = null;
130 normalizedRDN = null;
131 }
132
133
134
135 /**
136 * Creates a new RDN with the provided information. The number of
137 * type, name, and value elements must be nonzero and equal.
138 *
139 * @param attributeTypes The set of attribute types for this RDN.
140 * It must not be empty or {@code null}.
141 * @param attributeNames The set of user-provided names for this
142 * RDN. It must have the same number of
143 * elements as the {@code attributeTypes}
144 * argument.
145 * @param attributeValues The set of values for this RDN. It must
146 * have the same number of elements as the
147 * {@code attributeTypes} argument.
148 */
149 public RDN(List<AttributeType> attributeTypes,
150 List<String> attributeNames,
151 List<AttributeValue> attributeValues)
152 {
153 this.attributeTypes = new AttributeType[attributeTypes.size()];
154 this.attributeNames = new String[attributeNames.size()];
155 this.attributeValues = new AttributeValue[attributeValues.size()];
156
157 attributeTypes.toArray(this.attributeTypes);
158 attributeNames.toArray(this.attributeNames);
159 attributeValues.toArray(this.attributeValues);
160
161 numValues = attributeTypes.size();
162 rdnString = null;
163 normalizedRDN = null;
164 }
165
166
167
168 /**
169 * Creates a new RDN with the provided information. The number of
170 * type, name, and value elements must be nonzero and equal.
171 *
172 * @param attributeTypes The set of attribute types for this RDN.
173 * It must not be empty or {@code null}.
174 * @param attributeNames The set of user-provided names for this
175 * RDN. It must have the same number of
176 * elements as the {@code attributeTypes}
177 * argument.
178 * @param attributeValues The set of values for this RDN. It must
179 * have the same number of elements as the
180 * {@code attributeTypes} argument.
181 */
182 public RDN(AttributeType[] attributeTypes, String[] attributeNames,
183 AttributeValue[] attributeValues)
184 {
185 this.numValues = attributeTypes.length;
186 this.attributeTypes = attributeTypes;
187 this.attributeNames = attributeNames;
188 this.attributeValues = attributeValues;
189
190 rdnString = null;
191 normalizedRDN = null;
192 }
193
194
195
196 /**
197 * Creates a new RDN with the provided information.
198 *
199 * @param attributeType The attribute type for this RDN. It must
200 * not be {@code null}.
201 * @param attributeValue The value for this RDN. It must not be
202 * {@code null}.
203 *
204 * @return The RDN created with the provided information.
205 */
206 public static RDN create(AttributeType attributeType,
207 AttributeValue attributeValue)
208 {
209 return new RDN(attributeType, attributeValue);
210 }
211
212
213
214 /**
215 * Retrieves the number of attribute-value pairs contained in this
216 * RDN.
217 *
218 * @return The number of attribute-value pairs contained in this
219 * RDN.
220 */
221 public int getNumValues()
222 {
223 return numValues;
224 }
225
226
227
228 /**
229 * Indicates whether this RDN includes the specified attribute type.
230 *
231 * @param attributeType The attribute type for which to make the
232 * determination.
233 *
234 * @return <CODE>true</CODE> if the RDN includes the specified
235 * attribute type, or <CODE>false</CODE> if not.
236 */
237 public boolean hasAttributeType(AttributeType attributeType)
238 {
239 for (AttributeType t : attributeTypes)
240 {
241 if (t.equals(attributeType))
242 {
243 return true;
244 }
245 }
246
247 return false;
248 }
249
250
251
252 /**
253 * Indicates whether this RDN includes the specified attribute type.
254 *
255 * @param lowerName The name or OID for the attribute type for
256 * which to make the determination, formatted in
257 * all lowercase characters.
258 *
259 * @return <CODE>true</CODE> if the RDN includes the specified
260 * attribute type, or <CODE>false</CODE> if not.
261 */
262 public boolean hasAttributeType(String lowerName)
263 {
264 for (AttributeType t : attributeTypes)
265 {
266 if (t.hasNameOrOID(lowerName))
267 {
268 return true;
269 }
270 }
271
272 for (String s : attributeNames)
273 {
274 if (s.equalsIgnoreCase(lowerName))
275 {
276 return true;
277 }
278 }
279
280 return false;
281 }
282
283
284
285 /**
286 * Retrieves the attribute type at the specified position in the set
287 * of attribute types for this RDN.
288 *
289 * @param pos The position of the attribute type to retrieve.
290 *
291 * @return The attribute type at the specified position in the set
292 * of attribute types for this RDN.
293 */
294 public AttributeType getAttributeType(int pos)
295 {
296 return attributeTypes[pos];
297 }
298
299
300
301 /**
302 * Retrieves the name for the attribute type at the specified
303 * position in the set of attribute types for this RDN.
304 *
305 * @param pos The position of the attribute type for which to
306 * retrieve the name.
307 *
308 * @return The name for the attribute type at the specified
309 * position in the set of attribute types for this RDN.
310 */
311 public String getAttributeName(int pos)
312 {
313 return attributeNames[pos];
314 }
315
316
317
318 /**
319 * Retrieves the attribute value that is associated with the
320 * specified attribute type.
321 *
322 * @param attributeType The attribute type for which to retrieve
323 * the corresponding value.
324 *
325 * @return The value for the requested attribute type, or
326 * <CODE>null</CODE> if the specified attribute type is not
327 * present in the RDN.
328 */
329 public AttributeValue getAttributeValue(AttributeType attributeType)
330 {
331 for (int i=0; i < numValues; i++)
332 {
333 if (attributeTypes[i].equals(attributeType))
334 {
335 return attributeValues[i];
336 }
337 }
338
339 return null;
340 }
341
342
343
344 /**
345 * Retrieves the value for the attribute type at the specified
346 * position in the set of attribute types for this RDN.
347 *
348 * @param pos The position of the attribute type for which to
349 * retrieve the value.
350 *
351 * @return The value for the attribute type at the specified
352 * position in the set of attribute types for this RDN.
353 */
354 public AttributeValue getAttributeValue(int pos)
355 {
356 return attributeValues[pos];
357 }
358
359
360
361 /**
362 * Indicates whether this RDN is multivalued.
363 *
364 * @return <CODE>true</CODE> if this RDN is multivalued, or
365 * <CODE>false</CODE> if not.
366 */
367 public boolean isMultiValued()
368 {
369 return (numValues > 1);
370 }
371
372
373
374 /**
375 * Indicates whether this RDN contains the specified type-value
376 * pair.
377 *
378 * @param type The attribute type for which to make the
379 * determination.
380 * @param value The value for which to make the determination.
381 *
382 * @return <CODE>true</CODE> if this RDN contains the specified
383 * attribute value, or <CODE>false</CODE> if not.
384 */
385 public boolean hasValue(AttributeType type, AttributeValue value)
386 {
387 for (int i=0; i < numValues; i++)
388 {
389 if (attributeTypes[i].equals(type) &&
390 attributeValues[i].equals(value))
391 {
392 return true;
393 }
394 }
395
396 return false;
397 }
398
399
400
401 /**
402 * Adds the provided type-value pair from this RDN. Note that this
403 * is intended only for internal use when constructing DN values.
404 *
405 * @param type The attribute type of the pair to add.
406 * @param name The user-provided name of the pair to add.
407 * @param value The attribute value of the pair to add.
408 *
409 * @return <CODE>true</CODE> if the type-value pair was added to
410 * this RDN, or <CODE>false</CODE> if it was not (e.g., it
411 * was already present).
412 */
413 boolean addValue(AttributeType type, String name,
414 AttributeValue value)
415 {
416 for (int i=0; i < numValues; i++)
417 {
418 if (attributeTypes[i].equals(type) &&
419 attributeValues[i].equals(value))
420 {
421 return false;
422 }
423 }
424
425 numValues++;
426
427 AttributeType[] newTypes = new AttributeType[numValues];
428 System.arraycopy(attributeTypes, 0, newTypes, 0,
429 attributeTypes.length);
430 newTypes[attributeTypes.length] = type;
431 attributeTypes = newTypes;
432
433 String[] newNames = new String[numValues];
434 System.arraycopy(attributeNames, 0, newNames, 0,
435 attributeNames.length);
436 newNames[attributeNames.length] = name;
437 attributeNames = newNames;
438
439 AttributeValue[] newValues = new AttributeValue[numValues];
440 System.arraycopy(attributeValues, 0, newValues, 0,
441 attributeValues.length);
442 newValues[attributeValues.length] = value;
443 attributeValues = newValues;
444
445 rdnString = null;
446 normalizedRDN = null;
447
448 return true;
449 }
450
451
452
453 /**
454 * Retrieves a version of the provided value in a form that is
455 * properly escaped for use in a DN or RDN.
456 *
457 * @param value The value to be represented in a DN-safe form.
458 *
459 * @return A version of the provided value in a form that is
460 * properly escaped for use in a DN or RDN.
461 */
462 private static String getDNValue(String value) {
463 if ((value == null) || (value.length() == 0)) {
464 return "";
465 }
466
467 // Only copy the string value if required.
468 boolean needsEscaping = false;
469 int length = value.length();
470
471 needsEscaping: {
472 char c = value.charAt(0);
473 if ((c == ' ') || (c == '#')) {
474 needsEscaping = true;
475 break needsEscaping;
476 }
477
478 if (value.charAt(length - 1) == ' ') {
479 needsEscaping = true;
480 break needsEscaping;
481 }
482
483 for (int i = 0; i < length; i++) {
484 c = value.charAt(i);
485 if (c < ' ') {
486 needsEscaping = true;
487 break needsEscaping;
488 } else {
489 switch (c) {
490 case ',':
491 case '+':
492 case '"':
493 case '\\':
494 case '<':
495 case '>':
496 case ';':
497 needsEscaping = true;
498 break needsEscaping;
499 }
500 }
501 }
502 }
503
504 if (!needsEscaping) {
505 return value;
506 }
507
508 // We need to copy and escape the string (allow for at least one
509 // escaped character).
510 StringBuilder buffer = new StringBuilder(length + 3);
511
512 // If the lead character is a space or a # it must be escaped.
513 int start = 0;
514 char c = value.charAt(0);
515 if ((c == ' ') || (c == '#')) {
516 buffer.append('\\');
517 buffer.append(c);
518 start = 1;
519 }
520
521 // Escape remaining characters as necessary.
522 for (int i = start; i < length; i++) {
523 c = value.charAt(i);
524 if (c < ' ') {
525 for (byte b : getBytes(String.valueOf(c))) {
526 buffer.append('\\');
527 buffer.append(byteToLowerHex(b));
528 }
529 } else {
530 switch (value.charAt(i)) {
531 case ',':
532 case '+':
533 case '"':
534 case '\\':
535 case '<':
536 case '>':
537 case ';':
538 buffer.append('\\');
539 buffer.append(c);
540 break;
541 default:
542 buffer.append(c);
543 break;
544 }
545 }
546 }
547
548 // If the last character is a space it must be escaped.
549 if (value.charAt(length - 1) == ' ') {
550 length = buffer.length();
551 buffer.insert(length - 1, '\\');
552 }
553
554 return buffer.toString();
555 }
556
557
558
559 /**
560 * Decodes the provided string as an RDN.
561 *
562 * @param rdnString
563 * The string to decode as an RDN.
564 * @return The decoded RDN.
565 * @throws DirectoryException
566 * If a problem occurs while trying to decode the provided
567 * string as a RDN.
568 */
569 public static RDN decode(String rdnString)
570 throws DirectoryException
571 {
572 // A null or empty RDN is not acceptable.
573 if (rdnString == null)
574 {
575 Message message = ERR_RDN_DECODE_NULL.get();
576 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
577 message);
578 }
579
580 int length = rdnString.length();
581 if (length == 0)
582 {
583 Message message = ERR_RDN_DECODE_NULL.get();
584 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
585 message);
586 }
587
588
589 // Iterate through the RDN string. The first thing to do is to
590 // get rid of any leading spaces.
591 int pos = 0;
592 char c = rdnString.charAt(pos);
593 while (c == ' ')
594 {
595 pos++;
596 if (pos == length)
597 {
598 // This means that the RDN was completely comprised of spaces,
599 // which is not valid.
600 Message message = ERR_RDN_DECODE_NULL.get();
601 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
602 message);
603 }
604 else
605 {
606 c = rdnString.charAt(pos);
607 }
608 }
609
610
611 // We know that it's not an empty RDN, so we can do the real
612 // processing. First, parse the attribute name. We can borrow
613 // the DN code for this.
614 boolean allowExceptions =
615 DirectoryServer.allowAttributeNameExceptions();
616 StringBuilder attributeName = new StringBuilder();
617 pos = DN.parseAttributeName(rdnString, pos, attributeName,
618 allowExceptions);
619
620
621 // Make sure that we're not at the end of the RDN string because
622 // that would be invalid.
623 if (pos >= length)
624 {
625 Message message = ERR_RDN_END_WITH_ATTR_NAME.get(
626 rdnString, attributeName.toString());
627 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
628 message);
629 }
630
631
632 // Skip over any spaces between the attribute name and its value.
633 c = rdnString.charAt(pos);
634 while (c == ' ')
635 {
636 pos++;
637 if (pos >= length)
638 {
639 // This means that we hit the end of the string before
640 // finding a '='. This is illegal because there is no
641 // attribute-value separator.
642 Message message = ERR_RDN_END_WITH_ATTR_NAME.get(
643 rdnString, attributeName.toString());
644 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
645 message);
646 }
647 else
648 {
649 c = rdnString.charAt(pos);
650 }
651 }
652
653
654 // The next character must be an equal sign. If it is not, then
655 // that's an error.
656 if (c == '=')
657 {
658 pos++;
659 }
660 else
661 {
662 Message message = ERR_RDN_NO_EQUAL.get(
663 rdnString, attributeName.toString(), c);
664 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
665 message);
666 }
667
668
669 // Skip over any spaces between the equal sign and the value.
670 while ((pos < length) && ((c = rdnString.charAt(pos)) == ' '))
671 {
672 pos++;
673 }
674
675
676 // If we are at the end of the RDN string, then that must mean
677 // that the attribute value was empty. This will probably never
678 // happen in a real-world environment, but technically isn't
679 // illegal. If it does happen, then go ahead and return the RDN.
680 if (pos >= length)
681 {
682 String name = attributeName.toString();
683 String lowerName = toLowerCase(name);
684 AttributeType attrType =
685 DirectoryServer.getAttributeType(lowerName);
686
687 if (attrType == null)
688 {
689 // This must be an attribute type that we don't know about.
690 // In that case, we'll create a new attribute using the
691 // default syntax. If this is a problem, it will be caught
692 // later either by not finding the target entry or by not
693 // allowing the entry to be added.
694 attrType = DirectoryServer.getDefaultAttributeType(name);
695 }
696
697 AttributeValue value = new AttributeValue(new ASN1OctetString(),
698 new ASN1OctetString());
699 return new RDN(attrType, name, value);
700 }
701
702
703 // Parse the value for this RDN component. This can be done using
704 // the DN code.
705 ByteString parsedValue = new ASN1OctetString();
706 pos = DN.parseAttributeValue(rdnString, pos, parsedValue);
707
708
709 // Create the new RDN with the provided information. However,
710 // don't return it yet because this could be a multi-valued RDN.
711 String name = attributeName.toString();
712 String lowerName = toLowerCase(name);
713 AttributeType attrType =
714 DirectoryServer.getAttributeType(lowerName);
715 if (attrType == null)
716 {
717 // This must be an attribute type that we don't know about.
718 // In that case, we'll create a new attribute using the default
719 // syntax. If this is a problem, it will be caught later either
720 // by not finding the target entry or by not allowing the entry
721 // to be added.
722 attrType = DirectoryServer.getDefaultAttributeType(name);
723 }
724
725 AttributeValue value = new AttributeValue(attrType, parsedValue);
726 RDN rdn = new RDN(attrType, name, value);
727
728
729 // Skip over any spaces that might be after the attribute value.
730 while ((pos < length) && ((c = rdnString.charAt(pos)) == ' '))
731 {
732 pos++;
733 }
734
735
736 // Most likely, this is the end of the RDN. If so, then return
737 // it.
738 if (pos >= length)
739 {
740 return rdn;
741 }
742
743
744 // If the next character is a comma or semicolon, then that is not
745 // allowed. It would be legal for a DN but not an RDN.
746 if ((c == ',') || (c == ';'))
747 {
748 Message message = ERR_RDN_UNEXPECTED_COMMA.get(rdnString, pos);
749 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
750 message);
751 }
752
753
754 // If the next character is anything but a plus sign, then it is
755 // illegal.
756 if (c != '+')
757 {
758 Message message =
759 ERR_RDN_ILLEGAL_CHARACTER.get(rdnString, c, pos);
760 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
761 message);
762 }
763
764
765 // If we have gotten here, then it is a multi-valued RDN. Parse
766 // the remaining attribute/value pairs and add them to the RDN
767 // that we've already created.
768 while (true)
769 {
770 // Skip over the plus sign and any spaces that may follow it
771 // before the next attribute name.
772 pos++;
773 while ((pos < length) && ((c = rdnString.charAt(pos)) == ' '))
774 {
775 pos++;
776 }
777
778
779 // Parse the attribute name.
780 attributeName = new StringBuilder();
781 pos = DN.parseAttributeName(rdnString, pos, attributeName,
782 allowExceptions);
783
784
785 // Make sure we're not at the end of the RDN.
786 if (pos >= length)
787 {
788 Message message = ERR_RDN_END_WITH_ATTR_NAME.get(
789 rdnString, attributeName.toString());
790 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
791 message);
792 }
793
794
795 // Skip over any spaces between the attribute name and the equal
796 // sign.
797 c = rdnString.charAt(pos);
798 while (c == ' ')
799 {
800 pos++;
801 if (pos >= length)
802 {
803 // This means that we hit the end of the string before
804 // finding a '='. This is illegal because there is no
805 // attribute-value separator.
806 Message message = ERR_RDN_END_WITH_ATTR_NAME.get(
807 rdnString, attributeName.toString());
808 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
809 message);
810 }
811 else
812 {
813 c = rdnString.charAt(pos);
814 }
815 }
816
817
818 // The next character must be an equal sign.
819 if (c == '=')
820 {
821 pos++;
822 }
823 else
824 {
825 Message message = ERR_RDN_NO_EQUAL.get(
826 rdnString, attributeName.toString(), c);
827 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
828 message);
829 }
830
831
832 // Skip over any spaces after the equal sign.
833 while ((pos < length) && ((c = rdnString.charAt(pos)) == ' '))
834 {
835 pos++;
836 }
837
838
839 // If we are at the end of the RDN string, then that must mean
840 // that the attribute value was empty. This will probably never
841 // happen in a real-world environment, but technically isn't
842 // illegal. If it does happen, then go ahead and return the
843 // RDN.
844 if (pos >= length)
845 {
846 name = attributeName.toString();
847 lowerName = toLowerCase(name);
848 attrType = DirectoryServer.getAttributeType(lowerName);
849
850 if (attrType == null)
851 {
852 // This must be an attribute type that we don't know about.
853 // In that case, we'll create a new attribute using the
854 // default syntax. If this is a problem, it will be caught
855 // later either by not finding the target entry or by not
856 // allowing the entry to be added.
857 attrType = DirectoryServer.getDefaultAttributeType(name);
858 }
859
860 value = new AttributeValue(new ASN1OctetString(),
861 new ASN1OctetString());
862 rdn.addValue(attrType, name, value);
863 return rdn;
864 }
865
866
867 // Parse the value for this RDN component.
868 parsedValue = new ASN1OctetString();
869 pos = DN.parseAttributeValue(rdnString, pos, parsedValue);
870
871
872 // Update the RDN to include the new attribute/value.
873 name = attributeName.toString();
874 lowerName = toLowerCase(name);
875 attrType = DirectoryServer.getAttributeType(lowerName);
876 if (attrType == null)
877 {
878 // This must be an attribute type that we don't know about.
879 // In that case, we'll create a new attribute using the
880 // default syntax. If this is a problem, it will be caught
881 // later either by not finding the target entry or by not
882 // allowing the entry to be added.
883 attrType = DirectoryServer.getDefaultAttributeType(name);
884 }
885
886 value = new AttributeValue(attrType, parsedValue);
887 rdn.addValue(attrType, name, value);
888
889
890 // Skip over any spaces that might be after the attribute value.
891 while ((pos < length) && ((c = rdnString.charAt(pos)) == ' '))
892 {
893 pos++;
894 }
895
896
897 // If we're at the end of the string, then return the RDN.
898 if (pos >= length)
899 {
900 return rdn;
901 }
902
903
904 // If the next character is a comma or semicolon, then that is
905 // not allowed. It would be legal for a DN but not an RDN.
906 if ((c == ',') || (c == ';'))
907 {
908 Message message =
909 ERR_RDN_UNEXPECTED_COMMA.get(rdnString, pos);
910 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
911 message);
912 }
913
914
915 // If the next character is anything but a plus sign, then it is
916 // illegal.
917 if (c != '+')
918 {
919 Message message =
920 ERR_RDN_ILLEGAL_CHARACTER.get(rdnString, c, pos);
921 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
922 message);
923 }
924 }
925 }
926
927
928
929 /**
930 * Creates a duplicate of this RDN that can be modified without
931 * impacting this RDN.
932 *
933 * @return A duplicate of this RDN that can be modified without
934 * impacting this RDN.
935 */
936 public RDN duplicate()
937 {
938 AttributeType[] newTypes = new AttributeType[numValues];
939 System.arraycopy(attributeTypes, 0, newTypes, 0, numValues);
940
941 String[] newNames = new String[numValues];
942 System.arraycopy(attributeNames, 0, newNames, 0, numValues);
943
944 AttributeValue[] newValues = new AttributeValue[numValues];
945 System.arraycopy(attributeValues, 0, newValues, 0, numValues);
946
947 return new RDN(newTypes, newNames, newValues);
948 }
949
950
951
952 /**
953 * Indicates whether the provided object is equal to this RDN. It
954 * will only be considered equal if it is an RDN object that
955 * contains the same number of elements in the same order with the
956 * same types and normalized values.
957 *
958 * @param o The object for which to make the determination.
959 *
960 * @return <CODE>true</CODE> if it is determined that the provided
961 * object is equal to this RDN, or <CODE>false</CODE> if
962 * not.
963 */
964 public boolean equals(Object o)
965 {
966 if (this == o)
967 {
968 return true;
969 }
970
971 if ((o == null) || (! (o instanceof RDN)))
972 {
973 return false;
974 }
975
976 RDN rdn = (RDN) o;
977 return toNormalizedString().equals(rdn.toNormalizedString());
978 }
979
980
981
982 /**
983 * Retrieves the hash code for this RDN. It will be calculated as
984 * the sum of the hash codes of the types and values.
985 *
986 * @return The hash code for this RDN.
987 */
988 public int hashCode()
989 {
990 return toNormalizedString().hashCode();
991 }
992
993
994
995 /**
996 * Retrieves a string representation of this RDN.
997 *
998 * @return A string representation of this RDN.
999 */
1000 public String toString()
1001 {
1002 if (rdnString == null)
1003 {
1004 StringBuilder buffer = new StringBuilder();
1005
1006 buffer.append(attributeNames[0]);
1007 buffer.append("=");
1008
1009 String s = attributeValues[0].getStringValue();
1010 buffer.append(getDNValue(s));
1011
1012 for (int i=1; i < numValues; i++)
1013 {
1014 buffer.append("+");
1015 buffer.append(attributeNames[i]);
1016 buffer.append("=");
1017
1018 s = attributeValues[i].getStringValue();
1019 buffer.append(getDNValue(s));
1020 }
1021
1022 rdnString = buffer.toString();
1023 }
1024
1025 return rdnString;
1026 }
1027
1028
1029
1030 /**
1031 * Appends a string representation of this RDN to the provided
1032 * buffer.
1033 *
1034 * @param buffer The buffer to which the string representation
1035 * should be appended.
1036 */
1037 public void toString(StringBuilder buffer)
1038 {
1039 buffer.append(toString());
1040 }
1041
1042
1043
1044 /**
1045 * Retrieves a normalized string representation of this RDN.
1046 *
1047 * @return A normalized string representation of this RDN.
1048 */
1049 public String toNormalizedString()
1050 {
1051 if (normalizedRDN == null)
1052 {
1053 StringBuilder buffer = new StringBuilder();
1054 toNormalizedString(buffer);
1055 }
1056
1057 return normalizedRDN;
1058 }
1059
1060
1061
1062 /**
1063 * Appends a normalized string representation of this RDN to the
1064 * provided buffer.
1065 *
1066 * @param buffer The buffer to which to append the information.
1067 */
1068 public void toNormalizedString(StringBuilder buffer)
1069 {
1070 if (normalizedRDN != null)
1071 {
1072 buffer.append(normalizedRDN);
1073 return;
1074 }
1075
1076 boolean bufferEmpty = (buffer.length() == 0);
1077
1078 if (attributeNames.length == 1)
1079 {
1080 toLowerCase(attributeTypes[0].getNameOrOID(), buffer);
1081 buffer.append('=');
1082
1083 try
1084 {
1085 String s = attributeValues[0].getNormalizedStringValue();
1086 buffer.append(getDNValue(s));
1087 }
1088 catch (Exception e)
1089 {
1090 if (debugEnabled())
1091 {
1092 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1093 }
1094
1095 String s = attributeValues[0].getStringValue();
1096 buffer.append(getDNValue(s));
1097 }
1098 }
1099 else
1100 {
1101 TreeSet<String> rdnElementStrings = new TreeSet<String>();
1102
1103 for (int i=0; i < attributeNames.length; i++)
1104 {
1105 StringBuilder b2 = new StringBuilder();
1106 toLowerCase(attributeTypes[i].getNameOrOID(), b2);
1107 b2.append('=');
1108
1109 try
1110 {
1111 String s = attributeValues[i].getNormalizedStringValue();
1112 b2.append(getDNValue(s));
1113 }
1114 catch (Exception e)
1115 {
1116 if (debugEnabled())
1117 {
1118 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1119 }
1120
1121 String s = attributeValues[i].getStringValue();
1122 b2.append(getDNValue(s));
1123 }
1124
1125 rdnElementStrings.add(b2.toString());
1126 }
1127
1128 Iterator<String> iterator = rdnElementStrings.iterator();
1129 buffer.append(iterator.next());
1130
1131 while (iterator.hasNext())
1132 {
1133 buffer.append('+');
1134 buffer.append(iterator.next());
1135 }
1136 }
1137
1138 if (bufferEmpty)
1139 {
1140 normalizedRDN = buffer.toString();
1141 }
1142 }
1143
1144
1145
1146 /**
1147 * Compares this RDN with the provided RDN based on an alphabetic
1148 * comparison of the attribute names and values.
1149 *
1150 * @param rdn The RDN against which to compare this RDN.
1151 *
1152 * @return A negative integer if this RDN should come before the
1153 * provided RDN, a positive integer if this RDN should come
1154 * after the provided RDN, or zero if there is no
1155 * difference with regard to ordering.
1156 */
1157 public int compareTo(RDN rdn)
1158 {
1159 if ((attributeTypes.length == 1) &&
1160 (rdn.attributeTypes.length == 1))
1161 {
1162 if (attributeTypes[0].equals(rdn.attributeTypes[0]))
1163 {
1164 OrderingMatchingRule omr =
1165 attributeTypes[0].getOrderingMatchingRule();
1166 if (omr == null)
1167 {
1168 try
1169 {
1170 return attributeValues[0].getNormalizedStringValue().
1171 compareTo(rdn.attributeValues[0].
1172 getNormalizedStringValue());
1173 }
1174 catch (Exception e)
1175 {
1176 if (debugEnabled())
1177 {
1178 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1179 }
1180
1181 return attributeValues[0].getStringValue().
1182 compareTo(rdn.attributeValues[0].
1183 getStringValue());
1184 }
1185 }
1186 else
1187 {
1188 try
1189 {
1190 return omr.compareValues(
1191 attributeValues[0].getNormalizedValue(),
1192 rdn.attributeValues[0].getNormalizedValue());
1193 }
1194 catch (Exception e)
1195 {
1196 if (debugEnabled())
1197 {
1198 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1199 }
1200
1201 return omr.compareValues(
1202 attributeValues[0].getValue(),
1203 rdn.attributeValues[0].getValue());
1204 }
1205 }
1206 }
1207 else
1208 {
1209 String name1 = toLowerCase(attributeTypes[0].getNameOrOID());
1210 String name2 =
1211 toLowerCase(rdn.attributeTypes[0].getNameOrOID());
1212 return name1.compareTo(name2);
1213 }
1214 }
1215
1216 if (equals(rdn))
1217 {
1218 return 0;
1219 }
1220
1221 TreeMap<String,AttributeType> typeMap1 =
1222 new TreeMap<String,AttributeType>();
1223 TreeMap<String,AttributeValue> valueMap1 =
1224 new TreeMap<String,AttributeValue>();
1225 for (int i=0; i < attributeTypes.length; i++)
1226 {
1227 String lowerName =
1228 toLowerCase(attributeTypes[i].getNameOrOID());
1229 typeMap1.put(lowerName, attributeTypes[i]);
1230 valueMap1.put(lowerName, attributeValues[i]);
1231 }
1232
1233 TreeMap<String,AttributeType> typeMap2 =
1234 new TreeMap<String,AttributeType>();
1235 TreeMap<String,AttributeValue> valueMap2 =
1236 new TreeMap<String,AttributeValue>();
1237 for (int i=0; i < rdn.attributeTypes.length; i++)
1238 {
1239 String lowerName =
1240 toLowerCase(rdn.attributeTypes[i].getNameOrOID());
1241 typeMap2.put(lowerName, rdn.attributeTypes[i]);
1242 valueMap2.put(lowerName, rdn.attributeValues[i]);
1243 }
1244
1245 Iterator<String> iterator1 = valueMap1.keySet().iterator();
1246 Iterator<String> iterator2 = valueMap2.keySet().iterator();
1247 String name1 = iterator1.next();
1248 String name2 = iterator2.next();
1249 AttributeType type1 = typeMap1.get(name1);
1250 AttributeType type2 = typeMap2.get(name2);
1251 AttributeValue value1 = valueMap1.get(name1);
1252 AttributeValue value2 = valueMap2.get(name2);
1253
1254 while (true)
1255 {
1256 if (type1.equals(type2))
1257 {
1258 int valueComparison;
1259 OrderingMatchingRule omr = type1.getOrderingMatchingRule();
1260 if (omr == null)
1261 {
1262 try
1263 {
1264 valueComparison =
1265 value1.getNormalizedStringValue().compareTo(
1266 value2.getNormalizedStringValue());
1267 }
1268 catch (Exception e)
1269 {
1270 if (debugEnabled())
1271 {
1272 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1273 }
1274
1275 valueComparison =
1276 value1.getStringValue().compareTo(
1277 value2.getStringValue());
1278 }
1279 }
1280 else
1281 {
1282 try
1283 {
1284 valueComparison =
1285 omr.compareValues(value1.getNormalizedValue(),
1286 value2.getNormalizedValue());
1287 }
1288 catch (Exception e)
1289 {
1290 if (debugEnabled())
1291 {
1292 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1293 }
1294
1295 valueComparison =
1296 omr.compareValues(value1.getValue(),
1297 value2.getValue());
1298 }
1299 }
1300
1301 if (valueComparison == 0)
1302 {
1303 if (! iterator1.hasNext())
1304 {
1305 if (iterator2.hasNext())
1306 {
1307 return -1;
1308 }
1309 else
1310 {
1311 return 0;
1312 }
1313 }
1314
1315 if (! iterator2.hasNext())
1316 {
1317 return 1;
1318 }
1319
1320 name1 = iterator1.next();
1321 name2 = iterator2.next();
1322 type1 = typeMap1.get(name1);
1323 type2 = typeMap2.get(name2);
1324 value1 = valueMap1.get(name1);
1325 value2 = valueMap2.get(name2);
1326 }
1327 else
1328 {
1329 return valueComparison;
1330 }
1331 }
1332 else
1333 {
1334 return name1.compareTo(name2);
1335 }
1336 }
1337 }
1338 }
1339