From ad8075db9d1ed81528a069d446c27c7e2edbf1d9 Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Mon, 11 Sep 2006 17:03:04 +0000
Subject: [PATCH] Implement a test suite for the LDIFReader class. Performed refactoring to reduce the amount of code duplication and improve the coupling between the LDIFReader class and the ChangeRecordEntry classes.

---
 opendj-sdk/opends/src/server/org/opends/server/util/ChangeRecordEntry.java                                     |   62 -
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestChangeRecordEntry.java         |   32 
 opendj-sdk/opends/src/server/org/opends/server/util/AddChangeRecordEntry.java                                  |   72 
 opendj-sdk/opends/src/server/org/opends/server/util/LDIFReader.java                                            | 1833 ++++++++++--------------------
 opendj-sdk/opends/src/server/org/opends/server/util/DeleteChangeRecordEntry.java                               |   40 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestDeleteChangeRecordEntry.java   |   40 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestLDIFReader.java                |  980 ++++++++++++++++
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestAddChangeRecordEntry.java      |   68 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyChangeRecordEntry.java   |   72 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyDNChangeRecordEntry.java |   88 
 opendj-sdk/opends/src/server/org/opends/server/tools/LDAPModify.java                                           |    8 
 opendj-sdk/opends/src/server/org/opends/server/util/ModifyChangeRecordEntry.java                               |  124 -
 opendj-sdk/opends/src/server/org/opends/server/util/ModifyDNChangeRecordEntry.java                             |  183 --
 13 files changed, 1,814 insertions(+), 1,788 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/tools/LDAPModify.java b/opendj-sdk/opends/src/server/org/opends/server/tools/LDAPModify.java
index 0eff754..7e1996b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tools/LDAPModify.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/tools/LDAPModify.java
@@ -49,6 +49,7 @@
 import org.opends.server.protocols.ldap.LDAPException;
 import org.opends.server.protocols.ldap.LDAPFilter;
 import org.opends.server.protocols.ldap.LDAPMessage;
+import org.opends.server.protocols.ldap.LDAPModification;
 import org.opends.server.protocols.ldap.ModifyRequestProtocolOp;
 import org.opends.server.protocols.ldap.ModifyResponseProtocolOp;
 import org.opends.server.protocols.ldap.ModifyDNRequestProtocolOp;
@@ -236,7 +237,7 @@
         case ADD:
           operationType = "ADD";
           AddChangeRecordEntry addEntry = (AddChangeRecordEntry) entry;
-          ArrayList<Attribute> attrs = addEntry.getAttributes();
+          List<Attribute> attrs = addEntry.getAttributes();
           ArrayList<LDAPAttribute> attributes =
               new ArrayList<LDAPAttribute>(attrs.size());
           for(Attribute a : attrs)
@@ -256,8 +257,9 @@
         case MODIFY:
           operationType = "MODIFY";
           ModifyChangeRecordEntry modEntry = (ModifyChangeRecordEntry) entry;
-          protocolOp = new ModifyRequestProtocolOp(asn1OctetStr,
-                                                   modEntry.getModifications());
+          ArrayList<LDAPModification> mods =
+            new ArrayList<LDAPModification>(modEntry.getModifications());
+          protocolOp = new ModifyRequestProtocolOp(asn1OctetStr, mods);
           msgID = MSGID_PROCESSING_OPERATION;
           System.out.println(getMessage(msgID, operationType, asn1OctetStr));
           break;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/AddChangeRecordEntry.java b/opendj-sdk/opends/src/server/org/opends/server/util/AddChangeRecordEntry.java
index 41aa150..90ff808 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/AddChangeRecordEntry.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/util/AddChangeRecordEntry.java
@@ -32,8 +32,9 @@
 import static org.opends.server.loggers.Debug.debugEnter;
 
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
@@ -58,28 +59,35 @@
   /**
    * The entry attributes for this operation.
    */
-  private HashMap<AttributeType,ArrayList<Attribute>> entryAttributes;
+  private final List<Attribute> attributes;
 
-  // The LDIF reader instance
-  private LDIFReader reader;
 
 
   /**
    * Creates a new entry with the provided information.
    *
-   * @param  dn      The distinguished name for this entry.
-   * @param  reader  The LDIF reader instance used to read the entries.
+   * @param dn
+   *          The distinguished name for this entry.
+   * @param attributes
+   *          The entry attributes for this operation.
    */
-  public AddChangeRecordEntry(DN dn, LDIFReader reader)
+  public AddChangeRecordEntry(DN dn,
+      Map<AttributeType,List<Attribute>> attributes)
   {
-    super(dn, reader);
+    super(dn);
+
     assert debugConstructor(CLASS_NAME, String.valueOf(dn),
-                            String.valueOf(reader));
+                            String.valueOf(attributes));
 
 
-    this.reader = reader;
-    entryAttributes = new HashMap<AttributeType, ArrayList<Attribute>>();
-
+    this.attributes = new ArrayList<Attribute>(attributes.size());
+    for (List<Attribute> list : attributes.values())
+    {
+      for (Attribute a : list)
+      {
+        this.attributes.add(a);
+      }
+    }
   }
 
 
@@ -97,45 +105,19 @@
   }
 
 
+
   /**
    * Retrieves the entire set of attributes for this entry.
-   * The caller must not modify the contents of this list.
+   * <p>
+   * The returned list is read-only.
    *
-   * @return  The entire set of attributes for this entry.
+   * @return The entire unmodifiable list of attributes for this entry.
    */
-  public ArrayList<Attribute> getAttributes()
+  public List<Attribute> getAttributes()
   {
     assert debugEnter(CLASS_NAME, "getAttributes");
 
-    ArrayList<Attribute> attributes = new ArrayList<Attribute>();
-
-    for (ArrayList<Attribute> list : entryAttributes.values())
-    {
-      for (Attribute a : list)
-      {
-        attributes.add(a);
-      }
-    }
-    return attributes;
-  }
-
-  /**
-   * Parse the lines and populate the internal structures.
-   *
-   * @param lines       The lines to parse.
-   * @param lineNumber  The current line number
-   *
-   * @exception LDIFException if there is an error during parsing.
-   */
-  public void parse(LinkedList<StringBuilder> lines, long lineNumber)
-    throws LDIFException
-  {
-    assert debugEnter(CLASS_NAME, "parse", String.valueOf(lines),
-                      String.valueOf(lineNumber));
-    for(StringBuilder line : lines)
-    {
-      reader.readAttribute(lines, line, getDN(), entryAttributes);
-    }
+    return Collections.unmodifiableList(attributes);
   }
 
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/ChangeRecordEntry.java b/opendj-sdk/opends/src/server/org/opends/server/util/ChangeRecordEntry.java
index 944abbf..db7680e 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/ChangeRecordEntry.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/util/ChangeRecordEntry.java
@@ -31,11 +31,7 @@
 import static org.opends.server.loggers.Debug.debugConstructor;
 import static org.opends.server.loggers.Debug.debugEnter;
 
-import java.util.ArrayList;
-import java.util.LinkedList;
-
 import org.opends.server.types.DN;
-import org.opends.server.types.RDN;
 
 
 
@@ -58,30 +54,6 @@
 
 
   /**
-   * Creates a new entry with the provided information.
-   *
-   * @param  dn      The distinguished name for this entry.
-   * @param  reader  The LDIF reader.
-   */
-  protected ChangeRecordEntry(DN dn, LDIFReader reader)
-  {
-    assert debugConstructor(CLASS_NAME, String.valueOf(dn),
-                            String.valueOf(reader));
-
-    if (dn == null)
-    {
-      this.dn = new DN(new ArrayList<RDN>(0));
-    }
-    else
-    {
-      this.dn = dn;
-    }
-
-  }
-
-
-
-  /**
    * Creates a new change record entry with the provided information.
    *
    * @param  dn  The distinguished name for this change record entry.
@@ -116,44 +88,10 @@
 
 
   /**
-   * Specifies the distinguished name for this entry.
-   *
-   * @param  dn  The distinguished name for this entry.
-   */
-  public final void setDN(DN dn)
-  {
-    assert debugEnter(CLASS_NAME, "setDN", String.valueOf(dn));
-
-    if (dn == null)
-    {
-      this.dn = new DN(new ArrayList<RDN>(0));
-    }
-    else
-    {
-      this.dn = dn;
-    }
-  }
-
-
-
-  /**
    * Retrieves the name of the change operation type.
    *
    * @return  The name of the change operation type.
    */
   public abstract ChangeOperationType getChangeOperationType();
-
-
-
-  /**
-   * Parse and populate internal structures from the specified lines.
-   * @param lines The list of lines to parse from.
-   * @param lineNumber The current line number during the parsing.
-   *
-   * @exception LDIFException If there is a parsing error.
-   */
-  public abstract void parse(LinkedList<StringBuilder> lines, long lineNumber)
-         throws LDIFException;
-
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/DeleteChangeRecordEntry.java b/opendj-sdk/opends/src/server/org/opends/server/util/DeleteChangeRecordEntry.java
index 83a416a..d40d58d 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/DeleteChangeRecordEntry.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/util/DeleteChangeRecordEntry.java
@@ -30,16 +30,11 @@
 
 import static org.opends.server.loggers.Debug.debugConstructor;
 import static org.opends.server.loggers.Debug.debugEnter;
-import static org.opends.server.messages.MessageHandler.getMessage;
-import static org.opends.server.messages.UtilityMessages.*;
-
-import java.util.LinkedList;
 
 import org.opends.server.types.DN;
 
 
 
-
 /**
  * This class defines a data structure for a change record entry for
  * an delete operation.  It includes a DN and a set of attributes, as well as
@@ -59,14 +54,12 @@
    * Creates a new entry with the provided information.
    *
    * @param  dn      The distinguished name for this entry.
-   * @param  reader  The LDIFReader instance used to read the entries.
    */
-  public DeleteChangeRecordEntry(DN dn, LDIFReader reader)
+  public DeleteChangeRecordEntry(DN dn)
   {
-    super(dn, reader);
-    assert debugConstructor(CLASS_NAME, String.valueOf(dn),
-                            String.valueOf(reader));
+    super(dn);
 
+    assert debugConstructor(CLASS_NAME, String.valueOf(dn));
   }
 
 
@@ -83,32 +76,5 @@
     return ChangeOperationType.DELETE;
   }
 
-
-  /**
-   * Parse the lines and populate the internal structures.
-   *
-   * @param lines       The lines to parse.
-   * @param lineNumber  The current line number.
-   *
-   * @exception LDIFException if there is an error during parsing.
-   */
-
-  public void parse(LinkedList<StringBuilder> lines, long lineNumber)
-         throws LDIFException
-  {
-    assert debugEnter(CLASS_NAME, "parse", String.valueOf(lines),
-                      String.valueOf(lineNumber));
-
-    if(! lines.isEmpty())
-    {
-      int msgID = MSGID_LDIF_INVALID_DELETE_ATTRIBUTES;
-      String message = getMessage(msgID);
-
-      throw new LDIFException(msgID, message, lineNumber, true);
-
-    }
-  }
-
-
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/LDIFReader.java b/opendj-sdk/opends/src/server/org/opends/server/util/LDIFReader.java
index b5d93d0..ed42f27 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/LDIFReader.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/util/LDIFReader.java
@@ -28,11 +28,17 @@
 
 
 
+import static org.opends.server.loggers.Debug.*;
+import static org.opends.server.loggers.Error.logError;
+import static org.opends.server.messages.MessageHandler.getMessage;
+import static org.opends.server.messages.UtilityMessages.*;
+import static org.opends.server.util.StaticUtils.toLowerCase;
+
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -45,25 +51,22 @@
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.PluginConfigManager;
 import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.protocols.ldap.LDAPAttribute;
+import org.opends.server.protocols.ldap.LDAPModification;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.AttributeValue;
+import org.opends.server.types.DN;
 import org.opends.server.types.DebugLogCategory;
 import org.opends.server.types.DebugLogSeverity;
-import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
 import org.opends.server.types.ErrorLogCategory;
 import org.opends.server.types.ErrorLogSeverity;
 import org.opends.server.types.LDIFImportConfig;
+import org.opends.server.types.ModificationType;
 import org.opends.server.types.ObjectClass;
 import org.opends.server.types.RDN;
 
-import static org.opends.server.loggers.Debug.*;
-import static org.opends.server.loggers.Error.*;
-import static org.opends.server.messages.MessageHandler.*;
-import static org.opends.server.messages.UtilityMessages.*;
-import static org.opends.server.util.StaticUtils.*;
-
 
 
 /**
@@ -71,7 +74,7 @@
  * provides support for both standard entries and change entries (as would be
  * used with a tool like ldapmodify).
  */
