From 56274dff0f872d02abb69608f6d98fa087c8e6f2 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Thu, 15 Feb 2007 00:21:49 +0000
Subject: [PATCH] Rewrite the DN and RDN code for significant performance improvements, especially in the area of DN parsing.

---
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java                                           |   21 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java                  |    4 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestDN.java                                  |  373 +--
 opendj-sdk/opends/src/server/org/opends/server/tools/makeldif/TemplateEntry.java                                          |   45 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestAddResponseProtocolOp.java      |    9 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyDNResponseProtocolOp.java |    7 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestRDN.java                                 |  495 +--
 opendj-sdk/opends/src/server/org/opends/server/types/DN.java                                                              | 3167 +++++++++++++++++++++++----
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java        |   11 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestCompareResponseProtocolOp.java  |    7 
 opendj-sdk/opends/src/server/org/opends/server/types/RDN.java                                                             | 2507 ++++++++-------------
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestDeleteResponseProtocolOp.java   |    7 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyResponseProtocolOp.java   |    7 
 opendj-sdk/opends/src/server/org/opends/server/types/AttributeValue.java                                                  |  117 +
 14 files changed, 4,182 insertions(+), 2,595 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
index 97388e7..b8a5a52 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
+++ b/opendj-sdk/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);
   }
 
   /**
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tools/makeldif/TemplateEntry.java b/opendj-sdk/opends/src/server/org/opends/server/tools/makeldif/TemplateEntry.java
index fa4c9be..18ebd2e 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tools/makeldif/TemplateEntry.java
+++ b/opendj-sdk/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());
+        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;
+          }
 
-        builder.append(type, value);
+          names[i]  = t.getPrimaryName();
+          values[i] = new AttributeValue(t, v.getValue().toString());
+        }
+
+        rdn = new RDN(rdnAttrs, names, values);
       }
 
-      dn = parentDN.concat(builder.getInstance());
+      dn = parentDN.concat(rdn);
     }
 
     return dn;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/AttributeValue.java b/opendj-sdk/opends/src/server/org/opends/server/types/AttributeValue.java
index 9853d0c..e526e99 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/AttributeValue.java
+++ b/opendj-sdk/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.
    *
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/DN.java b/opendj-sdk/opends/src/server/org/opends/server/types/DN.java
index 242e0cd..99fdedc 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/DN.java
+++ b/opendj-sdk/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";
+       "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,203 +106,97 @@
    * 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.
+   * @param  rdnComponents  The set of RDN components that make up
+   *                        this DN.
    */
-  private DN(RDN[] rdnComponents, int offset, int count) {
+  public DN(RDN[] rdnComponents)
+  {
     assert debugConstructor(CLASS_NAME,
-        String.valueOf(rdnComponents), String.valueOf(offset));
+                            String.valueOf(rdnComponents));
 
-    this.rdnComponents = rdnComponents;
-    this.offset = offset;
-    this.numComponents = count;
-    this.normalizedDN = normalize();
+    if (rdnComponents == null)
+    {
+      this.rdnComponents = new RDN[0];
+    }
+    else
+    {
+      this.rdnComponents = rdnComponents;
+    }
+
+    numComponents = this.rdnComponents.length;
+    dnString      = null;
+    normalizedDN  = toNormalizedString();
   }
 
 
 
   /**
-   * 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
-   * this DN.
+   * Creates a new DN with the provided set of RDNs, arranged with the
+   * suffix as the last element.
    *
-   * @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.
+   * @param  rdnComponents  The set of RDN components that make up
+   *                        this DN.
    */
