From 47800d82adb5c09fe0fc3bd1e5dd6d7b505c026a Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Wed, 16 Mar 2016 20:45:56 +0000
Subject: [PATCH] OPENDJ-2776 Migrate remaining DN/RDN unit tests from the server

---
 opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/RDNTestCase.java     |   82 +++++-
 opendj-sdk/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties |    5 
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java             |   71 ++++-
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java             |  228 ++++++++----------
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java              |   31 +-
 opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java      |  287 +++++++++++++++++------
 6 files changed, 452 insertions(+), 252 deletions(-)

diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java
index 34691b7..935053a 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java
@@ -176,86 +176,6 @@
         }
     }
 
-    private static ByteString delimitAndEvaluateEscape(final SubstringReader reader) {
-        final StringBuilder valueBuffer = new StringBuilder();
-        StringBuilder hexBuffer = null;
-        reader.skipWhitespaces();
-
-        boolean escaped = false;
-        int trailingSpaces = 0;
-        while (reader.remaining() > 0) {
-            final char c = reader.read();
-            if (escaped) {
-                // This character is escaped.
-                if (isHexDigit(c)) {
-                    // Unicode characters.
-                    if (reader.remaining() <= 0) {
-                        throw new LocalizedIllegalArgumentException(
-                                ERR_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID.get(reader.getString()));
-                    }
-
-                    // Check the next byte for hex.
-                    final char c2 = reader.read();
-                    if (isHexDigit(c2)) {
-                        if (hexBuffer == null) {
-                            hexBuffer = new StringBuilder();
-                        }
-                        hexBuffer.append(c);
-                        hexBuffer.append(c2);
-                        // We may be at the end.
-                        if (reader.remaining() == 0) {
-                            appendHexChars(reader, valueBuffer, hexBuffer);
-                        }
-                    } else {
-                        throw new LocalizedIllegalArgumentException(
-                                ERR_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID.get(reader.getString()));
-                    }
-                } else {
-                    appendHexChars(reader, valueBuffer, hexBuffer);
-                    valueBuffer.append(c);
-                }
-                escaped = false;
-            } else if (c == '\\') {
-                escaped = true;
-                trailingSpaces = 0;
-            } else {
-                // Check for delimited chars.
-                if (c == '+' || c == ',' || c == ';') {
-                    reader.reset();
-                    appendHexChars(reader, valueBuffer, hexBuffer);
-                    valueBuffer.setLength(valueBuffer.length() - trailingSpaces);
-                    return ByteString.valueOfUtf8(valueBuffer);
-                }
-                // It is definitely not a delimiter at this point.
-                appendHexChars(reader, valueBuffer, hexBuffer);
-                valueBuffer.append(c);
-                trailingSpaces = c != ' ' ? 0 : trailingSpaces + 1;
-            }
-            reader.mark();
-        }
-
-        reader.reset();
-        valueBuffer.setLength(valueBuffer.length() - trailingSpaces);
-        return ByteString.valueOfUtf8(valueBuffer);
-    }
-
-    private static void appendHexChars(final SubstringReader reader,
-                                       final StringBuilder valueBuffer,
-                                       final StringBuilder hexBuffer) {
-        if (hexBuffer == null) {
-            return;
-        }
-        final ByteString bytes = ByteString.valueOfHex(hexBuffer.toString());
-        try {
-            valueBuffer.append(new String(bytes.toByteArray(), "UTF-8"));
-        } catch (final Exception e) {
-            throw new LocalizedIllegalArgumentException(
-                    ERR_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE.get(reader.getString(), String.valueOf(e)));
-        }
-        // Clean up the hex buffer.
-        hexBuffer.setLength(0);
-    }
-
     private static String readAttributeName(final SubstringReader reader) {
         int length = 1;
         reader.mark();
@@ -267,7 +187,6 @@
             boolean lastWasPeriod = false;
             while (reader.remaining() > 0) {
                 c = reader.read();
-
                 if (c == '=' || c == ' ') {
                     // This signals the end of the OID.
                     break;
@@ -283,29 +202,28 @@
                 }
                 length++;
             }
+            if (lastWasPeriod) {
+                throw illegalCharacter(reader, '.');
+            }
         } else if (isAlpha(c)) {
             // This must be an attribute description. In this case, we will
             // only accept alphabetic characters, numeric digits, and the
             // hyphen.
             while (reader.remaining() > 0) {
                 c = reader.read();
-
                 if (c == '=' || c == ' ') {
                     // This signals the end of the OID.
                     break;
                 } else if (!isAlpha(c) && !isDigit(c) && c != '-') {
                     throw illegalCharacter(reader, c);
                 }
-
                 length++;
             }
         } else {
             throw illegalCharacter(reader, c);
         }
-
-        reader.reset();
-
         // Return the position of the first non-space character after the token
+        reader.reset();
         return reader.read(length);
     }
 
@@ -333,47 +251,18 @@
             // Value is HEX encoded BER.
             return readAttributeValueAsBER(reader);
         } else if (c == '"') {
-            // The value should continue until the corresponding closing quotation mark.
-            return readAttributeValueWithinQuotes(reader);
+            // Legacy support for RFC 2253. The value should continue until the
+            // corresponding closing quotation mark and has the same format as
+            // RFC 4514 attribute values, except that special characters,
+            // excluding double quote and back-slash, do not need escaping.
+            reader.mark();
+            return readAttributeValue(reader, true);
         } else {
             // Otherwise, use general parsing to find the end of the value.
-            return readAttributeValueUnescaped(reader);
+            return readAttributeValue(reader, false);
         }
     }
 