-public class LDIFReader
+public final class LDIFReader
 {
   /**
    * The fully-qualified name of this class for debugging purposes.
@@ -310,28 +313,7 @@
           String message = getMessage(msgID, String.valueOf(entryDN),
                                       lastEntryLineNumber,
                                       invalidReason.toString());
-
-          BufferedWriter rejectWriter = importConfig.getRejectWriter();
-          if (rejectWriter != null)
-          {
-            try
-            {
-              rejectWriter.write("# " + message);
-              rejectWriter.newLine();
-              for (StringBuilder sb : lines)
-              {
-                rejectWriter.write(sb.toString());
-                rejectWriter.newLine();
-              }
-
-              rejectWriter.newLine();
-            }
-            catch (Exception e)
-            {
-              assert debugException(CLASS_NAME, "readEntry", e);
-            }
-          }
-
+          logToRejectWriter(lines, message);
           entriesRejected++;
           throw new LDIFException(msgID, message, lastEntryLineNumber, true);
         }
@@ -391,19 +373,19 @@
       {
         if(changeType.equals("add"))
         {
-          entry = new AddChangeRecordEntry(entryDN, this);
+          entry = parseAddChangeRecordEntry(entryDN, lines);
         } else if (changeType.equals("delete"))
         {
-          entry = new DeleteChangeRecordEntry(entryDN, this);
+          entry = parseDeleteChangeRecordEntry(entryDN, lines);
         } else if (changeType.equals("modify"))
         {
-          entry = new ModifyChangeRecordEntry(entryDN, this);
+          entry = parseModifyChangeRecordEntry(entryDN, lines);
         } else if (changeType.equals("modrdn"))
         {
-          entry = new ModifyDNChangeRecordEntry(entryDN, this);
+          entry = parseModifyDNChangeRecordEntry(entryDN, lines);
         } else if (changeType.equals("moddn"))
         {
-          entry = new ModifyDNChangeRecordEntry(entryDN, this);
+          entry = parseModifyDNChangeRecordEntry(entryDN, lines);
         } else
         {
           int msgID = MSGID_LDIF_INVALID_CHANGETYPE_ATTRIBUTE;
@@ -416,7 +398,7 @@
         // default to "add"?
         if(defaultAdd)
         {
-          entry = new AddChangeRecordEntry(entryDN, this);
+          entry = parseAddChangeRecordEntry(entryDN, lines);
         } else
         {
           int msgID = MSGID_LDIF_INVALID_CHANGETYPE_ATTRIBUTE;
@@ -426,16 +408,12 @@
         }
       }
 
-      // Parse the lines to populate the change record entry data structures.
-      entry.parse(lines, lastEntryLineNumber);
-
       return entry;
     }
   }
 
 
 
-
   /**
    * Reads a set of lines from the next entry in the LDIF source.
    *
@@ -512,23 +490,7 @@
         {
           int    msgID   = MSGID_LDIF_INVALID_LEADING_SPACE;
           String message = getMessage(msgID, lineNumber, line);
-
-          BufferedWriter rejectWriter = importConfig.getRejectWriter();
-          if (rejectWriter != null)
-          {
-            rejectWriter.write("# " + message);
-            rejectWriter.newLine();
-            for (StringBuilder sb : lines)
-            {
-              rejectWriter.write(sb.toString());
-              rejectWriter.newLine();
-            }
-
-            rejectWriter.write(line);
-            rejectWriter.newLine();
-            rejectWriter.newLine();
-          }
-
+          logToRejectWriter(lines, message);
           throw new LDIFException(msgID, message, lineNumber, false);
         }
       }
@@ -584,26 +546,7 @@
       int    msgID   = MSGID_LDIF_NO_ATTR_NAME;
       String message = getMessage(msgID, lastEntryLineNumber, line.toString());
 
-      BufferedWriter rejectWriter = importConfig.getRejectWriter();
-      if (rejectWriter != null)
-      {
-        try
-        {
-          rejectWriter.write("# " + message);
-          rejectWriter.newLine();
-          for (StringBuilder sb : lines)
-          {
-            rejectWriter.write(sb.toString());
-            rejectWriter.newLine();
-          }
-
-          rejectWriter.newLine();
-        }
-        catch (Exception e)
-        {
-          assert debugException(CLASS_NAME, "readDN", e);
-        }
-      }
+      logToRejectWriter(lines, message);
 
       throw new LDIFException(msgID, message, lastEntryLineNumber, true);
     }
@@ -619,26 +562,7 @@
       int    msgID   = MSGID_LDIF_NO_DN;
       String message = getMessage(msgID, lastEntryLineNumber, line.toString());
 
-      BufferedWriter rejectWriter = importConfig.getRejectWriter();
-      if (rejectWriter != null)
-      {
-        try
-        {
-          rejectWriter.write("# " + message);
-          rejectWriter.newLine();
-          for (StringBuilder sb : lines)
-          {
-            rejectWriter.write(sb.toString());
-            rejectWriter.newLine();
-          }
-
-          rejectWriter.newLine();
-        }
-        catch (Exception e)
-        {
-          assert debugException(CLASS_NAME, "readDN", e);
-        }
-      }
+      logToRejectWriter(lines, message);
 
       throw new LDIFException(msgID, message, lastEntryLineNumber, true);
     }
@@ -679,26 +603,7 @@
         String message = getMessage(msgID, lastEntryLineNumber, line,
                                     String.valueOf(e));
 
-        BufferedWriter rejectWriter = importConfig.getRejectWriter();
-        if (rejectWriter != null)
-        {
-          try
-          {
-            rejectWriter.write("# " + message);
-            rejectWriter.newLine();
-            for (StringBuilder sb : lines)
-            {
-              rejectWriter.write(sb.toString());
-              rejectWriter.newLine();
-            }
-
-            rejectWriter.newLine();
-          }
-          catch (Exception e2)
-          {
-            assert debugException(CLASS_NAME, "readDN", e2);
-          }
-        }
+        logToRejectWriter(lines, message);
 
         throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
       }
@@ -715,26 +620,7 @@
         String message = getMessage(msgID, lastEntryLineNumber, line.toString(),
                                     de.getErrorMessage());
 
-        BufferedWriter rejectWriter = importConfig.getRejectWriter();
-        if (rejectWriter != null)
-        {
-          try
-          {
-            rejectWriter.write("# " + message);
-            rejectWriter.newLine();
-            for (StringBuilder sb : lines)
-            {
-              rejectWriter.write(sb.toString());
-              rejectWriter.newLine();
-            }
-
-            rejectWriter.newLine();
-          }
-          catch (Exception e)
-          {
-            assert debugException(CLASS_NAME, "readDN", e);
-          }
-        }
+        logToRejectWriter(lines, message);
 
         throw new LDIFException(msgID, message, lastEntryLineNumber, true, de);
       }
@@ -746,26 +632,7 @@
         String message = getMessage(msgID, lastEntryLineNumber, line.toString(),
                                     String.valueOf(e));
 
-        BufferedWriter rejectWriter = importConfig.getRejectWriter();
-        if (rejectWriter != null)
-        {
-          try
-          {
-            rejectWriter.write("# " + message);
-            rejectWriter.newLine();
-            for (StringBuilder sb : lines)
-            {
-              rejectWriter.write(sb.toString());
-              rejectWriter.newLine();
-            }
-
-            rejectWriter.newLine();
-          }
-          catch (Exception e2)
-          {
-            assert debugException(CLASS_NAME, "readDN", e2);
-          }
-        }
+        logToRejectWriter(lines, message);
 
         throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
       }
@@ -794,26 +661,7 @@
         String message = getMessage(msgID, lastEntryLineNumber, line.toString(),
                                     de.getErrorMessage());
 
-        BufferedWriter rejectWriter = importConfig.getRejectWriter();
-        if (rejectWriter != null)
-        {
-          try
-          {
-            rejectWriter.write("# " + message);
-            rejectWriter.newLine();
-            for (StringBuilder sb : lines)
-            {
-              rejectWriter.write(sb.toString());
-              rejectWriter.newLine();
-            }
-
-            rejectWriter.newLine();
-          }
-          catch (Exception e)
-          {
-            assert debugException(CLASS_NAME, "readDN", e);
-          }
-        }
+        logToRejectWriter(lines, message);
 
         throw new LDIFException(msgID, message, lastEntryLineNumber, true, de);
       }
@@ -825,26 +673,7 @@
         String message = getMessage(msgID, lastEntryLineNumber, line.toString(),
                                     String.valueOf(e));
 
-        BufferedWriter rejectWriter = importConfig.getRejectWriter();
-        if (rejectWriter != null)
-        {
-          try
-          {
-            rejectWriter.write("# " + message);
-            rejectWriter.newLine();
-            for (StringBuilder sb : lines)
-            {
-              rejectWriter.write(sb.toString());
-              rejectWriter.newLine();
-            }
-
-            rejectWriter.newLine();
-          }
-          catch (Exception e2)
-          {
-            assert debugException(CLASS_NAME, "readDN", e2);
-          }
-        }
+        logToRejectWriter(lines, message);
 
         throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
       }
@@ -852,6 +681,7 @@
   }
 
 
+
   /**
    * Reads the changetype of the entry from the provided list of lines.  If
    * there is no changetype attribute then an add is assumed.
@@ -882,28 +712,7 @@
     {
       int    msgID   = MSGID_LDIF_NO_ATTR_NAME;
       String message = getMessage(msgID, lastEntryLineNumber, line.toString());
-
-      BufferedWriter rejectWriter = importConfig.getRejectWriter();
-      if (rejectWriter != null)
-      {
-        try
-        {
-          rejectWriter.write("# " + message);
-          rejectWriter.newLine();
-          for (StringBuilder sb : lines)
-          {
-            rejectWriter.write(sb.toString());
-            rejectWriter.newLine();
-          }
-
-          rejectWriter.newLine();
-        }
-        catch (Exception e)
-        {
-          assert debugException(CLASS_NAME, "readChangeType", e);
-        }
-      }
-
+      logToRejectWriter(lines, message);
       throw new LDIFException(msgID, message, lastEntryLineNumber, true);
     }
 
@@ -915,7 +724,7 @@
     } else
     {
       // Remove the line
-      StringBuilder ln = lines.remove();
+      lines.remove();
     }
 
 
@@ -957,28 +766,7 @@
         int    msgID   = MSGID_LDIF_COULD_NOT_BASE64_DECODE_DN;
         String message = getMessage(msgID, lastEntryLineNumber, line,
                                     String.valueOf(e));
-
-        BufferedWriter rejectWriter = importConfig.getRejectWriter();
-        if (rejectWriter != null)
-        {
-          try
-          {
-            rejectWriter.write("# " + message);
-            rejectWriter.newLine();
-            for (StringBuilder sb : lines)
-            {
-              rejectWriter.write(sb.toString());
-              rejectWriter.newLine();
-            }
-
-            rejectWriter.newLine();
-          }
-          catch (Exception e2)
-          {
-            assert debugException(CLASS_NAME, "readChangeType", e2);
-          }
-        }
-
+        logToRejectWriter(lines, message);
         throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
       }
 
@@ -1027,270 +815,24 @@
        HashMap<AttributeType,List<Attribute>> operationalAttributes)
           throws LDIFException
   {
-    assert debugEnter(CLASS_NAME, "readAttribute", String.valueOf(line),
-                        String.valueOf(entryDN), String.valueOf(objectClasses),
-                        String.valueOf(userAttributes),
-                        String.valueOf(operationalAttributes));
+    assert debugEnter(CLASS_NAME, "readAttribute",
+        String.valueOf(line),
+        String.valueOf(entryDN),
+        String.valueOf(objectClasses),
+        String.valueOf(userAttributes),
+        String.valueOf(operationalAttributes));
 
-
-    int colonPos = line.indexOf(":");
-    if (colonPos <= 0)
-    {
-      int    msgID   = MSGID_LDIF_NO_ATTR_NAME;
-      String message = getMessage(msgID, lastEntryLineNumber, line.toString());
-
-      BufferedWriter rejectWriter = importConfig.getRejectWriter();
-      if (rejectWriter != null)
-      {
-        try
-        {
-          rejectWriter.write("# " + message);
-          rejectWriter.newLine();
-          for (StringBuilder sb : lines)
-          {
-            rejectWriter.write(sb.toString());
-            rejectWriter.newLine();
-          }
-
-          rejectWriter.newLine();
-        }
-        catch (Exception e)
-        {
-          assert debugException(CLASS_NAME, "readAttribute", e);
-        }
-      }
-
-      throw new LDIFException(msgID, message, lastEntryLineNumber, true);
-    }
-
-    LinkedHashSet<String> options;
-    String attrName = line.substring(0, colonPos);
-    int semicolonPos = attrName.indexOf(';');
-    if (semicolonPos > 0)
-    {
-      options = new LinkedHashSet<String>();
-      int nextPos = attrName.indexOf(';', semicolonPos+1);
-      while (nextPos > 0)
-      {
-        String option = attrName.substring(semicolonPos+1, nextPos);
-        if (option.length() > 0)
-        {
-          options.add(option);
-          semicolonPos = nextPos;
-          nextPos = attrName.indexOf(';', semicolonPos+1);
-        }
-      }
-
-      String option = attrName.substring(semicolonPos+1);
-      if (option.length() > 0)
-      {
-        options.add(option);
-      }
-
-      attrName = attrName.substring(0, attrName.indexOf(';'));
-    }
-    else
-    {
-      options   = null;
-    }
+    // Parse the attribute type description.
+    int colonPos = parseColonPosition(lines, line);
+    String attrDescr = line.substring(0, colonPos);
+    Attribute attribute = parseAttrDescription(attrDescr);
+    String attrName = attribute.getName();
     String lowerName = toLowerCase(attrName);
+    LinkedHashSet<String> options = attribute.getOptions();
 
-
-    // Look at the character immediately after the colon.  If there is none,
-    // then assume an attribute with an empty value.  If it is another colon,
-    // then the value must be base64-encoded.  If it is a less-than sign, then
-    // assume that it is a URL.  Otherwise, it is a regular value.
-    int length = line.length();
-    ASN1OctetString value;
-    if (colonPos == (length-1))
-    {
-      value = new ASN1OctetString();
-    }
-    else
-    {
-      char c = line.charAt(colonPos+1);
-      if (c == ':')
-      {
-        // The value is base64-encoded.  Find the first non-blank character,
-        // take the rest of the line, and base64-decode it.
-        int pos = colonPos+2;
-        while ((pos < length) && (line.charAt(pos) == ' '))
-        {
-          pos++;
-        }
-
-        try
-        {
-          value = new ASN1OctetString(Base64.decode(line.substring(pos)));
-        }
-        catch (Exception e)
-        {
-          // The value did not have a valid base64-encoding.
-          assert debugException(CLASS_NAME, "readAttribute", e);
-
-          int    msgID   = MSGID_LDIF_COULD_NOT_BASE64_DECODE_ATTR;
-          String message = getMessage(msgID, String.valueOf(entryDN),
-                                      lastEntryLineNumber, line,
-                                      String.valueOf(e));
-
-          BufferedWriter rejectWriter = importConfig.getRejectWriter();
-          if (rejectWriter != null)
-          {
-            try
-            {
-              rejectWriter.write("# " + message);
-              rejectWriter.newLine();
-              for (StringBuilder sb : lines)
-              {
-                rejectWriter.write(sb.toString());
-                rejectWriter.newLine();
-              }
-
-              rejectWriter.newLine();
-            }
-            catch (Exception e2)
-            {
-              assert debugException(CLASS_NAME, "readAttribute", e2);
-            }
-          }
-
-          throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
-        }
-      }
-      else if (c == '<')
-      {
-        // Find the first non-blank character, decode the rest of the line as a
-        // URL, and read its contents.
-        int pos = colonPos+2;
-        while ((pos < length) && (line.charAt(pos) == ' '))
-        {
-          pos++;
-        }
-
-        URL contentURL;
-        try
-        {
-          contentURL = new URL(line.substring(pos));
-        }
-        catch (Exception e)
-        {
-          // The URL was malformed or had an invalid protocol.
-          assert debugException(CLASS_NAME, "readAttribute", e);
-
-          int    msgID   = MSGID_LDIF_INVALID_URL;
-          String message = getMessage(msgID, String.valueOf(entryDN),
-                                      lastEntryLineNumber,
-                                      String.valueOf(attrName),
-                                      String.valueOf(e));
-
-          BufferedWriter rejectWriter = importConfig.getRejectWriter();
-          if (rejectWriter != null)
-          {
-            try
-            {
-              rejectWriter.write("# " + message);
-              rejectWriter.newLine();
-              for (StringBuilder sb : lines)
-              {
-                rejectWriter.write(sb.toString());
-                rejectWriter.newLine();
-              }
-
-              rejectWriter.newLine();
-            }
-            catch (Exception e2)
-            {
-              assert debugException(CLASS_NAME, "readAttribute", e2);
-            }
-          }
-
-          throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
-        }
-
-
-        InputStream inputStream = null;
-        ByteArrayOutputStream outputStream = null;
-        try
-        {
-          outputStream = new ByteArrayOutputStream();
-          inputStream  = contentURL.openConnection().getInputStream();
-
-          int bytesRead;
-          while ((bytesRead = inputStream.read(buffer)) > 0)
-          {
-            outputStream.write(buffer, 0, bytesRead);
-          }
-
-          value = new ASN1OctetString(outputStream.toByteArray());
-        }
-        catch (Exception e)
-        {
-          // We were unable to read the contents of that URL for some reason.
-          assert debugException(CLASS_NAME, "readAttribute", e);
-
-          int msgID = MSGID_LDIF_URL_IO_ERROR;
-          String message = getMessage(msgID, String.valueOf(entryDN),
-                                      lastEntryLineNumber,
-                                      String.valueOf(attrName),
-                                      String.valueOf(contentURL),
-                                      String.valueOf(e));
-
-          BufferedWriter rejectWriter = importConfig.getRejectWriter();
-          if (rejectWriter != null)
-          {
-            try
-            {
-              rejectWriter.write("# " + message);
-              rejectWriter.newLine();
-              for (StringBuilder sb : lines)
-              {
-                rejectWriter.write(sb.toString());
-                rejectWriter.newLine();
-              }
-
-              rejectWriter.newLine();
-            }
-            catch (Exception e2)
-            {
-              assert debugException(CLASS_NAME, "readAttribute", e2);
-            }
-          }
-
-          throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
-        }
-        finally
-        {
-          if (outputStream != null)
-          {
-            try
-            {
-              outputStream.close();
-            } catch (Exception e) {}
-          }
-
-          if (inputStream != null)
-          {
-            try
-            {
-              inputStream.close();
-            } catch (Exception e) {}
-          }
-        }
-      }
-      else
-      {
-        // The rest of the line should be the value.  Skip over any spaces and
-        // take the rest of the line as the value.
-        int pos = colonPos+1;
-        while ((pos < length) && (line.charAt(pos) == ' '))
-        {
-          pos++;
-        }
-
-        value = new ASN1OctetString(line.substring(pos));
-      }
-    }
-
+    // Now parse the attribute value.
+    ASN1OctetString value = parseSingleValue(lines, line, entryDN,
+        colonPos, attrName);
 
     // See if this is an objectclass or an attribute.  Then get the
     // corresponding definition and add the value to the appropriate hash.
@@ -1348,45 +890,7 @@
       }
 
 
-      AttributeValue attributeValue;
-      try
-      {
-        attributeValue = new AttributeValue(attrType, value);
-      }
-      catch (Exception e)
-      {
-        assert debugException(CLASS_NAME, "readAttribute", e);
-
-        int    msgID   = MSGID_LDIF_INVALID_ATTR_SYNTAX;
-        String message = getMessage(msgID, String.valueOf(entryDN),
-                                    lastEntryLineNumber, value.stringValue(),
-                                    attrName, String.valueOf(e));
-
-        BufferedWriter rejectWriter = importConfig.getRejectWriter();
-        if (rejectWriter != null)
-        {
-          try
-          {
-            rejectWriter.write("# " + message);
-            rejectWriter.newLine();
-            for (StringBuilder sb : lines)
-            {
-              rejectWriter.write(sb.toString());
-              rejectWriter.newLine();
-            }
-
-            rejectWriter.newLine();
-          }
-          catch (Exception e2)
-          {
-            assert debugException(CLASS_NAME, "readAttribute", e2);
-          }
-        }
-
-        throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
-      }
-
-
+      AttributeValue attributeValue = new AttributeValue(attrType, value);
       List<Attribute> attrList;
       if (attrType.isOperational())
       {
@@ -1433,28 +937,7 @@
             String message = getMessage(msgID, String.valueOf(entryDN),
                                         lastEntryLineNumber, attrName,
                                         value.stringValue());
-
-            BufferedWriter rejectWriter = importConfig.getRejectWriter();
-            if (rejectWriter != null)
-            {
-              try
-              {
-                rejectWriter.write("# " + message);
-                rejectWriter.newLine();
-                for (StringBuilder sb : lines)
-                {
-                  rejectWriter.write(sb.toString());
-                  rejectWriter.newLine();
-                }
-
-                rejectWriter.newLine();
-              }
-              catch (Exception e)
-              {
-                assert debugException(CLASS_NAME, "readAttribute", e);
-              }
-            }
-
+            logToRejectWriter(lines, message);
             throw new LDIFException(msgID, message, lastEntryLineNumber, true);
           }
           else if (attrType.isSingleValue() && (! valueSet.isEmpty()))
@@ -1462,28 +945,7 @@
             int    msgID   = MSGID_LDIF_MULTIPLE_VALUES_FOR_SINGLE_VALUED_ATTR;
             String message = getMessage(msgID, String.valueOf(entryDN),
                                         lastEntryLineNumber, attrName);
-
-            BufferedWriter rejectWriter = importConfig.getRejectWriter();
-            if (rejectWriter != null)
-            {
-              try
-              {
-                rejectWriter.write("# " + message);
-                rejectWriter.newLine();
-                for (StringBuilder sb : lines)
-                {
-                  rejectWriter.write(sb.toString());
-                  rejectWriter.newLine();
-                }
-
-                rejectWriter.newLine();
-              }
-              catch (Exception e)
-              {
-                assert debugException(CLASS_NAME, "readAttribute", e);
-              }
-            }
-
+            logToRejectWriter(lines, message);
             throw new LDIFException(msgID, message, lastEntryLineNumber, true);
           }
           else
@@ -1506,367 +968,6 @@
   }
 
 
-  /**
-   * Decodes the provided line as an LDIF attribute and adds it to the
-   * appropriate hash.
-   *
-   * @param  lines                  The full set of lines that comprise the
-   *                                entry (used for writing reject information).
-   * @param  line                   The line to decode.
-   * @param  entryDN                The DN of the entry being decoded.
-   * @param  entryAttributes        The set of attributes decoded so far
-   *                                for the current entry.
-   *
-   * @throws  LDIFException  If a problem occurs while trying to decode the
-   *                         attribute contained in the provided entry.
-   */
-  public void readAttribute(LinkedList<StringBuilder> lines,
-       StringBuilder line, DN entryDN,
-       HashMap<AttributeType,ArrayList<Attribute>> entryAttributes)
-          throws LDIFException
-  {
-    assert debugEnter(CLASS_NAME, "readAttribute", String.valueOf(line),
-                        String.valueOf(entryDN),
-                        String.valueOf(entryAttributes));
-
-
-    int colonPos = line.indexOf(":");
-    if (colonPos <= 0)
-    {
-      int    msgID   = MSGID_LDIF_NO_ATTR_NAME;
-      String message = getMessage(msgID, lastEntryLineNumber, line.toString());
-
-      BufferedWriter rejectWriter = importConfig.getRejectWriter();
-      if (rejectWriter != null)
-      {
-        try
-        {
-          rejectWriter.write("# " + message);
-          rejectWriter.newLine();
-          for (StringBuilder sb : lines)
-          {
-            rejectWriter.write(sb.toString());
-            rejectWriter.newLine();
-          }
-
-          rejectWriter.newLine();
-        }
-        catch (Exception e)
-        {
-          assert debugException(CLASS_NAME, "readAttribute", e);
-        }
-      }
-
-      throw new LDIFException(msgID, message, lastEntryLineNumber, true);
-    }
-
-    String attrDescr = line.substring(0, colonPos);
-    Attribute attribute = parseAttrDescription(attrDescr);
-    String attrName = attribute.getName();
-    LinkedHashSet<String> options = attribute.getOptions();
-
-    // Look at the character immediately after the colon.  If there is none,
-    // then assume an attribute with an empty value.  If it is another colon,
-    // then the value must be base64-encoded.  If it is a less-than sign, then
-    // assume that it is a URL.  Otherwise, it is a regular value.
-    int length = line.length();
-    ASN1OctetString value;
-    if (colonPos == (length-1))
-    {
-      value = new ASN1OctetString();
-    }
-    else
-    {
-      char c = line.charAt(colonPos+1);
-      if (c == ':')
-      {
-        // The value is base64-encoded.  Find the first non-blank character,
-        // take the rest of the line, and base64-decode it.
-        int pos = colonPos+2;
-        while ((pos < length) && (line.charAt(pos) == ' '))
-        {
-          pos++;
-        }
-
-        try
-        {
-          value = new ASN1OctetString(Base64.decode(line.substring(pos)));
-        }
-        catch (Exception e)
-        {
-          // The value did not have a valid base64-encoding.
-          assert debugException(CLASS_NAME, "readAttribute", e);
-
-          int    msgID   = MSGID_LDIF_COULD_NOT_BASE64_DECODE_ATTR;
-          String message = getMessage(msgID, String.valueOf(entryDN),
-                                      lastEntryLineNumber, line,
-                                      String.valueOf(e));
-
-          BufferedWriter rejectWriter = importConfig.getRejectWriter();
-          if (rejectWriter != null)
-          {
-            try
-            {
-              rejectWriter.write("# " + message);
-              rejectWriter.newLine();
-              for (StringBuilder sb : lines)
-              {
-                rejectWriter.write(sb.toString());
-                rejectWriter.newLine();
-              }
-
-              rejectWriter.newLine();
-            }
-            catch (Exception e2)
-            {
-              assert debugException(CLASS_NAME, "readAttribute", e2);
-            }
-          }
-
-          throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
-        }
-      }
-      else if (c == '<')
-      {
-        // Find the first non-blank character, decode the rest of the line as a
-        // URL, and read its contents.
-        int pos = colonPos+2;
-        while ((pos < length) && (line.charAt(pos) == ' '))
-        {
-          pos++;
-        }
-
-        URL contentURL;
-        try
-        {
-          contentURL = new URL(line.substring(pos));
-        }
-        catch (Exception e)
-        {
-          // The URL was malformed or had an invalid protocol.
-          assert debugException(CLASS_NAME, "readAttribute", e);
-
-          int    msgID   = MSGID_LDIF_INVALID_URL;
-          String message = getMessage(msgID, String.valueOf(entryDN),
-                                      lastEntryLineNumber,
-                                      String.valueOf(attrName),
-                                      String.valueOf(e));
-
-          BufferedWriter rejectWriter = importConfig.getRejectWriter();
-          if (rejectWriter != null)
-          {
-            try
-            {
-              rejectWriter.write("# " + message);
-              rejectWriter.newLine();
-              for (StringBuilder sb : lines)
-              {
-                rejectWriter.write(sb.toString());
-                rejectWriter.newLine();
-              }
-
-              rejectWriter.newLine();
-            }
-            catch (Exception e2)
-            {
-              assert debugException(CLASS_NAME, "readAttribute", e2);
-            }
-          }
-
-          throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
-        }
-
-
-        InputStream inputStream = null;
-        ByteArrayOutputStream outputStream = null;
-        try
-        {
-          outputStream = new ByteArrayOutputStream();
-          inputStream  = contentURL.openConnection().getInputStream();
-
-          int bytesRead;
-          while ((bytesRead = inputStream.read(buffer)) > 0)
-          {
-            outputStream.write(buffer, 0, bytesRead);
-          }
-
-          value = new ASN1OctetString(outputStream.toByteArray());
-        }
-        catch (Exception e)
-        {
-          // We were unable to read the contents of that URL for some reason.
-          assert debugException(CLASS_NAME, "readAttribute", e);
-
-          int msgID = MSGID_LDIF_URL_IO_ERROR;
-          String message = getMessage(msgID, String.valueOf(entryDN),
-                                      lastEntryLineNumber,
-                                      String.valueOf(attrName),
-                                      String.valueOf(contentURL),
-                                      String.valueOf(e));
-
-          BufferedWriter rejectWriter = importConfig.getRejectWriter();
-          if (rejectWriter != null)
-          {
-            try
-            {
-              rejectWriter.write("# " + message);
-              rejectWriter.newLine();
-              for (StringBuilder sb : lines)
-              {
-                rejectWriter.write(sb.toString());
-                rejectWriter.newLine();
-              }
-
-              rejectWriter.newLine();
-            }
-            catch (Exception e2)
-            {
-              assert debugException(CLASS_NAME, "readAttribute", e2);
-            }
-          }
-
-          throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
-        }
-        finally
-        {
-          if (outputStream != null)
-          {
-            try
-            {
-              outputStream.close();
-            } catch (Exception e) {}
-          }
-
-          if (inputStream != null)
-          {
-            try
-            {
-              inputStream.close();
-            } catch (Exception e) {}
-          }
-        }
-      }
-      else
-      {
-        // The rest of the line should be the value.  Skip over any spaces and
-        // take the rest of the line as the value.
-        int pos = colonPos+1;
-        while ((pos < length) && (line.charAt(pos) == ' '))
-        {
-          pos++;
-        }
-
-        value = new ASN1OctetString(line.substring(pos));
-      }
-    }
-
-    AttributeType attrType = attribute.getAttributeType();
-
-
-    AttributeValue attributeValue;
-    try
-    {
-      attributeValue = new AttributeValue(attrType, value);
-    }
-    catch (Exception e)
-    {
-      assert debugException(CLASS_NAME, "readAttribute", e);
-
-      int    msgID   = MSGID_LDIF_INVALID_ATTR_SYNTAX;
-      String message = getMessage(msgID, String.valueOf(entryDN),
-                                  lastEntryLineNumber, value.stringValue(),
-                                  attrName, String.valueOf(e));
-
-      BufferedWriter rejectWriter = importConfig.getRejectWriter();
-      if (rejectWriter != null)
-      {
-        try
-        {
-          rejectWriter.write("# " + message);
-          rejectWriter.newLine();
-          for (StringBuilder sb : lines)
-          {
-            rejectWriter.write(sb.toString());
-            rejectWriter.newLine();
-          }
-
-          rejectWriter.newLine();
-        }
-        catch (Exception e2)
-        {
-          assert debugException(CLASS_NAME, "readAttribute", e2);
-        }
-      }
-
-      throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
-    }
-
-
-    ArrayList<Attribute> attrList;
-    attrList = entryAttributes.get(attrType);
-    if (attrList == null)
-    {
-      attribute.getValues().add(attributeValue);
-
-      attrList = new ArrayList<Attribute>();
-      attrList.add(attribute);
-      entryAttributes.put(attrType, attrList);
-      return;
-    }
-
-
-    // Check to see if any of the attributes in the list have the same set of
-    // options.  If so, then try to add a value to that attribute.
-    for (Attribute a : attrList)
-    {
-      if (a.optionsEqual(options))
-      {
-        LinkedHashSet<AttributeValue> valueSet = a.getValues();
-        if (valueSet.contains(attributeValue))
-        {
-          int    msgID   = MSGID_LDIF_DUPLICATE_ATTR;
-          String message = getMessage(msgID, String.valueOf(entryDN),
-                                      lastEntryLineNumber, attrName,
-                                      value.stringValue());
-
-          BufferedWriter rejectWriter = importConfig.getRejectWriter();
-          if (rejectWriter != null)
-          {
-            try
-            {
-              rejectWriter.write("# " + message);
-              rejectWriter.newLine();
-              for (StringBuilder sb : lines)
-              {
-                rejectWriter.write(sb.toString());
-                rejectWriter.newLine();
-              }
-
-              rejectWriter.newLine();
-            }
-            catch (Exception e)
-            {
-              assert debugException(CLASS_NAME, "readAttribute", e);
-            }
-          }
-
-          throw new LDIFException(msgID, message, lastEntryLineNumber, true);
-        }
-        else
-        {
-          valueSet.add(attributeValue);
-          return;
-        }
-      }
-    }
-
-
-    // No set of matching options was found, so create a new one and add it to
-    // the list.
-    attribute.getValues().add(attributeValue);
-    attrList.add(attribute);
-    return;
-  }
 
   /**
    * Decodes the provided line as an LDIF attribute and returns the
@@ -1885,295 +986,36 @@
    *                                entry or if the parsed attribute name does
    *                                not match the specified attribute name.
    */
-  public Attribute readSingleValueAttribute(
+  private Attribute readSingleValueAttribute(
        LinkedList<StringBuilder> lines, StringBuilder line, DN entryDN,
        String attributeName) throws LDIFException
   {
-    assert debugEnter(CLASS_NAME, "readAttribute", String.valueOf(lines),
-      String.valueOf(line),
-                        String.valueOf(entryDN),
-                        String.valueOf(attributeName));
+    assert debugEnter(CLASS_NAME, "readSingleValueAttribute",
+        String.valueOf(lines),
+        String.valueOf(line),
+        String.valueOf(entryDN),
+        String.valueOf(attributeName));
 
-
-    int colonPos = line.indexOf(":");
-    if (colonPos <= 0)
-    {
-      int    msgID   = MSGID_LDIF_NO_ATTR_NAME;
-      String message = getMessage(msgID, lastEntryLineNumber, line.toString());
-
-      BufferedWriter rejectWriter = importConfig.getRejectWriter();
-      if (rejectWriter != null)
-      {
-        try
-        {
-          rejectWriter.write("# " + message);
-          rejectWriter.newLine();
-          for (StringBuilder sb : lines)
-          {
-            rejectWriter.write(sb.toString());
-            rejectWriter.newLine();
-          }
-
-          rejectWriter.newLine();
-        }
-        catch (Exception e)
-        {
-          assert debugException(CLASS_NAME, "readAttribute", e);
-        }
-      }
-
-      throw new LDIFException(msgID, message, lastEntryLineNumber, true);
-    }
-
+    // Parse the attribute type description.
+    int colonPos = parseColonPosition(lines, line);
     String attrDescr = line.substring(0, colonPos);
     Attribute attribute = parseAttrDescription(attrDescr);
     String attrName = attribute.getName();
     String lowerName = toLowerCase(attrName);
 
-    if(attributeName != null && !attributeName.toLowerCase().equals(lowerName))
+    if(attributeName != null && !toLowerCase(attributeName).equals(lowerName))
     {
       int msgID = MSGID_LDIF_INVALID_CHANGERECORD_ATTRIBUTE;
       String message = getMessage(msgID, lowerName, attributeName);
       throw new LDIFException(msgID, message, lastEntryLineNumber, false);
     }
 
-
-    // Look at the character immediately after the colon.  If there is none,
-    // then assume an attribute with an empty value.  If it is another colon,
-    // then the value must be base64-encoded.  If it is a less-than sign, then
-    // assume that it is a URL.  Otherwise, it is a regular value.
-    int length = line.length();
-    ASN1OctetString value;
-    if (colonPos == (length-1))
-    {
-      value = new ASN1OctetString();
-    }
-    else
-    {
-      char c = line.charAt(colonPos+1);
-      if (c == ':')
-      {
-        // The value is base64-encoded.  Find the first non-blank character,
-        // take the rest of the line, and base64-decode it.
-        int pos = colonPos+2;
-        while ((pos < length) && (line.charAt(pos) == ' '))
-        {
-          pos++;
-        }
-
-        try
-        {
-          value = new ASN1OctetString(Base64.decode(line.substring(pos)));
-        }
-        catch (Exception e)
-        {
-          // The value did not have a valid base64-encoding.
-          assert debugException(CLASS_NAME, "readAttribute", e);
-
-          int    msgID   = MSGID_LDIF_COULD_NOT_BASE64_DECODE_ATTR;
-          String message = getMessage(msgID, String.valueOf(entryDN),
-                                      lastEntryLineNumber, line,
-                                      String.valueOf(e));
-
-          BufferedWriter rejectWriter = importConfig.getRejectWriter();
-          if (rejectWriter != null)
-          {
-            try
-            {
-              rejectWriter.write("# " + message);
-              rejectWriter.newLine();
-              for (StringBuilder sb : lines)
-              {
-                rejectWriter.write(sb.toString());
-                rejectWriter.newLine();
-              }
-
-              rejectWriter.newLine();
-            }
-            catch (Exception e2)
-            {
-              assert debugException(CLASS_NAME, "readAttribute", e2);
-            }
-          }
-
-          throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
-        }
-      }
-      else if (c == '<')
-      {
-        // Find the first non-blank character, decode the rest of the line as a
-        // URL, and read its contents.
-        int pos = colonPos+2;
-        while ((pos < length) && (line.charAt(pos) == ' '))
-        {
-          pos++;
-        }
-
-        URL contentURL;
-        try
-        {
-          contentURL = new URL(line.substring(pos));
-        }
-        catch (Exception e)
-        {
-          // The URL was malformed or had an invalid protocol.
-          assert debugException(CLASS_NAME, "readAttribute", e);
-
-          int    msgID   = MSGID_LDIF_INVALID_URL;
-          String message = getMessage(msgID, String.valueOf(entryDN),
-                                      lastEntryLineNumber,
-                                      String.valueOf(attrName),
-                                      String.valueOf(e));
-
-          BufferedWriter rejectWriter = importConfig.getRejectWriter();
-          if (rejectWriter != null)
-          {
-            try
-            {
-              rejectWriter.write("# " + message);
-              rejectWriter.newLine();
-              for (StringBuilder sb : lines)
-              {
-                rejectWriter.write(sb.toString());
-                rejectWriter.newLine();
-              }
-
-              rejectWriter.newLine();
-            }
-            catch (Exception e2)
-            {
-              assert debugException(CLASS_NAME, "readAttribute", e2);
-            }
-          }
-
-          throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
-        }
-
-
-        InputStream inputStream = null;
-        ByteArrayOutputStream outputStream = null;
-        try
-        {
-          outputStream = new ByteArrayOutputStream();
-          inputStream  = contentURL.openConnection().getInputStream();
-
-          int bytesRead;
-          while ((bytesRead = inputStream.read(buffer)) > 0)
-          {
-            outputStream.write(buffer, 0, bytesRead);
-          }
-
-          value = new ASN1OctetString(outputStream.toByteArray());
-        }
-        catch (Exception e)
-        {
-          // We were unable to read the contents of that URL for some reason.
-          assert debugException(CLASS_NAME, "readAttribute", e);
-
-          int msgID = MSGID_LDIF_URL_IO_ERROR;
-          String message = getMessage(msgID, String.valueOf(entryDN),
-                                      lastEntryLineNumber,
-                                      String.valueOf(attrName),
-                                      String.valueOf(contentURL),
-                                      String.valueOf(e));
-
-          BufferedWriter rejectWriter = importConfig.getRejectWriter();
-          if (rejectWriter != null)
-          {
-            try
-            {
-              rejectWriter.write("# " + message);
-              rejectWriter.newLine();
-              for (StringBuilder sb : lines)
-              {
-                rejectWriter.write(sb.toString());
-                rejectWriter.newLine();
-              }
-
-              rejectWriter.newLine();
-            }
-            catch (Exception e2)
-            {
-              assert debugException(CLASS_NAME, "readAttribute", e2);
-            }
-          }
-
-          throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
-        }
-        finally
-        {
-          if (outputStream != null)
-          {
-            try
-            {
-              outputStream.close();
-            } catch (Exception e) {}
-          }
-
-          if (inputStream != null)
-          {
-            try
-            {
-              inputStream.close();
-            } catch (Exception e) {}
-          }
-        }
-      }
-      else
-      {
-        // The rest of the line should be the value.  Skip over any spaces and
-        // take the rest of the line as the value.
-        int pos = colonPos+1;
-        while ((pos < length) && (line.charAt(pos) == ' '))
-        {
-          pos++;
-        }
-
-        value = new ASN1OctetString(line.substring(pos));
-      }
-    }
+    //  Now parse the attribute value.
+    ASN1OctetString value = parseSingleValue(lines, line, entryDN,
+        colonPos, attrName);
 
     AttributeType attrType = attribute.getAttributeType();
-
-
-    AttributeValue attributeValue;
-    try
-    {
-      attributeValue = new AttributeValue(attrType, value);
-    }
-    catch (Exception e)
-    {
-      assert debugException(CLASS_NAME, "readAttribute", e);
-
-      int    msgID   = MSGID_LDIF_INVALID_ATTR_SYNTAX;
-      String message = getMessage(msgID, String.valueOf(entryDN),
-                                  lastEntryLineNumber, value.stringValue(),
-                                  attrName, String.valueOf(e));
-
-      BufferedWriter rejectWriter = importConfig.getRejectWriter();
-      if (rejectWriter != null)
-      {
-        try
-        {
-          rejectWriter.write("# " + message);
-          rejectWriter.newLine();
-          for (StringBuilder sb : lines)
-          {
-            rejectWriter.write(sb.toString());
-            rejectWriter.newLine();
-          }
-
-          rejectWriter.newLine();
-        }
-        catch (Exception e2)
-        {
-          assert debugException(CLASS_NAME, "readAttribute", e2);
-        }
-      }
-
-      throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
-    }
-
+    AttributeValue attributeValue = new AttributeValue(attrType, value);
     attribute.getValues().add(attributeValue);
 
     return attribute;
@@ -2216,7 +1058,8 @@
     {
       try
       {
-        rejectWriter.write("# " + message);
+        rejectWriter.write("# ");
+        rejectWriter.write(message);
         rejectWriter.newLine();
 
         for (StringBuilder sb : lastEntryHeaderLines)
@@ -2260,7 +1103,7 @@
    * @return A new attribute with no values, representing the attribute type
    * and its options.
    */
-  public static Attribute parseAttrDescription(String attrDescr)
+  private static Attribute parseAttrDescription(String attrDescr)
   {
     assert debugEnter(CLASS_NAME, "parseAttributeDescription",
                       String.valueOf(attrDescr));
@@ -2354,5 +1197,551 @@
 
     return entriesRejected;
   }
+
+
+
+  /**
+   * Parse a modifyDN change record entry from LDIF.
+   *
+   * @param entryDN
+   *          The name of the entry being modified.
+   * @param lines
+   *          The lines to parse.
+   * @return Returns the parsed modifyDN change record entry.
+   * @throws LDIFException
+   *           If there was an error when parsing the change record.
+   */
+  private ChangeRecordEntry parseModifyDNChangeRecordEntry(DN entryDN,
+      LinkedList<StringBuilder> lines) throws LDIFException {
+    assert debugEnter(CLASS_NAME, "parseModifyDNChangeRecordEntry",
+        String.valueOf(entryDN),
+        String.valueOf(lines),
+        String.valueOf(lineNumber));
+
+    DN newSuperiorDN = null;
+    RDN newRDN = null;
+    boolean deleteOldRDN = false;
+
+    if(lines.isEmpty())
+    {
+      int msgID = MSGID_LDIF_NO_MOD_DN_ATTRIBUTES;
+      String message = getMessage(msgID);
+      throw new LDIFException(msgID, message, lineNumber, true);
+    }
+
+    StringBuilder line = lines.remove();
+    String rdnStr = getModifyDNAttributeValue(lines, line, entryDN, "newrdn");
+
+    try
+    {
+      newRDN = RDN.decode(rdnStr);
+    } catch (DirectoryException de)
+    {
+      assert debugException(CLASS_NAME, "parse", de);
+      int    msgID   = MSGID_LDIF_INVALID_DN;
+      String message = getMessage(msgID, lineNumber, line.toString(),
+          de.getErrorMessage());
+      throw new LDIFException(msgID, message, lineNumber, true);
+    } catch (Exception e)
+    {
+      assert debugException(CLASS_NAME, "parse", e);
+      int    msgID   = MSGID_LDIF_INVALID_DN;
+      String message = getMessage(msgID, lineNumber, line.toString(),
+          e.getMessage());
+      throw new LDIFException(msgID, message, lineNumber, true);
+    }
+
+    if(lines.isEmpty())
+    {
+      int msgID = MSGID_LDIF_NO_DELETE_OLDRDN_ATTRIBUTE;
+      String message = getMessage(msgID);
+      throw new LDIFException(msgID, message, lineNumber, true);
+    }
+    lineNumber++;
+
+    line = lines.remove();
+    String delStr = getModifyDNAttributeValue(lines, line,
+        entryDN, "deleteoldrdn");
+
+    if(delStr.equalsIgnoreCase("false") ||
+        delStr.equalsIgnoreCase("no") ||
+        delStr.equalsIgnoreCase("0"))
+    {
+      deleteOldRDN = false;
+    } else if(delStr.equalsIgnoreCase("true") ||
+        delStr.equalsIgnoreCase("yes") ||
+        delStr.equalsIgnoreCase("1"))
+    {
+      deleteOldRDN = true;
+    } else
+    {
+      int msgID = MSGID_LDIF_INVALID_DELETE_OLDRDN_ATTRIBUTE;
+      String message = getMessage(msgID, delStr);
+      throw new LDIFException(msgID, message, lineNumber, true);
+    }
+
+    if(!lines.isEmpty())
+    {
+      lineNumber++;
+
+      line = lines.remove();
+
+      String dnStr = getModifyDNAttributeValue(lines, line,
+          entryDN, "newsuperior");
+      try
+      {
+        newSuperiorDN = DN.decode(dnStr);
+      } catch (DirectoryException de)
+      {
+        assert debugException(CLASS_NAME, "parse", de);
+        int    msgID   = MSGID_LDIF_INVALID_DN;
+        String message = getMessage(msgID, lineNumber, line.toString(),
+            de.getErrorMessage());
+        throw new LDIFException(msgID, message, lineNumber, true);
+      } catch (Exception e)
+      {
+        assert debugException(CLASS_NAME, "parse", e);
+        int    msgID   = MSGID_LDIF_INVALID_DN;
+        String message = getMessage(msgID, lineNumber, line.toString(),
+            e.getMessage());
+        throw new LDIFException(msgID, message, lineNumber, true);
+      }
+    }
+
+    return new ModifyDNChangeRecordEntry(entryDN, newSuperiorDN,
+        newRDN, deleteOldRDN);
+  }
+
+
+
+  /**
+   * Return the string value for the specified attribute name which only
+   * has one value.
+   *
+   * @param lines
+   *          The set of lines for this change record entry.
+   * @param line
+   *          The line currently being examined.
+   * @param entryDN
+   *          The name of the entry being modified.
+   * @param attributeName
+   *          The attribute name
+   * @return the string value for the attribute name.
+   * @throws LDIFException
+   *           If a problem occurs while attempting to determine the
+   *           attribute value.
+   */
+
+  private String getModifyDNAttributeValue(LinkedList<StringBuilder> lines,
+                                   StringBuilder line,
+                                   DN entryDN,
+                                   String attributeName) throws LDIFException
+  {
+    assert debugEnter(CLASS_NAME, "getModifyDNAttributeValue",
+        String.valueOf(lines),
+        String.valueOf(line),
+        String.valueOf(entryDN),
+        String.valueOf(attributeName));
+
+    Attribute attr =
+      readSingleValueAttribute(lines, line, entryDN, attributeName);
+    LinkedHashSet<AttributeValue> values = attr.getValues();
+
+    // Get the attribute value
+    Object[] vals = values.toArray();
+    return (((AttributeValue)vals[0]).getStringValue());
+  }
+
+
+
+  /**
+   * Parse a modify change record entry from LDIF.
+   *
+   * @param entryDN
+   *          The name of the entry being modified.
+   * @param lines
+   *          The lines to parse.
+   * @return Returns the parsed modify change record entry.
+   * @throws LDIFException
+   *           If there was an error when parsing the change record.
+   */
+  private ChangeRecordEntry parseModifyChangeRecordEntry(DN entryDN,
+      LinkedList<StringBuilder> lines) throws LDIFException {
+    assert debugEnter(CLASS_NAME, "parseModifyChangeRecordEntry",
+        String.valueOf(entryDN),
+        String.valueOf(lines),
+        String.valueOf(lineNumber));
+
+    List<LDAPModification> modifications = new ArrayList<LDAPModification>();
+    while(!lines.isEmpty())
+    {
+      ModificationType modType = null;
+
+      StringBuilder line = lines.remove();
+      Attribute attr =
+        readSingleValueAttribute(lines, line, entryDN, null);
+      String name = attr.getName();
+      LinkedHashSet<AttributeValue> values = attr.getValues();
+
+      // Get the attribute description
+      String attrDescr = values.iterator().next().getStringValue();
+
+      String lowerName = toLowerCase(name);
+      if(lowerName.equals("add"))
+      {
+        modType = ModificationType.ADD;
+      } else if(lowerName.equals("delete"))
+      {
+        modType = ModificationType.DELETE;
+      } else if(lowerName.equals("replace"))
+      {
+        modType = ModificationType.REPLACE;
+      } else if(lowerName.equals("increment"))
+      {
+        modType = ModificationType.INCREMENT;
+      } else
+      {
+        // Invalid attribute name.
+        int msgID = MSGID_LDIF_INVALID_MODIFY_ATTRIBUTE;
+        String message = getMessage(msgID, name,
+            "add, delete, replace, increment");
+        throw new LDIFException(msgID, message, lineNumber, true);
+      }
+
+      // Now go through the rest of the attributes till the "-" line is
+      // reached.
+      Attribute modAttr = LDIFReader.parseAttrDescription(attrDescr);
+      while (! lines.isEmpty())
+      {
+        line = lines.remove();
+        if(line.toString().equals("-"))
+        {
+          break;
+        }
+        Attribute a =
+          readSingleValueAttribute(lines, line, entryDN, attrDescr);
+        modAttr.getValues().addAll(a.getValues());
+      }
+
+      LDAPAttribute ldapAttr = new LDAPAttribute(modAttr);
+      LDAPModification mod = new LDAPModification(modType, ldapAttr);
+      modifications.add(mod);
+    }
+
+    return new ModifyChangeRecordEntry(entryDN, modifications);
+  }
+
+
+
+  /**
+   * Parse a delete change record entry from LDIF.
+   *
+   * @param entryDN
+   *          The name of the entry being deleted.
+   * @param lines
+   *          The lines to parse.
+   * @return Returns the parsed delete change record entry.
+   * @throws LDIFException
+   *           If there was an error when parsing the change record.
+   */
+  private ChangeRecordEntry parseDeleteChangeRecordEntry(DN entryDN,
+      LinkedList<StringBuilder> lines) throws LDIFException {
+    assert debugEnter(CLASS_NAME, "parseDeleteChangeRecordEntry",
+        String.valueOf(entryDN),
+        String.valueOf(lines),
+        String.valueOf(lineNumber));
+
+    if(!lines.isEmpty())
+    {
+      int msgID = MSGID_LDIF_INVALID_DELETE_ATTRIBUTES;
+      String message = getMessage(msgID);
+
+      throw new LDIFException(msgID, message, lineNumber, true);
+    }
+
+    return new DeleteChangeRecordEntry(entryDN);
+  }
+
+
+
+  /**
+   * Parse an add change record entry from LDIF.
+   *
+   * @param entryDN
+   *          The name of the entry being added.
+   * @param lines
+   *          The lines to parse.
+   * @return Returns the parsed add change record entry.
+   * @throws LDIFException
+   *           If there was an error when parsing the change record.
+   */
+  private ChangeRecordEntry parseAddChangeRecordEntry(DN entryDN,
+      LinkedList<StringBuilder> lines) throws LDIFException {
+    assert debugEnter(CLASS_NAME, "parseAddChangeRecordEntry",
+        String.valueOf(entryDN),
+        String.valueOf(lines),
+        String.valueOf(lineNumber));
+
+    HashMap<ObjectClass,String> objectClasses =
+      new HashMap<ObjectClass,String>();
+    HashMap<AttributeType,List<Attribute>> attributes =
+      new HashMap<AttributeType, List<Attribute>>();
+    for(StringBuilder line : lines)
+    {
+      readAttribute(lines, line, entryDN, objectClasses,
+          attributes, attributes);
+    }
+
+    // Reconstruct the object class attribute.
+    AttributeType ocType = DirectoryServer.getObjectClassAttributeType();
+    LinkedHashSet<AttributeValue> ocValues =
+      new LinkedHashSet<AttributeValue>(objectClasses.size());
+    for (String value : objectClasses.values()) {
+      AttributeValue av = new AttributeValue(ocType, value);
+      ocValues.add(av);
+    }
+    Attribute ocAttr = new Attribute(ocType, "objectClass", ocValues);
+    List<Attribute> ocAttrList = new ArrayList<Attribute>(1);
+    ocAttrList.add(ocAttr);
+    attributes.put(ocType, ocAttrList);
+
+    return new AddChangeRecordEntry(entryDN, attributes);
+  }
+
+
+
+  /**
+   * Parse colon position in an attribute description.
+   *
+   * @param lines
+   *          The current set of lines.
+   * @param line
+   *          The current line.
+   * @return The colon position.
+   * @throws LDIFException
+   *           If the colon was badly placed or not found.
+   */
+  private int parseColonPosition(LinkedList<StringBuilder> lines,
+      StringBuilder line) throws LDIFException {
+    assert debugEnter(CLASS_NAME, "parseColonPosition",
+        String.valueOf(lines),
+        String.valueOf(lineNumber));
+
+    int colonPos = line.indexOf(":");
+    if (colonPos <= 0)
+    {
+      int    msgID   = MSGID_LDIF_NO_ATTR_NAME;
+      String message = getMessage(msgID, lastEntryLineNumber, line.toString());
+      logToRejectWriter(lines, message);
+      throw new LDIFException(msgID, message, lastEntryLineNumber, true);
+    }
+    return colonPos;
+  }
+
+
+
+  /**
+   * Parse a single attribute value from a line of LDIF.
+   *
+   * @param lines
+   *          The current set of lines.
+   * @param line
+   *          The current line.
+   * @param entryDN
+   *          The DN of the entry being parsed.
+   * @param colonPos
+   *          The position of the separator colon in the line.
+   * @param attrName
+   *          The name of the attribute being parsed.
+   * @return The parsed attribute value.
+   * @throws LDIFException
+   *           If an error occurred when parsing the attribute value.
+   */
+  private ASN1OctetString parseSingleValue(
+      LinkedList<StringBuilder> lines,
+      StringBuilder line,
+      DN entryDN,
+      int colonPos,
+      String attrName) throws LDIFException {
+    assert debugEnter(CLASS_NAME, "parseSingleValue",
+        String.valueOf(lines),
+        String.valueOf(line),
+        String.valueOf(entryDN),
+        String.valueOf(colonPos),
+        String.valueOf(attrName));
+
+    // Look at the character immediately after the colon. If there is
+    // none, then assume an attribute with an empty value. If it is another
+    // colon, then the value must be base64-encoded. If it is a less-than
+    // sign, then assume that it is a URL. Otherwise, it is a regular value.
+    int length = line.length();
+    ASN1OctetString value;
+    if (colonPos == (length-1))
+    {
+      value = new ASN1OctetString();
+    }
+    else
+    {
+      char c = line.charAt(colonPos+1);
+      if (c == ':')
+      {
+        // The value is base64-encoded. Find the first non-blank
+        // character, take the rest of the line, and base64-decode it.
+        int pos = colonPos+2;
+        while ((pos < length) && (line.charAt(pos) == ' '))
+        {
+          pos++;
+        }
+
+        try
+        {
+          value = new ASN1OctetString(Base64.decode(line.substring(pos)));
+        }
+        catch (Exception e)
+        {
+          // The value did not have a valid base64-encoding.
+          assert debugException(CLASS_NAME, "readAttribute", e);
+
+          int    msgID   = MSGID_LDIF_COULD_NOT_BASE64_DECODE_ATTR;
+          String message = getMessage(msgID, String.valueOf(entryDN),
+                                      lastEntryLineNumber, line,
+                                      String.valueOf(e));
+          logToRejectWriter(lines, message);
+          throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
+        }
+      }
+      else if (c == '<')
+      {
+        // Find the first non-blank character, decode the rest of the
+        // line as a URL, and read its contents.
+        int pos = colonPos+2;
+        while ((pos < length) && (line.charAt(pos) == ' '))
+        {
+          pos++;
+        }
+
+        URL contentURL;
+        try
+        {
+          contentURL = new URL(line.substring(pos));
+        }
+        catch (Exception e)
+        {
+          // The URL was malformed or had an invalid protocol.
+          assert debugException(CLASS_NAME, "readAttribute", e);
+
+          int    msgID   = MSGID_LDIF_INVALID_URL;
+          String message = getMessage(msgID, String.valueOf(entryDN),
+                                      lastEntryLineNumber,
+                                      String.valueOf(attrName),
+                                      String.valueOf(e));
+          logToRejectWriter(lines, message);
+          throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
+        }
+
+
+        InputStream inputStream = null;
+        ByteArrayOutputStream outputStream = null;
+        try
+        {
+          outputStream = new ByteArrayOutputStream();
+          inputStream  = contentURL.openConnection().getInputStream();
+
+          int bytesRead;
+          while ((bytesRead = inputStream.read(buffer)) > 0)
+          {
+            outputStream.write(buffer, 0, bytesRead);
+          }
+
+          value = new ASN1OctetString(outputStream.toByteArray());
+        }
+        catch (Exception e)
+        {
+          // We were unable to read the contents of that URL for some
+          // reason.
+          assert debugException(CLASS_NAME, "readAttribute", e);
+
+          int msgID = MSGID_LDIF_URL_IO_ERROR;
+          String message = getMessage(msgID, String.valueOf(entryDN),
+                                      lastEntryLineNumber,
+                                      String.valueOf(attrName),
+                                      String.valueOf(contentURL),
+                                      String.valueOf(e));
+          logToRejectWriter(lines, message);
+          throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
+        }
+        finally
+        {
+          if (outputStream != null)
+          {
+            try
+            {
+              outputStream.close();
+            } catch (Exception e) {}
+          }
+
+          if (inputStream != null)
+          {
+            try
+            {
+              inputStream.close();
+            } catch (Exception e) {}
+          }
+        }
+      }
+      else
+      {
+        // The rest of the line should be the value. Skip over any
+        // spaces and take the rest of the line as the value.
+        int pos = colonPos+1;
+        while ((pos < length) && (line.charAt(pos) == ' '))
+        {
+          pos++;
+        }
+
+        value = new ASN1OctetString(line.substring(pos));
+      }
+    }
+    return value;
+  }
+
+
+
+  /**
+   * Log a message to the reject writer if one is configured.
+   *
+   * @param lines
+   *          The set of rejected lines.
+   * @param message
+   *          The associated error message.
+   */
+  private void logToRejectWriter(LinkedList<StringBuilder> lines,
+      String message) {
+    assert debugEnter(CLASS_NAME, "logToRejectWriter",
+        String.valueOf(lines),
+        String.valueOf(message));
+
+    BufferedWriter rejectWriter = importConfig.getRejectWriter();
+    if (rejectWriter != null)
+    {
+      try
+      {
+        rejectWriter.write("# ");
+        rejectWriter.write(message);
+        rejectWriter.newLine();
+        for (StringBuilder sb : lines)
+        {
+          rejectWriter.write(sb.toString());
+          rejectWriter.newLine();
+        }
+
+        rejectWriter.newLine();
+      }
+      catch (Exception e)
+      {
+        assert debugException(CLASS_NAME, "logToRejectWriter", e);
+      }
+    }
+  }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/ModifyChangeRecordEntry.java b/opendj-sdk/opends/src/server/org/opends/server/util/ModifyChangeRecordEntry.java
index f6ce024..e4abda7 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/ModifyChangeRecordEntry.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/util/ModifyChangeRecordEntry.java
@@ -28,20 +28,16 @@
 
 
 
+import static org.opends.server.loggers.Debug.debugConstructor;
 import static org.opends.server.loggers.Debug.debugEnter;
-import static org.opends.server.messages.MessageHandler.getMessage;
-import static org.opends.server.messages.UtilityMessages.*;
 
 import java.util.ArrayList;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 
-import org.opends.server.protocols.ldap.LDAPAttribute;
 import org.opends.server.protocols.ldap.LDAPModification;
-import org.opends.server.types.Attribute;
-import org.opends.server.types.AttributeValue;
 import org.opends.server.types.DN;
-import org.opends.server.types.ModificationType;
 
 
 
@@ -59,38 +55,47 @@
       "org.opends.server.util.ModifyChangeRecordEntry";
 
 
-  private LDIFReader reader;
+  /**
+   * The modifications for this change record.
+   */
+  private final List<LDAPModification> modifications;
 
-  private ArrayList<LDAPModification> modifications =
-      new ArrayList<LDAPModification>();
+
 
   /**
    * Creates a new entry with the provided information.
    *
    * @param  dn      The distinguished name for this entry.
-   * @param  reader  The LDIFReader instance used to read the entries.
+   * @param modifications The modifications for this change record.
    */
-  public ModifyChangeRecordEntry(DN dn, LDIFReader reader)
+  public ModifyChangeRecordEntry(DN dn,
+      Collection<LDAPModification> modifications)
   {
-    super(dn, reader);
+    super(dn);
 
-    this.reader = reader;
+    assert debugConstructor(CLASS_NAME, String.valueOf(dn),
+        String.valueOf(modifications));
+
+    this.modifications = new ArrayList<LDAPModification>(modifications);
   }
 
 
   /**
-   * Get the list of modifications from the attributes.
+   * Get the list of modifications.
+   * <p>
+   * The returned list is read-only.
    *
-   * @return the list of modifications.
+   * @return Returns the unmodifiable list of modifications.
    */
-  public ArrayList<LDAPModification> getModifications()
+  public List<LDAPModification> getModifications()
   {
     assert debugEnter(CLASS_NAME, "getModifications");
 
-    return modifications;
-
+    return Collections.unmodifiableList(modifications);
   }
 
+
+
   /**
    * Retrieves the name of the change operation type.
    *
@@ -102,84 +107,5 @@
 
     return ChangeOperationType.MODIFY;
   }
-
-  /**
-   * Parse the lines and populate the internal structures.
-   *
-   * @param lines         The lines to parse.
-   * @param lineNumber    The current line number.
-   *
-   * @exception LDIFException if there is an error during parsing.
-   */
-
-  public void parse(LinkedList<StringBuilder> lines, long lineNumber)
-      throws LDIFException
-  {
-    assert debugEnter(CLASS_NAME, "parse", String.valueOf(lines),
-          String.valueOf(lineNumber));
-
-    while(!lines.isEmpty())
-    {
-      ModificationType modType = null;
-
-      StringBuilder line = lines.remove();
-      Attribute attr =
-           reader.readSingleValueAttribute(lines, line, getDN(), null);
-      String name = attr.getName();
-      LinkedHashSet<AttributeValue> values = attr.getValues();
-      // Only 1 entry should be present
-      if(values.size() != 1)
-      {
-          int msgID = MSGID_LDIF_INVALID_MODIFY_ATTRIBUTE_VAL;
-          String message = getMessage(msgID, name);
-          throw new LDIFException(msgID, message, lineNumber, true);
-      }
-
-      // Get the attribute description
-      String attrDescr = values.iterator().next().getStringValue();
-
-      String lowerName = name.toLowerCase();
-      if(lowerName.equals("add"))
-      {
-        modType = ModificationType.ADD;
-      } else if(lowerName.equals("delete"))
-      {
-        modType = ModificationType.DELETE;
-      } else if(lowerName.equals("replace"))
-      {
-        modType = ModificationType.REPLACE;
-      } else if(lowerName.equals("increment"))
-      {
-        modType = ModificationType.INCREMENT;
-      } else
-      {
-        // Invalid attribute name.
-        int msgID = MSGID_LDIF_INVALID_MODIFY_ATTRIBUTE;
-        String message = getMessage(msgID, name,
-              "add, delete, replace, increment");
-        throw new LDIFException(msgID, message, lineNumber, true);
-      }
-
-      // Now go through the rest of the attributes till the "-" line is reached.
-      Attribute modAttr = LDIFReader.parseAttrDescription(attrDescr);
-      while (! lines.isEmpty())
-      {
-        line = lines.remove();
-        if(line.toString().equals("-"))
-        {
-          break;
-        }
-        Attribute a =
-             reader.readSingleValueAttribute(lines, line, getDN(), attrDescr);
-        modAttr.getValues().addAll(a.getValues());
-      }
-
-      LDAPAttribute ldapAttr = new LDAPAttribute(modAttr);
-      LDAPModification mod = new LDAPModification(modType, ldapAttr);
-      modifications.add(mod);
-    }
-  }
-
-
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/ModifyDNChangeRecordEntry.java b/opendj-sdk/opends/src/server/org/opends/server/util/ModifyDNChangeRecordEntry.java
index 85c0726..e065452 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/ModifyDNChangeRecordEntry.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/util/ModifyDNChangeRecordEntry.java
@@ -30,16 +30,7 @@
 
 import static org.opends.server.loggers.Debug.debugConstructor;
 import static org.opends.server.loggers.Debug.debugEnter;
-import static org.opends.server.loggers.Debug.debugException;
-import static org.opends.server.messages.MessageHandler.getMessage;
-import static org.opends.server.messages.UtilityMessages.*;
 
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-
-import org.opends.server.core.DirectoryException;
-import org.opends.server.types.Attribute;
-import org.opends.server.types.AttributeValue;
 import org.opends.server.types.DN;
 import org.opends.server.types.RDN;
 
@@ -58,32 +49,40 @@
   private static final String CLASS_NAME =
       "org.opends.server.util.ModifyDNChangeRecordEntry";
 
-  private LDIFReader reader;
-
   // The new RDN.
-  private RDN newRDN = null;
+  private final RDN newRDN;
 
   // The new superior DN.
-  private DN newSuperiorDN = null;
+  private final DN newSuperiorDN;
 
   // Delete the old RDN?
-  private boolean deleteOldRDN = false;
+  private final boolean deleteOldRDN;
 
 
   /**
    * Creates a new entry with the provided information.
    *
-   * @param  dn      The distinguished name for this entry.
-   * @param  reader  The LDIFReader instance used to read the entries.
+   * @param dn
+   *          The distinguished name for this entry.
+   * @param newSuperiorDN
+   *          The new superior DN.
+   * @param newRDN
+   *          The new RDN.
+   * @param deleteOldRDN
+   *          Delete the old RDN?
    */
-  public ModifyDNChangeRecordEntry(DN dn, LDIFReader reader)
+  public ModifyDNChangeRecordEntry(DN dn, DN newSuperiorDN,
+      RDN newRDN, boolean deleteOldRDN)
   {
-    super(dn, reader);
+    super(dn);
     assert debugConstructor(CLASS_NAME, String.valueOf(dn),
-                            String.valueOf(reader));
+                            String.valueOf(newSuperiorDN),
+                            String.valueOf(newRDN),
+                            String.valueOf(deleteOldRDN));
 
-    this.reader = reader;
-
+    this.newSuperiorDN = newSuperiorDN;
+    this.newRDN = newRDN;
+    this.deleteOldRDN = deleteOldRDN;
   }
 
 
@@ -137,147 +136,5 @@
 
     return ChangeOperationType.MODIFY_DN;
   }
-
-  /**
-   * Parse the lines and populate the internal structures.
-   *
-   * @param lines        The lines to parse.
-   * @param lineNumber   The current line number.
-   *
-   * @exception LDIFException if there is an error during parsing.
-   */
-
-  public void parse(LinkedList<StringBuilder> lines, long lineNumber)
-         throws LDIFException
-  {
-    assert debugEnter(CLASS_NAME, "parse", String.valueOf(lines),
-                      String.valueOf(lineNumber));
-
-    if(lines.isEmpty())
-    {
-      int msgID = MSGID_LDIF_NO_MOD_DN_ATTRIBUTES;
-      String message = getMessage(msgID);
-      throw new LDIFException(msgID, message, lineNumber, true);
-    }
-
-    StringBuilder line = lines.remove();
-    String rdnStr = getAttributeValue(lines, line, "newrdn");
-
-    try
-    {
-      newRDN = RDN.decode(rdnStr);
-    } catch (DirectoryException de)
-    {
-      assert debugException(CLASS_NAME, "parse", de);
-      int    msgID   = MSGID_LDIF_INVALID_DN;
-      String message = getMessage(msgID, lineNumber, line.toString(),
-                                  de.getErrorMessage());
-      throw new LDIFException(msgID, message, lineNumber, true);
-    } catch (Exception e)
-    {
-      assert debugException(CLASS_NAME, "parse", e);
-      int    msgID   = MSGID_LDIF_INVALID_DN;
-      String message = getMessage(msgID, lineNumber, line.toString(),
-                                  e.getMessage());
-      throw new LDIFException(msgID, message, lineNumber, true);
-    }
-
-    if(lines.isEmpty())
-    {
-      int msgID = MSGID_LDIF_NO_DELETE_OLDRDN_ATTRIBUTE;
-      String message = getMessage(msgID);
-      throw new LDIFException(msgID, message, lineNumber, true);
-    }
-    lineNumber++;
-
-    line = lines.remove();
-    String delStr = getAttributeValue(lines, line, "deleteoldrdn");
-
-    if(delStr.equalsIgnoreCase("false") ||
-    delStr.equalsIgnoreCase("no") ||
-    delStr.equalsIgnoreCase("0"))
-    {
-      deleteOldRDN = false;
-    } else if(delStr.equalsIgnoreCase("true") ||
-        delStr.equalsIgnoreCase("yes") ||
-        delStr.equalsIgnoreCase("1"))
-    {
-      deleteOldRDN = true;
-    } else
-    {
-      int msgID = MSGID_LDIF_INVALID_DELETE_OLDRDN_ATTRIBUTE;
-      String message = getMessage(msgID, delStr);
-      throw new LDIFException(msgID, message, lineNumber, true);
-    }
-
-    if(!lines.isEmpty())
-    {
-      lineNumber++;
-
-      line = lines.remove();
-
-      String dnStr = getAttributeValue(lines, line, "newsuperior");
-      try
-      {
-        newSuperiorDN = DN.decode(dnStr);
-      } catch (DirectoryException de)
-      {
-        assert debugException(CLASS_NAME, "parse", de);
-        int    msgID   = MSGID_LDIF_INVALID_DN;
-        String message = getMessage(msgID, lineNumber, line.toString(),
-                                    de.getErrorMessage());
-        throw new LDIFException(msgID, message, lineNumber, true);
-      } catch (Exception e)
-      {
-        assert debugException(CLASS_NAME, "parse", e);
-        int    msgID   = MSGID_LDIF_INVALID_DN;
-        String message = getMessage(msgID, lineNumber, line.toString(),
-                                    e.getMessage());
-        throw new LDIFException(msgID, message, lineNumber, true);
-      }
-
-    }
-
-  }
-
-  /**
-   * Return the string value for the specified attribute name which
-   * only has one value.
-   *
-   * @param  lines          The set of lines for this change record entry.
-   * @param  line           The line currently being examined.
-   * @param  attributeName  The attribute name
-   *
-   * @return the string value for the attribute name.
-   *
-   * @throws  LDIFException  If a problem occurs while attempting to determine
-   *                         the attribute value.
-   */
-
-  private String getAttributeValue(LinkedList<StringBuilder> lines,
-                                   StringBuilder line,
-                                   String attributeName) throws LDIFException
-  {
-    assert debugEnter(CLASS_NAME, "getAttributeValue", String.valueOf(lines),
-                      String.valueOf(line), String.valueOf(attributeName));
-
-    Attribute attr =
-      reader.readSingleValueAttribute(lines, line, getDN(), attributeName);
-    LinkedHashSet<AttributeValue> values = attr.getValues();
-
-    // Only 1 entry should be present
-    if(values.size() != 1)
-    {
-      int msgID = MSGID_LDIF_INVALID_MODIFY_ATTRIBUTE_VAL;
-      String message = getMessage(msgID, attributeName);
-      throw new LDIFException(msgID, message);
-    }
-
-    // Get the attribute value
-
-    Object[] vals = values.toArray();
-    return (((AttributeValue)vals[0]).getStringValue());
-  }
-
 }
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestAddChangeRecordEntry.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestAddChangeRecordEntry.java
index 0d0663d..00b0952 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestAddChangeRecordEntry.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestAddChangeRecordEntry.java
@@ -26,12 +26,16 @@
  */
 package org.opends.server.util;
 
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import org.opends.server.TestCaseUtils;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
 import org.opends.server.types.DN;
-import org.opends.server.types.LDIFImportConfig;
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -45,8 +49,11 @@
  * been overridden.
  */
 public final class TestAddChangeRecordEntry extends UtilTestCase {
-  // An empty LDIF reader.
-  private LDIFReader emptyReader;
+  // Set of attributes.
+  private Map<AttributeType, List<Attribute>> attributes;
+
+  // The attribute being added.
+  private Attribute attribute;
 
   /**
    * Once-only initialization.
@@ -60,9 +67,11 @@
     // start the server.
     TestCaseUtils.startServer();
 
-    InputStream stream = new ByteArrayInputStream(new byte[0]);
-    LDIFImportConfig config = new LDIFImportConfig(stream);
-    emptyReader = new LDIFReader(config);
+    attributes = new HashMap<AttributeType, List<Attribute>>();
+    attribute = new Attribute("cn", "hello world");
+    ArrayList<Attribute> alist = new ArrayList<Attribute>(1);
+    alist.add(attribute);
+    attributes.put(attribute.getAttributeType(), alist);
   }
 
   /**
@@ -73,7 +82,7 @@
    */
   @Test
   public void testConstructorNullDN() throws Exception {
-    AddChangeRecordEntry entry = new AddChangeRecordEntry(null, emptyReader);
+    AddChangeRecordEntry entry = new AddChangeRecordEntry(null, attributes);
 
     Assert.assertEquals(entry.getDN(), new DN());
   }
@@ -87,7 +96,7 @@
   @Test
   public void testConstructorEmptyDN() throws Exception {
     AddChangeRecordEntry entry = new AddChangeRecordEntry(new DN(),
-        emptyReader);
+        attributes);
 
     Assert.assertEquals(entry.getDN(), new DN());
   }
@@ -104,7 +113,7 @@
     DN testDN2 = DN.decode("dc=hello, dc=world");
 
     AddChangeRecordEntry entry = new AddChangeRecordEntry(testDN1,
-        emptyReader);
+        attributes);
 
     Assert.assertEquals(entry.getDN(), testDN2);
   }
