/**
   * 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, AttributeValues.create(AT_CN, "john")));
      Assert.assertTrue(entry.hasValue(AT_SN, null, AttributeValues.create(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();
    }
  }
  /**
   * 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());
  }
  /**
   * 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, AttributeValues.create(AT_DESCR, "once upon a time in the west")));
    } 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 = Attributes.create("description", TEMP_FILE_STRING);
      Assert.assertTrue(add.getAttributes().contains(attr));

      // Check final state.
      Assert.assertNull(reader.readChangeRecord(false));
    } 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();
    }
  }
  /**
   * 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 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 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 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(Message.raw("Rejected"));

      reader.readChangeRecord(false);
      reader.rejectLastEntry(Message.raw("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 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<RawModification> i;
      Modification mod;
      Attribute attr;

      // 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>();
      AttributeBuilder builder = new AttributeBuilder(AT_OC, "objectclass");
      builder.add(AttributeValues.create(AT_OC, "top"));
      builder.add(AttributeValues.create(AT_OC, "person"));
      builder.add(AttributeValues.create(AT_OC, "organizationalPerson"));

      attrs.add(builder.toAttribute());
      attrs.add(Attributes.create("cn", "Fiona Jensen"));
      attrs.add(Attributes.create("sn", "Jensen"));
      attrs.add(Attributes.create("uid", "fiona"));
      attrs.add(Attributes.create("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 = Attributes.create("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 = Attributes.empty(AT_DESCR);
      Assert.assertEquals(mod.getAttribute(), attr);

      Assert.assertTrue(i.hasNext());
      mod = i.next().toModification();
      Assert.assertEquals(mod.getModificationType(), ModificationType.REPLACE);
      builder = new AttributeBuilder(AT_TELN, "telephonenumber");
      builder.add(AttributeValues.create(AT_TELN, "+1 408 555 1234"));
      builder.add(AttributeValues.create(AT_TELN, "+1 408 555 5678"));
      Assert.assertEquals(mod.getAttribute(), builder.toAttribute());

      Assert.assertTrue(i.hasNext());
      mod = i.next().toModification();
      Assert.assertEquals(mod.getModificationType(), ModificationType.DELETE);
      attr = Attributes.create("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 = Attributes.empty(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 = Attributes.empty(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 = Attributes.empty(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();
    }
  }