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

neil_a_wilson
15.21.2007 0df28b32026c9de2eeada877df13ae99152efde2
Rewrite the DN and RDN code for significant performance improvements,
especially in the area of DN parsing.

OpenDS Issue Number: 1235
14 files modified
6841 ■■■■■ changed files
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java 21 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/makeldif/TemplateEntry.java 47 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/AttributeValue.java 117 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/DN.java 3265 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/RDN.java 2477 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java 2 ●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java 9 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestAddResponseProtocolOp.java 7 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestCompareResponseProtocolOp.java 7 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestDeleteResponseProtocolOp.java 7 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyDNResponseProtocolOp.java 7 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyResponseProtocolOp.java 7 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestDN.java 373 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestRDN.java 495 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.backends.jeb;
@@ -53,6 +53,7 @@
import org.opends.server.types.Entry;
import org.opends.server.types.LockType;
import org.opends.server.types.Modification;
import org.opends.server.types.RDN;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchScope;
import org.opends.server.util.StaticUtils;
@@ -3100,8 +3101,22 @@
   */
  public static DN modDN(DN oldDN, int oldSuffixLen, DN newSuffixDN)
  {
    DN localName = oldDN.getLocalName(oldSuffixLen);
    return newSuffixDN.concat(localName);
    int oldDNNumComponents    = oldDN.getNumComponents();
    int oldDNKeepComponents   = oldDNNumComponents - oldSuffixLen;
    int newSuffixDNComponents = newSuffixDN.getNumComponents();
    RDN[] newDNComponents = new RDN[oldDNKeepComponents+newSuffixDNComponents];
    for (int i=0; i < oldDNKeepComponents; i++)
    {
      newDNComponents[i] = oldDN.getRDN(i);
    }
    for (int i=oldDNKeepComponents, j=0; j < newSuffixDNComponents; i++,j++)
    {
      newDNComponents[i] = newSuffixDN.getRDN(j);
    }
    return new DN(newDNComponents);
  }
  /**
opends/src/server/org/opends/server/tools/makeldif/TemplateEntry.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.tools.makeldif;
@@ -148,29 +148,50 @@
  /**
   * Retrieves teh DN for this template entry, if it is known.
   * Retrieves the DN for this template entry, if it is known.
   *
   * @return  The DN for this template entry if it is known, or
   *          <CODE>null</CODE> if it cannot yet be determined.
   */
  public DN getDN()
  {
    if (dn == null) {
      RDN.Builder builder = RDN.createBuilder();
      for (AttributeType type : template.getRDNAttributes()) {
        TemplateValue v = getValue(type);
        if (v == null) {
    if (dn == null)
    {
      RDN rdn;
      AttributeType[] rdnAttrs = template.getRDNAttributes();
      if (rdnAttrs.length == 1)
      {
        AttributeType t = rdnAttrs[0];
        TemplateValue v = getValue(t);
        if (v == null)
        {
          return null;
        }
        AttributeValue value = new AttributeValue(type,
            v.getValue().toString());
        builder.append(type, value);
        AttributeValue value = new AttributeValue(t, v.getValue().toString());
        rdn = new RDN(t, value);
      }
      else
      {
        String[]         names  = new String[rdnAttrs.length];
        AttributeValue[] values = new AttributeValue[rdnAttrs.length];
        for (int i=0; i < rdnAttrs.length; i++)
        {
          AttributeType t = rdnAttrs[i];
          TemplateValue v = getValue(t);
          if (v == null)
          {
            return null;
      }
      dn = parentDN.concat(builder.getInstance());
          names[i]  = t.getPrimaryName();
          values[i] = new AttributeValue(t, v.getValue().toString());
        }
        rdn = new RDN(rdnAttrs, names, values);
      }
      dn = parentDN.concat(rdn);
    }
    return dn;
opends/src/server/org/opends/server/types/AttributeValue.java
@@ -32,6 +32,7 @@
import org.opends.server.protocols.asn1.ASN1OctetString;
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.util.Validator.*;
@@ -257,6 +258,122 @@
  /**
   * Retrieves a string representation of the user-defined form of
   * this attribute value in a form suitable for use in a DN.
   *
   * @return  A string representation of the user-defined form of this
   *          attribute value in a form suitable for use in a DN.
   */
  public String getDNStringValue()
  {
    assert debugEnter(CLASS_NAME, "getDNStringValue");
    return getDNValue(getStringValue());
  }
  /**
   * Retrieves a string representation of the normalized form of this
   * attribute value in a form suitable for use in a DN.
   *
   * @return  A string representation of the normalized form of this
   *          attribute value in a form suitable for use in a DN.
   *
   * @throws  DirectoryException  If an error occurs while trying to
   *                              normalize the value (e.g., if it is
   *                              not acceptable for use with the
   *                              associated equality matching rule).
   */
  public String getNormalizedDNStringValue()
         throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "getNormalizedDNStringValue");
    return getDNValue(getNormalizedStringValue());
  }
  /**
   * Retrieves a version of the provided value in a form that is
   * properly escaped for use in a DN or RDN.
   *
   * @param  value  The value to be represented in a DN-safe form.
   *
   * @return  A version of the provided value in a form that is
   *          properly escaped for use in a DN or RDN.
   */
  private static String getDNValue(String value)
  {
    assert debugEnter(CLASS_NAME, "getDNValue",
                      String.valueOf(value));
    if ((value == null) || (value.length() == 0))
    {
      return "";
    }
    StringBuilder buffer = new StringBuilder(value);
    int length = buffer.length();
    for (int i=0; i < length; i++)
    {
      char c = buffer.charAt(i);
      if ((c < ' ') || (c > '~'))
      {
        buffer.deleteCharAt(i);
        length -= 1;
        for (byte b : getBytes(String.valueOf(c)))
        {
          buffer.insert(i++, "\\");
          buffer.insert(i++, byteToLowerHex(b));
          i++;
          length += 3;
        }
        i -= 1;
      }
      else
      {
        switch (buffer.charAt(i))
        {
          case ',':
          case '+':
          case '"':
          case '\\':
          case '<':
          case '>':
          case ';':
            buffer.insert(i++, '\\');
            length++;
        }
      }
    }
    char c = buffer.charAt(0);
    if ((c == ' ') || (c == '#'))
    {
      buffer.insert(0, '\\');
      length++;
    }
    if (buffer.charAt(length-1) == ' ')
    {
      buffer.insert(length-1, '\\');
      length++;
    }
    return buffer.toString();
  }
  /**
   * Determines whether this attribute value is equal to the provided
   * object.
   *
opends/src/server/org/opends/server/types/DN.java
@@ -22,20 +22,23 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.util.Validator.ensureNotNull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.messages.SchemaMessages.*;
import static org.opends.server.util.StaticUtils.*;
@@ -43,196 +46,58 @@
 * This class defines a data structure for storing and interacting
 * with the distinguished names associated with entries in the
 * Directory Server.
 * <p>
 * All the methods in this class will throw a
 * <code>NullPointerException</code> when provided with
 * <code>null</code> reference parameters unless otherwise stated.
 */
public final class DN implements Comparable<DN>, Serializable {
  // FIXME: Don't store the normalized form and define equals(),
  // hashCode(), and compareTo() in terms of RDN components (which
  // cache their normalized form). This could potentially lead to less
  // memory utilization for very little performance loss.
  // FIXME: Optimize normalization for common use-cases. E.g.
  // concat(DN) can simply join the two normalized forms together.
  // Similarly, getParent() and getLocalName() can avoid
  // recalculating the normalized form and take a substring from the
  // source DN's normalized form (this requires parsing commas, but
  // could save memory).
public class DN
       implements Comparable<DN>, Serializable
{
  /**
   * The fully-qualified name of this class for debugging purposes.
   */
  private static final String CLASS_NAME =
    "org.opends.server.types.DN";
  /**
   * A singleton instance of the null DN (a DN with no components).
   */
  public static DN NULL_DN = new DN();
  /**
   * The serial version identifier required to satisfy the compiler
   * because this class implements the
   * <code>java.io.Serializable</code> interface. This value was
   * generated using the <code>serialver</code> command-line utility
   * <CODE>java.io.Serializable</CODE> interface.  This value was
   * generated using the <CODE>serialver</CODE> command-line utility
   * included with the Java SDK.
   */
  private static final long serialVersionUID = 1184263456768819888L;
  // The number of RDN components that comprise this DN.
  private final int numComponents;
  // The index of the first RDN component (furthest from the root) in
  // this DN.
  private final int offset;
  // The number of RDN components that comprise this DN.
  private int numComponents;
  // The set of RDN components that comprise this DN, arranged with
  // the suffix as the last element.
  private final RDN[] rdnComponents;
  private RDN[] rdnComponents;
  // The cached normalized string representation of this DN.
  private final String normalizedDN;
  // The string representation of this DN.
  private String dnString;
  // A DN comprising of zero RDN components.
  private static final DN EMPTY_DN = new DN(new RDN[0], 0, 0);
  // The normalized string representation of this DN.
  private String normalizedDN;
  /**
   * Creates a new DN comprising of zero RDN components (a null DN or
   * root DSE).
   *
   * @return Returns a new DN comprising of zero RDN components.
   * Creates a new DN with no RDN components (i.e., a null DN or root
   * DSE).
   */
  public static DN nullDN() {
    return EMPTY_DN;
  }
  /**
   * Creates a new <code>DN</code> containing the specified RDN
   * sequence.
   * <p>
   * If the argument RDN sequence is empty, then the effect is the
   * same as if {@link #nullDN()} had been called.
   *
   * @param rdns
   *          The RDN sequence that the new DN will represent.
   * @return Returns a DN that represents the specified RDN sequence
   *         onto the end of this DN.
   */
  public static DN create(RDN... rdns) {
    ensureNotNull(rdns);
    if (rdns.length == 0) {
      return nullDN();
    }
    RDN[] allRDNs = new RDN[rdns.length];
    System.arraycopy(rdns, 0, allRDNs, 0, rdns.length);
    return new DN(allRDNs, 0, rdns.length);
  }
  /**
   * Returns a <code>DN</code> object holding the value of the
   * specified <code>String</code>. The argument is interpreted as
   * representing the LDAP string representation of a DN.
   * <p>
   * This method is identical to {@link #decode(String)}.
   *
   * @param s
   *          The string to be parsed, or <code>null</code> in
   *          which case a <code>nullDN</code> will be returned.
   * @return Returns a <code>DN</code> holding the value represented
   *         by the <code>string</code> argument.
   * @throws DirectoryException
   *           If a problem occurs while trying to decode the provided
   *           string as a DN.
   */
  public static DN valueOf(String s) throws DirectoryException {
    return decode(s);
  }
  /**
   * Decodes the provided ASN.1 octet string as a DN.
   *
   * @param dnString
   *          The ASN.1 octet string to decode as a DN, or
   *          <code>null</code> in which case a <code>nullDN</code>
   *          will be returned.
   * @return The decoded DN.
   * @throws DirectoryException
   *           If a problem occurs while trying to decode the provided
   *           ASN.1 octet string as a DN.
   */
  public static DN decode(ByteString dnString)
      throws DirectoryException {
    assert debugEnter(CLASS_NAME, "decode", String.valueOf(dnString));
    // A null or empty DN is acceptable.
    if (dnString == null) {
      return nullDN();
    }
    byte[] dnBytes = dnString.value();
    int length = dnBytes.length;
    if (length == 0) {
      return nullDN();
    }
    // Use string-based decoder.
    return decode(dnString.stringValue());
  }
  /**
   * Decodes the provided string as a DN.
   *
   * @param dnString
   *          The string to decode as a DN, or <code>null</code> in
   *          which case a <code>nullDN</code> will be returned.
   * @return The decoded DN.
   * @throws DirectoryException
   *           If a problem occurs while trying to decode the provided
   *           string as a DN.
   */
  public static DN decode(String dnString) throws DirectoryException {
    assert debugEnter(CLASS_NAME, "decode", String.valueOf(dnString));
    // A null or empty DN is acceptable.
    if (dnString == null || dnString.length() == 0) {
      return nullDN();
    }
    // Parse the first RDN.
    int pos = 0;
    RDN.Builder builder = RDN.createBuilder();
    pos = builder.parse(dnString, pos, true);
    if (builder.isEmpty()) {
      return nullDN();
    } else {
      // Parse the remaining RDNs.
      List<RDN> rdns = new ArrayList<RDN>(10);
      rdns.add(builder.getInstance());
      while (pos >= 0) {
        // Skip the RDN separator.
        pos++;
        // Parse the next RDN.
        builder.clear();
        pos = builder.parse(dnString, pos, false);
        rdns.add(builder.getInstance());
      }
      // Parse successful - create the DN.
      int sz = rdns.size();
      return new DN(rdns.toArray(new RDN[sz]), 0, sz);
    }
  public DN()
  {
    this(new RDN[0]);
  }
@@ -241,279 +106,69 @@
   * Creates a new DN with the provided set of RDNs, arranged with the
   * suffix as the last element.
   *
   * @param rdnComponents
   *          The set of RDN components that make up this DN.
   * @param offset
   *          The index of the first RDN component (furthest from the
   *          root) in this DN.
   * @param count
   *          The number of RDNs to include in this DN.
   */
  private DN(RDN[] rdnComponents, int offset, int count) {
    assert debugConstructor(CLASS_NAME,
        String.valueOf(rdnComponents), String.valueOf(offset));
    this.rdnComponents = rdnComponents;
    this.offset = offset;
    this.numComponents = count;
    this.normalizedDN = normalize();
  }
  /**
   * Concatenates the specified DN to the end of this DN.
   * <p>
   * If the argument DN is the null DN, then this DN is returned.
   * Conversely, if this DN is the null DN then the argument DN will
   * be returned. Otherwise, the returned DN will be a descendent of
   * @param  rdnComponents  The set of RDN components that make up
   * this DN.
   *
   * @param localName
   *          The DN that will be concatenated to the end of this DN.
   * @return Returns a DN that represents the concatenation of the
   *         specified DN onto the end of this DN.
   */
  public DN concat(DN localName) {
    ensureNotNull(localName);
    if (localName.isNullDN()) {
      return this;
    }
    if (isNullDN()) {
      return localName;
    }
    RDN[] allRDNs = new RDN[numComponents + localName.numComponents];
    System.arraycopy(localName.rdnComponents, localName.offset,
        allRDNs, 0, localName.numComponents);
    System.arraycopy(rdnComponents, offset, allRDNs,
        localName.numComponents, numComponents);
    return new DN(allRDNs, 0, allRDNs.length);
  }
  /**
   * Concatenates the specified RDN sequence to the end of this DN.
   * <p>
   * If the argument RDN sequence is empty, then this DN is returned.
   * Otherwise, the returned DN will be a descendent of this DN.
   *
   * @param rdns
   *          The RDN sequence that will be concatenated to the end of
   *          this DN.
   * @return Returns a DN that represents the concatenation of the
   *         specified RDN sequence onto the end of this DN.
   */
  public DN concat(RDN... rdns) {
    ensureNotNull(rdns);
    if (rdns.length == 0) {
      return this;
    }
    // Don't check if this is a nullDN, because were going to copy the
    // RDN sequence anyway.
    RDN[] allRDNs = new RDN[rdns.length + numComponents];
    System.arraycopy(rdns, 0, allRDNs, 0, rdns.length);
    System.arraycopy(rdnComponents, offset, allRDNs,
        rdns.length, numComponents);
    return new DN(allRDNs, 0, allRDNs.length);
  }
  /**
   * Get the parent DN of this DN.
   *
   * @return Returns the parent DN of this DN, or <code>null</code>
   *         if this DN does not have a parent (i.e. it is a DN having
   *         a single RDN component, or the null DN).
   */
  public DN getParent() {
    if (numComponents <= 1) {
      return null;
    } else {
      return new DN(rdnComponents, offset + 1, numComponents - 1);
    }
  }
  /**
   * Create a local name (a relative DN) from this DN.
   * <p>
   * Examples: <blockquote>
   *
   * <pre>
   * DN dn = DN.decode(&quot;cn=john,o=example,c=us&quot;);
   *
   * dn.getLocalName(0) returns &quot;cn=john,o=example,c=us&quot;
   * dn.getLocalName(1) returns &quot;cn=john,o=example&quot;
   * dn.getLocalName(3) returns &quot;&quot; (null DN).
   * </pre>
   *
   * </blockquote>
   *
   * @param beginIndex
   *          The index of the first RDN component (nearest the root),
   *          inclusive.
   * @return Returns the specified local name.
   * @throws IndexOutOfBoundsException
   *           If <code>beginIndex</code> is negative, or greater
   *           than the number of RDN components in this DN.
   */
  public DN getLocalName(int beginIndex)
      throws IndexOutOfBoundsException {
    return getLocalName(beginIndex, numComponents);
  }
  /**
   * Create a local name (a relative DN) from this DN.
   * <p>
   * Examples: <blockquote>
   *
   * <pre>
   * DN dn = DN.decode(&quot;cn=john,o=example,c=us&quot;);
   *
   * dn.getLocalName(0, 3) returns &quot;cn=john,o=example,c=us&quot;
   * dn.getLocalName(1, 2) returns &quot;o=example&quot;
   * dn.getLocalName(2, 2) returns &quot;&quot; (null DN).
   * </pre>
   *
   * </blockquote>
   *
   * @param beginIndex
   *          The index of the first RDN component (nearest the root),
   *          inclusive.
   * @param endIndex
   *          The index of the last RDN component (furthest from the
   *          root), exclusive.
   * @return Returns the specified local name.
   * @throws IndexOutOfBoundsException
   *           If <code>beginIndex</code> is negative, or
   *           <code>endIndex</code> is larger than the number of
   *           RDN components in this DN, or <code>beginIndex</code>
   *           is larger than <code>endIndex</code>.
   */
  public DN getLocalName(int beginIndex, int endIndex)
      throws IndexOutOfBoundsException {
    if (beginIndex < 0) {
      throw new IndexOutOfBoundsException("beginIndex out of range: "
          + beginIndex);
    }
    if (endIndex > numComponents) {
      throw new IndexOutOfBoundsException("endIndex out of range: "
          + endIndex);
    }
    if (beginIndex > endIndex) {
      throw new IndexOutOfBoundsException(
          "beginIndex greater than endIndex");
    }
    if (beginIndex == 0 && endIndex == numComponents) {
      return this;
    } else {
      int i = offset + numComponents - endIndex;
      return new DN(rdnComponents, i, endIndex - beginIndex);
    }
  }
  /**
   * Get the number of RDN components that make up this DN.
   *
   * @return Returns the number of RDN components that make up this
   *         DN.
   */
  public int getNumComponents() {
    assert debugEnter(CLASS_NAME, "getNumComponents");
    return numComponents;
  }
  /**
   * Retrieves the outermost RDN component for this DN (i.e., the one
   * that is furthest from the suffix). This method is equivalent to
   * calling <code>getRDN(0)</code> for non-null DNs.
   *
   * @return The outermost RDN component for this DN, or
   *         <code>null</code> if there are no RDN components in the
   *         DN.
   */
  public RDN getRDN() {
    assert debugEnter(CLASS_NAME, "getRDN");
    if (numComponents == 0) {
      return null;
    } else {
      return getRDN(0);
    }
  }
  /**
   * Get the RDN at the specified index.
   *
   * @param index
   *          The index of the RDN to retrieve, where <code>0</code>
   *          indicates the outermost RDN component (i.e. the one that
   *          is furthest from the suffix).
   * @return Returns the RDN at the specified index.
   * @throws IndexOutOfBoundsException
   *           If <code>index</code> is negative, or greater than or
   *           equal to the number of RDN components in this DN.
   */
  public RDN getRDN(int index) throws IndexOutOfBoundsException {
    if (index < 0) {
      throw new IndexOutOfBoundsException("index out of range: "
          + index);
    }
    if (index >= numComponents) {
      throw new IndexOutOfBoundsException("index out of range: "
          + index);
    }
    return rdnComponents[offset + index];
  }
  /**
   * Retrieves the DN of the entry that is the immediate parent for
   * this entry.
   *
   * @return The DN of the entry that is the immediate parent for this
   *         entry, or <code>null</code> if the entry with this DN
   *         does not have a parent (either because there is only a
   *         single RDN component or because this DN is a suffix
   *         defined in the server).
   */
  public DN getParentDNInSuffix() {
    assert debugEnter(CLASS_NAME, "getParentDNInSuffix");
    if ((numComponents <= 1) ||
        DirectoryServer.isNamingContext(this))
  public DN(RDN[] rdnComponents)
    {
      return null;
    assert debugConstructor(CLASS_NAME,
                            String.valueOf(rdnComponents));
    if (rdnComponents == null)
    {
      this.rdnComponents = new RDN[0];
    }
    else
    {
      this.rdnComponents = rdnComponents;
    }
    return getParent();
    numComponents = this.rdnComponents.length;
    dnString      = null;
    normalizedDN  = toNormalizedString();
  }
  /**
   * Creates a new DN with the provided set of RDNs, arranged with the
   * suffix as the last element.
   *
   * @param  rdnComponents  The set of RDN components that make up
   *                        this DN.
   */
  public DN(ArrayList<RDN> rdnComponents)
  {
    assert debugConstructor(CLASS_NAME,
                            String.valueOf(rdnComponents));
    if ((rdnComponents == null) || rdnComponents.isEmpty())
    {
      this.rdnComponents = new RDN[0];
    }
    else
    {
      this.rdnComponents = new RDN[rdnComponents.size()];
      rdnComponents.toArray(this.rdnComponents);
    }
    numComponents = this.rdnComponents.length;
    dnString      = null;
    normalizedDN  = toNormalizedString();
  }
  /**
   * Retrieves a singleton instance of the null DN.
   *
   * @return  A singleton instance of the null DN.
   */
  public static DN nullDN()
  {
    assert debugEnter(CLASS_NAME, "nullDN");
    return NULL_DN;
  }
@@ -523,10 +178,11 @@
   * the root DSE for the Directory Server, or the authorization DN
   * for an anonymous or unauthenticated client.
   *
   * @return <code>true</code> if this does represent a null DN, or
   *         <code>false</code> if it does not.
   * @return  <CODE>true</CODE> if this does represent a null DN, or
   *          <CODE>false</CODE> if it does not.
   */
  public boolean isNullDN() {
  public boolean isNullDN()
  {
    assert debugEnter(CLASS_NAME, "isNullDN");
    return (numComponents == 0);
@@ -535,28 +191,226 @@
  /**
   * Retrieves the number of RDN components for this DN.
   *
   * @return  The number of RDN components for this DN.
   */
  public int getNumComponents()
  {
    assert debugEnter(CLASS_NAME, "getNumComponents");
    return numComponents;
  }
  /**
   * Retrieves the outermost RDN component for this DN (i.e., the one
   * that is furthest from the suffix).
   *
   * @return  The outermost RDN component for this DN, or
   *          <CODE>null</CODE> if there are no RDN components in the
   *          DN.
   */
  public RDN getRDN()
  {
    assert debugEnter(CLASS_NAME, "getRDN");
    if (numComponents == 0)
    {
      return null;
    }
    else
    {
      return rdnComponents[0];
    }
  }
  /**
   * Retrieves the RDN component at the specified position in the set
   * of components for this DN.
   *
   * @param  pos  The position of the RDN component to retrieve.
   *
   * @return  The RDN component at the specified position in the set
   *          of components for this DN.
   */
  public RDN getRDN(int pos)
  {
    assert debugEnter(CLASS_NAME, "getRDN", String.valueOf(pos));
    return rdnComponents[pos];
  }
  /**
   * Retrieves the DN of the entry that is the immediate parent for
   * this entry.  Note that this method does not take the server's
   * naming context configuration into account when making the
   * determination.
   *
   * @return  The DN of the entry that is the immediate parent for
   *          this entry, or <CODE>null</CODE> if the entry with this
   *          DN does not have a parent.
   */
  public DN getParent()
  {
    assert debugEnter(CLASS_NAME, "getParent");
    if (numComponents <= 1)
    {
      return null;
    }
    RDN[] parentComponents = new RDN[numComponents-1];
    System.arraycopy(rdnComponents, 1, parentComponents, 0,
                     numComponents-1);
    return new DN(parentComponents);
  }
  /**
   * Retrieves the DN of the entry that is the immediate parent for
   * this entry.  This method does take the server's naming context
   * configuration into account, so if the current DN is a naming
   * context for the server, then it will not be considered to have a
   * parent.
   *
   * @return  The DN of the entry that is the immediate parent for
   *          this entry, or <CODE>null</CODE> if the entry with this
   *          DN does not have a parent (either because there is only
   *          a single RDN component or because this DN is a suffix
   *          defined in the server).
   */
  public DN getParentDNInSuffix()
  {
    assert debugEnter(CLASS_NAME, "getParent");
    if ((numComponents <= 1) ||
        DirectoryServer.isNamingContext(this))
    {
      return null;
    }
    RDN[] parentComponents = new RDN[numComponents-1];
    System.arraycopy(rdnComponents, 1, parentComponents, 0,
                     numComponents-1);
    return new DN(parentComponents);
  }
  /**
   * Creates a new DN that is a child of this DN, using the specified
   * RDN.
   *
   * @param  rdn  The RDN for the child of this DN.
   *
   * @return  A new DN that is a child of this DN, using the specified
   *          RDN.
   */
  public DN concat(RDN rdn)
  {
    assert debugEnter(CLASS_NAME, "concat", String.valueOf(rdn));
    RDN[] newComponents = new RDN[rdnComponents.length+1];
    newComponents[0] = rdn;
    System.arraycopy(rdnComponents, 0, newComponents, 1,
                     rdnComponents.length);
    return new DN(newComponents);
  }
  /**
   * Creates a new DN that is a descendant of this DN, using the
   * specified RDN components.
   *
   * @param  rdnComponents  The RDN components for the descendant of
   *                        this DN.
   *
   * @return  A new DN that is a descendant of this DN, using the
   *          specified RDN components.
   */
  public DN concat(RDN[] rdnComponents)
  {
    assert debugEnter(CLASS_NAME, "concat",
                      String.valueOf(rdnComponents));
    RDN[] newComponents =
         new RDN[rdnComponents.length+this.rdnComponents.length];
    System.arraycopy(rdnComponents, 0, newComponents, 0,
                     rdnComponents.length);
    System.arraycopy(this.rdnComponents, 0, newComponents,
                     rdnComponents.length, this.rdnComponents.length);
    return new DN(newComponents);
  }
  /**
   * Creates a new DN that is a descendant of this DN, using the
   * specified DN as a relative base DN.  That is, the resulting DN
   * will first have the components of the provided DN followed by the
   * components of this DN.
   *
   * @param  relativeBaseDN  The relative base DN to concatenate onto
   *                         this DN.
   *
   * @return  A new DN that is a descendant of this DN, using the
   *          specified DN as a relative base DN.
   */
  public DN concat(DN relativeBaseDN)
  {
    assert debugEnter(CLASS_NAME, "concat",
                      String.valueOf(relativeBaseDN));
    RDN[] newComponents =
               new RDN[rdnComponents.length+
                       relativeBaseDN.rdnComponents.length];
    System.arraycopy(relativeBaseDN.rdnComponents, 0, newComponents,
                     0, relativeBaseDN.rdnComponents.length);
    System.arraycopy(rdnComponents, 0, newComponents,
                     relativeBaseDN.rdnComponents.length,
                     rdnComponents.length);
    return new DN(newComponents);
  }
  /**
   * Indicates whether this DN is a descendant of the provided DN
   * (i.e., that the RDN components of the provided DN are the same as
   * the last RDN components for this DN).
   *
   * @param dn
   *          The DN for which to make the determination.
   * @return <code>true</code> if this DN is a descendant of the
   *         provided DN, or <code>false</code> if not.
   * @param  dn  The DN for which to make the determination.
   *
   * @return  <CODE>true</CODE> if this DN is a descendant of the
   *          provided DN, or <CODE>false</CODE> if not.
   */
  public boolean isDescendantOf(DN dn) {
    assert debugEnter(CLASS_NAME, "isDescendantOf", String
        .valueOf(dn));
  public boolean isDescendantOf(DN dn)
  {
    assert debugEnter(CLASS_NAME, "isDescendantOf",
                      String.valueOf(dn));
    ensureNotNull(dn);
    int diff = numComponents - dn.numComponents;
    if (diff < 0) {
    int offset = numComponents - dn.numComponents;
    if (offset < 0)
    {
      return false;
    }
    for (int i = 0; i < dn.numComponents; i++) {
      if (!getRDN(i + diff).equals(dn.getRDN(i))) {
    for (int i=0; i < dn.numComponents; i++)
    {
      if (! rdnComponents[i+offset].equals(dn.rdnComponents[i]))
      {
        return false;
      }
    }
@@ -571,23 +425,25 @@
   * (i.e., that the RDN components of this DN are the same as the
   * last RDN components for the provided DN).
   *
   * @param dn
   *          The DN for which to make the determination.
   * @return <code>true</code> if this DN is an ancestor of the
   *         provided DN, or <code>false</code> if not.
   * @param  dn  The DN for which to make the determination.
   *
   * @return  <CODE>true</CODE> if this DN is an ancestor of the
   *          provided DN, or <CODE>false</CODE> if not.
   */
  public boolean isAncestorOf(DN dn) {
  public boolean isAncestorOf(DN dn)
  {
    assert debugEnter(CLASS_NAME, "isAncestorOf", String.valueOf(dn));
    ensureNotNull(dn);
    int diff = dn.numComponents - numComponents;
    if (diff < 0) {
    int offset = dn.numComponents - numComponents;
    if (offset < 0)
    {
      return false;
    }
    for (int i = 0; i < numComponents; i++) {
      if (!getRDN(i).equals(dn.getRDN(i + diff))) {
    for (int i=0; i < numComponents; i++)
    {
      if (! rdnComponents[i].equals(dn.rdnComponents[i+offset]))
      {
        return false;
      }
    }
@@ -598,26 +454,2312 @@
  /**
   * Decodes the provided ASN.1 octet string as a DN.
   *
   * @param  dnString  The ASN.1 octet string to decode as a DN.
   *
   * @return  The decoded DN.
   *
   * @throws  DirectoryException  If a problem occurs while trying to
   *                              decode the provided ASN.1 octet
   *                              string as a DN.
   */
  public static DN decode(ByteString dnString)
         throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "decode", String.valueOf(dnString));
    // A null or empty DN is acceptable.
    if (dnString == null)
    {
      return new DN(new ArrayList<RDN>(0));
    }
    byte[] dnBytes = dnString.value();
    int    length  = dnBytes.length;
    if (length == 0)
    {
      return new DN(new ArrayList<RDN>(0));
    }
    // See if we are dealing with any non-ASCII characters, or any
    // escaped characters.  If so, then the easiest and safest
    // approach is to convert the DN to a string and decode it that
    // way.
    for (byte b : dnBytes)
    {
      if (((b & 0x7F) != b) || (b == '\\'))
      {
        return decode(dnString.stringValue());
      }
    }
    // Iterate through the DN string.  The first thing to do is to get
    // rid of any leading spaces.
    int pos = 0;
    byte b = dnBytes[pos];
    while (b == ' ')
    {
      pos++;
      if (pos == length)
      {
        // This means that the DN was completely comprised of spaces
        // and therefore should be considered the same as a null or
        // empty DN.
        return new DN(new ArrayList<RDN>(0));
      }
      else
      {
        b = dnBytes[pos];
      }
    }
    // We know that it's not an empty DN, so we can do the real
    // processing.  Create a loop and iterate through all the RDN
    // components.
    boolean allowExceptions =
         DirectoryServer.allowAttributeNameExceptions();
    ArrayList<RDN> rdnComponents = new ArrayList<RDN>();
    while (true)
    {
      StringBuilder attributeName = new StringBuilder();
      pos = parseAttributeName(dnBytes, pos, attributeName,
                               allowExceptions);
      // Make sure that we're not at the end of the DN string because
      // that would be invalid.
      if (pos >= length)
      {
        int    msgID   = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME;
        String message = getMessage(msgID, dnString.stringValue(),
                                    attributeName.toString());
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
      // Skip over any spaces between the attribute name and its
      // value.
      b = dnBytes[pos];
      while (b == ' ')
      {
        pos++;
        if (pos >= length)
        {
          // This means that we hit the end of the value before
          // finding a '='.  This is illegal because there is no
          // attribute-value separator.
          int    msgID   = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME;
          String message = getMessage(msgID, dnString.stringValue(),
                                      attributeName.toString());
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
        else
        {
          b = dnBytes[pos];
        }
      }
      // The next character must be an equal sign.  If it is not,
      // then that's an error.
      if (b == '=')
      {
        pos++;
      }
      else
      {
        int    msgID   = MSGID_ATTR_SYNTAX_DN_NO_EQUAL;
        String message = getMessage(msgID, dnString.stringValue(),
                                    attributeName.toString(),
                                    (char) b);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
      // Skip over any spaces after the equal sign.
      while ((pos < length) && ((b = dnBytes[pos]) == ' '))
      {
        pos++;
      }
      // If we are at the end of the DN string, then that must mean
      // that the attribute value was empty.  This will probably never
      // happen in a real-world environment, but technically isn't
      // illegal.  If it does happen, then go ahead and create the RDN
      // component and return the DN.
      if (pos >= length)
      {
        String        name      = attributeName.toString();
        String        lowerName = toLowerCase(name);
        AttributeType attrType  =
             DirectoryServer.getAttributeType(lowerName);
        if (attrType == null)
        {
          // This must be an attribute type that we don't know about.
          // In that case, we'll create a new attribute using the
          // default syntax.  If this is a problem, it will be caught
          // later either by not finding the target entry or by not
          // allowing the entry to be added.
          attrType = DirectoryServer.getDefaultAttributeType(name);
        }
        AttributeValue value =
             new AttributeValue(new ASN1OctetString(),
                                new ASN1OctetString());
        rdnComponents.add(new RDN(attrType, name, value));
        return new DN(rdnComponents);
      }
      // Parse the value for this RDN component.
      ByteString parsedValue = new ASN1OctetString();
      pos = parseAttributeValue(dnBytes, pos, parsedValue);
      // Create the new RDN with the provided information.
      String name            = attributeName.toString();
      String lowerName       = toLowerCase(name);
      AttributeType attrType =
           DirectoryServer.getAttributeType(lowerName);
      if (attrType == null)
      {
        // This must be an attribute type that we don't know about.
        // In that case, we'll create a new attribute using the
        // default syntax.  If this is a problem, it will be caught
        // later either by not finding the target entry or by not
        // allowing the entry to be added.
        attrType = DirectoryServer.getDefaultAttributeType(name);
      }
      AttributeValue value =
           new AttributeValue(attrType, parsedValue);
      RDN rdn = new RDN(attrType, name, value);
      // Skip over any spaces that might be after the attribute value.
      while ((pos < length) && ((b = dnBytes[pos]) == ' '))
      {
        pos++;
      }
      // Most likely, we will be at either the end of the RDN
      // component or the end of the DN.  If so, then handle that
      // appropriately.
      if (pos >= length)
      {
        // We're at the end of the DN string and should have a valid
        // DN so return it.
        rdnComponents.add(rdn);
        return new DN(rdnComponents);
      }
      else if ((b == ',') || (b == ';'))
      {
        // We're at the end of the RDN component, so add it to the
        // list, skip over the comma/semicolon, and start on the next
        // component.
        rdnComponents.add(rdn);
        pos++;
        continue;
      }
      else if (b != '+')
      {
        // This should not happen.  At any rate, it's an illegal
        // character, so throw an exception.
        int    msgID   = MSGID_ATTR_SYNTAX_DN_INVALID_CHAR;
        String message = getMessage(msgID, new String(dnBytes),
                                    (char) b, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
      // If we have gotten here, then this must be a multi-valued RDN.
      // In that case, parse the remaining attribute/value pairs and
      // add them to the RDN that we've already created.
      while (true)
      {
        // Skip over the plus sign and any spaces that may follow it
        // before the next attribute name.
        pos++;
        while ((pos < length) && (dnBytes[pos] == ' '))
        {
          pos++;
        }
        // Parse the attribute name from the DN string.
        attributeName = new StringBuilder();
        pos = parseAttributeName(dnBytes, pos, attributeName,
                                 allowExceptions);
        // Make sure that we're not at the end of the DN string
        // because that would be invalid.
        if (pos >= length)
        {
          int    msgID   = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME;
          String message = getMessage(msgID, dnString.stringValue(),
                                      attributeName.toString());
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
        // Skip over any spaces between the attribute name and its
        // value.
        b = dnBytes[pos];
        while (b == ' ')
        {
          pos++;
          if (pos >= length)
          {
            // This means that we hit the end of the value before
            // finding a '='.  This is illegal because there is no
            // attribute-value separator.
            int    msgID   = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME;
            String message = getMessage(msgID, dnString.stringValue(),
                                        attributeName.toString());
            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                         message, msgID);
          }
          else
          {
            b = dnBytes[pos];
          }
        }
        // The next character must be an equal sign.  If it is not,
        // then that's an error.
        if (b == '=')
        {
          pos++;
        }
        else
        {
          int    msgID   = MSGID_ATTR_SYNTAX_DN_NO_EQUAL;
          String message = getMessage(msgID, dnString.stringValue(),
                                      attributeName.toString(),
                                      (char) b);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
        // Skip over any spaces after the equal sign.
        while ((pos < length) && ((b = dnBytes[pos]) == ' '))
        {
          pos++;
        }
        // If we are at the end of the DN string, then that must mean
        // that the attribute value was empty.  This will probably
        // never happen in a real-world environment, but technically
        // isn't illegal.  If it does happen, then go ahead and create
        // the RDN component and return the DN.
        if (pos >= length)
        {
          name      = attributeName.toString();
          lowerName = toLowerCase(name);
          attrType  = DirectoryServer.getAttributeType(lowerName);
          if (attrType == null)
          {
            // This must be an attribute type that we don't know
            // about.  In that case, we'll create a new attribute
            // using the default syntax.  If this is a problem, it
            // will be caught later either by not finding the target
            // entry or by not allowing the entry to be added.
            attrType = DirectoryServer.getDefaultAttributeType(name);
          }
          value = new AttributeValue(new ASN1OctetString(),
                                     new ASN1OctetString());
          rdn.addValue(attrType, name, value);
          rdnComponents.add(rdn);
          return new DN(rdnComponents);
        }
        // Parse the value for this RDN component.
        parsedValue = new ASN1OctetString();
        pos = parseAttributeValue(dnBytes, pos, parsedValue);
        // Create the new RDN with the provided information.
        name      = attributeName.toString();
        lowerName = toLowerCase(name);
        attrType  = DirectoryServer.getAttributeType(lowerName);
        if (attrType == null)
        {
          // This must be an attribute type that we don't know about.
          // In that case, we'll create a new attribute using the
          // default syntax.  If this is a problem, it will be caught
          // later either by not finding the target entry or by not
          // allowing the entry to be added.
          attrType = DirectoryServer.getDefaultAttributeType(name);
        }
        value = new AttributeValue(attrType, parsedValue);
        rdn.addValue(attrType, name, value);
        // Skip over any spaces that might be after the attribute
        // value.
        while ((pos < length) && ((b = dnBytes[pos]) == ' '))
        {
          pos++;
        }
        // Most likely, we will be at either the end of the RDN
        // component or the end of the DN.  If so, then handle that
        // appropriately.
        if (pos >= length)
        {
          // We're at the end of the DN string and should have a valid
          // DN so return it.
          rdnComponents.add(rdn);
          return new DN(rdnComponents);
        }
        else if ((b == ',') || (b == ';'))
        {
          // We're at the end of the RDN component, so add it to the
          // list, skip over the comma/semicolon, and start on the
          // next component.
          rdnComponents.add(rdn);
          pos++;
          break;
        }
        else if (b != '+')
        {
          // This should not happen.  At any rate, it's an illegal
          // character, so throw an exception.
          int    msgID   = MSGID_ATTR_SYNTAX_DN_INVALID_CHAR;
          String message = getMessage(msgID, dnString.stringValue(),
                                      (char) b, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
      }
    }
  }
  /**
   * Decodes the provided string as a DN.
   *
   * @param  dnString  The string to decode as a DN.
   *
   * @return  The decoded DN.
   *
   * @throws  DirectoryException  If a problem occurs while trying to
   *                              decode the provided string as a DN.
   */
  public static DN decode(String dnString)
         throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "decode", String.valueOf(dnString));
    // A null or empty DN is acceptable.
    if (dnString == null)
    {
      return new DN(new ArrayList<RDN>(0));
    }
    int length = dnString.length();
    if (length == 0)
    {
      return new DN(new ArrayList<RDN>(0));
    }
    // Iterate through the DN string.  The first thing to do is to get
    // rid of any leading spaces.
    int pos = 0;
    char c = dnString.charAt(pos);
    while (c == ' ')
    {
      pos++;
      if (pos == length)
      {
        // This means that the DN was completely comprised of spaces
        // and therefore should be considered the same as a null or
        // empty DN.
        return new DN(new ArrayList<RDN>(0));
      }
      else
      {
        c = dnString.charAt(pos);
      }
    }
    // We know that it's not an empty DN, so we can do the real
    // processing.  Create a loop and iterate through all the RDN
    // components.
    boolean allowExceptions =
         DirectoryServer.allowAttributeNameExceptions();
    ArrayList<RDN> rdnComponents = new ArrayList<RDN>();
    while (true)
    {
      StringBuilder attributeName = new StringBuilder();
      pos = parseAttributeName(dnString, pos, attributeName,
                               allowExceptions);
      // Make sure that we're not at the end of the DN string because
      // that would be invalid.
      if (pos >= length)
      {
        int    msgID   = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME;
        String message = getMessage(msgID, dnString,
                                    attributeName.toString());
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
      // Skip over any spaces between the attribute name and its
      // value.
      c = dnString.charAt(pos);
      while (c == ' ')
      {
        pos++;
        if (pos >= length)
        {
          // This means that we hit the end of the value before
          // finding a '='.  This is illegal because there is no
          // attribute-value separator.
          int    msgID   = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME;
          String message = getMessage(msgID, dnString,
                                      attributeName.toString());
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
        else
        {
          c = dnString.charAt(pos);
        }
      }
      // The next character must be an equal sign.  If it is not, then
      // that's an error.
      if (c == '=')
      {
        pos++;
      }
      else
      {
        int    msgID   = MSGID_ATTR_SYNTAX_DN_NO_EQUAL;
        String message = getMessage(msgID, dnString,
                                    attributeName.toString(), c);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
      // Skip over any spaces after the equal sign.
      while ((pos < length) && ((c = dnString.charAt(pos)) == ' '))
      {
        pos++;
      }
      // If we are at the end of the DN string, then that must mean
      // that the attribute value was empty.  This will probably never
      // happen in a real-world environment, but technically isn't
      // illegal.  If it does happen, then go ahead and create the
      // RDN component and return the DN.
      if (pos >= length)
      {
        String        name      = attributeName.toString();
        String        lowerName = toLowerCase(name);
        AttributeType attrType  =
             DirectoryServer.getAttributeType(lowerName);
        if (attrType == null)
        {
          // This must be an attribute type that we don't know about.
          // In that case, we'll create a new attribute using the
          // default syntax.  If this is a problem, it will be caught
          // later either by not finding the target entry or by not
          // allowing the entry to be added.
          attrType = DirectoryServer.getDefaultAttributeType(name);
        }
        AttributeValue value =
             new AttributeValue(new ASN1OctetString(),
                                new ASN1OctetString());
        rdnComponents.add(new RDN(attrType, name, value));
        return new DN(rdnComponents);
      }
      // Parse the value for this RDN component.
      ByteString parsedValue = new ASN1OctetString();
      pos = parseAttributeValue(dnString, pos, parsedValue);
      // Create the new RDN with the provided information.
      String name            = attributeName.toString();
      String lowerName       = toLowerCase(name);
      AttributeType attrType =
           DirectoryServer.getAttributeType(lowerName);
      if (attrType == null)
      {
        // This must be an attribute type that we don't know about.
        // In that case, we'll create a new attribute using the
        // default syntax.  If this is a problem, it will be caught
        // later either by not finding the target entry or by not
        // allowing the entry to be added.
        attrType = DirectoryServer.getDefaultAttributeType(name);
      }
      AttributeValue value =
           new AttributeValue(attrType, parsedValue);
      RDN rdn = new RDN(attrType, name, value);
      // Skip over any spaces that might be after the attribute value.
      while ((pos < length) && ((c = dnString.charAt(pos)) == ' '))
      {
        pos++;
      }
      // Most likely, we will be at either the end of the RDN
      // component or the end of the DN.  If so, then handle that
      // appropriately.
      if (pos >= length)
      {
        // We're at the end of the DN string and should have a valid
        // DN so return it.
        rdnComponents.add(rdn);
        return new DN(rdnComponents);
      }
      else if ((c == ',') || (c == ';'))
      {
        // We're at the end of the RDN component, so add it to the
        // list, skip over the comma/semicolon, and start on the next
        // component.
        rdnComponents.add(rdn);
        pos++;
        continue;
      }
      else if (c != '+')
      {
        // This should not happen.  At any rate, it's an illegal
        // character, so throw an exception.
        int    msgID   = MSGID_ATTR_SYNTAX_DN_INVALID_CHAR;
        String message = getMessage(msgID, dnString, c, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
      // If we have gotten here, then this must be a multi-valued RDN.
      // In that case, parse the remaining attribute/value pairs and
      // add them to the RDN that we've already created.
      while (true)
      {
        // Skip over the plus sign and any spaces that may follow it
        // before the next attribute name.
        pos++;
        while ((pos < length) && (dnString.charAt(pos) == ' '))
        {
          pos++;
        }
        // Parse the attribute name from the DN string.
        attributeName = new StringBuilder();
        pos = parseAttributeName(dnString, pos, attributeName,
                                 allowExceptions);
        // Make sure that we're not at the end of the DN string
        // because that would be invalid.
        if (pos >= length)
        {
          int    msgID   = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME;
          String message = getMessage(msgID, dnString,
                                      attributeName.toString());
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
        // Skip over any spaces between the attribute name and its
        // value.
        c = dnString.charAt(pos);
        while (c == ' ')
        {
          pos++;
          if (pos >= length)
          {
            // This means that we hit the end of the value before
            // finding a '='.  This is illegal because there is no
            // attribute-value separator.
            int    msgID   = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME;
            String message = getMessage(msgID, dnString,
                                        attributeName.toString());
            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                         message, msgID);
          }
          else
          {
            c = dnString.charAt(pos);
          }
        }
        // The next character must be an equal sign.  If it is not,
        // then that's an error.
        if (c == '=')
        {
          pos++;
        }
        else
        {
          int    msgID   = MSGID_ATTR_SYNTAX_DN_NO_EQUAL;
          String message = getMessage(msgID, dnString,
                                      attributeName.toString(), c);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
        // Skip over any spaces after the equal sign.
        while ((pos < length) && ((c = dnString.charAt(pos)) == ' '))
        {
          pos++;
        }
        // If we are at the end of the DN string, then that must mean
        // that the attribute value was empty.  This will probably
        // never happen in a real-world environment, but technically
        // isn't illegal.  If it does happen, then go ahead and create
        // the RDN component and return the DN.
        if (pos >= length)
        {
          name      = attributeName.toString();
          lowerName = toLowerCase(name);
          attrType  = DirectoryServer.getAttributeType(lowerName);
          if (attrType == null)
          {
            // This must be an attribute type that we don't know
            // about.  In that case, we'll create a new attribute
            // using the default syntax.  If this is a problem, it
            // will be caught later either by not finding the target
            // entry or by not allowing the entry to be added.
            attrType = DirectoryServer.getDefaultAttributeType(name);
          }
          value = new AttributeValue(new ASN1OctetString(),
                                     new ASN1OctetString());
          rdn.addValue(attrType, name, value);
          rdnComponents.add(rdn);
          return new DN(rdnComponents);
        }
        // Parse the value for this RDN component.
        parsedValue = new ASN1OctetString();
        pos = parseAttributeValue(dnString, pos, parsedValue);
        // Create the new RDN with the provided information.
        name      = attributeName.toString();
        lowerName = toLowerCase(name);
        attrType  = DirectoryServer.getAttributeType(lowerName);
        if (attrType == null)
        {
          // This must be an attribute type that we don't know about.
          // In that case, we'll create a new attribute using the
          // default syntax.  If this is a problem, it will be caught
          // later either by not finding the target entry or by not
          // allowing the entry to be added.
          attrType = DirectoryServer.getDefaultAttributeType(name);
        }
        value = new AttributeValue(attrType, parsedValue);
        rdn.addValue(attrType, name, value);
        // Skip over any spaces that might be after the attribute
        // value.
        while ((pos < length) && ((c = dnString.charAt(pos)) == ' '))
        {
          pos++;
        }
        // Most likely, we will be at either the end of the RDN
        // component or the end of the DN.  If so, then handle that
        // appropriately.
        if (pos >= length)
        {
          // We're at the end of the DN string and should have a valid
          // DN so return it.
          rdnComponents.add(rdn);
          return new DN(rdnComponents);
        }
        else if ((c == ',') || (c == ';'))
        {
          // We're at the end of the RDN component, so add it to the
          // list, skip over the comma/semicolon, and start on the
          // next component.
          rdnComponents.add(rdn);
          pos++;
          break;
        }
        else if (c != '+')
        {
          // This should not happen.  At any rate, it's an illegal
          // character, so throw an exception.
          int    msgID   = MSGID_ATTR_SYNTAX_DN_INVALID_CHAR;
          String message = getMessage(msgID, dnString, c, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
      }
    }
  }
  /**
   * Parses an attribute name from the provided DN string starting at
   * the specified location.
   *
   * @param  dnBytes          The byte array containing the DN to
   *                          parse.
   * @param  pos              The position at which to start parsing
   *                          the attribute name.
   * @param  attributeName    The buffer to which to append the parsed
   *                          attribute name.
   * @param  allowExceptions  Indicates whether to allow certain
   *                          exceptions to the strict requirements
   *                          for attribute names.
   *
   * @return  The position of the first character that is not part of
   *          the attribute name.
   *
   * @throws  DirectoryException  If it was not possible to parse a
   *                              valid attribute name from the
   *                              provided DN string.
   */
  public static int parseAttributeName(byte[] dnBytes, int pos,
                                       StringBuilder attributeName,
                                       boolean allowExceptions)
          throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "parseAttributeName",
                      String.valueOf(dnBytes), String.valueOf(pos),
                      "java.lang.StringBuilder");
    int length = dnBytes.length;
    // Skip over any leading spaces.
    if (pos < length)
    {
      while (dnBytes[pos] == ' ')
      {
        pos++;
        if (pos == length)
        {
          // This means that the remainder of the DN was completely
          // comprised of spaces.  If we have gotten here, then we
          // know that there is at least one RDN component, and
          // therefore the last non-space character of the DN must
          // have been a comma. This is not acceptable.
          int    msgID   = MSGID_ATTR_SYNTAX_DN_END_WITH_COMMA;
          String message = getMessage(msgID, new String(dnBytes));
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
      }
    }
    // Next, we should find the attribute name for this RDN component.
    // It may either be a name (with only letters, digits, and dashes
    // and starting with a letter) or an OID (with only digits and
    // periods, optionally prefixed with "oid."), and there is also a
    // special case in which we will allow underscores.  Because of
    // the complexity involved, read the entire name first with
    // minimal validation and then do more thorough validation later.
    boolean       checkForOID   = false;
    boolean       endOfName     = false;
    while (pos < length)
    {
      // To make the switch more efficient, we'll include all ASCII
      // characters in the range of allowed values and then reject the
      // ones that aren't allowed.
      byte b = dnBytes[pos];
      switch (b)
      {
        case ' ':
          // This should denote the end of the attribute name.
          endOfName = true;
          break;
        case '!':
        case '"':
        case '#':
        case '$':
        case '%':
        case '&':
        case '\'':
        case '(':
        case ')':
        case '*':
        case '+':
        case ',':
          // None of these are allowed in an attribute name or any
          // character immediately following it.
          int    msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          String message = getMessage(msgID, new String(dnBytes),
                                      (char) b, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        case '-':
          // This will be allowed as long as it isn't the first
          // character in the attribute name.
          if (attributeName.length() > 0)
          {
            attributeName.append((char) b);
          }
          else
          {
            msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DASH;
            message = getMessage(msgID, new String(dnBytes),
                                 (char) b);
            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                         message, msgID);
          }
          break;
        case '.':
          // The period could be allowed if the attribute name is
          // actually expressed as an OID.  We'll accept it for now,
          // but make sure to check it later.
          attributeName.append((char) b);
          checkForOID = true;
          break;
        case '/':
          // This is not allowed in an attribute name or any character
          // immediately following it.
          msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          message = getMessage(msgID, new String(dnBytes),
                               (char) b, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
          // Digits are always allowed if they are not the first
          // character.  However, they may be allowed if they are the
          // first character if the valid is an OID or if the
          // attribute name exceptions option is enabled.  Therefore,
          // we'll accept it now and check it later.
          attributeName.append((char) b);
          break;
        case ':':
        case ';': // NOTE:  attribute options are not allowed in a DN.
        case '<':
          // None of these are allowed in an attribute name or any
          // character immediately following it.
          msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          message = getMessage(msgID, new String(dnBytes),
                               (char) b, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        case '=':
          // This should denote the end of the attribute name.
          endOfName = true;
          break;
        case '>':
        case '?':
        case '@':
          // None of these are allowed in an attribute name or any
          // character immediately following it.
          msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          message = getMessage(msgID, new String(dnBytes),
                               (char) b, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
        case 'G':
        case 'H':
        case 'I':
        case 'J':
        case 'K':
        case 'L':
        case 'M':
        case 'N':
        case 'O':
        case 'P':
        case 'Q':
        case 'R':
        case 'S':
        case 'T':
        case 'U':
        case 'V':
        case 'W':
        case 'X':
        case 'Y':
        case 'Z':
          // These will always be allowed.
          attributeName.append((char) b);
          break;
        case '[':
        case '\\':
        case ']':
        case '^':
          // None of these are allowed in an attribute name or any
          // character immediately following it.
          msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          message = getMessage(msgID, new String(dnBytes),
                               (char) b, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        case '_':
          // This will never be allowed as the first character.  It
          // may be allowed for subsequent characters if the attribute
          // name exceptions option is enabled.
          if (attributeName.length() == 0)
          {
            msgID   =
                 MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_UNDERSCORE;
            message = getMessage(msgID, new String(dnBytes),
                           ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                         message, msgID);
          }
          else if (allowExceptions)
          {
            attributeName.append((char) b);
          }
          else
          {
            msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_UNDERSCORE_CHAR;
            message = getMessage(msgID, new String(dnBytes),
                           ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                         message, msgID);
          }
          break;
        case '`':
          // This is not allowed in an attribute name or any character
          // immediately following it.
          msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          message = getMessage(msgID, new String(dnBytes),
                               (char) b, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        case 'a':
        case 'b':
        case 'c':
        case 'd':
        case 'e':
        case 'f':
        case 'g':
        case 'h':
        case 'i':
        case 'j':
        case 'k':
        case 'l':
        case 'm':
        case 'n':
        case 'o':
        case 'p':
        case 'q':
        case 'r':
        case 's':
        case 't':
        case 'u':
        case 'v':
        case 'w':
        case 'x':
        case 'y':
        case 'z':
          // These will always be allowed.
          attributeName.append((char) b);
          break;
        default:
          // This is not allowed in an attribute name or any character
          // immediately following it.
          msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          message = getMessage(msgID, new String(dnBytes),
                               (char) b, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
      }
      if (endOfName)
      {
        break;
      }
      pos++;
    }
    // We should now have the full attribute name.  However, we may
    // still need to perform some validation, particularly if the name
    // contains a period or starts with a digit.  It must also have at
    // least one character.
    if (attributeName.length() == 0)
    {
      int    msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_NO_NAME;
      String message = getMessage(msgID, new String(dnBytes));
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                   message, msgID);
    }
    else if (checkForOID)
    {
      boolean validOID = true;
      int namePos = 0;
      int nameLength = attributeName.length();
      char ch = attributeName.charAt(0);
      if ((ch == 'o') || (ch == 'O'))
      {
        if (nameLength <= 4)
        {
          validOID = false;
        }
        else
        {
          if ((((ch = attributeName.charAt(1)) == 'i') ||
               (ch == 'I')) &&
              (((ch = attributeName.charAt(2)) == 'd') ||
               (ch == 'D')) &&
              (attributeName.charAt(3) == '.'))
          {
            attributeName.delete(0, 4);
            nameLength -= 4;
          }
          else
          {
            validOID = false;
          }
        }
      }
      while (validOID && (namePos < nameLength))
      {
        ch = attributeName.charAt(namePos++);
        if (isDigit(ch))
        {
          while (validOID && (namePos < nameLength) &&
                 isDigit(attributeName.charAt(namePos)))
          {
            namePos++;
          }
          if ((namePos < nameLength) &&
              (attributeName.charAt(namePos) != '.'))
          {
            validOID = false;
          }
        }
        else if (ch == '.')
        {
          if ((namePos == 1) ||
              (attributeName.charAt(namePos-2) == '.'))
          {
            validOID = false;
          }
        }
        else
        {
          validOID = false;
        }
      }
      if (validOID && (attributeName.charAt(nameLength-1) == '.'))
      {
        validOID = false;
      }
      if (! validOID)
      {
        int    msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_PERIOD;
        String message = getMessage(msgID, new String(dnBytes),
                                    attributeName.toString());
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
    }
    else if (isDigit(attributeName.charAt(0)) && (! allowExceptions))
    {
      int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DIGIT;
      String message = getMessage(msgID, new String(dnBytes),
                            attributeName.charAt(0),
                            ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                   message, msgID);
    }
    return pos;
  }
  /**
   * Parses an attribute name from the provided DN string starting at
   * the specified location.
   *
   * @param  dnString         The DN string to be parsed.
   * @param  pos              The position at which to start parsing
   *                          the attribute name.
   * @param  attributeName    The buffer to which to append the parsed
   *                          attribute name.
   * @param  allowExceptions  Indicates whether to allow certain
   *                          exceptions to the strict requirements
   *                          for attribute names.
   *
   * @return  The position of the first character that is not part of
   *          the attribute name.
   *
   * @throws  DirectoryException  If it was not possible to parse a
   *                              valid attribute name from the
   *                              provided DN string.
   */
  public static int parseAttributeName(String dnString, int pos,
                                       StringBuilder attributeName,
                                       boolean allowExceptions)
          throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "parseAttributeName",
                      String.valueOf(dnString), String.valueOf(pos),
                      "java.lang.StringBuilder");
    int length = dnString.length();
    // Skip over any leading spaces.
    if (pos < length)
    {
      while (dnString.charAt(pos) == ' ')
      {
        pos++;
        if (pos == length)
        {
          // This means that the remainder of the DN was completely
          // comprised of spaces.  If we have gotten here, then we
          // know that there is at least one RDN component, and
          // therefore the last non-space character of the DN must
          // have been a comma. This is not acceptable.
          int    msgID   = MSGID_ATTR_SYNTAX_DN_END_WITH_COMMA;
          String message = getMessage(msgID, dnString);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
      }
    }
    // Next, we should find the attribute name for this RDN component.
    // It may either be a name (with only letters, digits, and dashes
    // and starting with a letter) or an OID (with only digits and
    // periods, optionally prefixed with "oid."), and there is also a
    // special case in which we will allow underscores.  Because of
    // the complexity involved, read the entire name first with
    // minimal validation and then do more thorough validation later.
    boolean       checkForOID   = false;
    boolean       endOfName     = false;
    while (pos < length)
    {
      // To make the switch more efficient, we'll include all ASCII
      // characters in the range of allowed values and then reject the
      // ones that aren't allowed.
      char c = dnString.charAt(pos);
      switch (c)
      {
        case ' ':
          // This should denote the end of the attribute name.
          endOfName = true;
          break;
        case '!':
        case '"':
        case '#':
        case '$':
        case '%':
        case '&':
        case '\'':
        case '(':
        case ')':
        case '*':
        case '+':
        case ',':
          // None of these are allowed in an attribute name or any
          // character immediately following it.
          int    msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          String message = getMessage(msgID, dnString, c, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        case '-':
          // This will be allowed as long as it isn't the first
          // character in the attribute name.
          if (attributeName.length() > 0)
          {
            attributeName.append(c);
          }
          else
          {
            msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DASH;
            message = getMessage(msgID, dnString, c);
            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                         message, msgID);
          }
          break;
        case '.':
          // The period could be allowed if the attribute name is
          // actually expressed as an OID.  We'll accept it for now,
          // but make sure to check it later.
          attributeName.append(c);
          checkForOID = true;
          break;
        case '/':
          // This is not allowed in an attribute name or any character
          // immediately following it.
          msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          message = getMessage(msgID, dnString, c, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
          // Digits are always allowed if they are not the first
          // character. However, they may be allowed if they are the
          // first character if the valid is an OID or if the
          // attribute name exceptions option is enabled.  Therefore,
          // we'll accept it now and check it later.
          attributeName.append(c);
          break;
        case ':':
        case ';': // NOTE:  attribute options are not allowed in a DN.
        case '<':
          // None of these are allowed in an attribute name or any
          // character immediately following it.
          msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          message = getMessage(msgID, dnString, c, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        case '=':
          // This should denote the end of the attribute name.
          endOfName = true;
          break;
        case '>':
        case '?':
        case '@':
          // None of these are allowed in an attribute name or any
          // character immediately following it.
          msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          message = getMessage(msgID, dnString, c, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
        case 'G':
        case 'H':
        case 'I':
        case 'J':
        case 'K':
        case 'L':
        case 'M':
        case 'N':
        case 'O':
        case 'P':
        case 'Q':
        case 'R':
        case 'S':
        case 'T':
        case 'U':
        case 'V':
        case 'W':
        case 'X':
        case 'Y':
        case 'Z':
          // These will always be allowed.
          attributeName.append(c);
          break;
        case '[':
        case '\\':
        case ']':
        case '^':
          // None of these are allowed in an attribute name or any
          // character immediately following it.
          msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          message = getMessage(msgID, dnString, c, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        case '_':
          // This will never be allowed as the first character.  It
          // may be allowed for subsequent characters if the attribute
          // name exceptions option is enabled.
          if (attributeName.length() == 0)
          {
            msgID   =
                 MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_UNDERSCORE;
            message = getMessage(msgID, dnString,
                           ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                         message, msgID);
          }
          else if (allowExceptions)
          {
            attributeName.append(c);
          }
          else
          {
            msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_UNDERSCORE_CHAR;
            message = getMessage(msgID, dnString,
                           ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                         message, msgID);
          }
          break;
        case '`':
          // This is not allowed in an attribute name or any character
          // immediately following it.
          msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          message = getMessage(msgID, dnString, c, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        case 'a':
        case 'b':
        case 'c':
        case 'd':
        case 'e':
        case 'f':
        case 'g':
        case 'h':
        case 'i':
        case 'j':
        case 'k':
        case 'l':
        case 'm':
        case 'n':
        case 'o':
        case 'p':
        case 'q':
        case 'r':
        case 's':
        case 't':
        case 'u':
        case 'v':
        case 'w':
        case 'x':
        case 'y':
        case 'z':
          // These will always be allowed.
          attributeName.append(c);
          break;
        default:
          // This is not allowed in an attribute name or any character
          // immediately following it.
          msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
          message = getMessage(msgID, dnString, c, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
      }
      if (endOfName)
      {
        break;
      }
      pos++;
    }
    // We should now have the full attribute name.  However, we may
    // still need to perform some validation, particularly if the
    // name contains a period or starts with a digit.  It must also
    // have at least one character.
    if (attributeName.length() == 0)
    {
      int    msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_NO_NAME;
      String message = getMessage(msgID, dnString);
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                   message, msgID);
    }
    else if (checkForOID)
    {
      boolean validOID = true;
      int namePos = 0;
      int nameLength = attributeName.length();
      char ch = attributeName.charAt(0);
      if ((ch == 'o') || (ch == 'O'))
      {
        if (nameLength <= 4)
        {
          validOID = false;
        }
        else
        {
          if ((((ch = attributeName.charAt(1)) == 'i') ||
               (ch == 'I')) &&
              (((ch = attributeName.charAt(2)) == 'd') ||
               (ch == 'D')) &&
              (attributeName.charAt(3) == '.'))
          {
            attributeName.delete(0, 4);
            nameLength -= 4;
          }
          else
          {
            validOID = false;
          }
        }
      }
      while (validOID && (namePos < nameLength))
      {
        ch = attributeName.charAt(namePos++);
        if (isDigit(ch))
        {
          while (validOID && (namePos < nameLength) &&
                 isDigit(attributeName.charAt(namePos)))
          {
            namePos++;
          }
          if ((namePos < nameLength) &&
              (attributeName.charAt(namePos) != '.'))
          {
            validOID = false;
          }
        }
        else if (ch == '.')
        {
          if ((namePos == 1) ||
              (attributeName.charAt(namePos-2) == '.'))
          {
            validOID = false;
          }
        }
        else
        {
          validOID = false;
        }
      }
      if (validOID && (attributeName.charAt(nameLength-1) == '.'))
      {
        validOID = false;
      }
      if (! validOID)
      {
        int    msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_PERIOD;
        String message = getMessage(msgID, dnString,
                                    attributeName.toString());
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
    }
    else if (isDigit(attributeName.charAt(0)) &&
             (! allowExceptions))
    {
      int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DIGIT;
      String message = getMessage(msgID, dnString,
                            attributeName.charAt(0),
                            ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                   message, msgID);
    }
    return pos;
  }
  /**
   * Parses the attribute value from the provided DN string starting
   * at the specified location.  When the value has been parsed, it
   * will be assigned to the provided ASN.1 octet string.
   *
   * @param  dnBytes         The byte array containing the DN to be
   *                         parsed.
   * @param  pos             The position of the first character in
   *                         the attribute value to parse.
   * @param  attributeValue  The ASN.1 octet string whose value should
   *                         be set to the parsed attribute value when
   *                         this method completes successfully.
   *
   * @return  The position of the first character that is not part of
   *          the attribute value.
   *
   * @throws  DirectoryException  If it was not possible to parse a
   *                              valid attribute value from the
   *                              provided DN string.
   */
  public static int parseAttributeValue(byte[] dnBytes, int pos,
                                        ByteString attributeValue)
          throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "parseAttributeValue",
                      String.valueOf(dnBytes), String.valueOf(pos),
                      "java.lang.StringBuilder");
    // All leading spaces have already been stripped so we can start
    // reading the value.  However, it may be empty so check for that.
    int length = dnBytes.length;
    if (pos >= length)
    {
      attributeValue.setValue("");
      return pos;
    }
    // Look at the first character.  If it is an octothorpe (#), then
    // that means that the value should be a hex string.
    byte b = dnBytes[pos++];
    if (b == '#')
    {
      // The first two characters must be hex characters.
      StringBuilder hexString = new StringBuilder();
      if ((pos+2) > length)
      {
        int    msgID   = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT;
        String message = getMessage(msgID, new String(dnBytes));
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
      for (int i=0; i < 2; i++)
      {
        b = dnBytes[pos++];
        if (isHexDigit(b))
        {
          hexString.append((char) b);
        }
        else
        {
          int    msgID   = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
          String message = getMessage(msgID, new String(dnBytes),
                                      (char) b);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
      }
      // 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, a plus sign, or a space.
      while (pos < length)
      {
        b = dnBytes[pos++];
        if (isHexDigit(b))
        {
          hexString.append((char) b);
          if (pos < length)
          {
            b = dnBytes[pos++];
            if (isHexDigit(b))
            {
              hexString.append((char) b);
            }
            else
            {
              int    msgID   = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
              String message = getMessage(msgID, new String(dnBytes),
                                          (char) b);
              throw new DirectoryException(
                             ResultCode.INVALID_DN_SYNTAX, message,
                             msgID);
            }
          }
          else
          {
            int    msgID   = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT;
            String message = getMessage(msgID, new String(dnBytes));
            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                         message, msgID);
          }
        }
        else if ((b == ' ') || (b == ',') || (b == ';') || (b == '+'))
        {
          // This denotes the end of the value.
          pos--;
          break;
        }
        else
        {
          int    msgID   = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
          String message = getMessage(msgID, new String(dnBytes),
                                      (char) b);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
      }
      // 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
      {
        attributeValue.setValue(hexStringToByteArray(
                                     hexString.toString()));
        return pos;
      }
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "parseAttributeValue", e);
        int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE;
        String message = getMessage(msgID, new String(dnBytes),
                                    String.valueOf(e));
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
    }
    // If the first character is a quotation mark, then the value
    // should continue until the corresponding closing quotation mark.
    else if (b == '"')
    {
      int valueStartPos = pos;
      // Keep reading until we find a closing quotation mark.
      while (true)
      {
        if (pos >= length)
        {
          // We hit the end of the DN before the closing quote.
          // That's an error.
          int    msgID   = MSGID_ATTR_SYNTAX_DN_UNMATCHED_QUOTE;
          String message = getMessage(msgID, new String(dnBytes));
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
        if (dnBytes[pos++] == '"')
        {
          // This is the end of the value.
          break;
        }
      }
      byte[] valueBytes = new byte[pos - valueStartPos - 1];
      System.arraycopy(dnBytes, valueStartPos, valueBytes, 0,
                       valueBytes.length);
      try
      {
        attributeValue.setValue(valueBytes);
      }
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "parseAttributeValue", e);
        // This should never happen.  Just in case, work around it by
        // converting to a string and back.
        String valueStr = new String(valueBytes);
        attributeValue.setValue(valueStr);
      }
      return pos;
    }
    // Otherwise, use general parsing to find the end of the value.
    else
    {
      // Keep reading until we find a comma/semicolon, a plus sign, or
      // the end of the DN.
      int valueStartPos = pos - 1;
      while (true)
      {
        if (pos >= length)
        {
          // This is the end of the DN and therefore the end of the
          // value.
          break;
        }
        b = dnBytes[pos++];
        if ((b == ',') || (b == ';') || (b == '+'))
        {
          pos--;
          break;
        }
      }
      // Convert the byte buffer to an array.
      byte[] valueBytes = new byte[pos - valueStartPos];
      System.arraycopy(dnBytes, valueStartPos, valueBytes, 0,
                       valueBytes.length);
      // Strip off any unescaped spaces that may be at the end of the
      // value.
      boolean extraSpaces = false;
      int     lastPos     = valueBytes.length - 1;
      while (lastPos > 0)
      {
        if (valueBytes[lastPos] == ' ')
        {
          extraSpaces = true;
          lastPos--;
        }
        else
        {
          break;
        }
      }
      if (extraSpaces)
      {
        byte[] newValueBytes = new byte[lastPos+1];
        System.arraycopy(valueBytes, 0, newValueBytes, 0, lastPos+1);
        valueBytes = newValueBytes;
      }
      try
      {
        attributeValue.setValue(valueBytes);
      }
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "parseAttributeValue", e);
        // This should never happen.  Just in case, work around it by
        // converting to a string and back.
        String valueStr = new String(valueBytes);
        attributeValue.setValue(valueStr);
      }
      return pos;
    }
  }
  /**
   * Parses the attribute value from the provided DN string starting
   * at the specified location.  When the value has been parsed, it
   * will be assigned to the provided ASN.1 octet string.
   *
   * @param  dnString        The DN string to be parsed.
   * @param  pos             The position of the first character in
   *                         the attribute value to parse.
   * @param  attributeValue  The ASN.1 octet string whose value should
   *                         be set to the parsed attribute value when
   *                         this method completes successfully.
   *
   * @return  The position of the first character that is not part of
   *          the attribute value.
   *
   * @throws  DirectoryException  If it was not possible to parse a
   *                              valid attribute value from the
   *                              provided DN string.
   */
  public static int parseAttributeValue(String dnString, int pos,
                                        ByteString attributeValue)
          throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "parseAttributeValue",
                      String.valueOf(dnString), String.valueOf(pos),
                      "java.lang.StringBuilder");
    // All leading spaces have already been stripped so we can start
    // reading the value.  However, it may be empty so check for that.
    int length = dnString.length();
    if (pos >= length)
    {
      attributeValue.setValue("");
      return pos;
    }
    // Look at the first character.  If it is an octothorpe (#), then
    // that means that the value should be a hex string.
    char c = dnString.charAt(pos++);
    if (c == '#')
    {
      // The first two characters must be hex characters.
      StringBuilder hexString = new StringBuilder();
      if ((pos+2) > length)
      {
        int    msgID   = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT;
        String message = getMessage(msgID, dnString);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
      for (int i=0; i < 2; i++)
      {
        c = dnString.charAt(pos++);
        if (isHexDigit(c))
        {
          hexString.append(c);
        }
        else
        {
          int    msgID   = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
          String message = getMessage(msgID, dnString, c);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
      }
      // 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 (pos < length)
      {
        c = dnString.charAt(pos++);
        if (isHexDigit(c))
        {
          hexString.append(c);
          if (pos < length)
          {
            c = dnString.charAt(pos++);
            if (isHexDigit(c))
            {
              hexString.append(c);
            }
            else
            {
              int    msgID   = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
              String message = getMessage(msgID, dnString, c);
              throw new DirectoryException(
                             ResultCode.INVALID_DN_SYNTAX, message,
                             msgID);
            }
          }
          else
          {
            int    msgID   = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT;
            String message = getMessage(msgID, dnString);
            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                         message, msgID);
          }
        }
        else if ((c == ' ') || (c == ',') || (c == ';'))
        {
          // This denotes the end of the value.
          pos--;
          break;
        }
        else
        {
          int    msgID   = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
          String message = getMessage(msgID, dnString, c);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
      }
      // 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
      {
        attributeValue.setValue(hexStringToByteArray(
                                     hexString.toString()));
        return pos;
      }
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "parseAttributeValue", e);
        int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE;
        String message = getMessage(msgID, dnString,
                                    String.valueOf(e));
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
    }
    // If the first character is a quotation mark, then the value
    // should continue until the corresponding closing quotation mark.
    else if (c == '"')
    {
      // Keep reading until we find an unescaped closing quotation
      // mark.
      boolean escaped = false;
      StringBuilder valueString = new StringBuilder();
      while (true)
      {
        if (pos >= length)
        {
          // We hit the end of the DN before the closing quote.
          // That's an error.
          int    msgID   = MSGID_ATTR_SYNTAX_DN_UNMATCHED_QUOTE;
          String message = getMessage(msgID, dnString);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
        c = dnString.charAt(pos++);
        if (escaped)
        {
          // The previous character was an escape, so we'll take this
          // one no matter what.
          valueString.append(c);
          escaped = false;
        }
        else if (c == '\\')
        {
          // The next character is escaped.  Set a flag to denote
          // this, but don't include the backslash.
          escaped = true;
        }
        else if (c == '"')
        {
          // This is the end of the value.
          break;
        }
        else
        {
          // This is just a regular character that should be in the
          // value.
          valueString.append(c);
        }
      }
      attributeValue.setValue(valueString.toString());
      return pos;
    }
    // Otherwise, use general parsing to find the end of the value.
    else
    {
      boolean escaped;
      StringBuilder valueString = new StringBuilder();
      StringBuilder hexChars    = new StringBuilder();
      if (c == '\\')
      {
        escaped = true;
      }
      else
      {
        escaped = false;
        valueString.append(c);
      }
      // Keep reading until we find an unescaped comma or plus sign or
      // the end of the DN.
      while (true)
      {
        if (pos >= length)
        {
          // This is the end of the DN and therefore the end of the
          // value.  If there are any hex characters, then we need to
          // deal with them accordingly.
          appendHexChars(dnString, valueString, hexChars);
          break;
        }
        c = dnString.charAt(pos++);
        if (escaped)
        {
          // The previous character was an escape, so we'll take this
          // one.  However, this could be a hex digit, and if that's
          // the case then the escape would actually be in front of
          // two hex digits that should be treated as a special
          // character.
          if (isHexDigit(c))
          {
            // It is a hexadecimal digit, so the next digit must be
            // one too.  However, this could be just one in a series
            // of escaped hex pairs that is used in a string
            // containing one or more multi-byte UTF-8 characters so
            // we can't just treat this byte in isolation.  Collect
            // all the bytes together and make sure to take care of
            // these hex bytes before appending anything else to the
            // value.
            if (pos >= length)
            {
              int    msgID   =
                   MSGID_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID;
              String message = getMessage(msgID, dnString);
              throw new DirectoryException(
                             ResultCode.INVALID_DN_SYNTAX, message,
                             msgID);
            }
            else
            {
              char c2 = dnString.charAt(pos++);
              if (isHexDigit(c2))
              {
                hexChars.append(c);
                hexChars.append(c2);
              }
              else
              {
                int    msgID   =
                     MSGID_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID;
                String message = getMessage(msgID, dnString);
                throw new DirectoryException(
                               ResultCode.INVALID_DN_SYNTAX, message,
                               msgID);
              }
            }
          }
          else
          {
            appendHexChars(dnString, valueString, hexChars);
            valueString.append(c);
          }
          escaped = false;
        }
        else if (c == '\\')
        {
          escaped = true;
        }
        else if ((c == ',') || (c == ';'))
        {
          appendHexChars(dnString, valueString, hexChars);
          pos--;
          break;
        }
        else if (c == '+')
        {
          appendHexChars(dnString, valueString, hexChars);
          pos--;
          break;
        }
        else
        {
          appendHexChars(dnString, valueString, hexChars);
          valueString.append(c);
        }
      }
      // Strip off any unescaped spaces that may be at the end of the
      // value.
      if (pos > 2 && dnString.charAt(pos-1) == ' ' &&
           dnString.charAt(pos-2) != '\\')
      {
        int lastPos = valueString.length() - 1;
        while (lastPos > 0)
        {
          if (valueString.charAt(lastPos) == ' ')
          {
            valueString.delete(lastPos, lastPos+1);
            lastPos--;
          }
          else
          {
            break;
          }
        }
      }
      attributeValue.setValue(valueString.toString());
      return pos;
    }
  }
  /**
   * Decodes a hexadecimal string from the provided
   * <CODE>hexChars</CODE> buffer, converts it to a byte array, and
   * then converts that to a UTF-8 string.  The resulting UTF-8 string
   * will be appended to the provided <CODE>valueString</CODE> buffer,
   * and the <CODE>hexChars</CODE> buffer will be cleared.
   *
   * @param  dnString     The DN string that is being decoded.
   * @param  valueString  The buffer containing the value to which the
   *                      decoded string should be appended.
   * @param  hexChars     The buffer containing the hexadecimal
   *                      characters to decode to a UTF-8 string.
   *
   * @throws  DirectoryException  If any problem occurs during the
   *                              decoding process.
   */
  public static void appendHexChars(String dnString,
                                    StringBuilder valueString,
                                    StringBuilder hexChars)
          throws DirectoryException
  {
    try
    {
      byte[] hexBytes = hexStringToByteArray(hexChars.toString());
      valueString.append(new String(hexBytes, "UTF-8"));
      hexChars.delete(0, hexChars.length());
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "appendHexChars", e);
      int    msgID   = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE;
      String message = getMessage(msgID, dnString, String.valueOf(e));
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                   message, msgID);
    }
  }
  /**
   * Indicates whether the provided object is equal to this DN. In
   * order for the object to be considered equal, it must be a DN with
   * the same number of RDN components and each corresponding RDN
   * component must be equal.
   *
   * @param o
   *          The object for which to make the determination.
   * @return <code>true</code> if the provided object is a DN that
   *         is equal to this DN, or <code>false</code> if it is
   *         not.
   * @param  o  The object for which to make the determination.
   *
   * @return  <CODE>true</CODE> if the provided object is a DN that is
   *          equal to this DN, or <CODE>false</CODE> if it is not.
   */
  public boolean equals(Object o) {
  public boolean equals(Object o)
  {
    assert debugEnter(CLASS_NAME, "equals", String.valueOf(o));
    if (this == o) {
    if (this == o)
    {
      return true;
    } else if (o instanceof DN) {
      DN other = (DN) o;
      return normalizedDN.equals(other.normalizedDN);
    } else {
    }
    if (o == null)
    {
      return false;
    }
    try
    {
      return (normalizedDN.equals(((DN) o).normalizedDN));
    }
    catch (Exception e)
    {
      // This most likely means that the object was null or wasn't a
      // DN.  In either case, it's faster to assume that it is and
      // return false on an exception than to perform the checks to
      // see if it meets the appropriate
      // conditions.
      assert debugException(CLASS_NAME, "equals", e);
      return false;
    }
  }
@@ -630,7 +2772,8 @@
   *
   * @return The hash code for this DN.
   */
  public int hashCode() {
  public int hashCode()
  {
    assert debugEnter(CLASS_NAME, "hashCode");
    return normalizedDN.hashCode();
@@ -643,12 +2786,32 @@
   *
   * @return A string representation of this DN.
   */
  public String toString() {
  public String toString()
  {
    assert debugEnter(CLASS_NAME, "toString");
    StringBuilder builder = new StringBuilder();
    toString(builder);
    return builder.toString();
    if (dnString == null)
    {
      if (numComponents == 0)
      {
        dnString = "";
      }
      else
      {
        StringBuilder buffer = new StringBuilder();
        rdnComponents[0].toString(buffer);
        for (int i=1; i < numComponents; i++)
        {
          buffer.append(",");
          rdnComponents[i].toString(buffer);
        }
        dnString = buffer.toString();
      }
    }
    return dnString;
  }
@@ -657,23 +2820,15 @@
   * Appends a string representation of this DN to the provided
   * buffer.
   *
   * @param buffer
   *          The buffer to which the information should be appended.
   * @param  buffer  The buffer to which the information should be
   *                 appended.
   */
  public void toString(StringBuilder buffer) {
  public void toString(StringBuilder buffer)
  {
    assert debugEnter(CLASS_NAME, "toString",
        "java.lang.StringBuilder");
    ensureNotNull(buffer);
    if (numComponents != 0) {
      getRDN(0).toString(buffer);
      for (int i = 1; i < numComponents; i++) {
        buffer.append(",");
        getRDN(i).toString(buffer);
      }
    }
    buffer.append(toString());
  }
@@ -683,9 +2838,31 @@
   *
   * @return A normalized string representation of this DN.
   */
  public String toNormalizedString() {
  public String toNormalizedString()
  {
    assert debugEnter(CLASS_NAME, "toNormalizedString");
    if (normalizedDN == null)
    {
      if (numComponents == 0)
      {
        normalizedDN = "";
      }
      else
      {
        StringBuilder buffer = new StringBuilder();
        rdnComponents[0].toNormalizedString(buffer);
        for (int i=1; i < numComponents; i++)
        {
          buffer.append(',');
          rdnComponents[i].toNormalizedString(buffer);
        }
        normalizedDN = buffer.toString();
      }
    }
    return normalizedDN;
  }
@@ -695,16 +2872,15 @@
   * Appends a normalized string representation of this DN to the
   * provided buffer.
   *
   * @param buffer
   *          The buffer to which the information should be appended.
   * @param  buffer  The buffer to which the information should be
   *                 appended.
   */
  public void toNormalizedString(StringBuilder buffer) {
  public void toNormalizedString(StringBuilder buffer)
  {
    assert debugEnter(CLASS_NAME, "toNormalizedString",
        "java.lang.StringBuilder");
    ensureNotNull(buffer);
    buffer.append(normalizedDN);
    buffer.append(toNormalizedString());
  }
@@ -714,69 +2890,54 @@
   * This order will be first hierarchical (ancestors will come before
   * descendants) and then alphabetical by attribute name(s) and
   * value(s).
   * <p>
   * NOTE: the implementation of this method does not perform a
   * lexicographic comparison of the DN's normalized form. Instead,
   * each individual RDN is compared using ordering matching rules
   * where possible.
   *
   * @param dn
   *          The DN against which to compare this DN.
   * @param  dn  The DN against which to compare this DN.
   *
   * @return A negative integer if this DN should come before the
   *         provided DN, a positive integer if this DN should come
   *         after the provided DN, or zero if there is no difference
   *         with regard to ordering.
   */
  public int compareTo(DN dn) {
    assert debugEnter(CLASS_NAME, "compareTo", String.valueOf(this),
        String.valueOf(dn));
  public int compareTo(DN dn)
  {
    assert debugEnter(CLASS_NAME, "compareTo", String.valueOf(dn));
    ensureNotNull(dn);
    int index1 = numComponents - 1;
    int index2 = dn.numComponents - 1;
    while (true) {
      if (index1 >= 0) {
        if (index2 >= 0) {
          int value = getRDN(index1).compareTo(dn.getRDN(index2));
          if (value != 0) {
            return value;
          }
        } else {
          return 1;
        }
      } else if (index2 >= 0) {
        return -1;
      } else {
    if (equals(dn))
    {
        return 0;
      }
      index1--;
      index2--;
    else if (isNullDN())
    {
      return -1;
    }
    else if (dn.isNullDN())
    {
      return 1;
    }
    else if (isAncestorOf(dn))
    {
      return -1;
    }
    else if (isDescendantOf(dn))
    {
      return 1;
    }
    else
    {
      int minComps = Math.min(numComponents, dn.numComponents);
      for (int i=0; i < minComps; i++)
      {
        RDN r1 = rdnComponents[rdnComponents.length-1-i];
        RDN r2 = dn.rdnComponents[dn.rdnComponents.length-1-i];
        int result = r1.compareTo(r2);
        if (result != 0)
        {
          return result;
    }
  }
  /**
   * Construct the normalized form of this DN.
   *
   * @return Returns the normalized string representation of this DN.
   */
  private String normalize() {
    if (numComponents == 0) {
      return "";
    } else {
      StringBuilder buffer = new StringBuilder();
      getRDN(0).toNormalizedString(buffer);
      for (int i = 1; i < numComponents; i++) {
        buffer.append(',');
        getRDN(i).toNormalizedString(buffer);
      }
      return buffer.toString();
      return 0;
    }
  }
}
opends/src/server/org/opends/server/types/RDN.java
@@ -22,29 +22,25 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.messages.SchemaMessages.*;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.util.Validator.ensureNotNull;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;
import java.util.TreeSet;
import org.opends.server.api.OrderingMatchingRule;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.util.StaticUtils;
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
@@ -52,55 +48,59 @@
 * This class defines a data structure for storing and interacting
 * with the relative distinguished names associated with entries in
 * the Directory Server.
 * <p>
 * All the methods in this class will throw a
 * <code>NullPointerException</code> when provided with
 * <code>null</code> reference parameters unless otherwise stated.
 */
public final class RDN implements Comparable<RDN> {
  // TODO: per-thread cache of common RDNs?
  // TODO: implement pimpl idiom and provide a "singleton"
  // implementation for common case where the RDN only has a single
  // type and value. This would result in less memory being used.
public class RDN
       implements Comparable<RDN>
{
  /**
   * The fully-qualified name of this class for debugging purposes.
   */
  private static final String CLASS_NAME =
    "org.opends.server.types.RDN";
  // The set of attribute types for the elements in this RDN.
  private final AttributeType[] attributeTypes;
  // The set of user-provided names for the attributes in this RDN.
  private final String[] attributeNames;
  // The set of attribute types for the elements in this RDN.
  private AttributeType[] attributeTypes;
  // The set of values for the elements in this RDN.
  private final AttributeValue[] attributeValues;
  private AttributeValue[] attributeValues;
  /**
   * The cached normalized string representation of this RDN.
   *
   * This non-final field will default to null. The Java memory model
   * guarantees that it will be initialized to null before being
   * visible to other threads.
   */
  // The number of values for this RDN.
  private int numValues;
  // The string representation of the normalized form of this RDN.
  private String normalizedRDN;
  // The string representation of this RDN.
  private String rdnString;
  // The set of user-provided names for the attributes in this RDN.
  private String[] attributeNames;
  /**
   * Creates a new RDN with the provided information.
   *
   * @param type
   *          The attribute type for this RDN.
   * @param value
   *          The value for this RDN.
   * @return Returns the new RDN.
   * @param  attributeType   The attribute type for this RDN.  It must
   *                         not be {@code null}.
   * @param  attributeValue  The value for this RDN.  It must not be
   *                         {@code null}.
   */
  public static RDN create(AttributeType type, AttributeValue value) {
    return create(type, type.getNameOrOID(), value);
  public RDN(AttributeType attributeType,
             AttributeValue attributeValue)
  {
    assert debugConstructor(CLASS_NAME, String.valueOf(attributeType),
                            String.valueOf(attributeValue));
    attributeTypes  = new AttributeType[] { attributeType };
    attributeNames  = new String[] { attributeType.getPrimaryName() };
    attributeValues = new AttributeValue[] { attributeValue };
    numValues     = 1;
    rdnString     = null;
    normalizedRDN = null;
  }
@@ -108,392 +108,98 @@
  /**
   * Creates a new RDN with the provided information.
   *
   * @param type
   *          The attribute type for this RDN.
   * @param name
   *          The user-provided name for this RDN.
   * @param value
   *          The value for this RDN.
   * @return Returns the new RDN.
   * @param  attributeType   The attribute type for this RDN.  It must
   *                         not be {@code null}.
   * @param  attributeName   The user-provided name for this RDN.  It
   *                         must not be {@code null}.
   * @param  attributeValue  The value for this RDN.  It must not be
   *                         {@code null}.
   */
  public static RDN create(AttributeType type, String name,
      AttributeValue value) {
    ensureNotNull(type, name, value);
  public RDN(AttributeType attributeType, String attributeName,
             AttributeValue attributeValue)
  {
    assert debugConstructor(CLASS_NAME, String.valueOf(attributeType),
                            String.valueOf(attributeName),
                            String.valueOf(attributeValue));
    AttributeType[] types = new AttributeType[] { type };
    String[] names = new String[] { name };
    AttributeValue[] values = new AttributeValue[] { value };
    attributeTypes  = new AttributeType[] { attributeType };
    attributeNames  = new String[] { attributeName };
    attributeValues = new AttributeValue[] { attributeValue };
    return new RDN(types, names, values);
    numValues     = 1;
    rdnString     = null;
    normalizedRDN = null;
  }
  /**
   * Create a new RDN builder which can be used to incrementally build
   * a new RDN.
   * Creates a new RDN with the provided information.  The number of
   * type, name, and value elements must be nonzero and equal.
   *
   * @return Returns the new RDN builder.
   * @param  attributeTypes   The set of attribute types for this RDN.
   *                          It must not be empty or {@code null}.
   * @param  attributeNames   The set of user-provided names for this
   *                          RDN.  It must have the same number of
   *                          elements as the {@attributeTypes}
   *                          argument.
   * @param  attributeValues  The set of values for this RDN.  It must
   *                          have the same number of elements as the
   *                          {@attributeTypes} argument.
   */
  public static Builder createBuilder() {
    return new Builder();
  public RDN(List<AttributeType> attributeTypes,
             List<String> attributeNames,
             List<AttributeValue> attributeValues)
  {
    assert debugConstructor(CLASS_NAME,
                            String.valueOf(attributeTypes),
                            String.valueOf(attributeNames),
                            String.valueOf(attributeValues));
    this.attributeTypes  = new AttributeType[attributeTypes.size()];
    this.attributeNames  = new String[attributeNames.size()];
    this.attributeValues = new AttributeValue[attributeValues.size()];
    attributeTypes.toArray(this.attributeTypes);
    attributeNames.toArray(this.attributeNames);
    attributeValues.toArray(this.attributeValues);
    numValues     = attributeTypes.size();
    rdnString     = null;
    normalizedRDN = null;
  }
  /**
   * This class provides an interface for constructing RDNs
   * incrementally.
   * <p>
   * Typically, an application will construct a new
   * <code>Builder</code> and append attribute value assertions
   * (AVAs) using the <code>append</code> method. When the RDN is
   * fully constructed, it can be retrieved using the
   * <code>getInstance</code> method.
   */
  public static final class Builder {
    // The list of attribute types.
    private List<AttributeType> attributeTypes;
    // The list of user-provided attribute names.
    private List<String> attributeNames;
    // The list of attribute values.
    private List<AttributeValue> attributeValues;
    /**
     * Create the new empty RDN builder.
     */
    private Builder() {
      clear();
    }
    /**
     * Appends the provided attribute value assertion to the RDN.
   * Creates a new RDN with the provided information.  The number of
   * type, name, and value elements must be nonzero and equal.
     *
     * @param type
     *          The attribute type.
     * @param value
     *          The attribute value.
     * @throws IllegalArgumentException
     *           If the RDN being constructed already contains an
     *           attribute value assertion for this attribute type.
   * @param  attributeTypes   The set of attribute types for this RDN.
   *                          It must not be empty or {@code null}.
   * @param  attributeNames   The set of user-provided names for this
   *                          RDN.  It must have the same number of
   *                          elements as the {@attributeTypes}
   *                          argument.
   * @param  attributeValues  The set of values for this RDN.  It must
   *                          have the same number of elements as the
   *                          {@attributeTypes} argument.
     */
    public void append(AttributeType type, AttributeValue value)
        throws IllegalArgumentException {
      append(type, type.getNameOrOID(), value);
    }
  public RDN(AttributeType[] attributeTypes, String[] attributeNames,
             AttributeValue[] attributeValues)
  {
    assert debugConstructor(CLASS_NAME,
                            String.valueOf(attributeTypes),
                            String.valueOf(attributeNames),
                            String.valueOf(attributeValues));
    this.numValues       = attributeTypes.length;
    this.attributeTypes  = attributeTypes;
    this.attributeNames  = attributeNames;
    this.attributeValues = attributeValues;
    /**
     * Appends the provided attribute value assertion to the RDN.
     *
     * @param type
     *          The attribute type.
     * @param name
     *          The user-provided attribute name.
     * @param value
     *          The attribute value.
     * @throws IllegalArgumentException
     *           If the RDN being constructed already contains an
     *           attribute value assertion for this attribute type.
     */
    public void append(AttributeType type, String name,
        AttributeValue value) throws IllegalArgumentException {
      ensureNotNull(type, name, value);
      if (attributeTypes.contains(type)) {
        throw new IllegalArgumentException(
            "Builder already contains the attribute type "
                + type.getNameOrOID());
      }
      attributeTypes.add(type);
      attributeNames.add(name);
      attributeValues.add(value);
    }
    /**
     * Parses an RDN from the provided string starting at the
     * specified location, appending any AVAs to this RDN builder.
     * <p>
     * This method is package visible so that it can be used by
     * the DN decoder. It is not intended for use elsewhere.
     *
     * @param s
     *          The string to be parsed.
     * @param pos
     *          The position of the first character in the string to
     *          parse.
     * @param allowEmpty
     *          Flag indicating whether or not the parsed RDN can be
     *          empty or not.
     * @return Returns <code>-1</code> if decoding was successful
     *         and parsing consumed the remainder of the string
     *         (including trailing space), or the position of the next
     *         RDN separator character (i.e. a ',' or ';').
     * @throws DirectoryException
     *           If it was not possible to parse a valid RDN from the
     *           provided string.
     */
    int parse(String s, int pos, boolean allowEmpty)
        throws DirectoryException {
      assert debugEnter(CLASS_NAME, "parse", String.valueOf(s),
          String.valueOf(pos));
      // There must be at least one AVA.
      int count = attributeTypes.size();
      pos = parseAVA(s, pos);
      if (pos == -1 && !allowEmpty) {
        if (count == attributeTypes.size()) {
          // Nothing was parsed.
          int msgID = MSGID_RDN_DECODE_NULL;
          String message = getMessage(msgID);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
              message, msgID);
        }
      }
      // Parse any remaining AVAs.
      while (pos != -1 && s.charAt(pos) == '+') {
        count = attributeTypes.size();
        pos = parseAVA(s, pos + 1);
        if (pos == -1 && count == attributeTypes.size()) {
          // Nothing was parsed.
          int msgID = MSGID_RDN_UNEXPECTED_COMMA;
          String message = getMessage(msgID, s, pos);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
              message, msgID);
        }
      }
      return pos;
    }
    /**
     * Parse a single AVA.
     *
     * @param s
     *          The string to be parsed.
     * @param pos
     *          The position of the first character in the string to
     *          parse.
     * @return Returns <code>-1</code> if decoding was successful
     *         and parsing consumed the remainder of the possibly
     *         empty string (including trailing space), or the
     *         position of the next AVA separator character (i.e. a
     *         '+', ',' or ';').
     * @throws DirectoryException
     *           If it was not possible to parse a valid AVA from the
     *           provided string.
     */
    private int parseAVA(String s, int pos)
        throws DirectoryException {
      int length = s.length();
      // Skip over any spaces that may follow it
      // before the next attribute name.
      char c;
      while ((pos < length) && ((c = s.charAt(pos)) == ' ')) {
        pos++;
      }
      // Reached the end of the string - let the caller handle this.
      if (pos >= length) {
        return -1;
      }
      // Parse the attribute name.
      StringBuilder attributeName = new StringBuilder();
      pos = parseAttributeName(s, pos, attributeName);
      // Make sure we're not at the end of the RDN.
      if (pos >= length) {
        int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME;
        String message = getMessage(msgID, s, attributeName
            .toString());
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
            message, msgID);
      }
      // Skip over any spaces between the attribute name and the
      // equal sign.
      c = s.charAt(pos);
      while (c == ' ') {
        pos++;
        if (pos >= length) {
          // This means that we hit the end of the string before
          // finding a '='. This is illegal because there is no
          // attribute-value separator.
          int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME;
          String message = getMessage(msgID, s, attributeName
              .toString());
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
              message, msgID);
        } else {
          c = s.charAt(pos);
        }
      }
      // The next character must be an equal sign.
      if (c == '=') {
        pos++;
      } else {
        int msgID = MSGID_ATTR_SYNTAX_DN_NO_EQUAL;
        String message = getMessage(msgID, s, attributeName
            .toString(), c, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
            message, msgID);
      }
      // Skip over any spaces after the equal sign.
      while ((pos < length) && ((c = s.charAt(pos)) == ' ')) {
        pos++;
      }
      // If we are at the end of the RDN string, then that must mean
      // that the attribute value was empty. This will probably
      // never happen in a real-world environment, but technically
      // isn't illegal. If it does happen, then go ahead and return
      // the RDN.
      if (pos >= length) {
        String name = attributeName.toString();
        String lowerName = toLowerCase(name);
        AttributeType attrType = DirectoryServer
            .getAttributeType(lowerName);
        if (attrType == null) {
          // This must be an attribute type that we don't know
          // about.
          // In that case, we'll create a new attribute using the
          // default syntax. If this is a problem, it will be caught
          // later either by not finding the target entry or by not
          // allowing the entry to be added.
          attrType = DirectoryServer.getDefaultAttributeType(name);
        }
        AttributeValue value = new AttributeValue(
            new ASN1OctetString(), new ASN1OctetString());
        append(attrType, name, value);
        return -1;
      }
      // Parse the value for this RDN component.
      ByteString parsedValue = new ASN1OctetString();
      pos = parseAttributeValue(s, pos, parsedValue);
      // Update the RDN to include the new attribute/value.
      String name = attributeName.toString();
      String lowerName = toLowerCase(name);
      AttributeType attrType = DirectoryServer
          .getAttributeType(lowerName);
      if (attrType == null) {
        // This must be an attribute type that we don't know about.
        // In that case, we'll create a new attribute using the
        // default syntax. If this is a problem, it will be caught
        // later either by not finding the target entry or by not
        // allowing the entry to be added.
        attrType = DirectoryServer.getDefaultAttributeType(name);
      }
      AttributeValue value = new AttributeValue(attrType,
          parsedValue);
      append(attrType, name, value);
      // Skip over any spaces that might be after the attribute
      // value.
      while ((pos < length) && ((c = s.charAt(pos)) == ' ')) {
        pos++;
      }
      // If we're at the end of the string, then return the RDN.
      if (pos >= length) {
        return -1;
      }
      // If the next character is a comma or semicolon, then that is
      // not allowed. It would be legal for a DN but not an RDN.
      if ((c == ',') || (c == ';')) {
        return pos;
      }
      // If the next character is anything but a plus sign, then it
      // is illegal.
      if (c != '+') {
        int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_CHAR;
        String message = getMessage(msgID, s, c, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
            message, msgID);
      }
      return pos;
    }
    /**
     * Removes all the attribute value assertions from this RDN
     * builder.
     */
    public void clear() {
      attributeTypes = new ArrayList<AttributeType>(3);
      attributeValues = new ArrayList<AttributeValue>(3);
      attributeNames = new ArrayList<String>(3);
    }
    /**
     * Returns <code>true</code> if this RDN builder is empty.
     *
     * @return Returns <code>true</code> if this RDN builder is
     *         empty.
     */
    public boolean isEmpty() {
      return attributeTypes.isEmpty();
    }
    /**
     * Creates a new RDN instance based on the current contents of
     * this RDN builder. Subsequent changes to this RDN builder do not
     * affect the contents of the returned <code>RDN</code>.
     *
     * @return Returns a new RDN instance based on the current
     *         contents of this RDN builder.
     * @throws IllegalStateException
     *           If a new RDN could not be created because this RDN
     *           builder is emtpy.
     */
    public RDN getInstance() throws IllegalStateException {
      int sz = attributeTypes.size();
      if (sz == 0) {
        throw new IllegalStateException("RDN builder is empty");
      }
      AttributeType[] types = new AttributeType[sz];
      String[] names = new String[sz];
      AttributeValue[] values = new AttributeValue[sz];
      attributeTypes.toArray(types);
      attributeNames.toArray(names);
      attributeValues.toArray(values);
      return new RDN(types, names, values);
    }
    rdnString     = null;
    normalizedRDN = null;
  }
@@ -501,21 +207,21 @@
  /**
   * Creates a new RDN with the provided information.
   *
   * @param types
   *          The attribute types for this RDN.
   * @param names
   *          The user-provided names for this RDN.
   * @param values
   *          The values for this RDN.
   * @param  attributeType   The attribute type for this RDN.  It must
   *                         not be {@code null}.
   * @param  attributeValue  The value for this RDN.  It must not be
   *                         {@code null}.
   *
   * @return  The RDN created with the provided information.
   */
  private RDN(AttributeType[] types, String[] names,
      AttributeValue[] values) {
    assert debugConstructor(CLASS_NAME, String.valueOf(types), String
        .valueOf(names), String.valueOf(values));
  public static RDN create(AttributeType attributeType,
                           AttributeValue attributeValue)
  {
    assert debugEnter(CLASS_NAME, "create",
                      String.valueOf(attributeType),
                      String.valueOf(attributeValue));
    this.attributeTypes = types;
    this.attributeNames = names;
    this.attributeValues = values;
    return new RDN(attributeType, attributeValue);
  }
@@ -527,86 +233,11 @@
   * @return The number of attribute-value pairs contained in this
   *         RDN.
   */
  public int getNumValues() {
  public int getNumValues()
  {
    assert debugEnter(CLASS_NAME, "getNumValues");
    return attributeTypes.length;
  }
  /**
   * Retrieves the attribute type at the specified AVA in this RDN.
   *
   * @param index
   *          The index of the AVA in this RDN.
   * @return Returns the attribute type at the specified AVA in this
   *         RDN.
   * @throws IndexOutOfBoundsException
   *           If <code>index</code> is out of range
   *           <code>(index < 0 || index >= getNumValues()</code>.
   */
  public AttributeType getAttributeType(int index)
      throws IndexOutOfBoundsException {
    assert debugEnter(CLASS_NAME, "getAttributeType");
    if (index < 0 || index >= attributeTypes.length) {
      throw new IndexOutOfBoundsException("Index: " + index
          + ", Size: " + attributeTypes.length);
    }
    return attributeTypes[index];
  }
  /**
   * Retrieves the user-defined attribute name at the specified AVA in
   * this RDN.
   *
   * @param index
   *          The index of the AVA in this RDN.
   * @return Returns the user-defined attribute name at the specified
   *         AVA in this RDN.
   * @throws IndexOutOfBoundsException
   *           If <code>index</code> is out of range
   *           <code>(index < 0 || index >= getNumValues()</code>.
   */
  public String getAttributeName(int index)
      throws IndexOutOfBoundsException {
    assert debugEnter(CLASS_NAME, "getAttributeName");
    if (index < 0 || index >= attributeTypes.length) {
      throw new IndexOutOfBoundsException("Index: " + index
          + ", Size: " + attributeTypes.length);
    }
    return attributeNames[index];
  }
  /**
   * Retrieves the attribute value at the specified AVA in this RDN.
   * <p>
   * Applications <b>must not</b> modify the contents of the returned
   * attribute value.
   *
   * @param index
   *          The index of the AVA in this RDN.
   * @return Returns the attribute value at the specified AVA in this
   *         RDN.
   * @throws IndexOutOfBoundsException
   *           If <code>index</code> is out of range
   *           <code>(index < 0 || index >= getNumValues()</code>.
   */
  public AttributeValue getAttributeValue(int index)
      throws IndexOutOfBoundsException {
    assert debugEnter(CLASS_NAME, "getAttributeValue");
    if (index < 0 || index >= attributeTypes.length) {
      throw new IndexOutOfBoundsException("Index: " + index
          + ", Size: " + attributeTypes.length);
    }
    return attributeValues[index];
    return numValues;
  }
@@ -614,19 +245,21 @@
  /**
   * Indicates whether this RDN includes the specified attribute type.
   *
   * @param attributeType
   *          The attribute type for which to make the determination.
   * @return <code>true</code> if the RDN includes the specified
   *         attribute type, or <code>false</code> if not.
   * @param  attributeType  The attribute type for which to make the
   *                        determination.
   *
   * @return  <CODE>true</CODE> if the RDN includes the specified
   *          attribute type, or <CODE>false</CODE> if not.
   */
  public boolean hasAttributeType(AttributeType attributeType) {
    assert debugEnter(CLASS_NAME, "hasAttributeType", String
        .valueOf(attributeType));
  public boolean hasAttributeType(AttributeType attributeType)
  {
    assert debugEnter(CLASS_NAME, "hasAttributeType",
                      String.valueOf(attributeType));
    ensureNotNull(attributeType);
    for (AttributeType t : attributeTypes) {
      if (t.equals(attributeType)) {
    for (AttributeType t : attributeTypes)
    {
      if (t.equals(attributeType))
      {
        return true;
      }
    }
@@ -637,28 +270,100 @@
  /**
   * Indicates whether this RDN includes the specified attribute type.
   *
   * @param  lowerName  The name or OID for the attribute type for
   *                    which to make the determination, formatted in
   *                    all lowercase characters.
   *
   * @return  <CODE>true</CODE> if the RDN includes the specified
   *          attribute type, or <CODE>false</CODE> if not.
   */
  public boolean hasAttributeType(String lowerName)
  {
    assert debugEnter(CLASS_NAME, "hasAttributeType",
                      String.valueOf(lowerName));
    for (AttributeType t : attributeTypes)
    {
      if (t.hasNameOrOID(lowerName))
      {
        return true;
      }
    }
    for (String s : attributeNames)
    {
      if (s.equalsIgnoreCase(lowerName))
      {
        return true;
      }
    }
    return false;
  }
  /**
   * Retrieves the attribute type at the specified position in the set
   * of attribute types for this RDN.
   *
   * @param  pos  The position of the attribute type to retrieve.
   *
   * @return  The attribute type at the specified position in the set
   *          of attribute types for this RDN.
   */
  public AttributeType getAttributeType(int pos)
  {
    assert debugEnter(CLASS_NAME, "getAttributeType",
                      String.valueOf(pos));
    return attributeTypes[pos];
  }
  /**
   * Retrieves the name for the attribute type at the specified
   * position in the set of attribute types for this RDN.
   *
   * @param  pos  The position of the attribute type for which to
   *              retrieve the name.
   *
   * @return  The name for the attribute type at the specified
   *          position in the set of attribute types for this RDN.
   */
  public String getAttributeName(int pos)
  {
    assert debugEnter(CLASS_NAME, "getAttributeName",
                      String.valueOf(pos));
    return attributeNames[pos];
  }
  /**
   * Retrieves the attribute value that is associated with the
   * specified attribute type.
   * <p>
   * Applications <b>must not</b> modify the contents of the returned
   * attribute value.
   *
   * @param attributeType
   *          The attribute type for which to retrieve the
   *          corresponding value.
   * @param  attributeType  The attribute type for which to retrieve
   *                        the corresponding value.
   *
   * @return The value for the requested attribute type, or
   *         <code>null</code> if the specified attribute type is
   *         not present in the RDN.
   *          <CODE>null</CODE> if the specified attribute type is not
   *          present in the RDN.
   */
  public AttributeValue getAttributeValue(
      AttributeType attributeType) {
    assert debugEnter(CLASS_NAME, "getAttributeValue", String
        .valueOf(attributeType));
  public AttributeValue getAttributeValue(AttributeType attributeType)
  {
    assert debugEnter(CLASS_NAME, "getAttributeValue",
                      String.valueOf(attributeType));
    ensureNotNull(attributeType);
    for (int i = 0; i < attributeTypes.length; i++) {
      if (attributeTypes[i].equals(attributeType)) {
    for (int i=0; i < numValues; i++)
    {
      if (attributeTypes[i].equals(attributeType))
      {
        return attributeValues[i];
      }
    }
@@ -669,59 +374,121 @@
  /**
   * Retrieves the value for the attribute type at the specified
   * position in the set of attribute types for this RDN.
   *
   * @param  pos  The position of the attribute type for which to
   *              retrieve the value.
   *
   * @return  The value for the attribute type at the specified
   *          position in the set of attribute types for this RDN.
   */
  public AttributeValue getAttributeValue(int pos)
  {
    assert debugEnter(CLASS_NAME, "getAttributeName",
                      String.valueOf(pos));
    return attributeValues[pos];
  }
  /**
   * Indicates whether this RDN is multivalued.
   *
   * @return <code>true</code> if this RDN is multivalued, or
   *         <code>false</code> if not.
   * @return  <CODE>true</CODE> if this RDN is multivalued, or
   *          <CODE>false</CODE> if not.
   */
  public boolean isMultiValued() {
  public boolean isMultiValued()
  {
    assert debugEnter(CLASS_NAME, "isMultiValued");
    return (attributeTypes.length > 1);
    return (numValues > 1);
  }
  /**
   * Returns an <code>RDN</code> object holding the value of the
   * specified <code>String</code>. The argument is interpreted as
   * representing the LDAP string representation of an RDN.
   * <p>
   * This method is identical to {@link #decode(String)}.
   * Indicates whether this RDN contains the specified type-value
   * pair.
   *
   * @param s
   *          The string to be parsed.
   * @return Returns a <code>RDN</code> holding the value
   *         represented by the <code>string</code> argument.
   * @throws DirectoryException
   *           If a problem occurs while trying to decode the provided
   *           string as a RDN.
   * @param  type   The attribute type for which to make the
   *                determination.
   * @param  value  The value for which to make the determination.
   *
   * @return  <CODE>true</CODE> if this RDN contains the specified
   *          attribute value, or <CODE>false</CODE> if not.
   */
  public static RDN valueOf(String s) throws DirectoryException {
    return decode(s);
  public boolean hasValue(AttributeType type, AttributeValue value)
  {
    assert debugEnter(CLASS_NAME, "hasValue", String.valueOf(type),
                      String.valueOf(value));
    for (int i=0; i < numValues; i++)
    {
      if (attributeTypes[i].equals(type) &&
          attributeValues[i].equals(value))
      {
        return true;
      }
    }
    return false;
  }
  /**
   * Decodes the provided ASN.1 octet string as a RDN.
   * Adds the provided type-value pair from this RDN.  Note that this
   * is intended only for internal use when constructing DN values.
   *
   * @param rdnString
   *          The ASN.1 octet string to decode as a RDN.
   * @return The decoded RDN.
   * @throws DirectoryException
   *           If a problem occurs while trying to decode the provided
   *           ASN.1 octet string as a RDN.
   * @param  type   The attribute type of the pair to add.
   * @param  name   The user-provided name of the pair to add.
   * @param  value  The attribute value of the pair to add.
   *
   * @return  <CODE>true</CODE> if the type-value pair was added to
   *          this RDN, or <CODE>false</CODE> if it was not (e.g., it
   *          was already present).
   */
  public static RDN decode(ByteString rdnString)
      throws DirectoryException {
    assert debugEnter(CLASS_NAME, "decode",
        String.valueOf(rdnString));
  boolean addValue(AttributeType type, String name,
                   AttributeValue value)
  {
    assert debugEnter(CLASS_NAME, "addValue", String.valueOf(type),
                      String.valueOf(name), String.valueOf(value));
    ensureNotNull(rdnString);
    for (int i=0; i < numValues; i++)
    {
      if (attributeTypes[i].equals(type) &&
          attributeValues[i].equals(value))
      {
        return false;
      }
    }
    // Use string-based decoder.
    return decode(rdnString.stringValue());
    numValues++;
    AttributeType[] newTypes = new AttributeType[numValues];
    System.arraycopy(attributeTypes, 0, newTypes, 0,
                     attributeTypes.length);
    newTypes[attributeTypes.length] = type;
    attributeTypes = newTypes;
    String[] newNames = new String[numValues];
    System.arraycopy(attributeNames, 0, newNames, 0,
                     attributeNames.length);
    newNames[attributeNames.length] = name;
    attributeNames = newNames;
    AttributeValue[] newValues = new AttributeValue[numValues];
    System.arraycopy(attributeValues, 0, newValues, 0,
                     attributeValues.length);
    newValues[attributeValues.length] = value;
    attributeValues = newValues;
    rdnString     = null;
    normalizedRDN = null;
    return true;
  }
@@ -729,75 +496,452 @@
  /**
   * Decodes the provided string as an RDN.
   *
   * @param rdnString
   *          The string to decode as an RDN.
   * @param  rdnString  The string to decode as an RDN.
   *
   * @return The decoded RDN.
   * @throws DirectoryException
   *           If a problem occurs while trying to decode the provided
   *           string as a RDN.
   *
   * @throws  DirectoryException  If a problem occurs while trying to
   *                              decode the provided string as a RDN.
   */
  public static RDN decode(String rdnString)
      throws DirectoryException {
         throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "decode",
        String.valueOf(rdnString));
    ensureNotNull(rdnString);
    // Use an RDN builder to parse the string.
    Builder builder = createBuilder();
    int pos = builder.parse(rdnString, 0, false);
    // A null or empty RDN is not acceptable.
    if (rdnString == null)
    {
      int    msgID   = MSGID_RDN_DECODE_NULL;
      String message = getMessage(msgID);
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                   message, msgID);
    }
    // Make sure that the string did not contain any trailing RDNs.
    if (pos != -1) {
    int length = rdnString.length();
    if (length == 0)
    {
      int    msgID   = MSGID_RDN_DECODE_NULL;
      String message = getMessage(msgID);
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                   message, msgID);
    }
    // Iterate through the RDN string.  The first thing to do is to
    // get rid of any leading spaces.
    int pos = 0;
    char c = rdnString.charAt(pos);
    while (c == ' ')
    {
      pos++;
      if (pos == length)
      {
        // This means that the RDN was completely comprised of spaces,
        // which is not valid.
        int    msgID   = MSGID_RDN_DECODE_NULL;
        String message = getMessage(msgID);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
      else
      {
        c = rdnString.charAt(pos);
      }
    }
    // We know that it's not an empty RDN, so we can do the real
    // processing.  First, parse the attribute name.  We can borrow
    // the DN code for this.
    boolean allowExceptions =
         DirectoryServer.allowAttributeNameExceptions();
    StringBuilder attributeName = new StringBuilder();
    pos = DN.parseAttributeName(rdnString, pos, attributeName,
                                allowExceptions);
    // Make sure that we're not at the end of the RDN string because
    // that would be invalid.
    if (pos >= length)
    {
      int    msgID   = MSGID_RDN_END_WITH_ATTR_NAME;
      String message = getMessage(msgID,
                            rdnString, attributeName.toString());
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                   message, msgID);
    }
    // Skip over any spaces between the attribute name and its value.
    c = rdnString.charAt(pos);
    while (c == ' ')
    {
      pos++;
      if (pos >= length)
      {
        // This means that we hit the end of the string before
        // finding a '='.  This is illegal because there is no
        // attribute-value separator.
        int    msgID   = MSGID_RDN_END_WITH_ATTR_NAME;
        String message = getMessage(msgID,
                              rdnString, attributeName.toString());
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
      else
      {
        c = rdnString.charAt(pos);
      }
    }
    // The next character must be an equal sign.  If it is not, then
    // that's an error.
    if (c == '=')
    {
      pos++;
    }
    else
    {
      int    msgID   = MSGID_RDN_NO_EQUAL;
      String message = getMessage(msgID, rdnString,
                                  attributeName.toString(), c, pos);
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                   message, msgID);
    }
    // Skip over any spaces between the equal sign and the value.
    while ((pos < length) && ((c = rdnString.charAt(pos)) == ' '))
    {
      pos++;
    }
    // If we are at the end of the RDN string, then that must mean
    // that the attribute value was empty.  This will probably never
    // happen in a real-world environment, but technically isn't
    // illegal.  If it does happen, then go ahead and return the RDN.
    if (pos >= length)
    {
      String        name      = attributeName.toString();
      String        lowerName = toLowerCase(name);
      AttributeType attrType  =
           DirectoryServer.getAttributeType(lowerName);
      if (attrType == null)
      {
        // This must be an attribute type that we don't know about.
        // In that case, we'll create a new attribute using the
        // default syntax.  If this is a problem, it will be caught
        // later either by not finding the target entry or by not
        // allowing the entry to be added.
        attrType = DirectoryServer.getDefaultAttributeType(name);
      }
      AttributeValue value = new AttributeValue(new ASN1OctetString(),
                                     new ASN1OctetString());
      return new RDN(attrType, name, value);
    }
    // Parse the value for this RDN component.  This can be done using
    // the DN code.
    ByteString parsedValue = new ASN1OctetString();
    pos = DN.parseAttributeValue(rdnString, pos, parsedValue);
    // Create the new RDN with the provided information.  However,
    // don't return it yet because this could be a multi-valued RDN.
    String name            = attributeName.toString();
    String lowerName       = toLowerCase(name);
    AttributeType attrType =
         DirectoryServer.getAttributeType(lowerName);
    if (attrType == null)
    {
      // This must be an attribute type that we don't know about.
      // In that case, we'll create a new attribute using the default
      // syntax.  If this is a problem, it will be caught later either
      // by not finding the target entry or by not allowing the entry
      // to be added.
      attrType = DirectoryServer.getDefaultAttributeType(name);
    }
    AttributeValue value = new AttributeValue(attrType, parsedValue);
    RDN rdn = new RDN(attrType, name, value);
    // Skip over any spaces that might be after the attribute value.
    while ((pos < length) && ((c = rdnString.charAt(pos)) == ' '))
    {
      pos++;
    }
    // Most likely, this is the end of the RDN.  If so, then return
    // it.
    if (pos >= length)
    {
      return rdn;
    }
    // If the next character is a comma or semicolon, then that is not
    // allowed.  It would be legal for a DN but not an RDN.
    if ((c == ',') || (c == ';'))
    {
      int msgID = MSGID_RDN_UNEXPECTED_COMMA;
      String message = getMessage(msgID, rdnString, pos);
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
          message, msgID);
    }
    // Return the parsed RDN instance.
    return builder.getInstance();
    // If the next character is anything but a plus sign, then it is
    // illegal.
    if (c != '+')
    {
      int    msgID   = MSGID_RDN_ILLEGAL_CHARACTER;
      String message = getMessage(msgID, rdnString, c, pos);
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                   message, msgID);
    }
    // If we have gotten here, then it is a multi-valued RDN.  Parse
    // the remaining attribute/value pairs and add them to the RDN
    // that we've already created.
    while (true)
    {
      // Skip over the plus sign and any spaces that may follow it
      // before the next attribute name.
      pos++;
      while ((pos < length) && ((c = rdnString.charAt(pos)) == ' '))
      {
        pos++;
      }
      // Parse the attribute name.
      attributeName = new StringBuilder();
      pos = DN.parseAttributeName(rdnString, pos, attributeName,
                                  allowExceptions);
      // Make sure we're not at the end of the RDN.
      if (pos >= length)
      {
        int    msgID   = MSGID_RDN_END_WITH_ATTR_NAME;
        String message = getMessage(msgID, rdnString,
                                    attributeName.toString());
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
      // Skip over any spaces between the attribute name and the equal
      // sign.
      c = rdnString.charAt(pos);
      while (c == ' ')
      {
        pos++;
        if (pos >= length)
        {
          // This means that we hit the end of the string before
          // finding a '='.  This is illegal because there is no
          // attribute-value separator.
          int    msgID   = MSGID_RDN_END_WITH_ATTR_NAME;
          String message = getMessage(msgID, rdnString,
                                      attributeName.toString());
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                       message, msgID);
        }
        else
        {
          c = rdnString.charAt(pos);
        }
      }
      // The next character must be an equal sign.
      if (c == '=')
      {
        pos++;
      }
      else
      {
        int    msgID   = MSGID_RDN_NO_EQUAL;
        String message = getMessage(msgID, rdnString,
                                    attributeName.toString(), c, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
      // Skip over any spaces after the equal sign.
      while ((pos < length) && ((c = rdnString.charAt(pos)) == ' '))
      {
        pos++;
      }
      // If we are at the end of the RDN string, then that must mean
      // that the attribute value was empty.  This will probably never
      // happen in a real-world environment, but technically isn't
      // illegal.  If it does happen, then go ahead and return the
      // RDN.
      if (pos >= length)
      {
        name      = attributeName.toString();
        lowerName = toLowerCase(name);
        attrType  = DirectoryServer.getAttributeType(lowerName);
        if (attrType == null)
        {
          // This must be an attribute type that we don't know about.
          // In that case, we'll create a new attribute using the
          // default syntax.  If this is a problem, it will be caught
          // later either by not finding the target entry or by not
          // allowing the entry to be added.
          attrType = DirectoryServer.getDefaultAttributeType(name);
        }
        value = new AttributeValue(new ASN1OctetString(),
                                   new ASN1OctetString());
        rdn.addValue(attrType, name, value);
        return rdn;
      }
      // Parse the value for this RDN component.
      parsedValue = new ASN1OctetString();
      pos = DN.parseAttributeValue(rdnString, pos, parsedValue);
      // Update the RDN to include the new attribute/value.
      name            = attributeName.toString();
      lowerName       = toLowerCase(name);
      attrType = DirectoryServer.getAttributeType(lowerName);
      if (attrType == null)
      {
        // This must be an attribute type that we don't know about.
        // In that case, we'll create a new attribute using the
        // default syntax.  If this is a problem, it will be caught
        // later either by not finding the target entry or by not
        // allowing the entry to be added.
        attrType = DirectoryServer.getDefaultAttributeType(name);
      }
      value = new AttributeValue(attrType, parsedValue);
      rdn.addValue(attrType, name, value);
      // Skip over any spaces that might be after the attribute value.
      while ((pos < length) && ((c = rdnString.charAt(pos)) == ' '))
      {
        pos++;
      }
      // If we're at the end of the string, then return the RDN.
      if (pos >= length)
      {
        return rdn;
      }
      // If the next character is a comma or semicolon, then that is
      // not allowed.  It would be legal for a DN but not an RDN.
      if ((c == ',') || (c == ';'))
      {
        int    msgID   = MSGID_RDN_UNEXPECTED_COMMA;
        String message = getMessage(msgID, rdnString, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
      // If the next character is anything but a plus sign, then it is
      // illegal.
      if (c != '+')
      {
        int    msgID   = MSGID_RDN_ILLEGAL_CHARACTER;
        String message = getMessage(msgID, rdnString, c, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
                                     message, msgID);
      }
    }
  }
  /**
   * Creates a duplicate of this RDN that can be modified without
   * impacting this RDN.
   *
   * @return  A duplicate of this RDN that can be modified without
   *          impacting this RDN.
   */
  public RDN duplicate()
  {
    assert debugEnter(CLASS_NAME, "duplicate");
    AttributeType[] newTypes = new AttributeType[numValues];
    System.arraycopy(attributeTypes, 0, newTypes, 0, numValues);
    String[] newNames = new String[numValues];
    System.arraycopy(attributeNames, 0, newNames, 0, numValues);
    AttributeValue[] newValues = new AttributeValue[numValues];
    System.arraycopy(attributeValues, 0, newValues, 0, numValues);
    return new RDN(newTypes, newNames, newValues);
  }
  /**
   * Indicates whether the provided object is equal to this RDN. It
   * will only be considered equal if it is an RDN object containing
   * the same attribute value assertions as this RDN (the order does
   * not matter).
   * will only be considered equal if it is an RDN object that
   * contains the same number of elements in the same order with the
   * same types and normalized values.
   *
   * @param o
   *          The object for which to make the determination.
   * @return <code>true</code> if it is determined that the provided
   *         object is equal to this RDN, or <code>false</code> if
   * @param  o  The object for which to make the determination.
   *
   * @return  <CODE>true</CODE> if it is determined that the provided
   *          object is equal to this RDN, or <CODE>false</CODE> if
   *         not.
   */
  public boolean equals(Object o) {
  public boolean equals(Object o)
  {
    assert debugEnter(CLASS_NAME, "equals", String.valueOf(o));
    if (this == o) {
    if (this == o)
    {
      return true;
    } else if (o instanceof RDN) {
      RDN other = (RDN) o;
    }
      String nvalue1 = toNormalizedString();
      String nvalue2 = other.toNormalizedString();
      return nvalue1.equals(nvalue2);
    } else {
    if ((o == null) || (! (o instanceof RDN)))
    {
      return false;
    }
    RDN rdn = (RDN) o;
    return toNormalizedString().equals(rdn.toNormalizedString());
  }
  /**
   * Retrieves the hash code for this RDN. It will be calculated as
   * the hash code of the RDN's normalized string representation.
   * the sum of the hash codes of the types and values.
   *
   * @return The hash code for this RDN.
   */
  public int hashCode() {
  public int hashCode()
  {
    assert debugEnter(CLASS_NAME, "hashCode");
    return toNormalizedString().hashCode();
@@ -810,12 +954,28 @@
   *
   * @return A string representation of this RDN.
   */
  public String toString() {
    assert debugEnter(CLASS_NAME, "toString");
  public String toString()
  {
    if (rdnString == null)
    {
    StringBuilder buffer = new StringBuilder();
    toString(buffer);
    return buffer.toString();
      buffer.append(attributeNames[0]);
      buffer.append("=");
      buffer.append(attributeValues[0].getDNStringValue());
      for (int i=1; i < numValues; i++)
      {
        buffer.append("+");
        buffer.append(attributeNames[i]);
        buffer.append("=");
        buffer.append(attributeValues[i].getDNStringValue());
      }
      rdnString = buffer.toString();
    }
    return rdnString;
  }
@@ -824,29 +984,15 @@
   * Appends a string representation of this RDN to the provided
   * buffer.
   *
   * @param buffer
   *          The buffer to which the string representation should be
   *          appended.
   * @param  buffer  The buffer to which the string representation
   *                 should be appended.
   */
  public void toString(StringBuilder buffer) {
  public void toString(StringBuilder buffer)
  {
    assert debugEnter(CLASS_NAME, "toString",
        "java.lang.StringBuilder");
    ensureNotNull(buffer);
    buffer.append(attributeNames[0]);
    buffer.append("=");
    String value = attributeValues[0].getStringValue();
    quoteAttributeValue(buffer, value);
    for (int i = 1; i < attributeTypes.length; i++) {
      buffer.append("+");
      buffer.append(attributeNames[i]);
      buffer.append("=");
      value = attributeValues[i].getStringValue();
      quoteAttributeValue(buffer, value);
    }
    buffer.append(toString());
  }
@@ -856,36 +1002,12 @@
   *
   * @return A normalized string representation of this RDN.
   */
  public String toNormalizedString() {
    if (normalizedRDN == null) {
      StringBuilder builder = new StringBuilder();
      if (attributeNames.length == 1) {
        // Optimize for the common case of a single AVA.
        appendNormalizedAVA(builder, attributeTypes[0],
            attributeValues[0]);
      } else {
        // Multiple AVAs require sorting.
        TreeMap<String, Integer> map;
        map = new TreeMap<String, Integer>();
        for (int i = 0; i < attributeTypes.length; i++) {
          map.put(attributeTypes[i].getNameOrOID(), i);
        }
        boolean isFirst = true;
        for (Integer i : map.values()) {
          if (!isFirst) {
            builder.append('+');
          } else {
            isFirst = false;
          }
          appendNormalizedAVA(builder, attributeTypes[i],
              attributeValues[i]);
        }
      }
      normalizedRDN = builder.toString();
  public String toNormalizedString()
  {
    if (normalizedRDN == null)
    {
      StringBuilder buffer = new StringBuilder();
      toNormalizedString(buffer);
    }
    return normalizedRDN;
@@ -897,899 +1019,262 @@
   * Appends a normalized string representation of this RDN to the
   * provided buffer.
   *
   * @param buffer
   *          The buffer to which to append the information.
   * @param  buffer  The buffer to which to append the information.
   */
  public void toNormalizedString(StringBuilder buffer) {
  public void toNormalizedString(StringBuilder buffer)
  {
    assert debugEnter(CLASS_NAME, "toNormalizedString",
        "java.lang.StringBuilder");
    ensureNotNull(buffer);
    if (normalizedRDN != null)
    {
      buffer.append(normalizedRDN);
      return;
    }
    buffer.append(toNormalizedString());
    boolean bufferEmpty = (buffer.length() == 0);
    if (attributeNames.length == 1)
    {
      toLowerCase(attributeTypes[0].getNameOrOID(), buffer);
      buffer.append('=');
      try
      {
        buffer.append(
             attributeValues[0].getNormalizedDNStringValue());
      }
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "toNormalizedString", e);
        buffer.append(attributeValues[0].getStringValue());
      }
    }
    else
    {
      TreeSet<String> rdnElementStrings = new TreeSet<String>();
      for (int i=0; i < attributeNames.length; i++)
      {
        StringBuilder b2 = new StringBuilder();
        toLowerCase(attributeTypes[i].getNameOrOID(), b2);
        b2.append('=');
        try
        {
          b2.append(attributeValues[i].getNormalizedStringValue());
        }
        catch (Exception e)
        {
          assert debugException(CLASS_NAME, "toNormalizedString", e);
          b2.append(attributeValues[i].getStringValue());
        }
        rdnElementStrings.add(b2.toString());
      }
      Iterator<String> iterator = rdnElementStrings.iterator();
      buffer.append(iterator.next());
      while (iterator.hasNext())
      {
        buffer.append('+');
        buffer.append(iterator.next());
      }
    }
    if (bufferEmpty)
    {
      normalizedRDN = buffer.toString();
    }
  }
  /**
   * Compares this RDN with the provided RDN.
   * <p>
   * The comparison will be done in order of the sorted RDN
   * components. It will attempt to use an ordering matching rule for
   * the associated attributes (if one is provided), but will fall
   * back on a bytewise comparison of the normalized values if
   * necessary.
   * Compares this RDN with the provided RDN based on an alphabetic
   * comparison of the attribute names and values.
   *
   * @param rdn
   *          The RDN against which to compare this RDN.
   * @param  rdn  The RDN against which to compare this RDN.
   *
   * @return A negative integer if this RDN should come before the
   *         provided RDN, a positive integer if this RDN should come
   *         after the provided RDN, or zero if there is no difference
   *         with regard to ordering.
   *          after the provided RDN, or zero if there is no
   *          difference with regard to ordering.
   */
  public int compareTo(RDN rdn) {
  public int compareTo(RDN rdn)
  {
    assert debugEnter(CLASS_NAME, "compareTo", String.valueOf(rdn));
    ensureNotNull(rdn);
    // Handle the common case efficiently.
    if (attributeTypes.length == 1
        && rdn.attributeTypes.length == 1) {
      AttributeType type1 = attributeTypes[0];
      AttributeType type2 = rdn.attributeTypes[0];
      AttributeValue value1 = attributeValues[0];
      AttributeValue value2 = rdn.attributeValues[0];
      return compareAVA(type1, value1, type2, value2);
    if ((attributeTypes.length == 1) &&
        (rdn.attributeTypes.length == 1))
    {
      if (attributeTypes[0].equals(rdn.attributeTypes[0]))
      {
        int valueComparison;
        OrderingMatchingRule omr =
             attributeTypes[0].getOrderingMatchingRule();
        if (omr == null)
        {
          try
          {
            return attributeValues[0].getNormalizedStringValue().
                        compareTo(rdn.attributeValues[0].
                             getNormalizedStringValue());
    }
          catch (Exception e)
          {
            assert debugException(CLASS_NAME, "compareTo", e);
    // We have at least one multi-valued RDNs, so we need to sort.
    TreeMap<String, Integer> map1;
    TreeMap<String, Integer> map2;
    map1 = new TreeMap<String, Integer>();
    map2 = new TreeMap<String, Integer>();
    for (int i = 0; i < attributeTypes.length; i++) {
      map1.put(attributeTypes[i].getNameOrOID(), i);
    }
    for (int i = 0; i < rdn.attributeTypes.length; i++) {
      map2.put(rdn.attributeTypes[i].getNameOrOID(), i);
    }
    // Now compare the sorted AVAs.
    Iterator<Integer> i1= map1.values().iterator();
    Iterator<Integer> i2 = map2.values().iterator();
    while (i1.hasNext() && i2.hasNext()) {
      int int1 = i1.next();
      int int2 = i2.next();
      AttributeType type1 = attributeTypes[int1];
      AttributeType type2 = rdn.attributeTypes[int2];
      AttributeValue value1 = attributeValues[int1];
      AttributeValue value2 = rdn.attributeValues[int2];
      int rc = compareAVA(type1, value1, type2, value2);
      if (rc != 0) {
        return rc;
            return attributeValues[0].getStringValue().
                        compareTo(rdn.attributeValues[0].
                             getStringValue());
      }
    }
        else
        {
          try
          {
            return omr.compareValues(
                        attributeValues[0].getNormalizedValue(),
                        rdn.attributeValues[0].getNormalizedValue());
          }
          catch (Exception e)
          {
            assert debugException(CLASS_NAME, "compareTo", e);
    // At least one of the iterators has finished.
    if (i1.hasNext() == false && i2.hasNext() == false) {
      return 0;
    } else if (i1.hasNext() == false) {
      return -1;
    } else {
      return 1;
            return omr.compareValues(
                        attributeValues[0].getValue(),
                        rdn.attributeValues[0].getValue());
    }
  }
  /**
   * Compare two AVAs for order.
   *
   * @param type1
   *          The attribute type of the first AVA.
   * @param value1
   *          The attribute value of the first AVA.
   * @param type2
   *          The attribute type of the second AVA.
   * @param value2
   *          The attribute value of the second AVA.
   * @return Returns a negative integer, zero, or a positive integer
   *         if the first AVA is less than, equal to, or greater than
   *         the second.
   */
  private int compareAVA(AttributeType type1, AttributeValue value1,
      AttributeType type2, AttributeValue value2) {
    if (type1.equals(type2)) {
      OrderingMatchingRule rule = type1.getOrderingMatchingRule();
      try {
        if (rule != null) {
          byte[] b1 = value1.getNormalizedValueBytes();
          byte[] b2 = value2.getNormalizedValueBytes();
          return rule.compare(b1, b2);
        } else {
          byte[] b1 = value1.getNormalizedValue().value();
          byte[] b2 = value2.getNormalizedValue().value();
          return StaticUtils.compare(b1, b2);
        }
      } catch (Exception e) {
        assert debugException(CLASS_NAME, "compareAVA", e);
        // Just get the raw values and do a comparison between them.
        byte[] b1 = value1.getValue().value();
        byte[] b2 = value2.getValue().value();
        return StaticUtils.compare(b1, b2);
      }
    } else {
      String name1 = toLowerCase(type1.getNameOrOID());
      String name2 = toLowerCase(type2.getNameOrOID());
      else
      {
        String name1 = toLowerCase(attributeTypes[0].getNameOrOID());
        String name2 =
             toLowerCase(rdn.attributeTypes[0].getNameOrOID());
      return name1.compareTo(name2);
    }
  }
    if (equals(rdn))
    {
      return 0;
    }
    TreeMap<String,AttributeType> typeMap1 =
         new TreeMap<String,AttributeType>();
    TreeMap<String,AttributeValue> valueMap1 =
         new TreeMap<String,AttributeValue>();
    for (int i=0; i < attributeTypes.length; i++)
    {
      String lowerName =
           toLowerCase(attributeTypes[i].getNameOrOID());
      typeMap1.put(lowerName, attributeTypes[i]);
      valueMap1.put(lowerName, attributeValues[i]);
    }
  /**
   * Normalize and append the provided attribute type and value to the
   * provided buffer.
   *
   * @param buffer
   *          The string buffer.
   * @param type
   *          The attribute type.
   * @param value
   *          The attribute value.
   */
  private void appendNormalizedAVA(StringBuilder buffer,
      AttributeType type, AttributeValue value) {
    toLowerCase(type.getNameOrOID(), buffer);
    buffer.append('=');
    TreeMap<String,AttributeType> typeMap2 =
         new TreeMap<String,AttributeType>();
    TreeMap<String,AttributeValue> valueMap2 =
         new TreeMap<String,AttributeValue>();
    for (int i=0; i < rdn.attributeTypes.length; i++)
    {
      String lowerName =
           toLowerCase(rdn.attributeTypes[i].getNameOrOID());
      typeMap2.put(lowerName, rdn.attributeTypes[i]);
      valueMap2.put(lowerName, rdn.attributeValues[i]);
    }
    try {
      quoteAttributeValue(buffer, value.getNormalizedStringValue());
    } catch (Exception e) {
      assert debugException(CLASS_NAME, "toNormalizedString", e);
      quoteAttributeValue(buffer, value.getStringValue());
    Iterator<String> iterator1 = valueMap1.keySet().iterator();
    Iterator<String> iterator2 = valueMap2.keySet().iterator();
    String           name1     = iterator1.next();
    String           name2     = iterator2.next();
    AttributeType    type1     = typeMap1.get(name1);
    AttributeType    type2     = typeMap2.get(name2);
    AttributeValue   value1    = valueMap1.get(name1);
    AttributeValue   value2    = valueMap2.get(name2);
    while (true)
    {
      if (type1.equals(type2))
      {
        int valueComparison;
        OrderingMatchingRule omr = type1.getOrderingMatchingRule();
        if (omr == null)
        {
          try
          {
            valueComparison =
                 value1.getNormalizedStringValue().compareTo(
                      value2.getNormalizedStringValue());
          }
          catch (Exception e)
          {
            assert debugException(CLASS_NAME, "compareTo", e);
            valueComparison =
                 value1.getStringValue().compareTo(
                      value2.getStringValue());
          }
        }
        else
        {
          try
          {
            valueComparison =
                 omr.compareValues(value1.getNormalizedValue(),
                                   value2.getNormalizedValue());
          }
          catch (Exception e)
          {
            assert debugException(CLASS_NAME, "compareTo", e);
            valueComparison =
                 omr.compareValues(value1.getValue(),
                                   value2.getValue());
    }
  }
  /**
   * Encode an attribute value according to the DN string encoding
   * rules, and append it to the provided buffer.
   *
   * @param buffer
   *          Append the attribtue value to this buffer.
   * @param value
   *          The value to be represented in a DN-safe form.
   */
  private void quoteAttributeValue(StringBuilder buffer,
      String value) {
    assert debugEnter(CLASS_NAME, "quoteAttributeValue", String
        .valueOf(value));
    // Do nothing if the value is empty.
    int length = value.length();
    if (length == 0) {
      return;
        if (valueComparison == 0)
        {
          if (! iterator1.hasNext())
          {
            if (iterator2.hasNext())
            {
              return -1;
            }
            else
            {
              return 0;
            }
    }
    // Assume 1-byte UTF8 and that no quoting will be required.
    buffer.ensureCapacity(buffer.length() + length);
    // Quote leading space or #.
    char c = value.charAt(0);
    if (c == ' ' || c == '#') {
      buffer.append('\\');
      buffer.append(c);
    } else {
      quoteChar(buffer, c);
          if (! iterator2.hasNext())
          {
            return 1;
    }
    // Process the remainder of the string.
    for (int i = 1; i < (length - 1); i++) {
      quoteChar(buffer, value.charAt(i));
          name1  = iterator1.next();
          name2  = iterator2.next();
          type1  = typeMap1.get(name1);
          type2  = typeMap2.get(name2);
          value1 = valueMap1.get(name1);
          value2 = valueMap2.get(name2);
    }
    // Quote trailing space.
    if (length > 1) {
      c = value.charAt(length - 1);
      if (c == ' ') {
        buffer.append('\\');
        buffer.append(c);
      } else {
        quoteChar(buffer, c);
        else
        {
          return valueComparison;
        }
      }
      else
      {
        return name1.compareTo(name2);
      }
      }
    }
  }
  /**
   * Encode a single attribute value from an RDN according to the DN
   * string encoding rules.
   *
   * @param buffer
   *          Append the character to this buffer.
   * @param c
   *          The character to be encoded.
   */
  private void quoteChar(StringBuilder buffer, char c) {
    if ((c < ' ') || (c > '~')) {
      for (byte b : getBytes(String.valueOf(c))) {
        buffer.append('\\');
        buffer.append(byteToLowerHex(b));
      }
    } else {
      switch (c) {
      case ',':
      case '+':
      case '"':
      case '\\':
      case '<':
      case '>':
      case ';':
        buffer.append('\\');
      }
      buffer.append(c);
    }
  }
  /**
   * Parses an attribute name from the provided DN string starting at
   * the specified location.
   *
   * @param dnString
   *          The DN string to be parsed.
   * @param pos
   *          The position at which to start parsing the attribute
   *          name.
   * @param attributeName
   *          The buffer to which to append the parsed attribute name.
   * @return The position of the first character that is not part of
   *         the attribute name.
   * @throws DirectoryException
   *           If it was not possible to parse a valid attribute name
   *           from the provided DN string.
   */
  private static int parseAttributeName(String dnString, int pos,
      StringBuilder attributeName) throws DirectoryException {
    assert debugEnter(CLASS_NAME, "parseAttributeName", String
        .valueOf(dnString), String.valueOf(pos),
        "java.lang.StringBuilder");
    boolean allowExceptions = DirectoryServer
        .allowAttributeNameExceptions();
    int length = dnString.length();
    // Skip over any leading spaces.
    if (pos < length) {
      while (dnString.charAt(pos) == ' ') {
        pos++;
        if (pos == length) {
          // This means that the remainder of the DN was completely
          // comprised of spaces. If we have gotten here, then we
          // know that there is at least one RDN component, and
          // therefore the last non-space character of the DN must
          // have been a comma. This is not acceptable.
          int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_COMMA;
          String message = getMessage(msgID, dnString);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
              message, msgID);
        }
      }
    }
    // Next, we should find the attribute name for this RDN component.
    // It may either be a name (with only letters, digits, and dashes
    // and starting with a letter) or an OID (with only digits and
    // periods, optionally prefixed with "oid."), and there is also a
    // special case in which we will allow underscores. Because of
    // the complexity involved, read the entire name first with
    // minimal validation and then do more thorough validation later.
    boolean checkForOID = false;
    boolean endOfName = false;
    while (pos < length) {
      // To make the switch more efficient, we'll include all ASCII
      // characters in the range of allowed values and then reject the
      // ones that aren't allowed.
      char c = dnString.charAt(pos);
      switch (c) {
      case ' ':
        // This should denote the end of the attribute name.
        endOfName = true;
        break;
      case '!':
      case '"':
      case '#':
      case '$':
      case '%':
      case '&':
      case '\'':
      case '(':
      case ')':
      case '*':
      case '+':
      case ',':
        // None of these are allowed in an attribute name or any
        // character immediately following it.
        int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
        String message = getMessage(msgID, dnString, c, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
            message, msgID);
      case '-':
        // This will be allowed as long as it isn't the first
        // character in the attribute name.
        if (attributeName.length() > 0) {
          attributeName.append(c);
        } else {
          msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DASH;
          message = getMessage(msgID, dnString, c);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
              message, msgID);
        }
        break;
      case '.':
        // The period could be allowed if the attribute name is
        // actually expressed as an OID. We'll accept it for now,
        // but make sure to check it later.
        attributeName.append(c);
        checkForOID = true;
        break;
      case '/':
        // This is not allowed in an attribute name or any character
        // immediately following it.
        msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
        message = getMessage(msgID, dnString, c, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
            message, msgID);
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
        // Digits are always allowed if they are not the first
        // character. However, they may be allowed if they are the
        // first character if the valid is an OID or if the
        // attribute name exceptions option is enabled. Therefore,
        // we'll accept it now and check it later.
        attributeName.append(c);
        break;
      case ':':
      case ';': // NOTE: attribute options are not allowed in a DN.
      case '<':
        // None of these are allowed in an attribute name or any
        // character immediately following it.
        msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
        message = getMessage(msgID, dnString, c, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
            message, msgID);
      case '=':
        // This should denote the end of the attribute name.
        endOfName = true;
        break;
      case '>':
      case '?':
      case '@':
        // None of these are allowed in an attribute name or any
        // character immediately following it.
        msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
        message = getMessage(msgID, dnString, c, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
            message, msgID);
      case 'A':
      case 'B':
      case 'C':
      case 'D':
      case 'E':
      case 'F':
      case 'G':
      case 'H':
      case 'I':
      case 'J':
      case 'K':
      case 'L':
      case 'M':
      case 'N':
      case 'O':
      case 'P':
      case 'Q':
      case 'R':
      case 'S':
      case 'T':
      case 'U':
      case 'V':
      case 'W':
      case 'X':
      case 'Y':
      case 'Z':
        // These will always be allowed.
        attributeName.append(c);
        break;
      case '[':
      case '\\':
      case ']':
      case '^':
        // None of these are allowed in an attribute name or any
        // character immediately following it.
        msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
        message = getMessage(msgID, dnString, c, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
            message, msgID);
      case '_':
        // This will never be allowed as the first character. It
        // may be allowed for subsequent characters if the attribute
        // name exceptions option is enabled.
        if (attributeName.length() == 0) {
          msgID =
            MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_UNDERSCORE;
          message = getMessage(msgID, dnString,
              ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
              message, msgID);
        } else if (allowExceptions) {
          attributeName.append(c);
        } else {
          msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_UNDERSCORE_CHAR;
          message = getMessage(msgID, dnString,
              ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
              message, msgID);
        }
        break;
      case '`':
        // This is not allowed in an attribute name or any character
        // immediately following it.
        msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
        message = getMessage(msgID, dnString, c, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
            message, msgID);
      case 'a':
      case 'b':
      case 'c':
      case 'd':
      case 'e':
      case 'f':
      case 'g':
      case 'h':
      case 'i':
      case 'j':
      case 'k':
      case 'l':
      case 'm':
      case 'n':
      case 'o':
      case 'p':
      case 'q':
      case 'r':
      case 's':
      case 't':
      case 'u':
      case 'v':
      case 'w':
      case 'x':
      case 'y':
      case 'z':
        // These will always be allowed.
        attributeName.append(c);
        break;
      default:
        // This is not allowed in an attribute name or any character
        // immediately following it.
        msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
        message = getMessage(msgID, dnString, c, pos);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
            message, msgID);
      }
      if (endOfName) {
        break;
      }
      pos++;
    }
    // We should now have the full attribute name. However, we may
    // still need to perform some validation, particularly if the
    // name contains a period or starts with a digit. It must also
    // have at least one character.
    if (attributeName.length() == 0) {
      int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_NO_NAME;
      String message = getMessage(msgID, dnString);
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
          message, msgID);
    } else if (checkForOID) {
      boolean validOID = true;
      int namePos = 0;
      int nameLength = attributeName.length();
      char ch = attributeName.charAt(0);
      if ((ch == 'o') || (ch == 'O')) {
        if (nameLength <= 4) {
          validOID = false;
        } else {
          if ((((ch = attributeName.charAt(1)) == 'i') || (ch == 'I'))
              && (((ch = attributeName.charAt(2)) == 'd')
                  || (ch == 'D'))
              && (attributeName.charAt(3) == '.')) {
            attributeName.delete(0, 4);
            nameLength -= 4;
          } else {
            validOID = false;
          }
        }
      }
      while (validOID && (namePos < nameLength)) {
        ch = attributeName.charAt(namePos++);
        if (isDigit(ch)) {
          while (validOID && (namePos < nameLength)
              && isDigit(attributeName.charAt(namePos))) {
            namePos++;
          }
          if ((namePos < nameLength)
              && (attributeName.charAt(namePos) != '.')) {
            validOID = false;
          }
        } else if (ch == '.') {
          if ((namePos == 1)
              || (attributeName.charAt(namePos - 2) == '.')) {
            validOID = false;
          }
        } else {
          validOID = false;
        }
      }
      if (validOID && (attributeName.charAt(nameLength - 1) == '.')) {
        validOID = false;
      }
      if (!validOID) {
        int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_PERIOD;
        String message = getMessage(msgID, dnString, attributeName
            .toString());
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
            message, msgID);
      }
    } else if (isDigit(attributeName.charAt(0))
        && (!allowExceptions)) {
      int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DIGIT;
      String message = getMessage(msgID, dnString, attributeName
          .charAt(0), ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
          message, msgID);
    }
    return pos;
  }
  /**
   * Parses the attribute value from the provided DN string starting
   * at the specified location. When the value has been parsed, it
   * will be assigned to the provided ASN.1 octet string.
   *
   * @param dnString
   *          The DN string to be parsed.
   * @param pos
   *          The position of the first character in the attribute
   *          value to parse.
   * @param attributeValue
   *          The ASN.1 octet string whose value should be set to the
   *          parsed attribute value when this method completes
   *          successfully.
   * @return The position of the first character that is not part of
   *         the attribute value.
   * @throws DirectoryException
   *           If it was not possible to parse a valid attribute value
   *           from the provided DN string.
   */
  private static int parseAttributeValue(String dnString, int pos,
      ByteString attributeValue) throws DirectoryException {
    assert debugEnter(CLASS_NAME, "parseAttributeValue", String
        .valueOf(dnString), String.valueOf(pos),
        "java.lang.StringBuilder");
    // All leading spaces have already been stripped so we can start
    // reading the value. However, it may be empty so check for that.
    int length = dnString.length();
    if (pos >= length) {
      attributeValue.setValue("");
      return pos;
    }
    // Look at the first character. If it is an octothorpe (#), then
    // that means that the value should be a hex string.
    char c = dnString.charAt(pos++);
    if (c == '#') {
      // The first two characters must be hex characters.
      StringBuilder hexString = new StringBuilder();
      if ((pos + 2) > length) {
        int msgID = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT;
        String message = getMessage(msgID, dnString);
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
            message, msgID);
      }
      for (int i = 0; i < 2; i++) {
        c = dnString.charAt(pos++);
        if (isHexDigit(c)) {
          hexString.append(c);
        } else {
          int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
          String message = getMessage(msgID, dnString, c);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
              message, msgID);
        }
      }
      // 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 (pos < length) {
        c = dnString.charAt(pos++);
        if (isHexDigit(c)) {
          hexString.append(c);
          if (pos < length) {
            c = dnString.charAt(pos++);
            if (isHexDigit(c)) {
              hexString.append(c);
            } else {
              int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
              String message = getMessage(msgID, dnString, c);
              throw new DirectoryException(
                  ResultCode.INVALID_DN_SYNTAX, message, msgID);
            }
          } else {
            int msgID = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT;
            String message = getMessage(msgID, dnString);
            throw new DirectoryException(
                ResultCode.INVALID_DN_SYNTAX, message, msgID);
          }
        } else if ((c == ' ') || (c == ',') || (c == ';')
            || (c == '+')) {
          // This denotes the end of the value.
          pos--;
          break;
        } else {
          int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
          String message = getMessage(msgID, dnString, c);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
              message, msgID);
        }
      }
      // 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 {
        attributeValue.setValue(hexStringToByteArray(hexString
            .toString()));
        return pos;
      } catch (Exception e) {
        assert debugException(CLASS_NAME, "parseAttributeValue", e);
        int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE;
        String message = getMessage(msgID, dnString, String
            .valueOf(e));
        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
            message, msgID);
      }
    }
    // If the first character is a quotation mark, then the value
    // should continue until the corresponding closing quotation mark.
    else if (c == '"') {
      // Keep reading until we find an unescaped closing quotation
      // mark.
      boolean escaped = false;
      StringBuilder valueString = new StringBuilder();
      while (true) {
        if (pos >= length) {
          // We hit the end of the DN before the closing quote.
          // That's an error.
          int msgID = MSGID_ATTR_SYNTAX_DN_UNMATCHED_QUOTE;
          String message = getMessage(msgID, dnString);
          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
              message, msgID);
        }
        c = dnString.charAt(pos++);
        if (escaped) {
          // The previous character was an escape, so we'll take this
          // one no matter what.
          valueString.append(c);
          escaped = false;
        } else if (c == '\\') {
          // The next character is escaped. Set a flag to denote
          // this, but don't include the backslash.
          escaped = true;
        } else if (c == '"') {
          // This is the end of the value.
          break;
        } else {
          // This is just a regular character that should be in the
          // value.
          valueString.append(c);
        }
      }
      attributeValue.setValue(valueString.toString());
      return pos;
    }
    // Otherwise, use general parsing to find the end of the value.
    else {
      boolean escaped;
      StringBuilder valueString = new StringBuilder();
      StringBuilder hexChars = new StringBuilder();
      if (c == '\\') {
        escaped = true;
      } else {
        escaped = false;
        valueString.append(c);
      }
      // Keep reading until we find an unescaped comma or plus sign or
      // the end of the DN.
      while (true) {
        if (pos >= length) {
          // This is the end of the DN and therefore the end of the
          // value. If there are any hex characters, then we need to
          // deal with them accordingly.
          appendHexChars(dnString, valueString, hexChars);
          break;
        }
        c = dnString.charAt(pos++);
        if (escaped) {
          // The previous character was an escape, so we'll take this
          // one. However, this could be a hex digit, and if that's
          // the case then the escape would actually be in front of
          // two hex digits that should be treated as a special
          // character.
          if (isHexDigit(c)) {
            // It is a hexadecimal digit, so the next digit must be
            // one too. However, this could be just one in a series
            // of escaped hex pairs that is used in a string
            // containing one or more multi-byte UTF-8 characters so
            // we can't just treat this byte in isolation. Collect
            // all the bytes together and make sure to take care of
            // these hex bytes before appending anything else to the
            // value.
            if (pos >= length) {
              int msgID =
                MSGID_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID;
              String message = getMessage(msgID, dnString);
              throw new DirectoryException(
                  ResultCode.INVALID_DN_SYNTAX, message, msgID);
            } else {
              char c2 = dnString.charAt(pos++);
              if (isHexDigit(c2)) {
                hexChars.append(c);
                hexChars.append(c2);
              } else {
                int msgID =
                  MSGID_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID;
                String message = getMessage(msgID, dnString);
                throw new DirectoryException(
                    ResultCode.INVALID_DN_SYNTAX, message, msgID);
              }
            }
          } else {
            appendHexChars(dnString, valueString, hexChars);
            valueString.append(c);
          }
          escaped = false;
        } else if (c == '\\') {
          escaped = true;
        } else if ((c == ',') || (c == ';') || (c == '+')) {
          appendHexChars(dnString, valueString, hexChars);
          pos--;
          break;
        } else {
          appendHexChars(dnString, valueString, hexChars);
          valueString.append(c);
        }
      }
      // Strip off any unescaped spaces that may be at the end of the
      // value.
      if (pos > 2 && dnString.charAt(pos - 1) == ' '
          && dnString.charAt(pos - 2) != '\\') {
        int lastPos = valueString.length() - 1;
        while (lastPos > 0) {
          if (valueString.charAt(lastPos) == ' ') {
            valueString.delete(lastPos, lastPos + 1);
            lastPos--;
          } else {
            break;
          }
        }
      }
      attributeValue.setValue(valueString.toString());
      return pos;
    }
  }
  /**
   * Decodes a hexadecimal string from the provided
   * <code>hexChars</code> buffer, converts it to a byte array, and
   * then converts that to a UTF-8 string. The resulting UTF-8 string
   * will be appended to the provided <code>valueString</code>
   * buffer, and the <code>hexChars</code> buffer will be cleared.
   *
   * @param dnString
   *          The DN string that is being decoded.
   * @param valueString
   *          The buffer containing the value to which the decoded
   *          string should be appended.
   * @param hexChars
   *          The buffer containing the hexadecimal characters to
   *          decode to a UTF-8 string.
   * @throws DirectoryException
   *           If any problem occurs during the decoding process.
   */
  private static void appendHexChars(String dnString,
      StringBuilder valueString, StringBuilder hexChars)
      throws DirectoryException {
    try {
      byte[] hexBytes = hexStringToByteArray(hexChars.toString());
      valueString.append(new String(hexBytes, "UTF-8"));
      hexChars.delete(0, hexChars.length());
    } catch (Exception e) {
      assert debugException(CLASS_NAME, "appendHexChars", e);
      int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE;
      String message = getMessage(msgID, dnString, String.valueOf(e));
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
          message, msgID);
    }
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.backends.jeb;
opends/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.monitors;
@@ -141,9 +141,10 @@
  {
    AttributeType cnType = DirectoryServer.getAttributeType(ATTR_COMMON_NAME);
    
    RDN rdn0 = RDN.create(cnType, new AttributeValue(cnType, monitorName));
    RDN rdn1 = RDN.create(cnType, new AttributeValue(cnType, "monitor"));
    DN monitorDN = DN.create(rdn0, rdn1);
    RDN[] rdns = new RDN[2];
    rdns[0] = RDN.create(cnType, new AttributeValue(cnType, monitorName));
    rdns[1] = RDN.create(cnType, new AttributeValue(cnType, "monitor"));
    DN monitorDN = new DN(rdns);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestAddResponseProtocolOp.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.protocols.ldap;
@@ -82,9 +82,10 @@
    AttributeValue attributeValue = new AttributeValue(attribute, "testValue");
    RDN rdn = RDN.create(attribute, attributeValue);
    RDN[] rdns = new RDN[1];
    rdns[0] = RDN.create(attribute, attributeValue);
    
    dn = DN.create(rdn);
    dn = new DN(rdns);
  }
  /**
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestCompareResponseProtocolOp.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.protocols.ldap;
@@ -84,8 +84,9 @@
    AttributeValue attributeValue = new AttributeValue(attribute, "testValue");
    RDN rdn = RDN.create(attribute, attributeValue);
    dn = DN.create(rdn);
    RDN[] rdns = new RDN[1];
    rdns[0] = RDN.create(attribute, attributeValue);
    dn = new DN(rdns);
  }
  /**
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestDeleteResponseProtocolOp.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.protocols.ldap;
@@ -82,8 +82,9 @@
    AttributeValue attributeValue = new AttributeValue(attribute, "testValue");
    RDN rdn = RDN.create(attribute, attributeValue);
    dn = DN.create(rdn);
    RDN[] rdns = new RDN[1];
    rdns[0] = RDN.create(attribute, attributeValue);
    dn = new DN(rdns);
  }
  /**
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyDNResponseProtocolOp.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.protocols.ldap;
@@ -84,8 +84,9 @@
    AttributeValue attributeValue = new AttributeValue(attribute, "testValue");
    RDN rdn = RDN.create(attribute, attributeValue);
    dn = DN.create(rdn);
    RDN[] rdns = new RDN[1];
    rdns[0] = RDN.create(attribute, attributeValue);
    dn = new DN(rdns);
  }
  /**
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyResponseProtocolOp.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.protocols.ldap;
@@ -84,8 +84,9 @@
    AttributeValue attributeValue = new AttributeValue(attribute, "testValue");
    RDN rdn = RDN.create(attribute, attributeValue);
    dn = DN.create(rdn);
    RDN[] rdns = new RDN[1];
    rdns[0] = RDN.create(attribute, attributeValue);
    dn = new DN(rdns);
  }
  /**
opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestDN.java
@@ -22,12 +22,14 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import java.util.ArrayList;
import static org.testng.Assert.*;
import org.opends.server.TestCaseUtils;
@@ -54,7 +56,12 @@
    return new Object[][] {
        { "", "", "" },
        { "   ", "", "" },
        { "cn=", "cn=", "cn=" },
        { "cn= ", "cn=", "cn=" },
        { "cn =", "cn=", "cn=" },
        { "cn = ", "cn=", "cn=" },
        { "dc=com", "dc=com", "dc=com" },
        { "dc=com+o=com", "dc=com+o=com", "dc=com+o=com" },
        { "DC=COM", "dc=com", "DC=COM" },
        { "dc = com", "dc=com", "dc=com" },
        { " dc = com ", "dc=com", "dc=com" },
@@ -126,12 +133,17 @@
   */
  @DataProvider(name = "illegalDNs")
  public Object[][] createIllegalData() {
    return new Object[][] { { "manager" }, { "manager " },
        { "cn+Jim" }, { "cn=Jim+" }, { "cn=Jim," }, { "cn=Jim,  " },
        { "cn+uid=Jim" }, { "-cn=Jim" }, { "/tmp=a" }, { "\\tmp=a" },
        { "cn;lang-en=Jim" }, { "@cn=Jim" }, { "_name_=Jim" },
        { "\u03c0=pi" }, { "v1.0=buggy" },
        { "1.3.6.1.4.1.1466..0=#04024869" }, };
    return new Object[][] { { "manager" }, { "manager " }, { "=Jim" },
        { " =Jim" }, { "= Jim" }, { " = Jim" }, { "cn+Jim" }, { "cn + Jim" },
        { "cn=Jim+" }, { "cn=Jim+manager" }, { "cn=Jim+manager " },
        { "cn=Jim+manager," }, { "cn=Jim," }, { "cn=Jim,  " }, { "c[n]=Jim" },
        { "_cn=Jim" }, { "c_n=Jim" }, { "cn\"=Jim" },  { "c\"n=Jim" },
        { "1cn=Jim" }, { "cn+uid=Jim" }, { "-cn=Jim" }, { "/tmp=a" },
        { "\\tmp=a" },  { "cn;lang-en=Jim" }, { "@cn=Jim" }, { "_name_=Jim" },
        { "\u03c0=pi" },  { "v1.0=buggy" }, { "1.=buggy" }, { ".1=buggy" },
        { "oid.1." }, { "1.3.6.1.4.1.1466..0=#04024869" },
        { "cn=#a" }, { "cn=#ag" }, { "cn=#ga" }, { "cn=#abcdefgh" },
        { "cn=a\\b" }, { "cn=a\\bg" }, { "cn=\"hello" } };
  }
@@ -162,23 +174,9 @@
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(expectedExceptions = { NullPointerException.class,
      AssertionError.class })
  public void testCreateNPE() throws Exception {
    DN.create((RDN[]) null);
  }
  /**
   * Tests the create method.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testCreateNullDN1() throws Exception {
    DN dn = DN.create(new RDN[0]);
    DN dn = new DN(new RDN[0]);
    assertEquals(dn, DN.nullDN());
  }
@@ -193,7 +191,37 @@
   */
  @Test
  public void testCreateNullDN2() throws Exception {
    DN dn = DN.create();
    DN dn = new DN();
    assertEquals(dn, DN.nullDN());
  }
  /**
   * Tests the create method.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testCreateNullDN3() throws Exception {
    DN dn = new DN((RDN[]) null);
    assertEquals(dn, DN.nullDN());
  }
  /**
   * Tests the create method.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testCreateNullDN4() throws Exception {
    DN dn = new DN((ArrayList<RDN>) null);
    assertEquals(dn, DN.nullDN());
  }
@@ -208,22 +236,7 @@
   */
  @Test
  public void testCreateWithSingleRDN1() throws Exception {
    DN dn = DN.create(new RDN[] { RDN.decode("dc=com") });
    assertEquals(dn, DN.decode("dc=com"));
  }
  /**
   * Tests the create method.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testCreateWithSingleRDN2() throws Exception {
    DN dn = DN.create(RDN.decode("dc=com"));
    DN dn = new DN(new RDN[] { RDN.decode("dc=com") });
    assertEquals(dn, DN.decode("dc=com"));
  }
@@ -238,7 +251,7 @@
   */
  @Test
  public void testCreateWithMultipleRDNs1() throws Exception {
    DN dn = DN.create(new RDN[] { RDN.decode("dc=foo"),
    DN dn = new DN(new RDN[] { RDN.decode("dc=foo"),
        RDN.decode("dc=opends"), RDN.decode("dc=org") });
    assertEquals(dn, DN.decode("dc=foo,dc=opends,dc=org"));
@@ -254,8 +267,11 @@
   */
  @Test
  public void testCreateWithMultipleRDNs2() throws Exception {
    DN dn = DN.create(RDN.decode("dc=foo"), RDN.decode("dc=opends"),
        RDN.decode("dc=org"));
    ArrayList<RDN> rdnList = new ArrayList<RDN>();
    rdnList.add(RDN.decode("dc=foo"));
    rdnList.add(RDN.decode("dc=opends"));
    rdnList.add(RDN.decode("dc=org"));
    DN dn = new DN(rdnList);
    assertEquals(dn, DN.decode("dc=foo,dc=opends,dc=org"));
  }
@@ -309,23 +325,32 @@
  /**
   * Tests the <CODE>valueOf</CODE> method which takes a String
   * argument.
   * Tests the toNoramlizedString methods.
   *
   * @param rawDN
   *          Raw DN string representation.
   * @param normDN
   *          Normalized DN string representation.
   * @param stringDN
   *          String representation.
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(dataProvider = "testDNs")
  public void testValueOf(String rawDN, String normDN, String stringDN)
      throws Exception {
    DN dn = DN.valueOf(rawDN);
    assertEquals(dn.toNormalizedString(), normDN);
  public void testToNormalizedString() throws Exception {
    DN dn = DN.decode("dc=example,dc=com");
    StringBuilder buffer = new StringBuilder();
    dn.toNormalizedString(buffer);
    assertEquals(buffer.toString(), "dc=example,dc=com");
    assertEquals(dn.toNormalizedString(), "dc=example,dc=com");
  }
  /**
   * Tests both variants of the {@code decode} method with null arguments.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  public void testDecodeNull() throws Exception {
    assertEquals(DN.decode((ByteString) null), DN.nullDN());
    assertEquals(DN.decode((String) null), DN.nullDN());
  }
@@ -386,32 +411,6 @@
  /**
   * Test that decoding an illegal DN as a String throws an exception.
   *
   * @param dn
   *          The illegal DN to be tested.
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(dataProvider = "illegalDNs", expectedExceptions = DirectoryException.class)
  public void testIllegalValueOf(String dn) throws Exception {
    try {
      DN.valueOf(dn);
    } catch (DirectoryException e) {
      throw e;
    } catch (Exception e) {
      System.out.println("Illegal DN <" + dn
          + "> threw the wrong type of exception");
      throw e;
    }
    throw new RuntimeException("Illegal DN <" + dn
        + "> did not throw an exception");
  }
  /**
   * Test the nullDN method.
   *
   * @throws Exception
@@ -530,6 +529,50 @@
  /**
   * Retrieves the naming contexts defined in the server.
   */
  @DataProvider(name = "namingContexts")
  public Object[][] getNamingContexts() {
    ArrayList<DN> contextList = new ArrayList<DN>();
    for (DN baseDN : DirectoryServer.getPublicNamingContexts().keySet())
    {
      contextList.add(baseDN);
    }
    for (DN baseDN : DirectoryServer.getPrivateNamingContexts().keySet())
    {
      contextList.add(baseDN);
    }
    Object[][] contextArray = new Object[contextList.size()][1];
    for (int i=0; i < contextArray.length; i++)
    {
      contextArray[i][0] = contextList.get(i);
    }
    return contextArray;
  }
  /**
   * Tests the getParentDNInSuffix method.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(dataProvider = "namingContexts")
  public void testGetParentDNInSuffix(DN namingContext) throws Exception {
    assertNull(namingContext.getParentDNInSuffix());
    DN childDN = namingContext.concat(RDN.decode("ou=People"));
    assertNotNull(childDN.getParentDNInSuffix());
    assertEquals(childDN.getParentDNInSuffix(), namingContext);
  }
  /**
   * Test the getParent method's interaction with other methods.
   *
   * @throws Exception
@@ -574,9 +617,6 @@
    assertEquals(p.concat(RDN.decode("dc=foo")), c);
    assertEquals(p.concat(DN.decode("dc=xxx,dc=foo")), DN
        .decode("dc=xxx,dc=foo,dc=bar,dc=opends,dc=org"));
    assertEquals(p.getLocalName(1), DN.decode("dc=bar,dc=opends"));
    assertEquals(p.getLocalName(0, 2), DN.decode("dc=opends,dc=org"));
  }
@@ -791,10 +831,6 @@
        .decode("dc=xxx,dc=foo,dc=bar,dc=opends,dc=org"));
    assertEquals(c.concat(DN.decode("dc=xxx,dc=yyy")), DN
        .decode("dc=xxx,dc=yyy,dc=foo,dc=bar,dc=opends,dc=org"));
    assertEquals(c.getLocalName(1), DN
        .decode("dc=foo,dc=bar,dc=opends"));
    assertEquals(c.getLocalName(1, 3), DN.decode("dc=bar,dc=opends"));
  }
@@ -843,21 +879,6 @@
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testConcatRDNNoOp() throws Exception {
    DN dn = DN.decode("dc=opends,dc=org");
    assertEquals(dn.concat(), dn);
  }
  /**
   * Test the concat(RDN...) method.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(expectedExceptions = { NullPointerException.class,
      AssertionError.class })
  public void testConcatRDNException() throws Exception {
@@ -898,52 +919,6 @@
  /**
   * Test the concat(RDN[]...) method.
   *
   * @param s
   *          The test DN string.
   * @param l
   *          The local name to be appended.
   * @param e
   *          The expected DN.
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(dataProvider = "createConcatDNTestData")
  public void testConcatRDNSequence2(String s, String l, String e)
      throws Exception {
    DN dn = DN.decode(s);
    DN localName = DN.decode(l);
    DN expected = DN.decode(e);
    // Construct sequence.
    DN actual = null;
    switch (localName.getNumComponents()) {
    case 0:
      actual = dn.concat();
      break;
    case 1:
      actual = dn.concat(localName.getRDN(0));
      break;
    case 2:
      actual = dn.concat(localName.getRDN(0), localName.getRDN(1));
      break;
    case 3:
      actual = dn.concat(localName.getRDN(0), localName.getRDN(1),
          localName.getRDN(2));
      break;
    case 4:
      actual = dn.concat(localName.getRDN(0), localName.getRDN(1),
          localName.getRDN(3), localName.getRDN(3));
      break;
    }
    assertEquals(actual, expected);
  }
  /**
   * Get local name test data provider.
   *
   * @return The array of test data.
@@ -986,86 +961,6 @@
  /**
   * Test the getLocalName methods.
   *
   * @param s
   *          The test DN string.
   * @param beginIndex
   *          The index of the uppermost RDN.
   * @param endIndex
   *          The index of the lowest RDN or -1 if there is no end
   *          index.
   * @param e
   *          The expected result.
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(dataProvider = "createGetLocalNameTestData")
  public void testGetLocalName(String s, int beginIndex,
      int endIndex, String e) throws Exception {
    DN dn = DN.decode(s);
    DN expected = DN.decode(e);
    if (endIndex < 0) {
      assertEquals(dn.getLocalName(beginIndex), expected);
    } else {
      assertEquals(dn.getLocalName(beginIndex, endIndex), expected);
    }
  }
  /**
   * Test the getLocalName method's interaction with other methods.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testGetLocalNameInteraction() throws Exception {
    DN p = DN.decode("dc=foo,dc=bar,dc=opends,dc=org");
    DN e = DN.decode("dc=bar,dc=opends");
    DN l = p.getLocalName(1, 3);
    assertFalse(l.isNullDN());
    assertEquals(l.getNumComponents(), 2);
    assertEquals(l.compareTo(e), 0);
    assertEquals(e.compareTo(l), 0);
    assertTrue(l.isAncestorOf(DN.decode("dc=foo,dc=bar,dc=opends")));
    assertFalse(DN.decode("dc=foo,dc=bar,dc=opends").isAncestorOf(l));
    assertTrue(l.isDescendantOf(DN.decode("dc=opends")));
    assertFalse(DN.decode("dc=opends").isDescendantOf(l));
    assertEquals(l, e);
    assertEquals(l.hashCode(), e.hashCode());
    assertEquals(l.toNormalizedString(), e.toNormalizedString());
    assertEquals(l.toString(), e.toString());
    assertEquals(l.getRDN(), RDN.decode("dc=bar"));
    assertEquals(l.getRDN(0), RDN.decode("dc=bar"));
    assertEquals(l.getRDN(1), RDN.decode("dc=opends"));
    assertEquals(l.getParent(), DN.decode("dc=opends"));
    assertEquals(l.getParent(), e.getParent());
    assertEquals(l.concat(RDN.decode("dc=xxx")), DN
        .decode("dc=xxx,dc=bar,dc=opends"));
    assertEquals(l.concat(DN.decode("dc=xxx,dc=yyy")), DN
        .decode("dc=xxx,dc=yyy,dc=bar,dc=opends"));
    assertEquals(l.getLocalName(1), DN.decode("dc=bar"));
    assertEquals(l.getLocalName(0, 1), DN.decode("dc=opends"));
  }
  /**
   * Is ancestor of test data provider.
   *
   * @return The array of test data.
@@ -1244,7 +1139,8 @@
        { "dc=aaa,dc=ccc", "dc=bbb", 1 },
        { "dc=bbb,dc=ccc", "dc=bbb", 1 },
        { "dc=ccc,dc=ccc", "dc=bbb", 1 },
        { "", "dc=bbb", -1 },
        { "dc=bbb", "", 1 }
    };
  }
@@ -1280,6 +1176,21 @@
  /**
   * Tests the equals method with a value that's not a DN.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testEqualsNonDN() throws Exception {
    DN dn = DN.decode("dc=example,dc=com");
    assertFalse(dn.equals("not a DN"));
  }
  /**
   * Test DN hashCode
   *
   * @param first
@@ -1366,5 +1277,5 @@
    DN dn = DN.decode(rawDN);
    assertEquals(dn.toString(), stringDN);
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestRDN.java
@@ -22,12 +22,14 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import java.util.ArrayList;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
@@ -93,113 +95,6 @@
  // First test the constructors.
  /**
   * Check the constructor throws a NPE when parameters are not
   * provided.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(expectedExceptions = { NullPointerException.class,
      AssertionError.class })
  public void testConstructorNPE1() throws Exception {
    RDN.create(AT_DC, null);
  }
  /**
   * Check the constructor throws a NPE when parameters are not
   * provided.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(expectedExceptions = { NullPointerException.class,
      AssertionError.class })
  public void testConstructorNPE2() throws Exception {
    RDN.create(null, AV_DC_ORG);
  }
  /**
   * Check the constructor throws a NPE when parameters are not
   * provided.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(expectedExceptions = { NullPointerException.class,
      AssertionError.class })
  public void testConstructorNPE3() throws Exception {
    RDN.create(AT_DC, "dc", null);
  }
  /**
   * Check the constructor throws a NPE when parameters are not
   * provided.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(expectedExceptions = { NullPointerException.class,
      AssertionError.class })
  public void testConstructorNPE4() throws Exception {
    RDN.create(AT_DC, null, AV_DC_ORG);
  }
  /**
   * Check the constructor throws a NPE when parameters are not
   * provided.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(expectedExceptions = { NullPointerException.class,
      AssertionError.class })
  public void testConstructorNPE5() throws Exception {
    RDN.create(null, "dc", AV_DC_ORG);
  }
  /**
   * Check the decode method throws a NPE when parameters are not
   * provided.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(expectedExceptions = { NullPointerException.class,
      AssertionError.class })
  public void testDecodeNPE() throws Exception {
    RDN.decode((String) null);
  }
  /**
   * Check the valueOf method throws a NPE when parameters are not
   * provided.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(expectedExceptions = { NullPointerException.class,
      AssertionError.class })
  public void testValueOfNPE() throws Exception {
    RDN.valueOf(null);
  }
  /**
   * Test RDN construction with single AVA.
   *
@@ -208,7 +103,7 @@
   */
  @Test
  public void testConstructor() throws Exception {
    RDN rdn = RDN.create(AT_DC, AV_DC_ORG);
    RDN rdn = new RDN(AT_DC, AV_DC_ORG);
    assertEquals(rdn.getNumValues(), 1);
    assertEquals(rdn.getAttributeType(0), AT_DC);
@@ -226,7 +121,7 @@
   */
  @Test
  public void testConstructorWithName() throws Exception {
    RDN rdn = RDN.create(AT_DC, "domainComponent", AV_DC_ORG);
    RDN rdn = new RDN(AT_DC, "domainComponent", AV_DC_ORG);
    assertEquals(rdn.getNumValues(), 1);
    assertEquals(rdn.getAttributeType(0), AT_DC);
@@ -237,103 +132,54 @@
  /**
   * Test RDN builder.
   * Test RDN construction with a multiple AVA elements.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testBuilder() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
  public void testConstructorMultiAVA() throws Exception {
    AttributeType[]  attrTypes  = { AT_DC, AT_CN };
    String[]         attrNames  = { AT_DC.getNameOrOID(),
                                    AT_CN.getNameOrOID() };
    AttributeValue[] attrValues = { AV_DC_ORG, AV_CN };
    builder.append(AT_DC, AV_DC_ORG);
    RDN rdn = builder.getInstance();
    RDN rdn = new RDN(attrTypes, attrNames, attrValues);
    assertEquals(rdn.getNumValues(), 1);
    assertEquals(rdn.getNumValues(), 2);
    assertEquals(rdn.getAttributeType(0), AT_DC);
    assertEquals(rdn.getAttributeName(0), AT_DC.getNameOrOID());
    assertEquals(rdn.getAttributeValue(0), AV_DC_ORG);
    assertEquals(rdn.getAttributeType(1), AT_CN);
    assertEquals(rdn.getAttributeName(1), AT_CN.getNameOrOID());
    assertEquals(rdn.getAttributeValue(1), AV_CN);
  }
  /**
   * Test RDN builder.
   * Test RDN construction with a multiple AVA elements.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testBuilderWithName() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
  public void testConstructorMultiAVAList() throws Exception {
    ArrayList<AttributeType>  typeList  = new ArrayList<AttributeType>();
    ArrayList<String>         nameList  = new ArrayList<String>();
    ArrayList<AttributeValue> valueList = new ArrayList<AttributeValue>();
    builder.append(AT_DC, "domainComponent", AV_DC_ORG);
    RDN rdn = builder.getInstance();
    typeList.add(AT_DC);
    nameList.add(AT_DC.getNameOrOID());
    valueList.add(AV_DC_ORG);
    assertEquals(rdn.getNumValues(), 1);
    assertEquals(rdn.getAttributeType(0), AT_DC);
    assertEquals(rdn.getAttributeName(0), "domainComponent");
    assertEquals(rdn.getAttributeValue(0), AV_DC_ORG);
  }
    typeList.add(AT_CN);
    nameList.add(AT_CN.getNameOrOID());
    valueList.add(AV_CN);
  /**
   * Test RDN builder.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testBuilderIsEmpty() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
    assertTrue(builder.isEmpty());
    builder.append(AT_DC, AV_DC_ORG);
    assertFalse(builder.isEmpty());
    builder.getInstance();
    assertFalse(builder.isEmpty());
    builder.clear();
    assertTrue(builder.isEmpty());
  }
  /**
   * Test RDN builder.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(expectedExceptions = IllegalArgumentException.class)
  public void testBuilderDupesNotAllowed() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
    builder.append(AT_DC, AV_DC_ORG);
    builder.append(AT_DC, AV_DC_OPENDS);
  }
  /**
   * Test RDN builder.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testBuilderMultiAVA() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
    builder.append(AT_DC, AV_DC_ORG);
    builder.append(AT_CN, AV_CN);
    RDN rdn = builder.getInstance();
    RDN rdn = new RDN(typeList, nameList, valueList);
    assertEquals(rdn.getNumValues(), 2);
@@ -357,6 +203,11 @@
  public Object[][] createData() {
    return new Object[][] {
        { "dc=hello world", "dc=hello world", "dc=hello world" },
        { "dc =hello world", "dc=hello world", "dc=hello world" },
        { "dc  =hello world", "dc=hello world", "dc=hello world" },
        { "dc= hello world", "dc=hello world", "dc=hello world" },
        { "dc=  hello world", "dc=hello world", "dc=hello world" },
        { "undefined=hello", "undefined=hello", "undefined=hello" },
        { "DC=HELLO WORLD", "dc=hello world", "DC=HELLO WORLD" },
        { "dc = hello    world", "dc=hello world",
            "dc=hello    world" },
@@ -417,61 +268,20 @@
  /**
   * Test RDN byte string decoder.
   *
   * @param rawRDN
   *          Raw RDN string representation.
   * @param normRDN
   *          Normalized RDN string representation.
   * @param stringRDN
   *          String representation.
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(dataProvider = "testRDNs")
  public void testDecodeByteString(String rawRDN, String normRDN,
      String stringRDN) throws Exception {
    ASN1OctetString octetString = new ASN1OctetString(rawRDN);
    RDN rdn = RDN.decode(octetString);
    assertEquals(rdn.toNormalizedString(), normRDN);
  }
  /**
   * Test valueOf.
   *
   * @param rawRDN
   *          Raw RDN string representation.
   * @param normRDN
   *          Normalized RDN string representation.
   * @param stringRDN
   *          String representation.
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(dataProvider = "testRDNs")
  public void testValueOf(String rawRDN, String normRDN,
      String stringRDN) throws Exception {
    RDN rdn = RDN.valueOf(rawRDN);
    assertEquals(rdn.toNormalizedString(), normRDN);
  }
  /**
   * Illegal RDN test data provider.
   *
   * @return The array of illegal test RDN strings.
   */
  @DataProvider(name = "illegalRDNs")
  public Object[][] createIllegalData() {
    return new Object[][] { { "" }, { "=" }, { "manager" },
        { "manager " }, { "cn+Jim" }, { "cn=Jim+" }, { "cn=Jim+sn" },
        { "cn=Jim," }, { "cn=Jim,  " }, { "cn=Jim, sn=Jam " },
        { "cn+uid=Jim" }, { "-cn=Jim" }, { "/tmp=a" }, { "\\tmp=a" },
        { "cn;lang-en=Jim" }, { "@cn=Jim" }, { "_name_=Jim" },
        { "\u03c0=pi" }, { "v1.0=buggy" },
    return new Object[][] { { null }, { "" }, { " " }, { "=" }, { "manager" },
        { "manager " }, { "cn+"}, { "cn+Jim" }, { "cn=Jim+" }, { "cn=Jim +" },
        { "cn=Jim+ " }, { "cn=Jim+sn" }, { "cn=Jim+sn " },
        { "cn=Jim+sn equals" }, { "cn=Jim," }, { "cn=Jim;" }, { "cn=Jim,  " },
        { "cn=Jim+sn=a," }, { "cn=Jim, sn=Jam " }, { "cn+uid=Jim" },
        { "-cn=Jim" }, { "/tmp=a" }, { "\\tmp=a" }, { "cn;lang-en=Jim" },
        { "@cn=Jim" }, { "_name_=Jim" }, { "\u03c0=pi" }, { "v1.0=buggy" },
        { "cn=Jim+sn=Bob++" }, { "cn=Jim+sn=Bob+," },
        { "1.3.6.1.4.1.1466..0=#04024869" }, };
  }
@@ -495,41 +305,6 @@
  /**
   * Test RDN byte string decoder against illegal strings.
   *
   * @param rawRDN
   *          Illegal RDN string representation.
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(dataProvider = "illegalRDNs", expectedExceptions = DirectoryException.class)
  public void testDecodeByteString(String rawRDN) throws Exception {
    ASN1OctetString octetString = new ASN1OctetString(rawRDN);
    RDN.decode(octetString);
    fail("Expected exception for value \"" + rawRDN + "\"");
  }
  /**
   * Test valueOf against illegal strings.
   *
   * @param rawRDN
   *          Illegal RDN string representation.
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(dataProvider = "illegalRDNs", expectedExceptions = DirectoryException.class)
  public void testValueOf(String rawRDN) throws Exception {
    RDN.valueOf(rawRDN);
    fail("Expected exception for value \"" + rawRDN + "\"");
  }
  /**
   * Test getAttributeName.
   *
   * @throws Exception
@@ -537,11 +312,12 @@
   */
  @Test
  public void testGetAttributeName() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
    AttributeType[]  attrTypes  = { AT_DC, AT_CN };
    String[]         attrNames  = { AT_DC.getNameOrOID(),
                                    AT_CN.getNameOrOID() };
    AttributeValue[] attrValues = { AV_DC_ORG, AV_CN };
    builder.append(AT_DC, AV_DC_ORG);
    builder.append(AT_CN, AV_CN);
    RDN rdn = builder.getInstance();
    RDN rdn = new RDN(attrTypes, attrNames, attrValues);
    assertEquals(rdn.getAttributeName(0), AT_DC.getNameOrOID());
    assertEquals(rdn.getAttributeName(1), AT_CN.getNameOrOID());
@@ -557,10 +333,8 @@
   */
  @Test(expectedExceptions = IndexOutOfBoundsException.class)
  public void testGetAttributeNameException() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
    builder.append(AT_DC, AV_DC_ORG);
    RDN rdn = builder.getInstance();
    RDN rdn = new RDN(new AttributeType[0], new String[0],
                      new AttributeValue[0]);
    rdn.getAttributeName(1);
  }
@@ -575,11 +349,12 @@
   */
  @Test
  public void testGetAttributeType() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
    AttributeType[]  attrTypes  = { AT_DC, AT_CN };
    String[]         attrNames  = { AT_DC.getNameOrOID(),
                                    AT_CN.getNameOrOID() };
    AttributeValue[] attrValues = { AV_DC_ORG, AV_CN };
    builder.append(AT_DC, AV_DC_ORG);
    builder.append(AT_CN, AV_CN);
    RDN rdn = builder.getInstance();
    RDN rdn = new RDN(attrTypes, attrNames, attrValues);
    assertEquals(rdn.getAttributeType(0), AT_DC);
    assertEquals(rdn.getAttributeType(1), AT_CN);
@@ -595,10 +370,8 @@
   */
  @Test(expectedExceptions = IndexOutOfBoundsException.class)
  public void testGetAttributeTypeException() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
    builder.append(AT_DC, AV_DC_ORG);
    RDN rdn = builder.getInstance();
    RDN rdn = new RDN(new AttributeType[0], new String[0],
                      new AttributeValue[0]);
    rdn.getAttributeType(1);
  }
@@ -613,11 +386,12 @@
   */
  @Test
  public void testGetAttributeValue() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
    AttributeType[]  attrTypes  = { AT_DC, AT_CN };
    String[]         attrNames  = { AT_DC.getNameOrOID(),
                                    AT_CN.getNameOrOID() };
    AttributeValue[] attrValues = { AV_DC_ORG, AV_CN };
    builder.append(AT_DC, AV_DC_ORG);
    builder.append(AT_CN, AV_CN);
    RDN rdn = builder.getInstance();
    RDN rdn = new RDN(attrTypes, attrNames, attrValues);
    assertEquals(rdn.getAttributeValue(0), AV_DC_ORG);
    assertEquals(rdn.getAttributeValue(1), AV_CN);
@@ -633,10 +407,8 @@
   */
  @Test(expectedExceptions = IndexOutOfBoundsException.class)
  public void testGetAttributeValueException() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
    builder.append(AT_DC, AV_DC_ORG);
    RDN rdn = builder.getInstance();
    RDN rdn = new RDN(new AttributeType[0], new String[0],
                      new AttributeValue[0]);
    rdn.getAttributeValue(1);
  }
@@ -651,10 +423,7 @@
   */
  @Test
  public void testGetAttributeValueByType() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
    builder.append(AT_DC, AV_DC_ORG);
    RDN rdn = builder.getInstance();
    RDN rdn = new RDN(AT_DC, AV_DC_ORG);
    assertEquals(rdn.getAttributeValue(AT_DC), AV_DC_ORG);
    assertNull(rdn.getAttributeValue(AT_CN));
@@ -670,14 +439,10 @@
   */
  @Test
  public void testGetNumValues() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
    builder.append(AT_DC, AV_DC_ORG);
    RDN rdn = builder.getInstance();
    RDN rdn = new RDN(AT_DC, AV_DC_ORG);
    assertEquals(rdn.getNumValues(), 1);
    builder.append(AT_CN, AV_CN);
    rdn = builder.getInstance();
    rdn.addValue(AT_CN, AT_CN.getNameOrOID(), AV_CN);
    assertEquals(rdn.getNumValues(), 2);
  }
@@ -690,13 +455,14 @@
   *           If the test failed unexpectedly.
   */
  @Test
  public void testHasAttributeType() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
    builder.append(AT_DC, AV_DC_ORG);
    RDN rdn = builder.getInstance();
  public void testHasAttributeType1() throws Exception {
    RDN rdn = new RDN(AT_DC, AV_DC_ORG);
    assertTrue(rdn.hasAttributeType(AT_DC));
    assertTrue(rdn.hasAttributeType("dc"));
    assertTrue(rdn.hasAttributeType(AT_DC.getOID()));
    assertFalse(rdn.hasAttributeType(AT_CN));
    assertFalse(rdn.hasAttributeType("cn"));
  }
@@ -709,20 +475,50 @@
   */
  @Test
  public void testIsMultiValued() throws Exception {
    RDN.Builder builder = RDN.createBuilder();
    builder.append(AT_DC, AV_DC_ORG);
    RDN rdn = builder.getInstance();
    RDN rdn = new RDN(AT_DC, AV_DC_ORG);
    assertEquals(rdn.getNumValues(), 1);
    assertFalse(rdn.isMultiValued());
    builder.append(AT_CN, AV_CN);
    rdn = builder.getInstance();
    rdn.addValue(AT_CN, AT_CN.getNameOrOID(), AV_CN);
    assertTrue(rdn.isMultiValued());
  }
  /**
   * Tests hasValue.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testHasValue() throws Exception {
    RDN rdn = new RDN(AT_DC, AV_DC_ORG);
    assertTrue(rdn.hasValue(AT_DC, AV_DC_ORG));
    assertFalse(rdn.hasValue(AT_CN, AV_CN));
    rdn.addValue(AT_CN, AT_CN.getNameOrOID(), AV_CN);
    assertTrue(rdn.hasValue(AT_DC, AV_DC_ORG));
    assertTrue(rdn.hasValue(AT_CN, AV_CN));
  }
  /**
   * Tests addValue with a duplicate value.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testAddDuplicateValue() throws Exception {
    RDN rdn = new RDN(AT_DC, AV_DC_ORG);
    assertFalse(rdn.addValue(AT_DC, AT_DC.getNameOrOID(), AV_DC_ORG));
  }
  /**
   * Test RDN string decoder.
   *
   * @param rawRDN
@@ -744,6 +540,44 @@
  /**
   * Tests the duplicate method with a single-valued RDN.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testDuplicateSingle() {
    RDN rdn1 = new RDN(AT_DC, AV_DC_ORG);
    RDN rdn2 = rdn1.duplicate();
    assertFalse(rdn1 == rdn2);
    assertEquals(rdn1, rdn2);
  }
  /**
   * Tests the duplicate method with a multivalued RDN.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testDuplicateMultiValued() {
    AttributeType[]  types  = new AttributeType[] { AT_DC, AT_CN };
    String[]         names  = new String[] { "dc", "cn" };
    AttributeValue[] values = new AttributeValue[] { AV_DC_ORG, AV_CN };
    RDN rdn1 = new RDN(types, names, values);
    RDN rdn2 = rdn1.duplicate();
    assertFalse(rdn1 == rdn2);
    assertEquals(rdn1, rdn2);
  }
  /**
   * RDN equality test data provider.
   *
   * @return The array of test RDN strings.
@@ -758,6 +592,13 @@
        { "cn=hello world\\ ", "cn=hello world", 0 },
        { "cn=HELLO WORLD", "cn=hello world", 0 },
        { "cn=HELLO+sn=WORLD", "sn=world+cn=hello", 0 },
        { "cn=HELLO+sn=WORLD", "cn=hello+sn=nurse", 1 },
        { "cn=HELLO+sn=WORLD", "cn=howdy+sn=yall", -1 },
        { "cn=hello", "cn=hello+sn=world", -1 },
        { "cn=hello+sn=world", "cn=hello", 1 },
        { "cn=hello+sn=world", "cn=hello+description=world", 1 },
        { "cn=hello", "sn=world", -1 },
        { "sn=hello", "cn=world", 1 },
        { "x-test-integer-type=10", "x-test-integer-type=9", 1 },
        { "x-test-integer-type=999", "x-test-integer-type=1000", -1 },
        { "x-test-integer-type=-1", "x-test-integer-type=0", -1 },
@@ -866,4 +707,34 @@
        + second + ">.");
  }
  /**
   * Tests the equals method with a null argument.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testEqualityNull() {
    RDN rdn = new RDN(AT_DC, AV_DC_ORG);
    assertFalse(rdn.equals(null));
}
  /**
   * Tests the equals method with a non-RDN argument.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test
  public void testEqualityNonRDN() {
    RDN rdn = new RDN(AT_DC, AV_DC_ORG);
    assertFalse(rdn.equals("this isn't an RDN"));
  }
}