@@ -117,28 +126,41 @@
    */
   @Test
   public void testChangeOperationType() throws Exception {
-    AddChangeRecordEntry entry = new AddChangeRecordEntry(null, emptyReader);
+    AddChangeRecordEntry entry = new AddChangeRecordEntry(null, attributes);
 
     Assert.assertEquals(entry.getChangeOperationType(),
         ChangeOperationType.ADD);
   }
 
   /**
-   * Tests parse and getAttributes methods.
-   * <p>
-   * Due to tight coupling between the
-   * {@link AddChangeRecordEntry#parse(java.util.LinkedList, long)}
-   * method and the {@link LDIFReader} class it is not easy to test the
-   * {@link AddChangeRecordEntry#getAttributes()} method. Instead, we'll
-   * test that in the {@link LDIFReader} test suite.
+   * Tests getAttributes method for empty modifications.
    * 
    * @throws Exception
    *           If the test failed unexpectedly.
    */
-  @Test(enabled = false)
-  public void testGetAttributes() throws Exception {
-    // FIXME: fix tight-coupling between parse() and LDIFReader.
-    Assert.assertTrue(false);
+  @Test
+  public void testGetAttributesEmpty() throws Exception {
+    Map<AttributeType, List<Attribute>> empty = Collections.emptyMap();
+    AddChangeRecordEntry entry = new AddChangeRecordEntry(null, empty);
+
+    List<Attribute> attrs = entry.getAttributes();
+    Assert.assertEquals(attrs.size(), 0);
   }
 
+  /**
+   * Tests getAttributes method for non-empty modifications.
+   * 
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testGetAttributesNonEmpty() throws Exception {
+    AddChangeRecordEntry entry = new AddChangeRecordEntry(null, attributes);
+
+    List<Attribute> attrs = entry.getAttributes();
+    Assert.assertEquals(attrs.size(), 1);
+
+    Attribute first = attrs.get(0);
+    Assert.assertEquals(first, attribute);
+  }
 }
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestChangeRecordEntry.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestChangeRecordEntry.java
index 8c4d3ff..27b43e8 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestChangeRecordEntry.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestChangeRecordEntry.java
@@ -26,8 +26,6 @@
  */
 package org.opends.server.util;
 
-import java.util.LinkedList;
-
 import org.opends.server.TestCaseUtils;
 import org.opends.server.types.DN;
 import org.testng.Assert;
@@ -65,15 +63,6 @@
       // Will not use.
       return null;
     }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void parse(LinkedList<StringBuilder> lines, long lineNumber)
