mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

matthew_swift
28.47.2010 f2160f4bd1c8ac67e5a86a6710d431e8932877f9
sdk/src/org/opends/sdk/RDN.java
@@ -22,329 +22,72 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 */
package org.opends.sdk;
import static com.sun.opends.sdk.messages.Messages.*;
import static com.sun.opends.sdk.util.StaticUtils.*;
import static com.sun.opends.sdk.messages.Messages.ERR_RDN_TYPE_NOT_FOUND;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.opends.sdk.schema.*;
import org.opends.sdk.schema.AttributeType;
import org.opends.sdk.schema.Schema;
import org.opends.sdk.schema.UnknownSchemaElementException;
import com.sun.opends.sdk.util.*;
import com.sun.opends.sdk.util.Iterators;
import com.sun.opends.sdk.util.SubstringReader;
/**
 * A relative distinguished name (RDN) as defined in RFC 4512 section
 * 2.3 is the name of an entry relative to its immediate superior. An
 * RDN is composed of an unordered set of one or more attribute value
 * assertions (AVA) consisting of an attribute description with zero
 * options and an attribute value. These AVAs are chosen to match
 * attribute values (each a distinguished value) of the entry.
 * A relative distinguished name (RDN) as defined in RFC 4512 section 2.3 is the
 * name of an entry relative to its immediate superior. An RDN is composed of an
 * unordered set of one or more attribute value assertions (AVA) consisting of
 * an attribute description with zero options and an attribute value. These AVAs
 * are chosen to match attribute values (each a distinguished value) of the
 * entry.
 * <p>
 * An entry's relative distinguished name must be unique among all
 * immediate subordinates of the entry's immediate superior (i.e. all
 * siblings).
 * An entry's relative distinguished name must be unique among all immediate
 * subordinates of the entry's immediate superior (i.e. all siblings).
 * <p>
 * The following are examples of string representations of RDNs:
 *
 *
 * <pre>
 * uid=12345
 * ou=Engineering
 * cn=Kurt Zeilenga+L=Redwood Shores
 * </pre>
 *
 * The last is an example of a multi-valued RDN; that is, an RDN
 * composed of multiple AVAs.
 *
 * @see <a href="http://tools.ietf.org/html/rfc4512#section-2.3">RFC
 *      4512 - Lightweight Directory Access Protocol (LDAP): Directory
 *      Information Models </a>
 *
 * The last is an example of a multi-valued RDN; that is, an RDN composed of
 * multiple AVAs.
 *
 * @see <a href="http://tools.ietf.org/html/rfc4512#section-2.3">RFC 4512 -
 *      Lightweight Directory Access Protocol (LDAP): Directory Information
 *      Models </a>
 */
