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

Violette Roche-Montane
08.40.2012 60f3502563cd2c71a751b74f0b34bfe2f5d17330
Fix OPENDJ-619 : Reduce the memory utilization of LDIF.diff(EntryReader, EntryReader) in the SDK
4 files modified
1828 ■■■■ changed files
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/DN.java 136 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleImpl.java 135 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIF.java 221 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFTestCase.java 1336 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/DN.java
@@ -27,19 +27,24 @@
package org.forgerock.opendj.ldap;
import static com.forgerock.opendj.util.StaticUtils.getBytes;
import static org.forgerock.opendj.ldap.CoreMessages.ERR_DN_TYPE_NOT_FOUND;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.TreeSet;
import java.util.WeakHashMap;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.ldap.schema.MatchingRule;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.Syntax;
import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException;
import com.forgerock.opendj.util.StaticUtils;
import com.forgerock.opendj.util.SubstringReader;
import com.forgerock.opendj.util.Validator;
@@ -799,4 +804,135 @@
        }
        return stringValue;
    }
    /**
     * Returns the normalized string representation of a DN.
     *
     * @return The normalized string representation of the provided DN.
     */
    public String toNormalizedString() {
        final StringBuilder builder = new StringBuilder(this.size());
        if (rdn() == null) {
            return builder.toString();
        }
        int i = this.size() - 1;
        normalizeRDN(builder, parent(i).rdn());
        for (i--; i >= 0; i--) {
            final RDN rdn = parent(i).rdn();
            // Only add a separator if the RDN is not RDN.maxValue().
            if (rdn.size() != 0) {
                builder.append('\u0000');
            }
            normalizeRDN(builder, rdn);
        }
        return builder.toString();
    }
    /**
     * Returns the normalized string representation of a RDN.
     *
     * @param builder
     *            The StringBuilder to use to construct the normalized string.
     * @param rdn
     *            The RDN.
     * @return The normalized string representation of the provided RDN.
     */
    private static StringBuilder normalizeRDN(final StringBuilder builder, final RDN rdn) {
        final int sz = rdn.size();
        switch (sz) {
        case 0:
            // Handle RDN.maxValue().
            builder.append('\u0001');
            break;
        case 1:
            normalizeAVA(builder, rdn.getFirstAVA());
            break;
        default:
            // Need to sort the AVAs before comparing.
            TreeSet<AVA> a = new TreeSet<AVA>();
            for (AVA ava : rdn) {
                a.add(ava);
            }
            Iterator<AVA> i = a.iterator();
            // Normalize the first AVA.
            normalizeAVA(builder, i.next());
            while (i.hasNext()) {
                builder.append('\u0001');
                normalizeAVA(builder, i.next());
            }
            break;
        }
        return builder;
    }
    /**
     * Returns the normalized string representation of an AVA.
     *
     * @param builder
     *            The StringBuilder to use to construct the normalized string.
     * @param ava
     *            The AVA.
     * @return The normalized string representation of the provided AVA.
     */
    private static StringBuilder normalizeAVA(final StringBuilder builder, final AVA ava) {
        ByteString value = ava.getAttributeValue();
        final MatchingRule matchingRule = ava.getAttributeType().getEqualityMatchingRule();
        if (matchingRule != null) {
            try {
                value = matchingRule.normalizeAttributeValue(ava.getAttributeValue());
            } catch (final DecodeException de) {
                // Ignore - we'll drop back to the user provided value.
            }
        }
        if (!ava.getAttributeType().getNames().iterator().hasNext()) {
            builder.append(ava.getAttributeType().getOID());
            builder.append("=#");
            StaticUtils.toHex(value, builder);
        } else {
            final String name = ava.getAttributeType().getNameOrOID();
            // Normalizing.
            StaticUtils.toLowerCase(name, builder);
            builder.append("=");
            final Syntax syntax = ava.getAttributeType().getSyntax();
            if (!syntax.isHumanReadable()) {
                builder.append("#");
                StaticUtils.toHex(value, builder);
            } else {
                final String str = value.toString();
                if (str.length() == 0) {
                    return builder;
                }
                char c = str.charAt(0);
                int startPos = 0;
                if ((c == ' ') || (c == '#')) {
                    builder.append('\\');
                    builder.append(c);
                    startPos = 1;
                }
                final int length = str.length();
                for (int si = startPos; si < length; si++) {
                    c = str.charAt(si);
                    if (c < ' ') {
                        for (final byte b : getBytes(String.valueOf(c))) {
                            builder.append('\\');
                            builder.append(StaticUtils.byteToLowerHex(b));
                        }
                    } else {
                        if ((c == ' ' && si == length - 1)
                                || (c == '"' || c == '+' || c == ',' || c == ';' || c == '<'
                                        || c == '=' || c == '>' || c == '\\' || c == '\u0000')) {
                            builder.append('\\');
                        }
                        builder.append(c);
                    }
                }
            }
        }
        return builder;
    }
}
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleImpl.java
@@ -22,151 +22,21 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2012 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.schema;
import static com.forgerock.opendj.util.StaticUtils.getBytes;
import java.util.Iterator;
import java.util.TreeSet;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.ldap.AVA;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.RDN;
import com.forgerock.opendj.util.StaticUtils;
/**
 * This class defines the distinguishedNameMatch matching rule defined in X.520
 * and referenced in RFC 2252.
 */