-        throws LDIFException {
-      // Will not use.
-    }
   }
 
   /**
@@ -88,7 +77,7 @@
     // start the server.
     TestCaseUtils.startServer();
   }
-  
+
   /**
    * Tests the constructor with null DN.
    * 
@@ -130,23 +119,4 @@
 
     Assert.assertEquals(entry.getDN(), testDN2);
   }
-
-  /**
-   * Tests the set DN method.
-   * 
-   * @throws Exception
-   *           If the test failed unexpectedly.
-   */
-  @Test(dependsOnMethods = { "testConstructorNonNullDN" })
-  public void testSetDN() throws Exception {
-    DN testDN1 = DN.decode("dc=hello, dc=world");
-    DN testDN2 = DN.decode("dc=goodbye, dc=world");
-    DN testDN3 = DN.decode("dc=goodbye, dc=world");
-
-    MyChangeRecordEntry entry = new MyChangeRecordEntry(testDN1);
-
-    entry.setDN(testDN2);
-
-    Assert.assertEquals(entry.getDN(), testDN3);
-  }
 }
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestDeleteChangeRecordEntry.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestDeleteChangeRecordEntry.java
index 9bf751d..923ff39 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestDeleteChangeRecordEntry.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestDeleteChangeRecordEntry.java
@@ -26,12 +26,8 @@
  */
 package org.opends.server.util;
 
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-
 import org.opends.server.TestCaseUtils;
 import org.opends.server.types.DN;