-    private static ByteString readAttributeValueUnescaped(final SubstringReader reader) {
-        reader.reset();
-        final ByteString bytes = delimitAndEvaluateEscape(reader);
-        if (bytes.length() == 0) {
-            // We don't allow an empty attribute value.
-            final LocalizableMessage message =
-                    ERR_ATTR_SYNTAX_DN_INVALID_REQUIRES_ESCAPE_CHAR.get(reader.getString(), reader.pos());
-            throw new LocalizedIllegalArgumentException(message);
-        }
-        return bytes;
-    }
-
-    private static ByteString readAttributeValueWithinQuotes(final SubstringReader reader) {
-        int length = 0;
-        reader.mark();
-        while (true) {
-            if (reader.remaining() <= 0) {
-                // We hit the end of the AVA before the closing quote. That's an error.
-                throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_DN_UNMATCHED_QUOTE.get(reader.getString()));
-            }
-
-            if (reader.read() == '"') {
-                // This is the end of the value.
-                break;
-            }
-            length++;
-        }
-        reader.reset();
-        final ByteString retString = ByteString.valueOfUtf8(reader.read(length));
-        reader.read();
-        return retString;
-    }
-
     private static ByteString readAttributeValueAsBER(final SubstringReader reader) {
         // The first two characters must be hex characters.
         reader.mark();
@@ -433,6 +322,101 @@
         }
     }
 
+    private static ByteString readAttributeValue(final SubstringReader reader, final boolean isQuoted) {
+        reader.reset();
+        final ByteString bytes = delimitAndEvaluateEscape(reader, isQuoted);
+        if (bytes.length() == 0) {
+            // We don't allow an empty attribute value.
+            final LocalizableMessage message =
+                    ERR_ATTR_SYNTAX_DN_INVALID_REQUIRES_ESCAPE_CHAR.get(reader.getString(), reader.pos());
+            throw new LocalizedIllegalArgumentException(message);
+        }
+        return bytes;
+    }
+
+    private static ByteString delimitAndEvaluateEscape(final SubstringReader reader, final boolean isQuoted) {
+        final StringBuilder valueBuffer = new StringBuilder();
+        StringBuilder hexBuffer = null;
+        boolean escaped = false;
+        int trailingSpaces = 0;
+        while (reader.remaining() > 0) {
+            final char c = reader.read();
+            if (escaped) {
+                // This character is escaped.
+                if (isHexDigit(c)) {
+                    // Unicode characters.
+                    if (reader.remaining() <= 0) {
+                        throw new LocalizedIllegalArgumentException(
+                                ERR_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID.get(reader.getString()));
+                    }
+
+                    // Check the next byte for hex.
+                    final char c2 = reader.read();
+                    if (isHexDigit(c2)) {
+                        if (hexBuffer == null) {
+                            hexBuffer = new StringBuilder();
+                        }
+                        hexBuffer.append(c);
+                        hexBuffer.append(c2);
+                        // We may be at the end.
+                        if (reader.remaining() == 0) {
+                            appendHexChars(reader, valueBuffer, hexBuffer);
+                        }
+                    } else {
+                        throw new LocalizedIllegalArgumentException(
+                                ERR_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID.get(reader.getString()));
+                    }
+                } else {
+                    appendHexChars(reader, valueBuffer, hexBuffer);
+                    valueBuffer.append(c);
+                }
+                escaped = false;
+            } else if (c == '\\') {
+                escaped = true;
+                trailingSpaces = 0;
+            } else if (isQuoted && c == '"') {
+                appendHexChars(reader, valueBuffer, hexBuffer);
+                reader.skipWhitespaces();
+                return ByteString.valueOfUtf8(valueBuffer);
+            } else if (!isQuoted && (c == '+' || c == ',' || c == ';')) {
+                reader.reset();
+                appendHexChars(reader, valueBuffer, hexBuffer);
+                valueBuffer.setLength(valueBuffer.length() - trailingSpaces);
+                return ByteString.valueOfUtf8(valueBuffer);
+            } else {
+                // It is definitely not a delimiter at this point.
+                appendHexChars(reader, valueBuffer, hexBuffer);
+                valueBuffer.append(c);
+                trailingSpaces = c != ' ' ? 0 : trailingSpaces + 1;
+            }
+            reader.mark();
+        }
+        if (isQuoted) {
+            // We hit the end of the AVA before the closing quote. That's an error.
+            throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_DN_UNMATCHED_QUOTE.get(reader.getString()));
+        }
+        reader.reset();
+        valueBuffer.setLength(valueBuffer.length() - trailingSpaces);
+        return ByteString.valueOfUtf8(valueBuffer);
+    }
+
+    private static void appendHexChars(final SubstringReader reader,
+                                       final StringBuilder valueBuffer,
+                                       final StringBuilder hexBuffer) {
+        if (hexBuffer == null) {
+            return;
+        }
+        final ByteString bytes = ByteString.valueOfHex(hexBuffer.toString());
+        try {
+            valueBuffer.append(new String(bytes.toByteArray(), "UTF-8"));
+        } catch (final Exception e) {
+            throw new LocalizedIllegalArgumentException(
+                    ERR_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE.get(reader.getString(), String.valueOf(e)));
+        }
+        // Clean up the hex buffer.
+        hexBuffer.setLength(0);
+    }
+
     private final AttributeType attributeType;
     private final String attributeName;
     private final ByteString attributeValue;
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java
index ad247e0..3a1a44b 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java
@@ -24,7 +24,6 @@
 import java.util.UUID;
 import java.util.WeakHashMap;
 
-import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.i18n.LocalizedIllegalArgumentException;
 import org.forgerock.opendj.ldap.schema.CoreSchema;
 import org.forgerock.opendj.ldap.schema.Schema;
@@ -263,34 +262,33 @@
             return ROOT_DN;
         }
 
