From 59685c84348f8d71dc3bbd3513c5eb10cc738a8e Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Fri, 03 Nov 2006 16:39:22 +0000
Subject: [PATCH] Refactor DN and RDN classes and improve their test coverage.

---
 opends/src/server/org/opends/server/types/AttributeValue.java                                                        |  116 
 opends/src/server/org/opends/server/core/EntryCacheConfigManager.java                                                |    6 
 opends/src/server/org/opends/server/tools/LDIFSearch.java                                                            |    2 
 opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandlerTestCase.java    |    6 
 opends/src/server/org/opends/server/types/SearchFilter.java                                                          |   16 
 opends/src/server/org/opends/server/messages/CoreMessages.java                                                       |   13 
 opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CRAMMD5SASLMechanismHandlerTestCase.java      |   14 
 opends/src/server/org/opends/server/core/KeyManagerProviderConfigManager.java                                        |    8 
 opends/src/server/org/opends/server/protocols/jmx/JmxConnectionHandler.java                                          |    4 
 opends/src/server/org/opends/server/synchronization/MultimasterSynchronization.java                                  |    2 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java                          |   72 
 opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyDNChangeRecordEntry.java                  |   21 
 opends/src/server/org/opends/server/core/RelativeSubtreeSpecification.java                                           |    2 
 opends/src/server/org/opends/server/tools/makeldif/UnderscoreParentDNTag.java                                        |    8 
 opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/JebTestCase.java                            |    4 
 opends/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java              |    8 
 opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java |    8 
 opends/src/server/org/opends/server/core/AddOperation.java                                                           |   37 
 opends/src/server/org/opends/server/types/DN.java                                                                    | 3162 +++-----------------
 opends/src/server/org/opends/server/core/ModifyOperation.java                                                        |    4 
 opends/src/server/org/opends/server/backends/RootDSEBackend.java                                                     |    3 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java                          |   34 
 opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyChangeRecordEntry.java                    |   13 
 opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestStaticUtils.java                                |   59 
 opends/src/server/org/opends/server/tools/LDIFDiff.java                                                              |   10 
 opends/src/server/org/opends/server/tools/makeldif/TemplateEntry.java                                                |   31 
 opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestDeleteChangeRecordEntry.java                    |    8 
 opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestAddChangeRecordEntry.java                       |   12 
 opends/src/server/org/opends/server/backends/MemoryBackend.java                                                      |    6 
 opends/src/server/org/opends/server/backends/MonitorBackend.java                                                     |   14 
 opends/src/server/org/opends/server/backends/jeb/EntryContainer.java                                                 |   34 
 opends/src/server/org/opends/server/schema/RFC3672SubtreeSpecificationSyntax.java                                    |    2 
 opends/src/server/org/opends/server/schema/RelativeSubtreeSpecificationSyntax.java                                   |    2 
 opends/src/server/org/opends/server/util/LDIFReader.java                                                             |    2 
 opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestDeleteResponseProtocolOp.java         |    9 
 opends/src/server/org/opends/server/backends/task/TaskBackend.java                                                   |   10 
 opends/src/server/org/opends/server/schema/OctetStringOrderingMatchingRule.java                                      |   35 
 opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestCompareResponseProtocolOp.java        |    9 
 opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestRDN.java                                       |  869 +++++
 opends/src/server/org/opends/server/types/RDN.java                                                                   | 2539 +++++++++------
 opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalSearchOperationTestCase.java  |    4 
 opends/src/server/org/opends/server/backends/task/TaskScheduler.java                                                 |    2 
 opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestAddResponseProtocolOp.java            |   10 
 opends/src/server/org/opends/server/types/LDAPURL.java                                                               |    2 
 opends/src/server/org/opends/server/tools/makeldif/DNTag.java                                                        |   20 
 opends/src/server/org/opends/server/core/Operation.java                                                              |    3 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/DeleteOperationTestCase.java                        |    4 
 opends/src/server/org/opends/server/backends/jeb/VerifyJob.java                                                      |    6 
 opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyResponseProtocolOp.java         |    9 
 opends/src/server/org/opends/server/backends/SchemaBackend.java                                                      |   15 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestRFC3672SubtreeSpecification.java                |    2 
 opends/src/server/org/opends/server/core/SimpleSubtreeSpecification.java                                             |   10 
 opends/src/server/org/opends/server/util/StaticUtils.java                                                            |  103 
 opends/src/server/org/opends/server/core/CertificateMapperConfigManager.java                                         |    7 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestRelativeSubtreeSpecification.java               |    2 
 opends/src/server/org/opends/server/backends/BackupBackend.java                                                      |   30 
 opends/src/server/org/opends/server/types/Entry.java                                                                 |   24 
 opends/src/server/org/opends/server/core/PersistentSearch.java                                                       |   10 
 opends/src/server/org/opends/server/backends/jeb/ImportJob.java                                                      |    4 
 opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestDN.java                                        | 1383 +++++++-
 opends/src/server/org/opends/server/core/TrustManagerProviderConfigManager.java                                      |    8 
 opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AnonymousSASLMechanismHandlerTestCase.java    |    6 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java                        |   24 
 opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java                                               |    4 
 opends/src/server/org/opends/server/core/DirectoryServer.java                                                        |   33 
 opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java                                  |    4 
 opends/src/server/org/opends/server/tools/LDIFModify.java                                                            |   10 
 opends/src/server/org/opends/server/backends/jeb/RootContainer.java                                                  |    2 
 /dev/null                                                                                                            |  132 
 opends/src/server/org/opends/server/core/CompareOperation.java                                                       |    4 
 opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyDNResponseProtocolOp.java       |    9 
 opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestEntry.java                                     |    2 
 opends/src/server/org/opends/server/core/ModifyDNOperation.java                                                      |   27 
 opends/src/server/org/opends/server/synchronization/SynchronizationDomain.java                                       |   10 
 opends/src/server/org/opends/server/core/RFC3672SubtreeSpecification.java                                            |    2 
 opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestChangeRecordEntry.java                          |    7 
 opends/src/server/org/opends/server/core/DeleteOperation.java                                                        |    4 
 opends/src/server/org/opends/server/tools/makeldif/UnderscoreDNTag.java                                              |   29 
 opends/src/server/org/opends/server/backends/jeb/DN2URI.java                                                         |    6 
 79 files changed, 4,607 insertions(+), 4,580 deletions(-)

diff --git a/opends/src/server/org/opends/server/backends/BackupBackend.java b/opends/src/server/org/opends/server/backends/BackupBackend.java
index 46b7195..fda38ea 100644
--- a/opends/src/server/org/opends/server/backends/BackupBackend.java
+++ b/opends/src/server/org/opends/server/backends/BackupBackend.java
@@ -245,19 +245,20 @@
     LinkedHashMap<AttributeType,List<Attribute>> userAttrs =
          new LinkedHashMap<AttributeType,List<Attribute>>(1);
 