final class DistinguishedNameEqualityMatchingRuleImpl extends AbstractMatchingRuleImpl {
    /**
     * Returns the normalized string representation of an AVA.
     *
     * @param builder
     *            The StringBuilder to use to construct the normalized string.
     * @param ava
     *            The AVA.
     * @return The normalized string representation of the provided AVA.
     */
    private static StringBuilder normalizeAVA(final StringBuilder builder, final AVA ava) {
        ByteString value = ava.getAttributeValue();
        final MatchingRule matchingRule = ava.getAttributeType().getEqualityMatchingRule();
        if (matchingRule != null) {
            try {
                value = matchingRule.normalizeAttributeValue(ava.getAttributeValue());
            } catch (final DecodeException de) {
                // Ignore - we'll drop back to the user provided value.
            }
        }
        if (!ava.getAttributeType().getNames().iterator().hasNext()) {
            builder.append(ava.getAttributeType().getOID());
            builder.append("=#");
            StaticUtils.toHex(value, builder);
        } else {
            final String name = ava.getAttributeType().getNameOrOID();
            // Normalizing.
            StaticUtils.toLowerCase(name, builder);
            builder.append("=");
            final Syntax syntax = ava.getAttributeType().getSyntax();
            if (!syntax.isHumanReadable()) {
                builder.append("#");
                StaticUtils.toHex(value, builder);
            } else {
                final String str = value.toString();
                if (str.length() == 0) {
                    return builder;
                }
                char c = str.charAt(0);
                int startPos = 0;
                if ((c == ' ') || (c == '#')) {
                    builder.append('\\');
                    builder.append(c);
                    startPos = 1;
                }
                final int length = str.length();
                for (int si = startPos; si < length; si++) {
                    c = str.charAt(si);
                    if (c < ' ') {
                        for (final byte b : getBytes(String.valueOf(c))) {
                            builder.append('\\');
                            builder.append(StaticUtils.byteToLowerHex(b));
                        }
                    } else {
                        if ((c == ' ' && si == length - 1)
                                || (c == '"' || c == '+' || c == ',' || c == ';' || c == '<'
                                        || c == '=' || c == '>' || c == '\\' || c == '\u0000')) {
                            builder.append('\\');
                        }
                        builder.append(c);
                    }
                }
            }
        }
        return builder;
    }
    /**
     * Returns the normalized string representation of a DN.
     *
     * @param builder
     *            The StringBuilder to use to construct the normalized string.
     * @param dn
     *            The DN.
     * @return The normalized string representation of the provided DN.
     */
    private static StringBuilder normalizeDN(final StringBuilder builder, final DN dn) {
        if (dn.rdn() == null) {
            return builder;
        }
        int i = dn.size() - 1;
        normalizeRDN(builder, dn.parent(i).rdn());
        for (i--; i >= 0; i--) {
            builder.append('\u0000');
            normalizeRDN(builder, dn.parent(i).rdn());
        }
        return builder;
    }
    /**
     * Returns the normalized string representation of a RDN.
     *
     * @param builder
     *            The StringBuilder to use to construct the normalized string.
     * @param rdn
     *            The RDN.
     * @return The normalized string representation of the provided RDN.
     */
    private static StringBuilder normalizeRDN(final StringBuilder builder, final RDN rdn) {
        final int sz = rdn.size();
        if (sz == 1) {
            return normalizeAVA(builder, rdn.getFirstAVA());
        } else {
            // Need to sort the AVAs before comparing.
            TreeSet<AVA> a = new TreeSet<AVA>();
            for (AVA ava : rdn) {
                a.add(ava);
            }
            Iterator<AVA> i = a.iterator();
            // Normalize the first AVA.
            normalizeAVA(builder, i.next());
            while (i.hasNext()) {
                builder.append('\u0001');
                normalizeAVA(builder, i.next());
            }
            return builder;
        }
    }
    /**
     * {@inheritDoc}
@@ -175,8 +45,7 @@
            throws DecodeException {
        try {
            DN dn = DN.valueOf(value.toString(), schema.asNonStrictSchema());
            StringBuilder builder = new StringBuilder(value.length());
            return ByteString.valueOf(normalizeDN(builder, dn));
            return ByteString.valueOf(dn.toNormalizedString());
        } catch (final LocalizedIllegalArgumentException e) {
            throw DecodeException.error(e.getMessageObject());
        }
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/LDIF.java
@@ -28,19 +28,29 @@
import static org.forgerock.opendj.ldap.CoreMessages.REJECTED_CHANGE_FAIL_DELETE;
import static org.forgerock.opendj.ldap.CoreMessages.REJECTED_CHANGE_FAIL_MODIFY;
import static org.forgerock.opendj.ldap.CoreMessages.REJECTED_CHANGE_FAIL_MODIFYDN;
import static com.forgerock.opendj.util.StaticUtils.getBytes;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.SortedMap;
import java.util.TreeMap;
import org.forgerock.opendj.asn1.ASN1;
import org.forgerock.opendj.asn1.ASN1Reader;
import org.forgerock.opendj.asn1.ASN1Writer;
import org.forgerock.opendj.ldap.AVA;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.Attributes;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.DecodeOptions;
@@ -58,9 +68,13 @@
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.schema.AttributeUsage;
import org.forgerock.opendj.ldap.schema.Schema;
import com.forgerock.opendj.ldap.LDAPUtils;
/**
 * This class contains common utility methods for creating and manipulating
 * readers and writers.
@@ -77,6 +91,26 @@
    // @formatter:on
    /**
     * Comparator ordering the DN ASC.
     */
    private static final Comparator<byte[][]> DN_ORDER2 = new Comparator<byte[][]>() {
        public int compare(byte[][] b1, byte[][] b2) {
            return DN_ORDER.compare(b1[0], b2[0]);
        }
    };
    /**
     * Comparator ordering the DN ASC.
     */
    private static final Comparator<byte[]> DN_ORDER = new Comparator<byte[]>() {
        public int compare(byte[] b1, byte[] b2) {
            final ByteString bs = ByteString.valueOf(b1);
            final ByteString bs2 = ByteString.valueOf(b2);
            return bs.compareTo(bs2);
        }
    };
    /**
     * Copies the content of {@code input} to {@code output}. This method does
     * not close {@code input} or {@code output}.
     *
@@ -137,10 +171,11 @@
     */
    public static ChangeRecordReader diff(final EntryReader source, final EntryReader target)
            throws IOException {
        final SortedMap<DN, Entry> sourceEntries = readEntries(source);
        final SortedMap<DN, Entry> targetEntries = readEntries(target);
        final Iterator<Entry> sourceIterator = sourceEntries.values().iterator();
        final Iterator<Entry> targetIterator = targetEntries.values().iterator();
        final List<byte[][]> source2 = readEntriesAsList(source);
        final List<byte[][]> target2 = readEntriesAsList(target);
        final Iterator<byte[][]> sourceIterator = source2.iterator();
        final Iterator<byte[][]> targetIterator = target2.iterator();
        return new ChangeRecordReader() {
            private Entry sourceEntry = nextEntry(sourceIterator);
@@ -201,11 +236,13 @@
                }
            }
            private Entry nextEntry(final Iterator<Entry> i) {
                return i.hasNext() ? i.next() : null;
            private Entry nextEntry(final Iterator<byte[][]> i) throws IOException {
                if (i.hasNext()) {
                    return decodeEntry(i.next()[1]);
                }
                return null;
            }
        };
    }
    /**
@@ -240,6 +277,12 @@
     * <b>NOTE:</b> this method reads the content of {@code input} into memory
     * before applying the changes, and is therefore not suited for use in cases
     * where a very large number of entries are to be patched.
     * <p>
     * <b>NOTE:</b> this method will not perform modifications required in order
     * to maintain referential integrity. In particular, if an entry references
     * another entry using a DN valued attribute and the referenced entry is
     * deleted, then the DN reference will not be removed. The same applies to
     * renamed entries and their references.
     *
     * @param input
     *            The entry reader containing the set of entries to be patched.
@@ -264,6 +307,12 @@
     * <b>NOTE:</b> this method reads the content of {@code input} into memory
     * before applying the changes, and is therefore not suited for use in cases
     * where a very large number of entries are to be patched.
     * <p>
     * <b>NOTE:</b> this method will not perform modifications required in order
     * to maintain referential integrity. In particular, if an entry references
     * another entry using a DN valued attribute and the referenced entry is
     * deleted, then the DN reference will not be removed. The same applies to
     * renamed entries and their references.
     *
     * @param input
     *            The entry reader containing the set of entries to be patched.
@@ -278,10 +327,12 @@
     */
    public static EntryReader patch(final EntryReader input, final ChangeRecordReader patch,
            final RejectedChangeRecordListener listener) throws IOException {
        final SortedMap<DN, Entry> entries = readEntries(input);
        final SortedMap<byte[], byte[]> entries = readEntriesAsMap(input);
        while (patch.hasNext()) {
            final ChangeRecord change = patch.readChangeRecord();
            final DN changeDN = change.getName();
            final byte[] changeNormDN = getBytes(change.getName().toNormalizedString());
            final DecodeException de =
                    change.accept(new ChangeRecordVisitor<DecodeException, Void>() {
@@ -289,17 +340,19 @@
                        @Override
                        public DecodeException visitChangeRecord(final Void p,
                                final AddRequest change) {
                            final Entry existingEntry = entries.get(change.getName());
                            if (existingEntry != null) {
                            if (entries.get(changeNormDN) != null) {
                                final Entry existingEntry = decodeEntry(entries.get(changeNormDN));
                                try {
                                    final Entry entry =
                                            listener.handleDuplicateEntry(change, existingEntry);
                                    entries.put(entry.getName(), entry);
                                    entries.put(getBytes(entry.getName().toNormalizedString()),
                                            encodeEntry(entry)[1]);
                                } catch (final DecodeException e) {
                                    return e;
                                }
                            } else {
                                entries.put(change.getName(), change);
                                entries.put(changeNormDN, encodeEntry(change)[1]);
                            }
                            return null;
                        }
@@ -307,7 +360,7 @@
                        @Override
                        public DecodeException visitChangeRecord(final Void p,
                                final DeleteRequest change) {
                            if (!entries.containsKey(change.getName())) {
                            if (entries.get(changeNormDN) == null) {
                                try {
                                    listener.handleRejectedChangeRecord(change,
                                            REJECTED_CHANGE_FAIL_DELETE.get(change.getName()
@@ -319,10 +372,12 @@
                                try {
                                    if (change.getControl(SubtreeDeleteRequestControl.DECODER,
                                            new DecodeOptions()) != null) {
                                        entries.subMap(change.getName(),
                                                change.getName().child(RDN.maxValue())).clear();
                                        entries.subMap(
                                                getBytes(change.getName().toNormalizedString()),
                                                getBytes(change.getName().child(RDN.maxValue())
                                                        .toNormalizedString())).clear();
                                    } else {
                                        entries.remove(change.getName());
                                        entries.remove(changeNormDN);
                                    }
                                } catch (final DecodeException e) {
                                    return e;
@@ -335,7 +390,7 @@
                        @Override
                        public DecodeException visitChangeRecord(final Void p,
                                final ModifyDNRequest change) {
                            if (!entries.containsKey(change.getName())) {
                            if (entries.get(changeNormDN) == null) {
                                try {
                                    listener.handleRejectedChangeRecord(change,
                                            REJECTED_CHANGE_FAIL_MODIFYDN.get(change.getName()
@@ -345,7 +400,7 @@
                                }
                            } else {
                                // Calculate the old and new DN.
                                final DN oldDN = change.getName();
                                final DN oldDN = changeDN;
                                DN newSuperior = change.getNewSuperior();
                                if (newSuperior == null) {
@@ -357,51 +412,72 @@
                                final DN newDN = newSuperior.child(change.getNewRDN());
                                // Move the renamed entries into a separate map
                                // in order to
                                // avoid cases where the renamed subtree
                                // overlaps.
                                final SortedMap<DN, Entry> renamedEntries =
                                        new TreeMap<DN, Entry>();
                                final Iterator<Map.Entry<DN, Entry>> i =
                                        entries.subMap(change.getName(),
                                                change.getName().child(RDN.maxValue())).entrySet()
                                                .iterator();
                                while (i.hasNext()) {
                                    final Map.Entry<DN, Entry> e = i.next();
                                    i.remove();
                                // in order to avoid cases where the renamed subtree overlaps.
                                final SortedMap<byte[], byte[]> renamedEntries =
                                        new TreeMap<byte[], byte[]>(DN_ORDER);
                                    final DN renamedDN = e.getKey().rename(oldDN, newDN);
                                    final Entry entry = e.getValue().setName(renamedDN);
                                    renamedEntries.put(renamedDN, entry);
                                // @formatter:off
                                final Iterator<Map.Entry<byte[], byte[]>> i =
                                    entries.subMap(changeNormDN,
                                        getBytes((changeDN.child(RDN.maxValue())).
                                                toNormalizedString())).entrySet().iterator();
                                // @formatter:on
                                while (i.hasNext()) {
                                    final Map.Entry<byte[], byte[]> e = i.next();
                                    final Entry entry = decodeEntry(e.getValue());
                                    final DN renamedDN = entry.getName().rename(oldDN, newDN);
                                    entry.setName(renamedDN);
                                    renamedEntries.put(getBytes(renamedDN.toNormalizedString()),
                                            encodeEntry(entry)[1]);
                                    i.remove();
                                }
                                // Modify the target entry.
                                final Entry entry = entries.values().iterator().next();
                                // Modify target entry
                                final Entry targetEntry =
                                        decodeEntry(renamedEntries.values().iterator().next());
                                if (change.isDeleteOldRDN()) {
                                    for (final AVA ava : oldDN.rdn()) {
                                        entry.removeAttribute(ava.toAttribute(), null);
                                        targetEntry.removeAttribute(ava.toAttribute(), null);
                                    }
                                }
                                for (final AVA ava : newDN.rdn()) {
                                    entry.addAttribute(ava.toAttribute());
                                    targetEntry.addAttribute(ava.toAttribute());
                                }
                                renamedEntries.remove(getBytes(targetEntry.getName()
                                        .toNormalizedString()));
                                renamedEntries.put(getBytes(targetEntry.getName()
                                        .toNormalizedString()), encodeEntry(targetEntry)[1]);
                                // Add the renamed entries.
                                for (final Entry renamedEntry : renamedEntries.values()) {
                                    final Entry existingEntry = entries.get(renamedEntry.getName());
                                    if (existingEntry != null) {
                                final Iterator<byte[]> j = renamedEntries.values().iterator();
                                while (j.hasNext()) {
                                    final Entry renamedEntry = decodeEntry(j.next());
                                    final byte[] existingEntryDn =
                                            entries.get(getBytes(renamedEntry.getName()
                                                    .toNormalizedString()));
                                    if (existingEntryDn != null) {
                                        final Entry existingEntry = decodeEntry(existingEntryDn);
                                        try {
                                            final Entry tmp =
                                                    listener.handleDuplicateEntry(change,
                                                            existingEntry, renamedEntry);
                                            entries.put(tmp.getName(), tmp);
                                            entries.put(
                                                    getBytes(tmp.getName().toNormalizedString()),
                                                    encodeEntry(tmp)[1]);
                                        } catch (final DecodeException e) {
                                            return e;
                                        }
                                    } else {
                                        entries.put(renamedEntry.getName(), renamedEntry);
                                        entries.put(getBytes(renamedEntry.getName()
                                                .toNormalizedString()),
                                                encodeEntry(renamedEntry)[1]);
                                    }
                                }
                                renamedEntries.clear();
                            }
                            return null;
                        }
@@ -409,7 +485,7 @@
                        @Override
                        public DecodeException visitChangeRecord(final Void p,
                                final ModifyRequest change) {
                            if (!entries.containsKey(change.getName())) {
                            if (entries.get(changeNormDN) == null) {
                                try {
                                    listener.handleRejectedChangeRecord(change,
                                            REJECTED_CHANGE_FAIL_MODIFY.get(change.getName()
@@ -418,7 +494,7 @@
                                    return e;
                                }
                            } else {
                                final Entry entry = entries.get(change.getName());
                                final Entry entry = decodeEntry(entries.get(changeNormDN));
                                for (final Modification modification : change.getModifications()) {
                                    final ModificationType modType =
                                            modification.getModificationType();
@@ -434,6 +510,7 @@
                                                + "\": modification type not supported");
                                    }
                                }
                                entries.put(changeNormDN, encodeEntry(entry)[1]);
                            }
                            return null;
                        }
@@ -446,7 +523,7 @@
        }
        return new EntryReader() {
            private final Iterator<Entry> iterator = entries.values().iterator();
            private final Iterator<byte[]> iterator = entries.values().iterator();
            @Override
            public void close() throws IOException {
@@ -464,7 +541,7 @@
            @Override
            public Entry readEntry() throws IOException {
                return iterator.next();
                return decodeEntry(iterator.next());
            }
        };
    }
@@ -612,15 +689,61 @@
        };
    }
    private static SortedMap<DN, Entry> readEntries(final EntryReader reader) throws IOException {
        final SortedMap<DN, Entry> entries = new TreeMap<DN, Entry>();
    private static List<byte[][]> readEntriesAsList(final EntryReader reader) throws IOException {
        final List<byte[][]> entries = new ArrayList<byte[][]>();
        while (reader.hasNext()) {
            final Entry entry = reader.readEntry();
            entries.put(entry.getName(), entry);
            entries.add(encodeEntry(entry));
        }
        // Sorting the list by DN
        Collections.sort(entries, DN_ORDER2);
        return entries;
    }
    private static TreeMap<byte[], byte[]> readEntriesAsMap(final EntryReader reader)
            throws IOException {
        final TreeMap<byte[], byte[]> entries = new TreeMap<byte[], byte[]>(DN_ORDER);
        while (reader.hasNext()) {
            final Entry entry = reader.readEntry();
            final byte[][] bEntry = encodeEntry(entry);
            entries.put(bEntry[0], bEntry[1]);
        }
        return entries;
    }
    private static SearchResultEntry decodeEntry(final byte[] asn1EntryFormat) {
        final ASN1Reader readerASN1 = ASN1.getReader(asn1EntryFormat);
        try {
            final SearchResultEntry sr =
                    LDAPUtils.decodeSearchResultEntry(readerASN1, new DecodeOptions());
            readerASN1.close();
            return sr;
        } catch (IOException ex) {
            throw new IllegalStateException(ex);
        }
    }
    private static byte[][] encodeEntry(final Entry entry) {
        final byte[][] bEntry = new byte[2][];
        final ByteStringBuilder bsb = new ByteStringBuilder();
        final ASN1Writer writer = ASN1.getWriter(bsb);
        // Store normalized DN
        bEntry[0] = getBytes(entry.getName().toNormalizedString());
        try {
            // Store ASN1 representation of the entry.
            LDAPUtils.encodeSearchResultEntry(writer, Responses.newSearchResultEntry(entry));
            bEntry[1] = bsb.toByteArray();
            return bEntry;
        } catch (final IOException ioe) {
            throw new IllegalStateException(ioe);
        }
    }
    // Prevent instantiation.
    private LDIF() {
        // Do nothing.
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldif/LDIFTestCase.java
@@ -29,6 +29,7 @@
import static org.fest.assertions.Assertions.assertThat;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@@ -202,12 +203,12 @@
        // @formatter:off
        final EntryReader reader = new LDIFEntryReader(
                "dn: uid=user.0,ou=People,dc=example,dc=com",
                "objectClass: person",
                "objectClass: inetorgperson",
                "objectClass: organizationalperson",
                "objectClass: top",
                "uid: user.0"
            "dn: uid=user.0,ou=People,dc=example,dc=com",
            "objectClass: person",
            "objectClass: inetorgperson",
            "objectClass: organizationalperson",
            "objectClass: top",
            "uid: user.0"
        );
        // @formatter:on
@@ -306,7 +307,7 @@
     *
     * @throws Exception
     */
    @Test()
    @Test(expectedExceptions = NoSuchElementException.class)
    public final void testLdifSearchWithSchemaMatchFullAttributesTypeOnly() throws Exception {
        // @formatter:off
@@ -335,13 +336,8 @@
            assertThat(entry.getAttribute("uid")).isNotNull();
            assertThat(entry.getAttribute("uid").getAttributeDescription()).isNotNull();
            assertThat(entry.getAttribute("uid")).isEmpty();
            try {
                assertThat(entry.getAttribute("uid").firstValueAsString()).isNull();
            } catch (NoSuchElementException ex) {
                // No values, only type.
                // Expected exception on entry.getAttribute("uid").firstValueAsString()
            }
            // The following assert throws an exception because no values contained in, only type.
            assertThat(entry.getAttribute("uid").firstValueAsString()).isNull();
        }
        resultReader.close();
    }
@@ -470,7 +466,7 @@
     *
     * @throws Exception
     */
    @Test()
    @Test(expectedExceptions = NoSuchElementException.class)
    public final void testLdifSearchWithSchemaMatchSpecifiedAttributeTypeOnly() throws Exception {
        // @formatter:off
@@ -496,12 +492,9 @@
            assertThat(entry.getAttributeCount()).isEqualTo(1);
            assertThat(entry.getAttribute("uid").getAttributeDescription()).isNotNull();
            assertThat(entry.getAttribute("uid")).isEmpty();
            try {
                assertThat(entry.getAttribute("uid").firstValueAsString()).isNull();
            } catch (NoSuchElementException ex) {
                // No values, only type.
                // Expected exception on entry.getAttribute("uid").firstValueAsString()
            }
            // The following assert throws an exception because no values contained in, only type.
            assertThat(entry.getAttribute("uid").firstValueAsString()).isNull();
        }
        resultReader.close();
    }
@@ -637,7 +630,10 @@
            "objectClass: person",
            "objectClass: inetorgperson"
        );
        final Entry e1 = new LinkedHashMapEntry("dn: uid=user.1,ou=People,dc=example,dc=com", "objectClass: person");
        final Entry e1 = new LinkedHashMapEntry(
            "dn: uid=user.1,ou=People,dc=example,dc=com",
            "objectClass: person"
        );
        // @formatter:on
        final SortedMap<DN, Entry> sourceEntries = new TreeMap<DN, Entry>();
@@ -646,10 +642,9 @@
        final Iterator<Entry> sourceIterator = sourceEntries.values().iterator();
        final EntryReader resultReader = LDIF.newEntryIteratorReader(sourceIterator);
        Entry entry = null;
        int cCount = 0;
        while (resultReader.hasNext()) {
            entry = resultReader.readEntry();
            final Entry entry = resultReader.readEntry();
            assertThat(entry.getName().toString()).isNotNull();
            assertThat(entry.getName().toString()).contains("ou=People,dc=example,dc=com");
            assertThat(entry.getAttributeCount()).isGreaterThanOrEqualTo(1);
@@ -862,7 +857,7 @@
        assertThat(cr).isInstanceOf(ModifyRequest.class);
        // @formatter:off
        /* Expected : 2 add / 1 delete.
        /* Expected : 2 add / 1 delete - output :
         * dn: uid=newuser,ou=People,dc=example,dc=com
         * changetype: modify
         * add: userPassword
@@ -1025,6 +1020,199 @@
    }
    /**
     * Differences between two short ldif examples.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifDiffEntriesShortExamples() throws Exception {
        // @formatter:off
        final LDIFEntryReader source = new LDIFEntryReader(
                "dn: uid=user.0,ou=People,dc=example,dc=com",
                "objectClass: top",
                "objectClass: person",
                "objectClass: organizationalperson",
                "objectClass: inetorgperson",
                "givenName: Aaccf",
                "sn: Amar",
                "cn: Aaccf Amar",
                "initials: APA",
                "employeeNumber: 0",
                "uid: user.0",
                "mail: user.0@example.com",
                "description: This is the description for Aaccf Amar.",
                "",
                "dn: uid=user.1,ou=People,dc=example,dc=com",
                "objectClass: top",
                "objectClass: person",
                "objectClass: organizationalperson",
                "objectClass: inetorgperson",
                "givenName: Aaren",
                "sn: Atp",
                "cn: Aaren Atp",
                "initials: AFA",
                "employeeNumber: 1",
                "uid: user.1",
                "mail: user.1@example.com",
                "description: This is the description for Aaren Atp.",
                "",
                "dn: uid=user.2,ou=People,dc=example,dc=com",
                "objectClass: top",
                "objectClass: person",
                "objectClass: organizationalperson",
                "objectClass: inetorgperson",
                "givenName: Aarika",
                "sn: Atpco",
                "cn: Aarika Atpco",
                "initials: AVA",
                "employeeNumber: 2",
                "uid: user.2",
                "mail: user.2@example.com",
                "description: This is the description for Aarika Atpco.",
                "",
                "dn: uid=user.3,ou=People,dc=example,dc=com",
                "objectClass: top",
                "objectClass: person",
                "objectClass: organizationalperson",
                "objectClass: inetorgperson",
                "givenName: Aaron",
                "sn: Atrc",
                "cn: Aaron Atrc",
                "initials: ATA",
                "employeeNumber: 3",
                "uid: user.3",
                "mail: user.3@example.com",
                "description: This is the description for Aaron Atrc.",
                "",
                "dn: uid=user.4,ou=People,dc=example,dc=com",
                "objectClass: top",
                "objectClass: person",
                "objectClass: organizationalperson",
                "objectClass: inetorgperson",
                "givenName: Aartjan",
                "sn: Aalders",
                "cn: Aartjan Aalders",
                "initials: AAA",
                "employeeNumber: 4",
                "uid: user.4",
                "mail: user.4@example.com",
                "description: This is the description for Aartjan Aalders."
        );
        final LDIFEntryReader target = new LDIFEntryReader(
                "dn: uid=user.0,ou=People,dc=example,dc=com",
                "objectClass: top",
                "objectClass: person",
                "objectClass: organizationalperson",
                "objectClass: inetorgperson",
                "givenName: Amar", // diff
                "sn: Amar",
                "cn: Aaccf Amar",
                "initials: APA",
                "employeeNumber: 55", // diff
                "uid: user.0",
                "mail: user.0@example.com",
                "description: This is the description for Aaccf Amar.",
                "work-phone: 650/506-0666", // diff
                "",
                "dn: uid=user.1,ou=People,dc=example,dc=com",
                "objectClass: top",
                "objectClass: person",
                "objectClass: organizationalperson",
                "objectClass: inetorgperson",
                "givenName: Aaren",
                "sn: Atp",
                "cn: Aaren Atp",
                "initials: AFA",
                "employeeNumber: 1",
                "uid: user.1",
                "mail: aaren@example.com", // diff
                "description: This is the description for Aaren Atp.",
                "",
                "dn: uid=user.2,ou=People,dc=example,dc=com",
                "objectClass: top",
                "objectClass: person",
                "objectClass: organizationalperson",
                "objectClass: inetorgperson",
                "givenName: Aarika",
                "sn: Atpco",
                "cn: Aarika Atpco",
                "initials: AVA",
                "employeeNumber: 2",
                "uid: user.2",
                "mail: user.2@example.com", // diff (delete description)
                "",
                "dn: uid=user.3,ou=People,dc=example,dc=com",
                "objectClass: top",
                "objectClass: person",
                "objectClass: organizationalperson",
                "objectClass: inetorgperson",
                "givenName: Aaron",
                "sn: Atrc",
                "cn: Aaron Atrc",
                "initials: ATA",
                "employeeNumber: 3",
                "uid: user.999", // diff
                "mail: user.999@example.com", // diff
                "description: This is the description for Aaron Atrc.",
                "",
                "dn: uid=user.4,ou=People,dc=example,dc=com",
                "objectClass: top",
                "objectClass: person",
                "objectClass: organizationalperson",
                "objectClass: inetorgperson",
                "givenName: Aartjan",
                "sn: Aalders",
                "cn: Aartjan Aalders",
                "initials: AAA",
                "employeeNumber: 4",
                "uid: user.4",
                "mail: user.4@example.com",
                "description: This is the description for Aartjan Aalders."
        );
        // @formatter:on
        final ChangeRecordReader reader = LDIF.diff(source, target);
        assertThat(reader.hasNext()).isTrue();
        ChangeRecord cr = reader.readChangeRecord();
        assertThat(cr.getName().toString()).isEqualTo("uid=user.0,ou=People,dc=example,dc=com");
        assertThat(cr instanceof ModifyRequest);
        assertThat(((ModifyRequest) cr).getModifications()).isNotEmpty();
        // 1st entry : 2 add/delete + 1 add(work-phone)
        assertThat(((ModifyRequest) cr).getModifications().size()).isEqualTo(5);
        // 2nd entry : 1 add/delete
        cr = reader.readChangeRecord();
        assertThat(cr.getName().toString()).isEqualTo("uid=user.1,ou=People,dc=example,dc=com");
        assertThat(cr instanceof ModifyRequest);
        assertThat(((ModifyRequest) cr).getModifications().size()).isEqualTo(2);
        // 3rd entry : 1 delete
        cr = reader.readChangeRecord();
        assertThat(cr.getName().toString()).isEqualTo("uid=user.2,ou=People,dc=example,dc=com");
        assertThat(cr instanceof ModifyRequest);
        assertThat(((ModifyRequest) cr).getModifications().size()).isEqualTo(1);
        assertThat(((ModifyRequest) cr).getModifications().get(0).getModificationType().toString())
                .isEqualTo("delete");
        assertThat(
                ((ModifyRequest) cr).getModifications().get(0).getAttribute()
                        .getAttributeDescriptionAsString()).isEqualTo("description");
        // 4th entry : 2 add/delete
        cr = reader.readChangeRecord();
        assertThat(cr.getName().toString()).isEqualTo("uid=user.3,ou=People,dc=example,dc=com");
        assertThat(cr instanceof ModifyRequest);
        assertThat(((ModifyRequest) cr).getModifications().size()).isEqualTo(4);
        // 5th entry : 0 modifications
        cr = reader.readChangeRecord();
        assertThat(cr.getName().toString()).isEqualTo("uid=user.4,ou=People,dc=example,dc=com");
        assertThat(cr instanceof ModifyRequest);
        assertThat(((ModifyRequest) cr).getModifications().size()).isEqualTo(0);
        assertThat(reader.hasNext()).isFalse();
        reader.close();
        target.close();
    }
    /**
     * Diff between two same entries : no modifications expected.
     *
     * @throws Exception
@@ -1081,13 +1269,12 @@
    }
    /**
     * Test the patch function. Apply simple patch to replace/add data to the
     * input.
     * Create a patch without any differences with the original.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatch() throws Exception {
    public final void testLdifPatchAddNoDiff() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
@@ -1095,6 +1282,1012 @@
            "sn: new user",
            "mail: mail@mailme.org"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "changetype: add",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=scarter,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(3); // objectclass - sn - mail
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * The patch add successfully an attribute 'manager' to the entry.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchAddDiff() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "changetype: add",
            "add: manager",
            "manager: uid=joneill,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=scarter,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5); // objectclass - sn - mail
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * The patch add two new entries to the original.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchAddDiffNewEntry() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: uid=joneill,ou=People,dc=example,dc=com",
            "changetype: add",
            "add: manager",
            "manager: uid=hamond,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org",
            "",
            "dn: uid=djackson,ou=People,dc=example,dc=com",
            "changetype: add",
            "add: manager",
            "manager: uid=joneill,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString())
                .isEqualTo("uid=djackson,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=joneill,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=scarter,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(3);
        reader.close();
    }
    /**
     * Try to modify an nonexistent entry. The patch throw an error via the
     * listener which is in RejectedChangeRecordListener.OVERWRITE.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchAddModifyNonExistantEntryDoNothing() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: cn=Lisa Jangles,ou=People,dc=example,dc=com",
            "changetype: modify",
            "add: telephonenumber",
            "telephonenumber: (408) 555-2468"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=scarter,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(3);
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * Testing to delete in entry.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchDeleteEntry() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "changetype: delete"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * Testing to delete in entry.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchDeleteEntryAmongSeveral() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=user.1,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aarika",
            "sn: Atpco",
            "cn: Aarika Atpco",
            "uid: user.1",
            "",
            "dn: uid=user.2,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Eniko",
            "sn: Eniko",
            "cn: Eniko Atpco",
            "uid: user.2",
            "",
            "dn: uid=user.3,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aaron",
            "sn: Atrc",
            "cn: Aaron Atrc",
            "uid: user.3"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: uid=user.2,ou=People,dc=example,dc=com",
            "changetype: delete"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=user.1,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=user.3,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * Testing to delete attributes in an entry.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchDeleteAttributesEntry() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Carter",
            "sn: Sam",
            "cn: Sam Carter",
            "uid: scarter",
            "mail: user.1@mail.com",
            "postalAdress: 42 Shepherd Street",
            "work-phone: 650/506-7000",
            "work-phone: 650/506-0666",
            "home-fax: 650-7001"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "changetype: modify",
            "delete: work-phone",
            "work-phone: 650/506-0666",
            "-",
            "delete: home-fax"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=scarter,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(8);
        assertThat(entry.getAttribute("work-phone").firstValueAsString()).isEqualTo("650/506-7000");
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * Modifying an entry : add
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchModifyEntry() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "changetype: modify",
            "add: work-phone",
            "work-phone: 650/506-7000"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=scarter,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(4);
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * The patch attempts to modify the dn adding uppercase.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchModifyDNEntryUppercaseUid() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org",
            "uid: scarter",
            "",
            "dn: uid=djackson,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org",
            "uid: djackson"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "changetype: modrdn",
            "newrdn: uid=Scarter",
            "deleteoldrdn: 1"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        // does not work with a single entry && ...
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString())
                .isEqualTo("uid=djackson,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(4);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=Scarter,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(4); //The patch create uid attribute on the selected entry.
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * Attemp to modify the entry adding uppercase in cn.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchModifyDNEntryUpperCaseDnNameSurname() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: cn=sam carter,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org",
            "uid: scarter",
            "",
            "dn: uid=djackson,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org",
            "uid: djackson"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: cn=sam carter,ou=People,dc=example,dc=com",
            "changetype: modrdn",
            "newrdn: cn=Sam Carter",
            "deleteoldrdn: 1"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        // does not work with a single entry && ...
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo(
                "cn=Sam Carter,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString())
                .isEqualTo("uid=djackson,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(4); //The patch create uid attribute on the selected entry.
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * The patch attempts to modify a rdn of a specific entry.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchModifyDNEntry() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org",
            "uid: scarter",
            "",
            "dn: uid=djackson,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org",
            "uid: djackson"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "changetype: modrdn",
            "newrdn: uid=Susan Jacobs",
            "deleteoldrdn: 1"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        // does not work with a single entry && ...
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString())
                .isEqualTo("uid=djackson,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(4);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo(
                "uid=Susan Jacobs,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(4); //The patch create uid attribute on the selected entry.
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    @Test()
    public final void testLdifPatchModifyDnEntry2() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=user.1,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aarika",
            "sn: Atpco",
            "cn: Aarika Atpco",
            "uid: user.1",
            "",
            "dn: uid=user.2,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Eniko",
            "sn: Eniko",
            "cn: Eniko Atpco",
            "uid: user.2",
            "",
            "dn: uid=user.3,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aaron",
            "sn: Atrc",
            "cn: Aaron Atrc",
            "uid: user.3"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: uid=user.2,ou=People,dc=example,dc=com",
            "changetype: modrdn",
            "newrdn: uid=user.22",
            "deleteoldrdn: 1"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=user.1,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=user.22,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=user.3,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * Rename a branch : from ou=People,dc=example,dc=com to ou=Human
     * Resources,dc=example,dc=com.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchModifyDnEntryBranch() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: organizationalunit",
            "ou: People",
            "",
            "dn: uid=user.1,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aarika",
            "sn: Atpco",
            "cn: Aarika Atpco",
            "uid: user.1",
            "",
            "dn: uid=user.2,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Eniko",
            "sn: Eniko",
            "cn: Eniko Atpco",
            "uid: user.2",
            "",
            "dn: uid=user.3,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aaron",
            "sn: Atrc",
            "cn: Aaron Atrc",
            "uid: user.3",
            "",
            "dn: uid=user.4,ou=People,dc=example,dc=org",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Allan",
            "sn: Zorg",
            "cn: Allan Zorg",
            "uid: user.4"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: ou=People,dc=example,dc=com",
            "changetype: modrdn",
            "newrdn: ou=Human Resources,dc=example,dc=com",
            "deleteoldrdn: 1"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("ou=Human Resources,dc=example,dc=com");
        assertThat(entry.getAllAttributes("ou").iterator().next().firstValueAsString()).isEqualTo(
                "Human Resources");
        assertThat(entry.getAttributeCount()).isEqualTo(2);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo(
                "uid=user.1,ou=Human Resources,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo(
                "uid=user.2,ou=Human Resources,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo(
                "uid=user.3,ou=Human Resources,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=user.4,ou=People,dc=example,dc=org");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * Rename a branch : from ou=People,dc=example,dc=com to ou=Human
     * Resources,dc=example,dc=com. In this example deleteoldrdn is set to 0.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchModifyDnEntryBranchKeepsOldRdn() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: organizationalunit",
            "ou: People",
            "",
            "dn: uid=user.1,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aarika",
            "sn: Atpco",
            "cn: Aarika Atpco",
            "uid: user.1",
            "",
            "dn: uid=user.2,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Eniko",
            "sn: Eniko",
            "cn: Eniko Atpco",
            "uid: user.2",
            "",
            "dn: uid=user.3,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aaron",
            "sn: Atrc",
            "cn: Aaron Atrc",
            "uid: user.3",
            "",
            "dn: uid=user.4,ou=People,dc=example,dc=org",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Allan",
            "sn: Zorg",
            "cn: Allan Zorg",
            "uid: user.4"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: ou=People,dc=example,dc=com",
            "changetype: modrdn",
            "newrdn: ou=Human Resources,dc=example,dc=com",
            "deleteoldrdn: 0"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("ou=Human Resources,dc=example,dc=com");
        assertThat(entry.getAllAttributes("ou").iterator().next().firstValueAsString()).isEqualTo(
                "People");
        assertThat(entry.getAttributeCount()).isEqualTo(2);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo(
                "uid=user.1,ou=Human Resources,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo(
                "uid=user.2,ou=Human Resources,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo(
                "uid=user.3,ou=Human Resources,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=user.4,ou=People,dc=example,dc=org");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * Moves an entry.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchModifyDnEntryNewSuperior() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=user.1,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aarika",
            "sn: Atpco",
            "cn: Aarika Atpco",
            "uid: user.1",
            "mail: user.1@mail.com",
            "",
            "dn: uid=user.2,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Eniko",
            "sn: Eniko",
            "cn: Eniko Atpco",
            "uid: user.2"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "# moves the entry from ou=People, dc=example,dc=com to Marketing",
            "dn: uid=user.1,ou=People,dc=example,dc=com",
            "changetype: modrdn",
            "newrdn: uid=user.1",
            "deleteoldrdn: 1",
            "newsuperior: ou=Marketing,dc=example,dc=com"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo(
                "uid=user.1,ou=Marketing,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(6);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=user.2,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * Apply simple patch to replace/add data to the input.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchAddReplace() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "changetype: modify",
            "replace:sn",
            "sn: scarter",
            "-",
            "add: manager",
            "manager: uid=joneill,ou=People,dc=example,dc=com"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=scarter,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(4); // objectclass - sn - mail - manager
        assertThat(entry.getAttribute("manager").firstValueAsString()).isEqualTo(
                "uid=joneill,ou=People,dc=example,dc=com");
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * Replace / add postalAdress.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchAddReplaceLanguageTagExample() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org",
            "postalAdress;lang-en: Shepherd Street"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "changetype: modify",
            "replace: postalAdress;lang-fr",
            "postalAdress;lang-fr: 355 avenue Leon Blum",
            "-",
            "replace: postalAdress;lang-en",
            "postalAdress;lang-en: 42 Shepherd Street"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=scarter,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(5); // objectclass - sn - mail - manager - postalAdress
        assertThat(entry.getAttribute("postalAdress;lang-fr").firstValueAsString()).isEqualTo(
                "355 avenue Leon Blum");
        assertThat(entry.getAttribute("postalAdress;lang-en").firstValueAsString()).isEqualTo(
                "42 Shepherd Street");
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * Test some changes : add/replace/delete...
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchVariousChanges() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org",
            "",
            "dn: uid=user.0,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aaccf",
            "sn: Amar",
            "cn: Aaccf Amar",
            "",
            "dn: uid=user.1,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aaren",
            "sn: Atp",
            "cn: Aaren Atp",
            "mail: AarenAtp@mail.org",
            "",
            "dn: uid=user.2,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aarika",
            "sn: Atpco",
            "cn: Aarika Atpco",
            "description:: ZnVubnkgZGVzY3JpcHRpb24gISA6RA==",
            "mail:: QWFyaWthQXRwY29AbWFpbC5vcmc=",
            "",
            "dn: uid=user.3,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Kadja",
            "sn: Atpcol",
            "cn: Kadja Atpcol"
        );
        // @formatter:on
        final File file = File.createTempFile("sdk", ".png");
        final String url = file.toURI().toURL().toString();
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "changetype: modify",
            "replace:sn",
            "sn: scarter",
            "-",
            "add: manager",
            "manager: uid=joneill,ou=People,dc=example,dc=com",
            "",
            "dn: uid=user.2,ou=People,dc=example,dc=com",
            "changetype: modify",
            "replace:description",
            "description:: QWFyaWthIEF0cGNvIGRlc2NyaXB0aW9uIDogbG9yZW0gaXBzdW0uLi4=",
            "-",
            "add: jpegphoto",
            "jpegphoto:< " + url,
            "",
            "dn: uid=user.3,ou=People,dc=example,dc=com",
            "changetype: delete"
        );
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getAttributeCount()).isEqualTo(4);
        assertThat(entry.getAttribute("manager").firstValueAsString()).isEqualTo(
                "uid=joneill,ou=People,dc=example,dc=com");
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getAttributeCount()).isEqualTo(4);
        assertThat(entry.getName().toString()).isEqualTo("uid=user.0,ou=People,dc=example,dc=com");
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getAttributeCount()).isEqualTo(5);
        assertThat(entry.getName().toString()).isEqualTo("uid=user.1,ou=People,dc=example,dc=com");
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getAttributeCount()).isEqualTo(7);
        assertThat(entry.getName().toString()).isEqualTo("uid=user.2,ou=People,dc=example,dc=com");
        assertThat(entry.getAttribute("mail").firstValueAsString()).isEqualTo(
                "AarikaAtpco@mail.org");
        assertThat(entry.getAttribute("description").firstValueAsString()).isEqualTo(
                "Aarika Atpco description : lorem ipsum...");
        assertThat(entry.getAttribute("jpegphoto")).isNotEmpty();
        assertThat(reader.hasNext()).isFalse();
        file.delete();
        reader.close();
    }
    /**
     * An example for illustrate an LDIFChangeRecordReader containing changes on
     * previous ldif.
     *
     * @throws Exception
     */
    @Test()
    public final void testLdifPatchContainingChanges() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org",
            "",
            "dn: uid=user.0,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aaccf",
            "sn: Amar",
            "cn: Aaccf Amar",
            "",
            "dn: uid=user.1,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aaren",
            "sn: Atp",
            "cn: Aaren Atp",
            "",
            "dn: uid=user.2,ou=People,dc=example,dc=com",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalperson",
            "objectClass: inetorgperson",
            "givenName: Aarika",
            "sn: Atpco",
            "cn: Aarika Atpco"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
@@ -1104,21 +2297,101 @@
                "sn: scarter",
                "-",
                "add: manager",
                "manager: uid=joneill,ou=People,dc=example,dc=com"
                "manager: uid=joneill,ou=People,dc=example,dc=com",
                "",
                "dn: uid=user.0,ou=People,dc=example,dc=com",
                "changetype: modify",
                "replace:sn",
                "sn: Amarr",
                "-",
                "delete: givenName",
                "",
                "dn: uid=user.1,ou=People,dc=example,dc=com",
                "changetype: modify",
                "replace:givenName",
                "givenName: Aarwen",
                "-",
                "add: manager",
                "manager: uid=joneill,ou=People,dc=example,dc=com",
                "-",
                "add: mail",
                "mail: Aarwen@mail.com",
                "-",
                "add: fax",
                "fax: 555 555-5555",
                "-",
                "add: description",
                "description:: QWFyd2VuIGRlc2NyaXB0aW9uLg=="
        );
        // @formatter:on
        // @formatter:on
        final EntryReader reader = LDIF.patch(input, patch);
        Entry entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=scarter,ou=People,dc=example,dc=com");
        assertThat(entry.getAttributeCount()).isEqualTo(4); // objectclass - sn - mail - manager
        // Attr. list : objectclass - sn - mail - manager
        assertThat(entry.getAttributeCount()).isEqualTo(4);
        assertThat(entry.getAttribute("manager").firstValueAsString()).isEqualTo(
                "uid=joneill,ou=People,dc=example,dc=com");
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        // Attr. list : objectclass - sn - cn
        assertThat(entry.getAttributeCount()).isEqualTo(3);
        assertThat(reader.hasNext()).isTrue();
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=user.1,ou=People,dc=example,dc=com");
        // Attr. list : objectclass - sn - cn - givenName - manager - mail - fax - description
        assertThat(entry.getAttributeCount()).isEqualTo(8);
        assertThat(entry.getAttribute("description").firstValueAsString()).isEqualTo(
                "Aarwen description.");
        assertThat(reader.hasNext()).isTrue();
        // Last entry, no modification on it.
        entry = reader.readEntry();
        assertThat(entry.getName().toString()).isEqualTo("uid=user.2,ou=People,dc=example,dc=com");
        // Attr. list : objectClass - givenname - sn - cn
        assertThat(entry.getAttributeCount()).isEqualTo(4);
        assertThat(reader.hasNext()).isFalse();
        reader.close();
    }
    /**
     * Try to apply a patch which data are not valid. Exception expected.
     *
     * @throws Exception
     */
    @SuppressWarnings("resource")
    @Test(expectedExceptions = DecodeException.class)
    public final void testLdifPatchInvalidChangeRecord() throws Exception {
        // @formatter:off
        final LDIFEntryReader input = new LDIFEntryReader(
            "dn: uid=scarter,ou=People,dc=example,dc=com",
            "objectClass: person",
            "sn: new user",
            "mail: mail@mailme.org"
        );
        // @formatter:on
        // @formatter:off
        final  LDIFChangeRecordReader patch = new LDIFChangeRecordReader(
                "dn: uid=scarter,ou=People,dc=example,dc=com",
                "changetype: modif\u0000",
                "replace:sn",
                "sn: scarter",
                "-",
                "add: manager\u0000",
                "manager: uid=joneill,ou=People,dc=example,dc=com"
        );
        // @formatter:on
        EntryReader reader = new LDIFEntryReader();
        try {
            reader = LDIF.patch(input, patch);
            reader.readEntry();
        } finally {
            reader.close();
        }
    }
    /**
     * LDIF patch - EntryReader/ChangeRecordReader doesn't allow null. Exception
     * expected.
     *
@@ -1128,5 +2401,4 @@
    public final void testLdifPatchDoesntAllowNull() throws Exception {
        LDIF.patch(null, null);
    }
}