-        RDN rdn;
+        final RDN rdn;
         try {
             rdn = RDN.decode(reader, schema);
         } catch (final UnknownSchemaElementException e) {
-            final LocalizableMessage message =
-                    ERR_DN_TYPE_NOT_FOUND.get(reader.getString(), e.getMessageObject());
-            throw new LocalizedIllegalArgumentException(message);
+            throw new LocalizedIllegalArgumentException(
+                    ERR_DN_TYPE_NOT_FOUND.get(reader.getString(), e.getMessageObject()));
         }
 
-        DN parent;
         if (reader.remaining() > 0 && reader.read() == ',') {
+            reader.skipWhitespaces();
+            if (reader.remaining() == 0) {
+                throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_DN_ATTR_NO_NAME.get(reader.getString()));
+            }
             reader.mark();
             final String parentString = reader.read(reader.remaining());
-
-            parent = cache.get(parentString);
+            DN parent = cache.get(parentString);
             if (parent == null) {
                 reader.reset();
                 parent = decode(reader, schema, cache);
 
-                // Only cache parent DNs since leaf DNs are likely to make the
-                // cache to volatile.
+                // Only cache parent DNs since leaf DNs are likely to make the cache to volatile.
                 cache.put(parentString, parent);
             }
+            return new DN(schema, parent, rdn);
         } else {
-            parent = ROOT_DN;
+            return new DN(schema, ROOT_DN, rdn);
         }
-
-        return new DN(schema, parent, rdn);
     }
 
     @SuppressWarnings("serial")
@@ -320,7 +318,10 @@
      */
     private ByteString normalizedDN;
 
-    /** The RFC 4514 string representation of this DN. */
+    /**
+     * The RFC 4514 string representation of this DN. A value of {@code null}
+     * indicates that the value needs to be computed lazily.
+     */
     private String stringValue;
 
     /** The schema used to create this DN. */
@@ -337,7 +338,7 @@
         this.parent = parent;
         this.rdn = rdn;
         this.size = size;
-        this.stringValue = rdn == null ? "" : null; // Compute lazily.
+        this.stringValue = rdn == null ? "" : null;
     }
 
     /**
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java
index 85854ac..5430c05 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java
@@ -16,12 +16,14 @@
  */
 package org.forgerock.opendj.ldap;
 
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_RDN_DUPLICATE_AVA_TYPES;
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_RDN_NO_AVAS;
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_RDN_TRAILING_GARBAGE;
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_RDN_TYPE_NOT_FOUND;
 import static org.forgerock.opendj.ldap.DN.AVA_CHAR_SEPARATOR;
-import static org.forgerock.opendj.ldap.DN.RDN_CHAR_SEPARATOR;
 import static org.forgerock.opendj.ldap.DN.NORMALIZED_AVA_SEPARATOR;
 import static org.forgerock.opendj.ldap.DN.NORMALIZED_RDN_SEPARATOR;
-
-import static com.forgerock.opendj.ldap.CoreMessages.ERR_RDN_TYPE_NOT_FOUND;
+import static org.forgerock.opendj.ldap.DN.RDN_CHAR_SEPARATOR;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -72,12 +74,12 @@
      * A constant holding a special RDN having zero AVAs
      * and which sorts before any RDN other than itself.
      */
-    private static final RDN MIN_VALUE = new RDN(new AVA[0], "");
+    private static final RDN MIN_VALUE = new RDN();
     /**
      * A constant holding a special RDN having zero AVAs
      * and which sorts after any RDN other than itself.
      */
-    private static final RDN MAX_VALUE = new RDN(new AVA[0], "");
+    private static final RDN MAX_VALUE = new RDN();
 
     /**
      * Returns a constant containing a special RDN which sorts before any
@@ -160,14 +162,19 @@
      */
     public static RDN valueOf(final String rdn, final Schema schema) {
         final SubstringReader reader = new SubstringReader(rdn);
+        final RDN parsedRdn;
         try {
-            return decode(reader, schema);
+            parsedRdn = decode(reader, schema);
         } catch (final UnknownSchemaElementException e) {
             throw new LocalizedIllegalArgumentException(ERR_RDN_TYPE_NOT_FOUND.get(rdn, e.getMessageObject()));
         }
+        if (reader.remaining() > 0) {
+            throw new LocalizedIllegalArgumentException(
+                    ERR_RDN_TRAILING_GARBAGE.get(rdn, reader.read(reader.remaining())));
+        }
+        return parsedRdn;
     }
 
-    /** FIXME: ensure that the decoded RDN does not contain multiple AVAs with the same type. */
     static RDN decode(final SubstringReader reader, final Schema schema) {
         final AVA firstAVA = AVA.decode(reader, schema);
 
@@ -189,10 +196,10 @@
             } while (reader.remaining() > 0 && reader.read() == '+');
 
             reader.reset();
-            return new RDN(avas.toArray(new AVA[avas.size()]), null);
+            return new RDN(avas);
         } else {
             reader.reset();
-            return new RDN(new AVA[] { firstAVA }, null);
+            return new RDN(firstAVA);
         }
     }
 
@@ -251,9 +258,40 @@
      *            The attribute-value assertions used to build this RDN.
      * @throws NullPointerException
      *             If {@code avas} is {@code null} or contains a null ava.
