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

Matthew Swift
20.52.2012 8d673dd2b125d0b974eb1e8376e053731c628354
Fix OPENDJ-157: Make methods like Entry.getAttribute(String) more user friendly

* add Entry test suite
* change default implementations of Entry methods so that they detect when the passed in attribute or attribute description contains a place-holder attribute type. When this is the case the methods fall back to a less efficient algorithm for finding matching attributes in the entry.
11 files modified
1706 ■■■■ changed files
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractEntry.java 217 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractMapEntry.java 41 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeDescription.java 100 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attributes.java 220 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entry.java 50 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/AttributeType.java 115 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java 109 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFReader.java 2 ●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java 2 ●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/AttributeDescriptionTestCase.java 56 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/EntryTestCase.java 794 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractEntry.java
@@ -28,6 +28,7 @@
package org.forgerock.opendj.ldap;
import java.util.Collection;
import java.util.Iterator;
import com.forgerock.opendj.util.Iterables;
import com.forgerock.opendj.util.Predicate;
@@ -43,6 +44,7 @@
    private static final Predicate<Attribute, AttributeDescription> FIND_ATTRIBUTES_PREDICATE =
            new Predicate<Attribute, AttributeDescription>() {
                @Override
                public boolean matches(final Attribute value, final AttributeDescription p) {
                    return value.getAttributeDescription().isSubTypeOf(p);
                }
@@ -50,94 +52,6 @@
            };
    /**
     * Returns {@code true} if {@code object} is an entry which is equal to
     * {@code entry}. Two entry are considered equal if their distinguished
     * names are equal, they both have the same number of attributes, and every
     * attribute contained in the first entry is also contained in the second
     * entry.
     *
     * @param entry
     *            The entry to be tested for equality.
     * @param object
     *            The object to be tested for equality with the entry.
     * @return {@code true} if {@code object} is an entry which is equal to
     *         {@code entry}, or {@code false} if not.
     */
    static boolean equals(final Entry entry, final Object object) {
        if (entry == object) {
            return true;
        }
        if (!(object instanceof Entry)) {
            return false;
        }
        final Entry other = (Entry) object;
        if (!entry.getName().equals(other.getName())) {
            return false;
        }
        // Distinguished name is the same, compare attributes.
        if (entry.getAttributeCount() != other.getAttributeCount()) {
            return false;
        }
        for (final Attribute attribute : entry.getAllAttributes()) {
            final Attribute otherAttribute =
                    other.getAttribute(attribute.getAttributeDescription());
            if (!attribute.equals(otherAttribute)) {
                return false;
            }
        }
        return true;
    }
    /**
     * Returns the hash code for {@code entry}. It will be calculated as the sum
     * of the hash codes of the distinguished name and all of the attributes.
     *
     * @param entry
     *            The entry whose hash code should be calculated.
     * @return The hash code for {@code entry}.
     */
    static int hashCode(final Entry entry) {
        int hashCode = entry.getName().hashCode();
        for (final Attribute attribute : entry.getAllAttributes()) {
            hashCode += attribute.hashCode();
        }
        return hashCode;
    }
    /**
     * Returns a string representation of {@code entry}.
     *
     * @param entry
     *            The entry whose string representation should be returned.
     * @return The string representation of {@code entry}.
     */
    static String toString(final Entry entry) {
        final StringBuilder builder = new StringBuilder();
        builder.append("Entry(");
        builder.append(entry.getName());
        builder.append(", {");
        boolean firstValue = true;
        for (final Attribute attribute : entry.getAllAttributes()) {
            if (!firstValue) {
                builder.append(", ");
            }
            builder.append(attribute);
            firstValue = false;
        }
        builder.append("})");
        return builder.toString();
    }
    /**
     * Sole constructor.
     */
    protected AbstractEntry() {
@@ -147,6 +61,7 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean addAttribute(final Attribute attribute) {
        return addAttribute(attribute, null);
    }
@@ -154,6 +69,7 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public Entry addAttribute(final String attributeDescription, final Object... values) {
        addAttribute(new LinkedAttribute(attributeDescription, values), null);
        return this;
@@ -162,6 +78,7 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsAttribute(final Attribute attribute,
            final Collection<ByteString> missingValues) {
        final Attribute a = getAttribute(attribute.getAttributeDescription());
@@ -187,6 +104,7 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsAttribute(final String attributeDescription, final Object... values) {
        return containsAttribute(new LinkedAttribute(attributeDescription, values), null);
    }
@@ -196,12 +114,34 @@
     */
    @Override
    public boolean equals(final Object object) {
        return equals(this, object);
        if (this == object) {
            return true;
        } else if (object instanceof Entry) {
            final Entry other = (Entry) object;
            if (!this.getName().equals(other.getName())) {
                return false;
            }
            // Distinguished name is the same, compare attributes.
            if (this.getAttributeCount() != other.getAttributeCount()) {
                return false;
            }
            for (final Attribute attribute : this.getAllAttributes()) {
                final Attribute otherAttribute =
                        other.getAttribute(attribute.getAttributeDescription());
                if (!attribute.equals(otherAttribute)) {
                    return false;
                }
            }
            return true;
        } else {
            return false;
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<Attribute> getAllAttributes(final AttributeDescription attributeDescription) {
        Validator.ensureNotNull(attributeDescription);
@@ -212,6 +152,7 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<Attribute> getAllAttributes(final String attributeDescription) {
        return getAllAttributes(AttributeDescription.valueOf(attributeDescription));
    }
@@ -219,6 +160,21 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public Attribute getAttribute(final AttributeDescription attributeDescription) {
        for (final Attribute attribute : getAllAttributes()) {
            final AttributeDescription ad = attribute.getAttributeDescription();
            if (isAssignable(attributeDescription, ad)) {
                return attribute;
            }
        }
        return null;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public Attribute getAttribute(final String attributeDescription) {
        return getAttribute(AttributeDescription.valueOf(attributeDescription));
    }
@@ -228,26 +184,64 @@
     */
    @Override
    public int hashCode() {
        return hashCode(this);
        int hashCode = this.getName().hashCode();
        for (final Attribute attribute : this.getAllAttributes()) {
            hashCode += attribute.hashCode();
        }
        return hashCode;
    }
    /**
     * {@inheritDoc}
     */
    public AttributeParser parseAttribute(AttributeDescription attributeDescription) {
    @Override
    public AttributeParser parseAttribute(final AttributeDescription attributeDescription) {
        return AttributeParser.parseAttribute(getAttribute(attributeDescription));
    }
    /**
     * {@inheritDoc}
     */
    public AttributeParser parseAttribute(String attributeDescription) {
    @Override
    public AttributeParser parseAttribute(final String attributeDescription) {
        return AttributeParser.parseAttribute(getAttribute(attributeDescription));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean removeAttribute(final Attribute attribute,
            final Collection<ByteString> missingValues) {
        final Iterator<Attribute> i = getAllAttributes().iterator();
        final AttributeDescription attributeDescription = attribute.getAttributeDescription();
        while (i.hasNext()) {
            final Attribute oldAttribute = i.next();
            if (isAssignable(attributeDescription, oldAttribute.getAttributeDescription())) {
                if (attribute.isEmpty()) {
                    i.remove();
                    return true;
                } else {
                    final boolean modified = oldAttribute.removeAll(attribute, missingValues);
                    if (oldAttribute.isEmpty()) {
                        i.remove();
                        return true;
                    }
                    return modified;
                }
            }
        }
        // Not found.
        if (missingValues != null) {
            missingValues.addAll(attribute);
        }
        return false;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean removeAttribute(final AttributeDescription attributeDescription) {
        return removeAttribute(Attributes.emptyAttribute(attributeDescription), null);
    }
@@ -255,6 +249,7 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public Entry removeAttribute(final String attributeDescription, final Object... values) {
        removeAttribute(new LinkedAttribute(attributeDescription, values), null);
        return this;
@@ -263,12 +258,20 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean replaceAttribute(final Attribute attribute) {
        if (attribute.isEmpty()) {
            return removeAttribute(attribute.getAttributeDescription());
        } else {
            removeAttribute(attribute.getAttributeDescription());
            addAttribute(attribute, null);
            // For consistency with addAttribute and removeAttribute, preserve
            // the existing attribute if it already exists.
            final Attribute oldAttribute = getAttribute(attribute.getAttributeDescription());
            if (oldAttribute != null) {
                oldAttribute.clear();
                oldAttribute.addAll(attribute);
            } else {
                addAttribute(attribute, null);
            }
            return true;
        }
    }
@@ -276,6 +279,7 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public Entry replaceAttribute(final String attributeDescription, final Object... values) {
        replaceAttribute(new LinkedAttribute(attributeDescription, values));
        return this;
@@ -284,6 +288,7 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public Entry setName(final String dn) {
        return setName(DN.valueOf(dn));
    }
@@ -293,7 +298,29 @@
     */
    @Override
    public String toString() {
        return toString(this);
        final StringBuilder builder = new StringBuilder();
        builder.append("Entry(");
        builder.append(this.getName());
        builder.append(", {");
        boolean firstValue = true;
        for (final Attribute attribute : this.getAllAttributes()) {
            if (!firstValue) {
                builder.append(", ");
            }
            builder.append(attribute);
            firstValue = false;
        }
        builder.append("})");
        return builder.toString();
    }
    private boolean isAssignable(final AttributeDescription from, final AttributeDescription to) {
        if (!from.isPlaceHolder()) {
            return from.equals(to);
        } else {
            return from.matches(to);
        }
    }
}
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractMapEntry.java
@@ -37,7 +37,6 @@
 */
abstract class AbstractMapEntry extends AbstractEntry {
    private final Map<AttributeDescription, Attribute> attributes;
    private DN name;
    /**
@@ -57,12 +56,11 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public final boolean addAttribute(final Attribute attribute,
            final Collection<ByteString> duplicateValues) {
        Validator.ensureNotNull(attribute);
        final AttributeDescription attributeDescription = attribute.getAttributeDescription();
        final Attribute oldAttribute = attributes.get(attributeDescription);
        final Attribute oldAttribute = getAttribute(attributeDescription);
        if (oldAttribute != null) {
            return oldAttribute.addAll(attribute, duplicateValues);
        } else {
@@ -74,6 +72,7 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public final Entry clearAttributes() {
        attributes.clear();
        return this;
@@ -82,6 +81,7 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public final Iterable<Attribute> getAllAttributes() {
        return attributes.values();
    }
@@ -89,15 +89,21 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public final Attribute getAttribute(final AttributeDescription attributeDescription) {
        Validator.ensureNotNull(attributeDescription);
        return attributes.get(attributeDescription);
        final Attribute attribute = attributes.get(attributeDescription);
        if (attribute == null && attributeDescription.isPlaceHolder()) {
            // Fall-back to inefficient search using place-holder.
            return super.getAttribute(attributeDescription);
        } else {
            return attribute;
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public final int getAttributeCount() {
        return attributes.size();
    }
@@ -105,6 +111,7 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public final DN getName() {
        return name;
    }
@@ -112,20 +119,27 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public final boolean removeAttribute(final Attribute attribute,
            final Collection<ByteString> missingValues) {
        Validator.ensureNotNull(attribute);
        final AttributeDescription attributeDescription = attribute.getAttributeDescription();
        if (attribute.isEmpty()) {
            return attributes.remove(attributeDescription) != null;
            if (attributes.remove(attributeDescription) != null) {
                return true;
            } else if (attributeDescription.isPlaceHolder()) {
                // Fall-back to inefficient remove using place-holder.
                return super.removeAttribute(attribute, missingValues);
            } else {
                return false;
            }
        } else {
            final Attribute oldAttribute = attributes.get(attributeDescription);
            final Attribute oldAttribute = getAttribute(attributeDescription);
            if (oldAttribute != null) {
                final boolean modified = oldAttribute.removeAll(attribute, missingValues);
                if (oldAttribute.isEmpty()) {
                    attributes.remove(attributeDescription);
                    // Use old attribute's description in case it is different
                    // (e.g. this may be the case when using place-holders).
                    attributes.remove(oldAttribute.getAttributeDescription());
                    return true;
                }
                return modified;
@@ -141,6 +155,7 @@
    /**
     * {@inheritDoc}
     */
    @Override
    public final Entry setName(final DN dn) {
        Validator.ensureNotNull(dn);
        this.name = dn;
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeDescription.java
@@ -67,7 +67,7 @@
        public abstract int compareTo(Impl other);
        public abstract boolean containsOption(String normalizedOption);
        public abstract boolean hasOption(String normalizedOption);
        public abstract boolean equals(Impl other);
@@ -128,7 +128,7 @@
        }
        @Override
        public boolean containsOption(final String normalizedOption) {
        public boolean hasOption(final String normalizedOption) {
            final int sz = normalizedOptions.length;
            for (int i = 0; i < sz; i++) {
                if (normalizedOptions[i].equals(normalizedOption)) {
@@ -169,7 +169,7 @@
            if (other == ZERO_OPTION_IMPL) {
                return true;
            } else if (other.size() == 1) {
                return containsOption(other.firstNormalizedOption());
                return hasOption(other.firstNormalizedOption());
            } else if (other.size() > size()) {
                return false;
            } else {
@@ -179,7 +179,7 @@
                // not worth it.
                final MultiOptionImpl tmp = (MultiOptionImpl) other;
                for (final String normalizedOption : tmp.normalizedOptions) {
                    if (!containsOption(normalizedOption)) {
                    if (!hasOption(normalizedOption)) {
                        return false;
                    }
                }
@@ -191,7 +191,7 @@
        public boolean isSuperTypeOf(final Impl other) {
            // Must contain a sub-set of other's options.
            for (final String normalizedOption : normalizedOptions) {
                if (!other.containsOption(normalizedOption)) {
                if (!other.hasOption(normalizedOption)) {
                    return false;
                }
            }
@@ -235,13 +235,13 @@
        }
        @Override
        public boolean containsOption(final String normalizedOption) {
        public boolean hasOption(final String normalizedOption) {
            return this.normalizedOption.equals(normalizedOption);
        }
        @Override
        public boolean equals(final Impl other) {
            return other.size() == 1 && other.containsOption(normalizedOption);
            return other.size() == 1 && other.hasOption(normalizedOption);
        }
        @Override
@@ -272,7 +272,7 @@
        @Override
        public boolean isSuperTypeOf(final Impl other) {
            // Other must have this option.
            return other.containsOption(normalizedOption);
            return other.hasOption(normalizedOption);
        }
        public Iterator<String> iterator() {
@@ -298,7 +298,7 @@
        }
        @Override
        public boolean containsOption(final String normalizedOption) {
        public boolean hasOption(final String normalizedOption) {
            return false;
        }
@@ -391,7 +391,7 @@
        Validator.ensureNotNull(option);
        final String normalizedOption = toLowerCase(option);
        if (pimpl.containsOption(normalizedOption)) {
        if (pimpl.hasOption(normalizedOption)) {
            return this;
        }
@@ -480,7 +480,7 @@
        Validator.ensureNotNull(option);
        final String normalizedOption = toLowerCase(option);
        if (!pimpl.containsOption(normalizedOption)) {
        if (!pimpl.hasOption(normalizedOption)) {
            return this;
        }
@@ -489,8 +489,7 @@
                new StringBuilder(oldAttributeDescription.length() - option.length() - 1);
        final String normalizedOldAttributeDescription = toLowerCase(oldAttributeDescription);
        final int index =
                normalizedOldAttributeDescription.indexOf(normalizedOption);
        final int index = normalizedOldAttributeDescription.indexOf(normalizedOption);
        builder.append(oldAttributeDescription, 0, index - 1 /* to semi-colon */);
        builder.append(oldAttributeDescription, index + option.length(), oldAttributeDescription
                .length());
@@ -1033,15 +1032,16 @@
     * @throws NullPointerException
     *             If {@code option} was {@code null}.
     */
    public boolean containsOption(final String option) {
    public boolean hasOption(final String option) {
        final String normalizedOption = toLowerCase(option);
        return pimpl.containsOption(normalizedOption);
        return pimpl.hasOption(normalizedOption);
    }
    /**
     * Indicates whether the provided object is an attribute description which
     * is equal to this attribute description. It will be considered equal if
     * the attribute type and normalized sorted list of options are identical.
     * the attribute types are {@link AttributeType#equals equal} and normalized
     * sorted list of options are identical.
     *
     * @param o
     *            The object for which to make the determination.
@@ -1053,19 +1053,12 @@
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof AttributeDescription)) {
        } else if (o instanceof AttributeDescription) {
            final AttributeDescription other = (AttributeDescription) o;
            return attributeType.equals(other.attributeType) && pimpl.equals(other.pimpl);
        } else {
            return false;
        }
        final AttributeDescription other = (AttributeDescription) o;
        if (!attributeType.equals(other.attributeType)) {
            return false;
        }
        // Attribute type is the same, compare options.
        return pimpl.equals(other.pimpl);
    }
    /**
@@ -1125,14 +1118,34 @@
    }
    /**
     * Indicates whether this attribute description is a temporary place-holder
     * allocated dynamically by a non-strict schema when no corresponding
     * registered attribute type was found.
     * <p>
     * Place holder attribute descriptions have an attribute type whose OID is
     * the normalized attribute name with the string {@code -oid} appended. In
     * addition, they will use the directory string syntax and case ignore
     * matching rule.
     *
     * @return {@code true} if this is a temporary place-holder attribute
     *         description allocated dynamically by a non-strict schema when no
     *         corresponding registered attribute type was found.
     * @see Schema#getAttributeType(String)
     * @see AttributeType#isPlaceHolder()
     */
    public boolean isPlaceHolder() {
        return attributeType.isPlaceHolder();
    }
    /**
     * Indicates whether or not this attribute description is a sub-type of the
     * provided attribute description as defined in RFC 4512 section 2.5.
     * Specifically, this method will return {@code true} if and only if the
     * following conditions are both {@code true}:
     * <ul>
     * <li>This attribute description has an attribute type which is equal to,
     * or is a sub-type of, the attribute type in the provided attribute
     * description.
     * <li>This attribute description has an attribute type which
     * {@link AttributeType#matches matches}, or is a sub-type of, the attribute
     * type in the provided attribute description.
     * <li>This attribute description contains all of the options contained in
     * the provided attribute description.
     * </ul>
@@ -1160,9 +1173,9 @@
     * Specifically, this method will return {@code true} if and only if the
     * following conditions are both {@code true}:
     * <ul>
     * <li>This attribute description has an attribute type which is equal to,
     * or is a super-type of, the attribute type in the provided attribute
     * description.
     * <li>This attribute description has an attribute type which
     * {@link AttributeType#matches matches}, or is a super-type of, the
     * attribute type in the provided attribute description.
     * <li>This attribute description contains a sub-set of the options
     * contained in the provided attribute description.
     * </ul>
@@ -1177,7 +1190,7 @@
     *             If {@code name} was {@code null}.
     */
    public boolean isSuperTypeOf(final AttributeDescription other) {
        if (!other.attributeType.isSubTypeOf(attributeType)) {
        if (!attributeType.isSuperTypeOf(other.attributeType)) {
            return false;
        } else {
            return pimpl.isSuperTypeOf(other.pimpl);
@@ -1185,6 +1198,25 @@
    }
    /**
     * Indicates whether the provided attribute description matches this
     * attribute description. It will be considered a match if the attribute
     * types {@link AttributeType#matches match} and the normalized sorted list
     * of options are identical.
     *
     * @param other
     *            The attribute description for which to make the determination.
     * @return {@code true} if the provided attribute description matches this
     *         attribute description, or {@code false} if not.
     */
    public boolean matches(final AttributeDescription other) {
        if (this == other) {
            return true;
        } else {
            return attributeType.matches(other.attributeType) && pimpl.equals(other.pimpl);
        }
    }
    /**
     * Returns the string representation of this attribute description as
     * defined in RFC4512 section 2.5.
     *
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attributes.java
@@ -30,6 +30,8 @@
import java.util.Collection;
import java.util.Iterator;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import com.forgerock.opendj.util.Iterators;
import com.forgerock.opendj.util.Validator;
@@ -97,7 +99,6 @@
    private static final class RenamedAttribute implements Attribute {
        private final Attribute attribute;
        private final AttributeDescription attributeDescription;
        private RenamedAttribute(final Attribute attribute,
@@ -106,31 +107,38 @@
            this.attributeDescription = attributeDescription;
        }
        @Override
        public boolean add(final ByteString value) {
            return attribute.add(value);
        }
        @Override
        public boolean add(final Object firstValue, final Object... remainingValues) {
            return attribute.add(firstValue, remainingValues);
        }
        @Override
        public boolean addAll(final Collection<? extends ByteString> values) {
            return attribute.addAll(values);
        }
        @Override
        public boolean addAll(final Collection<? extends ByteString> values,
                final Collection<? super ByteString> duplicateValues) {
            return attribute.addAll(values, duplicateValues);
        }
        @Override
        public void clear() {
            attribute.clear();
        }
        @Override
        public boolean contains(final Object value) {
            return attribute.contains(value);
        }
        @Override
        public boolean containsAll(final Collection<?> values) {
            return attribute.containsAll(values);
        }
@@ -140,18 +148,22 @@
            return AbstractAttribute.equals(this, object);
        }
        @Override
        public ByteString firstValue() {
            return attribute.firstValue();
        }
        @Override
        public String firstValueAsString() {
            return attribute.firstValueAsString();
        }
        @Override
        public AttributeDescription getAttributeDescription() {
            return attributeDescription;
        }
        @Override
        public String getAttributeDescriptionAsString() {
            return attributeDescription.toString();
        }
@@ -161,48 +173,59 @@
            return AbstractAttribute.hashCode(this);
        }
        public AttributeParser parse() {
            return attribute.parse();
        }
        @Override
        public boolean isEmpty() {
            return attribute.isEmpty();
        }
        @Override
        public Iterator<ByteString> iterator() {
            return attribute.iterator();
        }
        @Override
        public AttributeParser parse() {
            return attribute.parse();
        }
        @Override
        public boolean remove(final Object value) {
            return attribute.remove(value);
        }
        @Override
        public boolean removeAll(final Collection<?> values) {
            return attribute.removeAll(values);
        }
        @Override
        public <T> boolean removeAll(final Collection<T> values,
                final Collection<? super T> missingValues) {
            return attribute.removeAll(values, missingValues);
        }
        @Override
        public boolean retainAll(final Collection<?> values) {
            return attribute.retainAll(values);
        }
        @Override
        public <T> boolean retainAll(final Collection<T> values,
                final Collection<? super T> missingValues) {
            return attribute.retainAll(values, missingValues);
        }
        @Override
        public int size() {
            return attribute.size();
        }
        @Override
        public ByteString[] toArray() {
            return attribute.toArray();
        }
        @Override
        public <T> T[] toArray(final T[] array) {
            return attribute.toArray(array);
        }
@@ -215,6 +238,72 @@
    }
    /**
     * Singleton attribute.
     */
    private static final class SingletonAttribute extends AbstractAttribute {
        private final AttributeDescription attributeDescription;
        private ByteString normalizedValue;
        private final ByteString value;
        private SingletonAttribute(final AttributeDescription attributeDescription,
                final Object value) {
            this.attributeDescription = attributeDescription;
            this.value = ByteString.valueOf(value);
        }
        @Override
        public boolean add(final ByteString value) {
            throw new UnsupportedOperationException();
        }
        @Override
        public void clear() {
            throw new UnsupportedOperationException();
        }
        @Override
        public boolean contains(final Object value) {
            final ByteString normalizedValue = normalizeValue(this, ByteString.valueOf(value));
            return normalizedSingleValue().equals(normalizedValue);
        }
        @Override
        public AttributeDescription getAttributeDescription() {
            return attributeDescription;
        }
        @Override
        public boolean isEmpty() {
            return false;
        }
        @Override
        public Iterator<ByteString> iterator() {
            return Iterators.singletonIterator(value);
        }
        @Override
        public boolean remove(final Object value) {
            throw new UnsupportedOperationException();
        }
        @Override
        public int size() {
            return 1;
        }
        // Lazily computes the normalized single value.
        private ByteString normalizedSingleValue() {
            if (normalizedValue == null) {
                normalizedValue = normalizeValue(this, value);
            }
            return normalizedValue;
        }
    }
    /**
     * Unmodifiable attribute.
     */
    private static final class UnmodifiableAttribute implements Attribute {
@@ -225,31 +314,38 @@
            this.attribute = attribute;
        }
        @Override
        public boolean add(final ByteString value) {
            throw new UnsupportedOperationException();
        }
        @Override
        public boolean add(final Object firstValue, final Object... remainingValues) {
            throw new UnsupportedOperationException();
        }
        @Override
        public boolean addAll(final Collection<? extends ByteString> values) {
            throw new UnsupportedOperationException();
        }
        @Override
        public boolean addAll(final Collection<? extends ByteString> values,
                final Collection<? super ByteString> duplicateValues) {
            throw new UnsupportedOperationException();
        }
        @Override
        public void clear() {
            throw new UnsupportedOperationException();
        }
        @Override
        public boolean contains(final Object value) {
            return attribute.contains(value);
        }
        @Override
        public boolean containsAll(final Collection<?> values) {
            return attribute.containsAll(values);
        }
@@ -259,18 +355,22 @@
            return (object == this || attribute.equals(object));
        }
        @Override
        public ByteString firstValue() {
            return attribute.firstValue();
        }
        @Override
        public String firstValueAsString() {
            return attribute.firstValueAsString();
        }
        @Override
        public AttributeDescription getAttributeDescription() {
            return attribute.getAttributeDescription();
        }
        @Override
        public String getAttributeDescriptionAsString() {
            return attribute.getAttributeDescriptionAsString();
        }
@@ -280,48 +380,59 @@
            return attribute.hashCode();
        }
        @Override
        public boolean isEmpty() {
            return attribute.isEmpty();
        }
        @Override
        public Iterator<ByteString> iterator() {
            return Iterators.unmodifiableIterator(attribute.iterator());
        }
        @Override
        public AttributeParser parse() {
            return attribute.parse();
        }
        @Override
        public boolean remove(final Object value) {
            throw new UnsupportedOperationException();
        }
        @Override
        public boolean removeAll(final Collection<?> values) {
            throw new UnsupportedOperationException();
        }
        @Override
        public <T> boolean removeAll(final Collection<T> values,
                final Collection<? super T> missingValues) {
            throw new UnsupportedOperationException();
        }
        @Override
        public boolean retainAll(final Collection<?> values) {
            throw new UnsupportedOperationException();
        }
        @Override
        public <T> boolean retainAll(final Collection<T> values,
                final Collection<? super T> missingValues) {
            throw new UnsupportedOperationException();
        }
        @Override
        public int size() {
            return attribute.size();
        }
        @Override
        public ByteString[] toArray() {
            return attribute.toArray();
        }
        @Override
        public <T> T[] toArray(final T[] array) {
            return attribute.toArray(array);
        }
@@ -330,12 +441,13 @@
        public String toString() {
            return attribute.toString();
        }
    }
    /**
     * Returns a read-only empty attribute having the specified attribute
     * description.
     * description. Attempts to modify the returned attribute either directly,
     * or indirectly via an iterator, result in an
     * {@code UnsupportedOperationException}.
     *
     * @param attributeDescription
     *            The attribute description.
@@ -348,6 +460,26 @@
    }
    /**
     * Returns a read-only empty attribute having the specified attribute
     * description. The attribute description will be decoded using the default
     * schema. Attempts to modify the returned attribute either directly, or
     * indirectly via an iterator, result in an
     * {@code UnsupportedOperationException}.
     *
     * @param attributeDescription
     *            The attribute description.
     * @return The empty attribute.
     * @throws LocalizedIllegalArgumentException
     *             If {@code attributeDescription} could not be decoded using
     *             the default schema.
     * @throws NullPointerException
     *             If {@code attributeDescription} was {@code null}.
     */
    public static final Attribute emptyAttribute(final String attributeDescription) {
        return emptyAttribute(AttributeDescription.valueOf(attributeDescription));
    }
    /**
     * Returns a view of {@code attribute} having a different attribute
     * description. All operations on the returned attribute "pass-through" to
     * the underlying attribute.
@@ -368,6 +500,80 @@
    }
    /**
     * Returns a view of {@code attribute} having a different attribute
     * description. All operations on the returned attribute "pass-through" to
     * the underlying attribute. The attribute description will be decoded using
     * the default schema.
     *
     * @param attribute
     *            The attribute to be renamed.
     * @param attributeDescription
     *            The new attribute description for {@code attribute}.
     * @return A renamed view of {@code attribute}.
     * @throws LocalizedIllegalArgumentException
     *             If {@code attributeDescription} could not be decoded using
     *             the default schema.
     * @throws NullPointerException
     *             If {@code attribute} or {@code attributeDescription} was
     *             {@code null}.
     */
    public static final Attribute renameAttribute(final Attribute attribute,
            final String attributeDescription) {
        Validator.ensureNotNull(attribute, attributeDescription);
        return renameAttribute(attribute, AttributeDescription.valueOf(attributeDescription));
    }
    /**
     * Returns a read-only single-valued attribute having the specified
     * attribute description and value. Attempts to modify the returned
     * attribute either directly, or indirectly via an iterator, result in an
     * {@code UnsupportedOperationException}.
     * <p>
     * If {@code value} is not an instance of {@code ByteString} then it will be
     * converted using the {@link ByteString#valueOf(Object)} method.
     *
     * @param attributeDescription
     *            The attribute description.
     * @param value
     *            The single attribute value.
     * @return The single-valued attribute.
     * @throws NullPointerException
     *             If {@code attributeDescription} or {@code value} was
     *             {@code null}.
     */
    public static final Attribute singletonAttribute(
            final AttributeDescription attributeDescription, final Object value) {
        return new SingletonAttribute(attributeDescription, value);
    }
    /**
     * Returns a read-only single-valued attribute having the specified
     * attribute description. The attribute description will be decoded using
     * the default schema. Attempts to modify the returned attribute either
     * directly, or indirectly via an iterator, result in an
     * {@code UnsupportedOperationException}.
     * <p>
     * If {@code value} is not an instance of {@code ByteString} then it will be
     * converted using the {@link ByteString#valueOf(Object)} method.
     *
     * @param attributeDescription
     *            The attribute description.
     * @param value
     *            The single attribute value.
     * @return The single-valued attribute.
     * @throws LocalizedIllegalArgumentException
     *             If {@code attributeDescription} could not be decoded using
     *             the default schema.
     * @throws NullPointerException
     *             If {@code attributeDescription} or {@code value} was
     *             {@code null}.
     */
    public static final Attribute singletonAttribute(final String attributeDescription,
            final Object value) {
        return singletonAttribute(AttributeDescription.valueOf(attributeDescription), value);
    }
    /**
     * Returns a read-only view of {@code attribute}. Query operations on the
     * returned attribute "read-through" to the underlying attribute, and
     * attempts to modify the returned attribute either directly or indirectly
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entry.java
@@ -38,8 +38,12 @@
 * {@link #addAttribute(String, Object...)} and {@link #setName(String)}). In
 * these cases the default schema is used unless an alternative schema is
 * specified in the {@code Entry} constructor. The default schema is not used
 * for any other purpose. In particular, an {@code Entry} will permit attributes
 * to be added which have been decoded using multiple schemas.
 * for any other purpose. In particular, an {@code Entry} may contain attributes
 * which have been decoded using different schemas.
 * <p>
 * When determining whether or not an entry already contains a particular
 * attribute, attribute descriptions will be compared using
 * {@link AttributeDescription#matches}.
 * <p>
 * Full LDAP modify semantics are provided via the {@link #addAttribute},
 * {@link #removeAttribute}, and {@link #replaceAttribute} methods.
@@ -64,12 +68,14 @@
     * Ensures that this entry contains the provided attribute and values
     * (optional operation). This method has the following semantics:
     * <ul>
     * <li>If this entry does not already contain an attribute with the same
     * attribute description, then this entry will be modified such that it
     * contains {@code attribute}, even if it is empty.
     * <li>If this entry already contains an attribute with the same attribute
     * description, then the attribute values contained in {@code attribute}
     * will be merged with the existing attribute values.
     * <li>If this entry does not already contain an attribute with a
     * {@link AttributeDescription#matches matching} attribute description, then
     * this entry will be modified such that it contains {@code attribute}, even
     * if it is empty.
     * <li>If this entry already contains an attribute with a
     * {@link AttributeDescription#matches matching} attribute description, then
     * the attribute values contained in {@code attribute} will be merged with
     * the existing attribute values.
     * </ul>
     * <p>
     * <b>NOTE:</b> When {@code attribute} is non-empty, this method implements
@@ -91,12 +97,14 @@
     * Ensures that this entry contains the provided attribute and values
     * (optional operation). This method has the following semantics:
     * <ul>
     * <li>If this entry does not already contain an attribute with the same
     * attribute description, then this entry will be modified such that it
     * contains {@code attribute}, even if it is empty.
     * <li>If this entry already contains an attribute with the same attribute
     * description, then the attribute values contained in {@code attribute}
     * will be merged with the existing attribute values.
     * <li>If this entry does not already contain an attribute with a
     * {@link AttributeDescription#matches matching} attribute description, then
     * this entry will be modified such that it contains {@code attribute}, even
     * if it is empty.
     * <li>If this entry already contains an attribute with a
     * {@link AttributeDescription#matches matching} attribute description, then
     * the attribute values contained in {@code attribute} will be merged with
     * the existing attribute values.
     * </ul>
     * <p>
     * <b>NOTE:</b> When {@code attribute} is non-empty, this method implements
@@ -121,12 +129,14 @@
     * Ensures that this entry contains the provided attribute and values
     * (optional operation). This method has the following semantics:
     * <ul>
     * <li>If this entry does not already contain an attribute with the same
     * attribute description, then this entry will be modified such that it
     * contains {@code attribute}, even if it is empty.
     * <li>If this entry already contains an attribute with the same attribute
     * description, then the attribute values contained in {@code attribute}
     * will be merged with the existing attribute values.
     * <li>If this entry does not already contain an attribute with a
     * {@link AttributeDescription#matches matching} attribute description, then
     * this entry will be modified such that it contains {@code attribute}, even
     * if it is empty.
     * <li>If this entry already contains an attribute with a
     * {@link AttributeDescription#matches matching} attribute description, then
     * the attribute values contained in {@code attribute} will be merged with
     * the existing attribute values.
     * </ul>
     * <p>
     * The attribute description will be decoded using the schema associated
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/AttributeType.java
@@ -74,6 +74,9 @@
    // Indicates whether this definition is declared "obsolete".
    private final boolean isObsolete;
    // Indicates whether this definition is a temporary place-holder.
    private final boolean isPlaceHolder;
    // Indicates whether this attribute type is declared "single-value".
    private final boolean isSingleValue;
@@ -162,34 +165,45 @@
        }
        this.isObjectClassType = oid.equals("2.5.4.0");
        this.isPlaceHolder = false;
        this.normalizedName = StaticUtils.toLowerCase(getNameOrOID());
    }
    AttributeType(final String oid, final List<String> names, final String description,
            final MatchingRule equalityMatchingRule, final Syntax syntax) {
        super(description, Collections.<String, List<String>> emptyMap());
    /**
     * Creates a new place-holder attribute type having the specified name,
     * default syntax, and default matching rule. The OID of the place-holder
     * attribute will be the normalized attribute type name followed by the
     * suffix "-oid".
     *
     * @param name
     *            The name of the place-holder attribute type.
     */
    AttributeType(final String name) {
        super("", Collections.<String, List<String>> emptyMap());
        Validator.ensureNotNull(oid, names, description);
        final StringBuilder builder = new StringBuilder(name.length() + 4);
        StaticUtils.toLowerCase(name, builder);
        builder.append("-oid");
        this.oid = oid;
        this.names = names;
        this.oid = builder.toString();
        this.names = Collections.singletonList(name);
        this.isObsolete = false;
        this.superiorTypeOID = null;
        this.superiorType = null;
        this.equalityMatchingRule = Schema.getDefaultMatchingRule();
        this.equalityMatchingRuleOID = equalityMatchingRule.getOID();
        this.equalityMatchingRule = equalityMatchingRule;
        this.orderingMatchingRuleOID = null;
        this.substringMatchingRuleOID = null;
        this.approximateMatchingRuleOID = null;
        this.syntax = Schema.getDefaultSyntax();
        this.syntaxOID = syntax.getOID();
        this.syntax = syntax;
        this.isSingleValue = false;
        this.isCollective = false;
        this.isNoUserModification = false;
        this.attributeUsage = AttributeUsage.USER_APPLICATIONS;
        this.definition = buildDefinition();
        this.isObjectClassType = oid.equals("2.5.4.0");
        this.isObjectClassType = false;
        this.isPlaceHolder = true;
        this.normalizedName = StaticUtils.toLowerCase(getNameOrOID());
    }
@@ -200,7 +214,8 @@
     * <li>The {@code objectClass} attribute is less than all other attribute
     * types.
     * <li>User attributes are less than operational attributes.
     * <li>Lexicographic comparison of the primary name or OID.
     * <li>Lexicographic comparison of the primary name and then, if equal, the
     * OID.
     * </ul>
     *
     * @param type
@@ -219,9 +234,13 @@
        } else {
            final boolean isOperational = getUsage().isOperational();
            final boolean typeIsOperational = type.getUsage().isOperational();
            if (isOperational == typeIsOperational) {
                return normalizedName.compareTo(type.normalizedName);
                final int tmp = normalizedName.compareTo(type.normalizedName);
                if (tmp == 0) {
                    return oid.compareTo(type.oid);
                } else {
                    return tmp;
                }
            } else {
                return isOperational ? 1 : -1;
            }
@@ -452,6 +471,24 @@
    }
    /**
     * Indicates whether this attribute type is a temporary place-holder
     * allocated dynamically by a non-strict schema when no registered attribute
     * type was found.
     * <p>
     * Place holder attribute types have an OID which is the normalized
     * attribute name with the string {@code -oid} appended. In addition, they
     * will use the directory string syntax and case ignore matching rule.
     *
     * @return {@code true} if this is a temporary place-holder attribute type
     *         allocated dynamically by a non-strict schema when no registered
     *         attribute type was found.
     * @see Schema#getAttributeType(String)
     */
    public boolean isPlaceHolder() {
        return isPlaceHolder;
    }
    /**
     * Indicates whether this attribute type is declared "single-value".
     *
     * @return {@code true} if this attribute type is declared "single-value",
@@ -475,7 +512,7 @@
    public boolean isSubTypeOf(final AttributeType type) {
        AttributeType tmp = this;
        do {
            if (tmp.equals(type)) {
            if (tmp.matches(type)) {
                return true;
            }
            tmp = tmp.getSuperiorType();
@@ -484,6 +521,50 @@
    }
    /**
     * Indicates whether or not this attribute type is a super-type of the
     * provided attribute type.
     *
     * @param type
     *            The attribute type for which to make the determination.
     * @return {@code true} if this attribute type is a super-type of the
     *         provided attribute type, or {@code false} if not.
     * @throws NullPointerException
     *             If {@code type} was {@code null}.
     */
    public boolean isSuperTypeOf(final AttributeType type) {
        return type.isSubTypeOf(this);
    }
    /**
     * Implements a place-holder tolerant version of {@link #equals}. This
     * method returns {@true} in the following cases:
     * <ul>
     * <li>this attribute type is equal to the provided attribute type as
     * specified by {@link #equals}
     * <li>this attribute type is a place-holder and the provided attribute type
     * has a name which matches the name of this attribute type
     * <li>the provided attribute type is a place-holder and this attribute type
     * has a name which matches the name of the provided attribute type.
     * </ul>
     *
     * @param type
     *            The attribute type for which to make the determination.
     * @return {@code true} if the provided attribute type matches this
     *         attribute type.
     */
    public boolean matches(final AttributeType type) {
        if (this == type) {
            return true;
        } else if (oid.equals(type.oid)) {
            return true;
        } else if (isPlaceHolder != type.isPlaceHolder) {
            return isPlaceHolder ? type.hasName(normalizedName) : hasName(type.normalizedName);
        } else {
            return false;
        }
    }
    /**
     * Returns the string representation of this schema definition in the form
     * specified in RFC 2252.
     *
@@ -591,8 +672,7 @@
    boolean validate(final Schema schema, final List<AttributeType> invalidSchemaElements,
            final List<LocalizableMessage> warnings) {
        // Avoid validating this schema element more than once. This may occur
        // if
        // multiple attributes specify the same superior.
        // if multiple attributes specify the same superior.
        if (!needsValidating) {
            return isValid;
        }
@@ -612,8 +692,7 @@
            }
            // First ensure that the superior has been validated and fail if it
            // is
            // invalid.
            // is invalid.
            if (!superiorType.validate(schema, invalidSchemaElements, warnings)) {
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_SUPERIOR_TYPE.get(getNameOrOID(),
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/Schema.java
@@ -73,8 +73,10 @@
public final class Schema {
    private static final class EmptyImpl implements Impl {
        private EmptyImpl() {
            // Nothing to do.
        private final boolean isStrict;
        private EmptyImpl(final boolean isStrict) {
            this.isStrict = isStrict;
        }
        public boolean allowMalformedNamesAndOptions() {
@@ -95,17 +97,12 @@
        @Override
        public AttributeType getAttributeType(final String name) {
            // Construct an placeholder attribute type with the given name,
            // the default matching rule, and the default syntax. The OID of
            // the attribute will be the normalized OID alias with "-oid"
            // appended to the given name.
            final StringBuilder builder = new StringBuilder(name.length() + 4);
            StaticUtils.toLowerCase(name, builder);
            builder.append("-oid");
            final String noid = builder.toString();
            return new AttributeType(noid, Collections.singletonList(name), "", Schema
                    .getDefaultMatchingRule(), Schema.getDefaultSyntax());
            if (isStrict) {
                throw new UnknownSchemaElementException(WARN_ATTR_TYPE_UNKNOWN.get(name));
            } else {
                // Return a place-holder.
                return new AttributeType(name);
            }
        }
        @Override
@@ -298,7 +295,7 @@
        @Override
        public boolean isStrict() {
            return false;
            return isStrict;
        }
    }
@@ -389,9 +386,9 @@
    }
    private static final class NonStrictImpl implements Impl {
        private final Impl strictImpl;
        private final StrictImpl strictImpl;
        private NonStrictImpl(final Impl strictImpl) {
        private NonStrictImpl(final StrictImpl strictImpl) {
            this.strictImpl = strictImpl;
        }
@@ -413,20 +410,8 @@
        @Override
        public AttributeType getAttributeType(final String name) {
            if (!strictImpl.hasAttributeType(name)) {
                // Construct an placeholder attribute type with the given name,
                // the default matching rule, and the default syntax. The OID of
                // the attribute will be the normalized OID alias with "-oid"
                // appended to the given name.
                final StringBuilder builder = new StringBuilder(name.length() + 4);
                StaticUtils.toLowerCase(name, builder);
                builder.append("-oid");
                final String noid = builder.toString();
                return new AttributeType(noid, Collections.singletonList(name), "", Schema
                        .getDefaultMatchingRule(), Schema.getDefaultSyntax());
            }
            return strictImpl.getAttributeType(name);
            final AttributeType type = strictImpl.getAttributeType0(name);
            return type != null ? type : new AttributeType(name);
        }
        @Override
@@ -735,19 +720,12 @@
        @Override
        public AttributeType getAttributeType(final String name) {
            final AttributeType type = numericOID2AttributeTypes.get(name);
            final AttributeType type = getAttributeType0(name);
            if (type != null) {
                return type;
            } else {
                throw new UnknownSchemaElementException(WARN_ATTR_TYPE_UNKNOWN.get(name));
            }
            final List<AttributeType> attributes =
                    name2AttributeTypes.get(StaticUtils.toLowerCase(name));
            if (attributes != null) {
                if (attributes.size() == 1) {
                    return attributes.get(0);
                }
                throw new UnknownSchemaElementException(WARN_ATTR_TYPE_AMBIGIOUS.get(name));
            }
            throw new UnknownSchemaElementException(WARN_ATTR_TYPE_UNKNOWN.get(name));
        }
        @Override
@@ -1073,28 +1051,37 @@
        public boolean isStrict() {
            return true;
        }
        AttributeType getAttributeType0(final String name) {
            final AttributeType type = numericOID2AttributeTypes.get(name);
            if (type != null) {
                return type;
            }
            final List<AttributeType> attributes =
                    name2AttributeTypes.get(StaticUtils.toLowerCase(name));
            if (attributes != null) {
                if (attributes.size() == 1) {
                    return attributes.get(0);
                }
                throw new UnknownSchemaElementException(WARN_ATTR_TYPE_AMBIGIOUS.get(name));
            }
            return null;
        }
    }
    /*
     * WARNING: do not reference the core schema in the following declarations.
     */
    private static final Schema EMPTY_SCHEMA = new Schema(new EmptyImpl());
    private static final Schema EMPTY_STRICT_SCHEMA = new Schema(new EmptyImpl(true));
    private static final Schema EMPTY_NON_STRICT_SCHEMA = new Schema(new EmptyImpl(false));
    static final String ATTR_ATTRIBUTE_TYPES = "attributeTypes";
    static final String ATTR_DIT_CONTENT_RULES = "dITContentRules";
    static final String ATTR_DIT_STRUCTURE_RULES = "dITStructureRules";
    static final String ATTR_LDAP_SYNTAXES = "ldapSyntaxes";
    static final String ATTR_MATCHING_RULE_USE = "matchingRuleUse";
    static final String ATTR_MATCHING_RULES = "matchingRules";
    static final String ATTR_NAME_FORMS = "nameForms";
    static final String ATTR_OBJECT_CLASSES = "objectClasses";
    /**
@@ -1138,7 +1125,7 @@
     * @return The empty schema.
     */
    public static Schema getEmptySchema() {
        return EMPTY_SCHEMA;
        return EMPTY_NON_STRICT_SCHEMA;
    }
    /**
@@ -1457,10 +1444,13 @@
     * @see Schema#isStrict()
     */
    public Schema asNonStrictSchema() {
        if (impl.isStrict()) {
            return new Schema(new NonStrictImpl(impl));
        } else {
        if (!impl.isStrict()) {
            return this;
        } else if (impl instanceof StrictImpl) {
            return new Schema(new NonStrictImpl((StrictImpl) impl));
        } else {
            // EmptyImpl
            return EMPTY_NON_STRICT_SCHEMA;
        }
    }
@@ -1475,13 +1465,23 @@
    public Schema asStrictSchema() {
        if (impl.isStrict()) {
            return this;
        } else {
        } else if (impl instanceof NonStrictImpl) {
            return new Schema(((NonStrictImpl) impl).strictImpl);
        } else {
            // EmptyImpl
            return EMPTY_STRICT_SCHEMA;
        }
    }
    /**
     * Returns the attribute type with the specified name or numeric OID.
     * <p>
     * If the requested attribute type is not registered in this schema and this
     * schema is non-strict then a temporary "place-holder" attribute type will
     * be created and returned. Place holder attribute types have an OID which
     * is the normalized attribute name with the string {@code -oid} appended.
     * In addition, they will use the directory string syntax and case ignore
     * matching rule.
     *
     * @param name
     *            The name or OID of the attribute type to retrieve.
@@ -1489,6 +1489,7 @@
     * @throws UnknownSchemaElementException
     *             If this is a strict schema and the requested attribute type
     *             was not found or if the provided name is ambiguous.
     * @see AttributeType#isPlaceHolder()
     */
    public AttributeType getAttributeType(final String name) {
        return impl.getAttributeType(name);
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFReader.java
@@ -501,7 +501,7 @@
        // Ensure that the binary option is present if required.
        if (!syntax.isBEREncodingRequired()) {
            if (schemaValidationPolicy.checkAttributeValues().needsChecking()
                    && attributeDescription.containsOption("binary")) {
                    && attributeDescription.hasOption("binary")) {
                final LocalizableMessage message =
                        ERR_LDIF_UNEXPECTED_BINARY_OPTION.get(record.lineNumber, entry.getName()
                                .toString(), attrDescr);
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java
@@ -538,7 +538,7 @@
            // Ensure that the binary option is present if required.
            if (!syntax.isBEREncodingRequired()) {
                if (schemaValidationPolicy.checkAttributeValues().needsChecking()
                        && attributeDescription.containsOption("binary")) {
                        && attributeDescription.hasOption("binary")) {
                    final LocalizableMessage message =
                            ERR_LDIF_UNEXPECTED_BINARY_OPTION.get(record.lineNumber, entryDN
                                    .toString(), pair.value);
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/AttributeDescriptionTestCase.java
@@ -219,7 +219,7 @@
        assertEquals(attributeDescription.isObjectClass(), isObjectClass);
        assertFalse(attributeDescription.hasOptions());
        assertFalse(attributeDescription.containsOption("dummy"));
        assertFalse(attributeDescription.hasOption("dummy"));
        final Iterator<String> iterator = attributeDescription.getOptions().iterator();
        assertFalse(iterator.hasNext());
@@ -250,19 +250,19 @@
            assertTrue(attributeDescription.hasOptions());
        }
        assertFalse(attributeDescription.containsOption("dummy"));
        assertFalse(attributeDescription.hasOption("dummy"));
        if (containsFoo) {
            assertTrue(attributeDescription.containsOption("foo"));
            assertTrue(attributeDescription.containsOption("FOO"));
            assertTrue(attributeDescription.containsOption("FoO"));
            assertTrue(attributeDescription.hasOption("foo"));
            assertTrue(attributeDescription.hasOption("FOO"));
            assertTrue(attributeDescription.hasOption("FoO"));
        } else {
            assertFalse(attributeDescription.containsOption("foo"));
            assertFalse(attributeDescription.containsOption("FOO"));
            assertFalse(attributeDescription.containsOption("FoO"));
            assertFalse(attributeDescription.hasOption("foo"));
            assertFalse(attributeDescription.hasOption("FOO"));
            assertFalse(attributeDescription.hasOption("FoO"));
        }
        for (final String option : options) {
            assertTrue(attributeDescription.containsOption(option));
            assertTrue(attributeDescription.hasOption(option));
        }
        final Iterator<String> iterator = attributeDescription.getOptions().iterator();
@@ -278,8 +278,8 @@
        AttributeDescription ad1 = AttributeDescription.valueOf("cn");
        AttributeDescription ad2 = ad1.withOption("test");
        assertTrue(ad2.hasOptions());
        assertTrue(ad2.containsOption("test"));
        assertFalse(ad2.containsOption("dummy"));
        assertTrue(ad2.hasOption("test"));
        assertFalse(ad2.hasOption("dummy"));
        assertEquals(ad2.toString(), "cn;test");
        assertEquals(ad2.getOptions().iterator().next(), "test");
    }
@@ -296,9 +296,9 @@
        AttributeDescription ad1 = AttributeDescription.valueOf("cn;test1");
        AttributeDescription ad2 = ad1.withOption("test2");
        assertTrue(ad2.hasOptions());
        assertTrue(ad2.containsOption("test1"));
        assertTrue(ad2.containsOption("test2"));
        assertFalse(ad2.containsOption("dummy"));
        assertTrue(ad2.hasOption("test1"));
        assertTrue(ad2.hasOption("test2"));
        assertFalse(ad2.hasOption("dummy"));
        assertEquals(ad2.toString(), "cn;test1;test2");
        Iterator<String> i = ad2.getOptions().iterator();
        assertEquals(i.next(), "test1");
@@ -326,7 +326,7 @@
        AttributeDescription ad1 = AttributeDescription.valueOf("cn;test");
        AttributeDescription ad2 = ad1.withoutOption("test");
        assertFalse(ad2.hasOptions());
        assertFalse(ad2.containsOption("test"));
        assertFalse(ad2.hasOption("test"));
        assertEquals(ad2.toString(), "cn");
        assertFalse(ad2.getOptions().iterator().hasNext());
    }
@@ -343,8 +343,8 @@
        AttributeDescription ad1 = AttributeDescription.valueOf("cn;test1;test2");
        AttributeDescription ad2 = ad1.withoutOption("test1");
        assertTrue(ad2.hasOptions());
        assertFalse(ad2.containsOption("test1"));
        assertTrue(ad2.containsOption("test2"));
        assertFalse(ad2.hasOption("test1"));
        assertTrue(ad2.hasOption("test2"));
        assertEquals(ad2.toString(), "cn;test2");
        assertEquals(ad2.getOptions().iterator().next(), "test2");
    }
@@ -354,8 +354,8 @@
        AttributeDescription ad1 = AttributeDescription.valueOf("cn;test1;test2");
        AttributeDescription ad2 = ad1.withoutOption("test2");
        assertTrue(ad2.hasOptions());
        assertTrue(ad2.containsOption("test1"));
        assertFalse(ad2.containsOption("test2"));
        assertTrue(ad2.hasOption("test1"));
        assertFalse(ad2.hasOption("test2"));
        assertEquals(ad2.toString(), "cn;test1");
        assertEquals(ad2.getOptions().iterator().next(), "test1");
    }
@@ -372,9 +372,9 @@
        AttributeDescription ad1 = AttributeDescription.valueOf("cn;test1;test2;test3");
        AttributeDescription ad2 = ad1.withoutOption("test1");
        assertTrue(ad2.hasOptions());
        assertFalse(ad2.containsOption("test1"));
        assertTrue(ad2.containsOption("test2"));
        assertTrue(ad2.containsOption("test3"));
        assertFalse(ad2.hasOption("test1"));
        assertTrue(ad2.hasOption("test2"));
        assertTrue(ad2.hasOption("test3"));
        assertEquals(ad2.toString(), "cn;test2;test3");
        Iterator<String> i = ad2.getOptions().iterator();
        assertEquals(i.next(), "test2");
@@ -386,9 +386,9 @@
        AttributeDescription ad1 = AttributeDescription.valueOf("cn;test1;test2;test3");
        AttributeDescription ad2 = ad1.withoutOption("test2");
        assertTrue(ad2.hasOptions());
        assertTrue(ad2.containsOption("test1"));
        assertFalse(ad2.containsOption("test2"));
        assertTrue(ad2.containsOption("test3"));
        assertTrue(ad2.hasOption("test1"));
        assertFalse(ad2.hasOption("test2"));
        assertTrue(ad2.hasOption("test3"));
        assertEquals(ad2.toString(), "cn;test1;test3");
        Iterator<String> i = ad2.getOptions().iterator();
        assertEquals(i.next(), "test1");
@@ -400,9 +400,9 @@
        AttributeDescription ad1 = AttributeDescription.valueOf("cn;test1;test2;test3");
        AttributeDescription ad2 = ad1.withoutOption("test3");
        assertTrue(ad2.hasOptions());
        assertTrue(ad2.containsOption("test1"));
        assertTrue(ad2.containsOption("test2"));
        assertFalse(ad2.containsOption("test3"));
        assertTrue(ad2.hasOption("test1"));
        assertTrue(ad2.hasOption("test2"));
        assertFalse(ad2.hasOption("test3"));
        assertEquals(ad2.toString(), "cn;test1;test2");
        Iterator<String> i = ad2.getOptions().iterator();
        assertEquals(i.next(), "test1");
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/EntryTestCase.java
@@ -22,11 +22,21 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2012 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import org.testng.Assert;
import static org.fest.assertions.Assertions.assertThat;
import static org.forgerock.opendj.ldap.Attributes.emptyAttribute;
import static org.forgerock.opendj.ldap.Attributes.singletonAttribute;
import java.util.LinkedList;
import java.util.List;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.SchemaBuilder;
import org.forgerock.opendj.ldif.LDIFEntryReader;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -35,60 +45,778 @@
 */
@SuppressWarnings("javadoc")
public final class EntryTestCase extends SdkTestCase {
    private static interface EntryFactory {
        Entry newEntry(String... ldifLines);
        Entry newEntry(String... ldifLines) throws Exception;
    }
    private static final class LinkedHashMapEntryFactory implements EntryFactory {
        public Entry newEntry(final String... ldifLines) {
            return new LinkedHashMapEntry(ldifLines);
        @Override
        public Entry newEntry(final String... ldifLines) throws Exception {
            final LDIFEntryReader reader = new LDIFEntryReader(ldifLines).setSchema(SCHEMA);
            final Entry entry = reader.readEntry();
            assertThat(reader.hasNext()).isFalse();
            return new LinkedHashMapEntry(entry);
        }
    }
    private static final class TreeMapEntryFactory implements EntryFactory {
        public Entry newEntry(final String... ldifLines) {
            return new TreeMapEntry(ldifLines);
        @Override
        public Entry newEntry(final String... ldifLines) throws Exception {
            final LDIFEntryReader reader = new LDIFEntryReader(ldifLines).setSchema(SCHEMA);
            final Entry entry = reader.readEntry();
            assertThat(reader.hasNext()).isFalse();
            return new TreeMapEntry(entry);
        }
    }
    private static final AttributeDescription AD_CN;
    private static final AttributeDescription AD_CUSTOM1;
    private static final AttributeDescription AD_CUSTOM2;
    private static final AttributeDescription AD_NAME;
    private static final AttributeDescription AD_SN;
    private static final Schema SCHEMA;
    static {
        final SchemaBuilder builder = new SchemaBuilder(Schema.getCoreSchema());
        builder.addAttributeType("( 9.9.9.1 NAME 'custom1' SUP name )", false);
        builder.addAttributeType("( 9.9.9.2 NAME 'custom2' SUP name )", false);
        SCHEMA = builder.toSchema();
        AD_CUSTOM1 = AttributeDescription.valueOf("custom1", SCHEMA);
        AD_CUSTOM2 = AttributeDescription.valueOf("custom2", SCHEMA);
        AD_CN = AttributeDescription.valueOf("cn");
        AD_SN = AttributeDescription.valueOf("sn");
        AD_NAME = AttributeDescription.valueOf("name");
    }
    @DataProvider(name = "EntryFactory")
    public Object[][] entryFactory() {
    Object[][] entryFactory() {
        // Value, type, options, containsOptions("foo")
        return new Object[][] { { new TreeMapEntryFactory() }, { new LinkedHashMapEntryFactory() } };
    }
    @Test(dataProvider = "EntryFactory")
    public void smokeTest(final EntryFactory factory) throws Exception {
        final Entry entry1 =
                factory.newEntry("dn: cn=Joe Bloggs,dc=example,dc=com", "objectClass: top",
                        "objectClass: person", "cn: Joe Bloggs", "sn: Bloggs", "givenName: Joe",
                        "description: A description");
    public void testAddAttributeAttribute(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.addAttribute(new LinkedAttribute("sn", "sn"))).isTrue();
        assertThat(entry.getAttribute(AD_SN)).hasSize(1);
    }
        final Entry entry2 =
                factory.newEntry("dn: cn=Joe Bloggs,dc=example,dc=com", "changetype: add",
                        "objectClass: top", "objectClass: person", "cn: Joe Bloggs", "sn: Bloggs",
                        "givenName: Joe", "description: A description");
    @Test(dataProvider = "EntryFactory")
    public void testAddAttributeAttributeCollection(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> duplicateValues = new LinkedList<ByteString>();
        assertThat(entry.addAttribute(new LinkedAttribute("sn", "sn"), duplicateValues)).isTrue();
        assertThat(entry.getAttribute(AD_SN)).hasSize(1);
        assertThat(duplicateValues).hasSize(0);
    }
        Assert.assertEquals(entry1, entry2);
    @Test(dataProvider = "EntryFactory")
    public void testAddAttributeAttributeCollectionValueMissing(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> duplicateValues = new LinkedList<ByteString>();
        assertThat(entry.addAttribute(new LinkedAttribute("cn", "newcn"), duplicateValues))
                .isTrue();
        assertThat(entry.getAttribute(AD_CN)).hasSize(2);
        assertThat(duplicateValues).hasSize(0);
    }
        for (final Entry e : new Entry[] { entry1, entry2 }) {
            Assert.assertEquals(e.getName(), DN.valueOf("cn=Joe Bloggs,dc=example,dc=com"));
            Assert.assertEquals(e.getAttributeCount(), 5);
    @Test(dataProvider = "EntryFactory")
    public void testAddAttributeAttributeCollectionValuePresent(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> duplicateValues = new LinkedList<ByteString>();
        assertThat(entry.addAttribute(new LinkedAttribute("cn", "test"), duplicateValues))
                .isFalse();
        assertThat(entry.getAttribute(AD_CN)).hasSize(1);
        assertThat(duplicateValues).hasSize(1);
        assertThat(duplicateValues).contains(ByteString.valueOf("test"));
    }
            Assert.assertEquals(e.getAttribute("objectClass").size(), 2);
            Assert.assertTrue(e.containsAttribute("objectClass", "top", "person"));
            Assert.assertFalse(e.containsAttribute("objectClass", "top", "person", "foo"));
    @Test(dataProvider = "EntryFactory")
    public void testAddAttributeAttributeValueMissing(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.addAttribute(new LinkedAttribute("cn", "newcn"))).isTrue();
        assertThat(entry.getAttribute(AD_CN)).hasSize(2);
    }
            Assert.assertTrue(e.containsAttribute("objectClass"));
            Assert.assertTrue(e.containsAttribute("cn"));
            Assert.assertTrue(e.containsAttribute("cn", "Joe Bloggs"));
            Assert.assertFalse(e.containsAttribute("cn", "Jane Bloggs"));
            Assert.assertTrue(e.containsAttribute("sn"));
            Assert.assertTrue(e.containsAttribute("givenName"));
            Assert.assertTrue(e.containsAttribute("description"));
    @Test(dataProvider = "EntryFactory")
    public void testAddAttributeAttributeValuePresent(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.addAttribute(new LinkedAttribute("cn", "test"))).isFalse();
        assertThat(entry.getAttribute(AD_CN)).hasSize(1);
    }
            Assert.assertEquals(e.getAttribute("cn").firstValueAsString(), "Joe Bloggs");
            Assert.assertEquals(e.getAttribute("sn").firstValueAsString(), "Bloggs");
        }
    @Test(dataProvider = "EntryFactory")
    public void testAddAttributeString(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.addAttribute("sn", "sn")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_SN)).hasSize(1);
    }
    @Test(dataProvider = "EntryFactory")
    public void testAddAttributeStringCustom(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.addAttribute("custom2", "custom2")).isSameAs(entry);
        // This is expected to be null since the type was decoded using the
        // default schema and a temporary oid was allocated.
        assertThat(entry.getAttribute(AD_CUSTOM2)).isNull();
        assertThat(entry.getAttribute("custom2")).hasSize(1);
    }
    @Test(dataProvider = "EntryFactory")
    public void testAddAttributeStringCustomValueMissing(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.addAttribute("custom1", "xxxx")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_CUSTOM1)).hasSize(2);
    }
    @Test(dataProvider = "EntryFactory")
    public void testAddAttributeStringCustomValuePresent(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.addAttribute("custom1", "custom1")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_CUSTOM1)).hasSize(1);
    }
    @Test(dataProvider = "EntryFactory")
    public void testAddAttributeStringValueMissing(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.addAttribute("cn", "newcn")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_CN)).hasSize(2);
    }
    @Test(dataProvider = "EntryFactory")
    public void testAddAttributeStringValuePresent(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.addAttribute("cn", "test")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_CN)).hasSize(1);
    }
    @Test(dataProvider = "EntryFactory")
    public void testClearAttributes(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.clearAttributes()).isSameAs(entry);
        assertThat(entry.getAttributeCount()).isEqualTo(0);
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeAttributeCustomMissing(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.containsAttribute(emptyAttribute(AD_CUSTOM2), missingValues)).isFalse();
        assertThat(missingValues).isEmpty();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeAttributeCustomPresent1(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.containsAttribute(emptyAttribute(AD_CUSTOM1), missingValues)).isTrue();
        assertThat(missingValues).isEmpty();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeAttributeCustomPresent2(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.containsAttribute(emptyAttribute("custom1"), missingValues)).isTrue();
        assertThat(missingValues).isEmpty();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeAttributeCustomValueMissing1(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(
                entry.containsAttribute(singletonAttribute(AD_CUSTOM2, "missing"), missingValues))
                .isFalse();
        assertThat(missingValues).hasSize(1);
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeAttributeMissing(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.containsAttribute(emptyAttribute(AD_SN), missingValues)).isFalse();
        assertThat(missingValues).isEmpty();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeAttributePresent1(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.containsAttribute(emptyAttribute(AD_CN), missingValues)).isTrue();
        assertThat(missingValues).isEmpty();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeAttributePresent2(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.containsAttribute(emptyAttribute("cn"), missingValues)).isTrue();
        assertThat(missingValues).isEmpty();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeAttributeValueCustomMissing2(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(
                entry.containsAttribute(singletonAttribute(AD_CUSTOM1, "missing"), missingValues))
                .isFalse();
        assertThat(missingValues).hasSize(1);
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeAttributeValueCustomPresent(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(
                entry.containsAttribute(singletonAttribute(AD_CUSTOM1, "custom1"), missingValues))
                .isTrue();
        assertThat(missingValues).isEmpty();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeAttributeValueMissing1(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.containsAttribute(singletonAttribute(AD_SN, "missing"), missingValues))
                .isFalse();
        assertThat(missingValues).hasSize(1);
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeAttributeValueMissing2(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.containsAttribute(singletonAttribute(AD_CN, "missing"), missingValues))
                .isFalse();
        assertThat(missingValues).hasSize(1);
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeAttributeValuePresent(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.containsAttribute(singletonAttribute(AD_CN, "test"), missingValues))
                .isTrue();
        assertThat(missingValues).isEmpty();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeStringCustomMissing(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.containsAttribute("custom2")).isFalse();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeStringCustomPresent(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.containsAttribute("custom1")).isTrue();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeStringMissing(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.containsAttribute("sn")).isFalse();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeStringPresent(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.containsAttribute("cn")).isTrue();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeStringValueCustom(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.containsAttribute("custom1", "custom1")).isTrue();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeStringValueMissing1(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.containsAttribute("cn", "missing")).isFalse();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeStringValueMissing2(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.containsAttribute("sn", "missing")).isFalse();
    }
    @Test(dataProvider = "EntryFactory")
    public void testContainsAttributeStringValuePresent(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.containsAttribute("cn", "test")).isTrue();
    }
    @Test
    public void testEqualsHashCodeDifferentContentDifferentTypes1() throws Exception {
        final Entry e1 = createTestEntry(new TreeMapEntryFactory());
        // Extra attributes.
        final Entry e2 = createTestEntry(new LinkedHashMapEntryFactory()).addAttribute("sn", "sn");
        assertThat(e1).isNotEqualTo(e2);
        assertThat(e2).isNotEqualTo(e1);
        assertThat(e1.hashCode()).isNotEqualTo(e2.hashCode());
    }
    @Test
    public void testEqualsHashCodeDifferentContentDifferentTypes2() throws Exception {
        final Entry e1 = createTestEntry(new TreeMapEntryFactory());
        // Same attributes, extra values.
        final Entry e2 =
                createTestEntry(new LinkedHashMapEntryFactory()).addAttribute("cn", "newcn");
        assertThat(e1).isNotEqualTo(e2);
        assertThat(e2).isNotEqualTo(e1);
        assertThat(e1.hashCode()).isNotEqualTo(e2.hashCode());
    }
    @Test(dataProvider = "EntryFactory")
    public void testEqualsHashCodeDifferentContentSameTypes1(final EntryFactory factory)
            throws Exception {
        final Entry e1 = createTestEntry(factory);
        // Extra attributes.
        final Entry e2 = createTestEntry(factory).addAttribute("sn", "sn");
        assertThat(e1).isNotEqualTo(e2);
        assertThat(e2).isNotEqualTo(e1);
        assertThat(e1.hashCode()).isNotEqualTo(e2.hashCode());
    }
    @Test(dataProvider = "EntryFactory")
    public void testEqualsHashCodeDifferentContentSameTypes2(final EntryFactory factory)
            throws Exception {
        final Entry e1 = createTestEntry(factory);
        // Same attributes, extra values.
        final Entry e2 = createTestEntry(factory).addAttribute("cn", "newcn");
        assertThat(e1).isNotEqualTo(e2);
        assertThat(e2).isNotEqualTo(e1);
        assertThat(e1.hashCode()).isNotEqualTo(e2.hashCode());
    }
    @Test(dataProvider = "EntryFactory")
    public void testEqualsHashCodeDifferentDN(final EntryFactory factory) throws Exception {
        final Entry e1 = createTestEntry(factory);
        final Entry e2 = createTestEntry(factory).setName("cn=foobar");
        assertThat(e1).isNotEqualTo(e2);
        assertThat(e1.hashCode()).isNotEqualTo(e2.hashCode());
    }
    @Test(dataProvider = "EntryFactory")
    public void testEqualsHashCodeMutates(final EntryFactory factory) throws Exception {
        final Entry e = createTestEntry(factory);
        final int hc1 = e.hashCode();
        e.addAttribute("sn", "sn");
        final int hc2 = e.hashCode();
        assertThat(hc1).isNotEqualTo(hc2);
    }
    @Test
    public void testEqualsHashCodeSameContentDifferentTypes() throws Exception {
        final Entry e1 = createTestEntry(new TreeMapEntryFactory());
        final Entry e2 = createTestEntry(new LinkedHashMapEntryFactory());
        assertThat(e1).isEqualTo(e2);
        assertThat(e2).isEqualTo(e1);
        assertThat(e1.hashCode()).isEqualTo(e2.hashCode());
    }
    @Test(dataProvider = "EntryFactory")
    public void testEqualsHashCodeSameContentSameTypes(final EntryFactory factory) throws Exception {
        final Entry e1 = createTestEntry(factory);
        final Entry e2 = createTestEntry(factory);
        assertThat(e1).isEqualTo(e1);
        assertThat(e1).isEqualTo(e2);
        assertThat(e1.hashCode()).isEqualTo(e2.hashCode());
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAllAttributes(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.getAllAttributes().iterator()).hasSize(3);
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAllAttributesAttributeDescriptionMissing(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.getAllAttributes(AD_SN)).hasSize(0);
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAllAttributesAttributeDescriptionPresent(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.getAllAttributes(AD_CN)).hasSize(1);
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAllAttributesAttributeDescriptionPresentOptions(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        entry.addAttribute(singletonAttribute(AD_CN.withOption("lang-fr"), "xxxx"));
        assertThat(entry.getAllAttributes(AD_CN)).hasSize(2);
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAllAttributesAttributeDescriptionSupertype(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.getAllAttributes(AD_NAME)).hasSize(2);
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAllAttributesStringCustom(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        entry.addAttribute(singletonAttribute(AD_CUSTOM1.withOption("lang-fr"), "xxxx"));
        assertThat(entry.getAllAttributes("custom1")).hasSize(2);
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAllAttributesStringCustomOptions(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        entry.addAttribute("custom2", "value1");
        entry.addAttribute("custom2;lang-fr", "value2");
        assertThat(entry.getAllAttributes("custom2")).hasSize(2);
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAllAttributesStringMissing(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.getAllAttributes("sn")).hasSize(0);
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAllAttributesStringPresent(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.getAllAttributes("cn")).hasSize(1);
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAllAttributesStringSupertype(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.getAllAttributes("name")).hasSize(2);
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAttributeAttributeDescriptionMissing(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.getAttribute(AD_SN)).isNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAttributeAttributeDescriptionPresent(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.getAttribute(AD_CN)).isNotNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAttributeCount(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.getAttributeCount()).isEqualTo(3);
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAttributeStringCustom(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.getAttribute("custom1")).isNotNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAttributeStringMissing(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.getAttribute("sn")).isNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetAttributeStringPresent(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.getAttribute("cn")).isNotNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testGetName(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat((Object) entry.getName()).isEqualTo(DN.valueOf("cn=test"));
    }
    @Test(dataProvider = "EntryFactory")
    public void testParseAttributeAttributeDescriptionCustom(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.parseAttribute(AD_CUSTOM1).asString()).isEqualTo("custom1");
    }
    @Test(dataProvider = "EntryFactory")
    public void testParseAttributeAttributeDescriptionMissing(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.parseAttribute(AD_SN).asString()).isNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testParseAttributeAttributeDescriptionPresent(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.parseAttribute(AD_CN).asString()).isEqualTo("test");
    }
    @Test(dataProvider = "EntryFactory")
    public void testParseAttributeStringCustom(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.parseAttribute("custom1").asString()).isEqualTo("custom1");
    }
    @Test(dataProvider = "EntryFactory")
    public void testParseAttributeStringMissing(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.parseAttribute("sn").asString()).isNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testParseAttributeStringPresent(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.parseAttribute("cn").asString()).isEqualTo("test");
    }
    @Test(dataProvider = "EntryFactory")
    public void testRemoveAttributeAttributeDescriptionMissing(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.removeAttribute(AD_SN)).isFalse();
    }
    @Test(dataProvider = "EntryFactory")
    public void testRemoveAttributeAttributeDescriptionPresent(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.removeAttribute(AD_CN)).isTrue();
        assertThat(entry.getAttribute(AD_CN)).isNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testRemoveAttributeAttributeMissing(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.removeAttribute(emptyAttribute(AD_SN), missingValues)).isFalse();
        assertThat(missingValues).isEmpty();
    }
    @Test(dataProvider = "EntryFactory")
    public void testRemoveAttributeAttributePresent(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.removeAttribute(emptyAttribute(AD_CN), missingValues)).isTrue();
        assertThat(entry.getAttribute(AD_CN)).isNull();
        assertThat(missingValues).isEmpty();
    }
    @Test(dataProvider = "EntryFactory")
    public void testRemoveAttributeAttributeValueMissing1(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.removeAttribute(singletonAttribute(AD_CN, "missing"), missingValues))
                .isFalse();
        assertThat(entry.getAttribute(AD_CN)).isNotNull();
        assertThat(missingValues).hasSize(1);
    }
    @Test(dataProvider = "EntryFactory")
    public void testRemoveAttributeAttributeValueMissing2(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.removeAttribute(singletonAttribute(AD_SN, "missing"), missingValues))
                .isFalse();
        assertThat(missingValues).hasSize(1);
    }
    @Test(dataProvider = "EntryFactory")
    public void testRemoveAttributeAttributeValuePresent(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        final List<ByteString> missingValues = new LinkedList<ByteString>();
        assertThat(entry.removeAttribute(singletonAttribute(AD_CN, "test"), missingValues))
                .isTrue();
        assertThat(entry.getAttribute(AD_CN)).isNull();
        assertThat(missingValues).isEmpty();
    }
    @Test(dataProvider = "EntryFactory")
    public void testRemoveAttributeStringCustom(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.removeAttribute("custom1")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_CUSTOM1)).isNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testRemoveAttributeStringMissing(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.removeAttribute("sn")).isSameAs(entry);
        assertThat(entry.getAttributeCount()).isEqualTo(3);
    }
    @Test(dataProvider = "EntryFactory")
    public void testRemoveAttributeStringPresent(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.removeAttribute("cn")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_CN)).isNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testRemoveAttributeStringValueMissing1(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.removeAttribute("cn", "missing")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_CN)).isNotNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testRemoveAttributeStringValueMissing2(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.removeAttribute("sn", "missing")).isSameAs(entry);
        assertThat(entry.getAttributeCount()).isEqualTo(3);
    }
    @Test(dataProvider = "EntryFactory")
    public void testRemoveAttributeStringValuePresent(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.removeAttribute("cn", "test")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_CN)).isNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testReplaceAttributeAttributeMissingEmpty(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.replaceAttribute(emptyAttribute(AD_SN))).isFalse();
        assertThat(entry.getAttribute(AD_SN)).isNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testReplaceAttributeAttributeMissingValue(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.replaceAttribute(singletonAttribute(AD_SN, "sn"))).isTrue();
        assertThat(entry.getAttribute(AD_SN)).isEqualTo(singletonAttribute(AD_SN, "sn"));
    }
    @Test(dataProvider = "EntryFactory")
    public void testReplaceAttributeAttributePresentEmpty(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.replaceAttribute(emptyAttribute(AD_CN))).isTrue();
        assertThat(entry.getAttribute(AD_CN)).isNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testReplaceAttributeAttributePresentValue(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.replaceAttribute(singletonAttribute(AD_CN, "newcn"))).isTrue();
        assertThat(entry.getAttribute(AD_CN)).isEqualTo(singletonAttribute(AD_CN, "newcn"));
    }
    @Test(dataProvider = "EntryFactory")
    public void testReplaceAttributeStringCustomEmpty(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.replaceAttribute("custom1")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_CUSTOM1)).isNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testReplaceAttributeStringCustomMissingValue(final EntryFactory factory)
            throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.replaceAttribute("custom2", "xxxx")).isSameAs(entry);
        // This is expected to be null since the type was decoded using the
        // default schema and a temporary oid was allocated.
        assertThat(entry.getAttribute(AD_CUSTOM2)).isNull();
        assertThat(entry.getAttribute("custom2")).isEqualTo(singletonAttribute("custom2", "xxxx"));
    }
    @Test(dataProvider = "EntryFactory")
    public void testReplaceAttributeStringCustomValue(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.replaceAttribute("custom1", "xxxx")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_CUSTOM1))
                .isEqualTo(singletonAttribute(AD_CUSTOM1, "xxxx"));
    }
    @Test(dataProvider = "EntryFactory")
    public void testReplaceAttributeStringMissingEmpty(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.replaceAttribute("sn")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_SN)).isNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testReplaceAttributeStringMissingValue(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.replaceAttribute("sn", "sn")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_SN)).isEqualTo(singletonAttribute(AD_SN, "sn"));
    }
    @Test(dataProvider = "EntryFactory")
    public void testReplaceAttributeStringPresentEmpty(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.replaceAttribute("cn")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_CN)).isNull();
    }
    @Test(dataProvider = "EntryFactory")
    public void testReplaceAttributeStringPresentValue(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.replaceAttribute("cn", "newcn")).isSameAs(entry);
        assertThat(entry.getAttribute(AD_CN)).isEqualTo(singletonAttribute(AD_CN, "newcn"));
    }
    @Test(dataProvider = "EntryFactory")
    public void testSetNameDN(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.setName(DN.valueOf("cn=foobar"))).isSameAs(entry);
        assertThat((Object) entry.getName()).isEqualTo(DN.valueOf("cn=foobar"));
    }
    @Test(dataProvider = "EntryFactory")
    public void testSetNameString(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        assertThat(entry.setName("cn=foobar")).isSameAs(entry);
        assertThat((Object) entry.getName()).isEqualTo(DN.valueOf("cn=foobar"));
    }
    @Test(dataProvider = "EntryFactory")
    public void testToString(final EntryFactory factory) throws Exception {
        final Entry entry = createTestEntry(factory);
        // The String representation is unspecified but we should at least
        // expect the DN to be present.
        assertThat(entry.toString()).contains("cn=test");
    }
    private Entry createTestEntry(final EntryFactory factory) throws Exception {
        return factory.newEntry("dn: cn=test", "objectClass: top", "objectClass: extensibleObject",
                "cn: test", "custom1: custom1");
    }
}