-import org.opends.server.types.LDIFImportConfig;
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -45,9 +41,6 @@
  * been overridden.
  */
 public final class TestDeleteChangeRecordEntry extends UtilTestCase {
-  // An empty LDIF reader.
-  private LDIFReader emptyReader;
-
   /**
    * Once-only initialization.
    * 
@@ -59,10 +52,6 @@
     // This test suite depends on having the schema available, so we'll
     // start the server.
     TestCaseUtils.startServer();
-
-    InputStream stream = new ByteArrayInputStream(new byte[0]);
-    LDIFImportConfig config = new LDIFImportConfig(stream);
-    emptyReader = new LDIFReader(config);
   }
 
   /**
@@ -73,8 +62,7 @@
    */
   @Test
   public void testConstructorNullDN() throws Exception {
-    DeleteChangeRecordEntry entry = new DeleteChangeRecordEntry(null,
-        emptyReader);
+    DeleteChangeRecordEntry entry = new DeleteChangeRecordEntry(null);
 
     Assert.assertEquals(entry.getDN(), new DN());
   }
@@ -87,8 +75,7 @@
    */
   @Test
   public void testConstructorEmptyDN() throws Exception {
-    DeleteChangeRecordEntry entry = new DeleteChangeRecordEntry(new DN(),
-        emptyReader);
+    DeleteChangeRecordEntry entry = new DeleteChangeRecordEntry(new DN());
 
     Assert.assertEquals(entry.getDN(), new DN());
   }
@@ -104,8 +91,7 @@
     DN testDN1 = DN.decode("dc=hello, dc=world");
     DN testDN2 = DN.decode("dc=hello, dc=world");
 
-    DeleteChangeRecordEntry entry = new DeleteChangeRecordEntry(testDN1,
-        emptyReader);
+    DeleteChangeRecordEntry entry = new DeleteChangeRecordEntry(testDN1);
 
     Assert.assertEquals(entry.getDN(), testDN2);
   }