+     * @throws IllegalArgumentException
+     *             If {@code avas} is empty.
      */
     public RDN(final AVA... avas) {
-        this(avas, null);
+        Reject.ifNull(avas);
+        this.avas = validateAvas(avas);
+    }
+
+    private AVA[] validateAvas(final AVA[] avas) {
+        switch (avas.length) {
+        case 0:
+            throw new LocalizedIllegalArgumentException(ERR_RDN_NO_AVAS.get());
+        case 1:
+            // Guaranteed to be valid.
+            break;
+        case 2:
+            if (avas[0].getAttributeType().equals(avas[1].getAttributeType())) {
+                throw new LocalizedIllegalArgumentException(
+                        ERR_RDN_DUPLICATE_AVA_TYPES.get(avas[0].getAttributeName()));
+            }
+            break;
+        default:
+            final AVA[] sortedAVAs = Arrays.copyOf(avas, avas.length);
+            Arrays.sort(sortedAVAs);
+            AttributeType previousAttributeType = null;
+            for (AVA ava : sortedAVAs) {
+                if (ava.getAttributeType().equals(previousAttributeType)) {
+                    throw new LocalizedIllegalArgumentException(
+                            ERR_RDN_DUPLICATE_AVA_TYPES.get(ava.getAttributeName()));
+                }
+                previousAttributeType = ava.getAttributeType();
+            }
+        }
+        return avas;
     }
 
     /**
@@ -263,15 +301,18 @@
      *            The attribute-value assertions used to build this RDN.
      * @throws NullPointerException
      *             If {@code ava} is {@code null} or contains null ava.
+     * @throws IllegalArgumentException
+     *             If {@code avas} is empty.
      */
     public RDN(Collection<AVA> avas) {
-        this(avas.toArray(new AVA[avas.size()]), null);
+        Reject.ifNull(avas);
+        this.avas = validateAvas(avas.toArray(new AVA[avas.size()]));
     }
 