-  public DN concat(DN localName) {
-    ensureNotNull(localName);
+  public DN(ArrayList<RDN> rdnComponents)
+  {
+    assert debugConstructor(CLASS_NAME,
+                            String.valueOf(rdnComponents));
 
-    if (localName.isNullDN()) {
-      return this;
+    if ((rdnComponents == null) || rdnComponents.isEmpty())
+    {
+      this.rdnComponents = new RDN[0];
+    }
+    else
+    {
+      this.rdnComponents = new RDN[rdnComponents.size()];
+      rdnComponents.toArray(this.rdnComponents);
     }
 
-    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);
+    numComponents = this.rdnComponents.length;
+    dnString      = null;
+    normalizedDN  = toNormalizedString();
   }
 
 
 
   /**
-   * 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.
+   * Retrieves a singleton instance of the null 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.
+   * @return  A singleton instance of the null DN.
    */
-  public DN concat(RDN... rdns) {
-    ensureNotNull(rdns);
+  public static DN nullDN()
+  {
+    assert debugEnter(CLASS_NAME, "nullDN");
 
-    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);
+    return NULL_DN;
   }
 
 
 
   /**
-   * Get the parent DN of this DN.
+   * Indicates whether this represents a null DN.  This could target
+   * the root DSE for the Directory Server, or the authorization DN
+   * for an anonymous or unauthenticated client.
    *
-   * @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).
+   * @return  <CODE>true</CODE> if this does represent a null DN, or
+   *          <CODE>false</CODE> if it does not.
    */
-  public DN getParent() {
-    if (numComponents <= 1) {
-      return null;
-    } else {
-      return new DN(rdnComponents, offset + 1, numComponents - 1);
-    }
+  public boolean isNullDN()
+  {
+    assert debugEnter(CLASS_NAME, "isNullDN");
+
+    return (numComponents == 0);
   }
 
 
 
   /**
-   * Create a local name (a relative DN) from this DN.
-   * <p>
-   * Examples: <blockquote>
+   * Retrieves the number of RDN components for this DN.
    *
-   * <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.
+   * @return  The number of RDN components for 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() {
+  public int getNumComponents()
+  {
     assert debugEnter(CLASS_NAME, "getNumComponents");
 
     return numComponents;
@@ -447,65 +206,89 @@
 
   /**
    * 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.
+   * 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.
+   * @return  The outermost RDN component for this DN, or
+   *          <CODE>null</CODE> if there are no RDN components in the
+   *          DN.
    */
-  public RDN getRDN() {
+  public RDN getRDN()
+  {
     assert debugEnter(CLASS_NAME, "getRDN");
 
-    if (numComponents == 0) {
+    if (numComponents == 0)
+    {
       return null;
-    } else {
-      return getRDN(0);
+    }
+    else
+    {
+      return rdnComponents[0];
     }
   }
 
 
 
   /**
-   * Get the RDN at the specified index.
+   * Retrieves the RDN component at the specified position in the set
+   * of components for this DN.
    *
-   * @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.
+   * @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 index) throws IndexOutOfBoundsException {
-    if (index < 0) {
-      throw new IndexOutOfBoundsException("index out of range: "
-          + index);
-    }
+  public RDN getRDN(int pos)
+  {
+    assert debugEnter(CLASS_NAME, "getRDN", String.valueOf(pos));
 
-    if (index >= numComponents) {
-      throw new IndexOutOfBoundsException("index out of range: "
-          + index);
-    }
-
-    return rdnComponents[offset + index];
+    return rdnComponents[pos];
   }
 
 
 
   /**
    * Retrieves the DN of the entry that is the immediate parent for
-   * this entry.
+   * 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 (either because there is only a
-   *         single RDN component or because this DN is a suffix
-   *         defined in the server).
+   * @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 getParentDNInSuffix() {
-    assert debugEnter(CLASS_NAME, "getParentDNInSuffix");
+  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))
@@ -513,23 +296,92 @@
       return null;
     }
 
-    return getParent();
+    RDN[] parentComponents = new RDN[numComponents-1];
+    System.arraycopy(rdnComponents, 1, parentComponents, 0,
+                     numComponents-1);
+    return new DN(parentComponents);
   }
 
 
 
   /**
-   * Indicates whether this represents a null DN. This could target
-   * the root DSE for the Directory Server, or the authorization DN
-   * for an anonymous or unauthenticated client.
+   * Creates a new DN that is a child of this DN, using the specified
+   * RDN.
    *
-   * @return <code>true</code> if this does represent a null DN, or
-   *         <code>false</code> if it does not.
+   * @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 boolean isNullDN() {
-    assert debugEnter(CLASS_NAME, "isNullDN");
+  public DN concat(RDN rdn)
+  {
+    assert debugEnter(CLASS_NAME, "concat", String.valueOf(rdn));
 
-    return (numComponents == 0);
+    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);
   }
 
 
@@ -539,24 +391,26 @@
    * (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 @@
 
 
   /**
-   * Indicates whether the provided object is equal to this DN. In
+   * 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;
     }
   }
@@ -625,12 +2767,13 @@
 
 
   /**
-   * Retrieves the hash code for this DN. The hash code will be the
+   * Retrieves the hash code for this DN.  The hash code will be the
    * sum of the hash codes for all the RDN components.
    *
-   * @return The hash code for this DN.
+   * @return  The hash code for this DN.
    */
-  public int hashCode() {
+  public int hashCode()
+  {
     assert debugEnter(CLASS_NAME, "hashCode");
 
     return normalizedDN.hashCode();
@@ -641,14 +2784,34 @@
   /**
    * Retrieves a string representation of this DN.
    *
-   * @return A string representation of this DN.
+   * @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");
+                      "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());
   }
 
 
@@ -681,11 +2836,33 @@
   /**
    * Retrieves a normalized string representation of this DN.
    *
-   * @return A normalized string representation of this DN.
+   * @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");
+                      "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.
-   * @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.
+   * @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 {
-        return 0;
-      }
-
-      index1--;
-      index2--;
+    if (equals(dn))
+    {
+      return 0;
     }
-  }
-
-
-
-  /**
-   * 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);
+    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;
+        }
       }
 
-      return buffer.toString();
+      return 0;
     }
   }
 }
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/RDN.java b/opendj-sdk/opends/src/server/org/opends/server/types/RDN.java
index 548f0cf..3bbff0b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/RDN.java
+++ b/opendj-sdk/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";
+       "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;
+  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.
+   * Creates a new RDN with the provided information.  The number of
+   * type, name, and value elements must be nonzero and equal.
+   *
+   * @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 final class Builder {
-    // The list of attribute types.
-    private List<AttributeType> attributeTypes;
+  public RDN(AttributeType[] attributeTypes, String[] attributeNames,
+             AttributeValue[] attributeValues)
+  {
+    assert debugConstructor(CLASS_NAME,
+                            String.valueOf(attributeTypes),
+                            String.valueOf(attributeNames),
+                            String.valueOf(attributeValues));
 
-    // The list of user-provided attribute names.
-    private List<String> attributeNames;
+    this.numValues       = attributeTypes.length;
+    this.attributeTypes  = attributeTypes;
+    this.attributeNames  = attributeNames;
+    this.attributeValues = attributeValues;
 
-    // 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.
-     *
-     * @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.
-     */
-    public void append(AttributeType type, AttributeValue value)
-        throws IllegalArgumentException {
-      append(type, type.getNameOrOID(), value);
-    }
-
-
-
-    /**
-     * 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);
   }
 
 
@@ -524,89 +230,14 @@
    * Retrieves the number of attribute-value pairs contained in this
    * RDN.
    *
-   * @return The number of attribute-value pairs contained in this
-   *         RDN.
+   * @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.
-   * @return The value for the requested attribute type, or
-   *         <code>null</code> if the specified attribute type is
-   *         not present in the RDN.
+   * @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.
    */
-  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.
-   * @return The decoded RDN.
-   * @throws DirectoryException
-   *           If a problem occurs while trying to decode the provided
-   *           string as a 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.
    */
   public static RDN decode(String rdnString)
-      throws DirectoryException {
+         throws DirectoryException
+  {
     assert debugEnter(CLASS_NAME, "decode",
-        String.valueOf(rdnString));
+                      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 msgID = MSGID_RDN_UNEXPECTED_COMMA;
+    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);
+                                   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);
+      }
+    }
   }
 
 
 
   /**
-   * 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).
+   * Creates a duplicate of this RDN that can be modified without
+   * impacting this RDN.
    *
-   * @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.
+   * @return  A duplicate of this RDN that can be modified without
+   *          impacting this RDN.
    */
-  public boolean equals(Object o) {
+  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 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
+   *          not.
+   */
+  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.
+   * Retrieves the hash code for this RDN.  It will be calculated as
+   * the sum of the hash codes of the types and values.
    *
-   * @return The hash code for this RDN.
+   * @return  The hash code for this RDN.
    */
-  public int hashCode() {
+  public int hashCode()
+  {
     assert debugEnter(CLASS_NAME, "hashCode");
 
     return toNormalizedString().hashCode();
@@ -808,14 +952,30 @@
   /**
    * Retrieves a string representation of this RDN.
    *
-   * @return A string representation of this RDN.
+   * @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();
 
-    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");
+                      "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());
   }
 
 
@@ -854,38 +1000,14 @@
   /**
    * Retrieves a normalized string representation of this RDN.
    *
-   * @return A normalized string representation of this RDN.
+   * @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");
+                      "java.lang.StringBuilder");
 
-    ensureNotNull(buffer);
-
-    buffer.append(toNormalizedString());
-  }
-
-
-
-  /**
-   * 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.
-   *
-   * @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.
-   */
-  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);
-    }
-
-    // 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;
-      }
-    }
-
-    // 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;
-    }
-  }
-
-
-
-  /**
-   * 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());
-
-      return name1.compareTo(name2);
-    }
-  }
-
-
-
-  /**
-   * 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('=');
-
-    try {
-      quoteAttributeValue(buffer, value.getNormalizedStringValue());
-    } catch (Exception e) {
-      assert debugException(CLASS_NAME, "toNormalizedString", e);
-      quoteAttributeValue(buffer, value.getStringValue());
-    }
-  }
-
-
-
-  /**
-   * 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) {
+    if (normalizedRDN != null)
+    {
+      buffer.append(normalizedRDN);
       return;
     }
 
-    // Assume 1-byte UTF8 and that no quoting will be required.
-    buffer.ensureCapacity(buffer.length() + length);
+    boolean bufferEmpty = (buffer.length() == 0);
 
-    // Quote leading space or #.
-    char c = value.charAt(0);
-    if (c == ' ' || c == '#') {
-      buffer.append('\\');
-      buffer.append(c);
-    } else {
-      quoteChar(buffer, c);
-    }
+    if (attributeNames.length == 1)
+    {
+      toLowerCase(attributeTypes[0].getNameOrOID(), buffer);
+      buffer.append('=');
 
-    // Process the remainder of the string.
-    for (int i = 1; i < (length - 1); i++) {
-      quoteChar(buffer, value.charAt(i));
-    }
-
-    // Quote trailing space.
-    if (length > 1) {
-      c = value.charAt(length - 1);
-      if (c == ' ') {
-        buffer.append('\\');
-        buffer.append(c);
-      } else {
-        quoteChar(buffer, c);
+      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();
     }
   }
 
 
 
   /**
-   * Encode a single attribute value from an RDN according to the DN
-   * string encoding rules.
+   * Compares this RDN with the provided RDN based on an alphabetic
+   * comparison of the attribute names and values.
    *
-   * @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  rdn  The RDN against which to compare this RDN.
    *
-   * @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.
+   * @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.
    */