@@ -118,28 +104,10 @@
    */
   @Test
   public void testChangeOperationType() throws Exception {
-    DeleteChangeRecordEntry entry = new DeleteChangeRecordEntry(null,
-        emptyReader);
+    DeleteChangeRecordEntry entry = new DeleteChangeRecordEntry(null);
 
     Assert.assertEquals(entry.getChangeOperationType(),
         ChangeOperationType.DELETE);
   }
 
-  /**
-   * Tests parse method.
-   * <p>
-   * Due to tight coupling between the
-   * {@link DeleteChangeRecordEntry#parse(java.util.LinkedList, long)}
-   * method and the {@link LDIFReader} class we'll test this method in
-   * the {@link LDIFReader} test suite.
-   * 
-   * @throws Exception
-   *           If the test failed unexpectedly.
-   */
-  @Test(enabled = false)
-  public void testParse() throws Exception {
-    // FIXME: fix tight-coupling between parse() and LDIFReader.
-    Assert.assertTrue(false);
-  }
-
 }
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestLDIFReader.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestLDIFReader.java
new file mode 100644
index 0000000..f2e2844
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestLDIFReader.java
@@ -0,0 +1,980 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2006 Sun Microsystems, Inc.
+ */
+package org.opends.server.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.protocols.ldap.LDAPModification;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.opends.server.types.LDIFImportConfig;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+import org.opends.server.types.ObjectClass;
+import org.opends.server.types.RDN;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * This class defines a set of tests for the
+ * {@link org.opends.server.util.LDIFReader} class.
+ */
+public final class TestLDIFReader extends UtilTestCase {
+  // Top object class.
+  private ObjectClass OC_TOP;
+
+  // Person object class.
+  private ObjectClass OC_PERSON;
+
+  // Object class attribute type.
+  private AttributeType AT_OC;
+
+  // Common name attribute type.
+  private AttributeType AT_CN;
+
+  // Surname attribute type.
+  private AttributeType AT_SN;
+
+  // Description attribute type.
+  private AttributeType AT_DESCR;
+
+  // Telephone number attribute type.
+  private AttributeType AT_TELN;
+
+  // Temporary file containing an attribute value.
+  private File TEMP_FILE = null;
+
+  // Temporary file content.
+  private static final String TEMP_FILE_STRING = "hello world";
+
+  // Temporary file LDIF.
+  private static final String TEMP_FILE_LDIF = "dn: cn=john smith, dc=com\n"
+      + "changetype: add\n" + "objectClass: top\n"
+      + "objectClass: person\n" + "cn: john\n" + "sn: smith\n"
+      + "description:< file:///";
+
+
+  /**
+   * String of valid LDIF change records.
+   *
+   * Take from example 6 in the LDIF RFC + a couple of additions.
+   */
+  private static final String VALID_LDIF = "version: 1\n"
+      + "# Add a new entry\n"
+      + "dn: cn=Fiona Jensen, ou=Marketing, dc=airius, dc=com\n"
+      + "changetype: add\n"
+      + "objectclass: top\n"
+      + "objectclass: person\n"
+      + "objectclass: organizationalPerson\n"
+      + "cn: Fiona Jensen\n"
+      + "sn: Jensen\n"
+      + "uid: fiona\n"
+      + "telephonenumber: +1 408 555 1212\n"
+      + "\n"
+      + "# Delete an existing entry\n"
+      + "dn: cn=Robert Jensen, ou=Marketing, dc=airius, dc=com\n"
+      + "changetype: delete\n"
+      + "\n"
+      + "# Modify an entry's relative distinguished name\n"
+      + "dn: cn=Paul Jensen, ou=Product Development, dc=airius, dc=com\n"
+      + "changetype: modrdn\n"
+      + "newrdn: cn=Paula Jensen\n"
+      + "deleteoldrdn: 1\n"
+      + "\n"
+      + "# Rename an entry and move all of its children to a new location in\n"
+      + "# the directory tree (only implemented by LDAPv3 servers).\n"
+      + "dn: ou=PD Accountants, ou=Product Development, dc=airius, dc=com\n"
+      + "changetype: modrdn\n"
+      + "newrdn: ou=Product Development Accountants\n"
+      + "deleteoldrdn: 0\n"
+      + "newsuperior: ou=Accounting, dc=airius, dc=com\n"
+      + "\n"
+      + "# Modify an entry: add an additional value to the postaladdress\n"
+      + "# attribute, completely delete the description attribute, replace\n"
+      + "# the telephonenumber attribute with two values, and delete a specific\n"
+      + "# value from the facsimiletelephonenumber attribute\n"
+      + "dn: cn=Paula Jensen, ou=Product Development, dc=airius, dc=com\n"
+      + "changetype: modify\n"
+      + "add: postaladdress\n"
+      + "postaladdress: 123 Anystreet $ Sunnyvale, CA $ 94086\n"
+      + "-\n"
+      + "delete: description\n"
+      + "-\n"
+      + "replace: telephonenumber\n"
+      + "telephonenumber: +1 408 555 1234\n"
+      + "telephonenumber: +1 408 555 5678\n"
+      + "-\n"
+      + "delete: facsimiletelephonenumber\n"
+      + "facsimiletelephonenumber: +1 408 555 9876\n"
+      + "-\n"
+      + "\n"
+      + "# Modify an entry: replace the postaladdress attribute with an empty\n"
+      + "# set of values (which will cause the attribute to be removed), and\n"
+      + "# delete the entire description attribute. Note that the first will\n"
+      + "# always succeed, while the second will only succeed if at least\n"
+      + "# one value for the description attribute is present.\n"
+      + "dn: cn=Ingrid Jensen, ou=Product Support, dc=airius, dc=com\n"
+      + "changetype: modify\n"
+      + "replace: postaladdress\n"
+      + "-\n"
+      + "delete: description\n"
+      + "-\n"
+      + "\n"
+      + "# Modify rootDSE.\n"
+      + "dn: \n"
+      + "changetype: modify\n"
+      + "delete: description\n"
+      + "-\n"
+      + "\n"
+      + "# Modify base64 DN.\n"
+      + "dn:: dWlkPXJvZ2FzYXdhcmEsb3U95Za25qWt6YOoLG89QWlyaXVz\n"
+      + "# dn:: uid=<uid>,ou=<JapaneseOU>,o=Airius\n"
+      + "changetype:: bW9kaWZ5\n"
+      + "delete: description\n"
+      + "-\n"
+      + "\n";
+
+  /**
+   * Once-only initialization.
+   *
+   * @throws Exception
+   *           If an unexpected error occurred.
+   */
+  @BeforeClass
+  public void setUp() throws Exception {
+    // This test suite depends on having the schema available, so we'll
+    // start the server.
+    TestCaseUtils.startServer();
+
+    // Initialize schema bits.
+    OC_TOP = DirectoryServer.getObjectClass("top");
+    OC_PERSON = DirectoryServer.getObjectClass("person");
+
+    AT_OC = DirectoryServer.getObjectClassAttributeType();
+    AT_CN = DirectoryServer.getAttributeType("cn");
+    AT_SN = DirectoryServer.getAttributeType("sn");
+    AT_DESCR = DirectoryServer.getAttributeType("description");
+    AT_TELN = DirectoryServer.getAttributeType("telephonenumber");
+
+    // Create a temporary file containing an attribute value.
+    TEMP_FILE = File.createTempFile("tmp", "txt");
+    OutputStream out = null;
+    try {
+      out = new FileOutputStream(TEMP_FILE);
+      out.write(TEMP_FILE_STRING.getBytes("UTF-8"));
+    } finally {
+      if (out != null) {
+        out.close();
+      }
+    }
+  }
+
+  /**
+   * Once-only tear-down.
+   *
+   * @throws Exception
+   *           If an unexpected error occurred.
+   */
+  @AfterClass
+  public void tearDown() throws Exception {
+    if (TEMP_FILE != null) {
+      TEMP_FILE.delete();
+    }
+  }
+
+  /**
+   * Check the initial state of an LDIF reader.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testInitialState() throws Exception {
+    LDIFReader reader = createLDIFReader("");
+
+    try {
+      Assert.assertEquals(reader.getEntriesIgnored(), 0);
+      Assert.assertEquals(reader.getEntriesRead(), 0);
+      Assert.assertEquals(reader.getEntriesRejected(), 0);
+      Assert.assertEquals(reader.getLastEntryLineNumber(), -1);
+    } finally {
+      reader.close();
+    }
+  }
+
+  /**
+   * Attempt to read an entry from an empty LDIF stream.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testReadEntryEmptyStream() throws Exception {
+    LDIFReader reader = createLDIFReader("");
+
+    try {
+      Entry entry = reader.readEntry();
+
+      Assert.assertNull(entry);
+
+      Assert.assertEquals(reader.getEntriesIgnored(), 0);
+      Assert.assertEquals(reader.getEntriesRead(), 0);
+      Assert.assertEquals(reader.getEntriesRejected(), 0);
+      Assert.assertEquals(reader.getLastEntryLineNumber(), -1);
+    } finally {
+      reader.close();
+    }
+  }
+
+  /**
+   * Attempt to read an entry from an empty LDIF stream containing just
+   * the LDIF version.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dependsOnMethods = { "testReadEntryEmptyStream" })
+  public void testReadEntryEmptyStreamVersion() throws Exception {
+    LDIFReader reader = createLDIFReader("version: 1\n");
+
+    try {
+      Entry entry = reader.readEntry();
+
+      Assert.assertNull(entry);
+
+      Assert.assertEquals(reader.getEntriesIgnored(), 0);
+      Assert.assertEquals(reader.getEntriesRead(), 0);
+      Assert.assertEquals(reader.getEntriesRejected(), 0);
+      Assert.assertEquals(reader.getLastEntryLineNumber(), 1);
+    } finally {
+      reader.close();
+    }
+  }
+
+  /**
+   * Attempt to read a change record from an empty LDIF stream.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testChangeRecordEmptyStream() throws Exception {
+    LDIFReader reader = createLDIFReader("");
+    try {
+      ChangeRecordEntry change = reader.readChangeRecord(true);
+
+      Assert.assertNull(change);
+
+      Assert.assertEquals(reader.getEntriesIgnored(), 0);
+      Assert.assertEquals(reader.getEntriesRead(), 0);
+      Assert.assertEquals(reader.getEntriesRejected(), 0);
+      Assert.assertEquals(reader.getLastEntryLineNumber(), -1);
+    } finally {
+      reader.close();
+    }
+  }
+
+  /**
+   * Attempt to read a single entry.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dependsOnMethods = { "testReadEntryEmptyStream" })
+  public void testReadEntrySingle() throws Exception {
+    final String ldifString = "dn: cn=john, dc=foo, dc=com\n"
+        + "objectClass: top\n" + "objectClass: person\n" + "cn: john\n"
+        + "sn: smith\n";
+
+    LDIFReader reader = createLDIFReader(ldifString);
+
+    try {
+      Entry entry = reader.readEntry();
+      Assert.assertNotNull(entry);
+
+      Assert.assertEquals(entry.getDN(), DN
+          .decode("cn=john, dc=foo, dc=com"));
+      Assert.assertTrue(entry.hasObjectClass(OC_TOP));
+      Assert.assertTrue(entry.hasObjectClass(OC_PERSON));
+      Assert.assertTrue(entry.hasValue(AT_CN, null, new AttributeValue(
+          AT_CN, "john")));
+      Assert.assertTrue(entry.hasValue(AT_SN, null, new AttributeValue(
+          AT_SN, "smith")));
+
+      Assert.assertNull(reader.readEntry());
+
+      Assert.assertEquals(reader.getEntriesIgnored(), 0);
+      Assert.assertEquals(reader.getEntriesRead(), 1);
+      Assert.assertEquals(reader.getEntriesRejected(), 0);
+      Assert.assertEquals(reader.getLastEntryLineNumber(), 1);
+    } finally {
+      reader.close();
+    }
+  }
+
+  /**
+   * Attempt to read an entry containing a folded line.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dependsOnMethods = { "testReadEntrySingle" })
+  public void testReadEntryFoldedLine() throws Exception {
+    final String ldifString = "dn: cn=john, dc=foo, dc=com\n"
+        + "objectClass: top\n" + "objectClass: person\n" + "cn: john\n"
+        + "sn: smith\n" + "description: once upon a time\n"
+        + "  in the west\n";
+
+    LDIFReader reader = createLDIFReader(ldifString);
+
+    try {
+      Entry entry = reader.readEntry();
+      Assert.assertNotNull(entry);
+
+      Assert.assertTrue(entry.hasValue(AT_DESCR, null, new AttributeValue(
+          AT_DESCR, "once upon a time in the west")));
+    } finally {
+      reader.close();
+    }
+  }
+
+  /**
+   * Attempt to read an entry containing a base64 line.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dependsOnMethods = { "testReadEntrySingle" })
+  public void testReadEntryBase64() throws Exception {
+    final String ldifString = "dn: cn=john, dc=foo, dc=com\n"
+        + "objectClass: top\n" + "objectClass: person\n" + "cn: john\n"
+        + "sn: smith\n"
+        + "description:: b25jZSB1cG9uIGEgdGltZSBpbiB0aGUgd2VzdA==\n";
+
+    LDIFReader reader = createLDIFReader(ldifString);
+
+    try {
+      Entry entry = reader.readEntry();
+      Assert.assertNotNull(entry);
+
+      Assert.assertTrue(entry.hasValue(AT_DESCR, null, new AttributeValue(
+          AT_DESCR, "once upon a time in the west")));
+    } finally {
+      reader.close();
+    }
+  }
+
+  /**
+   * Attempt to read multiple entries.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dependsOnMethods = { "testReadEntrySingle" })
+  public void testReadEntryMultiple() throws Exception {
+    final String ldifString = "dn: cn=john, dc=foo, dc=com\n"
+        + "objectClass: top\n" + "objectClass: person\n" + "cn: john\n"
+        + "sn: smith\n" + "\n" + "dn: cn=anne, dc=foo, dc=com\n"
+        + "objectClass: top\n" + "objectClass: person\n" + "cn: anne\n"
+        + "sn: other\n" + "\n";
+
+    LDIFReader reader = createLDIFReader(ldifString);
+
+    try {
+      reader.readEntry();
+      Entry entry = reader.readEntry();
+
+      Assert.assertNotNull(entry);
+
+      Assert.assertEquals(entry.getDN(), DN
+          .decode("cn=anne, dc=foo, dc=com"));
+      Assert.assertTrue(entry.hasObjectClass(OC_TOP));
+      Assert.assertTrue(entry.hasObjectClass(OC_PERSON));
+      Assert.assertTrue(entry.hasValue(AT_CN, null, new AttributeValue(
+          AT_CN, "anne")));
+      Assert.assertTrue(entry.hasValue(AT_SN, null, new AttributeValue(
+          AT_SN, "other")));
+
+      Assert.assertNull(reader.readEntry());
+
+      Assert.assertEquals(reader.getEntriesIgnored(), 0);
+      Assert.assertEquals(reader.getEntriesRead(), 2);
+      Assert.assertEquals(reader.getEntriesRejected(), 0);
+      Assert.assertEquals(reader.getLastEntryLineNumber(), 7);
+    } finally {
+      reader.close();
+    }
+  }
+
+  /**
+   * Attempt to read multiple changes.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dependsOnMethods = { "testChangeRecordEmptyStream" })
+  public void testReadChangeMultiple() throws Exception {
+    LDIFReader reader = createLDIFReader(VALID_LDIF);
+
+    try {
+      ChangeRecordEntry change;
+      AddChangeRecordEntry add;
+      DeleteChangeRecordEntry delete;
+      ModifyChangeRecordEntry modify;
+      ModifyDNChangeRecordEntry modifyDN;
+      DN dn;
+      RDN rdn;
+      Iterator<LDAPModification> i;
+      Modification mod;
+      Attribute attr;
+      LinkedHashSet<AttributeValue> values;
+
+      // Change record #1.
+      change = reader.readChangeRecord(false);
+      Assert.assertTrue(change instanceof AddChangeRecordEntry);
+      add = (AddChangeRecordEntry) change;
+
+      dn = DN.decode("cn=Fiona Jensen, ou=Marketing, dc=airius, dc=com");
+      Assert.assertEquals(add.getDN(), dn);
+
+      List<Attribute> attrs = new ArrayList<Attribute>();
+
+      values = new LinkedHashSet<AttributeValue>();
+      values.add(new AttributeValue(AT_OC, "top"));
+      values.add(new AttributeValue(AT_OC, "person"));
+      values.add(new AttributeValue(AT_OC, "organizationalPerson"));
+
+      attrs.add(new Attribute(AT_OC, "objectclass", values));
+      attrs.add(new Attribute("cn", "Fiona Jensen"));
+      attrs.add(new Attribute("sn", "Jensen"));
+      attrs.add(new Attribute("uid", "fiona"));
+      attrs.add(new Attribute("telephonenumber", "+1 408 555 1212"));
+      Assert.assertTrue(add.getAttributes().containsAll(attrs));
+
+      // Change record #2.
+      change = reader.readChangeRecord(false);
+      Assert.assertTrue(change instanceof DeleteChangeRecordEntry);
+      delete = (DeleteChangeRecordEntry) change;
+
+      dn = DN.decode("cn=Robert Jensen, ou=Marketing, dc=airius, dc=com");
+      Assert.assertEquals(delete.getDN(), dn);
+
+      // Change record #3.
+      change = reader.readChangeRecord(false);
+      Assert.assertTrue(change instanceof ModifyDNChangeRecordEntry);
+      modifyDN = (ModifyDNChangeRecordEntry) change;
+
+      dn = DN
+          .decode("cn=Paul Jensen, ou=Product Development, dc=airius, dc=com");
+      Assert.assertEquals(modifyDN.getDN(), dn);
+
+      rdn = RDN.decode("cn=paula jensen");
+      Assert.assertEquals(modifyDN.getNewRDN(), rdn);
+      Assert.assertNull(modifyDN.getNewSuperiorDN());
+      Assert.assertTrue(modifyDN.deleteOldRDN());
+
+      // Change record #4.
+      change = reader.readChangeRecord(false);
+      Assert.assertTrue(change instanceof ModifyDNChangeRecordEntry);
+      modifyDN = (ModifyDNChangeRecordEntry) change;
+
+      dn = DN
+          .decode("ou=PD Accountants, ou=Product Development, dc=airius, dc=com");
+      Assert.assertEquals(modifyDN.getDN(), dn);
+
+      rdn = RDN.decode("ou=Product Development Accountants");
+      Assert.assertEquals(modifyDN.getNewRDN(), rdn);
+      dn = DN.decode("ou=Accounting, dc=airius, dc=com");
+      Assert.assertEquals(modifyDN.getNewSuperiorDN(), dn);
+      Assert.assertFalse(modifyDN.deleteOldRDN());
+
+      // Change record #5.
+      change = reader.readChangeRecord(false);
+      Assert.assertTrue(change instanceof ModifyChangeRecordEntry);
+      modify = (ModifyChangeRecordEntry) change;
+
+      dn = DN
+          .decode("cn=Paula Jensen, ou=Product Development, dc=airius, dc=com");
+      Assert.assertEquals(modify.getDN(), dn);
+
+      i = modify.getModifications().iterator();
+
+      Assert.assertTrue(i.hasNext());
+      mod = i.next().toModification();
+      Assert.assertEquals(mod.getModificationType(), ModificationType.ADD);
+      attr = new Attribute("postaladdress",
+          "123 Anystreet $ Sunnyvale, CA $ 94086");
+      Assert.assertEquals(mod.getAttribute(), attr);
+
+      Assert.assertTrue(i.hasNext());
+      mod = i.next().toModification();
+      Assert.assertEquals(mod.getModificationType(),
+          ModificationType.DELETE);
+      attr = new Attribute(AT_DESCR);
+      Assert.assertEquals(mod.getAttribute(), attr);
+
+      Assert.assertTrue(i.hasNext());
+      mod = i.next().toModification();
+      Assert.assertEquals(mod.getModificationType(),
+          ModificationType.REPLACE);
+      values = new LinkedHashSet<AttributeValue>();
+      values.add(new AttributeValue(AT_TELN, "+1 408 555 1234"));
+      values.add(new AttributeValue(AT_TELN, "+1 408 555 5678"));
+      attr = new Attribute(AT_TELN, "telephonenumber", values);
+      Assert.assertEquals(mod.getAttribute(), attr);
+
+      Assert.assertTrue(i.hasNext());
+      mod = i.next().toModification();
+      Assert.assertEquals(mod.getModificationType(),
+          ModificationType.DELETE);
+      attr = new Attribute("facsimiletelephonenumber", "+1 408 555 9876");
+      Assert.assertEquals(mod.getAttribute(), attr);
+
+      Assert.assertFalse(i.hasNext());
+
+      // Change record #6.
+      change = reader.readChangeRecord(false);
+      Assert.assertTrue(change instanceof ModifyChangeRecordEntry);
+      modify = (ModifyChangeRecordEntry) change;
+
+      dn = DN
+          .decode("cn=Ingrid Jensen, ou=Product Support, dc=airius, dc=com");
+      Assert.assertEquals(modify.getDN(), dn);
+
+      i = modify.getModifications().iterator();
+
+      Assert.assertTrue(i.hasNext());
+      mod = i.next().toModification();
+      Assert.assertEquals(mod.getModificationType(),
+          ModificationType.REPLACE);
+      attr = new Attribute(DirectoryServer
+          .getAttributeType("postaladdress"));
+      Assert.assertEquals(mod.getAttribute(), attr);
+
+      // Change record #7.
+      change = reader.readChangeRecord(false);
+      Assert.assertTrue(change instanceof ModifyChangeRecordEntry);
+      modify = (ModifyChangeRecordEntry) change;
+
+      Assert.assertTrue(modify.getDN().isNullDN());
+
+      i = modify.getModifications().iterator();
+
+      Assert.assertTrue(i.hasNext());
+      mod = i.next().toModification();
+      Assert.assertEquals(mod.getModificationType(),
+          ModificationType.DELETE);
+      attr = new Attribute(AT_DESCR);
+      Assert.assertEquals(mod.getAttribute(), attr);
+
+      // Change record #8.
+      change = reader.readChangeRecord(false);
+      Assert.assertTrue(change instanceof ModifyChangeRecordEntry);
+      modify = (ModifyChangeRecordEntry) change;
+
+      dn = DN.decode("uid=rogasawara, ou=\u55b6\u696d\u90e8, o=airius");
+      Assert.assertEquals(modify.getDN(), dn);
+
+      i = modify.getModifications().iterator();
+
+      Assert.assertTrue(i.hasNext());
+      mod = i.next().toModification();
+      Assert.assertEquals(mod.getModificationType(),
+          ModificationType.DELETE);
+      attr = new Attribute(AT_DESCR);
+      Assert.assertEquals(mod.getAttribute(), attr);
+
+      Assert.assertFalse(i.hasNext());
+
+      // Check final state.
+
+      Assert.assertNull(reader.readChangeRecord(false));
+
+      Assert.assertEquals(reader.getEntriesIgnored(), 0);
+      Assert.assertEquals(reader.getEntriesRead(), 0);
+      Assert.assertEquals(reader.getEntriesRejected(), 0);
+      Assert.assertEquals(reader.getLastEntryLineNumber(), 72);
+    } finally {
+      reader.close();
+    }
+  }
+
+  /**
+   * Attempt to read multiple changes and rejects one.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dependsOnMethods = { "testReadChangeMultiple" })
+  public void testReadChangeMultipleAndReject() throws Exception {
+    LDIFReader reader = createLDIFReader(VALID_LDIF);
+
+    try {
+      reader.readChangeRecord(false);
+      reader.readChangeRecord(false);
+      reader.readChangeRecord(false);
+
+      reader.readChangeRecord(false);
+      reader.rejectLastEntry("Rejected");
+
+      reader.readChangeRecord(false);
+      reader.rejectLastEntry("Rejected");
+
+      reader.readChangeRecord(false);
+      reader.readChangeRecord(false);
+      reader.readChangeRecord(false);
+
+      // Check final state.
+      Assert.assertNull(reader.readChangeRecord(false));
+      Assert.assertEquals(reader.getEntriesRejected(), 2);
+    } finally {
+      reader.close();
+    }
+  }
+
+  /**
+   * Attempt to read a change containing a file-based attribute.
+   *
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dependsOnMethods = { "testReadChangeMultiple" })
+  public void testReadChangeWithFileBaseAttribute() throws Exception {
+    StringBuilder buffer = new StringBuilder(TEMP_FILE_LDIF);
+    buffer.append(TEMP_FILE.getCanonicalPath());
+    buffer.append("\n");
+    LDIFReader reader = createLDIFReader(buffer.toString());
+
+    try {
+      ChangeRecordEntry change = reader.readChangeRecord(false);
+      Assert.assertTrue(change instanceof AddChangeRecordEntry);
+      AddChangeRecordEntry add = (AddChangeRecordEntry) change;
+
+      DN dn = DN.decode("cn=john smith, dc=com");
+      Assert.assertEquals(add.getDN(), dn);
+
+      Attribute attr = new Attribute("description", TEMP_FILE_STRING);
+      Assert.assertTrue(add.getAttributes().contains(attr));
+
+      // Check final state.
+      Assert.assertNull(reader.readChangeRecord(false));
+    } finally {
+      reader.close();
+    }
+  }
+
+  /**
+   * LDIF change reader - invalid data provider.
+   *
+   * @return Returns an array of invalid LDIF change records.
+   */
+  @DataProvider(name = "invalidLDIFChangeRecords")
+  public Object[][] createInvalidLDIFChangeRecords() {
+    return new Object[][] {
+        {
+          "changetype: add\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: add\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          ": cn=john smith, dc=com\n" +
+          "changetype: add\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "x: cn=john smith, dc=com\n" +
+          "changetype: add\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: foo\n" +
+          "changetype: add\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: cn,=john smith, dc=com\n" +
+          "changetype: add\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "chaxxngetype: add\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: foo\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype:: add\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          ": add\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype:\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: add\n" +
+          "xxxx\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: add\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: add\n" +
+          "objectClass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n" +
+          "cn: john\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: add\n" +
+          ": top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "objectClass: person\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: add\n" +
+          "objectclass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n" +
+          "description:: YnJva2VuIGJhc2U2NA*=="
+        },
+        {
+          "dn:: YnJva2VuIGJhc2U2NA*==" +
+          "changetype: add\n" +
+          "objectclass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn:: Y249YnJva2VuIGJhc2U2NCBkbix4" +
+          "changetype: add\n" +
+          "objectclass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: add\n" +
+          "objectclass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n" +
+          "description:< brok@n:///bad/url"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: add\n" +
+          "objectclass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n" +
+          "description:< file:///bad/path/name"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: delete\n" +
+          "objectclass: top\n" +
+          "objectClass: person\n" +
+          "cn: john\n" +
+          "sn: smith\n" +
+          "description:< file:///bad/path/name"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: modrdn\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: modrdn\n" +
+          "newrdn: x\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: modrdn\n" +
+          "newrdn: cn=foo\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: modrdn\n" +
+          "newrdn: cn=foo\n" +
+          "deleteoldxx: xxx\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: modrdn\n" +
+          "newrdn: cn=foo\n" +
+          "deleteoldrdn: xxx\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: modrdn\n" +
+          "newrdn: cn=foo\n" +
+          "deleteoldrdn: 0\n" +
+          "newsu: xxxx\n"
+        },
+        {
+          "dn: cn=john smith, dc=com\n" +
+          "changetype: modrdn\n" +
+          "newrdn: cn=foo\n" +
+          "deleteoldrdn: 0\n" +
+          "newsuperior: xxxx\n"
+        },
+    };
+  }
+
+  /**
+   * Tests the read change record method against invalid LDIF records.
+   *
+   * @param ldifString
+   *          The invalid LDIF change record.
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test(dataProvider = "invalidLDIFChangeRecords",
+      expectedExceptions = { LDIFException.class })
+  public void testReadChangeInvalidData(String ldifString) throws Exception {
+    LDIFReader reader = createLDIFReader(ldifString);
+    ChangeRecordEntry change = null;
+
+    try {
+      change = reader.readChangeRecord(false);
+    } finally {
+      reader.close();
+    }
+
+    Assert.fail("Expected exception but got result: "
+        + change.getChangeOperationType() + " - " + change.getDN());
+  }
+
+  /**
+   * Create an LDIF reader from a string of LDIF.
+   *
+   * @param ldifString
+   *          The string of LDIF. *
+   * @return Returns the LDIF reader.
+   * @throws Exception
+   *           If an error occurred.
+   */
+  private LDIFReader createLDIFReader(String ldifString) throws Exception {
+    byte[] bytes = StaticUtils.getBytes(ldifString);
+
+    LDIFReader reader = new LDIFReader(new LDIFImportConfig(
+        new ByteArrayInputStream(bytes)));
+
+    return reader;
+  }
+}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyChangeRecordEntry.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyChangeRecordEntry.java
index c8682cc..ec7752a 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyChangeRecordEntry.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyChangeRecordEntry.java
@@ -26,12 +26,16 @@
  */
 package org.opends.server.util;
 
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 import org.opends.server.TestCaseUtils;
+import org.opends.server.protocols.ldap.LDAPAttribute;
+import org.opends.server.protocols.ldap.LDAPModification;
+import org.opends.server.types.Attribute;
 import org.opends.server.types.DN;