-    private RDN(final AVA[] avas, final String stringValue) {
-        Reject.ifNull(avas);
-        this.avas = avas;
-        this.stringValue = stringValue;
+    // Special constructor for min/max RDN values.
+    private RDN() {
+        this.avas = new AVA[0];
+        this.stringValue = "";
     }
 
     @Override
diff --git a/opendj-sdk/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties b/opendj-sdk/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
index 27d3ed8..f2cf12d 100644
--- a/opendj-sdk/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
+++ b/opendj-sdk/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
@@ -469,6 +469,11 @@
  a subschemaSubentry attribute
 ERR_RDN_TYPE_NOT_FOUND=The RDN "%s" could not be parsed due to the \
  following reason: %s
+ERR_RDN_TRAILING_GARBAGE=The RDN "%s" could not be parsed because it \
+  contained trailing content after the RDN: "%s"
+ERR_RDN_DUPLICATE_AVA_TYPES=An RDN contained multiple components \
+  having the same attribute type "%s"
+ERR_RDN_NO_AVAS=An RDN must contain at least one attribute type and value
 ERR_DN_TYPE_NOT_FOUND=The DN "%s" could not be parsed due to the \
  following reason: %s
 ERR_ATTRIBUTE_DESCRIPTION_EMPTY=The attribute description \
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java
index 3d4d714..6c03e6f 100644
--- a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java
@@ -16,11 +16,12 @@
  */
 package org.forgerock.opendj.ldap;
 
-import static java.lang.Integer.*;
-
+import static java.lang.Integer.signum;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.fail;
-import static org.assertj.core.api.Assertions.*;
-import static org.testng.Assert.*;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
 
 import java.util.Collection;
 import java.util.Iterator;
@@ -45,13 +46,19 @@
      */
     @DataProvider(name = "createChildDNTestData")
     public Object[][] createChildDNTestData() {
-        return new Object[][] { { "", "", "" }, { "", "dc=org", "dc=org" },
-            { "", "dc=opendj,dc=org", "dc=opendj,dc=org" }, { "dc=org", "", "dc=org" },
+        // @formatter:off
+        return new Object[][] {
+            { "", "", "" },
+            { "", "dc=org", "dc=org" },
+            { "", "dc=opendj,dc=org", "dc=opendj,dc=org" },
+            { "dc=org", "", "dc=org" },
             { "dc=org", "dc=opendj", "dc=opendj,dc=org" },
             { "dc=org", "dc=foo,dc=opendj", "dc=foo,dc=opendj,dc=org" },
             { "dc=opendj,dc=org", "", "dc=opendj,dc=org" },
             { "dc=opendj,dc=org", "dc=foo", "dc=foo,dc=opendj,dc=org" },
-            { "dc=opendj,dc=org", "dc=bar,dc=foo", "dc=bar,dc=foo,dc=opendj,dc=org" }, };
+            { "dc=opendj,dc=org", "dc=bar,dc=foo", "dc=bar,dc=foo,dc=opendj,dc=org" },
+        };
+        // @formatter:on
     }
 
     /**
@@ -61,9 +68,14 @@
      */
     @DataProvider(name = "createChildRDNTestData")
     public Object[][] createChildRDNTestData() {
-        return new Object[][] { { "", "dc=org", "dc=org" },
+        // @formatter:off
+        return new Object[][] {
+            { "", "dc=org", "dc=org" },
             { "dc=org", "dc=opendj", "dc=opendj,dc=org" },
-            { "dc=opendj,dc=org", "dc=foo", "dc=foo,dc=opendj,dc=org" }, };
+            { "dc=opendj,dc=org", "dc=foo", "dc=foo,dc=opendj,dc=org" },
+        };
+        // @formatter:on
+
     }
 
     /**
@@ -73,6 +85,7 @@
      */
     @DataProvider(name = "testDNs")
     public Object[][] createData() {
+        // @formatter:off
         return new Object[][] {
             { "", "", "" },
             { "   ", "", "" },
@@ -125,8 +138,12 @@
             { "OU= Sales + CN = J. Smith ,DC=example,DC=net",
                 "cn=j. smith+ou=sales,dc=example,dc=net", "OU=Sales+CN=J. Smith,DC=example,DC=net" },
             { "cn=John+dc=", "dc=+cn=john", "cn=John+dc=" },
-            { "O=\"Sue, Grabbit and Runn\",C=US", "o=sue\\, grabbit and runn,c=us",
-                "O=Sue\\, Grabbit and Runn,C=US" }, };
+            { "O=\"Sue, Grabbit + Runn\",C=US", "o=sue\\, grabbit \\+ runn,c=us",
+                "O=Sue\\, Grabbit \\+ Runn,C=US" },
+            { "O=\"John \\\"Tiger\\\" Smith\",C=US", "o=john \\\"tiger\\\" smith,c=us",
+                "O=John \\\"Tiger\\\" Smith,C=US" },
+        };
+        // @formatter:on
     }
 
     /**
@@ -136,22 +153,19 @@
      */
     @DataProvider(name = "createDNComparisonData")
     public Object[][] createDNComparisonData() {
-        return new Object[][] { { "cn=hello world,dc=com", "cn=hello world,dc=com", 0 },
+        // @formatter:off
+        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 },
-             **/
+            { "governingStructureRule=10,dc=com", "governingStructureRule=9,dc=com", 1 },
+            { "governingStructureRule=999,dc=com", "governingStructureRule=1000,dc=com", -1 },
+            { "governingStructureRule=-1,dc=com", "governingStructureRule=0,dc=com", -1 },
+            { "governingStructureRule=0,dc=com", "governingStructureRule=-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 },
@@ -160,7 +174,9 @@
             { "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 }, { "", "dc=bbb", -1 }, { "dc=bbb", "", 1 } };
+            { "dc=ccc,dc=ccc", "dc=bbb", 1 }, { "", "dc=bbb", -1 }, { "dc=bbb", "", 1 }
+        };
+        // @formatter:on
     }
 
     /**
@@ -170,26 +186,40 @@
      */
     @DataProvider(name = "createDNEqualityData")
     public Object[][] createDNEqualityData() {
-        return new Object[][] { { "cn=hello world,dc=com", "cn=hello world,dc=com", 0 },
+        // @formatter:off
+        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 }, { "", "dc=bbb", -1 }, { "dc=bbb", "", 1 } };
+            { "governingStructureRule=10,dc=com", "governingStructureRule=9,dc=com", 1 },
+            { "governingStructureRule=999,dc=com", "governingStructureRule=1000,dc=com", -1 },
+            { "governingStructureRule=-1,dc=com", "governingStructureRule=0,dc=com", -1 },
+            { "governingStructureRule=0,dc=com", "governingStructureRule=-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 },
+            { "", "dc=bbb", -1 },
+            { "dc=bbb", "", 1 }
+        };
+        // @formatter:on
     }
 
     /**
@@ -199,7 +229,10 @@
      */
     @DataProvider(name = "illegalDNs")
     public Object[][] createIllegalData() {
-        return new Object[][] { { "manager" }, { "manager " },
+        // @formatter:off
+        return new Object[][] {
+            { "manager" },
+            { "manager " },
             { "=Jim" },
             { " =Jim" },
             { "= Jim" },
@@ -209,18 +242,41 @@
             { "cn=Jim+" },
             { "cn=Jim+manager" },
             { "cn=Jim+manager " },
-            { "cn=Jim+manager," }, // { "cn=Jim," }, { "cn=Jim,  " }, {
-                                   // "c[n]=Jim" },
-            { "_cn=Jim" }, { "c_n=Jim" }, { "cn\"=Jim" }, { "c\"n=Jim" }, { "1cn=Jim" },
-            { "cn+uid=Jim" }, { "-cn=Jim" }, { "/tmp=a" }, { "\\tmp=a" }, { "cn;lang-en=Jim" },
-            { "@cn=Jim" }, { "_name_=Jim" },
+            { "cn=Jim+manager," },
+            { "cn=Jim," },
+            { "cn=Jim,  " },
+            { "c[n]=Jim" },
+            { "_cn=Jim" },
+            { "c_n=Jim" },
+            { "cn\"=Jim" },
+            { "c\"n=Jim" },
+            { "1cn=Jim" },
+            { "cn+uid=Jim" },
+            { "-cn=Jim" },
+            { "/tmp=a" },
+            { "\\tmp=a" },
+            { "cn;lang-en=Jim" },
+            { "@cn=Jim" },
+            { "_name_=Jim" },
             { "\u03c0=pi" },
-            { "v1.0=buggy" }, // { "1.=buggy" }, { ".1=buggy" },
-            { "oid.1." }, { "1.3.6.1.4.1.1466..0=#04024869" }, { "cn=#a" }, { "cn=#ag" },
-            { "cn=#ga" }, { "cn=#abcdefgh" },
-            { "cn=a\\b" }, // { "cn=a\\bg" }, { "cn=\"hello" },
-            { "cn=+mail=,dc=example,dc=com" }, { "cn=xyz+sn=,dc=example,dc=com" },
-            { "cn=,dc=example,dc=com" } };
+            { "v1.0=buggy" },
+            { "1.=buggy" },
+            { ".1=buggy" },
+            { "oid.1." },
+            { "1.3.6.1.4.1.1466..0=#04024869" },
+            { "cn=#a" },
+            { "cn=#ag" },
+            { "cn=#ga" },
+            { "cn=#abcdefgh" },
+            { "cn=a\\b" },
+            { "cn=a\\bg" },
+            { "cn=\"hello" },
+            { "cn=+mail=,dc=example,dc=com" },
+            { "cn=xyz+sn=,dc=example,dc=com" },
+            { "cn=,dc=example,dc=com" },
+            { "cn=a+cn=b,dc=example,dc=com" }
+        };
+        // @formatter:on
     }
 
     /**
@@ -230,11 +286,17 @@
      */
     @DataProvider(name = "createIsChildOfTestData")
     public Object[][] createIsChildOfTestData() {
-        return new Object[][] { { "", "", false }, { "", "dc=org", false },
-            { "", "dc=opendj,dc=org", false }, { "", "dc=foo,dc=opendj,dc=org", false },
-            { "dc=org", "", true }, { "dc=org", "dc=org", false },
+        // @formatter:off
+        return new Object[][] {
+            { "", "", false },
+            { "", "dc=org", false },
+            { "", "dc=opendj,dc=org", false },
+            { "", "dc=foo,dc=opendj,dc=org", false },
+            { "dc=org", "", true },
+            { "dc=org", "dc=org", false },
             { "dc=org", "dc=opendj,dc=org", false },
-            { "dc=org", "dc=foo,dc=opendj,dc=org", false }, { "dc=opendj,dc=org", "", false },
+            { "dc=org", "dc=foo,dc=opendj,dc=org", false },
+            { "dc=opendj,dc=org", "", false },
             { "dc=opendj,dc=org", "dc=org", true },
             { "dc=opendj,dc=org", "dc=opendj,dc=org", false },
             { "dc=opendj,dc=org", "dc=foo,dc=opendj,dc=org", false },
@@ -242,8 +304,11 @@
             { "dc=foo,dc=opendj,dc=org", "dc=org", false },
             { "dc=foo,dc=opendj,dc=org", "dc=opendj,dc=org", true },
             { "dc=foo,dc=opendj,dc=org", "dc=foo,dc=opendj,dc=org", false },
-            { "dc=org", "dc=com", false }, { "dc=opendj,dc=org", "dc=foo,dc=org", false },
-            { "dc=opendj,dc=org", "dc=opendj,dc=com", false }, };
+            { "dc=org", "dc=com", false },
+            { "dc=opendj,dc=org", "dc=foo,dc=org", false },
+            { "dc=opendj,dc=org", "dc=opendj,dc=com", false },
+        };
+        // @formatter:on
     }
 
     /**
@@ -253,8 +318,15 @@
      */
     @DataProvider(name = "createNumComponentsTestData")
     public Object[][] createNumComponentsTestData() {
-        return new Object[][] { { "", 0 }, { "dc=com", 1 }, { "dc=opendj,dc=com", 2 },
-            { "dc=world,dc=opendj,dc=com", 3 }, { "dc=hello,dc=world,dc=opendj,dc=com", 4 }, };
+        // @formatter:off
+        return new Object[][] {
+            { "", 0 },
+            { "dc=com", 1 },
+            { "dc=opendj,dc=com", 2 },
+            { "dc=world,dc=opendj,dc=com", 3 },
+            { "dc=hello,dc=world,dc=opendj,dc=com", 4 },
+        };
+        // @formatter:on
     }
 
     /**
@@ -264,10 +336,15 @@
      */
     @DataProvider(name = "createParentAndRDNTestData")
     public Object[][] createParentAndRDNTestData() {
-        return new Object[][] { { "", null, null }, { "dc=com", "", "dc=com" },
+        // @formatter:off
+        return new Object[][] {
+            { "", null, null },
+            { "dc=com", "", "dc=com" },
             { "dc=opendj,dc=com", "dc=com", "dc=opendj" },
             { "dc=world,dc=opendj,dc=com", "dc=opendj,dc=com", "dc=world" },
-            { "dc=hello,dc=world,dc=opendj,dc=com", "dc=world,dc=opendj,dc=com", "dc=hello" }, };
+            { "dc=hello,dc=world,dc=opendj,dc=com", "dc=world,dc=opendj,dc=com", "dc=hello" },
+        };
+        // @formatter:on
     }
 
     /**
@@ -277,12 +354,17 @@
      */
     @DataProvider(name = "createRDNTestData")
     public Object[][] createRDNTestData() {
-        return new Object[][] { { "dc=com", 0, "dc=com" }, { "dc=opendj,dc=com", 0, "dc=opendj" },
+        // @formatter:off
+        return new Object[][] {
+            { "dc=com", 0, "dc=com" },
+            { "dc=opendj,dc=com", 0, "dc=opendj" },
             { "dc=opendj,dc=com", 1, "dc=com" },
             { "dc=hello,dc=world,dc=opendj,dc=com", 0, "dc=hello" },
             { "dc=hello,dc=world,dc=opendj,dc=com", 1, "dc=world" },
             { "dc=hello,dc=world,dc=opendj,dc=com", 2, "dc=opendj" },
-            { "dc=hello,dc=world,dc=opendj,dc=com", 3, "dc=com" }, };
+            { "dc=hello,dc=world,dc=opendj,dc=com", 3, "dc=com" },
+        };
+        // @formatter:on
     }
 
     /**
@@ -292,19 +374,29 @@
      */
     @DataProvider(name = "createSubordinateTestData")
     public Object[][] createSubordinateTestData() {
-        return new Object[][] { { "", "", true }, { "", "dc=org", false },
-            { "", "dc=opendj,dc=org", false }, { "", "dc=foo,dc=opendj,dc=org", false },
-            { "dc=org", "", true }, { "dc=org", "dc=org", true },
+        // @formatter:off
+        return new Object[][] {
+            { "", "", true },
+            { "", "dc=org", false },
+            { "", "dc=opendj,dc=org", false },
+            { "", "dc=foo,dc=opendj,dc=org", false },
+            { "dc=org", "", true },
+            { "dc=org", "dc=org", true },
             { "dc=org", "dc=opendj,dc=org", false },
-            { "dc=org", "dc=foo,dc=opendj,dc=org", false }, { "dc=opendj,dc=org", "", true },
+            { "dc=org", "dc=foo,dc=opendj,dc=org", false },
+            { "dc=opendj,dc=org", "", true },
             { "dc=opendj,dc=org", "dc=org", true },
             { "dc=opendj,dc=org", "dc=opendj,dc=org", true },
             { "dc=opendj,dc=org", "dc=foo,dc=opendj,dc=org", false },
-            { "dc=foo,dc=opendj,dc=org", "", true }, { "dc=foo,dc=opendj,dc=org", "dc=org", true },
+            { "dc=foo,dc=opendj,dc=org", "", true },
+            { "dc=foo,dc=opendj,dc=org", "dc=org", true },
             { "dc=foo,dc=opendj,dc=org", "dc=opendj,dc=org", true },
             { "dc=foo,dc=opendj,dc=org", "dc=foo,dc=opendj,dc=org", true },
-            { "dc=org", "dc=com", false }, { "dc=opendj,dc=org", "dc=foo,dc=org", false },
-            { "dc=opendj,dc=org", "dc=opendj,dc=com", false }, };
+            { "dc=org", "dc=com", false },
+            { "dc=opendj,dc=org", "dc=foo,dc=org", false },
+            { "dc=opendj,dc=org", "dc=opendj,dc=com", false },
+        };
+        // @formatter:on
     }
 
     /**
@@ -314,19 +406,29 @@
      */
     @DataProvider(name = "createSuperiorTestData")
     public Object[][] createSuperiorTestData() {
-        return new Object[][] { { "", "", true }, { "", "dc=org", true },
-            { "", "dc=opendj,dc=org", true }, { "", "dc=foo,dc=opendj,dc=org", true },
-            { "dc=org", "", false }, { "dc=org", "dc=org", true },
-            { "dc=org", "dc=opendj,dc=org", true }, { "dc=org", "dc=foo,dc=opendj,dc=org", true },
-            { "dc=opendj,dc=org", "", false }, { "dc=opendj,dc=org", "dc=org", false },
+        // @formatter:off
+        return new Object[][] {
+            { "", "", true },
+            { "", "dc=org", true },
+            { "", "dc=opendj,dc=org", true },
+            { "", "dc=foo,dc=opendj,dc=org", true },
+            { "dc=org", "", false },
+            { "dc=org", "dc=org", true },
+            { "dc=org", "dc=opendj,dc=org", true },
+            { "dc=org", "dc=foo,dc=opendj,dc=org", true },
+            { "dc=opendj,dc=org", "", false },
+            { "dc=opendj,dc=org", "dc=org", false },
             { "dc=opendj,dc=org", "dc=opendj,dc=org", true },
             { "dc=opendj,dc=org", "dc=foo,dc=opendj,dc=org", true },
             { "dc=foo,dc=opendj,dc=org", "", false },
             { "dc=foo,dc=opendj,dc=org", "dc=org", false },
             { "dc=foo,dc=opendj,dc=org", "dc=opendj,dc=org", false },
             { "dc=foo,dc=opendj,dc=org", "dc=foo,dc=opendj,dc=org", true },
-            { "dc=org", "dc=com", false }, { "dc=opendj,dc=org", "dc=foo,dc=org", false },
-            { "dc=opendj,dc=org", "dc=opendj,dc=com", false }, };
+            { "dc=org", "dc=com", false },
+            { "dc=opendj,dc=org", "dc=foo,dc=org", false },
+            { "dc=opendj,dc=org", "dc=opendj,dc=com", false },
+        };
+        // @formatter:on
     }
 
     @Test