public final class RDN implements Iterable<RDN.AVA>, Comparable<RDN>
public final class RDN implements Iterable<AVA>, Comparable<RDN>
{
  /**
   * An attribute value assertion (AVA) as defined in RFC 4512 section
   * 2.3 consists of an attribute description with zero options and an
   * attribute value.
   */
  public static final class AVA implements Comparable<AVA>
  {
    private final AttributeType attributeType;
    private final ByteString attributeValue;
    /**
     * Creates a new attribute value assertion (AVA) using the provided
     * attribute type and value.
     *
     * @param attributeType
     *          The attribute type.
     * @param attributeValue
     *          The attribute value.
     * @throws NullPointerException
     *           If {@code attributeType} or {@code attributeValue} was
     *           {@code null}.
     */
    public AVA(AttributeType attributeType, ByteString attributeValue)
        throws NullPointerException
    {
      Validator.ensureNotNull(attributeType, attributeValue);
      this.attributeType = attributeType;
      this.attributeValue = attributeValue;
    }
    /**
     * Creates a new attribute value assertion (AVA) using the provided
     * attribute type and value decoded using the default schema.
     * <p>
     * If {@code attributeValue} is not an instance of {@code
     * ByteString} then it will be converted using the
     * {@link ByteString#valueOf(Object)} method.
     *
     * @param attributeType
     *          The attribute type.
     * @param attributeValue
     *          The attribute value.
     * @throws UnknownSchemaElementException
     *           If {@code attributeType} was not found in the default
     *           schema.
     * @throws NullPointerException
     *           If {@code attributeType} or {@code attributeValue} was
     *           {@code null}.
     */
    public AVA(String attributeType, Object attributeValue)
        throws UnknownSchemaElementException, NullPointerException
    {
      Validator.ensureNotNull(attributeType, attributeValue);
      this.attributeType = Schema.getDefaultSchema().getAttributeType(
          attributeType);
      this.attributeValue = ByteString.valueOf(attributeValue);
    }
    /**
     * {@inheritDoc}
     */
    public int compareTo(AVA ava)
    {
      int result = attributeType.compareTo(ava.attributeType);
      if (result == 0)
      {
        final ByteString nv1 = getNormalizeValue();
        final ByteString nv2 = ava.getNormalizeValue();
        result = nv1.compareTo(nv2);
      }
      return result;
    }
    /**
     * {@inheritDoc}
     */
    public boolean equals(Object obj)
    {
      if (this == obj)
      {
        return true;
      }
      else if (obj instanceof AVA)
      {
        return compareTo((AVA) obj) == 0;
      }
      else
      {
        return false;
      }
    }
    /**
     * Returns the attribute type associated with this AVA.
     *
     * @return The attribute type associated with this AVA.
     */
    public AttributeType getAttributeType()
    {
      return attributeType;
    }
    /**
     * Returns the attribute value associated with this AVA.
     *
     * @return The attribute value associated with this AVA.
     */
    public ByteString getAttributeValue()
    {
      return attributeValue;
    }
    /**
     * {@inheritDoc}
     */
    public int hashCode()
    {
      return attributeType.hashCode() * 31
          + getNormalizeValue().hashCode();
    }
    /**
     * {@inheritDoc}
     */
    public String toString()
    {
      final StringBuilder builder = new StringBuilder();
      return toString(builder).toString();
    }
    private ByteString getNormalizeValue()
    {
      final MatchingRule matchingRule = attributeType
          .getEqualityMatchingRule();
      if (matchingRule != null)
      {
        try
        {
          return matchingRule.normalizeAttributeValue(attributeValue);
        }
        catch (final DecodeException de)
        {
          // Ignore - we'll drop back to the user provided value.
        }
      }
      return attributeValue;
    }
    private StringBuilder toNormalizedString(StringBuilder builder)
    {
      return toString(builder, true);
    }
    private StringBuilder toString(StringBuilder builder)
    {
      return toString(builder, false);
    }
    private StringBuilder toString(StringBuilder builder,
        boolean normalize)
    {
      final ByteString value = normalize ? getNormalizeValue()
          : attributeValue;
      if (!attributeType.getNames().iterator().hasNext())
      {
        builder.append(attributeType.getOID());
        builder.append("=#");
        StaticUtils.toHex(value, builder);
      }
      else
      {
        final String name = attributeType.getNameOrOID();
        if (normalize)
        {
          // Normalizing.
          StaticUtils.toLowerCase(name, builder);
        }
        else
        {
          builder.append(name);
        }
        builder.append("=");
        final Syntax syntax = attributeType.getSyntax();
        if (!syntax.isHumanReadable())
        {
          builder.append("#");
          StaticUtils.toHex(value, builder);
        }
        else
        {
          final String str = value.toString();
          char c;
          for (int si = 0; si < str.length(); si++)
          {
            c = str.charAt(si);
            if (c == ' ' || c == '#' || c == '"' || c == '+'
                || c == ',' || c == ';' || c == '<' || c == '='
                || c == '>' || c == '\\' || c == '\u0000')
            {
              builder.append('\\');
            }
            builder.append(c);
          }
        }
      }
      return builder;
    }
  }
  private static final char[] SPECIAL_CHARS = new char[] { '\"', '+',
      ',', ';', '<', '>', ' ', '#', '=', '\\' };
  private static final char[] DELIMITER_CHARS = new char[] { '+', ',',
      ';' };
  private static final char[] DQUOTE_CHAR = new char[] { '\"' };
  private static final Comparator<AVA> ATV_COMPARATOR = new Comparator<AVA>()
  {
    public int compare(AVA o1, AVA o2)
    {
      return o1.getAttributeType().compareTo(o2.getAttributeType());
    }
  };
  /**
   * Parses the provided LDAP string representation of an RDN using the
   * default schema.
   *
   * Parses the provided LDAP string representation of an RDN using the default
   * schema.
   *
   * @param rdn
   *          The LDAP string representation of a RDN.
   * @return The parsed RDN.
   * @throws LocalizedIllegalArgumentException
   *           If {@code rdn} is not a valid LDAP string representation
   *           of a RDN.
   *           If {@code rdn} is not a valid LDAP string representation of a
   *           RDN.
   * @throws NullPointerException
   *           If {@code rdn} was {@code null}.
   */
  public static RDN valueOf(String rdn)
      throws LocalizedIllegalArgumentException
  public static RDN valueOf(final String rdn)
      throws LocalizedIllegalArgumentException, NullPointerException
  {
    return valueOf(rdn, Schema.getDefaultSchema());
  }
@@ -352,22 +95,22 @@
  /**
   * Parses the provided LDAP string representation of a RDN using the
   * provided schema.
   *
   * Parses the provided LDAP string representation of a RDN using the provided
   * schema.
   *
   * @param rdn
   *          The LDAP string representation of a RDN.
   * @param schema
   *          The schema to use when parsing the RDN.
   * @return The parsed RDN.
   * @throws LocalizedIllegalArgumentException
   *           If {@code rdn} is not a valid LDAP string representation
   *           of a RDN.
   *           If {@code rdn} is not a valid LDAP string representation of a
   *           RDN.
   * @throws NullPointerException
   *           If {@code rdn} or {@code schema} was {@code null}.
   */
  public static RDN valueOf(String rdn, Schema schema)
      throws LocalizedIllegalArgumentException
  public static RDN valueOf(final String rdn, final Schema schema)
      throws LocalizedIllegalArgumentException, NullPointerException
  {
    final SubstringReader reader = new SubstringReader(rdn);
    try
@@ -384,285 +127,13 @@
  private static AVA readAttributeTypeAndValue(SubstringReader reader,
      Schema schema) throws LocalizedIllegalArgumentException,
      UnknownSchemaElementException
  {
    // Skip over any spaces at the beginning.
    reader.skipWhitespaces();
    final AttributeType attribute = readDNAttributeName(reader, schema);
    // Make sure that we're not at the end of the DN string because
    // that would be invalid.
    if (reader.remaining() == 0)
    {
      final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME
          .get(reader.getString(), attribute.getNameOrOID());
      throw new LocalizedIllegalArgumentException(message);
    }
    // The next character must be an equal sign. If it is not, then
    // that's an error.
    char c;
    if ((c = reader.read()) != '=')
    {
      final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_NO_EQUAL.get(reader
          .getString(), attribute.getNameOrOID(), c);
      throw new LocalizedIllegalArgumentException(message);
    }
    // Skip over any spaces after the equal sign.
    reader.skipWhitespaces();
    // Parse the value for this RDN component.
    final ByteString value = readDNAttributeValue(reader);
    return new AVA(attribute, value);
  }
  private static AttributeType readDNAttributeName(
      SubstringReader reader, Schema schema)
      throws LocalizedIllegalArgumentException,
      UnknownSchemaElementException
  {
    int length = 1;
    reader.mark();
    // The next character must be either numeric (for an OID) or
    // alphabetic (for
    // an attribute description).
    char c = reader.read();
    if (isDigit(c))
    {
      boolean lastWasPeriod = false;
      do
      {
        if (c == '.')
        {
          if (lastWasPeriod)
          {
            final LocalizableMessage message = ERR_ATTR_SYNTAX_OID_CONSECUTIVE_PERIODS
                .get(reader.getString(), reader.pos() - 1);
            throw new LocalizedIllegalArgumentException(message);
          }
          else
          {
            lastWasPeriod = true;
          }
        }
        else if (!isDigit(c))
        {
          // This must have been an illegal character.
          final LocalizableMessage message = ERR_ATTR_SYNTAX_OID_ILLEGAL_CHARACTER
              .get(reader.getString(), reader.pos() - 1);
          throw new LocalizedIllegalArgumentException(message);
        }
        else
        {
          lastWasPeriod = false;
        }
        length++;
      } while ((c = reader.read()) != '=');
    }
    if (isAlpha(c))
    {
      // This must be an attribute description. In this case, we will
      // only
      // accept alphabetic characters, numeric digits, and the hyphen.
      while ((c = reader.read()) != '=')
      {
        if (length == 0 && !isAlpha(c))
        {
          // This is an illegal character.
          final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR
              .get(reader.getString(), c, reader.pos() - 1);
          throw new LocalizedIllegalArgumentException(message);
        }
        if (!isAlpha(c) && !isDigit(c) && c != '-')
        {
          // This is an illegal character.
          final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR
              .get(reader.getString(), c, reader.pos() - 1);
          throw new LocalizedIllegalArgumentException(message);
        }
        length++;
      }
    }
    else
    {
      final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(
          reader.getString(), c, reader.pos() - 1);
      throw new LocalizedIllegalArgumentException(message);
    }
    reader.reset();
    // Return the position of the first non-space character after the
    // token.
    return schema.getAttributeType(reader.read(length));
  }
  private static ByteString readDNAttributeValue(SubstringReader reader)
      throws LocalizedIllegalArgumentException
  {
    // All leading spaces have already been stripped so we can start
    // reading the value. However, it may be empty so check for that.
    if (reader.remaining() == 0)
    {
      return ByteString.empty();
    }
    reader.mark();
    // Look at the first character. If it is an octothorpe (#), then
    // that means that the value should be a hex string.
    char c = reader.read();
    int length = 0;
    if (c == '#')
    {
      // The first two characters must be hex characters.
      reader.mark();
      if (reader.remaining() < 2)
      {
        final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT
            .get(reader.getString());
        throw new LocalizedIllegalArgumentException(message);
      }
      for (int i = 0; i < 2; i++)
      {
        c = reader.read();
        if (isHexDigit(c))
        {
          length++;
        }
        else
        {
          final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT
              .get(reader.getString(), c);
          throw new LocalizedIllegalArgumentException(message);
        }
      }
      // The rest of the value must be a multiple of two hex
      // characters. The end of the value may be designated by the
      // end of the DN, a comma or semicolon, or a space.
      while (reader.remaining() > 0)
      {
        c = reader.read();
        if (isHexDigit(c))
        {
          length++;
          if (reader.remaining() > 0)
          {
            c = reader.read();
            if (isHexDigit(c))
            {
              length++;
            }
            else
            {
              final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT
                  .get(reader.getString(), c);
              throw new LocalizedIllegalArgumentException(message);
            }
          }
          else
          {
            final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT
                .get(reader.getString());
            throw new LocalizedIllegalArgumentException(message);
          }
        }
        else if ((c == ' ') || (c == ',') || (c == ';'))
        {
          // This denotes the end of the value.
          break;
        }
        else
        {
          final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT
              .get(reader.getString(), c);
          throw new LocalizedIllegalArgumentException(message);
        }
      }
      // At this point, we should have a valid hex string. Convert it
      // to a byte array and set that as the value of the provided
      // octet string.
      try
      {
        reader.reset();
        return ByteString
            .wrap(hexStringToByteArray(reader.read(length)));
      }
      catch (final Exception e)
      {
        final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE
            .get(reader.getString(), String.valueOf(e));
        throw new LocalizedIllegalArgumentException(message);
      }
    }
    // If the first character is a quotation mark, then the value
    // should continue until the corresponding closing quotation mark.
    else if (c == '"')
    {
      try
      {
        return StaticUtils.evaluateEscapes(reader, DQUOTE_CHAR, false);
      }
      catch (final DecodeException e)
      {
        throw new LocalizedIllegalArgumentException(e
            .getMessageObject());
      }
    }
    // Otherwise, use general parsing to find the end of the value.
    else
    {
      reader.reset();
      ByteString bytes;
      try
      {
        bytes = StaticUtils.evaluateEscapes(reader, SPECIAL_CHARS,
            DELIMITER_CHARS, true);
      }
      catch (final DecodeException e)
      {
        throw new LocalizedIllegalArgumentException(e
            .getMessageObject());
      }
      if (bytes.length() == 0)
      {
        // We don't allow an empty attribute value.
        final LocalizableMessage message = ERR_ATTR_SYNTAX_DN_INVALID_REQUIRES_ESCAPE_CHAR
            .get(reader.getString(), reader.pos());
        throw new LocalizedIllegalArgumentException(message);
      }
      return bytes;
    }
  }
  // FIXME: ensure that the decoded RDN does not contain multiple AVAs
  // with the same type.
  static RDN decode(String rdnString, SubstringReader reader,
      Schema schema) throws LocalizedIllegalArgumentException,
  static RDN decode(final String rdnString, final SubstringReader reader,
      final Schema schema) throws LocalizedIllegalArgumentException,
      UnknownSchemaElementException
  {
    final AVA firstAVA = readAttributeTypeAndValue(reader, schema);
    final AVA firstAVA = AVA.decode(reader, schema);
    // Skip over any spaces that might be after the attribute value.
    reader.skipWhitespaces();
@@ -675,21 +146,22 @@
      do
      {
        avas.add(readAttributeTypeAndValue(reader, schema));
        avas.add(AVA.decode(reader, schema));
        // Skip over any spaces that might be after the attribute value.
        reader.skipWhitespaces();
        reader.mark();
      } while (reader.read() == '+');
      }
      while (reader.remaining() > 0 && reader.read() == '+');
      reader.reset();
      return new RDN(avas.toArray(new AVA[avas.size()]), rdnString);
      return new RDN(avas.toArray(new AVA[avas.size()]), null);
    }
    else
    {
      reader.reset();
      return new RDN(new AVA[] { firstAVA }, rdnString);
      return new RDN(new AVA[] { firstAVA }, null);
    }
  }
