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

Matthew Swift
16.51.2016 47800d82adb5c09fe0fc3bd1e5dd6d7b505c026a
OPENDJ-2776 Migrate remaining DN/RDN unit tests from the server

The unit tests exposed several bugs:

* RDN.valueOf() ignored trailing RDN sequences or garbage
* RDN constructors allowed zero AVAs
* RDN constructors allowed AVAs having duplicate attribute types
* AVA decoding allowed OIDs with trailing "." characters.
6 files modified
704 ■■■■■ changed files
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java 228 ●●●● patch | view | raw | blame | history
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java 31 ●●●● patch | view | raw | blame | history
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java 71 ●●●● patch | view | raw | blame | history
opendj-sdk/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties 5 ●●●●● patch | view | raw | blame | history
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java 287 ●●●● patch | view | raw | blame | history
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/RDNTestCase.java 82 ●●●● patch | view | raw | blame | history
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;
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;
    }
    /**
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
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 \
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);
    }
}
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));
    }
    /**