@@ -561,12 +663,16 @@
      * @throws Exception
      *             If the test failed unexpectedly.
      */
-    @Test(dataProvider = "illegalDNs", expectedExceptions = {
-            LocalizedIllegalArgumentException.class, NullPointerException.class })
+    @Test(dataProvider = "illegalDNs", expectedExceptions = LocalizedIllegalArgumentException.class)
     public void testIllegalStringDNs(final String dn) throws Exception {
         DN.valueOf(dn);
     }
 
+    @Test(dataProvider = "illegalDNs", expectedExceptions = LocalizedIllegalArgumentException.class)
+    public void testIllegalByteStringDNs(final String dn) throws Exception {
+        DN.valueOf(ByteString.valueOfUtf8(dn));
+    }
+
     /**
      * Test the isChildOf method.
      *
@@ -731,8 +837,8 @@
      * @throws Exception
      *             If the test failed unexpectedly.
      */
-    @Test(expectedExceptions = { NullPointerException.class, AssertionError.class })
-    public void testValueOfString() throws Exception {
+    @Test(expectedExceptions = NullPointerException.class)
+    public void valueOfStringShouldThrowNPEForNullParameter() {
         DN.valueOf((String) null);
     }
 
@@ -742,8 +848,8 @@
      * @throws Exception
      *             If the test failed unexpectedly.
      */
-    @Test(expectedExceptions = { NullPointerException.class, AssertionError.class })
-    public void testValueOfByteString() throws Exception {
+    @Test(expectedExceptions = NullPointerException.class)
+    public void valueOfByteStringShouldThrowNPEForNullParameter() throws Exception {
         DN.valueOf((ByteString) null);
     }
 
@@ -1266,4 +1372,25 @@
         assertThat(DN.valueOf(withWhiteSpace).toNormalizedByteString())
                 .isEqualTo(DN.valueOf(withoutWhiteSpace).toNormalizedByteString());
     }
+
+    @DataProvider
+    public Object[][] rdnShouldReturnNullWhenIndexIsOutOfRangeData() {
+        return new Object[][] {
+            { "", 0 },
+            { "", 1 },
+            { "dc=com", 1 },
+            { "dc=opends,dc=com", 2 },
+            { "dc=hello,dc=world,dc=opends,dc=com", 4 },
+        };
+    }
+
+    @Test(dataProvider = "rdnShouldReturnNullWhenIndexIsOutOfRangeData")
+    public void rdnShouldReturnNullWhenIndexIsOutOfRange(String rdn, int i) {
+        assertThat((Object) DN.valueOf(rdn).rdn(i)).isNull();
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void rdnShouldThrowIAEForNegativeIndexes() throws Exception {
+        DN.valueOf("dc=example,dc=com").rdn(-1);
+    }
 }
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/RDNTestCase.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/RDNTestCase.java
index 3d59411..a6d6547 100644
--- a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/RDNTestCase.java
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/RDNTestCase.java
@@ -104,19 +104,42 @@
      */
     @DataProvider(name = "illegalRDNs")
     public Object[][] createIllegalData() {
-        return new Object[][] { { null }, { "" }, { " " }, { "=" }, { "manager" }, { "manager " },
-            { "cn+" }, { "cn+Jim" },
+        // @formatter:off
+        return new Object[][] {
+            { null },
+            { "" },
+            { " " },
+            { "=" },
+            { "manager" },
+            { "manager " },
+            { "cn+" },
+            { "cn+Jim" },
             { "cn=Jim+" },
             { "cn=Jim +" },
             { "cn=Jim+ " },
+            { "cn=Jim+cn=John" },
             { "cn=Jim+sn" },
             { "cn=Jim+sn " },
-            { "cn=Jim+sn equals" }, // { "cn=Jim," }, { "cn=Jim;" }, {
-                                    // "cn=Jim,  " },
-            // { "cn=Jim+sn=a," }, { "cn=Jim, sn=Jam " }, { "cn+uid=Jim" },
-            { "-cn=Jim" }, { "/tmp=a" }, { "\\tmp=a" }, { "cn;lang-en=Jim" }, { "@cn=Jim" },
-            { "_name_=Jim" }, { "\u03c0=pi" }, { "v1.0=buggy" }, { "cn=Jim+sn=Bob++" },
-            { "cn=Jim+sn=Bob+," }, { "1.3.6.1.4.1.1466..0=#04024869" }, };
+            { "cn=Jim+sn equals" },
+            { "cn=Jim," },
+            { "cn=Jim;" },
+            { "cn=Jim,  " },
+            { "cn=Jim+sn=a," },
+            { "cn=Jim, sn=Jam " },
+            { "cn+uid=Jim" },
+            { "-cn=Jim" },
+            { "/tmp=a" },
+            { "\\tmp=a" },
+            { "cn;lang-en=Jim" },
+            { "@cn=Jim" },
+            { "_name_=Jim" },
+            { "\u03c0=pi" },
+            { "v1.0=buggy" },
+            { "cn=Jim+sn=Bob++" },
+            { "cn=Jim+sn=Bob+," },
+            { "1.3.6.1.4.1.1466..0=#04024869" },
+        };
+        // @formatter:on
     }
 
     /**
@@ -142,10 +165,10 @@
             { "cn=hello+sn=world", "cn=hello+description=world", 1 },
             { "cn=hello", "sn=world", -1 },
             { "sn=hello", "cn=world", 1 },
-            // { "x-test-integer-type=10", "x-test-integer-type=9", 1 },
-            // { "x-test-integer-type=999", "x-test-integer-type=1000", -1 },
-            // { "x-test-integer-type=-1", "x-test-integer-type=0", -1 },
-            // { "x-test-integer-type=0", "x-test-integer-type=-1", 1 },
+            { "governingStructureRule=10", "governingStructureRule=9", 1 },
+            { "governingStructureRule=999", "governingStructureRule=1000", -1 },
+            { "governingStructureRule=-1", "governingStructureRule=0", -1 },
+            { "governingStructureRule=0", "governingStructureRule=-1", 1 },
             { "cn=aaa", "cn=aaaa", -1 },
             { "cn=AAA", "cn=aaaa", -1 },
             { "cn=aaa", "cn=AAAA", -1 },
@@ -195,6 +218,11 @@
         return (value instanceof RDN) ? ((RDN) value) : RDN.valueOf(value.toString());
     }
 
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void ensureRDNIsCreatedWithNonEmptyArguments() {
+        new RDN();
+    }
+
     /**
      * Test RDN construction with single AVA.
      *
@@ -238,9 +266,9 @@
     @Test
     public void testConstructorWithMultipleAVAs() throws Exception {
         AVA example = new AVA("dc", "example");
-        AVA org = new AVA("dc", "org");
+        AVA user = new AVA("cn", "bjensen");
 
-        final RDN rdn = new RDN(example, org);
+        final RDN rdn = new RDN(example, user);
         assertEquals(rdn.size(), 2);
         Iterator<AVA> rdnIt = rdn.iterator();
         AVA firstAva = rdnIt.next();
@@ -248,16 +276,23 @@
         assertEquals(firstAva, example);
 
         AVA secondAva = rdnIt.next();
-        assertEquals(secondAva.getAttributeType(), ATTR_TYPE_DC);
-        assertEquals(secondAva, org);
+        assertEquals(secondAva.getAttributeType(), ATTR_TYPE_CN);
+        assertEquals(secondAva, user);
+    }
+
+    @Test(expectedExceptions = LocalizedIllegalArgumentException.class)
+    public void testConstructorWithDuplicateAVAs() {
+        AVA example = new AVA("dc", "example");
+        AVA org = new AVA("dc", "org");
+        new RDN(example, org);
     }
 
     @Test
     public void testConstructorWithCollectionOfAVAs() throws Exception {
         AVA example = new AVA("dc", "example");
-        AVA org = new AVA("dc", "org");
+        AVA user = new AVA("cn", "bjensen");
 
-        final RDN rdn = new RDN(Arrays.asList(example, org));
+        final RDN rdn = new RDN(Arrays.asList(example, user));
         assertEquals(rdn.size(), 2);
         Iterator<AVA> rdnIt = rdn.iterator();
         AVA firstAva = rdnIt.next();
@@ -265,8 +300,15 @@
         assertEquals(firstAva, example);
 
         AVA secondAva = rdnIt.next();
-        assertEquals(secondAva.getAttributeType(), ATTR_TYPE_DC);
-        assertEquals(secondAva, org);
+        assertEquals(secondAva.getAttributeType(), ATTR_TYPE_CN);
+        assertEquals(secondAva, user);
+    }
+
+    @Test(expectedExceptions = LocalizedIllegalArgumentException.class)
+    public void testConstructorWithCollectionOfDuplicateAVAs() {
+        AVA example = new AVA("dc", "example");
+        AVA org = new AVA("dc", "org");
+        new RDN(Arrays.asList(example, org));
     }
 
     /**

--
Gitblit v1.10.0