-  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();
+  public int compareTo(RDN rdn)
+  {
+    assert debugEnter(CLASS_NAME, "compareTo", String.valueOf(rdn));
 
-    int length = dnString.length();
+    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);
 
-    // 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);
+            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);
 
-    // 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;
+            return omr.compareValues(
+                        attributeValues[0].getValue(),
+                        rdn.attributeValues[0].getValue());
           }
         }
       }
-
-      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;
-        }
+      else
+      {
+        String name1 = toLowerCase(attributeTypes[0].getNameOrOID());
+        String name2 =
+             toLowerCase(rdn.attributeTypes[0].getNameOrOID());
+        return name1.compareTo(name2);
       }
-
-      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;
+    if (equals(rdn))
+    {
+      return 0;
     }
 
-    // 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);
-      }
+    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]);
+    }
 
-      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);
+    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]);
+    }
+
+    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);
 
-      // 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);
+            valueComparison =
+                 omr.compareValues(value1.getValue(),
+                                   value2.getValue());
+          }
+        }
 
-          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);
+        if (valueComparison == 0)
+        {
+          if (! iterator1.hasNext())
+          {
+            if (iterator2.hasNext())
+            {
+              return -1;
             }
-          } 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
+            {
+              return 0;
             }
-          } 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;
+          if (! iterator2.hasNext())
+          {
+            return 1;
           }