-import org.opends.server.types.LDIFImportConfig;
+import org.opends.server.types.ModificationType;
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -45,8 +49,11 @@
  * been overridden.
  */
 public final class TestModifyChangeRecordEntry extends UtilTestCase {
-  // An empty LDIF reader.
-  private LDIFReader emptyReader;
+  // Set of changes.
+  private List<LDAPModification> modifications;
+
+  // The attribute being added in the modifications.
+  private Attribute attribute;
 
   /**
    * Once-only initialization.
@@ -60,9 +67,13 @@
     // start the server.
     TestCaseUtils.startServer();
 
-    InputStream stream = new ByteArrayInputStream(new byte[0]);
-    LDIFImportConfig config = new LDIFImportConfig(stream);
-    emptyReader = new LDIFReader(config);
+    // Create a simple set of modifications.
+    modifications = new ArrayList<LDAPModification>();
+    attribute = new Attribute("cn", "hello world");
+    LDAPAttribute lattribute = new LDAPAttribute(attribute);
+    LDAPModification modification = new LDAPModification(
+        ModificationType.ADD, lattribute);
+    modifications.add(modification);
   }
 
   /**
@@ -74,7 +85,7 @@
   @Test
   public void testConstructorNullDN() throws Exception {
     ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(null,
-        emptyReader);
+        modifications);
 
     Assert.assertEquals(entry.getDN(), new DN());
   }
@@ -88,7 +99,7 @@
   @Test
   public void testConstructorEmptyDN() throws Exception {
     ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(new DN(),
-        emptyReader);
+        modifications);
 
     Assert.assertEquals(entry.getDN(), new DN());
   }
@@ -105,7 +116,7 @@
     DN testDN2 = DN.decode("dc=hello, dc=world");
 
     ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(testDN1,
-        emptyReader);
+        modifications);
 
     Assert.assertEquals(entry.getDN(), testDN2);
   }
@@ -119,28 +130,43 @@
   @Test
   public void testChangeOperationType() throws Exception {
     ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(null,
-        emptyReader);
+        modifications);
 
     Assert.assertEquals(entry.getChangeOperationType(),
         ChangeOperationType.MODIFY);
   }
 
   /**
-   * Tests parse and getAttributes methods.
-   * <p>
-   * Due to tight coupling between the
-   * {@link ModifyChangeRecordEntry#parse(java.util.LinkedList, long)}
-   * method and the {@link LDIFReader} class it is not easy to test the
-   * {@link ModifyChangeRecordEntry#getModifications()} method. Instead,
-   * we'll test that in the {@link LDIFReader} test suite.
+   * Tests getModifications method for empty modifications.
    * 
    * @throws Exception
    *           If the test failed unexpectedly.
    */
-  @Test(enabled = false)
-  public void testGetModifications() throws Exception {
-    // FIXME: fix tight-coupling between parse() and LDIFReader.
-    Assert.assertTrue(false);
+  @Test
+  public void testGetModificationsEmpty() throws Exception {
+    List<LDAPModification> empty = Collections.emptyList();
+    ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(null, empty);
+
+    List<LDAPModification> mods = entry.getModifications();
+    Assert.assertEquals(mods.size(), 0);
   }
 
+  /**
+   * Tests getModifications method for non-empty modifications.
+   * 
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testGetModificationsNonEmpty() throws Exception {
+    ModifyChangeRecordEntry entry = new ModifyChangeRecordEntry(null,
+        modifications);
+
+    List<LDAPModification> mods = entry.getModifications();
+    Assert.assertEquals(mods.size(), 1);
+
+    LDAPModification first = mods.get(0);
+    Assert.assertEquals(first.getModificationType(), ModificationType.ADD);
+    Assert.assertEquals(first.getAttribute().toAttribute(), attribute);
+  }
 }
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyDNChangeRecordEntry.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyDNChangeRecordEntry.java
index d16d3fe..4f09587 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyDNChangeRecordEntry.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/util/TestModifyDNChangeRecordEntry.java
@@ -26,12 +26,9 @@
  */
 package org.opends.server.util;
 
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-
 import org.opends.server.TestCaseUtils;
 import org.opends.server.types.DN;
-import org.opends.server.types.LDIFImportConfig;
+import org.opends.server.types.RDN;
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -45,8 +42,9 @@
  * been overridden.
  */
 public final class TestModifyDNChangeRecordEntry extends UtilTestCase {
-  // An empty LDIF reader.
-  private LDIFReader emptyReader;
+  private DN newSuperiorDN;
+
+  private RDN newRDN;
 
   /**
    * Once-only initialization.
@@ -60,9 +58,8 @@
     // start the server.
     TestCaseUtils.startServer();
 
-    InputStream stream = new ByteArrayInputStream(new byte[0]);
-    LDIFImportConfig config = new LDIFImportConfig(stream);
-    emptyReader = new LDIFReader(config);
+    newSuperiorDN = DN.decode("dc=com");
+    newRDN = RDN.decode("dc=foo");
   }
 
   /**
@@ -74,7 +71,7 @@
   @Test
   public void testConstructorNullDN() throws Exception {
     ModifyDNChangeRecordEntry entry = new ModifyDNChangeRecordEntry(null,
-        emptyReader);
+        newSuperiorDN, newRDN, false);
 
     Assert.assertEquals(entry.getDN(), new DN());
   }
@@ -88,7 +85,7 @@
   @Test
   public void testConstructorEmptyDN() throws Exception {
     ModifyDNChangeRecordEntry entry = new ModifyDNChangeRecordEntry(
-        new DN(), emptyReader);
+        new DN(), newSuperiorDN, newRDN, false);
 
     Assert.assertEquals(entry.getDN(), new DN());
   }
@@ -105,7 +102,7 @@
     DN testDN2 = DN.decode("dc=hello, dc=world");
 
     ModifyDNChangeRecordEntry entry = new ModifyDNChangeRecordEntry(
-        testDN1, emptyReader);
+        testDN1, newSuperiorDN, newRDN, false);
 
     Assert.assertEquals(entry.getDN(), testDN2);
   }
@@ -119,63 +116,66 @@
   @Test
   public void testChangeOperationType() throws Exception {
     ModifyDNChangeRecordEntry entry = new ModifyDNChangeRecordEntry(null,
-        emptyReader);
+        newSuperiorDN, newRDN, false);
 
     Assert.assertEquals(entry.getChangeOperationType(),
         ChangeOperationType.MODIFY_DN);
   }
 
   /**
-   * Tests parse and getNewRDN methods.
-   * <p>
-   * Due to tight coupling between the
-   * {@link ModifyDNChangeRecordEntry#parse(java.util.LinkedList, long)}
-   * method and the {@link LDIFReader} class it is not easy to test the
-   * {@link ModifyDNChangeRecordEntry#getNewRDN()} method. Instead,
-   * we'll test that in the {@link LDIFReader} test suite.
+   * Tests getNewRDN method.
    * 
    * @throws Exception
    *           If the test failed unexpectedly.
    */
-  @Test(enabled = false)
+  @Test
   public void testGetNewRDN() throws Exception {
-    // FIXME: fix tight-coupling between parse() and LDIFReader.
-    Assert.assertTrue(false);
+    ModifyDNChangeRecordEntry entry = new ModifyDNChangeRecordEntry(null,
+        newSuperiorDN, newRDN, false);
+
+    Assert.assertEquals(entry.getNewRDN(), newRDN.duplicate());
   }
 
   /**
-   * Tests parse and getNewSuperiorDN methods.
-   * <p>
-   * Due to tight coupling between the
-   * {@link ModifyDNChangeRecordEntry#parse(java.util.LinkedList, long)}
-   * method and the {@link LDIFReader} class it is not easy to test the
-   * {@link ModifyDNChangeRecordEntry#getNewSuperiorDN()} method.
-   * Instead, we'll test that in the {@link LDIFReader} test suite.
+   * Tests getNewSuperiorDN method.
    * 
    * @throws Exception
    *           If the test failed unexpectedly.
    */
-  @Test(enabled = false)
+  @Test
   public void testGetNewSuperiorDN() throws Exception {
-    // FIXME: fix tight-coupling between parse() and LDIFReader.
-    Assert.assertTrue(false);
+    ModifyDNChangeRecordEntry entry = new ModifyDNChangeRecordEntry(null,
+        newSuperiorDN, newRDN, false);
+
+    Assert
+        .assertEquals(entry.getNewSuperiorDN(), newSuperiorDN.duplicate());
   }
 
   /**
-   * Tests parse and deleteOldRDN methods.
-   * <p>
-   * Due to tight coupling between the
-   * {@link ModifyDNChangeRecordEntry#parse(java.util.LinkedList, long)}
-   * method and the {@link LDIFReader} class it is not easy to test the
-   * {@link ModifyDNChangeRecordEntry#deleteOldRDN()} method. Instead,
-   * we'll test that in the {@link LDIFReader} test suite.
+   * Tests deleteOldRDN method when false.
    * 
    * @throws Exception
    *           If the test failed unexpectedly.
    */
-  @Test(enabled = false)
-  public void testDeleteOldRDN() throws Exception {
-    // FIXME: fix tight-coupling between parse() and LDIFReader.
-    Assert.assertTrue(false);
+  @Test
+  public void testDeleteOldRDNFalse() throws Exception {
+    ModifyDNChangeRecordEntry entry = new ModifyDNChangeRecordEntry(null,
+        newSuperiorDN, newRDN, false);
+
+    Assert.assertEquals(entry.deleteOldRDN(), false);
+  }
+
+  /**
+   * Tests deleteOldRDN method.
+   * 
+   * @throws Exception
+   *           If the test failed unexpectedly.
+   */
+  @Test
+  public void testDeleteOldRDNTrue() throws Exception {
+    ModifyDNChangeRecordEntry entry = new ModifyDNChangeRecordEntry(null,
+        newSuperiorDN, newRDN, true);
+
+    Assert.assertEquals(entry.deleteOldRDN(), true);
   }
 }

--
Gitblit v1.10.0