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