+
+          name1  = iterator1.next();
+          name2  = iterator2.next();
+          type1  = typeMap1.get(name1);
+          type2  = typeMap2.get(name2);
+          value1 = valueMap1.get(name1);
+          value2 = valueMap2.get(name2);
+        }
+        else
+        {
+          return valueComparison;
         }
       }
-
-      attributeValue.setValue(valueString.toString());
-      return pos;
+      else
+      {
+        return name1.compareTo(name2);
+      }
     }
   }
-
-
-
-  /**
-   * 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);
-    }
-  }
-
 }
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java
index cf882a8..8549eb3 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java
+++ b/opendj-sdk/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;
 
@@ -1278,4 +1278,4 @@
     assertTrue(debugString.contains("NOT-INDEXED"));
 
   }
-}
\ No newline at end of file
+}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java
index e4f0458..d2ca0fb 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java
+++ b/opendj-sdk/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;
 
@@ -140,10 +140,11 @@
          throws Exception
   {
     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();
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestAddResponseProtocolOp.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestAddResponseProtocolOp.java
index e66a856..77403da 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestAddResponseProtocolOp.java
+++ b/opendj-sdk/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);
-    
-    dn = DN.create(rdn);
+    RDN[] rdns = new RDN[1];
+    rdns[0] = RDN.create(attribute, attributeValue);
+
+    dn = new DN(rdns);
   }
 
   /**
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestCompareResponseProtocolOp.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestCompareResponseProtocolOp.java
index 79a1d3e..00bbc83 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestCompareResponseProtocolOp.java
+++ b/opendj-sdk/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);
   }
 
   /**
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestDeleteResponseProtocolOp.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestDeleteResponseProtocolOp.java
index 7988f13..d02563d 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestDeleteResponseProtocolOp.java
+++ b/opendj-sdk/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);
   }
 
   /**
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyDNResponseProtocolOp.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyDNResponseProtocolOp.java
index dfed6c8..377f716 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyDNResponseProtocolOp.java
+++ b/opendj-sdk/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);
   }
 
   /**
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyResponseProtocolOp.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyResponseProtocolOp.java
index e5e67cf..2c72c33 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyResponseProtocolOp.java
+++ b/opendj-sdk/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);
   }
 
   /**
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestDN.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestDN.java
index 34bca38..c05b75e 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestDN.java
+++ b/opendj-sdk/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);
   }
-
 }
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestRDN.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestRDN.java
index eca7aa1..2df2dc9 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestRDN.java
+++ b/opendj-sdk/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"));
+  }
 }
+

--
Gitblit v1.10.0