@@ -706,16 +178,16 @@
  /**
   * Creates a new RDN using the provided attribute type and value.
   *
   *
   * @param attributeType
   *          The attribute type.
   * @param attributeValue
   *          The attribute value.
   * @throws NullPointerException
   *           If {@code attributeType} or {@code attributeValue} was
   *           {@code null}.
   *           If {@code attributeType} or {@code attributeValue} was {@code
   *           null}.
   */
  public RDN(AttributeType attributeType, ByteString attributeValue)
  public RDN(final AttributeType attributeType, final ByteString attributeValue)
      throws NullPointerException
  {
    this.avas = new AVA[] { new AVA(attributeType, attributeValue) };
@@ -724,25 +196,23 @@
  /**
   * Creates a new RDN using the provided attribute type and value
   * decoded using the default schema.
   * Creates a new RDN using the provided attribute type and value decoded using
   * the default schema.
   * <p>
   * If {@code attributeValue} is not an instance of {@code ByteString}
   * then it will be converted using the
   * {@link ByteString#valueOf(Object)} method.
   *
   * If {@code attributeValue} is not an instance of {@code ByteString} then it
   * will be converted using the {@link ByteString#valueOf(Object)} method.
   *
   * @param attributeType
   *          The attribute type.
   * @param attributeValue
   *          The attribute value.
   * @throws UnknownSchemaElementException
   *           If {@code attributeType} was not found in the default
   *           schema.
   *           If {@code attributeType} was not found in the default schema.
   * @throws NullPointerException
   *           If {@code attributeType} or {@code attributeValue} was
   *           {@code null}.
   *           If {@code attributeType} or {@code attributeValue} was {@code
   *           null}.
   */
  public RDN(String attributeType, Object attributeValue)
  public RDN(final String attributeType, final Object attributeValue)
      throws UnknownSchemaElementException, NullPointerException
  {
    this.avas = new AVA[] { new AVA(attributeType, attributeValue) };
@@ -750,7 +220,7 @@
  private RDN(AVA[] avas, String stringValue)
  private RDN(final AVA[] avas, final String stringValue)
  {
    this.avas = avas;
    this.stringValue = stringValue;
@@ -761,14 +231,14 @@
  /**
   * {@inheritDoc}
   */
  public int compareTo(RDN rdn)
  public int compareTo(final RDN rdn)
  {
    final int sz1 = avas.length;
    final int sz2 = rdn.avas.length;
    if (sz1 != sz2)
    {
      return sz1 - sz2;
      return sz1 - sz2 > 0 ? 1 : -1;
    }
    if (sz1 == 1)
@@ -779,11 +249,11 @@
    // Need to sort the AVAs before comparing.
    final AVA[] a1 = new AVA[sz1];
    System.arraycopy(avas, 0, a1, 0, sz1);
    Arrays.sort(a1, ATV_COMPARATOR);
    Arrays.sort(a1);
    final AVA[] a2 = new AVA[sz1];
    System.arraycopy(rdn.avas, 0, a2, 0, sz1);
    Arrays.sort(a2, ATV_COMPARATOR);
    Arrays.sort(a2);
    for (int i = 0; i < sz1; i++)
    {
@@ -802,7 +272,8 @@
  /**
   * {@inheritDoc}
   */
  public boolean equals(Object obj)
  @Override
  public boolean equals(final Object obj)
  {
    if (this == obj)
    {
@@ -821,15 +292,15 @@
  /**
   * Returns the attribute value contained in this RDN which is
   * associated with the provided attribute type, or {@code null} if
   * this RDN does not include such an attribute value.
   *
   * Returns the attribute value contained in this RDN which is associated with
   * the provided attribute type, or {@code null} if this RDN does not include
   * such an attribute value.
   *
   * @param attributeType
   *          The attribute type.
   * @return The attribute value.
   */
  public ByteString getAttributeValue(AttributeType attributeType)
  public ByteString getAttributeValue(final AttributeType attributeType)
  {
    for (final AVA ava : avas)
    {
@@ -845,7 +316,7 @@
  /**
   * Returns the first AVA contained in this RDN.
   *
   *
   * @return The first AVA contained in this RDN.
   */
  public AVA getFirstAVA()
@@ -858,13 +329,14 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public int hashCode()
  {
    // Avoid an algorithm that requires the AVAs to be sorted.
    int hash = 0;
    for (int i = 0; i < avas.length; i++)
    for (final AVA ava : avas)
    {
      hash += avas[i].hashCode();
      hash += ava.hashCode();
    }
    return hash;
  }
@@ -873,9 +345,9 @@
  /**
   * Returns {@code true} if this RDN contains more than one AVA.
   *
   * @return {@code true} if this RDN contains more than one AVA,
   *         otherwise {@code false}.
   *
   * @return {@code true} if this RDN contains more than one AVA, otherwise
   *         {@code false}.
   */
  public boolean isMultiValued()
  {
@@ -885,13 +357,13 @@
  /**
   * Returns an iterator of the AVAs contained in this RDN. The AVAs
   * will be returned in the user provided order.
   * Returns an iterator of the AVAs contained in this RDN. The AVAs will be
   * returned in the user provided order.
   * <p>
   * Attempts to remove AVAs using an iterator's {@code remove()} method
   * are not permitted and will result in an {@code
   * UnsupportedOperationException} being thrown.
   *
   * Attempts to remove AVAs using an iterator's {@code remove()} method are not
   * permitted and will result in an {@code UnsupportedOperationException} being
   * thrown.
   *
   * @return An iterator of the AVAs contained in this RDN.
   */
  public Iterator<AVA> iterator()
@@ -903,7 +375,7 @@
  /**
   * Returns the number of AVAs in this RDN.
   *
   *
   * @return The number of AVAs in this RDN.
   */
  public int size()
@@ -915,12 +387,13 @@
  /**
   * Returns the RFC 4514 string representation of this RDN.
   *
   *
   * @return The RFC 4514 string representation of this RDN.
   * @see <a href="http://tools.ietf.org/html/rfc4514">RFC 4514 -
   *      Lightweight Directory Access Protocol (LDAP): String
   *      Representation of Distinguished Names </a>
   * @see <a href="http://tools.ietf.org/html/rfc4514">RFC 4514 - Lightweight
   *      Directory Access Protocol (LDAP): String Representation of
   *      Distinguished Names </a>
   */
  @Override
  public String toString()
  {
    // We don't care about potential race conditions here.
@@ -930,7 +403,7 @@
      avas[0].toString(builder);
      for (int i = 1; i < avas.length; i++)
      {
        builder.append(',');
        builder.append('+');
        avas[i].toString(builder);
      }
      stringValue = builder.toString();
@@ -940,7 +413,7 @@
  StringBuilder toNormalizedString(StringBuilder builder)
  StringBuilder toNormalizedString(final StringBuilder builder)
  {
    final int sz = avas.length;
    if (sz == 1)
@@ -952,12 +425,12 @@
      // Need to sort the AVAs before comparing.
      final AVA[] a = new AVA[sz];
      System.arraycopy(avas, 0, a, 0, sz);
      Arrays.sort(a, ATV_COMPARATOR);
      a[0].toString(builder);
      Arrays.sort(a);
      // Normalize the first AVA.
      a[0].toNormalizedString(builder);
      for (int i = 1; i < sz; i++)
      {
        builder.append(',');
        builder.append('+');
        a[i].toNormalizedString(builder);
      }
@@ -967,7 +440,7 @@
  StringBuilder toString(StringBuilder builder)
  StringBuilder toString(final StringBuilder builder)
  {
    return builder.append(toString());
  }