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