-    AttributeType[]  attrTypes  = backupBaseDN.getRDN().getAttributeTypes();
-    AttributeValue[] attrValues = backupBaseDN.getRDN().getAttributeValues();
-    for (int i=0; i < attrTypes.length; i++)
+    RDN rdn = backupBaseDN.getRDN();
+    int numAVAs = rdn.getNumValues();
+    for (int i=0; i < numAVAs; i++)
     {
       LinkedHashSet<AttributeValue> valueSet =
            new LinkedHashSet<AttributeValue>(1);
-      valueSet.add(attrValues[i]);
+      valueSet.add(rdn.getAttributeValue(i));
 
+      AttributeType attrType = rdn.getAttributeType(i);
       ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-      attrList.add(new Attribute(attrTypes[i], attrTypes[i].getNameOrOID(),
+      attrList.add(new Attribute(attrType, attrType.getNameOrOID(),
                                  valueSet));
 
-      userAttrs.put(attrTypes[i], attrList);
+      userAttrs.put(attrType, attrList);
     }
 
     backupBaseEntry = new Entry(backupBaseDN, objectClasses, userAttrs,
@@ -370,7 +371,7 @@
     // If so, then it must point to a backup directory.  Otherwise, it must be
     // two levels below the backup base entry and must point to a specific
     // backup.
-    DN parentDN = entryDN.getParent();
+    DN parentDN = entryDN.getParentDNInSuffix();
     if (parentDN == null)
     {
       int    msgID   = MSGID_BACKUP_INVALID_BASE;
@@ -381,7 +382,7 @@
     {
       return getBackupDirectoryEntry(entryDN);
     }
-    else if (backupBaseDN.equals(parentDN.getParent()))
+    else if (backupBaseDN.equals(parentDN.getParentDNInSuffix()))
     {
       return getBackupEntry(entryDN);
     }
@@ -528,7 +529,7 @@
 
 
     // Next, get the backup directory from the parent DN.
-    DN parentDN = entryDN.getParent();
+    DN parentDN = entryDN.getParentDNInSuffix();
     if (parentDN == null)
     {
       int    msgID   = MSGID_BACKUP_NO_BACKUP_PARENT_DN;
@@ -929,7 +930,7 @@
         }
       }
     }
-    else if (backupBaseDN.equals(parentDN = baseDN.getParent()))
+    else if (backupBaseDN.equals(parentDN = baseDN.getParentDNInSuffix()))
     {
       Entry backupDirEntry = getBackupDirectoryEntry(baseDN);
 
@@ -983,7 +984,8 @@
     }
     else
     {
-      if ((parentDN == null) || (! backupBaseDN.equals(parentDN.getParent())))
+      if ((parentDN == null)
+          || (! backupBaseDN.equals(parentDN.getParentDNInSuffix())))
       {
         int    msgID   = MSGID_BACKUP_NO_SUCH_ENTRY;
         String message = getMessage(msgID);
@@ -1475,13 +1477,9 @@
   public static DN makeChildDN(DN parentDN, AttributeType rdnAttrType,
                                String rdnStringValue)
   {
-    RDN[] baseComponents = parentDN.getRDNComponents();
-    RDN[] components = new RDN[baseComponents.length+1];
     AttributeValue attrValue =
          new AttributeValue(rdnAttrType, rdnStringValue);
-    components[0] = new RDN(rdnAttrType, attrValue);
-    System.arraycopy(baseComponents, 0, components, 1, baseComponents.length);
-    return new DN(components);
+    return parentDN.concat(RDN.create(rdnAttrType, attrValue));
   }
 }
 
diff --git a/opends/src/server/org/opends/server/backends/MemoryBackend.java b/opends/src/server/org/opends/server/backends/MemoryBackend.java
index 8bc12d4..5b5f8e8 100644
--- a/opends/src/server/org/opends/server/backends/MemoryBackend.java
+++ b/opends/src/server/org/opends/server/backends/MemoryBackend.java
@@ -292,7 +292,7 @@
 
 
     // Get the parent DN and ensure that it exists in the backend.
-    DN parentDN = entryDN.getParent();
+    DN parentDN = entryDN.getParentDNInSuffix();
     if (parentDN == null)
     {
       int    msgID   = MSGID_MEMORYBACKEND_ENTRY_DOESNT_BELONG;
@@ -393,7 +393,7 @@
     childDNs.remove(entryDN);
     entryMap.remove(entryDN);
 
-    DN parentDN = entryDN.getParent();
+    DN parentDN = entryDN.getParentDNInSuffix();
     if (parentDN != null)
     {
       HashSet<DN> parentsChildren = childDNs.get(parentDN);
@@ -505,7 +505,7 @@
 
 
     // Make sure that the parent of the new entry exists.
-    DN parentDN = entry.getDN().getParent();
+    DN parentDN = entry.getDN().getParentDNInSuffix();
     if ((parentDN == null) || (! entryMap.containsKey(parentDN)))
     {
       int    msgID   = MSGID_MEMORYBACKEND_RENAME_PARENT_DOESNT_EXIST;
diff --git a/opends/src/server/org/opends/server/backends/MonitorBackend.java b/opends/src/server/org/opends/server/backends/MonitorBackend.java
index 9f84e20..206a778 100644
--- a/opends/src/server/org/opends/server/backends/MonitorBackend.java
+++ b/opends/src/server/org/opends/server/backends/MonitorBackend.java
@@ -371,7 +371,7 @@
 
     // See if the monitor base entry is the immediate parent for the requested
     // entry.  If not, then throw an exception.
-    DN parentDN = entryDN.getParent();
+    DN parentDN = entryDN.getParentDNInSuffix();
     if ((parentDN == null) || (! parentDN.equals(baseMonitorDN)))
     {
       if (baseMonitorDN.isAncestorOf(entryDN))
@@ -405,7 +405,7 @@
 
     // Get the RDN value and see if it matches the instance name for one of
     // the directory server monitor providers.
-    String rdnValue = entryRDN.getAttributeValues()[0].getStringValue();
+    String rdnValue = entryRDN.getAttributeValue(0).getStringValue();
     MonitorProvider monitorProvider =
          DirectoryServer.getMonitorProvider(rdnValue.toLowerCase());
     if (monitorProvider == null)
@@ -448,7 +448,7 @@
       return true;
     }
 
-    DN parentDN = entryDN.getParent();
+    DN parentDN = entryDN.getParentDNInSuffix();
     if ((parentDN == null) || (! parentDN.equals(baseMonitorDN)))
     {
       return false;
@@ -460,7 +460,7 @@
       return false;
     }
 
-    String rdnValue = rdn.getAttributeValues()[0].getStringValue();
+    String rdnValue = rdn.getAttributeValue(0).getStringValue();
     MonitorProvider monitorProvider =
          DirectoryServer.getMonitorProvider(toLowerCase(rdnValue));
     return (monitorProvider != null);
@@ -654,14 +654,14 @@
 
     // Make sure to include the RDN attribute.
     RDN            entryRDN = entryDN.getRDN();
-    AttributeType  rdnType  = entryRDN.getAttributeTypes()[0];
-    AttributeValue rdnValue = entryRDN.getAttributeValues()[0];
+    AttributeType  rdnType  = entryRDN.getAttributeType(0);
+    AttributeValue rdnValue = entryRDN.getAttributeValue(0);
 
     LinkedHashSet<AttributeValue> rdnValues =
          new LinkedHashSet<AttributeValue>(1);
     rdnValues.add(rdnValue);
 
-    Attribute rdnAttr = new Attribute(rdnType, entryRDN.getAttributeNames()[0],
+    Attribute rdnAttr = new Attribute(rdnType, entryRDN.getAttributeName(0),
                                       rdnValues);
     ArrayList<Attribute> rdnList = new ArrayList<Attribute>(1);
     rdnList.add(rdnAttr);
diff --git a/opends/src/server/org/opends/server/backends/RootDSEBackend.java b/opends/src/server/org/opends/server/backends/RootDSEBackend.java
index ee96da1..5611e60 100644
--- a/opends/src/server/org/opends/server/backends/RootDSEBackend.java
+++ b/opends/src/server/org/opends/server/backends/RootDSEBackend.java
@@ -72,7 +72,6 @@
 import org.opends.server.types.LDIFExportConfig;
 import org.opends.server.types.LDIFImportConfig;
 import org.opends.server.types.ObjectClass;
-import org.opends.server.types.RDN;
 import org.opends.server.types.RestoreConfig;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
@@ -230,7 +229,7 @@
 
     // Create the set of base DNs that we will handle.  In this case, it's just
     // the root DSE.
-    rootDSEDN    = new DN(new ArrayList<RDN>(0));
+    rootDSEDN    = DN.nullDN();
     this.baseDNs = new DN[] { rootDSEDN };
 
 
diff --git a/opends/src/server/org/opends/server/backends/SchemaBackend.java b/opends/src/server/org/opends/server/backends/SchemaBackend.java
index f70eda6..9e0bc7b 100644
--- a/opends/src/server/org/opends/server/backends/SchemaBackend.java
+++ b/opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -399,19 +399,16 @@
     RDN rdn = entryDN.getRDN();
     if (rdn != null)
     {
-      String[]         rdnNames  = rdn.getAttributeNames();
-      AttributeType[]  rdnTypes  = rdn.getAttributeTypes();
-      AttributeValue[] rdnValues = rdn.getAttributeValues();
-
-      int numRDNs = rdnTypes.length;
-      for (int i=0; i < numRDNs; i++)
+      int numAVAs = rdn.getNumValues();
+      for (int i=0; i < numAVAs; i++)
       {
         LinkedHashSet<AttributeValue> valueSet =
              new LinkedHashSet<AttributeValue>(1);
-        valueSet.add(rdnValues[i]);
+        valueSet.add(rdn.getAttributeValue(i));
 
-        AttributeType attrType = rdnTypes[i];
-        Attribute a = new Attribute(attrType, rdnNames[i], valueSet);
+        AttributeType attrType = rdn.getAttributeType(i);
+        String attrName = rdn.getAttributeName(i);
+        Attribute a = new Attribute(attrType, attrName, valueSet);
         ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
         attrList.add(a);
 
diff --git a/opends/src/server/org/opends/server/backends/jeb/DN2URI.java b/opends/src/server/org/opends/server/backends/jeb/DN2URI.java
index 7cb76a9..d349e1e 100644
--- a/opends/src/server/org/opends/server/backends/jeb/DN2URI.java
+++ b/opends/src/server/org/opends/server/backends/jeb/DN2URI.java
@@ -445,7 +445,7 @@
           {
             urlBaseDN =
                  EntryContainer.modDN(targetDN,
-                                      referralDN.getRDNComponents().length,
+                                      referralDN.getNumComponents(),
                                       ldapurl.getBaseDN());
           }
           ldapurl.setBaseDN(urlBaseDN);
@@ -616,8 +616,8 @@
           // Make sure the referral is within scope.
           if (searchOp.getScope() == SearchScope.SINGLE_LEVEL)
           {
-            if ((dn.getRDNComponents().length !=
-                 baseDN.getRDNComponents().length + 1))
+            if ((dn.getNumComponents() !=
+                 baseDN.getNumComponents() + 1))
             {
               continue;
             }
diff --git a/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java b/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
index 006f7c7..9363442 100644
--- a/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
+++ b/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -53,7 +53,6 @@
 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;
@@ -903,8 +902,8 @@
           if (searchScope == SearchScope.SINGLE_LEVEL)
           {
             // Check if this entry is an immediate child.
-            if ((dn.getRDNComponents().length !=
-                 baseDN.getRDNComponents().length + 1))
+            if ((dn.getNumComponents() !=
+                 baseDN.getNumComponents() + 1))
             {
               isInScope = false;
             }
@@ -1131,8 +1130,8 @@
             else if (searchScope == SearchScope.SINGLE_LEVEL)
             {
               // Check if this entry is an immediate child.
-              if ((entryDN.getRDNComponents().length ==
-                   baseDN.getRDNComponents().length + 1) &&
+              if ((entryDN.getNumComponents() ==
+                   baseDN.getNumComponents() + 1) &&
                    entryDN.isDescendantOf(baseDN))
               {
                 isInScope = true;
@@ -1147,8 +1146,8 @@
             }
             else if (searchScope == SearchScope.SUBORDINATE_SUBTREE)
             {
-              if ((entryDN.getRDNComponents().length >
-                   baseDN.getRDNComponents().length) &&
+              if ((entryDN.getNumComponents() >
+                   baseDN.getNumComponents()) &&
                    entryDN.isDescendantOf(baseDN))
               {
                 isInScope = true;
@@ -2702,7 +2701,7 @@
 
           // Construct the new DN of the entry.
           DN newDN = modDN(oldEntry.getDN(),
-                           oldApexDN.getRDNComponents().length,
+                           oldApexDN.getNumComponents(),
                            newApexEntry.getDN());
 
           if (requestedNewSuperiorDN != null)
@@ -3078,23 +3077,8 @@
    */
   public static DN modDN(DN oldDN, int oldSuffixLen, DN newSuffixDN)
   {
-    RDN[] oldRDNs = oldDN.getRDNComponents();
-    RDN[] suffixRDNs = newSuffixDN.getRDNComponents();
-
-    int prefixLen = oldRDNs.length - oldSuffixLen;
-    RDN[] newRDNs = new RDN[prefixLen + suffixRDNs.length];
-
-    // Copy the unchanged prefix.
-    System.arraycopy(oldRDNs, 0,
-                     newRDNs, 0,
-                     prefixLen);
-
-    // Copy the new suffix.
-    System.arraycopy(suffixRDNs, 0,
-                     newRDNs, prefixLen,
-                     suffixRDNs.length);
-
-    return new DN(newRDNs);
+    DN localName = oldDN.getLocalName(oldSuffixLen);
+    return newSuffixDN.concat(localName);
   }
 
   /**
diff --git a/opends/src/server/org/opends/server/backends/jeb/ImportJob.java b/opends/src/server/org/opends/server/backends/jeb/ImportJob.java
index 5508dcd..ae9fc55 100644
--- a/opends/src/server/org/opends/server/backends/jeb/ImportJob.java
+++ b/opends/src/server/org/opends/server/backends/jeb/ImportJob.java
@@ -705,7 +705,7 @@
         }
         else
         {
-          IDs = new ArrayList<EntryID>(entryDN.getRDNComponents().length);
+          IDs = new ArrayList<EntryID>(entryDN.getNumComponents());
           IDs.add(entryID);
           if (parentID != null)
           {
@@ -838,7 +838,7 @@
       importContext = importMap.get(nodeDN);
       if (importContext == null)
       {
-        nodeDN = nodeDN.getParent();
+        nodeDN = nodeDN.getParentDNInSuffix();
       }
     }
 
diff --git a/opends/src/server/org/opends/server/backends/jeb/RootContainer.java b/opends/src/server/org/opends/server/backends/jeb/RootContainer.java
index 0f76e75..efeb9d3 100644
--- a/opends/src/server/org/opends/server/backends/jeb/RootContainer.java
+++ b/opends/src/server/org/opends/server/backends/jeb/RootContainer.java
@@ -524,7 +524,7 @@
       ec = entryContainers.get(nodeDN);
       if (ec == null)
       {
-        nodeDN = nodeDN.getParent();
+        nodeDN = nodeDN.getParentDNInSuffix();
       }
     }
 
diff --git a/opends/src/server/org/opends/server/backends/jeb/VerifyJob.java b/opends/src/server/org/opends/server/backends/jeb/VerifyJob.java
index ec35008..6a2ff16 100644
--- a/opends/src/server/org/opends/server/backends/jeb/VerifyJob.java
+++ b/opends/src/server/org/opends/server/backends/jeb/VerifyJob.java
@@ -734,8 +734,8 @@
             }
 
             if (!childEntry.getDN().isDescendantOf(entry.getDN()) ||
-                 childEntry.getDN().getRDNComponents().length !=
-                 entry.getDN().getRDNComponents().length + 1)
+                 childEntry.getDN().getNumComponents() !=
+                 entry.getDN().getNumComponents() + 1)
             {
               errorCount++;
               System.err.printf("File id2children has ID %d with DN <%s> " +
@@ -1512,7 +1512,7 @@
     {
       return null;
     }
-    return dn.getParent();
+    return dn.getParentDNInSuffix();
   }
 
   /**
diff --git a/opends/src/server/org/opends/server/backends/task/TaskBackend.java b/opends/src/server/org/opends/server/backends/task/TaskBackend.java
index 79e4181..8e4783d 100644
--- a/opends/src/server/org/opends/server/backends/task/TaskBackend.java
+++ b/opends/src/server/org/opends/server/backends/task/TaskBackend.java
@@ -453,7 +453,7 @@
       return taskScheduler.getRecurringTaskParentEntry();
     }
 
-    DN parentDN = entryDN.getParent();
+    DN parentDN = entryDN.getParentDNInSuffix();
     if (parentDN == null)
     {
       return null;
@@ -509,7 +509,7 @@
       return true;
     }
 
-    DN parentDN = entryDN.getParent();
+    DN parentDN = entryDN.getParentDNInSuffix();
     if (parentDN == null)
     {
       return false;
@@ -553,7 +553,7 @@
 
     // Get the DN for the entry and then get its parent.
     DN entryDN = entry.getDN();
-    DN parentDN = entryDN.getParent();
+    DN parentDN = entryDN.getParentDNInSuffix();
 
     if (parentDN == null)
     {
@@ -618,7 +618,7 @@
 
     // Get the parent for the provided entry DN.  It must be either the
     // scheduled or recurring task parent DN.
-    DN parentDN = entryDN.getParent();
+    DN parentDN = entryDN.getParentDNInSuffix();
     if (parentDN == null)
     {
       int    msgID   = MSGID_TASKBE_DELETE_INVALID_ENTRY;
@@ -841,7 +841,7 @@
     }
     else
     {
-      DN parentDN = baseDN.getParent();
+      DN parentDN = baseDN.getParentDNInSuffix();
       if (parentDN == null)
       {
         int    msgID   = MSGID_TASKBE_SEARCH_INVALID_BASE;
diff --git a/opends/src/server/org/opends/server/backends/task/TaskScheduler.java b/opends/src/server/org/opends/server/backends/task/TaskScheduler.java
index edd9a3a..13a0de4 100644
--- a/opends/src/server/org/opends/server/backends/task/TaskScheduler.java
+++ b/opends/src/server/org/opends/server/backends/task/TaskScheduler.java
@@ -1009,7 +1009,7 @@
         }
         else
         {
-          DN parentDN = entryDN.getParent();
+          DN parentDN = entryDN.getParentDNInSuffix();
           if (parentDN == null)
           {
             int    msgID   = MSGID_TASKSCHED_ENTRY_HAS_NO_PARENT;
diff --git a/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java b/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java
index 1814727..02b2912 100644
--- a/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java
+++ b/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java
@@ -266,7 +266,7 @@
     // Check for a zero-length value, which would be for an anonymous user.
     if (authorizationID.value().length == 0)
     {
-      return new DN();
+      return DN.nullDN();
     }
 
 
@@ -346,7 +346,7 @@
       // If the authorization ID is just "u:", then it's an anonymous request.
       if (lowerAuthzID.length() == 2)
       {
-        return new DN();
+        return DN.nullDN();
       }
 
 
diff --git a/opends/src/server/org/opends/server/core/AddOperation.java b/opends/src/server/org/opends/server/core/AddOperation.java
index f07fdc6..8ca91ba 100644
--- a/opends/src/server/org/opends/server/core/AddOperation.java
+++ b/opends/src/server/org/opends/server/core/AddOperation.java
@@ -1014,11 +1014,11 @@
       Lock parentLock = null;
       Lock entryLock  = null;
 
-      DN parentDN = entryDN.getParent();
+      DN parentDN = entryDN.getParentDNInSuffix();
       if (parentDN == null)
       {
         // Either this entry is a suffix or doesn't belong in the directory.
-        if (entryDN.isSuffix())
+        if (DirectoryServer.isSuffix(entryDN))
         {
           // This is fine.  This entry is one of the configured suffixes.
           parentLock = null;
@@ -1166,7 +1166,7 @@
 
             if (parentEntry == null)
             {
-              DN matchedDN = parentDN.getParent();
+              DN matchedDN = parentDN.getParentDNInSuffix();
               while (matchedDN != null)
               {
                 try
@@ -1183,7 +1183,7 @@
                   break;
                 }
 
-                matchedDN = matchedDN.getParent();
+                matchedDN = matchedDN.getParentDNInSuffix();
               }
 
 
@@ -1211,15 +1211,12 @@
         // Check to make sure that all of the RDN attributes are included as
         // attribute values.  If not, then either add them or report an error.
         RDN rdn = entryDN.getRDN();
-        AttributeType[]  rdnTypes  = rdn.getAttributeTypes();
-        AttributeValue[] rdnValues = rdn.getAttributeValues();
-        String[]         rdnNames  = rdn.getAttributeNames();
-
-        for (int i=0; i < rdnTypes.length; i++)
+        int numAVAs = rdn.getNumValues();
+        for (int i=0; i < numAVAs; i++)
         {
-          AttributeType  t = rdnTypes[i];
-          AttributeValue v = rdnValues[i];
-
+          AttributeType  t = rdn.getAttributeType(i);
+          AttributeValue v = rdn.getAttributeValue(i);
+          String         n = rdn.getAttributeName(i);
           if (t.isOperational())
           {
             List<Attribute> attrList = operationalAttributes.get(t);
@@ -1233,7 +1230,7 @@
                 valueList.add(v);
 
                 attrList = new ArrayList<Attribute>();
-                attrList.add(new Attribute(t, rdnNames[i], valueList));
+                attrList.add(new Attribute(t, n, valueList));
 
                 operationalAttributes.put(t, attrList);
               }
@@ -1243,7 +1240,7 @@
 
                 int msgID = MSGID_ADD_MISSING_RDN_ATTRIBUTE;
                 appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              rdnNames[i]));
+                                              n));
 
                 break addProcessing;
               }
@@ -1277,7 +1274,7 @@
                   LinkedHashSet<AttributeValue> valueList =
                        new LinkedHashSet<AttributeValue>(1);
                   valueList.add(v);
-                  attrList.add(new Attribute(t, rdnNames[i], valueList));
+                  attrList.add(new Attribute(t, n, valueList));
                 }
                 else
                 {
@@ -1285,7 +1282,7 @@
 
                   int msgID = MSGID_ADD_MISSING_RDN_ATTRIBUTE;
                   appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                                rdnNames[i]));
+                                                n));
 
                   break addProcessing;
                 }
@@ -1305,7 +1302,7 @@
                 valueList.add(v);
 
                 attrList = new ArrayList<Attribute>();
-                attrList.add(new Attribute(t, rdnNames[i], valueList));
+                attrList.add(new Attribute(t, n, valueList));
 
                 userAttributes.put(t, attrList);
               }
@@ -1315,7 +1312,7 @@
 
                 int msgID = MSGID_ADD_MISSING_RDN_ATTRIBUTE;
                 appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              rdnNames[i]));
+                                              n));
 
                 break addProcessing;
               }
@@ -1349,7 +1346,7 @@
                   LinkedHashSet<AttributeValue> valueList =
                        new LinkedHashSet<AttributeValue>(1);
                   valueList.add(v);
-                  attrList.add(new Attribute(t, rdnNames[i], valueList));
+                  attrList.add(new Attribute(t, n, valueList));
                 }
                 else
                 {
@@ -1357,7 +1354,7 @@
 
                   int msgID = MSGID_ADD_MISSING_RDN_ATTRIBUTE;
                   appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                                rdnNames[i]));
+                                                n));
 
                   break addProcessing;
                 }
diff --git a/opends/src/server/org/opends/server/core/CertificateMapperConfigManager.java b/opends/src/server/org/opends/server/core/CertificateMapperConfigManager.java
index 1ffe6f6..30957dd 100644
--- a/opends/src/server/org/opends/server/core/CertificateMapperConfigManager.java
+++ b/opends/src/server/org/opends/server/core/CertificateMapperConfigManager.java
@@ -146,7 +146,8 @@
       try
       {
         ConfigEntry parentEntry =
-             DirectoryServer.getConfigEntry(configEntryDN.getParent());
+             DirectoryServer
+            .getConfigEntry(configEntryDN.getParentDNInSuffix());
         if (parentEntry != null)
         {
           parentEntry.registerAddListener(this);
@@ -172,7 +173,7 @@
     configEntry.registerChangeListener(this);
     try
     {
-      DN parentDN = configEntryDN.getParent();
+      DN parentDN = configEntryDN.getParentDNInSuffix();
       ConfigEntry parentEntry = DirectoryServer.getConfigEntry(parentDN);
       if (parentEntry != null)
       {
@@ -860,7 +861,7 @@
     configEntry.registerChangeListener(this);
     try
     {
-      DN parentDN = configEntry.getDN().getParent();
+      DN parentDN = configEntry.getDN().getParentDNInSuffix();
       ConfigEntry parentEntry = DirectoryServer.getConfigEntry(parentDN);
       if (parentEntry != null)
       {
diff --git a/opends/src/server/org/opends/server/core/CompareOperation.java b/opends/src/server/org/opends/server/core/CompareOperation.java
index ab312d8..3ad3fba 100644
--- a/opends/src/server/org/opends/server/core/CompareOperation.java
+++ b/opends/src/server/org/opends/server/core/CompareOperation.java
@@ -697,7 +697,7 @@
                                           String.valueOf(entryDN)));
 
             // See if one of the entry's ancestors exists.
-            DN parentDN = entryDN.getParent();
+            DN parentDN = entryDN.getParentDNInSuffix();
             while (parentDN != null)
             {
               try
@@ -714,7 +714,7 @@
                 break;
               }
 
-              parentDN = parentDN.getParent();
+              parentDN = parentDN.getParentDNInSuffix();
             }
 
             break compareProcessing;
diff --git a/opends/src/server/org/opends/server/core/DeleteOperation.java b/opends/src/server/org/opends/server/core/DeleteOperation.java
index 230ef40..70d034c 100644
--- a/opends/src/server/org/opends/server/core/DeleteOperation.java
+++ b/opends/src/server/org/opends/server/core/DeleteOperation.java
@@ -617,7 +617,7 @@
 
             try
             {
-              DN parentDN = entryDN.getParent();
+              DN parentDN = entryDN.getParentDNInSuffix();
               while (parentDN != null)
               {
                 if (DirectoryServer.entryExists(parentDN))
@@ -626,7 +626,7 @@
                   break;
                 }
 
-                parentDN = parentDN.getParent();
+                parentDN = parentDN.getParentDNInSuffix();
               }
             }
             catch (Exception e)
diff --git a/opends/src/server/org/opends/server/core/DirectoryServer.java b/opends/src/server/org/opends/server/core/DirectoryServer.java
index 94e6740..caf5bb0 100644
--- a/opends/src/server/org/opends/server/core/DirectoryServer.java
+++ b/opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -31,7 +31,6 @@
 import java.io.FileOutputStream;
 import java.io.PrintStream;
 import java.net.InetAddress;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -1926,7 +1925,7 @@
       throw new InitializationException(msgID, message, e);
     }
 
-    DN[] baseDNs   = { new DN(new RDN[0]) };
+    DN[] baseDNs   = { DN.nullDN() };
     rootDSEBackend = new RootDSEBackend();
     rootDSEBackend.initializeBackend(rootDSEConfigEntry, baseDNs);
   }
@@ -5372,7 +5371,7 @@
 
     while (backend == null)
     {
-      dn = dn.getParent();
+      dn = dn.getParentDNInSuffix();
       if (dn == null)
       {
         break;
@@ -5422,7 +5421,7 @@
       }
 
       boolean found = false;
-      DN parentDN = suffixDN.getParent();
+      DN parentDN = suffixDN.getParentDNInSuffix();
       while (parentDN != null)
       {
         b = directoryServer.suffixes.get(suffixDN);
@@ -5444,7 +5443,7 @@
           }
         }
 
-        parentDN = parentDN.getParent();
+        parentDN = parentDN.getParentDNInSuffix();
       }
 
 
@@ -5513,7 +5512,7 @@
           throw new ConfigException(msgID, message);
         }
 
-        DN parentDN = suffixDN.getParent();
+        DN parentDN = suffixDN.getParentDNInSuffix();
         while (parentDN != null)
         {
           b = directoryServer.suffixes.get(suffixDN);
@@ -5525,7 +5524,7 @@
             throw new ConfigException(msgID, message);
           }
 
-          parentDN = suffixDN.getParent();
+          parentDN = suffixDN.getParentDNInSuffix();
         }
       }
 
@@ -5542,7 +5541,7 @@
         throw new ConfigException(msgID, message);
       }
 
-      DN parentDN = suffixDN.getParent();
+      DN parentDN = suffixDN.getParentDNInSuffix();
       while (parentDN != null)
       {
         b = directoryServer.privateSuffixes.get(suffixDN);
@@ -5563,7 +5562,7 @@
           }
         }
 
-        parentDN = suffixDN.getParent();
+        parentDN = suffixDN.getParentDNInSuffix();
       }
 
 
@@ -5599,7 +5598,7 @@
         return;
       }
 
-      DN parentDN = suffixDN.getParent();
+      DN parentDN = suffixDN.getParentDNInSuffix();
       while (parentDN != null)
       {
         b = directoryServer.suffixes.get(parentDN);
@@ -5625,7 +5624,7 @@
         return;
       }
 
-      DN parentDN = suffixDN.getParent();
+      DN parentDN = suffixDN.getParentDNInSuffix();
       while (parentDN != null)
       {
         b = directoryServer.privateSuffixes.get(parentDN);
@@ -7328,7 +7327,7 @@
       {
         // The config handler hasn't been initialized yet.  Just return the DN
         // of the root DSE.
-        return new DN(new ArrayList<RDN>(0));
+        return DN.nullDN();
       }
 
       return configHandler.getConfigRootEntry().getDN();
@@ -7339,7 +7338,7 @@
 
       // This could theoretically happen if an alert needs to be sent before the
       // configuration is initialized.  In that case, just return an empty DN.
-      return new DN(new ArrayList<RDN>(0));
+      return DN.nullDN();
     }
   }
 
@@ -7782,11 +7781,9 @@
       // Cannot reach this point.
       throw new RuntimeException();
     }
-    RDN[] baseRDNs = monitorRootDN.getRDNComponents();
-    RDN[] rdns = new RDN[baseRDNs.length+1];
-    rdns[0] = new RDN(cnType, new AttributeValue(cnType, monitorName));
-    System.arraycopy(baseRDNs, 0, rdns, 1, baseRDNs.length);
-    return new DN(rdns);
+
+    RDN rdn = RDN.create(cnType, new AttributeValue(cnType, monitorName));
+    return monitorRootDN.concat(rdn);
   }
 }
 
diff --git a/opends/src/server/org/opends/server/core/EntryCacheConfigManager.java b/opends/src/server/org/opends/server/core/EntryCacheConfigManager.java
index fb85891..8c2c8eb 100644
--- a/opends/src/server/org/opends/server/core/EntryCacheConfigManager.java
+++ b/opends/src/server/org/opends/server/core/EntryCacheConfigManager.java
@@ -141,8 +141,8 @@
 
       try
       {
-        ConfigEntry parentEntry =
-             DirectoryServer.getConfigEntry(configEntryDN.getParent());
+        ConfigEntry parentEntry = DirectoryServer
+            .getConfigEntry(configEntryDN.getParentDNInSuffix());
         if (parentEntry != null)
         {
           parentEntry.registerAddListener(this);
@@ -168,7 +168,7 @@
     configEntry.registerChangeListener(this);
     try
     {
-      DN parentDN = configEntryDN.getParent();
+      DN parentDN = configEntryDN.getParentDNInSuffix();
       ConfigEntry parentEntry = DirectoryServer.getConfigEntry(parentDN);
       if (parentEntry != null)
       {
diff --git a/opends/src/server/org/opends/server/core/KeyManagerProviderConfigManager.java b/opends/src/server/org/opends/server/core/KeyManagerProviderConfigManager.java
index f36c5ce..064bcd6 100644
--- a/opends/src/server/org/opends/server/core/KeyManagerProviderConfigManager.java
+++ b/opends/src/server/org/opends/server/core/KeyManagerProviderConfigManager.java
@@ -143,8 +143,8 @@
 
       try
       {
-        ConfigEntry parentEntry =
-             DirectoryServer.getConfigEntry(configEntryDN.getParent());
+        ConfigEntry parentEntry = DirectoryServer
+            .getConfigEntry(configEntryDN.getParentDNInSuffix());
         if (parentEntry != null)
         {
           parentEntry.registerAddListener(this);
@@ -170,7 +170,7 @@
     configEntry.registerChangeListener(this);
     try
     {
-      DN parentDN = configEntryDN.getParent();
+      DN parentDN = configEntryDN.getParentDNInSuffix();
       ConfigEntry parentEntry = DirectoryServer.getConfigEntry(parentDN);
       if (parentEntry != null)
       {
@@ -857,7 +857,7 @@
     configEntry.registerChangeListener(this);
     try
     {
-      DN parentDN = configEntry.getDN().getParent();
+      DN parentDN = configEntry.getDN().getParentDNInSuffix();
       ConfigEntry parentEntry = DirectoryServer.getConfigEntry(parentDN);
       if (parentEntry != null)
       {
diff --git a/opends/src/server/org/opends/server/core/ModifyDNOperation.java b/opends/src/server/org/opends/server/core/ModifyDNOperation.java
index 7c275e4..978870d 100644
--- a/opends/src/server/org/opends/server/core/ModifyDNOperation.java
+++ b/opends/src/server/org/opends/server/core/ModifyDNOperation.java
@@ -927,7 +927,7 @@
       DN parentDN;
       if (newSuperior == null)
       {
-        parentDN = entryDN.getParent();
+        parentDN = entryDN.getParentDNInSuffix();
       }
       else
       {
@@ -942,14 +942,7 @@
         break modifyDNProcessing;
       }
 
-      RDN[] parentComponents = parentDN.getRDNComponents();
-      RDN[] newComponents    = new RDN[parentComponents.length+1];
-      System.arraycopy(parentComponents, 0, newComponents, 1,
-                       parentComponents.length);
-      newComponents[0] = newRDN;
-
-      DN newDN = new DN(newComponents);
-
+      DN newDN = parentDN.concat(newRDN);
 
       // Get the backend for the current entry, and the backend for the new
       // entry.  If either is null, or if they are different, then fail.
@@ -1092,7 +1085,7 @@
         if (currentEntry == null)
         {
           // See if one of the entry's ancestors exists.
-          parentDN = entryDN.getParent();
+          parentDN = entryDN.getParentDNInSuffix();
           while (parentDN != null)
           {
             try
@@ -1109,7 +1102,7 @@
               break;
             }
 
-            parentDN = parentDN.getParent();
+            parentDN = parentDN.getParentDNInSuffix();
           }
 
           setResultCode(ResultCode.NO_SUCH_OBJECT);
@@ -1416,10 +1409,10 @@
           {
             LinkedHashSet<AttributeValue> valueSet =
                  new LinkedHashSet<AttributeValue>(1);
-            valueSet.add(currentRDN.getAttributeValues()[i]);
+            valueSet.add(currentRDN.getAttributeValue(i));
 
-            Attribute a = new Attribute(currentRDN.getAttributeTypes()[i],
-                                        currentRDN.getAttributeNames()[i],
+            Attribute a = new Attribute(currentRDN.getAttributeType(i),
+                                        currentRDN.getAttributeName(i),
                                         valueSet);
 
             // If the associated attribute type is marked NO-USER-MODIFICATION,
@@ -1455,10 +1448,10 @@
         {
           LinkedHashSet<AttributeValue> valueSet =
                new LinkedHashSet<AttributeValue>(1);
-          valueSet.add(newRDN.getAttributeValues()[i]);
+          valueSet.add(newRDN.getAttributeValue(i));
 
-          Attribute a = new Attribute(newRDN.getAttributeTypes()[i],
-                                      newRDN.getAttributeNames()[i],
+          Attribute a = new Attribute(newRDN.getAttributeType(i),
+                                      newRDN.getAttributeName(i),
                                       valueSet);
 
           LinkedList<AttributeValue> duplicateValues =
diff --git a/opends/src/server/org/opends/server/core/ModifyOperation.java b/opends/src/server/org/opends/server/core/ModifyOperation.java
index 4767e51..c46d128 100644
--- a/opends/src/server/org/opends/server/core/ModifyOperation.java
+++ b/opends/src/server/org/opends/server/core/ModifyOperation.java
@@ -923,7 +923,7 @@
                                         String.valueOf(entryDN)));
 
           // See if one of the entry's ancestors exists.
-          DN parentDN = entryDN.getParent();
+          DN parentDN = entryDN.getParentDNInSuffix();
           while (parentDN != null)
           {
             try
@@ -940,7 +940,7 @@
               break;
             }
 
-            parentDN = parentDN.getParent();
+            parentDN = parentDN.getParentDNInSuffix();
           }
 
           break modifyProcessing;
diff --git a/opends/src/server/org/opends/server/core/Operation.java b/opends/src/server/org/opends/server/core/Operation.java
index 0d85d66..fd81051 100644
--- a/opends/src/server/org/opends/server/core/Operation.java
+++ b/opends/src/server/org/opends/server/core/Operation.java
@@ -42,7 +42,6 @@
 import org.opends.server.types.DN;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.OperationType;
-import org.opends.server.types.RDN;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.operation.PostOperationOperation;
 import org.opends.server.types.operation.PostResponseOperation;
@@ -752,7 +751,7 @@
       AuthenticationInfo authInfo = clientConnection.getAuthenticationInfo();
       if (authInfo == null)
       {
-        return new DN(new RDN[0]);
+        return DN.nullDN();
       }
       else
       {
diff --git a/opends/src/server/org/opends/server/core/PersistentSearch.java b/opends/src/server/org/opends/server/core/PersistentSearch.java
index 63c343a..2ae86ec 100644
--- a/opends/src/server/org/opends/server/core/PersistentSearch.java
+++ b/opends/src/server/org/opends/server/core/PersistentSearch.java
@@ -221,7 +221,7 @@
         }
         break;
       case SINGLE_LEVEL:
-        if (! baseDN.equals(entry.getDN().getParent()))
+        if (! baseDN.equals(entry.getDN().getParentDNInSuffix()))
         {
           return;
         }
@@ -331,7 +331,7 @@
         }
         break;
       case SINGLE_LEVEL:
-        if (! baseDN.equals(entry.getDN().getParent()))
+        if (! baseDN.equals(entry.getDN().getParentDNInSuffix()))
         {
           return;
         }
@@ -444,7 +444,7 @@
         }
         break;
       case SINGLE_LEVEL:
-        if (! baseDN.equals(oldEntry.getDN().getParent()))
+        if (! baseDN.equals(oldEntry.getDN().getParentDNInSuffix()))
         {
           return;
         }
@@ -567,8 +567,8 @@
 
         break;
       case SINGLE_LEVEL:
-        oldMatches = baseDN.equals(oldEntry.getDN().getParent());
-        newMatches = baseDN.equals(newEntry.getDN().getParent());
+        oldMatches = baseDN.equals(oldEntry.getDN().getParentDNInSuffix());
+        newMatches = baseDN.equals(newEntry.getDN().getParentDNInSuffix());
 
         if (! (oldMatches || newMatches))
         {
diff --git a/opends/src/server/org/opends/server/core/RFC3672SubtreeSpecification.java b/opends/src/server/org/opends/server/core/RFC3672SubtreeSpecification.java
index a964219..011032e 100644
--- a/opends/src/server/org/opends/server/core/RFC3672SubtreeSpecification.java
+++ b/opends/src/server/org/opends/server/core/RFC3672SubtreeSpecification.java
@@ -689,7 +689,7 @@
   public RFC3672SubtreeSpecification(DN rootDN, DN relativeBaseDN,
       int minimumDepth, int maximumDepth, Iterable<DN> chopBefore,
       Iterable<DN> chopAfter, Refinement refinements) {
-    super(relativeBaseDN == null ? rootDN : new DN(rootDN, relativeBaseDN),
+    super(relativeBaseDN == null ? rootDN : rootDN.concat(relativeBaseDN),
         minimumDepth, maximumDepth, chopBefore, chopAfter);
 
     assert debugConstructor(CLASS_NAME);
diff --git a/opends/src/server/org/opends/server/core/RelativeSubtreeSpecification.java b/opends/src/server/org/opends/server/core/RelativeSubtreeSpecification.java
index 5f58344..f53de92 100644
--- a/opends/src/server/org/opends/server/core/RelativeSubtreeSpecification.java
+++ b/opends/src/server/org/opends/server/core/RelativeSubtreeSpecification.java
@@ -248,7 +248,7 @@
   public RelativeSubtreeSpecification(DN rootDN, DN relativeBaseDN,
       int minimumDepth, int maximumDepth, Iterable<DN> chopBefore,
       Iterable<DN> chopAfter, SearchFilter filter) {
-    super(relativeBaseDN == null ? rootDN : new DN(rootDN, relativeBaseDN),
+    super(relativeBaseDN == null ? rootDN : rootDN.concat(relativeBaseDN),
         minimumDepth, maximumDepth, chopBefore, chopAfter);
 
     assert debugConstructor(CLASS_NAME);
diff --git a/opends/src/server/org/opends/server/core/SimpleSubtreeSpecification.java b/opends/src/server/org/opends/server/core/SimpleSubtreeSpecification.java
index cfb7673..b548c3d 100644
--- a/opends/src/server/org/opends/server/core/SimpleSubtreeSpecification.java
+++ b/opends/src/server/org/opends/server/core/SimpleSubtreeSpecification.java
@@ -390,7 +390,7 @@
       this.chopBefore = new TreeMap<DN, DN>();
 
       for (DN localName : chopBefore) {
-        this.chopBefore.put(new DN(baseDN, localName), localName);
+        this.chopBefore.put(baseDN.concat(localName), localName);
       }
     } else {
       // No chop before specifications.
@@ -402,7 +402,7 @@
       this.chopAfter = new TreeMap<DN, DN>();
 
       for (DN localName : chopAfter) {
-        this.chopAfter.put(new DN(baseDN, localName), localName);
+        this.chopAfter.put(baseDN.concat(localName), localName);
       }
     } else {
       // No chop after specifications.
@@ -428,10 +428,10 @@
     }
 
     // Check minimum and maximum depths.
-    int baseRDNCount = baseDN.getRDNComponents().length;
+    int baseRDNCount = baseDN.getNumComponents();
 
     if (minimumDepth > 0) {
-      int entryRDNCount = dn.getRDNComponents().length;
+      int entryRDNCount = dn.getNumComponents();
 
       if (entryRDNCount - baseRDNCount < minimumDepth) {
         return false;
@@ -439,7 +439,7 @@
     }
 
     if (maximumDepth >= 0) {
-      int entryRDNCount = dn.getRDNComponents().length;
+      int entryRDNCount = dn.getNumComponents();
 
       if (entryRDNCount - baseRDNCount > maximumDepth) {
         return false;
diff --git a/opends/src/server/org/opends/server/core/TrustManagerProviderConfigManager.java b/opends/src/server/org/opends/server/core/TrustManagerProviderConfigManager.java
index 50fa8d3..04a12fb 100644
--- a/opends/src/server/org/opends/server/core/TrustManagerProviderConfigManager.java
+++ b/opends/src/server/org/opends/server/core/TrustManagerProviderConfigManager.java
@@ -144,8 +144,8 @@
 
       try
       {
-        ConfigEntry parentEntry =
-             DirectoryServer.getConfigEntry(configEntryDN.getParent());
+        ConfigEntry parentEntry = DirectoryServer
+            .getConfigEntry(configEntryDN.getParentDNInSuffix());
         if (parentEntry != null)
         {
           parentEntry.registerAddListener(this);
@@ -171,7 +171,7 @@
     configEntry.registerChangeListener(this);
     try
     {
-      DN parentDN = configEntryDN.getParent();
+      DN parentDN = configEntryDN.getParentDNInSuffix();
       ConfigEntry parentEntry = DirectoryServer.getConfigEntry(parentDN);
       if (parentEntry != null)
       {
@@ -861,7 +861,7 @@
     configEntry.registerChangeListener(this);
     try
     {
-      DN parentDN = configEntry.getDN().getParent();
+      DN parentDN = configEntry.getDN().getParentDNInSuffix();
       ConfigEntry parentEntry = DirectoryServer.getConfigEntry(parentDN);
       if (parentEntry != null)
       {
diff --git a/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java b/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
index 82f4b81..50c6c8e 100644
--- a/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
+++ b/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
@@ -1015,7 +1015,7 @@
                                                 String.valueOf(entryDN)));
 
         // See if one of the entry's ancestors exists.
-        DN parentDN = entryDN.getParent();
+        DN parentDN = entryDN.getParentDNInSuffix();
         while (parentDN != null)
         {
           try
@@ -1032,7 +1032,7 @@
             break;
           }
 
-          parentDN = parentDN.getParent();
+          parentDN = parentDN.getParentDNInSuffix();
         }
 
         return null;
diff --git a/opends/src/server/org/opends/server/messages/CoreMessages.java b/opends/src/server/org/opends/server/messages/CoreMessages.java
index f4d0c07..a7357e7 100644
--- a/opends/src/server/org/opends/server/messages/CoreMessages.java
+++ b/opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -1754,10 +1754,11 @@
 
 
   /**
-   * The message ID for the string that will be used if an attempt is made to
-   * decode a string as an RDN but that string contained an unexpected comma or
-   * semicolon.  This takes two arguments, which are the RDN string to decode
-   * and the position of the illegal comma or semicolon.
+   * The message ID for the string that will be used if an attempt is
+   * made to decode a string as an RDN but that string contained an
+   * unexpected plus, comma, or semicolon. This takes two arguments,
+   * which are the RDN string to decode and the position of the
+   * illegal plus, comma, or semicolon.
    */
   public static final int MSGID_RDN_UNEXPECTED_COMMA =
        CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 174;
@@ -6572,8 +6573,8 @@
     registerMessage(MSGID_RDN_UNEXPECTED_COMMA,
                     "Unable to decode the provided string \"%s\" as a " +
                     "relative distinguished name because it contained an " +
-                    "unexpected comma or semicolon at position %d, which is " +
-                    "not allowed in an RDN.");
+                    "unexpected plus, comma, or semicolon at position %d, "+
+                    "which is not allowed in an RDN.");
     registerMessage(MSGID_RDN_ILLEGAL_CHARACTER,
                     "Unable to decode the provided string \"%s\" as a " +
                     "relative distinguished name because an illegal " +
diff --git a/opends/src/server/org/opends/server/protocols/jmx/JmxConnectionHandler.java b/opends/src/server/org/opends/server/protocols/jmx/JmxConnectionHandler.java
index 99238fc..bbfe835 100644
--- a/opends/src/server/org/opends/server/protocols/jmx/JmxConnectionHandler.java
+++ b/opends/src/server/org/opends/server/protocols/jmx/JmxConnectionHandler.java
@@ -337,7 +337,7 @@
     // in the current entry.
     // Always return true as the check will be performed by the
     // hasAcceptableConfiguration call
-    if (configEntry.getDN().compareTo(configEntryDN) == 0)
+    if (configEntry.getDN().equals(configEntryDN))
     {
       return true;
     }
@@ -370,7 +370,7 @@
     //
     // We are checking first if we are dealing with a change
     // in the current entry.
-    if (configEntry.getDN().compareTo(configEntryDN) == 0)
+    if (configEntry.getDN().equals(configEntryDN))
     {
       ArrayList<String> messages = new ArrayList<String>();
       return new ConfigChangeResult(ResultCode.SUCCESS, false, messages);
diff --git a/opends/src/server/org/opends/server/schema/OctetStringOrderingMatchingRule.java b/opends/src/server/org/opends/server/schema/OctetStringOrderingMatchingRule.java
index 29ca439..0ff9913 100644
--- a/opends/src/server/org/opends/server/schema/OctetStringOrderingMatchingRule.java
+++ b/opends/src/server/org/opends/server/schema/OctetStringOrderingMatchingRule.java
@@ -35,12 +35,10 @@
 import org.opends.server.types.ByteString;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.InitializationException;
+import org.opends.server.util.StaticUtils;
 
 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.schema.SchemaConstants.*;
-import static org.opends.server.util.StaticUtils.*;
 
 
 
@@ -229,36 +227,7 @@
     assert debugEnter(CLASS_NAME, "compareValues", String.valueOf(b1),
                       String.valueOf(b2));
 
-    int minLength = Math.min(b1.length, b2.length);
-
-    for (int i=0; i < minLength; i++)
-    {
-      if (b1[i] == b2[i])
-      {
-        continue;
-      }
-      else if (b1[i] < b2[i])
-      {
-        return -1;
-      }
-      else if (b1[i] > b2[i])
-      {
-        return 1;
-      }
-    }
-
-    if (b1.length == b2.length)
-    {
-      return 0;
-    }
-    else if (b1.length < b2.length)
-    {
-      return -1;
-    }
-    else
-    {
-      return 1;
-    }
+    return StaticUtils.compare(b1, b2);
   }
 }
 
diff --git a/opends/src/server/org/opends/server/schema/RFC3672SubtreeSpecificationSyntax.java b/opends/src/server/org/opends/server/schema/RFC3672SubtreeSpecificationSyntax.java
index 73d5480..227f3b7 100644
--- a/opends/src/server/org/opends/server/schema/RFC3672SubtreeSpecificationSyntax.java
+++ b/opends/src/server/org/opends/server/schema/RFC3672SubtreeSpecificationSyntax.java
@@ -277,7 +277,7 @@
 
     // Use the subtree specification code to make this determination.
     try {
-      RFC3672SubtreeSpecification.valueOf(new DN(), value.stringValue());
+      RFC3672SubtreeSpecification.valueOf(DN.nullDN(), value.stringValue());
 
       return true;
     } catch (DirectoryException e) {
diff --git a/opends/src/server/org/opends/server/schema/RelativeSubtreeSpecificationSyntax.java b/opends/src/server/org/opends/server/schema/RelativeSubtreeSpecificationSyntax.java
index b92910a..57e0aa6 100644
--- a/opends/src/server/org/opends/server/schema/RelativeSubtreeSpecificationSyntax.java
+++ b/opends/src/server/org/opends/server/schema/RelativeSubtreeSpecificationSyntax.java
@@ -278,7 +278,7 @@
 
     // Use the subtree specification code to make this determination.
     try {
-      RelativeSubtreeSpecification.valueOf(new DN(), value.stringValue());
+      RelativeSubtreeSpecification.valueOf(DN.nullDN(), value.stringValue());
 
       return true;
     } catch (DirectoryException e) {
diff --git a/opends/src/server/org/opends/server/synchronization/MultimasterSynchronization.java b/opends/src/server/org/opends/server/synchronization/MultimasterSynchronization.java
index bd7f70e..d354f23 100644
--- a/opends/src/server/org/opends/server/synchronization/MultimasterSynchronization.java
+++ b/opends/src/server/org/opends/server/synchronization/MultimasterSynchronization.java
@@ -462,7 +462,7 @@
     do
     {
       domain = domains.get(temp);
-      temp = temp.getParent();
+      temp = temp.getParentDNInSuffix();
       if (temp == null)
       {
         break;
diff --git a/opends/src/server/org/opends/server/synchronization/SynchronizationDomain.java b/opends/src/server/org/opends/server/synchronization/SynchronizationDomain.java
index 922bfe6..7b401b2 100644
--- a/opends/src/server/org/opends/server/synchronization/SynchronizationDomain.java
+++ b/opends/src/server/org/opends/server/synchronization/SynchronizationDomain.java
@@ -662,7 +662,7 @@
   {
     AddContext ctx = new AddContext(generateChangeNumber(addOperation),
         Historical.getEntryUuid(addOperation),
-        findEntryId(addOperation.getEntryDN().getParent()));
+        findEntryId(addOperation.getEntryDN().getParentDNInSuffix()));
 
     addOperation.setAttachment(SYNCHROCONTEXT, ctx);
   }
@@ -1491,13 +1491,7 @@
       throw new Exception("operation parameters are invalid");
     }
 
-    RDN[] parentComponents = parentDN.getRDNComponents();
-    RDN[] newComponents    = new RDN[parentComponents.length+1];
-    System.arraycopy(parentComponents, 0, newComponents, 1,
-        parentComponents.length);
-    newComponents[0] = newRDN;
-
-    DN newDN = new DN(newComponents);
+    DN newDN = parentDN.concat(newRDN);
 
     // get the current DN of this entry in the database.
     DN currentDN = findEntryDN(entryUid);
diff --git a/opends/src/server/org/opends/server/tools/LDIFDiff.java b/opends/src/server/org/opends/server/tools/LDIFDiff.java
index c1e65ab..4739c33 100644
--- a/opends/src/server/org/opends/server/tools/LDIFDiff.java
+++ b/opends/src/server/org/opends/server/tools/LDIFDiff.java
@@ -42,7 +42,6 @@
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.AttributeValue;
 import org.opends.server.types.DN;
-import org.opends.server.types.DNComparator;
 import org.opends.server.types.Entry;
 import org.opends.server.types.ExistingFileBehavior;
 import org.opends.server.types.LDIFImportConfig;
@@ -277,8 +276,7 @@
       return 1;
     }
 
-    DNComparator comparator = new DNComparator();
-    TreeMap<DN,Entry> sourceMap = new TreeMap<DN,Entry>(comparator);
+    TreeMap<DN,Entry> sourceMap = new TreeMap<DN,Entry>();
     try
     {
       while (true)
@@ -324,7 +322,7 @@
       return 1;
     }
 
-    TreeMap<DN,Entry> targetMap = new TreeMap<DN,Entry>(comparator);
+    TreeMap<DN,Entry> targetMap = new TreeMap<DN,Entry>();
     try
     {
       while (true)
@@ -437,9 +435,9 @@
 
         while (true)
         {
-          // Use the DN comparator to determine the relative order of the
+          // Compare the DNs to determine the relative order of the
           // entries.
-          int comparatorValue = comparator.compare(sourceDN, targetDN);
+          int comparatorValue = sourceDN.compareTo(targetDN);
           if (comparatorValue < 0)
           {
             // The source entry should be before the target entry, which means
diff --git a/opends/src/server/org/opends/server/tools/LDIFModify.java b/opends/src/server/org/opends/server/tools/LDIFModify.java
index 425219d..8f51f98 100644
--- a/opends/src/server/org/opends/server/tools/LDIFModify.java
+++ b/opends/src/server/org/opends/server/tools/LDIFModify.java
@@ -45,7 +45,6 @@
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.AttributeValue;
-import org.opends.server.types.DNComparator;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
@@ -126,11 +125,10 @@
          throws IOException, LDIFException
   {
     // Read the changes into memory.
-      DNComparator comparator = new DNComparator();
-      TreeMap<DN,AddChangeRecordEntry> adds =
-          new TreeMap<DN,AddChangeRecordEntry>(comparator);
-      TreeMap<DN,Entry> ldifEntries =
-          new TreeMap<DN,Entry>(comparator);
+    TreeMap<DN,AddChangeRecordEntry> adds =
+          new TreeMap<DN,AddChangeRecordEntry>();
+    TreeMap<DN,Entry> ldifEntries =
+          new TreeMap<DN,Entry>();
     HashMap<DN,DeleteChangeRecordEntry> deletes =
          new HashMap<DN,DeleteChangeRecordEntry>();
     HashMap<DN,LinkedList<Modification>> modifications =
diff --git a/opends/src/server/org/opends/server/tools/LDIFSearch.java b/opends/src/server/org/opends/server/tools/LDIFSearch.java
index ae48bb6..ff60b5e 100644
--- a/opends/src/server/org/opends/server/tools/LDIFSearch.java
+++ b/opends/src/server/org/opends/server/tools/LDIFSearch.java
@@ -521,7 +521,7 @@
     }
     else
     {
-      baseDNs.add(new DN());
+      baseDNs.add(DN.nullDN());
     }
 
 
diff --git a/opends/src/server/org/opends/server/tools/makeldif/DNTag.java b/opends/src/server/org/opends/server/tools/makeldif/DNTag.java
index be051e7..235f44b 100644
--- a/opends/src/server/org/opends/server/tools/makeldif/DNTag.java
+++ b/opends/src/server/org/opends/server/tools/makeldif/DNTag.java
@@ -32,7 +32,6 @@
 
 import org.opends.server.types.DN;
 import org.opends.server.types.InitializationException;
-import org.opends.server.types.RDN;
 
 import static org.opends.server.messages.MessageHandler.*;
 import static org.opends.server.messages.ToolMessages.*;
@@ -205,26 +204,25 @@
     }
     else if (numComponents > 0)
     {
-      RDN[] rdnComps = dn.getRDNComponents();
-      int count = Math.min(numComponents, rdnComps.length);
+      int count = Math.min(numComponents, dn.getNumComponents());
 
-      rdnComps[0].toString(templateValue.getValue());
-      for (int i=1; i < count; i++)
+      dn.getRDN(0).toString(templateValue.getValue());
+      for (int i = 1; i < count; i++)
       {
         templateValue.append(",");
-        rdnComps[i].toString(templateValue.getValue());
+        dn.getRDN(i).toString(templateValue.getValue());
       }
     }
     else
     {
-      RDN[] rdnComps = dn.getRDNComponents();
-      int count = Math.min(Math.abs(numComponents), rdnComps.length);
+      int sz = dn.getNumComponents();
+      int count = Math.min(Math.abs(numComponents), sz);
 
-      rdnComps[rdnComps.length-count].toString(templateValue.getValue());
-      for (int i=1; i < count; i++)
+      dn.getRDN(sz - count).toString(templateValue.getValue());
+      for (int i = 1; i < count; i++)
       {
         templateValue.append(",");
-        rdnComps[rdnComps.length-count+i].toString(templateValue.getValue());
+        dn.getRDN(sz - count + i).toString(templateValue.getValue());
       }
     }
 
diff --git a/opends/src/server/org/opends/server/tools/makeldif/TemplateEntry.java b/opends/src/server/org/opends/server/tools/makeldif/TemplateEntry.java
index 339f942..fa4c9be 100644
--- a/opends/src/server/org/opends/server/tools/makeldif/TemplateEntry.java
+++ b/opends/src/server/org/opends/server/tools/makeldif/TemplateEntry.java
@@ -155,33 +155,22 @@
    */
   public DN getDN()
   {
-    if (dn == null)
-    {
-      AttributeType[]  rdnAttributes = template.getRDNAttributes();
-      String[]         rdnNames      = new String[rdnAttributes.length];
-      AttributeValue[] rdnValues     = new AttributeValue[rdnAttributes.length];
+    if (dn == null) {
+      RDN.Builder builder = RDN.createBuilder();
 
-      for (int i=0; i < rdnAttributes.length; i++)
-      {
-        TemplateValue v = getValue(rdnAttributes[i]);
-        if (v == null)
-        {
+      for (AttributeType type : template.getRDNAttributes()) {
+        TemplateValue v = getValue(type);
+        if (v == null) {
           return null;
         }
 
-        rdnNames[i]  = rdnAttributes[i].getNameOrOID();
-        rdnValues[i] = new AttributeValue(rdnAttributes[i],
-                                          v.getValue().toString());
+        AttributeValue value = new AttributeValue(type,
+            v.getValue().toString());
+
+        builder.append(type, value);
       }
 
-      RDN[] parentComponents = parentDN.getRDNComponents();
-      RDN[] dnComponents = new RDN[parentComponents.length+1];
-
-      dnComponents[0] = new RDN(rdnAttributes, rdnNames, rdnValues);
-      System.arraycopy(parentComponents, 0, dnComponents, 1,
-                       parentComponents.length);
-
-      dn = new DN(dnComponents);
+      dn = parentDN.concat(builder.getInstance());
     }
 
     return dn;
diff --git a/opends/src/server/org/opends/server/tools/makeldif/UnderscoreDNTag.java b/opends/src/server/org/opends/server/tools/makeldif/UnderscoreDNTag.java
index 631c379..3702b24 100644
--- a/opends/src/server/org/opends/server/tools/makeldif/UnderscoreDNTag.java
+++ b/opends/src/server/org/opends/server/tools/makeldif/UnderscoreDNTag.java
@@ -32,7 +32,6 @@
 
 import org.opends.server.types.DN;
 import org.opends.server.types.InitializationException;
-import org.opends.server.types.RDN;
 
 import static org.opends.server.messages.MessageHandler.*;
 import static org.opends.server.messages.ToolMessages.*;
@@ -201,37 +200,33 @@
 
     if (numComponents == 0)
     {
-      RDN[] rdnComps = dn.getRDNComponents();
-
-      rdnComps[0].toString(templateValue.getValue());
-      for (int i=1; i < rdnComps.length; i++)
+      dn.getRDN(0).toString(templateValue.getValue());
+      for (int i=1; i < dn.getNumComponents(); i++)
       {
         templateValue.append("_");
-        rdnComps[i].toString(templateValue.getValue());
+        dn.getRDN(i).toString(templateValue.getValue());
       }
     }
     else if (numComponents > 0)
     {
-      RDN[] rdnComps = dn.getRDNComponents();
-      int count = Math.min(numComponents, rdnComps.length);
+      int count = Math.min(numComponents, dn.getNumComponents());
 
-      rdnComps[0].toString(templateValue.getValue());
-      for (int i=1; i < count; i++)
+      dn.getRDN(0).toString(templateValue.getValue());
+      for (int i = 1; i < count; i++)
       {
         templateValue.append("_");
-        rdnComps[i].toString(templateValue.getValue());
+        dn.getRDN(i).toString(templateValue.getValue());
       }
     }
     else
     {
-      RDN[] rdnComps = dn.getRDNComponents();
-      int count = Math.min(Math.abs(numComponents), rdnComps.length);
+      int sz = dn.getNumComponents();
+      int count = Math.min(Math.abs(numComponents), sz);
 
-      rdnComps[rdnComps.length-count].toString(templateValue.getValue());
-      for (int i=1; i < count; i++)
-      {
+      dn.getRDN(sz - count).toString(templateValue.getValue());
+      for (int i = 1; i < count; i++) {
         templateValue.append("_");
-        rdnComps[rdnComps.length-count+i].toString(templateValue.getValue());
+        dn.getRDN(sz - count + i).toString(templateValue.getValue());
       }
     }
 
diff --git a/opends/src/server/org/opends/server/tools/makeldif/UnderscoreParentDNTag.java b/opends/src/server/org/opends/server/tools/makeldif/UnderscoreParentDNTag.java
index 8ac2344..39b3a84 100644
--- a/opends/src/server/org/opends/server/tools/makeldif/UnderscoreParentDNTag.java
+++ b/opends/src/server/org/opends/server/tools/makeldif/UnderscoreParentDNTag.java
@@ -32,7 +32,6 @@
 
 import org.opends.server.types.DN;
 import org.opends.server.types.InitializationException;
-import org.opends.server.types.RDN;
 
 import static org.opends.server.messages.MessageHandler.*;
 import static org.opends.server.messages.ToolMessages.*;
@@ -132,12 +131,11 @@
       return TagResult.SUCCESS_RESULT;
     }
 
-    RDN[] rdnComps = parentDN.getRDNComponents();
-    rdnComps[0].toString(templateValue.getValue());
-    for (int i=1; i < rdnComps.length; i++)
+    parentDN.getRDN(0).toString(templateValue.getValue());
+    for (int i=1; i < parentDN.getNumComponents(); i++)
     {
       templateValue.append("_");
-      rdnComps[i].toString(templateValue.getValue());
+      parentDN.getRDN(i).toString(templateValue.getValue());
     }
 
     return TagResult.SUCCESS_RESULT;
diff --git a/opends/src/server/org/opends/server/types/AttributeValue.java b/opends/src/server/org/opends/server/types/AttributeValue.java
index a9d992e..7d454a7 100644
--- a/opends/src/server/org/opends/server/types/AttributeValue.java
+++ b/opends/src/server/org/opends/server/types/AttributeValue.java
@@ -32,7 +32,6 @@
 import org.opends.server.protocols.asn1.ASN1OctetString;
 
 import static org.opends.server.loggers.Debug.*;
-import static org.opends.server.util.StaticUtils.*;
 
 
 
@@ -173,22 +172,6 @@
 
 
   /**
-   * 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 the normalized form of this attribute value.
    *
    * @return  The normalized form of this attribute value.
@@ -263,105 +246,6 @@
 
 
   /**
-   * 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/opends/src/server/org/opends/server/types/AttributeValueComparator.java b/opends/src/server/org/opends/server/types/AttributeValueComparator.java
deleted file mode 100644
index 0d4f4c0..0000000
--- a/opends/src/server/org/opends/server/types/AttributeValueComparator.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * You can obtain a copy of the license at
- * trunk/opends/resource/legal-notices/OpenDS.LICENSE
- * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at
- * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
- * add the following below this CDDL HEADER, with the fields enclosed
- * by brackets "[]" replaced with your own identifying * information:
- *      Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
- */
-package org.opends.server.types;
-
-
-
-import java.util.Comparator;
-
-import org.opends.server.api.OrderingMatchingRule;
-
-import static org.opends.server.loggers.Debug.*;
-
-
-
-/**
- * This class defines a <CODE>Comparator</CODE> object that may be
- * used to compare attribute values, particularly for inclusion in a
- * sorted list.  The values must share the same attribute type, and if
- * possible the ordering matching rule for that attribute type will be
- * used to make the determination.  If there is no ordering matching
- * rule, then a bytewise comparison of the normalized values (from
- * left to right) will be used.
- */
-public class AttributeValueComparator
-       implements Comparator<AttributeValue>
-{
-  /**
-   * The fully-qualified name of this class for debugging purposes.
-   */
-  private static final String CLASS_NAME =
-       "org.opends.server.types.AttributeValueComparator";
-
-
-
-  // The attribute type with which this comparator is associated.
-  private AttributeType attributeType;
-
-  // The ordering matching rule for the attribute type, if any.
-  private OrderingMatchingRule matchingRule;
-
-
-
-  /**
-   * Creates a new instance of this attribute value comparator that
-   * may be used with values of the provided attribute type.
-   *
-   * @param  attributeType  The attribute type that should be used for
-   *                        this attribute value comparator.
-   */
-  public AttributeValueComparator(AttributeType attributeType)
-  {
-    assert debugConstructor(CLASS_NAME,
-                            String.valueOf(attributeType));
-
-    this.attributeType = attributeType;
-    this.matchingRule  = attributeType.getOrderingMatchingRule();
-  }
-
-
-
-  /**
-   * Compares the provided attribute values and returns an integer
-   * value that reflects the relative order between them.
-   *
-   * @param  value1  The first attribute value to compare.
-   * @param  value2  The second attribute value to compare.
-   *
-   * @return  A negative value if the first value should come before
-   *          the second in an ordered list, a positive value if they
-   *          first value should come after the second in an ordered
-   *          list, or zero if there is no difference between their
-   *          order (i.e., the values are equal).
-   */
-  public int compare(AttributeValue value1, AttributeValue value2)
-  {
-    assert debugEnter(CLASS_NAME, "compare", String.valueOf(value1),
-                      String.valueOf(value2));
-
-    try
-    {
-      if (matchingRule == null)
-      {
-        return compareValues(value1.getNormalizedValue(),
-                             value2.getNormalizedValue());
-      }
-      else
-      {
-        return matchingRule.compare(value1.getNormalizedValueBytes(),
-                                    value2.getNormalizedValueBytes());
-      }
-    }
-    catch (Exception e)
-    {
-      assert debugException(CLASS_NAME, "compare", e);
-
-      // Just get the raw values and do a comparison between them.
-      return compareValues(value1.getValue(), value2.getValue());
-    }
-  }
-
-
-
-  /**
-   * Compares the provided values using a simple bytewise comparison
-   * and returns an integer value that reflects the relative order
-   * between them.
-   *
-   * @param  value1  The first byte string to compare.
-   * @param  value2  The second byte string to compare.
-   *
-   * @return  A negative value if the first byte string should come
-   *          before the second in an ordered list, a positive value
-   *          if the first byte string should come after the second in
-   *          an ordered list, or zero if there is no difference
-   *          between them (i.e., the values are equal).
-   */
-  public static int compareValues(ByteString value1,
-                                  ByteString value2)
-  {
-    assert debugEnter(CLASS_NAME, "compareValues",
-                      String.valueOf(value1), String.valueOf(value2));
-
-    byte[] value1Bytes = value1.value();
-    byte[] value2Bytes = value2.value();
-
-    int index = 0;
-    while (true)
-    {
-      if (index < value1Bytes.length)
-      {
-        if (index < value2Bytes.length)
-        {
-          int value = value1Bytes[index] - value2Bytes[index];
-          if (value != 0)
-          {
-            return value;
-          }
-        }
-        else
-        {
-          return 1;
-        }
-      }
-      else if (index < value2Bytes.length)
-      {
-        return -1;
-      }
-      else
-      {
-        return 0;
-      }
-
-      index++;
-    }
-  }
-}
-
diff --git a/opends/src/server/org/opends/server/types/DN.java b/opends/src/server/org/opends/server/types/DN.java
index 857c8f2..45de88c 100644
--- a/opends/src/server/org/opends/server/types/DN.java
+++ b/opends/src/server/org/opends/server/types/DN.java
@@ -28,17 +28,14 @@
 
 
 
+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.*;
 
 
 
@@ -46,51 +43,196 @@
  * 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 class DN
-       implements Comparable<DN>, Serializable
-{
+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).
+
   /**
    * 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";
 
   /**
    * 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 int numComponents;
+  private final int numComponents;
+
+  // The index of the first RDN component (furthest from the root) in
+  // this DN.
+  private final int offset;
 
   // The set of RDN components that comprise this DN, arranged with
   // the suffix as the last element.
-  private RDN[] rdnComponents;
+  private final RDN[] rdnComponents;
 
-  // The string representation of this DN.
-  private String dnString;
+  // The cached normalized string representation of this DN.
+  private final String normalizedDN;
 
-  // The normalized string representation of this DN.
-  private String normalizedDN;
+  // A DN comprising of zero RDN components.
+  private static final DN EMPTY_DN = new DN(new RDN[0], 0, 0);
 
 
 
   /**
-   * Creates a new DN with no RDN components (i.e., a null DN or root
-   * DSE).
+   * 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.
    */
-  public DN()
-  {
-    this(new RDN[0]);
+  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);
+    }
   }
 
 
@@ -99,203 +241,255 @@
    * 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 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.
    */
-  public DN(RDN[] rdnComponents)
-  {
+  private DN(RDN[] rdnComponents, int offset, int count) {
     assert debugConstructor(CLASS_NAME,
-                            String.valueOf(rdnComponents));
+        String.valueOf(rdnComponents), String.valueOf(offset));
 
-    if (rdnComponents == null)
-    {
-      this.rdnComponents = new RDN[0];
-    }
-    else
-    {
-      this.rdnComponents = rdnComponents;
-    }
-
-    numComponents = this.rdnComponents.length;
-    dnString      = null;
-    normalizedDN  = toNormalizedString();
+    this.rdnComponents = rdnComponents;
+    this.offset = offset;
+    this.numComponents = count;
+    this.normalizedDN = normalize();
   }
 
 
 
   /**
-   * Creates a new DN with the provided set of RDNs, arranged with the
-   * suffix as the last element.
+   * 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.
    *
-   * @param  rdnComponents  The set of RDN components that make up
-   *                        this DN.
+   * @param localName
+   *          The DN that will be concatenated to the end of this DN.
+   * @return Returns a DN that represents the concatenation of the
+   *         specified DN onto the end of this DN.
    */
-  public DN(ArrayList<RDN> rdnComponents)
-  {
-    assert debugConstructor(CLASS_NAME,
-                            String.valueOf(rdnComponents));
+  public DN concat(DN localName) {
+    ensureNotNull(localName);
 
-    if ((rdnComponents == null) || rdnComponents.isEmpty())
-    {
-      this.rdnComponents = new RDN[0];
-    }
-    else
-    {
-      this.rdnComponents = new RDN[rdnComponents.size()];
-      rdnComponents.toArray(this.rdnComponents);
+    if (localName.isNullDN()) {
+      return this;
     }
 
-    numComponents = this.rdnComponents.length;
-    dnString      = null;
-    normalizedDN  = toNormalizedString();
+    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);
   }
 
 
 
   /**
-   * Constructs a new DN which is the concatenation of a base DN and a
-   * relative DN. The constructed DN has the following property:
+   * Concatenates the specified RDN sequence to the end of this DN.
+   * <p>
+   * If the argument RDN sequence is empty, then this DN is returned.
+   * Otherwise, the returned DN will be a descendent of this DN.
+   *
+   * @param rdns
+   *          The RDN sequence that will be concatenated to the end of
+   *          this DN.
+   * @return Returns a DN that represents the concatenation of the
+   *         specified RDN sequence onto the end of this DN.
+   */
+  public DN concat(RDN... rdns) {
+    ensureNotNull(rdns);
+
+    if (rdns.length == 0) {
+      return this;
+    }
+
+    // Don't check if this is a nullDN, because were going to copy the
+    // RDN sequence anyway.
+
+    RDN[] allRDNs = new RDN[rdns.length + numComponents];
+
+    System.arraycopy(rdns, 0, allRDNs, 0, rdns.length);
+    System.arraycopy(rdnComponents, offset, allRDNs,
+        rdns.length, numComponents);
+
+    return new DN(allRDNs, 0, allRDNs.length);
+  }
+
+
+
+  /**
+   * Get the parent DN of this DN.
+   *
+   * @return Returns the parent DN of this DN, or <code>null</code>
+   *         if this DN does not have a parent (i.e. it is a DN having
+   *         a single RDN component, or the null DN).
+   */
+  public DN getParent() {
+    if (numComponents <= 1) {
+      return null;
+    } else {
+      return new DN(rdnComponents, offset + 1, numComponents - 1);
+    }
+  }
+
+
+
+  /**
+   * Create a local name (a relative DN) from this DN.
+   * <p>
+   * Examples: <blockquote>
    *
    * <pre>
-   *         DN suffix;
-   *         DN localName;
+   * DN dn = DN.decode(&quot;cn=john,o=example,c=us&quot;);
    *
-   *         suffix.isAncestorOf(new DN(suffix, localName)) == true;
+   * 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>
    *
-   * @param baseDN
-   *          The base DN.
-   * @param localName
-   *          The relative DN.
+   * </blockquote>
+   *
+   * @param beginIndex
+   *          The index of the first RDN component (nearest the root),
+   *          inclusive.
+   * @return Returns the specified local name.
+   * @throws IndexOutOfBoundsException
+   *           If <code>beginIndex</code> is negative, or greater
+   *           than the number of RDN components in this DN.
    */
-  public DN(DN baseDN, DN localName)
-  {
-    this(baseDN, localName.rdnComponents);
+  public DN getLocalName(int beginIndex)
+      throws IndexOutOfBoundsException {
+    return getLocalName(beginIndex, numComponents);
   }
 
 
 
   /**
-   * Constructs a new DN which is the concatenation of a base DN and
-   * an RDN sequence.
+   * Create a local name (a relative DN) from this DN.
+   * <p>
+   * Examples: <blockquote>
    *
-   * @param  baseDN       The base DN.
-   * @param  rdnSequence  The RDN sequence which will become the
-   *                      outermost RDNs of the concatenated DN.
+   * <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(DN baseDN, RDN[] rdnSequence)
-  {
-    assert debugConstructor(CLASS_NAME, String.valueOf(baseDN),
-                            String.valueOf(rdnSequence));
+  public DN getLocalName(int beginIndex, int endIndex)
+      throws IndexOutOfBoundsException {
+    if (beginIndex < 0) {
+      throw new IndexOutOfBoundsException("beginIndex out of range: "
+          + beginIndex);
+    }
 
-    RDN[] allRDNs = new RDN[rdnSequence.length+baseDN.numComponents];
+    if (endIndex > numComponents) {
+      throw new IndexOutOfBoundsException("endIndex out of range: "
+          + endIndex);
+    }
 
-    System.arraycopy(rdnSequence, 0, allRDNs, 0, rdnSequence.length);
-    System.arraycopy(baseDN.rdnComponents, 0, allRDNs,
-                     rdnSequence.length, baseDN.numComponents);
+    if (beginIndex > endIndex) {
+      throw new IndexOutOfBoundsException(
+          "beginIndex greater than endIndex");
+    }
 
-    this.rdnComponents = allRDNs;
-    this.numComponents = allRDNs.length;
-    this.dnString = null;
-    this.normalizedDN = toNormalizedString();
+    if (beginIndex == 0 && endIndex == numComponents) {
+      return this;
+    } else {
+      int i = offset + numComponents - endIndex;
+      return new DN(rdnComponents, i, endIndex - beginIndex);
+    }
   }
 
 
 
   /**
-   * Retrieves the set of RDN components that make up this DN,
-   * arranged with the suffix as the last element. The caller must not
-   * modify the contents of the returned list.
+   * Get the number of RDN components that make up this DN.
    *
-   * @return The set of RDN components that make up this DN.
+   * @return Returns the number of RDN components that make up this
+   *         DN.
    */
-  public RDN[] getRDNComponents()
-  {
-    assert debugEnter(CLASS_NAME, "getRDNComponents");
+  public int getNumComponents() {
+    assert debugEnter(CLASS_NAME, "getNumComponents");
 
-    return rdnComponents;
-  }
-
-
-
-  /**
-   * Specifies the set of RDN components that make up this DN.
-   *
-   * @param  rdnComponents  The set of RDN components that make up
-   *                        this DN arranged with the suffix as the
-   *                        last element.
-   */
-  public void setRDNComponents(RDN[] rdnComponents)
-  {
-    assert debugEnter(CLASS_NAME, "setRDNComponents",
-                      String.valueOf(rdnComponents));
-
-    if (rdnComponents == null)
-    {
-      this.rdnComponents = new RDN[0];
-    }
-    else
-    {
-      this.rdnComponents = rdnComponents;
-    }
-
-    numComponents = this.rdnComponents.length;
-    dnString      = null;
-    normalizedDN  = null; // Get rid of the old cached value.
-    normalizedDN  = toNormalizedString();
-  }
-
-
-
-  /**
-   * Specifies the set of RDN components that make up this DN.
-   *
-   * @param  rdnComponents  The set of RDN components that make up
-   *                        this DN, arranged with the suffix as the
-   *                        last element.
-   */
-  public void setRDNComponents(ArrayList<RDN> rdnComponents)
-  {
-    assert debugEnter(CLASS_NAME, "setRDNComponents",
-                      String.valueOf(rdnComponents));
-
-    if (rdnComponents == null)
-    {
-      this.rdnComponents = new RDN[0];
-    }
-    else
-    {
-      this.rdnComponents = new RDN[rdnComponents.size()];
-      rdnComponents.toArray(this.rdnComponents);
-    }
-
-    numComponents = this.rdnComponents.length;
-    dnString      = null;
-    normalizedDN  = toNormalizedString();
+    return numComponents;
   }
 
 
 
   /**
    * Retrieves the outermost RDN component for this DN (i.e., the one
-   * that is furthest from the suffix).
+   * that is furthest from the suffix). This method is equivalent to
+   * calling <code>getRDN(0)</code> for non-null DNs.
    *
-   * @return  The outermost RDN component for this DN, or
-   *          <CODE>null</CODE> if there are no RDN components in the
-   *          DN.
+   * @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.
+   *
+   * @param index
+   *          The index of the RDN to retrieve, where <code>0</code>
+   *          indicates the outermost RDN component (i.e. the one that
+   *          is furthest from the suffix).
+   * @return Returns the RDN at the specified index.
+   * @throws IndexOutOfBoundsException
+   *           If <code>index</code> is negative, or greater than or
+   *           equal to the number of RDN components in this DN.
+   */
+  public RDN getRDN(int index) throws IndexOutOfBoundsException {
+    if (index < 0) {
+      throw new IndexOutOfBoundsException("index out of range: "
+          + index);
     }
+
+    if (index >= numComponents) {
+      throw new IndexOutOfBoundsException("index out of range: "
+          + index);
+    }
+
+    return rdnComponents[offset + index];
   }
 
 
@@ -304,39 +498,33 @@
    * Retrieves the DN of the entry that is the immediate parent for
    * this entry.
    *
-   * @return  The DN of the entry that is the immediate parent for
-   *          this entry, or <CODE>null</CODE> if the entry with this
-   *          DN does not have a parent (either because there is only
-   *          a single RDN component or because this DN is a suffix
-   *          defined in the server).
+   * @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 getParent()
-  {
-    assert debugEnter(CLASS_NAME, "getParent");
+  public DN getParentDNInSuffix() {
+    assert debugEnter(CLASS_NAME, "getParentDNInSuffix");
 
-    if ((numComponents <= 1) || (DirectoryServer.isSuffix(this)))
-    {
+    if ((numComponents <= 1) || (DirectoryServer.isSuffix(this))) {
       return null;
     }
 
-    RDN[] parentComponents = new RDN[numComponents-1];
-    System.arraycopy(rdnComponents, 1, parentComponents, 0,
-                     numComponents-1);
-    return new DN(parentComponents);
+    return getParent();
   }
 
 
 
   /**
-   * Indicates whether this represents a null DN.  This could target
+   * 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  <CODE>true</CODE> if this does represent a null DN, or
-   *          <CODE>false</CODE> if it does not.
+   * @return <code>true</code> if this does represent a null DN, or
+   *         <code>false</code> if it does not.
    */
-  public boolean isNullDN()
-  {
+  public boolean isNullDN() {
     assert debugEnter(CLASS_NAME, "isNullDN");
 
     return (numComponents == 0);
@@ -345,47 +533,28 @@
 
 
   /**
-   * Indicates whether this DN is one of the suffixes defined in the
-   * Directory Server.
-   *
-   * @return  <CODE>true</CODE> if this DN is one of the suffixes
-   *          defined in the Directory Server, or <CODE>false</CODE>
-   *          if not.
-   */
-  public boolean isSuffix()
-  {
-    assert debugEnter(CLASS_NAME, "isSuffix");
-
-    return DirectoryServer.isSuffix(this);
-  }
-
-
-
-  /**
    * Indicates whether this DN is a descendant of the provided DN
    * (i.e., that the RDN components of the provided DN are the same as
    * the last RDN components for this DN).
    *
-   * @param  dn  The DN for which to make the determination.
-   *
-   * @return  <CODE>true</CODE> if this DN is a descendant of the
-   *          provided DN, or <CODE>false</CODE> if not.
+   * @param dn
+   *          The DN for which to make the determination.
+   * @return <code>true</code> if this DN is a descendant of the
+   *         provided DN, or <code>false</code> if not.
    */
-  public boolean isDescendantOf(DN dn)
-  {
-    assert debugEnter(CLASS_NAME, "isDescendantOf",
-                      String.valueOf(dn));
+  public boolean isDescendantOf(DN dn) {
+    assert debugEnter(CLASS_NAME, "isDescendantOf", String
+        .valueOf(dn));
 
-    int offset = numComponents - dn.numComponents;
-    if (offset < 0)
-    {
+    ensureNotNull(dn);
+
+    int diff = numComponents - dn.numComponents;
+    if (diff < 0) {
       return false;
     }
 
-    for (int i=0; i < dn.numComponents; i++)
-    {
-      if (! rdnComponents[i+offset].equals(dn.rdnComponents[i]))
-      {
+    for (int i = 0; i < dn.numComponents; i++) {
+      if (!getRDN(i + diff).equals(dn.getRDN(i))) {
         return false;
       }
     }
@@ -400,25 +569,23 @@
    * (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));
 
-    int offset = dn.numComponents - numComponents;
-    if (offset < 0)
-    {
+    ensureNotNull(dn);
+
+    int diff = dn.numComponents - numComponents;
+    if (diff < 0) {
       return false;
     }
 
-    for (int i=0; i < numComponents; i++)
-    {
-      if (! rdnComponents[i].equals(dn.rdnComponents[i+offset]))
-      {
+    for (int i = 0; i < numComponents; i++) {
+      if (!getRDN(i).equals(dn.getRDN(i + diff))) {
         return false;
       }
     }
@@ -429,2334 +596,26 @@
 
 
   /**
-   * 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);
-    }
-  }
-
-
-
-  /**
-   * Creates a duplicate of this DN that can be modified without
-   * impacting this DN.
-   *
-   * @return  A duplicate of this DN that can be modified without
-   *          impacting this DN.
-   */
-  public DN duplicate()
-  {
-    assert debugEnter(CLASS_NAME, "duplicate");
-
-    RDN[] rdnCopy = new RDN[numComponents];
-    for (int i=0; i < numComponents; i++)
-    {
-      rdnCopy[i] = rdnComponents[i].duplicate();
-    }
-
-    return new DN(rdnCopy);
-  }
-
-
-
-  /**
-   * Indicates whether the provided object is equal to this DN.  In
+   * 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;
-    }
-
-    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);
-
+    } else if (o instanceof DN) {
+      DN other = (DN) o;
+      return normalizedDN.equals(other.normalizedDN);
+    } else {
       return false;
     }
   }
@@ -2764,13 +623,12 @@
 
 
   /**
-   * 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();
@@ -2781,34 +639,14 @@
   /**
    * 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");
 
-    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;
+    StringBuilder builder = new StringBuilder();
+    toString(builder);
+    return builder.toString();
   }
 
 
@@ -2817,15 +655,23 @@
    * 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");
 
-    buffer.append(toString());
+    ensureNotNull(buffer);
+
+    if (numComponents != 0) {
+      getRDN(0).toString(buffer);
+
+      for (int i = 1; i < numComponents; i++) {
+        buffer.append(",");
+        getRDN(i).toString(buffer);
+      }
+    }
   }
 
 
@@ -2833,33 +679,11 @@
   /**
    * 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;
   }
 
@@ -2869,15 +693,16 @@
    * 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");
 
-    buffer.append(toNormalizedString());
+    ensureNotNull(buffer);
+
+    buffer.append(normalizedDN);
   }
 
 
@@ -2887,54 +712,69 @@
    * 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(dn));
+  public int compareTo(DN dn) {
+    assert debugEnter(CLASS_NAME, "compareTo", String.valueOf(this),
+        String.valueOf(dn));
 
-    if (equals(dn))
-    {
-      return 0;
-    }
-    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;
+    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;
       }
 
-      return 0;
+      index1--;
+      index2--;
+    }
+  }
+
+
+
+  /**
+   * Construct the normalized form of this DN.
+   *
+   * @return Returns the normalized string representation of this DN.
+   */
+  private String normalize() {
+    if (numComponents == 0) {
+      return "";
+    } else {
+      StringBuilder buffer = new StringBuilder();
+      getRDN(0).toNormalizedString(buffer);
+
+      for (int i = 1; i < numComponents; i++) {
+        buffer.append(',');
+        getRDN(i).toNormalizedString(buffer);
+      }
+
+      return buffer.toString();
     }
   }
 }
-
diff --git a/opends/src/server/org/opends/server/types/DNComparator.java b/opends/src/server/org/opends/server/types/DNComparator.java
deleted file mode 100644
index d7655fd..0000000
--- a/opends/src/server/org/opends/server/types/DNComparator.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * You can obtain a copy of the license at
- * trunk/opends/resource/legal-notices/OpenDS.LICENSE
- * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at
- * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
- * add the following below this CDDL HEADER, with the fields enclosed
- * by brackets "[]" replaced with your own identifying * information:
- *      Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
- */
-package org.opends.server.types;
-
-
-
-import java.util.Comparator;
-
-import static org.opends.server.loggers.Debug.*;
-
-
-
-/**
- * This class defines a <CODE>Comparator</CODE> object that may be
- * used to compare DNs, particularly for inclusion in a sorted list.
- * The DNs will be compared first by hierarchy (so a child will always
- * be ordered after its parent) and then using RDN comparators for DN
- * components from suffix to leaf.
- */
-public class DNComparator
-       implements Comparator<DN>
-{
-  /**
-   * The fully-qualified name of this class for debugging purposes.
-   */
-  private static final String CLASS_NAME =
-       "org.opends.server.types.DNComparator";
-
-
-
-  // The RDN comparator that will be used to compare RDN components.
-  private RDNComparator rdnComparator;
-
-
-
-  /**
-   * Creates a new instance of this DN comparator.
-   */
-  public DNComparator()
-  {
-    assert debugConstructor(CLASS_NAME);
-
-    rdnComparator = new RDNComparator();
-  }
-
-
-
-  /**
-   * Compares the provided DNs and returns an integer value that
-   * reflects the relative order between them.
-   *
-   * @param  dn1  The first DN to compare.
-   * @param  dn2  The second DN to compare.
-   *
-   * @return  A negative value if the first DN should come before the
-   *          second in an ordered list, a positive value if they
-   *          first DN should come after the second in an ordered
-   *          list, or zero if there is no difference between their
-   *          order (i.e., the DNs are equal).
-   */
-  public int compare(DN dn1, DN dn2)
-  {
-    assert debugEnter(CLASS_NAME, "compare", String.valueOf(dn1),
-                      String.valueOf(dn2));
-
-    RDN[] rdns1 = dn1.getRDNComponents();
-    RDN[] rdns2 = dn2.getRDNComponents();
-
-    int index1 = rdns1.length - 1;
-    int index2 = rdns2.length - 1;
-
-    while (true)
-    {
-      if (index1 >= 0)
-      {
-        if (index2 >= 0)
-        {
-          int value = rdnComparator.compare(rdns1[index1],
-                                            rdns2[index2]);
-          if (value != 0)
-          {
-            return value;
-          }
-        }
-        else
-        {
-          return 1;
-        }
-      }
-      else if (index2 >= 0)
-      {
-        return -1;
-      }
-      else
-      {
-        return 0;
-      }
-
-      index1--;
-      index2--;
-    }
-  }
-}
-
diff --git a/opends/src/server/org/opends/server/types/Entry.java b/opends/src/server/org/opends/server/types/Entry.java
index f2d9f54..6235eea 100644
--- a/opends/src/server/org/opends/server/types/Entry.java
+++ b/opends/src/server/org/opends/server/types/Entry.java
@@ -143,7 +143,7 @@
 
     if (dn == null)
     {
-      this.dn = new DN(new ArrayList<RDN>(0));
+      this.dn = DN.nullDN();
     }
     else
     {
@@ -207,7 +207,7 @@
 
     if (dn == null)
     {
-      this.dn = new DN(new ArrayList<RDN>(0));
+      this.dn = DN.nullDN();
     }
     else
     {
@@ -2352,8 +2352,10 @@
             }
 
             // Make sure that all attributes in the RDN are allowed.
-            for (AttributeType t : rdn.getAttributeTypes())
+            int numAVAs = rdn.getNumValues();
+            for (int i = 0; i < numAVAs; i++)
             {
+              AttributeType t = rdn.getAttributeType(i);
               if (! nameForm.isRequiredOrOptional(t))
               {
                 int msgID = MSGID_ENTRY_SCHEMA_RDN_DISALLOWED_ATTR;
@@ -2408,7 +2410,7 @@
             else
             {
               // Get the DN of the parent entry if possible.
-              DN parentDN = dn.getParent();
+              DN parentDN = dn.getParentDNInSuffix();
               if (parentDN != null)
               {
                 // Get the parent entry and check its structural
@@ -2768,8 +2770,6 @@
   {
     assert debugEnter(CLASS_NAME, "duplicate");
 
-    DN dnCopy = dn.duplicate();
-
     HashMap<ObjectClass,String> objectClassesCopy =
          new HashMap<ObjectClass,String>(objectClasses);
 
@@ -2783,7 +2783,7 @@
                   operationalAttributes.size());
     deepCopy(operationalAttributes, operationalAttrsCopy, false);
 
-    return new Entry(dnCopy, objectClassesCopy, userAttrsCopy,
+    return new Entry(dn, objectClassesCopy, userAttrsCopy,
                      operationalAttrsCopy);
   }
 
@@ -2806,8 +2806,6 @@
   {
     assert debugEnter(CLASS_NAME, "duplicate");
 
-    DN dnCopy = dn.duplicate();
-
     HashMap<ObjectClass,String> objectClassesCopy;
     if (typesOnly)
     {
@@ -2838,7 +2836,7 @@
     HashMap<AttributeType,List<Attribute>> operationalAttrsCopy =
          new HashMap<AttributeType,List<Attribute>>(0);
 
-    return new Entry(dnCopy, objectClassesCopy, userAttrsCopy,
+    return new Entry(dn, objectClassesCopy, userAttrsCopy,
                      operationalAttrsCopy);
   }
 
@@ -2890,8 +2888,6 @@
   {
     assert debugEnter(CLASS_NAME, "duplicate");
 
-    DN dnCopy = dn.duplicate();
-
     HashMap<ObjectClass,String> objectClassesCopy =
          new HashMap<ObjectClass,String>(objectClasses.size());
 
@@ -2903,7 +2899,7 @@
          new HashMap<AttributeType,List<Attribute>>(
                   operationalAttributes.size());
 
-    return new Entry(dnCopy, objectClassesCopy, userAttrsCopy,
+    return new Entry(dn, objectClassesCopy, userAttrsCopy,
                      operationalAttrsCopy);
   }
 
@@ -3216,7 +3212,7 @@
 
       case SINGLE_LEVEL:
         // The parent DN for this entry must equal the base DN.
-        return baseDN.equals(dn.getParent());
+        return baseDN.equals(dn.getParentDNInSuffix());
 
       case WHOLE_SUBTREE:
         // The base DN must be an ancestor of the entry DN.
diff --git a/opends/src/server/org/opends/server/types/LDAPURL.java b/opends/src/server/org/opends/server/types/LDAPURL.java
index 38aa628..849330d 100644
--- a/opends/src/server/org/opends/server/types/LDAPURL.java
+++ b/opends/src/server/org/opends/server/types/LDAPURL.java
@@ -76,7 +76,7 @@
   /**
    * The default base DN that will be used if none is provided.
    */
-  public static final DN DEFAULT_BASE_DN = new DN();
+  public static final DN DEFAULT_BASE_DN = DN.nullDN();
 
 
 
diff --git a/opends/src/server/org/opends/server/types/RDN.java b/opends/src/server/org/opends/server/types/RDN.java
index 9fc3a9b..548f0cf 100644
--- a/opends/src/server/org/opends/server/types/RDN.java
+++ b/opends/src/server/org/opends/server/types/RDN.java
@@ -28,18 +28,23 @@
 
 
 
+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.TreeSet;
+import java.util.TreeMap;
 
+import org.opends.server.api.OrderingMatchingRule;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.protocols.asn1.ASN1OctetString;
-
-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.*;
+import org.opends.server.util.StaticUtils;
 
 
 
@@ -47,57 +52,55 @@
  * 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 class RDN
-       implements Comparable<RDN>
-{
+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.
+
   /**
    * 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 AttributeType[] attributeTypes;
-
-  // The set of values for the elements in this RDN.
-  private AttributeValue[] attributeValues;
-
-  // 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;
+  private final AttributeType[] attributeTypes;
 
   // The set of user-provided names for the attributes in this RDN.
-  private String[] attributeNames;
+  private final String[] attributeNames;
+
+  // The set of values for the elements in this RDN.
+  private final 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.
+   */
+  private String normalizedRDN;
 
 
 
   /**
    * Creates a new RDN with the provided information.
    *
-   * @param  attributeType   The attribute type for this RDN.
-   * @param  attributeValue  The value for this RDN.
+   * @param type
+   *          The attribute type for this RDN.
+   * @param value
+   *          The value for this RDN.
+   * @return Returns the new RDN.
    */
-  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;
+  public static RDN create(AttributeType type, AttributeValue value) {
+    return create(type, type.getNameOrOID(), value);
   }
 
 
@@ -105,85 +108,414 @@
   /**
    * Creates a new RDN with the provided information.
    *
-   * @param  attributeType   The attribute type for this RDN.
-   * @param  attributeName   The user-provided name for this RDN.
-   * @param  attributeValue  The value for this RDN.
+   * @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.
    */
-  public RDN(AttributeType attributeType, String attributeName,
-             AttributeValue attributeValue)
-  {
-    assert debugConstructor(CLASS_NAME, String.valueOf(attributeType),
-                            String.valueOf(attributeName),
-                            String.valueOf(attributeValue));
+  public static RDN create(AttributeType type, String name,
+      AttributeValue value) {
+    ensureNotNull(type, name, value);
 
-    attributeTypes  = new AttributeType[] { attributeType };
-    attributeNames  = new String[] { attributeName };
-    attributeValues = new AttributeValue[] { attributeValue };
+    AttributeType[] types = new AttributeType[] { type };
+    String[] names = new String[] { name };
+    AttributeValue[] values = new AttributeValue[] { value };
 
-    numValues     = 1;
-    rdnString     = null;
-    normalizedRDN = null;
+    return new RDN(types, names, values);
   }
 
 
 
   /**
-   * Creates a new RDN with the provided information.  The number of
-   * type, name, and value elements must be nonzero and equal.
+   * Create a new RDN builder which can be used to incrementally build
+   * a new RDN.
    *
-   * @param  attributeTypes   The set of attribute types for this RDN.
-   * @param  attributeNames   The set of user-provided names for this
-   *                          RDN.
-   * @param  attributeValues  The set of values for this RDN.
+   * @return Returns the new RDN 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;
+  public static Builder createBuilder() {
+    return new Builder();
   }
 
 
 
   /**
-   * 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.
-   * @param  attributeNames   The set of user-provided names for this
-   *                          RDN.
-   * @param  attributeValues  The set of values for this RDN.
+   * This class provides an interface for constructing RDNs
+   * incrementally.
+   * <p>
+   * Typically, an application will construct a new
+   * <code>Builder</code> and append attribute value assertions
+   * (AVAs) using the <code>append</code> method. When the RDN is
+   * fully constructed, it can be retrieved using the
+   * <code>getInstance</code> method.
    */
-  public RDN(AttributeType[] attributeTypes, String[] attributeNames,
-             AttributeValue[] attributeValues)
-  {
-    assert debugConstructor(CLASS_NAME,
-                            String.valueOf(attributeTypes),
-                            String.valueOf(attributeNames),
-                            String.valueOf(attributeValues));
+  public static final class Builder {
+    // The list of attribute types.
+    private List<AttributeType> attributeTypes;
 
-    this.numValues       = attributeTypes.length;
-    this.attributeTypes  = attributeTypes;
-    this.attributeNames  = attributeNames;
-    this.attributeValues = attributeValues;
+    // The list of user-provided attribute names.
+    private List<String> attributeNames;
 
-    rdnString     = null;
-    normalizedRDN = null;
+    // 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);
+    }
+  }
+
+
+
+  /**
+   * 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.
+   */
+  private RDN(AttributeType[] types, String[] names,
+      AttributeValue[] values) {
+    assert debugConstructor(CLASS_NAME, String.valueOf(types), String
+        .valueOf(names), String.valueOf(values));
+
+    this.attributeTypes = types;
+    this.attributeNames = names;
+    this.attributeValues = values;
   }
 
 
@@ -192,29 +524,89 @@
    * 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 numValues;
+    return attributeTypes.length;
   }
 
 
 
   /**
-   * Retrieves the set of attribute types for this RDN.  The returned
-   * array must not be modified by the caller.
+   * Retrieves the attribute type at the specified AVA in this RDN.
    *
-   * @return  The set of attribute types for 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[] getAttributeTypes()
-  {
-    assert debugEnter(CLASS_NAME, "getAttributeTypes");
+  public AttributeType getAttributeType(int index)
+      throws IndexOutOfBoundsException {
+    assert debugEnter(CLASS_NAME, "getAttributeType");
 
-    return attributeTypes;
+    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];
   }
 
 
@@ -222,21 +614,19 @@
   /**
    * 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));
 
-    for (AttributeType t : attributeTypes)
-    {
-      if (t.equals(attributeType))
-      {
+    ensureNotNull(attributeType);
+
+    for (AttributeType t : attributeTypes) {
+      if (t.equals(attributeType)) {
         return true;
       }
     }
@@ -247,91 +637,28 @@
 
 
   /**
-   * 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 set of user-provided names for this RDN.  The
-   * returned array must not be modified by the caller.
-   *
-   * @return  The set of user-provided names for this RDN.
-   */
-  public String[] getAttributeNames()
-  {
-    assert debugEnter(CLASS_NAME, "getAttributeNames");
-
-    return attributeNames;
-  }
-
-
-
-  /**
-   * Retrieves the set of attribute values for this RDN.  The returned
-   * list must not be modified by the caller.
-   *
-   * @return  The set of attribute values for this RDN.
-   */
-  public AttributeValue[] getAttributeValues()
-  {
-    assert debugEnter(CLASS_NAME, "getAttributeValues");
-
-    return attributeValues;
-  }
-
-
-
-  /**
    * 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));
 
-    for (int i=0; i < numValues; i++)
-    {
-      if (attributeTypes[i].equals(attributeType))
-      {
+    ensureNotNull(attributeType);
+
+    for (int i = 0; i < attributeTypes.length; i++) {
+      if (attributeTypes[i].equals(attributeType)) {
         return attributeValues[i];
       }
     }
@@ -344,252 +671,57 @@
   /**
    * 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 (numValues > 1);
+    return (attributeTypes.length > 1);
   }
 
 
 
   /**
-   * Indicates whether this RDN contains the specified type-value
-   * pair.
+   * 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)}.
    *
-   * @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.
+   * @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.
    */
-  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;
+  public static RDN valueOf(String s) throws DirectoryException {
+    return decode(s);
   }
 
 
 
   /**
-   * Adds the provided type-value pair from this RDN.
+   * Decodes 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).
+   * @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.
    */
-  public boolean addValue(AttributeType type, String name,
-                          AttributeValue value)
-  {
-    assert debugEnter(CLASS_NAME, "addValue", String.valueOf(type),
-                      String.valueOf(name), String.valueOf(value));
+  public static RDN decode(ByteString rdnString)
+      throws DirectoryException {
+    assert debugEnter(CLASS_NAME, "decode",
+        String.valueOf(rdnString));
 
-    for (int i=0; i < numValues; i++)
-    {
-      if (attributeTypes[i].equals(type) &&
-          attributeValues[i].equals(value))
-      {
-        return false;
-      }
-    }
+    ensureNotNull(rdnString);
 
-    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;
-  }
-
-
-
-  /**
-   * Removes the provided type-value pair from this RDN.
-   *
-   * @param  type   The attribute type of the pair to remove.
-   * @param  value  The attribute value of the pair to remove.
-   *
-   * @return  <CODE>true</CODE> if the type-value pair was found and
-   *          removed from this RDN, or <CODE>false</CODE> if it was
-   *          not.
-   */
-  public boolean removeValue(AttributeType type, AttributeValue value)
-  {
-    assert debugEnter(CLASS_NAME, "removeValue", String.valueOf(type),
-                      String.valueOf(value));
-
-    for (int i=0; i < numValues; i++)
-    {
-      if (attributeTypes[i].equals(type) &&
-          attributeValues[i].equals(value))
-      {
-        numValues--;
-
-        if (numValues == 0)
-        {
-          attributeTypes  = new AttributeType[0];
-          attributeNames  = new String[0];
-          attributeValues = new AttributeValue[0];
-        }
-        else if (i == 0)
-        {
-          AttributeType[] newTypes = new AttributeType[numValues];
-          System.arraycopy(attributeTypes, 1, newTypes, 0, numValues);
-          attributeTypes = newTypes;
-
-          String[] newNames = new String[numValues];
-          System.arraycopy(attributeNames, 1, newNames, 0, numValues);
-          attributeNames = newNames;
-
-          AttributeValue[] newValues = new AttributeValue[numValues];
-          System.arraycopy(attributeValues, 1, newValues, 0,
-                           numValues);
-          attributeValues = newValues;
-        }
-        else if (i == numValues)
-        {
-          AttributeType[] newTypes = new AttributeType[numValues];
-          System.arraycopy(attributeTypes, 0, newTypes, 0, numValues);
-          attributeTypes = newTypes;
-
-          String[] newNames = new String[numValues];
-          System.arraycopy(attributeNames, 0, newNames, 0, numValues);
-          attributeNames = newNames;
-
-          AttributeValue[] newValues = new AttributeValue[numValues];
-          System.arraycopy(attributeValues, 0, newValues, 0,
-                           numValues);
-          attributeValues = newValues;
-        }
-        else
-        {
-          int remaining = numValues - i;
-
-          AttributeType[] newTypes = new AttributeType[numValues];
-          System.arraycopy(attributeTypes, 0, newTypes, 0, i);
-          System.arraycopy(attributeTypes, i+1, newTypes, i,
-                           remaining);
-          attributeTypes = newTypes;
-
-          String[] newNames = new String[numValues];
-          System.arraycopy(attributeNames, 0, newNames, 0, i);
-          System.arraycopy(attributeNames, i+1, newNames, i,
-                           remaining);
-          attributeNames = newNames;
-
-          AttributeValue[] newValues = new AttributeValue[numValues];
-          System.arraycopy(attributeValues, 0, newValues, 0, i);
-          System.arraycopy(attributeValues, i+1, newValues, i,
-                           remaining);
-          attributeValues = newValues;
-        }
-
-        rdnString     = null;
-        normalizedRDN = null;
-
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-
-
-  /**
-   * Replaces the set of values for this RDN with the provided
-   * name-value pair.
-   *
-   * @param  type   The attribute type for this RDN.
-   * @param  name   The user-provided name for this RDN.
-   * @param  value  The attribute value for this RDN.
-   */
-  public void replaceValues(AttributeType type, String name,
-                            AttributeValue value)
-  {
-    assert debugEnter(CLASS_NAME, "replaceValues",
-                      String.valueOf(type), String.valueOf(name),
-                      String.valueOf(value));
-
-    attributeTypes  = new AttributeType[] { type };
-    attributeNames  = new String[] { name };
-    attributeValues = new AttributeValue[] { value };
-
-    numValues     = 1;
-    rdnString     = null;
-    normalizedRDN = null;
-  }
-
-
-
-  /**
-   * Replaces the set of values for this RDN with the provided set of
-   * name-value pairs.  The number of elements in each list must be
-   * equal and greater than one.
-   *
-   * @param  attributeTypes   The set of attribute types for this RDN.
-   * @param  attributeNames   The set of user-provided names for this
-   *                          RDN.
-   * @param  attributeValues  The set of values for this RDN.
-   */
-  public void replaceValues(ArrayList<AttributeType> attributeTypes,
-                            ArrayList<String> attributeNames,
-                            ArrayList<AttributeValue> attributeValues)
-  {
-    assert debugEnter(CLASS_NAME, "replaceValues",
-                      String.valueOf(attributeTypes),
-                      String.valueOf(attributeNames),
-                      String.valueOf(attributeValues));
-
-    this.attributeTypes = new AttributeType[attributeTypes.size()];
-    attributeTypes.toArray(this.attributeTypes);
-
-    this.attributeNames = new String[attributeNames.size()];
-    attributeNames.toArray(this.attributeNames);
-
-    this.attributeValues = new AttributeValue[attributeValues.size()];
-    attributeValues.toArray(this.attributeValues);
-
-    numValues     = attributeTypes.size();
-    rdnString     = null;
-    normalizedRDN = null;
+    // Use string-based decoder.
+    return decode(rdnString.stringValue());
   }
 
 
@@ -597,477 +729,78 @@
   /**
    * 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);
 
-    // 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);
-    }
+    // Use an RDN builder to parse the string.
+    Builder builder = createBuilder();
+    int pos = builder.parse(rdnString, 0, false);
 
-    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;
+    // Make sure that the string did not contain any trailing RDNs.
+    if (pos != -1) {
+      int msgID = MSGID_RDN_UNEXPECTED_COMMA;
       String message = getMessage(msgID, rdnString, pos);
       throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
-                                   message, msgID);
+          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);
-    }
-
-
-    // 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);
-      }
-    }
+    // Return the parsed RDN instance.
+    return builder.getInstance();
   }
 
 
 
   /**
-   * Creates a duplicate of this RDN that can be modified without
-   * impacting this RDN.
+   * 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).
    *
-   * @return  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.
    */
-  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)
-  {
+  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;
 
-    if ((o == null) || (! (o instanceof RDN)))
-    {
+      String nvalue1 = toNormalizedString();
+      String nvalue2 = other.toNormalizedString();
+      return nvalue1.equals(nvalue2);
+    } else {
       return false;
     }
-
-    RDN rdn = (RDN) o;
-    if (numValues != rdn.numValues)
-    {
-      return false;
-    }
-
-    for (int i=0; i < numValues; i++)
-    {
-      if ((! attributeTypes[i].equals(rdn.attributeTypes[i])) ||
-          (! attributeValues[i].equals(rdn.attributeValues[i])))
-      {
-        return false;
-      }
-    }
-
-    return true;
   }
 
 
 
   /**
-   * Retrieves the hash code for this RDN.  It will be calculated as
-   * the sum of the hash codes of the types and values.
+   * Retrieves the hash code for this RDN. It will be calculated as
+   * the hash code of the RDN's normalized string representation.
    *
-   * @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");
 
-    int hashCode = 0;
-
-    for (int i=0; i < numValues; i++)
-    {
-      hashCode += attributeTypes[i].hashCode() +
-                  attributeValues[i].hashCode();
-    }
-
-    return hashCode;
+    return toNormalizedString().hashCode();
   }
 
 
@@ -1075,30 +808,14 @@
   /**
    * Retrieves a string representation of this RDN.
    *
-   * @return  A string representation of this RDN.
+   * @return A string representation of this RDN.
    */
-  public String toString()
-  {
-    if (rdnString == null)
-    {
-      StringBuilder buffer = new StringBuilder();
+  public String toString() {
+    assert debugEnter(CLASS_NAME, "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;
+    StringBuilder buffer = new StringBuilder();
+    toString(buffer);
+    return buffer.toString();
   }
 
 
@@ -1107,15 +824,29 @@
    * 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");
 
-    buffer.append(toString());
+    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);
+    }
   }
 
 
@@ -1123,14 +854,38 @@
   /**
    * 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 buffer = new StringBuilder();
-      toNormalizedString(buffer);
+  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();
     }
 
     return normalizedRDN;
@@ -1142,143 +897,899 @@
    * 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");
 
-    if (normalizedRDN != null)
-    {
-      buffer.append(normalizedRDN);
-      return;
+    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);
     }
 
-    boolean bufferEmpty = (buffer.length() == 0);
+    // We have at least one multi-valued RDNs, so we need to sort.
+    TreeMap<String, Integer> map1;
+    TreeMap<String, Integer> map2;
 
-    if (attributeNames.length == 1)
-    {
-      toLowerCase(attributeNames[0], buffer);
-      buffer.append('=');
+    map1 = new TreeMap<String, Integer>();
+    map2 = new TreeMap<String, Integer>();
 
-      try
-      {
-        buffer.append(
-             attributeValues[0].getNormalizedDNStringValue());
-      }
-      catch (Exception e)
-      {
-        assert debugException(CLASS_NAME, "toNormalizedString", e);
-
-        buffer.append(attributeValues[0].getStringValue());
-      }
+    for (int i = 0; i < attributeTypes.length; i++) {
+      map1.put(attributeTypes[i].getNameOrOID(), i);
     }
-    else
-    {
-      TreeSet<String> rdnElementStrings = new TreeSet<String>();
 
-      for (int i=0; i < attributeNames.length; i++)
-      {
-        StringBuilder b2 = new StringBuilder();
-        toLowerCase(attributeNames[i], b2);
-        b2.append('=');
+    for (int i = 0; i < rdn.attributeTypes.length; i++) {
+      map2.put(rdn.attributeTypes[i].getNameOrOID(), i);
+    }
 
-        try
-        {
-          b2.append(attributeValues[i].getNormalizedStringValue());
-        }
-        catch (Exception e)
-        {
-          assert debugException(CLASS_NAME, "toNormalizedString", e);
+    // Now compare the sorted AVAs.
+    Iterator<Integer> i1= map1.values().iterator();
+    Iterator<Integer> i2 = map2.values().iterator();
 
-          b2.append(attributeValues[i].getStringValue());
-        }
+    while (i1.hasNext() && i2.hasNext()) {
+      int int1 = i1.next();
+      int int2 = i2.next();
 
-        rdnElementStrings.add(b2.toString());
-      }
+      AttributeType type1 = attributeTypes[int1];
+      AttributeType type2 = rdn.attributeTypes[int2];
 
-      Iterator<String> iterator = rdnElementStrings.iterator();
-      buffer.append(iterator.next());
+      AttributeValue value1 = attributeValues[int1];
+      AttributeValue value2 = rdn.attributeValues[int2];
 
-      while (iterator.hasNext())
-      {
-        buffer.append('+');
-        buffer.append(iterator.next());
+      int rc = compareAVA(type1, value1, type2, value2);
+      if (rc != 0) {
+        return rc;
       }
     }
 
-    if (bufferEmpty)
-    {
-      normalizedRDN = buffer.toString();
+    // 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;
     }
   }
 
 
 
   /**
-   * Compares this RDN with the provided RDN based on an alphabetic
-   * comparison of the attribute names and values.
+   * Compare two AVAs for order.
    *
-   * @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.
+   * @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.
    */
-  public int compareTo(RDN rdn)
-  {
-    assert debugEnter(CLASS_NAME, "compareTo", String.valueOf(rdn));
+  private int compareAVA(AttributeType type1, AttributeValue value1,
+      AttributeType type2, AttributeValue value2) {
+    if (type1.equals(type2)) {
+      OrderingMatchingRule rule = type1.getOrderingMatchingRule();
 
-    if (equals(rdn))
-    {
-      return 0;
-    }
+      try {
+        if (rule != null) {
+          byte[] b1 = value1.getNormalizedValueBytes();
+          byte[] b2 = value2.getNormalizedValueBytes();
 
-    int minValues = Math.min(numValues, rdn.numValues);
-    for (int i=0; i < minValues; i++)
-    {
-      String n1 = attributeNames[i].toLowerCase();
-      String n2 = rdn.attributeNames[i].toLowerCase();
+          return rule.compare(b1, b2);
+        } else {
+          byte[] b1 = value1.getNormalizedValue().value();
+          byte[] b2 = value2.getNormalizedValue().value();
 
-      int result = n1.compareTo(n2);
-      if (result != 0)
-      {
-        return result;
-      }
-
-      try
-      {
-        String v1 = attributeValues[i].getNormalizedStringValue();
-        String v2 = rdn.attributeValues[i].getNormalizedStringValue();
-
-        result = v1.compareTo(v2);
-        if (result != 0)
-        {
-          return result;
+          return StaticUtils.compare(b1, b2);
         }
-      }
-      catch (Exception e)
-      {
-        assert debugException(CLASS_NAME, "compareTo", e);
+      } catch (Exception e) {
+        assert debugException(CLASS_NAME, "compareAVA", e);
 
-        return 0;
-      }
-    }
+        // Just get the raw values and do a comparison between them.
+        byte[] b1 = value1.getValue().value();
+        byte[] b2 = value2.getValue().value();
 
-    if (numValues > minValues)
-    {
-      return 1;
-    }
-    else if (rdn.numValues > minValues)
-    {
-      return -1;
-    }
-    else
-    {
-      return 0;
+        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) {
+      return;
+    }
+
+    // Assume 1-byte UTF8 and that no quoting will be required.
+    buffer.ensureCapacity(buffer.length() + length);
+
+    // Quote leading space or #.
+    char c = value.charAt(0);
+    if (c == ' ' || c == '#') {
+      buffer.append('\\');
+      buffer.append(c);
+    } else {
+      quoteChar(buffer, c);
+    }
+
+    // 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);
+      }
+    }
+  }
+
+
+
+  /**
+   * Encode a single attribute value from an RDN according to the DN
+   * string encoding rules.
+   *
+   * @param buffer
+   *          Append the character to this buffer.
+   * @param c
+   *          The character to be encoded.
+   */
+  private void quoteChar(StringBuilder buffer, char c) {
+    if ((c < ' ') || (c > '~')) {
+      for (byte b : getBytes(String.valueOf(c))) {
+        buffer.append('\\');
+        buffer.append(byteToLowerHex(b));
+      }
+    } else {
+      switch (c) {
+      case ',':
+      case '+':
+      case '"':
+      case '\\':
+      case '<':
+      case '>':
+      case ';':
+        buffer.append('\\');
+      }
+
+      buffer.append(c);
+    }
+  }
+
+
+
+  /**
+   * Parses an attribute name from the provided DN string starting at
+   * the specified location.
+   *
+   * @param dnString
+   *          The DN string to be parsed.
+   * @param pos
+   *          The position at which to start parsing the attribute
+   *          name.
+   * @param attributeName
+   *          The buffer to which to append the parsed attribute name.
+   * @return The position of the first character that is not part of
+   *         the attribute name.
+   * @throws DirectoryException
+   *           If it was not possible to parse a valid attribute name
+   *           from the provided DN string.
+   */
+  private static int parseAttributeName(String dnString, int pos,
+      StringBuilder attributeName) throws DirectoryException {
+    assert debugEnter(CLASS_NAME, "parseAttributeName", String
+        .valueOf(dnString), String.valueOf(pos),
+        "java.lang.StringBuilder");
+    boolean allowExceptions = DirectoryServer
+        .allowAttributeNameExceptions();
+
+    int length = dnString.length();
+
+    // Skip over any leading spaces.
+    if (pos < length) {
+      while (dnString.charAt(pos) == ' ') {
+        pos++;
+        if (pos == length) {
+          // This means that the remainder of the DN was completely
+          // comprised of spaces. If we have gotten here, then we
+          // know that there is at least one RDN component, and
+          // therefore the last non-space character of the DN must
+          // have been a comma. This is not acceptable.
+          int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_COMMA;
+          String message = getMessage(msgID, dnString);
+          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+              message, msgID);
+        }
+      }
+    }
+
+    // Next, we should find the attribute name for this RDN component.
+    // It may either be a name (with only letters, digits, and dashes
+    // and starting with a letter) or an OID (with only digits and
+    // periods, optionally prefixed with "oid."), and there is also a
+    // special case in which we will allow underscores. Because of
+    // the complexity involved, read the entire name first with
+    // minimal validation and then do more thorough validation later.
+    boolean checkForOID = false;
+    boolean endOfName = false;
+    while (pos < length) {
+      // To make the switch more efficient, we'll include all ASCII
+      // characters in the range of allowed values and then reject the
+      // ones that aren't allowed.
+      char c = dnString.charAt(pos);
+      switch (c) {
+      case ' ':
+        // This should denote the end of the attribute name.
+        endOfName = true;
+        break;
+
+      case '!':
+      case '"':
+      case '#':
+      case '$':
+      case '%':
+      case '&':
+      case '\'':
+      case '(':
+      case ')':
+      case '*':
+      case '+':
+      case ',':
+        // None of these are allowed in an attribute name or any
+        // character immediately following it.
+        int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+        String message = getMessage(msgID, dnString, c, pos);
+        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+            message, msgID);
+
+      case '-':
+        // This will be allowed as long as it isn't the first
+        // character in the attribute name.
+        if (attributeName.length() > 0) {
+          attributeName.append(c);
+        } else {
+          msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DASH;
+          message = getMessage(msgID, dnString, c);
+          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+              message, msgID);
+        }
+        break;
+
+      case '.':
+        // The period could be allowed if the attribute name is
+        // actually expressed as an OID. We'll accept it for now,
+        // but make sure to check it later.
+        attributeName.append(c);
+        checkForOID = true;
+        break;
+
+      case '/':
+        // This is not allowed in an attribute name or any character
+        // immediately following it.
+        msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+        message = getMessage(msgID, dnString, c, pos);
+        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+            message, msgID);
+
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+      case '6':
+      case '7':
+      case '8':
+      case '9':
+        // Digits are always allowed if they are not the first
+        // character. However, they may be allowed if they are the
+        // first character if the valid is an OID or if the
+        // attribute name exceptions option is enabled. Therefore,
+        // we'll accept it now and check it later.
+        attributeName.append(c);
+        break;
+
+      case ':':
+      case ';': // NOTE: attribute options are not allowed in a DN.
+      case '<':
+        // None of these are allowed in an attribute name or any
+        // character immediately following it.
+        msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+        message = getMessage(msgID, dnString, c, pos);
+        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+            message, msgID);
+
+      case '=':
+        // This should denote the end of the attribute name.
+        endOfName = true;
+        break;
+
+      case '>':
+      case '?':
+      case '@':
+        // None of these are allowed in an attribute name or any
+        // character immediately following it.
+        msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+        message = getMessage(msgID, dnString, c, pos);
+        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+            message, msgID);
+
+      case 'A':
+      case 'B':
+      case 'C':
+      case 'D':
+      case 'E':
+      case 'F':
+      case 'G':
+      case 'H':
+      case 'I':
+      case 'J':
+      case 'K':
+      case 'L':
+      case 'M':
+      case 'N':
+      case 'O':
+      case 'P':
+      case 'Q':
+      case 'R':
+      case 'S':
+      case 'T':
+      case 'U':
+      case 'V':
+      case 'W':
+      case 'X':
+      case 'Y':
+      case 'Z':
+        // These will always be allowed.
+        attributeName.append(c);
+        break;
+
+      case '[':
+      case '\\':
+      case ']':
+      case '^':
+        // None of these are allowed in an attribute name or any
+        // character immediately following it.
+        msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+        message = getMessage(msgID, dnString, c, pos);
+        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+            message, msgID);
+
+      case '_':
+        // This will never be allowed as the first character. It
+        // may be allowed for subsequent characters if the attribute
+        // name exceptions option is enabled.
+        if (attributeName.length() == 0) {
+          msgID =
+            MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_UNDERSCORE;
+          message = getMessage(msgID, dnString,
+              ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
+          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+              message, msgID);
+        } else if (allowExceptions) {
+          attributeName.append(c);
+        } else {
+          msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_UNDERSCORE_CHAR;
+          message = getMessage(msgID, dnString,
+              ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
+          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+              message, msgID);
+        }
+        break;
+
+      case '`':
+        // This is not allowed in an attribute name or any character
+        // immediately following it.
+        msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+        message = getMessage(msgID, dnString, c, pos);
+        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+            message, msgID);
+
+      case 'a':
+      case 'b':
+      case 'c':
+      case 'd':
+      case 'e':
+      case 'f':
+      case 'g':
+      case 'h':
+      case 'i':
+      case 'j':
+      case 'k':
+      case 'l':
+      case 'm':
+      case 'n':
+      case 'o':
+      case 'p':
+      case 'q':
+      case 'r':
+      case 's':
+      case 't':
+      case 'u':
+      case 'v':
+      case 'w':
+      case 'x':
+      case 'y':
+      case 'z':
+        // These will always be allowed.
+        attributeName.append(c);
+        break;
+
+      default:
+        // This is not allowed in an attribute name or any character
+        // immediately following it.
+        msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR;
+        message = getMessage(msgID, dnString, c, pos);
+        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+            message, msgID);
+      }
+
+      if (endOfName) {
+        break;
+      }
+
+      pos++;
+    }
+
+    // We should now have the full attribute name. However, we may
+    // still need to perform some validation, particularly if the
+    // name contains a period or starts with a digit. It must also
+    // have at least one character.
+    if (attributeName.length() == 0) {
+      int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_NO_NAME;
+      String message = getMessage(msgID, dnString);
+      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+          message, msgID);
+    } else if (checkForOID) {
+      boolean validOID = true;
+
+      int namePos = 0;
+      int nameLength = attributeName.length();
+      char ch = attributeName.charAt(0);
+      if ((ch == 'o') || (ch == 'O')) {
+        if (nameLength <= 4) {
+          validOID = false;
+        } else {
+          if ((((ch = attributeName.charAt(1)) == 'i') || (ch == 'I'))
+              && (((ch = attributeName.charAt(2)) == 'd')
+                  || (ch == 'D'))
+              && (attributeName.charAt(3) == '.')) {
+            attributeName.delete(0, 4);
+            nameLength -= 4;
+          } else {
+            validOID = false;
+          }
+        }
+      }
+
+      while (validOID && (namePos < nameLength)) {
+        ch = attributeName.charAt(namePos++);
+        if (isDigit(ch)) {
+          while (validOID && (namePos < nameLength)
+              && isDigit(attributeName.charAt(namePos))) {
+            namePos++;
+          }
+
+          if ((namePos < nameLength)
+              && (attributeName.charAt(namePos) != '.')) {
+            validOID = false;
+          }
+        } else if (ch == '.') {
+          if ((namePos == 1)
+              || (attributeName.charAt(namePos - 2) == '.')) {
+            validOID = false;
+          }
+        } else {
+          validOID = false;
+        }
+      }
+
+      if (validOID && (attributeName.charAt(nameLength - 1) == '.')) {
+        validOID = false;
+      }
+
+      if (!validOID) {
+        int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_PERIOD;
+        String message = getMessage(msgID, dnString, attributeName
+            .toString());
+        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+            message, msgID);
+      }
+    } else if (isDigit(attributeName.charAt(0))
+        && (!allowExceptions)) {
+      int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DIGIT;
+      String message = getMessage(msgID, dnString, attributeName
+          .charAt(0), ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS);
+      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+          message, msgID);
+    }
+
+    return pos;
+  }
+
+
+
+  /**
+   * Parses the attribute value from the provided DN string starting
+   * at the specified location. When the value has been parsed, it
+   * will be assigned to the provided ASN.1 octet string.
+   *
+   * @param dnString
+   *          The DN string to be parsed.
+   * @param pos
+   *          The position of the first character in the attribute
+   *          value to parse.
+   * @param attributeValue
+   *          The ASN.1 octet string whose value should be set to the
+   *          parsed attribute value when this method completes
+   *          successfully.
+   * @return The position of the first character that is not part of
+   *         the attribute value.
+   * @throws DirectoryException
+   *           If it was not possible to parse a valid attribute value
+   *           from the provided DN string.
+   */
+  private static int parseAttributeValue(String dnString, int pos,
+      ByteString attributeValue) throws DirectoryException {
+    assert debugEnter(CLASS_NAME, "parseAttributeValue", String
+        .valueOf(dnString), String.valueOf(pos),
+        "java.lang.StringBuilder");
+
+    // All leading spaces have already been stripped so we can start
+    // reading the value. However, it may be empty so check for that.
+    int length = dnString.length();
+    if (pos >= length) {
+      attributeValue.setValue("");
+      return pos;
+    }
+
+    // Look at the first character. If it is an octothorpe (#), then
+    // that means that the value should be a hex string.
+    char c = dnString.charAt(pos++);
+    if (c == '#') {
+      // The first two characters must be hex characters.
+      StringBuilder hexString = new StringBuilder();
+      if ((pos + 2) > length) {
+        int msgID = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT;
+        String message = getMessage(msgID, dnString);
+        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+            message, msgID);
+      }
+
+      for (int i = 0; i < 2; i++) {
+        c = dnString.charAt(pos++);
+        if (isHexDigit(c)) {
+          hexString.append(c);
+        } else {
+          int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
+          String message = getMessage(msgID, dnString, c);
+          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+              message, msgID);
+        }
+      }
+
+      // The rest of the value must be a multiple of two hex
+      // characters. The end of the value may be designated by the
+      // end of the DN, a comma or semicolon, or a space.
+      while (pos < length) {
+        c = dnString.charAt(pos++);
+        if (isHexDigit(c)) {
+          hexString.append(c);
+
+          if (pos < length) {
+            c = dnString.charAt(pos++);
+            if (isHexDigit(c)) {
+              hexString.append(c);
+            } else {
+              int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
+              String message = getMessage(msgID, dnString, c);
+              throw new DirectoryException(
+                  ResultCode.INVALID_DN_SYNTAX, message, msgID);
+            }
+          } else {
+            int msgID = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT;
+            String message = getMessage(msgID, dnString);
+            throw new DirectoryException(
+                ResultCode.INVALID_DN_SYNTAX, message, msgID);
+          }
+        } else if ((c == ' ') || (c == ',') || (c == ';')
+            || (c == '+')) {
+          // This denotes the end of the value.
+          pos--;
+          break;
+        } else {
+          int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT;
+          String message = getMessage(msgID, dnString, c);
+          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+              message, msgID);
+        }
+      }
+
+      // At this point, we should have a valid hex string. Convert it
+      // to a byte array and set that as the value of the provided
+      // octet string.
+      try {
+        attributeValue.setValue(hexStringToByteArray(hexString
+            .toString()));
+        return pos;
+      } catch (Exception e) {
+        assert debugException(CLASS_NAME, "parseAttributeValue", e);
+
+        int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE;
+        String message = getMessage(msgID, dnString, String
+            .valueOf(e));
+        throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+            message, msgID);
+      }
+    }
+
+    // If the first character is a quotation mark, then the value
+    // should continue until the corresponding closing quotation mark.
+    else if (c == '"') {
+      // Keep reading until we find an unescaped closing quotation
+      // mark.
+      boolean escaped = false;
+      StringBuilder valueString = new StringBuilder();
+      while (true) {
+        if (pos >= length) {
+          // We hit the end of the DN before the closing quote.
+          // That's an error.
+          int msgID = MSGID_ATTR_SYNTAX_DN_UNMATCHED_QUOTE;
+          String message = getMessage(msgID, dnString);
+          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+              message, msgID);
+        }
+
+        c = dnString.charAt(pos++);
+        if (escaped) {
+          // The previous character was an escape, so we'll take this
+          // one no matter what.
+          valueString.append(c);
+          escaped = false;
+        } else if (c == '\\') {
+          // The next character is escaped. Set a flag to denote
+          // this, but don't include the backslash.
+          escaped = true;
+        } else if (c == '"') {
+          // This is the end of the value.
+          break;
+        } else {
+          // This is just a regular character that should be in the
+          // value.
+          valueString.append(c);
+        }
+      }
+
+      attributeValue.setValue(valueString.toString());
+      return pos;
+    }
+
+    // Otherwise, use general parsing to find the end of the value.
+    else {
+      boolean escaped;
+      StringBuilder valueString = new StringBuilder();
+      StringBuilder hexChars = new StringBuilder();
+
+      if (c == '\\') {
+        escaped = true;
+      } else {
+        escaped = false;
+        valueString.append(c);
+      }
+
+      // Keep reading until we find an unescaped comma or plus sign or
+      // the end of the DN.
+      while (true) {
+        if (pos >= length) {
+          // This is the end of the DN and therefore the end of the
+          // value. If there are any hex characters, then we need to
+          // deal with them accordingly.
+          appendHexChars(dnString, valueString, hexChars);
+          break;
+        }
+
+        c = dnString.charAt(pos++);
+        if (escaped) {
+          // The previous character was an escape, so we'll take this
+          // one. However, this could be a hex digit, and if that's
+          // the case then the escape would actually be in front of
+          // two hex digits that should be treated as a special
+          // character.
+          if (isHexDigit(c)) {
+            // It is a hexadecimal digit, so the next digit must be
+            // one too. However, this could be just one in a series
+            // of escaped hex pairs that is used in a string
+            // containing one or more multi-byte UTF-8 characters so
+            // we can't just treat this byte in isolation. Collect
+            // all the bytes together and make sure to take care of
+            // these hex bytes before appending anything else to the
+            // value.
+            if (pos >= length) {
+              int msgID =
+                MSGID_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID;
+              String message = getMessage(msgID, dnString);
+              throw new DirectoryException(
+                  ResultCode.INVALID_DN_SYNTAX, message, msgID);
+            } else {
+              char c2 = dnString.charAt(pos++);
+              if (isHexDigit(c2)) {
+                hexChars.append(c);
+                hexChars.append(c2);
+              } else {
+                int msgID =
+                  MSGID_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID;
+                String message = getMessage(msgID, dnString);
+                throw new DirectoryException(
+                    ResultCode.INVALID_DN_SYNTAX, message, msgID);
+              }
+            }
+          } else {
+            appendHexChars(dnString, valueString, hexChars);
+            valueString.append(c);
+          }
+
+          escaped = false;
+        } else if (c == '\\') {
+          escaped = true;
+        } else if ((c == ',') || (c == ';') || (c == '+')) {
+          appendHexChars(dnString, valueString, hexChars);
+          pos--;
+          break;
+        } else {
+          appendHexChars(dnString, valueString, hexChars);
+          valueString.append(c);
+        }
+      }
+
+      // Strip off any unescaped spaces that may be at the end of the
+      // value.
+      if (pos > 2 && dnString.charAt(pos - 1) == ' '
+          && dnString.charAt(pos - 2) != '\\') {
+        int lastPos = valueString.length() - 1;
+        while (lastPos > 0) {
+          if (valueString.charAt(lastPos) == ' ') {
+            valueString.delete(lastPos, lastPos + 1);
+            lastPos--;
+          } else {
+            break;
+          }
+        }
+      }
+
+      attributeValue.setValue(valueString.toString());
+      return pos;
+    }
+  }
+
+
+
+  /**
+   * Decodes a hexadecimal string from the provided
+   * <code>hexChars</code> buffer, converts it to a byte array, and
+   * then converts that to a UTF-8 string. The resulting UTF-8 string
+   * will be appended to the provided <code>valueString</code>
+   * buffer, and the <code>hexChars</code> buffer will be cleared.
+   *
+   * @param dnString
+   *          The DN string that is being decoded.
+   * @param valueString
+   *          The buffer containing the value to which the decoded
+   *          string should be appended.
+   * @param hexChars
+   *          The buffer containing the hexadecimal characters to
+   *          decode to a UTF-8 string.
+   * @throws DirectoryException
+   *           If any problem occurs during the decoding process.
+   */
+  private static void appendHexChars(String dnString,
+      StringBuilder valueString, StringBuilder hexChars)
+      throws DirectoryException {
+    try {
+      byte[] hexBytes = hexStringToByteArray(hexChars.toString());
+      valueString.append(new String(hexBytes, "UTF-8"));
+      hexChars.delete(0, hexChars.length());
+    } catch (Exception e) {
+      assert debugException(CLASS_NAME, "appendHexChars", e);
+
+      int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE;
+      String message = getMessage(msgID, dnString, String.valueOf(e));
+      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
+          message, msgID);
+    }
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/types/RDNComparator.java b/opends/src/server/org/opends/server/types/RDNComparator.java
deleted file mode 100644
index e95842f..0000000
--- a/opends/src/server/org/opends/server/types/RDNComparator.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * You can obtain a copy of the license at
- * trunk/opends/resource/legal-notices/OpenDS.LICENSE
- * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at
- * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
- * add the following below this CDDL HEADER, with the fields enclosed
- * by brackets "[]" replaced with your own identifying * information:
- *      Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
- */
-package org.opends.server.types;
-
-
-
-import java.util.Comparator;
-
-import static org.opends.server.loggers.Debug.*;
-
-
-
-/**
- * This class defines a <CODE>Comparator</CODE> object that may be
- * used to compare RDNs, particularly for inclusion in a sorted list.
- * The comparison will be done in order of the 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.
- */
-public class RDNComparator
-       implements Comparator<RDN>
-{
-  /**
-   * The fully-qualified name of this class for debugging purposes.
-   */
-  private static final String CLASS_NAME =
-       "org.opends.server.types.RDNComparator";
-
-
-
-  /**
-   * Creates a new instance of this RDN comparator.
-   */
-  public RDNComparator()
-  {
-    assert debugConstructor(CLASS_NAME);
-  }
-
-
-
-  /**
-   * Compares the provided RDNs and returns an integer value that
-   * reflects the relative order between them.
-   *
-   * @param  rdn1  The first RDN to compare.
-   * @param  rdn2  The second RDN to compare.
-   *
-   * @return  A negative value if the first RDN should come before the
-   *          second in an ordered list, a positive value if they
-   *          first RDN should come after the second in an ordered
-   *          list, or zero if there is no difference between their
-   *          order (i.e., the RDNs are equal).
-   */
-  public int compare(RDN rdn1, RDN rdn2)
-  {
-    assert debugEnter(CLASS_NAME, "compare", String.valueOf(rdn1),
-                      String.valueOf(rdn2));
-
-    AttributeType[]  types1  = rdn1.getAttributeTypes();
-    AttributeType[]  types2  = rdn2.getAttributeTypes();
-    AttributeValue[] values1 = rdn1.getAttributeValues();
-    AttributeValue[] values2 = rdn2.getAttributeValues();
-
-    int index = 0;
-
-    while (true)
-    {
-      if (index < types1.length)
-      {
-        if (index < types1.length)
-        {
-          if (types1[index].equals(types2[index]))
-          {
-            AttributeValueComparator valueComparator =
-                 new AttributeValueComparator(types1[index]);
-            int value = valueComparator.compare(values1[index],
-                                                values2[index]);
-            if (value != 0)
-            {
-              return value;
-            }
-          }
-          else
-          {
-            return types1[index].getNormalizedPrimaryName().compareTo(
-                        types2[index].getNormalizedPrimaryName());
-          }
-        }
-        else
-        {
-          return 1;
-        }
-      }
-      else if (index < types1.length)
-      {
-        return -1;
-      }
-      else
-      {
-        return 0;
-      }
-
-      index++;
-    }
-  }
-}
-
diff --git a/opends/src/server/org/opends/server/types/SearchFilter.java b/opends/src/server/org/opends/server/types/SearchFilter.java
index 7dbd3ca..1d91d29 100644
--- a/opends/src/server/org/opends/server/types/SearchFilter.java
+++ b/opends/src/server/org/opends/server/types/SearchFilter.java
@@ -3558,21 +3558,23 @@
     // attributes, then do so.
     if (dnAttributes)
     {
-      for (RDN rdn : entry.getDN().getRDNComponents())
+      DN entryDN = entry.getDN();
+      int count = entryDN.getNumComponents();
+      for (int rdnIndex = 0; rdnIndex < count; rdnIndex++)
       {
-        AttributeType[]  types  = rdn.getAttributeTypes();
-        AttributeValue[] values = rdn.getAttributeValues();
-
-        for (int i=0; i < types.length; i++)
+        RDN rdn = entryDN.getRDN(rdnIndex);
+        int numAVAs = rdn.getNumValues();
+        for (int i=0; i < numAVAs; i++)
         {
           try
           {
             if ((attributeType == null) ||
-                attributeType.equals(types[i]))
+                attributeType.equals(rdn.getAttributeType(i)))
             {
 
+              AttributeValue v = rdn.getAttributeValue(i);
               ByteString nv =
-                   matchingRule.normalizeValue(values[i].getValue());
+                   matchingRule.normalizeValue(v.getValue());
               ConditionResult r =
                    matchingRule.valuesMatch(nv, normalizedValue);
               switch (r)
diff --git a/opends/src/server/org/opends/server/util/LDIFReader.java b/opends/src/server/org/opends/server/util/LDIFReader.java
index 679bcaf..8b6c10b 100644
--- a/opends/src/server/org/opends/server/util/LDIFReader.java
+++ b/opends/src/server/org/opends/server/util/LDIFReader.java
@@ -580,7 +580,7 @@
     int length = line.length();
     if (colonPos == (length-1))
     {
-      return new DN(new ArrayList<RDN>(0));
+      return DN.nullDN();
     }
 
     if (line.charAt(colonPos+1) == ':')
diff --git a/opends/src/server/org/opends/server/util/StaticUtils.java b/opends/src/server/org/opends/server/util/StaticUtils.java
index b886c7e..d14109b 100644
--- a/opends/src/server/org/opends/server/util/StaticUtils.java
+++ b/opends/src/server/org/opends/server/util/StaticUtils.java
@@ -1273,15 +1273,59 @@
 
 
   /**
-   * Indicates whether the two array lists are equal.  They will be considered
-   * equal if they have the same number of elements, and the corresponding
-   * elements between them are equal (in the same order).
+   * Compare two byte arrays for order. Returns a negative integer,
+   * zero, or a positive integer as the first argument is less than,
+   * equal to, or greater than the second.
    *
-   * @param  list1  The first list for which to make the determination.
-   * @param  list2  The second list for which to make the determination.
+   * @param a
+   *          The first byte array to be compared.
+   * @param a2
+   *          The second byte array to be compared.
+   * @return Returns a negative integer, zero, or a positive integer
+   *         if the first byte array is less than, equal to, or greater
+   *         than the second.
+   */
+  public static int compare(byte[] a, byte[] a2) {
+    if (a == a2) {
+      return 0;
+    }
+
+    if (a == null) {
+      return -1;
+    }
+
+    if (a2 == null) {
+      return 1;
+    }
+
+    int minLength = Math.min(a.length, a2.length);
+    for (int i = 0; i < minLength; i++) {
+      if (a[i] != a2[i]) {
+        if (a[i] < a2[i]) {
+          return -1;
+        } else if (a[i] > a2[i]) {
+          return 1;
+        }
+      }
+    }
+
+    return (a.length - a2.length);
+  }
+
+
+
+  /**
+   * Indicates whether the two array lists are equal. They will be
+   * considered equal if they have the same number of elements, and
+   * the corresponding elements between them are equal (in the same
+   * order).
    *
-   * @return  <CODE>true</CODE> if the two array lists are equal, or
-   *          <CODE>false</CODE> if they are not.
+   * @param list1
+   *          The first list for which to make the determination.
+   * @param list2
+   *          The second list for which to make the determination.
+   * @return <CODE>true</CODE> if the two array lists are equal, or
+   *         <CODE>false</CODE> if they are not.
    */
   public static boolean listsAreEqual(List list1, List list2)
   {
@@ -3259,29 +3303,28 @@
 
     // Get the information about the RDN attributes.
     RDN rdn = dn.getRDN();
-    AttributeType[]  rdnTypes  = rdn.getAttributeTypes();
-    String[]         rdnNames  = rdn.getAttributeNames();
-    AttributeValue[] rdnValues = rdn.getAttributeValues();
-
+    int numAVAs = rdn.getNumValues();
 
     // If there is only one RDN attribute, then see which objectclass we should
     // use.
     ObjectClass structuralClass;
-    if (rdnTypes.length == 1)
+    if (numAVAs == 1)
     {
-      if (rdnTypes[0].hasName(ATTR_C))
+      AttributeType attrType = rdn.getAttributeType(0);
+
+      if (attrType.hasName(ATTR_C))
       {
         structuralClass = DirectoryServer.getObjectClass(OC_COUNTRY, true);
       }
-      else if (rdnTypes[0].hasName(ATTR_DC))
+      else if (attrType.hasName(ATTR_DC))
       {
         structuralClass = DirectoryServer.getObjectClass(OC_DOMAIN, true);
       }
-      else if (rdnTypes[0].hasName(ATTR_O))
+      else if (attrType.hasName(ATTR_O))
       {
         structuralClass = DirectoryServer.getObjectClass(OC_ORGANIZATION, true);
       }
-      else if (rdnTypes[0].hasName(ATTR_OU))
+      else if (attrType.hasName(ATTR_OU))
       {
         structuralClass =
              DirectoryServer.getObjectClass(OC_ORGANIZATIONAL_UNIT_LC, true);
@@ -3315,11 +3358,15 @@
          new LinkedHashMap<AttributeType,List<Attribute>>();
 
     boolean extensibleObjectAdded = false;
-    for (int i=0; i < rdnTypes.length; i++)
+    for (int i=0; i < numAVAs; i++)
     {
+      AttributeType attrType = rdn.getAttributeType(i);
+      AttributeValue attrValue = rdn.getAttributeValue(i);
+      String attrName = rdn.getAttributeName(i);
+
       // First, see if this type is allowed by the untypedObject class.  If not,
       // then we'll need to include the extensibleObject class.
-      if ((! structuralClass.isRequiredOrOptional(rdnTypes[i])) &&
+      if ((! structuralClass.isRequiredOrOptional(attrType)) &&
           (! extensibleObjectAdded))
       {
         ObjectClass extensibleObjectOC =
@@ -3337,36 +3384,36 @@
       // Create the attribute and add it to the appropriate map.
       LinkedHashSet<AttributeValue> valueSet =
            new LinkedHashSet<AttributeValue>(1);
-      valueSet.add(rdnValues[i]);
+      valueSet.add(attrValue);
 
-      if (rdnTypes[i].isOperational())
+      if (attrType.isOperational())
       {
-        List<Attribute> attrList = operationalAttributes.get(rdnTypes[i]);
+        List<Attribute> attrList = operationalAttributes.get(attrType);
         if ((attrList == null) || attrList.isEmpty())
         {
           attrList = new ArrayList<Attribute>(1);
-          attrList.add(new Attribute(rdnTypes[i], rdnNames[i], valueSet));
-          operationalAttributes.put(rdnTypes[i], attrList);
+          attrList.add(new Attribute(attrType, attrName, valueSet));
+          operationalAttributes.put(attrType, attrList);
         }
         else
         {
           Attribute attr = attrList.get(0);
-          attr.getValues().add(rdnValues[i]);
+          attr.getValues().add(attrValue);
         }
       }
       else
       {
-        List<Attribute> attrList = userAttributes.get(rdnTypes[i]);
+        List<Attribute> attrList = userAttributes.get(attrType);
         if ((attrList == null) || attrList.isEmpty())
         {
           attrList = new ArrayList<Attribute>(1);
-          attrList.add(new Attribute(rdnTypes[i], rdnNames[i], valueSet));
-          userAttributes.put(rdnTypes[i], attrList);
+          attrList.add(new Attribute(attrType, attrName, valueSet));
+          userAttributes.put(attrType, attrList);
         }
         else
         {
           Attribute attr = attrList.get(0);
-          attr.getValues().add(rdnValues[i]);
+          attr.getValues().add(attrValue);
         }
       }
     }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/JebTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/JebTestCase.java
index 7831bc0..97ca98d 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/JebTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/JebTestCase.java
@@ -39,7 +39,6 @@
 import org.opends.server.tools.makeldif.MakeLDIFInputStream;
 import org.opends.server.tools.makeldif.TemplateFile;
 import org.opends.server.types.DN;
-import org.opends.server.types.DNComparator;
 import org.opends.server.types.LDIFImportConfig;
 import org.opends.server.types.ResultCode;
 import org.opends.server.DirectoryServerTestCase;
@@ -53,8 +52,7 @@
  */
 @Test(groups = { "precommit", "jeb" })
 public abstract class JebTestCase extends DirectoryServerTestCase {
-    private DNComparator comparator = new DNComparator();
-    private TreeMap<DN,Entry> entryTreeMap = new TreeMap<DN,Entry>(comparator);
+    private TreeMap<DN,Entry> entryTreeMap = new TreeMap<DN,Entry>();
     int numEntries;
     
     /**
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
index 6714e0b..a5761ac 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
@@ -116,17 +116,17 @@
                         noControls, new ASN1OctetString("cn=Directory Manager"),
                         new ASN1OctetString("password")),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                        null, new DN(), new ASN1OctetString()),
+                        null, DN.nullDN(), new ASN1OctetString()),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                        noControls, new DN(), new ASN1OctetString()),
+                        noControls, DN.nullDN(), new ASN1OctetString()),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, nullDN, new ASN1OctetString()),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, nullDN, new ASN1OctetString()),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                        null, new DN(), nullOS),
+                        null, DN.nullDN(), nullOS),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                        noControls, new DN(), nullOS),
+                        noControls, DN.nullDN(), nullOS),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, nullDN, nullOS),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
@@ -187,18 +187,18 @@
                         noControls, nullOS, "PLAIN",
                         new ASN1OctetString("\u0000u:test.user\u0000password")),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                        null, new DN(), "EXTERNAL", null),
+                        null, DN.nullDN(), "EXTERNAL", null),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                        noControls, new DN(), "EXTERNAL", null),
+                        noControls, DN.nullDN(), "EXTERNAL", null),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, nullDN, "EXTERNAL", null),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, nullDN, "EXTERNAL", null),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                        null, new DN(), "PLAIN",
+                        null, DN.nullDN(), "PLAIN",
                         new ASN1OctetString("\u0000u:test.user\u0000password")),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                        noControls, new DN(), "PLAIN",
+                        noControls, DN.nullDN(), "PLAIN",
                         new ASN1OctetString("\u0000u:test.user\u0000password")),
       new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, nullDN, "PLAIN",
@@ -608,7 +608,7 @@
          new ASN1OctetString("\u0000dn:cn=Directory Manager\u0000password");
 
     BindOperation bindOperation =
-                       conn.processSASLBind(new DN(), "PLAIN", saslCreds);
+                       conn.processSASLBind(DN.nullDN(), "PLAIN", saslCreds);
     assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
     assertNotNull(bindOperation.getSASLAuthUserEntry());
   }
@@ -676,7 +676,7 @@
          new ASN1OctetString("\u0000dn:cn=Directory Manager\u0000password");
 
     BindOperation bindOperation =
-         conn.processSASLBind(new DN(), "PLAIN", saslCreds);
+         conn.processSASLBind(DN.nullDN(), "PLAIN", saslCreds);
     assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
     assertNotNull(bindOperation.getUserEntryDN());
   }
@@ -721,7 +721,7 @@
          new ASN1OctetString("\u0000dn:cn=Directory Manager\u0000password");
 
     BindOperation bindOperation =
-         conn.processSASLBind(new DN(), "PLAIN", saslCreds);
+         conn.processSASLBind(DN.nullDN(), "PLAIN", saslCreds);
     assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
     assertTrue(bindOperation.getProcessingStartTime() > 0);
     assertTrue(bindOperation.getProcessingStopTime() >=
@@ -793,7 +793,7 @@
          new ASN1OctetString("\u0000dn:cn=Directory Manager\u0000password");
 
     BindOperation bindOperation =
-         conn.processSASLBind(new DN(), "PLAIN", saslCreds);
+         conn.processSASLBind(DN.nullDN(), "PLAIN", saslCreds);
     assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
     assertNotNull(bindOperation.getResponseLogElements());
     assertTrue(bindOperation.getResponseLogElements().length > 0);
@@ -867,7 +867,7 @@
          new ASN1OctetString("\u0000dn:cn=Directory Manager\u0000password");
 
     BindOperation bindOperation =
-         conn.processSASLBind(new DN(), "PLAIN", saslCreds);
+         conn.processSASLBind(DN.nullDN(), "PLAIN", saslCreds);
     assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
 
     assertTrue(InvocationCounterPlugin.getPreParseCount() > 0);
@@ -1636,7 +1636,7 @@
 
     BindOperation bindOperation =
          new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                           requestControls, new DN(), new ASN1OctetString());
+                           requestControls, DN.nullDN(), new ASN1OctetString());
     bindOperation.run();
     assertEquals(bindOperation.getResultCode(),
                  ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
@@ -1662,7 +1662,7 @@
 
     BindOperation bindOperation =
          new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                           requestControls, new DN(), "PLAIN", saslCreds);
+                           requestControls, DN.nullDN(), "PLAIN", saslCreds);
     bindOperation.run();
     assertEquals(bindOperation.getResultCode(),
                  ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
@@ -1685,7 +1685,7 @@
 
     BindOperation bindOperation =
          new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                           requestControls, new DN(), new ASN1OctetString());
+                           requestControls, DN.nullDN(), new ASN1OctetString());
     bindOperation.run();
     assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
   }
@@ -1710,7 +1710,7 @@
 
     BindOperation bindOperation =
          new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                           requestControls, new DN(), "PLAIN", saslCreds);
+                           requestControls, DN.nullDN(), "PLAIN", saslCreds);
     bindOperation.run();
     assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
   }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/DeleteOperationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/DeleteOperationTestCase.java
index 343ed1a..6a01495 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/DeleteOperationTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/DeleteOperationTestCase.java
@@ -93,9 +93,9 @@
       new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                           null, new ASN1OctetString("o=test")),
       new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                          new ArrayList<Control>(), new DN()),
+                          new ArrayList<Control>(), DN.nullDN()),
       new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                          null, new DN()),
+                          null, DN.nullDN()),
       new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                           new ArrayList<Control>(), DN.decode("o=test")),
       new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java
index f43db15..918a95b 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java
@@ -196,9 +196,9 @@
                               new Attribute("description", "foo")));
 
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
-                                   conn.nextMessageID(), null, new DN(), mods));
+                                   conn.nextMessageID(), null, DN.nullDN(), mods));
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
-                                   conn.nextMessageID(), noControls, new DN(),
+                                   conn.nextMessageID(), noControls, DN.nullDN(),
                                    mods));
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
@@ -212,9 +212,9 @@
                               new Attribute("description", "foo")));
 
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
-                                   conn.nextMessageID(), null, new DN(), mods));
+                                   conn.nextMessageID(), null, DN.nullDN(), mods));
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
-                                   conn.nextMessageID(), noControls, new DN(),
+                                   conn.nextMessageID(), noControls, DN.nullDN(),
                                    mods));
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
@@ -228,9 +228,9 @@
                               new Attribute("description", "foo")));
 
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
-                                   conn.nextMessageID(), null, new DN(), mods));
+                                   conn.nextMessageID(), null, DN.nullDN(), mods));
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
-                                   conn.nextMessageID(), noControls, new DN(),
+                                   conn.nextMessageID(), noControls, DN.nullDN(),
                                    mods));
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
@@ -246,9 +246,9 @@
                               new Attribute("description", "bar")));
 
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
-                                   conn.nextMessageID(), null, new DN(), mods));
+                                   conn.nextMessageID(), null, DN.nullDN(), mods));
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
-                                   conn.nextMessageID(), noControls, new DN(),
+                                   conn.nextMessageID(), noControls, DN.nullDN(),
                                    mods));
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
@@ -264,9 +264,9 @@
                               new Attribute("cn", "bar")));
 
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
-                                   conn.nextMessageID(), null, new DN(), mods));
+                                   conn.nextMessageID(), null, DN.nullDN(), mods));
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
-                                   conn.nextMessageID(), noControls, new DN(),
+                                   conn.nextMessageID(), noControls, DN.nullDN(),
                                    mods));
     opList.add(new ModifyOperation(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
@@ -375,7 +375,7 @@
                               new Attribute("description", "foo")));
     ModifyOperation modifyOperation =
          new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                             null, new DN(), mods);
+                             null, DN.nullDN(), mods);
     assertNotNull(modifyOperation.getEntryDN());
   }
 
@@ -400,7 +400,7 @@
                               new Attribute("description", "foo")));
     ModifyOperation modifyOperation =
          new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                             null, new DN(), mods);
+                             null, DN.nullDN(), mods);
     assertNotNull(modifyOperation.getEntryDN());
 
     modifyOperation.setRawEntryDN(new ASN1OctetString("ou=Users,o=test"));
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
index ae01530..3f57605 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
@@ -241,8 +241,10 @@
     Entry newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.test0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    RDN rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -264,8 +266,10 @@
     newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -296,8 +300,10 @@
     Entry newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.test0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    RDN rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -319,8 +325,10 @@
     newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -351,8 +359,10 @@
     Entry newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.test0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    RDN rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -374,8 +384,10 @@
     newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -406,8 +418,10 @@
     Entry newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.test0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    RDN rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -429,8 +443,10 @@
     newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -461,8 +477,10 @@
     Entry newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.test0,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    RDN rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -484,8 +502,10 @@
     newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -516,8 +536,10 @@
     Entry newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.test0,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    RDN rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -539,8 +561,10 @@
     newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -798,8 +822,10 @@
     Entry newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.test0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    RDN rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -821,8 +847,10 @@
     newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -856,8 +884,10 @@
     Entry newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.test0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    RDN rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -879,8 +909,10 @@
     newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -941,8 +973,10 @@
     Entry newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.test0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    RDN rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
@@ -964,8 +998,10 @@
     newEntry = DirectoryServer.getEntry(DN.decode(
         "uid=user.0,ou=People,dc=example,dc=com"));
     assertNotNull(newEntry);
-    for(AttributeType attribute : newEntry.getDN().getRDN().getAttributeTypes())
+    rdn = newEntry.getDN().getRDN();
+    for (int i = 0; i < rdn.getNumValues(); i++)
     {
+      AttributeType attribute = rdn.getAttributeType(i);
       assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.0")));
       assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0")));
     }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestRFC3672SubtreeSpecification.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestRFC3672SubtreeSpecification.java
index 17b93d3..3ac81cb 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestRFC3672SubtreeSpecification.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestRFC3672SubtreeSpecification.java
@@ -40,7 +40,7 @@
     SubtreeSpecificationTestCase {
 
   // Cached root DN.
-  private DN rootDN = new DN();
+  private DN rootDN = DN.nullDN();
 
   /**
    * Tests the {@link RFC3672SubtreeSpecification#valueOf(DN, String)}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestRelativeSubtreeSpecification.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestRelativeSubtreeSpecification.java
index 748126f..a6290b3 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestRelativeSubtreeSpecification.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestRelativeSubtreeSpecification.java
@@ -44,7 +44,7 @@
     SubtreeSpecificationTestCase {
 
   // Cached root DN.
-  private DN rootDN = new DN();
+  private DN rootDN = DN.nullDN();
 
   /**
    * Tests the {@link RelativeSubtreeSpecification#valueOf(DN, String)}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AnonymousSASLMechanismHandlerTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AnonymousSASLMechanismHandlerTestCase.java
index b01df89..6181470 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AnonymousSASLMechanismHandlerTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AnonymousSASLMechanismHandlerTestCase.java
@@ -139,7 +139,7 @@
          InternalClientConnection.getRootConnection();
     BindOperation bindOperation =
          new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                           new ArrayList<Control>(), new DN(),
+                           new ArrayList<Control>(), DN.nullDN(),
                            SASL_MECHANISM_ANONYMOUS, null);
     handler.processSASLBind(bindOperation);
     assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
@@ -166,7 +166,7 @@
          InternalClientConnection.getRootConnection();
     BindOperation bindOperation =
          new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                           new ArrayList<Control>(), new DN(),
+                           new ArrayList<Control>(), DN.nullDN(),
                            SASL_MECHANISM_ANONYMOUS, new ASN1OctetString());
     handler.processSASLBind(bindOperation);
     assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
@@ -192,7 +192,7 @@
          InternalClientConnection.getRootConnection();
     BindOperation bindOperation =
          new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
-                           new ArrayList<Control>(), new DN(),
+                           new ArrayList<Control>(), DN.nullDN(),
                            SASL_MECHANISM_ANONYMOUS,
                            new ASN1OctetString("Internal Trace String"));
     handler.processSASLBind(bindOperation);
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CRAMMD5SASLMechanismHandlerTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CRAMMD5SASLMechanismHandlerTestCase.java
index 64054b0..c46c4d0 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CRAMMD5SASLMechanismHandlerTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CRAMMD5SASLMechanismHandlerTestCase.java
@@ -688,7 +688,7 @@
     InternalClientConnection conn =
          new InternalClientConnection(new AuthenticationInfo());
     BindOperation bindOperation =
-         conn.processSASLBind(new DN(), SASL_MECHANISM_CRAM_MD5,
+         conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_CRAM_MD5,
                               new ASN1OctetString("invalid"));
     assertFalse(bindOperation.getResultCode() == ResultCode.SUCCESS);
   }
@@ -708,12 +708,12 @@
     InternalClientConnection conn =
          new InternalClientConnection(new AuthenticationInfo());
     BindOperation bindOperation =
-         conn.processSASLBind(new DN(), SASL_MECHANISM_CRAM_MD5, null);
+         conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_CRAM_MD5, null);
     assertEquals(bindOperation.getResultCode(),
                  ResultCode.SASL_BIND_IN_PROGRESS);
 
     bindOperation =
-         conn.processSASLBind(new DN(), SASL_MECHANISM_CRAM_MD5,
+         conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_CRAM_MD5,
                               new ASN1OctetString("malformed"));
     assertFalse(bindOperation.getResultCode() == ResultCode.SUCCESS);
   }
@@ -733,14 +733,14 @@
     InternalClientConnection conn =
          new InternalClientConnection(new AuthenticationInfo());
     BindOperation bindOperation =
-         conn.processSASLBind(new DN(), SASL_MECHANISM_CRAM_MD5, null);
+         conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_CRAM_MD5, null);
     assertEquals(bindOperation.getResultCode(),
                  ResultCode.SASL_BIND_IN_PROGRESS);
 
     ASN1OctetString creds =
          new ASN1OctetString("dn:cn=Directory Manager malformeddigest");
     bindOperation =
-         conn.processSASLBind(new DN(), SASL_MECHANISM_CRAM_MD5, creds);
+         conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_CRAM_MD5, creds);
     assertFalse(bindOperation.getResultCode() == ResultCode.SUCCESS);
   }
 
@@ -760,7 +760,7 @@
     InternalClientConnection conn =
          new InternalClientConnection(new AuthenticationInfo());
     BindOperation bindOperation =
-         conn.processSASLBind(new DN(), SASL_MECHANISM_CRAM_MD5, null);
+         conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_CRAM_MD5, null);
     assertEquals(bindOperation.getResultCode(),
                  ResultCode.SASL_BIND_IN_PROGRESS);
 
@@ -768,7 +768,7 @@
          new ASN1OctetString("dn:cn=Directory Manager " +
                           "malformedcredswiththerightlength");
     bindOperation =
-         conn.processSASLBind(new DN(), SASL_MECHANISM_CRAM_MD5, creds);
+         conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_CRAM_MD5, creds);
     assertFalse(bindOperation.getResultCode() == ResultCode.SUCCESS);
   }
 }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandlerTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandlerTestCase.java
index 436375b..3b5429d 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandlerTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandlerTestCase.java
@@ -823,7 +823,7 @@
     InternalClientConnection conn =
          new InternalClientConnection(new AuthenticationInfo());
     BindOperation bindOperation =
-         conn.processSASLBind(new DN(), SASL_MECHANISM_DIGEST_MD5,
+         conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_DIGEST_MD5,
                               new ASN1OctetString("invalid"));
     assertFalse(bindOperation.getResultCode() == ResultCode.SUCCESS);
   }
@@ -843,12 +843,12 @@
     InternalClientConnection conn =
          new InternalClientConnection(new AuthenticationInfo());
     BindOperation bindOperation =
-         conn.processSASLBind(new DN(), SASL_MECHANISM_DIGEST_MD5, null);
+         conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_DIGEST_MD5, null);
     assertEquals(bindOperation.getResultCode(),
                  ResultCode.SASL_BIND_IN_PROGRESS);
 
     bindOperation =
-         conn.processSASLBind(new DN(), SASL_MECHANISM_DIGEST_MD5,
+         conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_DIGEST_MD5,
                               new ASN1OctetString("malformed"));
     assertFalse(bindOperation.getResultCode() == ResultCode.SUCCESS);
   }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java
index 81daa25..e4f0458 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java
@@ -140,10 +140,10 @@
          throws Exception
   {
     AttributeType cnType = DirectoryServer.getAttributeType(ATTR_COMMON_NAME);
-    RDN[] components = new RDN[2];
-    components[0] = new RDN(cnType, new AttributeValue(cnType, monitorName));
-    components[1] = new RDN(cnType, new AttributeValue(cnType, "monitor"));
-    DN monitorDN = new DN(components);
+    
+    RDN rdn0 = RDN.create(cnType, new AttributeValue(cnType, monitorName));
+    RDN rdn1 = RDN.create(cnType, new AttributeValue(cnType, "monitor"));
+    DN monitorDN = DN.create(rdn0, rdn1);
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java
index ed229f7..3bb47c9 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java
@@ -449,7 +449,7 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
     BindOperation bindOperation =
-         conn.processSASLBind(new DN(), "PLAIN", creds);
+         conn.processSASLBind(DN.nullDN(), "PLAIN", creds);
     assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
   }
 
@@ -902,7 +902,7 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
     InternalSearchOperation searchOperation =
-         conn.processSearch(new DN(), SearchScope.BASE_OBJECT,
+         conn.processSearch(DN.nullDN(), SearchScope.BASE_OBJECT,
               SearchFilter.createFilterFromString("(objectClass=*)"));
     assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
     assertFalse(searchOperation.getSearchEntries().isEmpty());
@@ -924,7 +924,7 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
     InternalSearchOperation searchOperation =
-         conn.processSearch(new DN(), SearchScope.BASE_OBJECT,
+         conn.processSearch(DN.nullDN(), SearchScope.BASE_OBJECT,
               DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
               SearchFilter.createFilterFromString("(objectClass=*)"),
               new LinkedHashSet<String>());
@@ -952,7 +952,7 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
     InternalSearchOperation searchOperation =
-         conn.processSearch(new DN(), SearchScope.BASE_OBJECT,
+         conn.processSearch(DN.nullDN(), SearchScope.BASE_OBJECT,
               DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
               SearchFilter.createFilterFromString("(objectClass=*)"),
               new LinkedHashSet<String>(), searchListener);
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalSearchOperationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalSearchOperationTestCase.java
index 17144b7..8464003 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalSearchOperationTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalSearchOperationTestCase.java
@@ -133,7 +133,7 @@
          InternalClientConnection.getRootConnection();
     new InternalSearchOperation(conn, conn.nextOperationID(),
                                 conn.nextMessageID(), new ArrayList<Control>(),
-                                new DN(), SearchScope.BASE_OBJECT,
+                                DN.nullDN(), SearchScope.BASE_OBJECT,
                                 DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0,
                                 false, searchFilter,
                                 new LinkedHashSet<String>(), null);
@@ -158,7 +158,7 @@
          InternalClientConnection.getRootConnection();
     new InternalSearchOperation(conn, conn.nextOperationID(),
                                 conn.nextMessageID(), new ArrayList<Control>(),
-                                new DN(), SearchScope.BASE_OBJECT,
+                                DN.nullDN(), SearchScope.BASE_OBJECT,
                                 DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0,
                                 false, searchFilter,
                                 new LinkedHashSet<String>(),
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestAddResponseProtocolOp.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestAddResponseProtocolOp.java
index 7021b77..e66a856 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestAddResponseProtocolOp.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestAddResponseProtocolOp.java
@@ -82,11 +82,9 @@
 
     AttributeValue attributeValue = new AttributeValue(attribute, "testValue");
 
-    RDN rdn = new RDN(attribute, attributeValue);
-
-    RDN[] rdns = {rdn};
-
-    dn = new DN(rdns);
+    RDN rdn = RDN.create(attribute, attributeValue);
+    
+    dn = DN.create(rdn);
   }
 
   /**
@@ -295,7 +293,7 @@
 
 
     //Test case for a full encode decode operation with an empty DN params.
-    addEncoded = new AddResponseProtocolOp(resultCode, resultMsg, new DN(),
+    addEncoded = new AddResponseProtocolOp(resultCode, resultMsg, DN.nullDN(),
                                            referralURLs);
     element = addEncoded.encode();
     addDecoded = (AddResponseProtocolOp)AddResponseProtocolOp.decode(element);
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestCompareResponseProtocolOp.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestCompareResponseProtocolOp.java
index c34b36f..79a1d3e 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestCompareResponseProtocolOp.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestCompareResponseProtocolOp.java
@@ -84,11 +84,8 @@
 
     AttributeValue attributeValue = new AttributeValue(attribute, "testValue");
 
-    RDN rdn = new RDN(attribute, attributeValue);
-
-    RDN[] rdns = {rdn};
-
-    dn = new DN(rdns);
+    RDN rdn = RDN.create(attribute, attributeValue);
+    dn = DN.create(rdn);
   }
 
   /**
@@ -303,7 +300,7 @@
 
 
     //Test case for a full encode decode operation with an empty DN params.
-    deleteEncoded = new CompareResponseProtocolOp(resultCode, resultMsg, new DN(),
+    deleteEncoded = new CompareResponseProtocolOp(resultCode, resultMsg, DN.nullDN(),
                                                  referralURLs);
     element = deleteEncoded.encode();
     deleteDecoded = (CompareResponseProtocolOp)CompareResponseProtocolOp.decode(
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestDeleteResponseProtocolOp.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestDeleteResponseProtocolOp.java
index bb5f2af..7988f13 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestDeleteResponseProtocolOp.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestDeleteResponseProtocolOp.java
@@ -82,11 +82,8 @@
 
     AttributeValue attributeValue = new AttributeValue(attribute, "testValue");
 
-    RDN rdn = new RDN(attribute, attributeValue);
-
-    RDN[] rdns = {rdn};
-
-    dn = new DN(rdns);
+    RDN rdn = RDN.create(attribute, attributeValue);
+    dn = DN.create(rdn);
   }
 
   /**
@@ -301,7 +298,7 @@
 
 
     //Test case for a full encode decode operation with an empty DN params.
-    deleteEncoded = new DeleteResponseProtocolOp(resultCode, resultMsg, new DN(),
+    deleteEncoded = new DeleteResponseProtocolOp(resultCode, resultMsg, DN.nullDN(),
                                            referralURLs);
     element = deleteEncoded.encode();
     deleteDecoded = (DeleteResponseProtocolOp)DeleteResponseProtocolOp.decode(
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyDNResponseProtocolOp.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyDNResponseProtocolOp.java
index 0001437..dfed6c8 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyDNResponseProtocolOp.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyDNResponseProtocolOp.java
@@ -84,11 +84,8 @@
 
     AttributeValue attributeValue = new AttributeValue(attribute, "testValue");
 
-    RDN rdn = new RDN(attribute, attributeValue);
-
-    RDN[] rdns = {rdn};
-
-    dn = new DN(rdns);
+    RDN rdn = RDN.create(attribute, attributeValue);
+    dn = DN.create(rdn);
   }
 
   /**
@@ -303,7 +300,7 @@
 
 
     //Test case for a full encode decode operation with an empty DN params.
-    deleteEncoded = new ModifyDNResponseProtocolOp(resultCode, resultMsg, new DN(),
+    deleteEncoded = new ModifyDNResponseProtocolOp(resultCode, resultMsg, DN.nullDN(),
                                                   referralURLs);
     element = deleteEncoded.encode();
     deleteDecoded = (ModifyDNResponseProtocolOp)ModifyDNResponseProtocolOp.decode(
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyResponseProtocolOp.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyResponseProtocolOp.java
index d4c3380..e5e67cf 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyResponseProtocolOp.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyResponseProtocolOp.java
@@ -84,11 +84,8 @@
 
     AttributeValue attributeValue = new AttributeValue(attribute, "testValue");
 
-    RDN rdn = new RDN(attribute, attributeValue);
-
-    RDN[] rdns = {rdn};
-
-    dn = new DN(rdns);
+    RDN rdn = RDN.create(attribute, attributeValue);
+    dn = DN.create(rdn);
   }
 
   /**
@@ -303,7 +300,7 @@
 
 
     //Test case for a full encode decode operation with an empty DN params.
-    modifyEncoded = new ModifyResponseProtocolOp(resultCode, resultMsg, new DN(),
+    modifyEncoded = new ModifyResponseProtocolOp(resultCode, resultMsg, DN.nullDN(),
                                                  referralURLs);
     element = modifyEncoded.encode();
     modifyDecoded = (ModifyResponseProtocolOp)ModifyResponseProtocolOp.decode(
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestDN.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestDN.java
index 6490cd2..34bca38 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestDN.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestDN.java
@@ -26,16 +26,18 @@
  */
 package org.opends.server.types;
 
+
+
 import static org.testng.Assert.*;
 
 import org.opends.server.TestCaseUtils;
+import org.opends.server.core.DirectoryServer;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 import org.testng.annotations.BeforeClass;
-import org.testng.annotations.AfterClass;
 
-import java.util.Arrays;
+
 
 /**
  * This class defines a set of tests for the org.opends.server.core.DN
@@ -50,45 +52,73 @@
   @DataProvider(name = "testDNs")
   public Object[][] createData() {
     return new Object[][] {
-        { "", "" },
-        { "   ", "" },
-        { "dc=com", "dc=com" },
-        { "DC=COM", "dc=com" },
-        { "dc = com", "dc=com" },
-        { " dc = com ", "dc=com" },
-        { "dc=example,dc=com", "dc=example,dc=com" },
-        { "dc=example, dc=com", "dc=example,dc=com" },
-        { "dc=example ,dc=com", "dc=example,dc=com" },
-        { "dc =example , dc  =   com", "dc=example,dc=com" },
+        { "", "", "" },
+        { "   ", "", "" },
+        { "dc=com", "dc=com", "dc=com" },
+        { "DC=COM", "dc=com", "DC=COM" },
+        { "dc = com", "dc=com", "dc=com" },
+        { " dc = com ", "dc=com", "dc=com" },
+        { "dc=example,dc=com", "dc=example,dc=com",
+            "dc=example,dc=com" },
+        { "dc=example, dc=com", "dc=example,dc=com",
+            "dc=example,dc=com" },
+        { "dc=example ,dc=com", "dc=example,dc=com",
+            "dc=example,dc=com" },
+        { "dc =example , dc  =   com", "dc=example,dc=com",
+            "dc=example,dc=com" },
         { "givenName=John+cn=Doe,ou=People,dc=example,dc=com",
-            "cn=doe+givenname=john,ou=people,dc=example,dc=com" },
+            "cn=doe+givenname=john,ou=people,dc=example,dc=com",
+            "givenName=John+cn=Doe,ou=People,dc=example,dc=com" },
         { "givenName=John\\+cn=Doe,ou=People,dc=example,dc=com",
-            "givenname=john\\+cn=doe,ou=people,dc=example,dc=com" },
+            "givenname=john\\+cn=doe,ou=people,dc=example,dc=com",
+            "givenName=John\\+cn=Doe,ou=People,dc=example,dc=com" },
         { "cn=Doe\\, John,ou=People,dc=example,dc=com",
-            "cn=doe\\, john,ou=people,dc=example,dc=com" },
-        { "UID=jsmith,DC=example,DC=net", "uid=jsmith,dc=example,dc=net" },
+            "cn=doe\\, john,ou=people,dc=example,dc=com",
+            "cn=Doe\\, John,ou=People,dc=example,dc=com" },
+        { "UID=jsmith,DC=example,DC=net",
+            "uid=jsmith,dc=example,dc=net",
+            "UID=jsmith,DC=example,DC=net" },
         { "OU=Sales+CN=J. Smith,DC=example,DC=net",
-            "cn=j. smith+ou=sales,dc=example,dc=net" },
+            "cn=j. smith+ou=sales,dc=example,dc=net",
+            "OU=Sales+CN=J. Smith,DC=example,DC=net" },
         { "CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net",
-            "cn=james \\\"jim\\\" smith\\, iii,dc=example,dc=net" },
+            "cn=james \\\"jim\\\" smith\\, iii,dc=example,dc=net",
+            "CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net" },
+        { "CN=John Smith\\2C III,DC=example,DC=net",
+            "cn=john smith\\, iii,dc=example,dc=net",
+            "CN=John Smith\\, III,DC=example,DC=net" },
+        { "CN=\\23John Smith\\20,DC=example,DC=net",
+            "cn=\\#john smith,dc=example,dc=net",
+            "CN=\\#John Smith\\ ,DC=example,DC=net" },
         { "CN=Before\\0dAfter,DC=example,DC=net",
-            "cn=before\\0dafter,dc=example,dc=net" },
-        { "1.3.6.1.4.1.1466.0=#04024869", "1.3.6.1.4.1.1466.0=\\04\\02hi" },
-        { "CN=Lu\\C4\\8Di\\C4\\87", "cn=lu\\c4\\8di\\c4\\87" },
+            "cn=before\\0dafter,dc=example,dc=net",
+            "CN=Before\\0dAfter,DC=example,DC=net" },
+        { "1.3.6.1.4.1.1466.0=#04024869",
+            "1.3.6.1.4.1.1466.0=\\04\\02hi",
+            "1.3.6.1.4.1.1466.0=\\04\\02Hi" },
+        { "1.1.1=", "1.1.1=", "1.1.1=" },
+        { "CN=Lu\\C4\\8Di\\C4\\87", "cn=lu\\c4\\8di\\c4\\87",
+            "CN=Lu\\c4\\8di\\c4\\87" },
         { "ou=\\e5\\96\\b6\\e6\\a5\\ad\\e9\\83\\a8,o=Airius",
-            "ou=\\e5\\96\\b6\\e6\\a5\\ad\\e9\\83\\a8,o=airius" },
-        { "photo=\\ john \\ ,dc=com", "photo=\\ john \\ ,dc=com" },
-        { "AB-global=", "ab-global=" },
+            "ou=\\e5\\96\\b6\\e6\\a5\\ad\\e9\\83\\a8,o=airius",
+            "ou=\\e5\\96\\b6\\e6\\a5\\ad\\e9\\83\\a8,o=Airius" },
+        { "photo=\\ john \\ ,dc=com", "photo=\\ john \\ ,dc=com",
+            "photo=\\ john \\ ,dc=com" },
+        { "AB-global=", "ab-global=", "AB-global=" },
         { "OU= Sales + CN = J. Smith ,DC=example,DC=net",
-             "cn=j. smith+ou=sales,dc=example,dc=net" },
-         { "cn=John+a=", "a=+cn=john" },
-         { "OID.1.3.6.1.4.1.1466.0=#04024869",
-              "1.3.6.1.4.1.1466.0=\\04\\02hi" },
-         { "O=\"Sue, Grabbit and Runn\",C=US",
-              "o=sue\\, grabbit and runn,c=us" },
-    };
+            "cn=j. smith+ou=sales,dc=example,dc=net",
+            "OU=Sales+CN=J. Smith,DC=example,DC=net" },
+        { "cn=John+a=", "a=+cn=john", "cn=John+a=" },
+        { "OID.1.3.6.1.4.1.1466.0=#04024869",
+            "1.3.6.1.4.1.1466.0=\\04\\02hi",
+            "1.3.6.1.4.1.1466.0=\\04\\02Hi" },
+        { "O=\"Sue, Grabbit and Runn\",C=US",
+            "o=sue\\, grabbit and runn,c=us",
+            "O=Sue\\, Grabbit and Runn,C=US" }, };
   }
 
+
+
   /**
    * Illegal DN test data provider.
    *
@@ -96,66 +126,15 @@
    */
   @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 " },
+        { "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" }, };
   }
 
-  /**
-   * DN compare test data provider.
-   *
-   * @return The unsorted and sorted DN strings .
-   */
-  @DataProvider(name = "compareDNs")
-  public Object[][] createSortData() {
-    return new Object[][] {
-         {
-              // Not sorted.
-              new String[]
-                   {
-                        "UID=jsmith,DC=example,DC=net",
-                        "dc=com",
-                        "",
-                        "dc=example,dc=net",
-                        "uid=jsmith,dc=example,dc=com",
-                        "dc=example,dc=com",
-                        "cn=jsmith,dc=example,dc=com",
-                        "",
-                        "dc =example , dc  =   com",
-                        "uid=asmith,dc=example,dc=net",
-                   },
-              // Sorted.
-              new String[]
-                   {
-                        "",
-                        "",
-                        "dc=com",
-                        "dc =example , dc  =   com",
-                        "dc=example,dc=com",
-                        "cn=jsmith,dc=example,dc=com",
-                        "uid=jsmith,dc=example,dc=com",
-                        "dc=example,dc=net",
-                        "uid=asmith,dc=example,dc=net",
-                        "UID=jsmith,DC=example,DC=net",
-                   },
-         }
-    };
-  }
+
 
   /**
    * Set up the environment for performing the tests in this suite.
@@ -165,138 +144,799 @@
    */
   @BeforeClass
   public void setUp() throws Exception {
-    // This test suite depends on having the schema available, so we'll start
-    // the server.
+    // This test suite depends on having the schema available, so
+    // we'll start the server.
     TestCaseUtils.startServer();
+
+    AttributeType dummy = DirectoryServer.getDefaultAttributeType(
+        "x-test-integer-type", DirectoryServer
+            .getDefaultIntegerSyntax());
+    DirectoryServer.getSchema().registerAttributeType(dummy, true);
   }
 
+
+
+  /**
+   * Tests the create method.
+   *
+   * @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]);
+
+    assertEquals(dn, DN.nullDN());
+  }
+
+
+
+  /**
+   * Tests the create method.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testCreateNullDN2() throws Exception {
+    DN dn = DN.create();
+
+    assertEquals(dn, DN.nullDN());
+  }
+
+
+
+  /**
+   * Tests the create method.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @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"));
+
+    assertEquals(dn, DN.decode("dc=com"));
+  }
+
+
+
+  /**
+   * Tests the create method.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testCreateWithMultipleRDNs1() throws Exception {
+    DN dn = DN.create(new RDN[] { RDN.decode("dc=foo"),
+        RDN.decode("dc=opends"), RDN.decode("dc=org") });
+
+    assertEquals(dn, DN.decode("dc=foo,dc=opends,dc=org"));
+  }
+
+
+
+  /**
+   * Tests the create method.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testCreateWithMultipleRDNs2() throws Exception {
+    DN dn = DN.create(RDN.decode("dc=foo"), RDN.decode("dc=opends"),
+        RDN.decode("dc=org"));
+
+    assertEquals(dn, DN.decode("dc=foo,dc=opends,dc=org"));
+  }
+
+
+
   /**
    * Tests the <CODE>decode</CODE> method which takes a String
    * argument.
    *
    * @param rawDN
-   *          The raw undecoded DN string.
+   *          Raw DN string representation.
    * @param normDN
-   *          The expected normalized value.
+   *          Normalized DN string representation.
+   * @param stringDN
+   *          String representation.
    * @throws Exception
    *           If the test failed unexpectedly.
    */
   @Test(dataProvider = "testDNs")
-  public void testDecodeString(String rawDN, String normDN)
-      throws Exception {
+  public void testDecodeString(String rawDN, String normDN,
+      String stringDN) throws Exception {
     DN dn = DN.decode(rawDN);
-    assertEquals(normDN, dn.toNormalizedString());
+    assertEquals(dn.toNormalizedString(), normDN);
   }
 
+
+
   /**
    * Tests the <CODE>decode</CODE> method which takes a String
    * argument.
    *
    * @param rawDN
-   *          The raw undecoded DN string.
+   *          Raw DN string representation.
    * @param normDN
-   *          The expected normalized value.
+   *          Normalized DN string representation.
+   * @param stringDN
+   *          String representation.
    * @throws Exception
    *           If the test failed unexpectedly.
    */
   @Test(dataProvider = "testDNs")
-  public void testDecodeOctetString(String rawDN, String normDN)
-      throws Exception {
+  public void testDecodeOctetString(String rawDN, String normDN,
+      String stringDN) throws Exception {
     ASN1OctetString octetString = new ASN1OctetString(rawDN);
 
     DN dn = DN.decode(octetString);
-    assertEquals(normDN, dn.toNormalizedString());
+    assertEquals(dn.toNormalizedString(), normDN);
+  }
+
+
+
+  /**
+   * Tests the <CODE>valueOf</CODE> method which takes a String
+   * argument.
+   *
+   * @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);
   }
 
 
 
   /**
    * Test that decoding an illegal DN as a String throws an exception.
-   * @param dn The illegal DN to be tested.
+   *
+   * @param dn
+   *          The illegal DN to be tested.
    * @throws Exception
    *           If the test failed unexpectedly.
    */
-  @Test(dataProvider = "illegalDNs",
-        expectedExceptions = DirectoryException.class )
-  public void testIllegalStringDNs(String dn)
-       throws Exception
-  {
-    try
-    {
+  @Test(dataProvider = "illegalDNs", expectedExceptions = DirectoryException.class)
+  public void testIllegalStringDNs(String dn) throws Exception {
+    try {
       DN.decode(dn);
-    }
-    catch (DirectoryException e)
-    {
+    } catch (DirectoryException e) {
       throw e;
-    }
-    catch (Exception e)
-    {
-      System.out.println(
-           "Illegal DN <" + dn + "> threw the wrong type of exception");
+    } 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");
+    throw new RuntimeException("Illegal DN <" + dn
+        + "> did not throw an exception");
   }
 
+
+
   /**
-   * Test that decoding an illegal DN as an octet string throws an exception.
-   * @param dn The illegal DN to be tested.
+   * Test that decoding an illegal DN as an octet 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 testIllegalOctetStringDNs(String dn)
-       throws Exception
-  {
+  @Test(dataProvider = "illegalDNs", expectedExceptions = DirectoryException.class)
+  public void testIllegalOctetStringDNs(String dn) throws Exception {
     ASN1OctetString octetString = new ASN1OctetString(dn);
 
-    try
-    {
+    try {
       DN.decode(octetString);
-    }
-    catch (DirectoryException e)
-    {
+    } catch (DirectoryException e) {
       throw e;
-    }
-    catch (Exception e)
-    {
-      System.out.println(
-           "Illegal DN <" + dn + "> threw the wrong type of exception");
+    } 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");
+    throw new RuntimeException("Illegal DN <" + dn
+        + "> did not throw an exception");
   }
 
+
+
   /**
-   * Test the <CODE>compareTo</CODE> method.
-   * @param unsorted An array of string DNs in no particular order.
-   * @param sorted An array of the same DNs in sort order.
+   * 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 = "compareDNs")
-  public void testCompareTo(String[] unsorted, String[] sorted)
-       throws Exception
-  {
-    DN[] expected = new DN[sorted.length];
-    for (int i = 0; i < sorted.length; i++)
-    {
-      expected[i] = DN.decode(sorted[i]);
+  @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");
+  }
 
-    DN[] actual = new DN[unsorted.length];
-    for (int i = 0; i < unsorted.length; i++)
-    {
-      actual[i] = DN.decode(unsorted[i]);
+
+
+  /**
+   * Test the nullDN method.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testNullDN() throws Exception {
+    DN nullDN = DN.nullDN();
+
+    assertTrue(nullDN.getNumComponents() == 0);
+    assertEquals(nullDN.toNormalizedString(), "");
+  }
+
+
+
+  /**
+   * Test the isNullDN method.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testIsNullDNWithNullDN() throws Exception {
+    DN nullDN = DN.nullDN();
+    assertTrue(nullDN.isNullDN());
+  }
+
+
+
+  /**
+   * Test the isNullDN method.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testIsNullDNWithNonNullDN() throws Exception {
+    DN dn = DN.decode("dc=com");
+    assertFalse(dn.isNullDN());
+  }
+
+
+
+  /**
+   * DN test data provider.
+   *
+   * @return The array of test DN strings.
+   */
+  @DataProvider(name = "createNumComponentsTestData")
+  public Object[][] createNumComponentsTestData() {
+    return new Object[][] { { "", 0 }, { "dc=com", 1 },
+        { "dc=opends,dc=com", 2 },
+        { "dc=world,dc=opends,dc=com", 3 },
+        { "dc=hello,dc=world,dc=opends,dc=com", 4 }, };
+  }
+
+
+
+  /**
+   * Test the getNumComponents method.
+   *
+   * @param s
+   *          The test DN string.
+   * @param sz
+   *          The expected number of RDNs.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createNumComponentsTestData")
+  public void testNumComponents(String s, int sz) throws Exception {
+    DN dn = DN.decode(s);
+    assertEquals(dn.getNumComponents(), sz);
+  }
+
+
+
+  /**
+   * DN test data provider.
+   *
+   * @return The array of test DN strings.
+   */
+  @DataProvider(name = "createParentAndRDNTestData")
+  public Object[][] createParentAndRDNTestData() {
+    return new Object[][] {
+        { "", null, null },
+        { "dc=com", null, "dc=com" },
+        { "dc=opends,dc=com", "dc=com", "dc=opends" },
+        { "dc=world,dc=opends,dc=com", "dc=opends,dc=com", "dc=world" },
+        { "dc=hello,dc=world,dc=opends,dc=com",
+            "dc=world,dc=opends,dc=com", "dc=hello" }, };
+  }
+
+
+
+  /**
+   * Test the getParent method.
+   *
+   * @param s
+   *          The test DN string.
+   * @param p
+   *          The expected parent.
+   * @param r
+   *          The expected rdn.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createParentAndRDNTestData")
+  public void testGetParent(String s, String p, String r)
+      throws Exception {
+    DN dn = DN.decode(s);
+    DN parent = (p != null ? DN.decode(p) : null);
+
+    assertEquals(dn.getParent(), parent, "For DN " + s);
+  }
+
+
+
+  /**
+   * Test the getParent method's interaction with other methods.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testGetParentInteraction() throws Exception {
+    DN c = DN.decode("dc=foo,dc=bar,dc=opends,dc=org");
+    DN e = DN.decode("dc=bar,dc=opends,dc=org");
+    DN p = c.getParent();
+
+    assertFalse(p.isNullDN());
+
+    assertEquals(p.getNumComponents(), 3);
+
+    assertEquals(p.compareTo(c), -1);
+    assertEquals(c.compareTo(p), 1);
+
+    assertTrue(p.isAncestorOf(c));
+    assertFalse(c.isAncestorOf(p));
+
+    assertTrue(c.isDescendantOf(p));
+    assertFalse(p.isDescendantOf(c));
+
+    assertEquals(p, e);
+    assertEquals(p.hashCode(), e.hashCode());
+
+    assertEquals(p.toNormalizedString(), e.toNormalizedString());
+    assertEquals(p.toString(), e.toString());
+
+    assertEquals(p.getRDN(), RDN.decode("dc=bar"));
+
+    assertEquals(p.getRDN(0), RDN.decode("dc=bar"));
+    assertEquals(p.getRDN(1), RDN.decode("dc=opends"));
+    assertEquals(p.getRDN(2), RDN.decode("dc=org"));
+
+    assertEquals(p.getParent(), DN.decode("dc=opends,dc=org"));
+    assertEquals(p.getParent(), e.getParent());
+
+    assertEquals(p.concat(RDN.decode("dc=foo")), DN
+        .decode("dc=foo,dc=bar,dc=opends,dc=org"));
+    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"));
+  }
+
+
+
+  /**
+   * Test the getRDN method.
+   *
+   * @param s
+   *          The test DN string.
+   * @param p
+   *          The expected parent.
+   * @param r
+   *          The expected rdn.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createParentAndRDNTestData")
+  public void testGetRDN(String s, String p, String r)
+      throws Exception {
+    DN dn = DN.decode(s);
+    RDN rdn = (r != null ? RDN.decode(r) : null);
+
+    assertEquals(dn.getRDN(), rdn, "For DN " + s);
+  }
+
+
+
+  /**
+   * DN test data provider.
+   *
+   * @return The array of test DN strings.
+   */
+  @DataProvider(name = "createRDNTestData")
+  public Object[][] createRDNTestData() {
+    return new Object[][] { { "dc=com", 0, "dc=com" },
+        { "dc=opends,dc=com", 0, "dc=opends" },
+        { "dc=opends,dc=com", 1, "dc=com" },
+        { "dc=hello,dc=world,dc=opends,dc=com", 0, "dc=hello" },
+        { "dc=hello,dc=world,dc=opends,dc=com", 1, "dc=world" },
+        { "dc=hello,dc=world,dc=opends,dc=com", 2, "dc=opends" },
+        { "dc=hello,dc=world,dc=opends,dc=com", 3, "dc=com" }, };
+  }
+
+
+
+  /**
+   * Test the getRDN indexed method.
+   *
+   * @param s
+   *          The test DN string.
+   * @param i
+   *          The RDN index.
+   * @param r
+   *          The expected rdn.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createRDNTestData")
+  public void testGetRDNIndexed(String s, int i, String r)
+      throws Exception {
+    DN dn = DN.decode(s);
+    RDN rdn = RDN.decode(r);
+
+    assertEquals(dn.getRDN(i), rdn, "For DN " + s);
+  }
+
+
+
+  /**
+   * DN test data provider.
+   *
+   * @return The array of test DN strings.
+   */
+  @DataProvider(name = "createRDNIllegalTestData")
+  public Object[][] createRDNIllegalTestData() {
+    return new Object[][] { { "", 0 }, { "", -1 }, { "", 1 },
+        { "dc=com", -1 }, { "dc=com", 1 },
+        { "dc=opends,dc=com", -1 }, { "dc=opends,dc=com", 2 },
+        { "dc=hello,dc=world,dc=opends,dc=com", -1 },
+        { "dc=hello,dc=world,dc=opends,dc=com", 4 }, };
+  }
+
+
+
+  /**
+   * Test the getRDN indexed method with illegal indexes.
+   *
+   * @param s
+   *          The test DN string.
+   * @param i
+   *          The illegal RDN index.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createRDNIllegalTestData", expectedExceptions = IndexOutOfBoundsException.class)
+  public void testGetRDNIndexedException(String s, int i)
+      throws Exception {
+    DN dn = DN.decode(s);
+
+    // Shoudld throw.
+    dn.getRDN(i);
+
+    fail("Excepted exception for RDN index " + i + " in DN " + s);
+  }
+
+
+
+  /**
+   * Concat DN test data provider.
+   *
+   * @return The array of test data.
+   */
+  @DataProvider(name = "createConcatDNTestData")
+  public Object[][] createConcatDNTestData() {
+    return new Object[][] {
+        { "", "", "" },
+        { "", "dc=org", "dc=org" },
+        { "", "dc=opends,dc=org", "dc=opends,dc=org" },
+        { "dc=org", "", "dc=org" },
+        { "dc=org", "dc=opends", "dc=opends,dc=org" },
+        { "dc=org", "dc=foo,dc=opends", "dc=foo,dc=opends,dc=org" },
+        { "dc=opends,dc=org", "", "dc=opends,dc=org" },
+        { "dc=opends,dc=org", "dc=foo", "dc=foo,dc=opends,dc=org" },
+        { "dc=opends,dc=org", "dc=bar,dc=foo",
+            "dc=bar,dc=foo,dc=opends,dc=org" }, };
+  }
+
+
+
+  /**
+   * Test the concat(DN) 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 testConcatDN(String s, String l, String e)
+      throws Exception {
+    DN dn = DN.decode(s);
+    DN localName = DN.decode(l);
+    DN expected = DN.decode(e);
+
+    assertEquals(dn.concat(localName), expected);
+  }
+
+
+
+  /**
+   * Test the concat(DN) method.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(expectedExceptions = { NullPointerException.class,
+      AssertionError.class })
+  public void testConcatDNException() throws Exception {
+    DN dn = DN.decode("dc=org");
+    dn.concat((DN) null);
+  }
+
+
+
+  /**
+   * Test the concat(DN) method's interaction with other methods.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testConcatDNInteraction() throws Exception {
+    DN p = DN.decode("dc=opends,dc=org");
+    DN l = DN.decode("dc=foo,dc=bar");
+    DN e = DN.decode("dc=foo,dc=bar,dc=opends,dc=org");
+    DN c = p.concat(l);
+
+    assertFalse(c.isNullDN());
+
+    assertEquals(c.getNumComponents(), 4);
+
+    assertEquals(c.compareTo(p), 1);
+    assertEquals(p.compareTo(c), -1);
+
+    assertTrue(p.isAncestorOf(c));
+    assertFalse(c.isAncestorOf(p));
+
+    assertTrue(c.isDescendantOf(p));
+    assertFalse(p.isDescendantOf(c));
+
+    assertEquals(c, e);
+    assertEquals(c.hashCode(), e.hashCode());
+
+    assertEquals(c.toNormalizedString(), e.toNormalizedString());
+    assertEquals(c.toString(), e.toString());
+
+    assertEquals(c.getRDN(), RDN.decode("dc=foo"));
+
+    assertEquals(c.getRDN(0), RDN.decode("dc=foo"));
+    assertEquals(c.getRDN(1), RDN.decode("dc=bar"));
+    assertEquals(c.getRDN(2), RDN.decode("dc=opends"));
+    assertEquals(c.getRDN(3), RDN.decode("dc=org"));
+
+    assertEquals(c.getParent(), DN.decode("dc=bar,dc=opends,dc=org"));
+    assertEquals(c.getParent(), e.getParent());
+
+    assertEquals(c.concat(RDN.decode("dc=xxx")), DN
+        .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"));
+  }
+
+
+
+  /**
+   * Concat RDN test data provider.
+   *
+   * @return The array of test data.
+   */
+  @DataProvider(name = "createConcatRDNTestData")
+  public Object[][] createConcatRDNTestData() {
+    return new Object[][] { { "", "dc=org", "dc=org" },
+        { "dc=org", "dc=opends", "dc=opends,dc=org" },
+        { "dc=opends,dc=org", "dc=foo", "dc=foo,dc=opends,dc=org" }, };
+  }
+
+
+
+  /**
+   * Test the concat(RDN...) method.
+   *
+   * @param s
+   *          The test DN string.
+   * @param r
+   *          The RDN to be appended.
+   * @param e
+   *          The expected DN.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createConcatRDNTestData")
+  public void testConcatSingleRDN(String s, String r, String e)
+      throws Exception {
+    DN dn = DN.decode(s);
+    RDN rdn = RDN.decode(r);
+    DN expected = DN.decode(e);
+
+    assertEquals(dn.concat(rdn), expected);
+  }
+
+
+
+  /**
+   * Test the concat(RDN...) method.
+   *
+   * @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 {
+    DN dn = DN.decode("dc=org");
+    dn.concat((RDN[]) null);
+  }
+
+
+
+  /**
+   * 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 testConcatRDNSequence1(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.
+    RDN[] rdns = new RDN[localName.getNumComponents()];
+    for (int i = 0; i < localName.getNumComponents(); i++) {
+      rdns[i] = localName.getRDN(i);
     }
 
-    Arrays.sort(actual);
+    assertEquals(dn.concat(rdns), expected);
+  }
+
+
+
+  /**
+   * 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);
   }
@@ -304,38 +944,427 @@
 
 
   /**
-   * Test the <CODE>duplicate</CODE> method.
+   * Get local name test data provider.
+   *
+   * @return The array of test data.
+   */
+  @DataProvider(name = "createGetLocalNameTestData")
+  public Object[][] createGetLocalNameTestData() {
+    return new Object[][] {
+        { "", 0, -1, "" },
+        { "", 0, 0, "" },
+        { "dc=org", 0, -1, "dc=org" },
+        { "dc=org", 1, -1, "" },
+        { "dc=org", 0, 0, "" },
+        { "dc=org", 0, 1, "dc=org" },
+        { "dc=org", 1, 1, "" },
+        { "dc=opends,dc=org", 0, -1, "dc=opends,dc=org" },
+        { "dc=opends,dc=org", 1, -1, "dc=opends" },
+        { "dc=opends,dc=org", 2, -1, "" },
+        { "dc=opends,dc=org", 0, 0, "" },
+        { "dc=opends,dc=org", 0, 1, "dc=org" },
+        { "dc=opends,dc=org", 0, 2, "dc=opends,dc=org" },
+        { "dc=opends,dc=org", 1, 1, "" },
+        { "dc=opends,dc=org", 1, 2, "dc=opends" },
+        { "dc=opends,dc=org", 2, 2, "" },
+        { "dc=foo,dc=opends,dc=org", 0, -1, "dc=foo,dc=opends,dc=org" },
+        { "dc=foo,dc=opends,dc=org", 1, -1, "dc=foo,dc=opends" },
+        { "dc=foo,dc=opends,dc=org", 2, -1, "dc=foo" },
+        { "dc=foo,dc=opends,dc=org", 3, -1, "" },
+        { "dc=foo,dc=opends,dc=org", 0, 0, "" },
+        { "dc=foo,dc=opends,dc=org", 0, 1, "dc=org" },
+        { "dc=foo,dc=opends,dc=org", 0, 2, "dc=opends,dc=org" },
+        { "dc=foo,dc=opends,dc=org", 0, 3, "dc=foo,dc=opends,dc=org" },
+        { "dc=foo,dc=opends,dc=org", 1, 1, "" },
+        { "dc=foo,dc=opends,dc=org", 1, 2, "dc=opends" },
+        { "dc=foo,dc=opends,dc=org", 1, 3, "dc=foo,dc=opends" },
+        { "dc=foo,dc=opends,dc=org", 2, 2, "" },
+        { "dc=foo,dc=opends,dc=org", 2, 3, "dc=foo" },
+        { "dc=foo,dc=opends,dc=org", 3, 3, "" }, };
+  }
+
+
+
+  /**
+   * 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 testDuplicate() throws Exception
-  {
-    String s = "dc=example,dc=com";
-    DN orig = DN.decode(s);
-    DN dup = orig.duplicate();
+  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);
 
-    // The duplicate and the original should compare equal.
-    assertEquals(dup, orig);
+    assertFalse(l.isNullDN());
 
-    // Alter the duplicate.
-    RDN[] origRDNs = dup.getRDNComponents();
-    RDN[] dupRDNs = new RDN[origRDNs.length];
-    System.arraycopy(origRDNs, 0, dupRDNs, 0, origRDNs.length);
+    assertEquals(l.getNumComponents(), 2);
 
-    AttributeValue[] values = new AttributeValue[1];
-    values[0] = origRDNs[0].getAttributeValues()[0];
-    values[0] = new AttributeValue(origRDNs[0].getAttributeTypes()[0],
-                                   new ASN1OctetString("modified"));
-    dupRDNs[0] = new RDN(origRDNs[0].getAttributeTypes(),
-                         origRDNs[0].getAttributeNames(),
-                         values);
+    assertEquals(l.compareTo(e), 0);
+    assertEquals(e.compareTo(l), 0);
 
-    dup.setRDNComponents(dupRDNs);
+    assertTrue(l.isAncestorOf(DN.decode("dc=foo,dc=bar,dc=opends")));
+    assertFalse(DN.decode("dc=foo,dc=bar,dc=opends").isAncestorOf(l));
 
-    // Check that the duplicate and the original are different.
-    String msg = String.format("<%s> and <%s>", dup, orig);
-    assertTrue(!dup.equals(orig), msg);
+    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.
+   */
+  @DataProvider(name = "createIsAncestorOfTestData")
+  public Object[][] createIsAncestorOfTestData() {
+    return new Object[][] {
+        { "", "", true },
+        { "", "dc=org", true },
+        { "", "dc=opends,dc=org", true },
+        { "", "dc=foo,dc=opends,dc=org", true },
+        { "dc=org", "", false },
+        { "dc=org", "dc=org", true },
+        { "dc=org", "dc=opends,dc=org", true },
+        { "dc=org", "dc=foo,dc=opends,dc=org", true },
+        { "dc=opends,dc=org", "", false },
+        { "dc=opends,dc=org", "dc=org", false },
+        { "dc=opends,dc=org", "dc=opends,dc=org", true },
+        { "dc=opends,dc=org", "dc=foo,dc=opends,dc=org", true },
+        { "dc=foo,dc=opends,dc=org", "", false },
+        { "dc=foo,dc=opends,dc=org", "dc=org", false },
+        { "dc=foo,dc=opends,dc=org", "dc=opends,dc=org", false },
+        { "dc=foo,dc=opends,dc=org", "dc=foo,dc=opends,dc=org", true },
+        { "dc=org", "dc=com", false },
+        { "dc=opends,dc=org", "dc=foo,dc=org", false },
+        { "dc=opends,dc=org", "dc=opends,dc=com", false }, };
+  }
+
+
+
+  /**
+   * Test the isAncestoryOf method.
+   *
+   * @param s
+   *          The test DN string.
+   * @param d
+   *          The dn parameter.
+   * @param e
+   *          The expected result.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createIsAncestorOfTestData")
+  public void testIsAncestorOf(String s, String d, boolean e)
+      throws Exception {
+    DN dn = DN.decode(s);
+    DN other = DN.decode(d);
+
+    assertEquals(dn.isAncestorOf(other), e, s + " isAncestoryOf " + d);
+  }
+
+
+
+  /**
+   * Test the isAncestorOf method.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(expectedExceptions = { NullPointerException.class,
+      AssertionError.class })
+  public void testIsAncestorOfException() throws Exception {
+    DN dn = DN.decode("dc=com");
+    dn.isAncestorOf(null);
+  }
+
+
+
+  /**
+   * Is descendant of test data provider.
+   *
+   * @return The array of test data.
+   */
+  @DataProvider(name = "createIsDescendantOfTestData")
+  public Object[][] createIsDescendantOfTestData() {
+    return new Object[][] {
+        { "", "", true },
+        { "", "dc=org", false },
+        { "", "dc=opends,dc=org", false },
+        { "", "dc=foo,dc=opends,dc=org", false },
+        { "dc=org", "", true },
+        { "dc=org", "dc=org", true },
+        { "dc=org", "dc=opends,dc=org", false },
+        { "dc=org", "dc=foo,dc=opends,dc=org", false },
+        { "dc=opends,dc=org", "", true },
+        { "dc=opends,dc=org", "dc=org", true },
+        { "dc=opends,dc=org", "dc=opends,dc=org", true },
+        { "dc=opends,dc=org", "dc=foo,dc=opends,dc=org", false },
+        { "dc=foo,dc=opends,dc=org", "", true },
+        { "dc=foo,dc=opends,dc=org", "dc=org", true },
+        { "dc=foo,dc=opends,dc=org", "dc=opends,dc=org", true },
+        { "dc=foo,dc=opends,dc=org", "dc=foo,dc=opends,dc=org", true },
+        { "dc=org", "dc=com", false },
+        { "dc=opends,dc=org", "dc=foo,dc=org", false },
+        { "dc=opends,dc=org", "dc=opends,dc=com", false }, };
+  }
+
+
+
+  /**
+   * Test the isDescendantOf method.
+   *
+   * @param s
+   *          The test DN string.
+   * @param d
+   *          The dn parameter.
+   * @param e
+   *          The expected result.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createIsDescendantOfTestData")
+  public void testIsDescendantOf(String s, String d, boolean e)
+      throws Exception {
+    DN dn = DN.decode(s);
+    DN other = DN.decode(d);
+
+    assertEquals(dn.isDescendantOf(other), e, s + " isDescendantOf "
+        + d);
+  }
+
+
+
+  /**
+   * Test the isDescendantOf method.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(expectedExceptions = { NullPointerException.class,
+      AssertionError.class })
+  public void testIsDescendantOfException() throws Exception {
+    DN dn = DN.decode("dc=com");
+    dn.isDescendantOf(null);
+  }
+
+
+
+  /**
+   * DN equality test data provider.
+   *
+   * @return The array of test DN strings.
+   */
+  @DataProvider(name = "createDNEqualityData")
+  public Object[][] createDNEqualityData() {
+    return new Object[][] {
+        { "cn=hello world,dc=com", "cn=hello world,dc=com", 0 },
+        { "cn=hello world,dc=com", "CN=hello world,dc=com", 0 },
+        { "cn=hello   world,dc=com", "cn=hello world,dc=com", 0 },
+        { "  cn =  hello world  ,dc=com", "cn=hello world,dc=com", 0 },
+        { "cn=hello world\\ ,dc=com", "cn=hello world,dc=com", 0 },
+        { "cn=HELLO WORLD,dc=com", "cn=hello world,dc=com", 0 },
+        { "cn=HELLO+sn=WORLD,dc=com", "sn=world+cn=hello,dc=com", 0 },
+        { "x-test-integer-type=10,dc=com",
+            "x-test-integer-type=9,dc=com", 1 },
+        { "x-test-integer-type=999,dc=com",
+            "x-test-integer-type=1000,dc=com", -1 },
+        { "x-test-integer-type=-1,dc=com",
+            "x-test-integer-type=0,dc=com", -1 },
+        { "x-test-integer-type=0,dc=com",
+            "x-test-integer-type=-1,dc=com", 1 },
+        { "cn=aaa,dc=com", "cn=aaaa,dc=com", -1 },
+        { "cn=AAA,dc=com", "cn=aaaa,dc=com", -1 },
+        { "cn=aaa,dc=com", "cn=AAAA,dc=com", -1 },
+        { "cn=aaaa,dc=com", "cn=aaa,dc=com", 1 },
+        { "cn=AAAA,dc=com", "cn=aaa,dc=com", 1 },
+        { "cn=aaaa,dc=com", "cn=AAA,dc=com", 1 },
+        { "cn=aaab,dc=com", "cn=aaaa,dc=com", 1 },
+        { "cn=aaaa,dc=com", "cn=aaab,dc=com", -1 },
+        { "dc=aaa,dc=aaa", "dc=bbb", -1 },
+        { "dc=bbb,dc=aaa", "dc=bbb", -1 },
+        { "dc=ccc,dc=aaa", "dc=bbb", -1 },
+        { "dc=aaa,dc=bbb", "dc=bbb", 1 },
+        { "dc=bbb,dc=bbb", "dc=bbb", 1 },
+        { "dc=ccc,dc=bbb", "dc=bbb", 1 },
+        { "dc=aaa,dc=ccc", "dc=bbb", 1 },
+        { "dc=bbb,dc=ccc", "dc=bbb", 1 },
+        { "dc=ccc,dc=ccc", "dc=bbb", 1 },
+
+    };
+  }
+
+
+
+  /**
+   * Test DN equality
+   *
+   * @param first
+   *          First DN to compare.
+   * @param second
+   *          Second DN to compare.
+   * @param result
+   *          Expected comparison result.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createDNEqualityData")
+  public void testEquality(String first, String second, int result)
+      throws Exception {
+    DN dn1 = DN.decode(first);
+    DN dn2 = DN.decode(second);
+
+    if (result == 0) {
+      assertTrue(dn1.equals(dn2), "DN equality for <" + first
+          + "> and <" + second + ">");
+    } else {
+      assertFalse(dn1.equals(dn2), "DN equality for <" + first
+          + "> and <" + second + ">");
+    }
+  }
+
+
+
+  /**
+   * Test DN hashCode
+   *
+   * @param first
+   *          First DN to compare.
+   * @param second
+   *          Second DN to compare.
+   * @param result
+   *          Expected comparison result.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createDNEqualityData")
+  public void testHashCode(String first, String second, int result)
+      throws Exception {
+    DN dn1 = DN.decode(first);
+    DN dn2 = DN.decode(second);
+
+    int h1 = dn1.hashCode();
+    int h2 = dn2.hashCode();
+
+    if (result == 0) {
+      if (h1 != h2) {
+        fail("Hash codes for <" + first + "> and <" + second
+            + "> should be the same.");
+      }
+    } else {
+      if (h1 == h2) {
+        fail("Hash codes for <" + first + "> and <" + second
+            + "> should be the same.");
+      }
+    }
+  }
+
+
+
+  /**
+   * Test DN compareTo
+   *
+   * @param first
+   *          First DN to compare.
+   * @param second
+   *          Second DN to compare.
+   * @param result
+   *          Expected comparison result.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createDNEqualityData")
+  public void testCompareTo(String first, String second, int result)
+      throws Exception {
+    DN dn1 = DN.decode(first);
+    DN dn2 = DN.decode(second);
+
+    int rc = dn1.compareTo(dn2);
+
+    // Normalize the result.
+    if (rc < 0) {
+      rc = -1;
+    } else if (rc > 0) {
+      rc = 1;
+    }
+
+    assertEquals(rc, result, "Comparison for <" + first + "> and <"
+        + second + ">.");
+  }
+
+
+
+  /**
+   * Test DN string decoder.
+   *
+   * @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 testToString(String rawDN, String normDN,
+      String stringDN) throws Exception {
+    DN dn = DN.decode(rawDN);
+    assertEquals(dn.toString(), stringDN);
   }
 
 }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestEntry.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestEntry.java
index cca595a..e030d25 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestEntry.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestEntry.java
@@ -284,7 +284,7 @@
         "{ base \"dc=example, dc=com\", maximum 2 }" };
 
     // Relative to the root DN.
-    DN rootDN = new DN();
+    DN rootDN = DN.nullDN();
 
     SubtreeSpecificationSet expected = new SubtreeSpecificationSet();
     for (String value : values) {
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestRDN.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestRDN.java
new file mode 100644
index 0000000..eca7aa1
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestRDN.java
@@ -0,0 +1,869 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2006 Sun Microsystems, Inc.
+ */
+package org.opends.server.types;
+
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.fail;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+
+
+/**
+ * This class defines a set of tests for the
+ * {@link org.opends.server.types.RDN} class.
+ */
+public final class TestRDN extends TypesTestCase {
+
+  // Domain component attribute type.
+  private AttributeType AT_DC;
+
+  // Common name attribute type.
+  private AttributeType AT_CN;
+
+  // Test attribute value.
+  private AttributeValue AV_DC_ORG;
+
+  // Test attribute value.
+  private AttributeValue AV_DC_OPENDS;
+
+  // Test attribute value.
+  private AttributeValue AV_CN;
+
+
+
+  /**
+   * Set up the environment for performing the tests in this suite.
+   *
+   * @throws Exception
+   *           If the environment could not be set up.
+   */
+  @BeforeClass
+  public void setUp() throws Exception {
+    // This test suite depends on having the schema available, so
+    // we'll start the server.
+    TestCaseUtils.startServer();
+
+    AT_DC = DirectoryServer.getAttributeType("dc");
+    AT_CN = DirectoryServer.getAttributeType("cn");
+
+    AttributeType dummy = DirectoryServer.getDefaultAttributeType(
+        "x-test-integer-type", DirectoryServer
+            .getDefaultIntegerSyntax());
+    DirectoryServer.getSchema().registerAttributeType(dummy, true);
+
+    AV_DC_ORG = new AttributeValue(AT_DC, "org");
+    AV_DC_OPENDS = new AttributeValue(AT_DC, "opends");
+    AV_CN = new AttributeValue(AT_DC, "hello world");
+  }
+
+
+
+  // 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.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testConstructor() throws Exception {
+    RDN rdn = RDN.create(AT_DC, AV_DC_ORG);
+
+    assertEquals(rdn.getNumValues(), 1);
+    assertEquals(rdn.getAttributeType(0), AT_DC);
+    assertEquals(rdn.getAttributeName(0), AT_DC.getNameOrOID());
+    assertEquals(rdn.getAttributeValue(0), AV_DC_ORG);
+  }
+
+
+
+  /**
+   * Test RDN construction with single AVA and a user-defined name.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testConstructorWithName() throws Exception {
+    RDN rdn = RDN.create(AT_DC, "domainComponent", 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);
+  }
+
+
+
+  /**
+   * Test RDN builder.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testBuilder() throws Exception {
+    RDN.Builder builder = RDN.createBuilder();
+
+    builder.append(AT_DC, AV_DC_ORG);
+    RDN rdn = builder.getInstance();
+
+    assertEquals(rdn.getNumValues(), 1);
+    assertEquals(rdn.getAttributeType(0), AT_DC);
+    assertEquals(rdn.getAttributeName(0), AT_DC.getNameOrOID());
+    assertEquals(rdn.getAttributeValue(0), AV_DC_ORG);
+  }
+
+
+
+  /**
+   * Test RDN builder.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testBuilderWithName() throws Exception {
+    RDN.Builder builder = RDN.createBuilder();
+
+    builder.append(AT_DC, "domainComponent", AV_DC_ORG);
+    RDN rdn = builder.getInstance();
+
+    assertEquals(rdn.getNumValues(), 1);
+    assertEquals(rdn.getAttributeType(0), AT_DC);
+    assertEquals(rdn.getAttributeName(0), "domainComponent");
+    assertEquals(rdn.getAttributeValue(0), AV_DC_ORG);
+  }
+
+
+
+  /**
+   * 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();
+
+    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);
+  }
+
+
+
+  /**
+   * RDN test data provider.
+   *
+   * @return The array of test RDN strings.
+   */
+  @DataProvider(name = "testRDNs")
+  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" },
+        { "givenName=John+cn=Doe", "cn=doe+givenname=john",
+            "givenName=John+cn=Doe" },
+        { "givenName=John\\+cn=Doe", "givenname=john\\+cn=doe",
+            "givenName=John\\+cn=Doe" },
+        { "cn=Doe\\, John", "cn=doe\\, john", "cn=Doe\\, John" },
+        { "OU=Sales+CN=J. Smith", "cn=j. smith+ou=sales",
+            "OU=Sales+CN=J. Smith" },
+        { "CN=James \\\"Jim\\\" Smith\\, III",
+            "cn=james \\\"jim\\\" smith\\, iii",
+            "CN=James \\\"Jim\\\" Smith\\, III" },
+        { "CN=Before\\0dAfter", "cn=before\\0dafter",
+            "CN=Before\\0dAfter" },
+        { "1.3.6.1.4.1.1466.0=#04024869",
+            "1.3.6.1.4.1.1466.0=\\04\\02hi",
+            "1.3.6.1.4.1.1466.0=\\04\\02Hi" },
+        { "CN=Lu\\C4\\8Di\\C4\\87", "cn=lu\\c4\\8di\\c4\\87",
+            "CN=Lu\\c4\\8di\\c4\\87" },
+        { "ou=\\e5\\96\\b6\\e6\\a5\\ad\\e9\\83\\a8",
+            "ou=\\e5\\96\\b6\\e6\\a5\\ad\\e9\\83\\a8",
+            "ou=\\e5\\96\\b6\\e6\\a5\\ad\\e9\\83\\a8" },
+        { "photo=\\ john \\ ", "photo=\\ john \\ ",
+            "photo=\\ john \\ " },
+        { "AB-global=", "ab-global=", "AB-global=" },
+        { "cn=John+a=", "a=+cn=john", "cn=John+a=" },
+        { "OID.1.3.6.1.4.1.1466.0=#04024869",
+            "1.3.6.1.4.1.1466.0=\\04\\02hi",
+            "1.3.6.1.4.1.1466.0=\\04\\02Hi" },
+        { "O=\"Sue, Grabbit and Runn\"", "o=sue\\, grabbit and runn",
+            "O=Sue\\, Grabbit and Runn" }, };
+  }
+
+
+
+  /**
+   * Test RDN 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 testDecodeString(String rawRDN, String normRDN,
+      String stringRDN) throws Exception {
+    RDN rdn = RDN.decode(rawRDN);
+    assertEquals(rdn.toNormalizedString(), normRDN);
+  }
+
+
+
+  /**
+   * 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" },
+        { "1.3.6.1.4.1.1466..0=#04024869" }, };
+  }
+
+
+
+  /**
+   * Test RDN 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 testDecodeString(String rawRDN) throws Exception {
+    RDN.decode(rawRDN);
+
+    fail("Expected exception for value \"" + rawRDN + "\"");
+  }
+
+
+
+  /**
+   * 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
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testGetAttributeName() throws Exception {
+    RDN.Builder builder = RDN.createBuilder();
+
+    builder.append(AT_DC, AV_DC_ORG);
+    builder.append(AT_CN, AV_CN);
+    RDN rdn = builder.getInstance();
+
+    assertEquals(rdn.getAttributeName(0), AT_DC.getNameOrOID());
+    assertEquals(rdn.getAttributeName(1), AT_CN.getNameOrOID());
+  }
+
+
+
+  /**
+   * Test getAttributeName IndexOutOfBoundsException.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @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.getAttributeName(1);
+  }
+
+
+
+  /**
+   * Test getAttributeType.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testGetAttributeType() throws Exception {
+    RDN.Builder builder = RDN.createBuilder();
+
+    builder.append(AT_DC, AV_DC_ORG);
+    builder.append(AT_CN, AV_CN);
+    RDN rdn = builder.getInstance();
+
+    assertEquals(rdn.getAttributeType(0), AT_DC);
+    assertEquals(rdn.getAttributeType(1), AT_CN);
+  }
+
+
+
+  /**
+   * Test getAttributeType IndexOutOfBoundsException.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @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.getAttributeType(1);
+  }
+
+
+
+  /**
+   * Test getAttributeValue.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testGetAttributeValue() throws Exception {
+    RDN.Builder builder = RDN.createBuilder();
+
+    builder.append(AT_DC, AV_DC_ORG);
+    builder.append(AT_CN, AV_CN);
+    RDN rdn = builder.getInstance();
+
+    assertEquals(rdn.getAttributeValue(0), AV_DC_ORG);
+    assertEquals(rdn.getAttributeValue(1), AV_CN);
+  }
+
+
+
+  /**
+   * Test getAttributeValue IndexOutOfBoundsException.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @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.getAttributeValue(1);
+  }
+
+
+
+  /**
+   * Test getAttributeValue.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testGetAttributeValueByType() throws Exception {
+    RDN.Builder builder = RDN.createBuilder();
+
+    builder.append(AT_DC, AV_DC_ORG);
+    RDN rdn = builder.getInstance();
+
+    assertEquals(rdn.getAttributeValue(AT_DC), AV_DC_ORG);
+    assertNull(rdn.getAttributeValue(AT_CN));
+  }
+
+
+
+  /**
+   * Test getNumValues.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testGetNumValues() throws Exception {
+    RDN.Builder builder = RDN.createBuilder();
+
+    builder.append(AT_DC, AV_DC_ORG);
+    RDN rdn = builder.getInstance();
+    assertEquals(rdn.getNumValues(), 1);
+
+    builder.append(AT_CN, AV_CN);
+    rdn = builder.getInstance();
+    assertEquals(rdn.getNumValues(), 2);
+  }
+
+
+
+  /**
+   * Test hasAttributeType.
+   *
+   * @throws Exception
+   *           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();
+
+    assertTrue(rdn.hasAttributeType(AT_DC));
+    assertFalse(rdn.hasAttributeType(AT_CN));
+  }
+
+
+
+  /**
+   * Test isMultiValued.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testIsMultiValued() throws Exception {
+    RDN.Builder builder = RDN.createBuilder();
+
+    builder.append(AT_DC, AV_DC_ORG);
+    RDN rdn = builder.getInstance();
+    assertFalse(rdn.isMultiValued());
+
+    builder.append(AT_CN, AV_CN);
+    rdn = builder.getInstance();
+    assertTrue(rdn.isMultiValued());
+  }
+
+
+
+  /**
+   * Test RDN 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 testToString(String rawRDN, String normRDN,
+      String stringRDN) throws Exception {
+    RDN rdn = RDN.decode(rawRDN);
+    assertEquals(rdn.toString(), stringRDN);
+  }
+
+
+
+  /**
+   * RDN equality test data provider.
+   *
+   * @return The array of test RDN strings.
+   */
+  @DataProvider(name = "createRDNEqualityData")
+  public Object[][] createRDNEqualityData() {
+    return new Object[][] {
+        { "cn=hello world", "cn=hello world", 0 },
+        { "cn=hello world", "CN=hello world", 0 },
+        { "cn=hello   world", "cn=hello world", 0 },
+        { "  cn =  hello world  ", "cn=hello world", 0 },
+        { "cn=hello world\\ ", "cn=hello world", 0 },
+        { "cn=HELLO WORLD", "cn=hello world", 0 },
+        { "cn=HELLO+sn=WORLD", "sn=world+cn=hello", 0 },
+        { "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 },
+        { "x-test-integer-type=0", "x-test-integer-type=-1", 1 },
+        { "cn=aaa", "cn=aaaa", -1 }, { "cn=AAA", "cn=aaaa", -1 },
+        { "cn=aaa", "cn=AAAA", -1 }, { "cn=aaaa", "cn=aaa", 1 },
+        { "cn=AAAA", "cn=aaa", 1 }, { "cn=aaaa", "cn=AAA", 1 },
+        { "cn=aaab", "cn=aaaa", 1 }, { "cn=aaaa", "cn=aaab", -1 } };
+  }
+
+
+
+  /**
+   * Test RDN equality
+   *
+   * @param first
+   *          First RDN to compare.
+   * @param second
+   *          Second RDN to compare.
+   * @param result
+   *          Expected comparison result.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createRDNEqualityData")
+  public void testEquality(String first, String second, int result)
+      throws Exception {
+    RDN rdn1 = RDN.decode(first);
+    RDN rdn2 = RDN.decode(second);
+
+    if (result == 0) {
+      assertTrue(rdn1.equals(rdn2), "RDN equality for <" + first
+          + "> and <" + second + ">");
+    } else {
+      assertFalse(rdn1.equals(rdn2), "RDN equality for <" + first
+          + "> and <" + second + ">");
+    }
+  }
+
+
+
+  /**
+   * Test RDN hashCode
+   *
+   * @param first
+   *          First RDN to compare.
+   * @param second
+   *          Second RDN to compare.
+   * @param result
+   *          Expected comparison result.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createRDNEqualityData")
+  public void testHashCode(String first, String second, int result)
+      throws Exception {
+    RDN rdn1 = RDN.decode(first);
+    RDN rdn2 = RDN.decode(second);
+
+    int h1 = rdn1.hashCode();
+    int h2 = rdn2.hashCode();
+
+    if (result == 0) {
+      if (h1 != h2) {
+        fail("Hash codes for <" + first + "> and <" + second
+            + "> should be the same.");
+      }
+    } else {
+      if (h1 == h2) {
+        fail("Hash codes for <" + first + "> and <" + second
+            + "> should be the same.");
+      }
+    }
+  }
+
+
+
+  /**
+   * Test RDN compareTo
+   *
+   * @param first
+   *          First RDN to compare.
+   * @param second
+   *          Second RDN to compare.
+   * @param result
+   *          Expected comparison result.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "createRDNEqualityData")
+  public void testCompareTo(String first, String second, int result)
+      throws Exception {
+    RDN rdn1 = RDN.decode(first);
+    RDN rdn2 = RDN.decode(second);
+
+    int rc = rdn1.compareTo(rdn2);
+
+    // Normalize the result.
+    if (rc < 0) {
+      rc = -1;
+    } else if (rc > 0) {
+      rc = 1;
+    }
+
+    assertEquals(rc, result, "Comparison for <" + first + "> and <"
+        + second + ">.");
+  }
+
+}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestAddChangeRecordEntry.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestAddChangeRecordEntry.java
index a174139..01882ec 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestAddChangeRecordEntry.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestAddChangeRecordEntry.java
@@ -83,7 +83,7 @@
   @Test(expectedExceptions = { NullPointerException.class,
                                AssertionError.class })
   public void testConstructorNullDN() throws Exception {
-    AddChangeRecordEntry entry = new AddChangeRecordEntry(null, attributes);
+    new AddChangeRecordEntry(null, attributes);
   }
 
   /**
@@ -94,10 +94,10 @@
    */
   @Test
   public void testConstructorEmptyDN() throws Exception {
-    AddChangeRecordEntry entry = new AddChangeRecordEntry(new DN(),
+    AddChangeRecordEntry entry = new AddChangeRecordEntry(DN.nullDN(),
         attributes);
 
-    Assert.assertEquals(entry.getDN(), new DN());
+    Assert.assertEquals(entry.getDN(), DN.nullDN());
   }
 
   /**
@@ -125,7 +125,7 @@
    */
   @Test
   public void testChangeOperationType() throws Exception {
-    AddChangeRecordEntry entry = new AddChangeRecordEntry(new DN(), attributes);
+    AddChangeRecordEntry entry = new AddChangeRecordEntry(DN.nullDN(), attributes);
 
     Assert.assertEquals(entry.getChangeOperationType(),
         ChangeOperationType.ADD);
@@ -140,7 +140,7 @@
   @Test
   public void testGetAttributesEmpty() throws Exception {
     Map<AttributeType, List<Attribute>> empty = Collections.emptyMap();
-    AddChangeRecordEntry entry = new AddChangeRecordEntry(new DN(), empty);
+    AddChangeRecordEntry entry = new AddChangeRecordEntry(DN.nullDN(), empty);
 
     List<Attribute> attrs = entry.getAttributes();
     Assert.assertEquals(attrs.size(), 0);
@@ -154,7 +154,7 @@
    */
   @Test
   public void testGetAttributesNonEmpty() throws Exception {
-    AddChangeRecordEntry entry = new AddChangeRecordEntry(new DN(), attributes);
+    AddChangeRecordEntry entry = new AddChangeRecordEntry(DN.nullDN(), attributes);
 
     List<Attribute> attrs = entry.getAttributes();
     Assert.assertEquals(attrs.size(), 1);
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestChangeRecordEntry.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestChangeRecordEntry.java
index d7eca57..2940f7c 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestChangeRecordEntry.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestChangeRecordEntry.java
@@ -87,10 +87,9 @@
   @Test(expectedExceptions = { NullPointerException.class,
                                AssertionError.class })
   public void testConstructorNullDN() throws Exception {
-    MyChangeRecordEntry entry = new MyChangeRecordEntry(null);
+    new MyChangeRecordEntry(null);
   }
 
-
   /**
    * Tests the constructor with empty DN.
    *
@@ -99,9 +98,9 @@
    */
   @Test
   public void testConstructorEmptyDN() throws Exception {
-    MyChangeRecordEntry entry = new MyChangeRecordEntry(new DN());
+    MyChangeRecordEntry entry = new MyChangeRecordEntry(DN.nullDN());
 
-    Assert.assertEquals(entry.getDN(), new DN());
+    Assert.assertEquals(entry.getDN(), DN.nullDN());
   }
 
   /**
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestDeleteChangeRecordEntry.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestDeleteChangeRecordEntry.java
index 3edf953..2ecb885 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestDeleteChangeRecordEntry.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestDeleteChangeRecordEntry.java
@@ -63,7 +63,7 @@
   @Test(expectedExceptions = { NullPointerException.class,
                                AssertionError.class })
   public void testConstructorNullDN() throws Exception {
-    DeleteChangeRecordEntry entry = new DeleteChangeRecordEntry(null);
+    new DeleteChangeRecordEntry(null);
   }
 
   /**
@@ -74,9 +74,9 @@
    */
   @Test
   public void testConstructorEmptyDN() throws Exception {
-    DeleteChangeRecordEntry entry = new DeleteChangeRecordEntry(new DN());
+    DeleteChangeRecordEntry entry = new DeleteChangeRecordEntry(DN.nullDN());
 
-    Assert.assertEquals(entry.getDN(), new DN());
+    Assert.assertEquals(entry.getDN(), DN.nullDN());
   }
 
   /**
@@ -103,7 +103,7 @@
    */
   @Test
   public void testChangeOperationType() throws Exception {
-    DeleteChangeRecordEntry entry = new DeleteChangeRecordEntry(new DN());
+    DeleteChangeRecordEntry entry = new DeleteChangeRecordEntry(DN.nullDN());
 
     Assert.assertEquals(entry.getChangeOperationType(),
         ChangeOperationType.DELETE);
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyChangeRecordEntry.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyChangeRecordEntry.java
index db2ab0d..c7c4a0f 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyChangeRecordEntry.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyChangeRecordEntry.java
@@ -85,8 +85,7 @@
   @Test(expectedExceptions = { NullPointerException.class,
                                AssertionError.class })
   public void testConstructorNullDN() throws Exception {
-    ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(null,
-        modifications);
+    new ModifyChangeRecordEntry(null, modifications);
   }
 
   /**
@@ -97,10 +96,10 @@
    */
   @Test
   public void testConstructorEmptyDN() throws Exception {
-    ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(new DN(),
+    ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(DN.nullDN(),
         modifications);
 
-    Assert.assertEquals(entry.getDN(), new DN());
+    Assert.assertEquals(entry.getDN(), DN.nullDN());
   }
 
   /**
@@ -128,7 +127,7 @@
    */
   @Test
   public void testChangeOperationType() throws Exception {
-    ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(new DN(),
+    ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(DN.nullDN(),
         modifications);
 
     Assert.assertEquals(entry.getChangeOperationType(),
@@ -144,7 +143,7 @@
   @Test
   public void testGetModificationsEmpty() throws Exception {
     List<LDAPModification> empty = Collections.emptyList();
-    ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(new DN(),
+    ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(DN.nullDN(),
                                                                 empty);
 
     List<LDAPModification> mods = entry.getModifications();
@@ -159,7 +158,7 @@
    */
   @Test
   public void testGetModificationsNonEmpty() throws Exception {
-    ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(new DN(),
+    ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(DN.nullDN(),
         modifications);
 
     List<LDAPModification> mods = entry.getModifications();
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyDNChangeRecordEntry.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyDNChangeRecordEntry.java
index 804535b..3428a07 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyDNChangeRecordEntry.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyDNChangeRecordEntry.java
@@ -71,8 +71,7 @@
   @Test(expectedExceptions = { NullPointerException.class,
                                AssertionError.class })
   public void testConstructorNullDN() throws Exception {
-    ModifyDNChangeRecordEntry entry =
-         new ModifyDNChangeRecordEntry(null, newRDN, false, newSuperiorDN);
+    new ModifyDNChangeRecordEntry(null, newRDN, false, newSuperiorDN);
   }
 
   /**
@@ -84,9 +83,9 @@
   @Test
   public void testConstructorEmptyDN() throws Exception {
     ModifyDNChangeRecordEntry entry = new ModifyDNChangeRecordEntry(
-        new DN(), newRDN, false, newSuperiorDN);
+        DN.nullDN(), newRDN, false, newSuperiorDN);
 
-    Assert.assertEquals(entry.getDN(), new DN());
+    Assert.assertEquals(entry.getDN(), DN.nullDN());
   }
 
   /**
@@ -115,7 +114,7 @@
   @Test
   public void testChangeOperationType() throws Exception {
     ModifyDNChangeRecordEntry entry =
-         new ModifyDNChangeRecordEntry(new DN(), newRDN, false, newSuperiorDN);
+         new ModifyDNChangeRecordEntry(DN.nullDN(), newRDN, false, newSuperiorDN);
 
     Assert.assertEquals(entry.getChangeOperationType(),
         ChangeOperationType.MODIFY_DN);
@@ -130,9 +129,9 @@
   @Test
   public void testGetNewRDN() throws Exception {
     ModifyDNChangeRecordEntry entry =
-         new ModifyDNChangeRecordEntry(new DN(), newRDN, false, newSuperiorDN);
+         new ModifyDNChangeRecordEntry(DN.nullDN(), newRDN, false, newSuperiorDN);
 
-    Assert.assertEquals(entry.getNewRDN(), newRDN.duplicate());
+    Assert.assertEquals(entry.getNewRDN(), newRDN);
   }
 
   /**
@@ -144,10 +143,10 @@
   @Test
   public void testGetNewSuperiorDN() throws Exception {
     ModifyDNChangeRecordEntry entry =
-         new ModifyDNChangeRecordEntry(new DN(), newRDN, false, newSuperiorDN);
+         new ModifyDNChangeRecordEntry(DN.nullDN(), newRDN, false, newSuperiorDN);
 
     Assert
-        .assertEquals(entry.getNewSuperiorDN(), newSuperiorDN.duplicate());
+        .assertEquals(entry.getNewSuperiorDN(), newSuperiorDN);
   }
 
   /**
@@ -159,7 +158,7 @@
   @Test
   public void testDeleteOldRDNFalse() throws Exception {
     ModifyDNChangeRecordEntry entry =
-         new ModifyDNChangeRecordEntry(new DN(), newRDN, false, newSuperiorDN);
+         new ModifyDNChangeRecordEntry(DN.nullDN(), newRDN, false, newSuperiorDN);
 
     Assert.assertEquals(entry.deleteOldRDN(), false);
   }
@@ -173,7 +172,7 @@
   @Test
   public void testDeleteOldRDNTrue() throws Exception {
     ModifyDNChangeRecordEntry entry =
-         new ModifyDNChangeRecordEntry(new DN(), newRDN, true, newSuperiorDN);
+         new ModifyDNChangeRecordEntry(DN.nullDN(), newRDN, true, newSuperiorDN);
 
     Assert.assertEquals(entry.deleteOldRDN(), true);
   }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestStaticUtils.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestStaticUtils.java
index 059c3d5..a6ee8a4 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestStaticUtils.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestStaticUtils.java
@@ -293,6 +293,65 @@
   }
 
   /**
+   * Create test data for {@link StaticUtils#compare(byte[], byte[])}.
+   * 
+   * @return Returns an array of test data.
+   */
+  @DataProvider(name = "compareBytesTestData")
+  public Object[][] createCompareBytesTestData() {
+    return new Object[][] {
+        { null, null, 0 },
+        { null, new byte[0], -1 },
+        { new byte[0], null, 1 },
+        { new byte[0], new byte[0], 0 },
+        { new byte[] { 0x00 }, new byte[] { 0x00 }, 0 },
+        { new byte[] { 0x01 }, new byte[] { 0x00 }, 1 },
+        { new byte[] { 0x7f }, new byte[] { 0x00 }, 1 },
+        { new byte[] { (byte) 0x80 }, new byte[] { 0x00 }, -1 },
+        { new byte[] { (byte) 0xff }, new byte[] { 0x00 }, -1 },
+        { new byte[] { 0x00 }, new byte[] { 0x01 }, -1 },
+        { new byte[] { 0x00 }, new byte[] { 0x7f }, -1 },
+        { new byte[] { 0x00 }, new byte[] { (byte) 0x80 }, 1 },
+        { new byte[] { 0x00 }, new byte[] { (byte) 0xff }, 1 },
+        { new byte[] { 0x00, 0x01, 0x02 },
+            new byte[] { 0x00, 0x01, 0x02 }, 0 },
+        { new byte[] { 0x00, 0x01 }, new byte[] { 0x00, 0x01, 0x02 },
+            -1 },
+        { new byte[] { 0x00, 0x01, 0x02 }, new byte[] { 0x00, 0x01 },
+            1 }, };
+  }
+
+  /**
+   * Tests the {@link StaticUtils#compare(byte[], byte[])} method.
+   * 
+   * @param a
+   *          The first byte array.
+   * @param a2
+   *          The second byte array.
+   * @param expected
+   *          The expected result.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "compareBytesTestData")
+  public void testCompareBytes(byte[] a, byte[] a2, int expected)
+      throws Exception {
+    int rc = StaticUtils.compare(a, a2);
+
+    if (expected < 0 && rc >= 0) {
+      Assert.fail("Expected negative result but got " + rc);
+    }
+
+    if (expected > 0 && rc <= 0) {
+      Assert.fail("Expected positive result but got " + rc);
+    }
+
+    if (expected == 0 && rc != 0) {
+      Assert.fail("Expected zero result but got " + rc);
+    }
+  }
+
+  /**
    * Create test strings for the {@link StaticUtils#isDigit(char)}.
    *
    * @return Returns an array of test data.

--
Gitblit v1.10.0