From 869998098af41978080fadabbfab637030ec7aa3 Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Tue, 19 Nov 2013 13:31:55 +0000
Subject: [PATCH] Checkpoint commit for OPENDJ-1075 Port server make-ldif tool to the SDK CR-2586

---
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/EntryGenerator.java             |  450 +--
 opendj3/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties            |   41 
 opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/TemplateTagTestcase.java        |  293 ++
 opendj3/opendj-core/src/main/resources/org/forgerock/opendj/ldif/example.template           |    4 
 opendj3/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template |   62 
 opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldap/RDNTestCase.java                |   78 
 opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/EntryGeneratorTestCase.java     |  346 ++
 opendj3/opendj-core/src/main/java/com/forgerock/opendj/util/Pair.java                       |  152 +
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateFile.java               | 2405 +++++++++-------------
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateTag.java                | 2345 ++++-----------------
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java                        |   27 
 11 files changed, 2,512 insertions(+), 3,691 deletions(-)

diff --git a/opendj3/opendj-core/src/main/java/com/forgerock/opendj/util/Pair.java b/opendj3/opendj-core/src/main/java/com/forgerock/opendj/util/Pair.java
new file mode 100644
index 0000000..7953a69
--- /dev/null
+++ b/opendj3/opendj-core/src/main/java/com/forgerock/opendj/util/Pair.java
@@ -0,0 +1,152 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * 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
+ *
+ *
+ *      Copyright 2013 ForgeRock AS
+ */
+package com.forgerock.opendj.util;
+
+/**
+ * Ordered pair of arbitrary objects.
+ *
+ * @param <F>
+ *            type of the first pair element
+ * @param <S>
+ *            type of the second pair element
+ */
+public final class Pair<F, S> {
+
+    /** An empty Pair. */
+    public static final Pair<?, ?> EMPTY = Pair.of(null, null);
+
+    /** The first pair element. */
+    private final F first;
+
+    /** The second pair element. */
+    private final S second;
+
+    /**
+     * Creates a pair.
+     *
+     * @param first
+     *            the first element of the constructed pair
+     * @param second
+     *            the second element of the constructed pair
+     */
+    private Pair(F first, S second) {
+        this.first = first;
+        this.second = second;
+    }
+
+    /**
+     * Factory method to build a new Pair.
+     *
+     * @param first
+     *            the first element of the constructed pair
+     * @param second
+     *            the second element of the constructed pair
+     * @param <F>
+     *            type of the first pair element
+     * @param <S>
+     *            type of the second pair element
+     * @return A new Pair built with the provided elements
+     */
+    public static <F, S> Pair<F, S> of(F first, S second) {
+        return new Pair<F, S>(first, second);
+    }
+
+    /**
+     * Returns an empty Pair matching the required types.
+     *
+     * @param <F>
+     *            type of the first pair element
+     * @param <S>
+     *            type of the second pair element
+     * @return An empty Pair matching the required types
+     */
+    @SuppressWarnings("unchecked")
+    public static <F, S> Pair<F, S> empty() {
+        return (Pair<F, S>) EMPTY;
+    }
+
+    /**
+     * Returns the first element of this pair.
+     *
+     * @return the first element of this pair
+     */
+    public F getFirst() {
+        return first;
+    }
+
+    /**
+     * Returns the second element of this pair.
+     *
+     * @return the second element of this pair
+     */
+    public S getSecond() {
+        return second;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((first == null) ? 0 : first.hashCode());
+        result = prime * result + ((second == null) ? 0 : second.hashCode());
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof Pair)) {
+            return false;
+        }
+        Pair<?, ?> other = (Pair<?, ?>) obj;
+        if (first == null) {
+            if (other.first != null) {
+                return false;
+            }
+        } else if (!first.equals(other.first)) {
+            return false;
+        }
+        if (second == null) {
+            if (other.second != null) {
+                return false;
+            }
+        } else if (!second.equals(other.second)) {
+            return false;
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "Pair [" + first + ", " + second + "]";
+    }
+
+}
diff --git a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java
index 9372bad..efa55e7 100644
--- a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java
@@ -31,6 +31,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 
@@ -42,6 +43,7 @@
 
 import com.forgerock.opendj.util.Iterators;
 import com.forgerock.opendj.util.SubstringReader;
+import com.forgerock.opendj.util.Validator;
 
 /**
  * A relative distinguished name (RDN) as defined in RFC 4512 section 2.3 is the
@@ -216,7 +218,32 @@
         this.avas = new AVA[] { new AVA(attributeType, attributeValue) };
     }
 
+    /**
+     * Creates a new RDN using the provided AVAs.
+     *
+     * @param avas
+     *            The attribute-value assertions used to build this RDN.
+     * @throws NullPointerException
+     *             If {@code avas} is {@code null} or contains a null ava.
+     */
+    public RDN(final AVA... avas) {
+        this(avas, null);
+    }
+
+    /**
+     * Creates a new RDN using the provided AVAs.
+     *
+     * @param avas
+     *            The attribute-value assertions used to build this RDN.
+     * @throws NullPointerException
+     *             If {@code ava} is {@code null} or contains null ava.
+     */
+    public RDN(Collection<AVA> avas) {
+        this(avas.toArray(new AVA[avas.size()]), null);
+    }
+
     private RDN(final AVA[] avas, final String stringValue) {
+        Validator.ensureNotNull((Object[]) avas);
         this.avas = avas;
         this.stringValue = stringValue;
     }
diff --git a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/EntryGenerator.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/EntryGenerator.java
index ebbbbf1..6d56afd 100644
--- a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/EntryGenerator.java
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/EntryGenerator.java
@@ -31,20 +31,17 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Random;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
 
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.opendj.ldap.DecodeException;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.schema.Schema;
-import org.forgerock.opendj.ldif.TemplateFile.EntryWriter;
-import org.forgerock.opendj.ldif.TemplateFile.TemplateEntry;
 
 import com.forgerock.opendj.util.Validator;
 
@@ -52,247 +49,187 @@
  * Generator of entries based on a {@code TemplateFile template file}, which can
  * be provided as a file, a list of lines, an array of lines, or an input
  * stream.
+ * <p>
+ * To build a generator with all default values, including default template
+ * file, use the empty constructor:
+ *
+ * <pre>
+ *  generator = new EntryGenerator();
+ * </pre>
+ * <p>
+ * To build a generator with some custom values, use the non-empty constructor
+ * and the <code>set</code> methods:
+ *
+ * <pre>
+ * generator = new EntryGenerator(templatePath)
+ *     .setResourcePath(path)
+ *     .setSchema(schema)
+ * </pre>
  */
 public final class EntryGenerator implements EntryReader {
 
-    private static final TemplateEntry POISON_ENTRY = TemplateFile.TemplateEntry.NULL_TEMPLATE_ENTRY;
+    private static final int DEFAULT_RANDOM_SEED = 1;
 
     /** Template file that contains directives for generation of entries. */
-    private final TemplateFile templateFile;
+    private TemplateFile templateFile;
 
-    /** Queue used to hold generated entries until they can be read. */
-    private final BlockingQueue<TemplateEntry> entryQueue;
+    /** Warnings issued by the parsing of the template file. */
+    private final List<LocalizableMessage> warnings = new LinkedList<LocalizableMessage>();
 
-    /** The next available entry. */
-    private TemplateEntry nextEntry;
+    /** Indicates if the generator is closed. */
+    private boolean isClosed = false;
 
-    private final List<LocalizableMessage> warnings;
+    /** Indicates if the generator is initialized, which means template file has been parsed. */
+    private boolean isInitialized = false;
 
-    private volatile IOException ioException;
-
-    private volatile boolean generationIsFinished = false;
-
-    private volatile boolean isClosed = false;
-
-    /** Thread that provides generation of entries. */
-    private Thread generationThread;
+    /** Random seed is used to generate random data. */
+    private int randomSeed = DEFAULT_RANDOM_SEED;
 
     /**
-     * Creates a reader.
-     *
-     * @param templateFile
-     *            contains definition of entries to generate. It must have
-     *            already been parsed
-     * @param warnings
-     *            list of warnings that were issued when parsing the template
-     *            file
-     * @param entryQueue
-     *            used to hold generated entries and block generation until
-     *            entries are read
+     * Path to the directory that may contain additional resource files needed
+     * during the generation process. It may be {@code null}.
      */
-    private EntryGenerator(TemplateFile templateFile, LinkedList<LocalizableMessage> warnings,
-            BlockingQueue<TemplateEntry> entryQueue) {
-        this.templateFile = templateFile;
-        this.warnings = warnings;
-        this.entryQueue = entryQueue;
+    private String resourcePath;
+
+    /**
+     * Schema is used to create attributes. If not provided, the default schema
+     * is used.
+     */
+    private Schema schema;
+
+    /**
+     * Path of template file, can be {@code null} if template file has been
+     * provided through another way.
+     */
+    private String templatePath;
+
+    /**
+     * Lines of template file, can be {@code null} if template file has been
+     * provided through another way.
+     */
+    private String[] templateLines;
+
+    /**
+     * Input stream containing template file, can be {@code null} if template
+     * file has been provided through another way.
+     */
+    private InputStream templateStream;
+
+    /** Dictionary of constants to use in the template file. */
+    private Map<String, String> constants = new HashMap<String, String>();
+
+    /**
+     * Creates a generator using default values.
+     * <p>
+     * The default template file will be used to generate entries.
+     */
+    public EntryGenerator() {
+        // nothing to do
     }
 
     /**
-     * Returns a builder to create a reader based on a template file given by
-     * the provided path.
+     * Creates a generator from the provided template path.
      *
      * @param templatePath
-     *            path of the template file
-     * @return a builder allowing to create the reader
+     *            Path of the template file.
      */
-    public static Builder newReader(final String templatePath) {
-        return new Builder(templatePath);
+    public EntryGenerator(final String  templatePath) {
+        Validator.ensureNotNull(templatePath);
+        this.templatePath = templatePath;
     }
 
     /**
-     * Returns a builder to create a reader based on a template file given by
-     * the provided lines.
+     * Creates a generator from the provided template lines.
      *
      * @param templateLines
-     *            lines defining the template file
-     * @return a builder allowing to create the reader
+     *            Lines defining the template file.
      */
-    public static Builder newReader(final String... templateLines) {
-        return new Builder(templateLines);
+    public EntryGenerator(final String... templateLines) {
+        Validator.ensureNotNull((Object[]) templateLines);
+        this.templateLines = templateLines;
     }
 
     /**
-     * Returns a builder to create a reader based on a template file given by
-     * the provided lines.
+     * Creates a generator from the provided template lines.
      *
      * @param templateLines
-     *            lines defining the template file
-     * @return a builder allowing to create the reader
+     *            Lines defining the template file.
      */
-    public static Builder newReader(final List<String> templateLines) {
-        return new Builder(templateLines.toArray(new String[templateLines.size()]));
+    public EntryGenerator(final List<String> templateLines) {
+        Validator.ensureNotNull(templateLines);
+        this.templateLines = templateLines.toArray(new String[templateLines.size()]);
     }
 
     /**
-     * Returns a builder to create a reader based on a template file given by
-     * the provided stream.
+     * Creates a generator from the provided input stream.
      *
      * @param templateStream
-     *            input stream to read the template file
-     * @return a builder allowing to create the reader
+     *            Input stream to read the template file.
      */
-    public static Builder newReader(final InputStream templateStream) {
-        return new Builder(templateStream);
+    public EntryGenerator(final InputStream templateStream) {
+        Validator.ensureNotNull(templateStream);
+        this.templateStream = templateStream;
     }
 
     /**
-     * Builder of {@code EntryGenerator readers}.
-     * <p>
+     * Sets the random seed to use when generating entries.
      *
-     * To build a reader with all default values:
-     * <pre>
-     * {@code reader = EntryGenerator.newReader(...).build() }
-     * </pre>
-     * <p>
-     *
-     * To build a reader with some custom values, using the
-     * <code>set</code> methods:
-     * <pre>
-     * {@code reader = EntryGenerator.newReader(...).
-     *    setResourcePath(path).
-     *    setSchema(schema).
-     *    build() }
-     * </pre>
+     * @param seed
+     *            Seed to use.
+     * @return A reference to this {@code EntryGenerator}.
      */
-    public static final class Builder {
-
-        private static final int DEFAULT_QUEUE_SIZE = 100;
-        private static final int DEFAULT_RANDOM_SEED = 1;
-        private static final String DEFAULT_RESOURCE_PATH = ".";
-
-        private String templatePath;
-        private String[] templateLines;
-        private InputStream templateStream;
-
-        private TemplateFile templateFile;
-        private int maxNumberOfEntriesInQueue = DEFAULT_QUEUE_SIZE;
-        private int randomSeed = DEFAULT_RANDOM_SEED;
-        private String resourcePath = DEFAULT_RESOURCE_PATH;
-        private Schema schema;
-
-        private Builder(String templatePath) {
-            this.templatePath = templatePath;
-        }
-
-        private Builder(String[] templateLines) {
-            this.templateLines = templateLines;
-        }
-
-        private Builder(InputStream templateStream) {
-            this.templateStream = templateStream;
-        }
-
-        /**
-         * Sets the capacity of the queue holding generated entries.
-         *
-         * @param max
-         *            capacity of the queue that holds generated entries
-         * @return A reference to this {@code EntryGenerator.Builder}.
-         */
-        public Builder setMaxNumberOfEntriesInQueue(final int max) {
-            Validator.ensureTrue(max > 0, "queue capacity must be strictly superior to zero");
-            maxNumberOfEntriesInQueue = max;
-            return this;
-        }
-
-        /**
-         * Sets the random seed to use when generating entries.
-         *
-         * @param seed
-         *            seed to use
-         * @return A reference to this {@code EntryGenerator.Builder}.
-         */
-        public Builder setRandomSeed(final int seed) {
-            randomSeed = seed;
-            return this;
-        }
-
-        /**
-         * Sets the resource path, used to looks for resources files like first
-         * names, last names, or other custom resources.
-         *
-         * @param path
-         *            resource path
-         * @return A reference to this {@code EntryGenerator.Builder}.
-         */
-        public Builder setResourcePath(final String path) {
-            Validator.ensureNotNull(path);
-            resourcePath = path;
-            return this;
-        }
-
-        /**
-         * Sets the schema which should be when generating entries. The default
-         * schema is used if no other is specified.
-         *
-         * @param schema
-         *            The schema which should be used for generating entries.
-         * @return A reference to this {@code EntryGenerator.Builder}.
-         */
-        public Builder setSchema(final Schema schema) {
-            this.schema = schema;
-            return this;
-        }
-
-        /**
-         * Return an instance of reader.
-         *
-         * @return a new instance of reader
-         * @throws IOException
-         *             If an error occurs while reading template file.
-         * @throws DecodeException
-         *             If some other problem occurs during initialization
-         */
-        public EntryGenerator build() throws IOException, DecodeException {
-            if (schema == null) {
-                schema = Schema.getDefaultSchema();
-            }
-            templateFile = new TemplateFile(schema, resourcePath, new Random(randomSeed));
-            LinkedList<LocalizableMessage> warnings = new LinkedList<LocalizableMessage>();
-            try {
-                if (templatePath != null) {
-                    templateFile.parse(templatePath, warnings);
-                } else if (templateLines != null) {
-                    templateFile.parse(templateLines, warnings);
-                } else if (templateStream != null) {
-                    templateFile.parse(templateStream, warnings);
-                } else {
-                    // this should never happen
-                    throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_MISSING_TEMPLATE_FILE.get());
-                }
-            } catch (IOException e) {
-                throw e;
-            } catch (Exception e) {
-                throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_EXCEPTION_DURING_PARSE.get(e.getMessage()), e);
-            }
-            EntryGenerator reader = new EntryGenerator(templateFile,
-                    warnings, new LinkedBlockingQueue<TemplateEntry>(maxNumberOfEntriesInQueue));
-            reader.startEntriesGeneration();
-            return reader;
-        }
+    public EntryGenerator setRandomSeed(final int seed) {
+        randomSeed = seed;
+        return this;
     }
 
     /**
-     * Start generation of entries by launching a separate thread.
+     * Sets the resource path, used to looks for resources files like first
+     * names, last names, or other custom resources.
+     *
+     * @param path
+     *            Resource path.
+     * @return A reference to this {@code EntryGenerator}.
      */
-    private void startEntriesGeneration() {
-        generationThread =
-                new Thread(new EntriesGenerator(new MakeEntryWriter(), templateFile), "MakeLDIF Generator Thread");
-        generationThread.start();
+    public EntryGenerator setResourcePath(final String path) {
+        Validator.ensureNotNull(path);
+        resourcePath = path;
+        return this;
+    }
+
+    /**
+     * Sets the schema which should be when generating entries. The default
+     * schema is used if no other is specified.
+     *
+     * @param schema
+     *            The schema which should be used for generating entries.
+     * @return A reference to this {@code EntryGenerator}.
+     */
+    public EntryGenerator setSchema(final Schema schema) {
+        this.schema = schema;
+        return this;
+    }
+
+    /**
+     * Sets a constant to use in template file. It overrides the constant set in
+     * the template file.
+     *
+     * @param name
+     *            Name of the constant.
+     * @param value
+     *            Value of the constant.
+     * @return A reference to this {@code EntryGenerator}.
+     */
+    public EntryGenerator setConstant(String name, Object value) {
+        constants.put(name, value.toString());
+        return this;
     }
 
     /**
      * Checks if there are some warning(s) after the parsing of template file.
+     * <p>
+     * Warnings are available only after the first call to {@code hasNext()} or
+     * {@code readEntry()} methods.
      *
      * @return true if there is at least one warning
      */
@@ -302,6 +239,9 @@
 
     /**
      * Returns the warnings generated by the parsing of template file.
+     * <p>
+     * Warnings are available only after the first call to {@code hasNext()}
+     * or {@code readEntry()} methods.
      *
      * @return the list of warnings, which is empty if there is no warning
      */
@@ -312,120 +252,60 @@
     @Override
     public void close() {
         isClosed = true;
-        ioException = null;
-        try {
-            generationThread.join(0);
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-        }
     }
 
     @Override
     public boolean hasNext() throws IOException {
         if (isClosed) {
             return false;
-        } else if (ioException != null) {
-            throw ioException;
-        } else if (nextEntry != null) {
-            return true;
-        } else if (generationIsFinished) {
-            nextEntry = entryQueue.poll();
-        } else {
-            try {
-                nextEntry = entryQueue.take();
-            } catch (InterruptedException ie) {
-                Thread.currentThread().interrupt();
-            }
         }
-        if (nextEntry == POISON_ENTRY) {
-            nextEntry = null;
-        }
-        return nextEntry != null;
+        ensureGeneratorIsInitialized();
+        return templateFile.hasNext();
     }
 
     @Override
     public Entry readEntry() throws IOException {
         if (!hasNext()) {
-            // LDIF reader has completed successfully.
             throw new NoSuchElementException();
         } else {
-            final Entry entry = nextEntry.toEntry();
-            nextEntry = null;
-            return entry;
+            return templateFile.nextEntry();
         }
     }
 
     /**
-     * Entry writer that store entries into the entry queue of this reader, and
-     * record close and exception events.
+     * Check that generator is initialized, and initialize it
+     * if it has not been initialized.
      */
-    private final class MakeEntryWriter implements EntryWriter {
-
-        @Override
-        public boolean writeEntry(final TemplateEntry entry) {
-            while (!isClosed) {
-                try {
-                    if (entryQueue.offer(entry, 500, TimeUnit.MILLISECONDS)) {
-                        return true;
-                    }
-                } catch (InterruptedException ie) {
-                    // nothing to do
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public void closeEntryWriter() {
-            generationIsFinished = true;
-            writeEntry(POISON_ENTRY);
-        }
-
-        public void setIOException(final IOException ioe) {
-            ioException = ioe;
+    private void ensureGeneratorIsInitialized() throws IOException {
+        if (!isInitialized) {
+            isInitialized = true;
+            initialize();
         }
     }
 
-
     /**
-     * Generator of entries, that writes entries to a provided
-     * {@code EntryWriter writer}.
+     * Initializes the generator, by retrieving template file and parsing it.
      */
-    private static final class EntriesGenerator implements Runnable {
-
-        private final MakeEntryWriter entryWriter;
-
-        private final TemplateFile templateFile;
-
-        /**
-         * Creates a generator that writes to provided writer using the provided
-         * template file.
-         *
-         * @param entryWriter
-         * @param templateFile
-         */
-        EntriesGenerator(final MakeEntryWriter entryWriter, final TemplateFile templateFile) {
-            this.entryWriter = entryWriter;
-            this.templateFile = templateFile;
+    private void initialize() throws IOException {
+        if (schema == null) {
+            schema = Schema.getDefaultSchema();
         }
-
-        /**
-         * Run the generation of entries.
-         */
-        public void run() {
-            generate();
-        }
-
-        /**
-         * Generates entries to the entry writer.
-         */
-        void generate() {
-            try {
-                templateFile.generateEntries(entryWriter);
-            } catch (IOException e) {
-                entryWriter.setIOException(e);
-                entryWriter.closeEntryWriter();
+        templateFile = new TemplateFile(schema, constants, resourcePath, new Random(randomSeed));
+        try {
+            if (templatePath != null) {
+                templateFile.parse(templatePath, warnings);
+            } else if (templateLines != null) {
+                templateFile.parse(templateLines, warnings);
+            } else if (templateStream != null) {
+                templateFile.parse(templateStream, warnings);
+            } else {
+                // use default template file
+                templateFile.parse(warnings);
             }
+        } catch (IOException e) {
+            throw e;
+        } catch (Exception e) {
+            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_EXCEPTION_DURING_PARSE.get(e.getMessage()), e);
         }
     }
 
diff --git a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateFile.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateFile.java
index 4af2d24..4b5d35e 100644
--- a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateFile.java
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateFile.java
@@ -36,29 +36,30 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Random;
+import java.util.Set;
 import java.util.StringTokenizer;
 
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.opendj.ldap.AVA;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.AttributeDescription;
-import org.forgerock.opendj.ldap.AttributeFactory;
+import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.DecodeException;
-import org.forgerock.opendj.ldap.Entries;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.LinkedAttribute;
 import org.forgerock.opendj.ldap.LinkedHashMapEntry;
 import org.forgerock.opendj.ldap.RDN;
 import org.forgerock.opendj.ldap.schema.AttributeType;
-import org.forgerock.opendj.ldap.schema.CoreSchema;
-import org.forgerock.opendj.ldap.schema.ObjectClass;
 import org.forgerock.opendj.ldap.schema.Schema;
 import org.forgerock.opendj.ldif.TemplateTag.AttributeValueTag;
 import org.forgerock.opendj.ldif.TemplateTag.DNTag;
@@ -79,7 +80,9 @@
 import org.forgerock.opendj.ldif.TemplateTag.UnderscoreDNTag;
 import org.forgerock.opendj.ldif.TemplateTag.UnderscoreParentDNTag;
 
+import com.forgerock.opendj.util.Pair;
 import com.forgerock.opendj.util.StaticUtils;
+import com.forgerock.opendj.util.Validator;
 
 /**
  * A template file allow to generate entries from a collection of constant
@@ -88,21 +91,24 @@
  * @see EntryGenerator
  */
 class TemplateFile {
-    /**
-     * The name of the file holding the list of first names.
-     */
-    public static final String FIRST_NAME_FILE = "first.names";
 
-    /**
-     * The name of the file holding the list of last names.
-     */
-    public static final String LAST_NAME_FILE = "last.names";
+    /** Default resource path used if no resource path is provided. */
+    private static final File DEFAULT_RESOURCES_PATH = new File("org/forgerock/opendj/ldif");
+
+    /** Default template path used if no template file is provided. */
+    private static final String DEFAULT_TEMPLATE_PATH = "example.template";
+
+    /** The name of the file holding the list of first names. */
+    private static final String FIRST_NAME_FILE = "first.names";
+
+    /** The name of the file holding the list of last names. */
+    private static final String LAST_NAME_FILE = "last.names";
 
     /**
      * A map of the contents of various text files used during the parsing
      * process, mapped from absolute path to the array of lines in the file.
      */
-    private Map<String, String[]> fileLines;
+    private final Map<String, String[]> fileLines = new HashMap<String, String[]>();
 
     /** The index of the next first name value that should be used. */
     private int firstNameIndex;
@@ -120,22 +126,22 @@
      * A counter that will be used in case we have exhausted all possible first
      * and last name combinations.
      */
-    private int nameUniquenessCounter;
+    private int nameUniquenessCounter = 1;
 
     /** The set of branch definitions for this template file. */
-    private Map<DN, Branch> branches;
+    private final Map<DN, Branch> branches = new LinkedHashMap<DN, Branch>();
 
     /** The set of constant definitions for this template file. */
-    private Map<String, String> constants;
+    private final Map<String, String> constants;
 
     /** The set of registered tags for this template file. */
-    private Map<String, TemplateTag> registeredTags;
+    private final Map<String, TemplateTag> registeredTags = new LinkedHashMap<String, TemplateTag>();
 
     /** The set of template definitions for this template file. */
-    private Map<String, Template> templates;
+    private final Map<String, Template> templates = new LinkedHashMap<String, Template>();
 
     /** The random number generator for this template file. */
-    private Random random;
+    private final Random random;
 
     /** The next first name that should be used. */
     private String firstName;
@@ -149,14 +155,11 @@
      */
     private String resourcePath;
 
-    /** The path to the directory containing the template file, if available. */
-    private String templatePath;
-
     /** The set of first names to use when generating the LDIF. */
-    private String[] firstNames;
+    private String[] firstNames = new String[0];
 
     /** The set of last names to use when generating the LDIF. */
-    private String[] lastNames;
+    private String[] lastNames = new String[0];
 
     /** Schema used to create attributes. */
     private final Schema schema;
@@ -165,110 +168,54 @@
      * Creates a new, empty template file structure.
      *
      * @param schema
-     *            LDAP Schema to use
+     *            LDAP Schema to use.
+     * @param constants
+     *            Constants to use, override any constant defined in the
+     *            template file. May be {@code null}.
      * @param resourcePath
      *            The path to the directory that may contain additional resource
-     *            files needed during the generation process.
+     *            files needed during the generation process. May be
+     *            {@code null}.
+     * @throws IOException
+     *             if a problem occurs when initializing
      */
-    public TemplateFile(Schema schema, String resourcePath) {
-        this(schema, resourcePath, new Random());
+    TemplateFile(Schema schema, Map<String, String> constants, String resourcePath) throws IOException {
+        this(schema, constants, resourcePath, new Random());
     }
 
     /**
      * Creates a new, empty template file structure.
      *
      * @param schema
-     *            used to create attributes
+     *            LDAP Schema to use.
+     * @param constants
+     *            Constants to use, override any constant defined in the
+     *            template file. May be {@code null}.
      * @param resourcePath
      *            The path to the directory that may contain additional resource
-     *            files needed during the generation process.
+     *            files needed during the generation process. May be
+     *            {@code null}.
      * @param random
      *            The random number generator for this template file.
+     * @throws IOException
+     *             if a problem occurs when initializing
      */
-    public TemplateFile(Schema schema, String resourcePath, Random random) {
+    TemplateFile(Schema schema, Map<String, String> constants, String resourcePath, Random random)
+            throws IOException {
+        Validator.ensureNotNull(schema, random);
         this.schema = schema;
+        this.constants = constants != null ? constants : new HashMap<String, String>();
         this.resourcePath = resourcePath;
         this.random = random;
-
-        fileLines = new HashMap<String, String[]>();
-        branches = new LinkedHashMap<DN, Branch>();
-        constants = new LinkedHashMap<String, String>();
-        registeredTags = new LinkedHashMap<String, TemplateTag>();
-        templates = new LinkedHashMap<String, Template>();
-        templatePath = null;
-        firstNames = new String[0];
-        lastNames = new String[0];
-        firstName = null;
-        lastName = null;
-        firstNameIndex = 0;
-        lastNameIndex = 0;
-        nameLoopCounter = 0;
-        nameUniquenessCounter = 1;
-
         registerDefaultTags();
-
-        readNames();
+        retrieveFirstAndLastNames();
     }
 
-    /**
-     * Retrieves the set of tags that have been registered. They will be in the
-     * form of a mapping between the name of the tag (in all lowercase
-     * characters) and the corresponding tag implementation.
-     *
-     * @return The set of tags that have been registered.
-     */
-    public Map<String, TemplateTag> getTags() {
-        return registeredTags;
-    }
-
-    /**
-     * Retrieves the tag with the specified name.
-     *
-     * @param lowerName
-     *            The name of the tag to retrieve, in all lowercase characters.
-     * @return The requested tag, or <CODE>null</CODE> if no such tag has been
-     *         registered.
-     */
-    public TemplateTag getTag(String lowerName) {
+    TemplateTag getTag(String lowerName) {
         return registeredTags.get(lowerName);
     }
 
     /**
-     * Registers the specified class as a tag that may be used in templates.
-     *
-     * @param tagClass
-     *            The fully-qualified name of the class to register as a tag.
-     * @throws DecodeException
-     *             If a problem occurs while attempting to register the
-     *             specified tag.
-     */
-    public void registerTag(String tagClass) throws DecodeException {
-        Class<?> c;
-        try {
-            c = Class.forName(tagClass);
-        } catch (Exception e) {
-            final LocalizableMessage message = ERR_ENTRY_GENERATOR_CANNOT_LOAD_TAG_CLASS.get(tagClass);
-            throw DecodeException.fatalError(message, e);
-        }
-
-        TemplateTag t;
-        try {
-            t = (TemplateTag) c.newInstance();
-        } catch (Exception e) {
-            final LocalizableMessage message = ERR_ENTRY_GENERATOR_CANNOT_INSTANTIATE_TAG.get(tagClass);
-            throw DecodeException.fatalError(message, e);
-        }
-
-        String lowerName = t.getName().toLowerCase();
-        if (registeredTags.containsKey(lowerName)) {
-            final LocalizableMessage message = ERR_ENTRY_GENERATOR_CONFLICTING_TAG_NAME.get(tagClass, t.getName());
-            throw DecodeException.fatalError(message);
-        } else {
-            registeredTags.put(lowerName, t);
-        }
-    }
-
-    /**
      * Registers the set of tags that will always be available for use in
      * templates.
      */
@@ -278,182 +225,45 @@
             ListTag.class, ParentDNTag.class, PresenceTag.class, RandomTag.class, RDNTag.class,
             SequentialTag.class, StaticTextTag.class, UnderscoreDNTag.class, UnderscoreParentDNTag.class };
 
-        for (Class<?> c : defaultTagClasses) {
+        for (final Class<?> c : defaultTagClasses) {
             try {
-                TemplateTag t = (TemplateTag) c.newInstance();
+                final TemplateTag t = (TemplateTag) c.newInstance();
                 registeredTags.put(t.getName().toLowerCase(), t);
             } catch (Exception e) {
-                // this should never happen
-                StaticUtils.DEFAULT_LOG.error(ERR_ENTRY_GENERATOR_CANNOT_INSTANTIATE_TAG.get(c.getName()).toString());
+                // this is a programming error
+                throw new RuntimeException(ERR_ENTRY_GENERATOR_CANNOT_INSTANTIATE_TAG.get(c.getName()).toString(), e);
             }
         }
     }
 
-    /**
-     * Retrieves the set of constants defined for this template file.
-     *
-     * @return The set of constants defined for this template file.
-     */
-    public Map<String, String> getConstants() {
-        return constants;
-    }
-
-    /**
-     * Retrieves the value of the constant with the specified name.
-     *
-     * @param lowerName
-     *            The name of the constant to retrieve, in all lowercase
-     *            characters.
-     * @return The value of the constant with the specified name, or
-     *         <CODE>null</CODE> if there is no such constant.
-     */
-    public String getConstant(String lowerName) {
-        return constants.get(lowerName);
-    }
-
-    /**
-     * Registers the provided constant for use in the template.
-     *
-     * @param name
-     *            The name for the constant.
-     * @param value
-     *            The value for the constant.
-     */
-    public void registerConstant(String name, String value) {
-        constants.put(name.toLowerCase(), value);
-    }
-
-    /**
-     * Retrieves the set of branches defined in this template file.
-     *
-     * @return The set of branches defined in this template file.
-     */
-    public Map<DN, Branch> getBranches() {
-        return branches;
-    }
-
-    /**
-     * Retrieves the branch registered with the specified DN.
-     *
-     * @param branchDN
-     *            The DN for which to retrieve the corresponding branch.
-     * @return The requested branch, or <CODE>null</CODE> if no such branch has
-     *         been registered.
-     */
-    public Branch getBranch(DN branchDN) {
-        return branches.get(branchDN);
-    }
-
-    /**
-     * Registers the provided branch in this template file.
-     *
-     * @param branch
-     *            The branch to be registered.
-     */
-    public void registerBranch(Branch branch) {
-        branches.put(branch.getBranchDN(), branch);
-    }
-
-    /**
-     * Retrieves the set of templates defined in this template file.
-     *
-     * @return The set of templates defined in this template file.
-     */
-    public Map<String, Template> getTemplates() {
-        return templates;
-    }
-
-    /**
-     * Retrieves the template with the specified name.
-     *
-     * @param lowerName
-     *            The name of the template to retrieve, in all lowercase
-     *            characters.
-     * @return The requested template, or <CODE>null</CODE> if there is no such
-     *         template.
-     */
-    public Template getTemplate(String lowerName) {
-        return templates.get(lowerName);
-    }
-
-    /**
-     * Registers the provided template for use in this template file.
-     *
-     * @param template
-     *            The template to be registered.
-     */
-    public void registerTemplate(Template template) {
-        templates.put(template.getName().toLowerCase(), template);
-    }
-
-    /**
-     * Retrieves the random number generator for this template file.
-     *
-     * @return The random number generator for this template file.
-     */
-    public Random getRandom() {
+    Random getRandom() {
         return random;
     }
 
-    /**
-     * Reads the first and last names in standard files or use default values if
-     * files can't be read.
-     */
-    private void readNames() {
-        firstNames = readNamesFromFile(FIRST_NAME_FILE);
-        if (firstNames == null) {
-            firstNames = new String[] { "Christophe", "Gael", "Gary", "Jean-Noel", "Laurent", "Ludovic", "Mark",
-                "Matthew", "Nicolas", "Violette" };
-        }
-        lastNames = readNamesFromFile(LAST_NAME_FILE);
-        if (lastNames == null) {
-            lastNames = new String[] { "Maahs", "Maas", "Mabes", "Mabson", "Mabuchi", "Mac", "Mac Maid", "MacAdams",
-                "MacArthur", "MacCarthy" };
-        }
-    }
-
-    /**
-     * Returns an array of names read in the provided file, or null if a problem
-     * occurs.
-     */
-    private String[] readNamesFromFile(String fileName) {
-        String[] names = null;
-        File file = getFile(fileName);
-        if (file != null) {
-            try {
-                List<String> nameList = readDataFile(file);
-                names = new String[nameList.size()];
-                nameList.toArray(names);
-            } catch (IOException e) {
-                // TODO : I18N
-                StaticUtils.DEFAULT_LOG.error("Unable to read names file {}", fileName);
-            }
-        }
-        return names;
-    }
-
-    /**
-     * Read a file of data, and return a list containing one item per line.
-     */
-    private List<String> readDataFile(File file) throws FileNotFoundException, IOException {
-        List<String> data = new ArrayList<String>();
-        BufferedReader reader = null;
+    private void retrieveFirstAndLastNames() throws IOException {
+        BufferedReader first = null;
         try {
-            reader = new BufferedReader(new FileReader(file));
-            while (true) {
-                String line = reader.readLine();
-                if (line == null) {
-                    break;
-                } else {
-                    data.add(line);
-                }
+            first = getReader(FIRST_NAME_FILE);
+            if (first == null) {
+                throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_COULD_NOT_FIND_NAME_FILE.get(FIRST_NAME_FILE));
             }
+            final List<String> names = readLines(first);
+            firstNames = names.toArray(new String[names.size()]);
         } finally {
-            if (reader != null) {
-                reader.close();
-            }
+            StaticUtils.closeSilently(first);
         }
-        return data;
+
+        BufferedReader last = null;
+        try {
+            last = getReader(LAST_NAME_FILE);
+            if (last == null) {
+                throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_COULD_NOT_FIND_NAME_FILE.get(LAST_NAME_FILE));
+            }
+            final List<String> names = readLines(last);
+            lastNames = names.toArray(new String[names.size()]);
+        } finally {
+            StaticUtils.closeSilently(first);
+        }
     }
 
     /**
@@ -463,22 +273,21 @@
      * names and the number of last names being relatively prime. This method
      * should be called before beginning generation of each template entry.
      */
-    public void nextFirstAndLastNames() {
+    void nextFirstAndLastNames() {
         firstName = firstNames[firstNameIndex++];
         lastName = lastNames[lastNameIndex++];
 
-        // If we've already exhausted every possible combination, then append an
-        // integer to the last name.
+        // If we've already exhausted every possible combination
+        // then append an integer to the last name.
         if (nameUniquenessCounter > 1) {
             lastName += nameUniquenessCounter;
         }
 
         if (firstNameIndex >= firstNames.length) {
-            // We're at the end of the first name list, so start over. If the
-            // first
-            // name list is larger than the last name list, then we'll also need
-            // to
-            // set the last name index to the next loop counter position.
+            // We're at the end of the first name list, so start over.
+            // If the first name list is larger than the last name list,
+            // then we'll also need to set the last name index
+            // to the next loop counter position.
             firstNameIndex = 0;
             if (firstNames.length > lastNames.length) {
                 lastNameIndex = ++nameLoopCounter;
@@ -490,11 +299,10 @@
         }
 
         if (lastNameIndex >= lastNames.length) {
-            // We're at the end of the last name list, so start over. If the
-            // last
-            // name list is larger than the first name list, then we'll also
-            // need to
-            // set the first name index to the next loop counter position.
+            // We're at the end of the last name list, so start over.
+            // If the last name list is larger than the first name list,
+            // then we'll also need to set the first name index
+            // to the next loop counter position.
             lastNameIndex = 0;
             if (lastNames.length > firstNames.length) {
                 firstNameIndex = ++nameLoopCounter;
@@ -506,29 +314,35 @@
         }
     }
 
-    /**
-     * Retrieves the first name value that should be used for the current entry.
-     *
-     * @return The first name value that should be used for the current entry.
-     */
-    public String getFirstName() {
+    String getFirstName() {
         return firstName;
     }
 
-    /**
-     * Retrieves the last name value that should be used for the current entry.
-     *
-     * @return The last name value that should be used for the current entry.
-     */
-    public String getLastName() {
+    String getLastName() {
         return lastName;
     }
 
     /**
-     * Parses the contents of the specified file as a MakeLDIF template file
-     * definition.
+     * Parses the contents of the default template file definition, that will be
+     * used to generate entries.
      *
-     * @param filename
+     * @param warnings
+     *            A list into which any warnings identified may be placed.
+     * @throws IOException
+     *             If a problem occurs while attempting to read data from the
+     *             default template file.
+     * @throws DecodeException
+     *             If any other problem occurs while parsing the template file.
+     */
+    void parse(List<LocalizableMessage> warnings) throws IOException, DecodeException {
+        parse(DEFAULT_TEMPLATE_PATH, warnings);
+    }
+
+    /**
+     * Parses the contents of the provided file as an entry generator template
+     * file definition.
+     *
+     * @param templateFilename
      *            The name of the file containing the template data.
      * @param warnings
      *            A list into which any warnings identified may be placed.
@@ -538,37 +352,31 @@
      * @throws DecodeException
      *             If any other problem occurs while parsing the template file.
      */
-    public void parse(String filename, List<LocalizableMessage> warnings) throws IOException, DecodeException {
-        ArrayList<String> fileLines = new ArrayList<String>();
-
-        templatePath = null;
-        File f = getFile(filename);
-        if ((f == null) || (!f.exists())) {
-            LocalizableMessage message = ERR_ENTRY_GENERATOR_COULD_NOT_FIND_TEMPLATE_FILE.get(filename);
-            throw new IOException(message.toString());
-        } else {
-            templatePath = f.getParentFile().getAbsolutePath();
-        }
-
-        BufferedReader reader = new BufferedReader(new FileReader(f));
-        while (true) {
-            String line = reader.readLine();
-            if (line == null) {
-                break;
-            } else {
-                fileLines.add(line);
+    void parse(String templateFilename, List<LocalizableMessage> warnings) throws IOException, DecodeException {
+        BufferedReader templateReader = null;
+        try {
+            templateReader = getReader(templateFilename);
+            if (templateReader == null) {
+                throw DecodeException.fatalError(
+                        ERR_ENTRY_GENERATOR_COULD_NOT_FIND_TEMPLATE_FILE.get(templateFilename));
             }
+            if (resourcePath == null) {
+                // Use the template file directory as resource path
+                final File file = getFile(templateFilename);
+                if (file != null) {
+                    resourcePath = file.getParentFile().getAbsolutePath();
+                }
+            }
+            final List<String> fileLines = readLines(templateReader);
+            final String[] lines = fileLines.toArray(new String[fileLines.size()]);
+            parse(lines, warnings);
+        } finally {
+            StaticUtils.closeSilently(templateReader);
         }
-
-        reader.close();
-
-        String[] lines = new String[fileLines.size()];
-        fileLines.toArray(lines);
-        parse(lines, warnings);
     }
 
     /**
-     * Parses the data read from the provided input stream as a MakeLDIF
+     * Parses the contents of the provided input stream as an entry generator
      * template file definition.
      *
      * @param inputStream
@@ -581,28 +389,48 @@
      * @throws DecodeException
      *             If any other problem occurs while parsing the template.
      */
-    public void parse(InputStream inputStream, List<LocalizableMessage> warnings) throws IOException, DecodeException {
-        ArrayList<String> fileLines = new ArrayList<String>();
-
-        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
-        while (true) {
-            String line = reader.readLine();
-            if (line == null) {
-                break;
-            } else {
-                fileLines.add(line);
-            }
+    void parse(InputStream inputStream, List<LocalizableMessage> warnings) throws IOException, DecodeException {
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new InputStreamReader(inputStream));
+            final List<String> fileLines = readLines(reader);
+            final String[] lines = fileLines.toArray(new String[fileLines.size()]);
+            parse(lines, warnings);
+        } finally {
+            StaticUtils.closeSilently(reader);
         }
+    }
 
-        reader.close();
+    private static final String INCLUDE_LABEL = "include ";
+    private static final String DEFINE_LABEL = "define ";
+    private static final String BRANCH_LABEL = "branch: ";
+    private static final String TEMPLATE_LABEL = "template: ";
+    private static final String SUBORDINATE_TEMPLATE_LABEL = "subordinatetemplate: ";
+    private static final String RDNATTR_LABEL = "rdnattr: ";
+    private static final String EXTENDS_LABEL = "extends: ";
 
-        String[] lines = new String[fileLines.size()];
-        fileLines.toArray(lines);
-        parse(lines, warnings);
+    /**
+     * Structure to hold template data during parsing of the template.
+     */
+    private static class TemplateData {
+        final Map<String, TemplateTag> tags = new LinkedHashMap<String, TemplateTag>();
+        final Map<DN, Branch> branches = new LinkedHashMap<DN, Branch>();
+        final Map<String, Template> templates = new LinkedHashMap<String, Template>();
     }
 
     /**
-     * Parses the provided data as a MakeLDIF template file definition.
+     * Enumeration of elements that act as "container" of other elements.
+     */
+    private enum Element {
+        BRANCH, TEMPLATE;
+
+        String getLabel() {
+            return toString().toLowerCase();
+        }
+    }
+
+    /**
+     * Parses the provided lines as an entry generator template file definition.
      *
      * @param lines
      *            The lines that make up the template file.
@@ -611,168 +439,183 @@
      * @throws DecodeException
      *             If any other problem occurs while parsing the template lines.
      */
-    public void parse(String[] lines, List<LocalizableMessage> warnings) throws DecodeException {
-        // Create temporary variables that will be used to hold the data read.
-        LinkedHashMap<String, TemplateTag> templateFileIncludeTags = new LinkedHashMap<String, TemplateTag>();
-        LinkedHashMap<String, String> templateFileConstants = new LinkedHashMap<String, String>();
-        LinkedHashMap<DN, Branch> templateFileBranches = new LinkedHashMap<DN, Branch>();
-        LinkedHashMap<String, Template> templateFileTemplates = new LinkedHashMap<String, Template>();
+    void parse(final String[] lines, final List<LocalizableMessage> warnings) throws DecodeException {
+        TemplateData templateData = new TemplateData();
 
         for (int lineNumber = 0; lineNumber < lines.length; lineNumber++) {
-            String line = lines[lineNumber];
+            final String line = replaceConstants(lines[lineNumber], lineNumber, constants, warnings);
 
-            line = replaceConstants(line, lineNumber, templateFileConstants, warnings);
-
-            String lowerLine = line.toLowerCase();
+            final String lowerLine = line.toLowerCase();
             if ((line.length() == 0) || line.startsWith("#")) {
                 // This is a comment or a blank line, so we'll ignore it.
                 continue;
-            } else if (lowerLine.startsWith("include ")) {
-                // This should be an include definition. The next element should
-                // be the
-                // name of the class. Load and instantiate it and make sure
-                // there are
-                // no conflicts.
-                String className = line.substring(8).trim();
-
-                Class<?> tagClass;
-                try {
-                    tagClass = Class.forName(className);
-                } catch (Exception e) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_CANNOT_LOAD_TAG_CLASS.get(className);
-                    throw DecodeException.fatalError(message, e);
-                }
-
-                TemplateTag tag;
-                try {
-                    tag = (TemplateTag) tagClass.newInstance();
-                } catch (Exception e) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_CANNOT_INSTANTIATE_TAG.get(className);
-                    throw DecodeException.fatalError(message, e);
-                }
-
-                String lowerName = tag.getName().toLowerCase();
-                if (registeredTags.containsKey(lowerName) || templateFileIncludeTags.containsKey(lowerName)) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_CONFLICTING_TAG_NAME.get(className, tag.getName());
-                    throw DecodeException.fatalError(message);
-                }
-
-                templateFileIncludeTags.put(lowerName, tag);
-            } else if (lowerLine.startsWith("define ")) {
-                // This should be a constant definition. The rest of the line
-                // should
-                // contain the constant name, an equal sign, and the constant
-                // value.
-                int equalPos = line.indexOf('=', 7);
-                if (equalPos < 0) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_DEFINE_MISSING_EQUALS.get(lineNumber);
-                    throw DecodeException.fatalError(message);
-                }
-
-                String name = line.substring(7, equalPos).trim();
-                if (name.length() == 0) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_DEFINE_NAME_EMPTY.get(lineNumber);
-                    throw DecodeException.fatalError(message);
-                }
-
-                String lowerName = name.toLowerCase();
-                if (templateFileConstants.containsKey(lowerName)) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_CONFLICTING_CONSTANT_NAME.get(name, lineNumber);
-                    throw DecodeException.fatalError(message);
-                }
-
-                String value = line.substring(equalPos + 1);
-                if (value.length() == 0) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_WARNING_DEFINE_VALUE_EMPTY.get(name, lineNumber);
-                    warnings.add(message);
-                }
-
-                templateFileConstants.put(lowerName, value);
-            } else if (lowerLine.startsWith("branch: ")) {
-                int startLineNumber = lineNumber;
-                ArrayList<String> lineList = new ArrayList<String>();
-                lineList.add(line);
-                while (true) {
-                    lineNumber++;
-                    if (lineNumber >= lines.length) {
-                        break;
-                    }
-
-                    line = lines[lineNumber];
-                    if (line.length() == 0) {
-                        break;
-                    } else {
-                        line = replaceConstants(line, lineNumber, templateFileConstants, warnings);
-                        lineList.add(line);
-                    }
-                }
-
-                String[] branchLines = new String[lineList.size()];
-                lineList.toArray(branchLines);
-
-                Branch b = parseBranchDefinition(branchLines, lineNumber, templateFileIncludeTags, warnings);
-                DN branchDN = b.getBranchDN();
-                if (templateFileBranches.containsKey(branchDN)) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_CONFLICTING_BRANCH_DN.get(
-                            String.valueOf(branchDN), startLineNumber);
-                    throw DecodeException.fatalError(message);
-                } else {
-                    templateFileBranches.put(branchDN, b);
-                }
-            } else if (lowerLine.startsWith("template: ")) {
-                int startLineNumber = lineNumber;
-                ArrayList<String> lineList = new ArrayList<String>();
-                lineList.add(line);
-                while (true) {
-                    lineNumber++;
-                    if (lineNumber >= lines.length) {
-                        break;
-                    }
-
-                    line = lines[lineNumber];
-                    if (line.length() == 0) {
-                        break;
-                    } else {
-                        line = replaceConstants(line, lineNumber, templateFileConstants, warnings);
-                        lineList.add(line);
-                    }
-                }
-
-                String[] templateLines = new String[lineList.size()];
-                lineList.toArray(templateLines);
-
-                Template t = parseTemplateDefinition(templateLines, startLineNumber, templateFileIncludeTags,
-                        templateFileTemplates, warnings);
-                String lowerName = t.getName().toLowerCase();
-                if (templateFileTemplates.containsKey(lowerName)) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_CONFLICTING_TEMPLATE_NAME.get(
-                            String.valueOf(t.getName()), startLineNumber);
-                    throw DecodeException.fatalError(message);
-                } else {
-                    templateFileTemplates.put(lowerName, t);
-                }
+            } else if (lowerLine.startsWith(INCLUDE_LABEL)) {
+                parseInclude(line, templateData.tags);
+            } else if (lowerLine.startsWith(DEFINE_LABEL)) {
+                parseDefine(lineNumber, line, constants, warnings);
+            } else if (lowerLine.startsWith(BRANCH_LABEL)) {
+                lineNumber = parseBranch(lineNumber, line, lines, templateData, warnings);
+            } else if (lowerLine.startsWith(TEMPLATE_LABEL)) {
+                lineNumber = parseTemplate(lineNumber, line, lines, templateData, warnings);
             } else {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_UNEXPECTED_TEMPLATE_FILE_LINE.get(line, lineNumber);
-                throw DecodeException.fatalError(message);
+                throw DecodeException.fatalError(
+                        ERR_ENTRY_GENERATOR_UNEXPECTED_TEMPLATE_FILE_LINE.get(line, lineNumber));
             }
         }
 
-        // If we've gotten here, then we're almost done. We just need to
-        // finalize
-        // the branch and template definitions and then update the template file
-        // variables.
-        for (Branch b : templateFileBranches.values()) {
-            b.completeBranchInitialization(templateFileTemplates);
+        // Finalize the branch and template definitions
+        // and then update the template file variables.
+        for (Branch b : templateData.branches.values()) {
+            b.completeBranchInitialization(templateData.templates);
         }
 
-        for (Template t : templateFileTemplates.values()) {
-            t.completeTemplateInitialization(templateFileTemplates);
+        for (Template t : templateData.templates.values()) {
+            t.completeTemplateInitialization(templateData.templates);
         }
 
-        registeredTags.putAll(templateFileIncludeTags);
-        constants.putAll(templateFileConstants);
-        branches.putAll(templateFileBranches);
-        templates.putAll(templateFileTemplates);
+        registeredTags.putAll(templateData.tags);
+        branches.putAll(templateData.branches);
+        templates.putAll(templateData.templates);
+
+        // Initialize iterator on branches and current branch used
+        // to read entries
+        if (branchesIterator == null) {
+            branchesIterator = branches.values().iterator();
+            if (branchesIterator.hasNext()) {
+                currentBranch = branchesIterator.next();
+            }
+        }
+    }
+
+    private void parseInclude(final String line, final Map<String, TemplateTag> templateFileIncludeTags)
+            throws DecodeException {
+        // The next element should be the name of the class.
+        // Load and instantiate it and make sure there are no conflicts.
+        final String className = line.substring(INCLUDE_LABEL.length()).trim();
+
+        Class<?> tagClass = null;
+        try {
+            tagClass = Class.forName(className);
+        } catch (Exception e) {
+            final LocalizableMessage message = ERR_ENTRY_GENERATOR_CANNOT_LOAD_TAG_CLASS.get(className);
+            throw DecodeException.fatalError(message, e);
+        }
+
+        TemplateTag tag;
+        try {
+            tag = (TemplateTag) tagClass.newInstance();
+        } catch (Exception e) {
+            final LocalizableMessage message = ERR_ENTRY_GENERATOR_CANNOT_INSTANTIATE_TAG.get(className);
+            throw DecodeException.fatalError(message, e);
+        }
+
+        String lowerName = tag.getName().toLowerCase();
+        if (registeredTags.containsKey(lowerName) || templateFileIncludeTags.containsKey(lowerName)) {
+            final LocalizableMessage message = ERR_ENTRY_GENERATOR_CONFLICTING_TAG_NAME.get(className, tag.getName());
+            throw DecodeException.fatalError(message);
+        }
+
+        templateFileIncludeTags.put(lowerName, tag);
+    }
+
+    private void parseDefine(final int lineNumber, final String line, final Map<String, String> templateFileConstants,
+            final List<LocalizableMessage> warnings) throws DecodeException {
+        // The rest of the line should contain the constant name,
+        // an equal sign, and the constant value.
+        final int equalPos = line.indexOf('=', DEFINE_LABEL.length());
+        if (equalPos < 0) {
+            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_DEFINE_MISSING_EQUALS.get(lineNumber));
+        }
+
+        final String name = line.substring(DEFINE_LABEL.length(), equalPos).trim();
+        if (name.length() == 0) {
+            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_DEFINE_NAME_EMPTY.get(lineNumber));
+        }
+
+        final String value = line.substring(equalPos + 1);
+        if (value.length() == 0) {
+            warnings.add(ERR_ENTRY_GENERATOR_WARNING_DEFINE_VALUE_EMPTY.get(name, lineNumber));
+        }
+
+        final String lowerName = name.toLowerCase();
+        if (!templateFileConstants.containsKey(lowerName)) {
+            templateFileConstants.put(lowerName, value);
+        }
+    }
+
+    /**
+     * Parses the complete branch and returns the current line number at the
+     * end.
+     */
+    private int parseBranch(final int startLineNumber, final String startLine, final String[] lines,
+            final TemplateData templateData, final List<LocalizableMessage> warnings) throws DecodeException {
+        final String[] branchLines =
+                parseLinesUntilEndOfBlock(startLineNumber, startLine, lines, warnings);
+        final Branch branch = parseBranchDefinition(branchLines, startLineNumber, templateData.tags, warnings);
+        final DN branchDN = branch.getBranchDN();
+        if (templateData.branches.containsKey(branchDN)) {
+            throw DecodeException.fatalError(
+                    ERR_ENTRY_GENERATOR_CONFLICTING_BRANCH_DN.get(String.valueOf(branchDN), startLineNumber));
+        }
+        templateData.branches.put(branchDN, branch);
+        // position to next line after end of branch
+        return startLineNumber + branchLines.length;
+    }
+
+    /**
+     * Parses the complete template and returns the current line number at the
+     * end.
+     */
+    private int parseTemplate(final int startLineNumber, final String startLine, final String[] lines,
+            final TemplateData templateData, final List<LocalizableMessage> warnings) throws DecodeException {
+        final String[] templateLines =
+                parseLinesUntilEndOfBlock(startLineNumber, startLine, lines, warnings);
+        final Template template =
+                parseTemplateDefinition(startLineNumber, templateLines, templateData, warnings);
+        final String lowerName = template.getName().toLowerCase();
+        if (templateData.templates.containsKey(lowerName)) {
+            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_CONFLICTING_TEMPLATE_NAME.get(
+                    String.valueOf(template.getName()), startLineNumber));
+        }
+        templateData.templates.put(lowerName, template);
+        // position to next line after end of template
+        return startLineNumber + templateLines.length;
+    }
+
+    /**
+     * Parses lines of a block until the block ends (with an empty line) or
+     * lines ends.
+     *
+     * @param startLineNumber
+     *            Line number at beginning of block.
+     * @param startLine
+     *            First line of block.
+     * @param lines
+     *            The list of all lines in the template.
+     * @param warnings
+     *            A list into which any warnings identified may be placed.
+     * @return The lines of the block
+     */
+    private String[] parseLinesUntilEndOfBlock(final int startLineNumber, final String startLine,
+            final String[] lines, final List<LocalizableMessage> warnings) {
+        final List<String> lineList = new ArrayList<String>();
+        String line = startLine;
+        lineList.add(line);
+
+        int lineNumber = startLineNumber;
+        while (true) {
+            lineNumber++;
+            if (lineNumber >= lines.length) {
+                break;
+            }
+            line = lines[lineNumber];
+            if (line.length() == 0) {
+                break;
+            }
+            line = replaceConstants(line, lineNumber, constants, warnings);
+            lineList.add(line);
+        }
+        return lineList.toArray(new String[lineList.size()]);
     }
 
     /**
@@ -789,27 +632,26 @@
      * @return The line in which all constant variables have been replaced with
      *         their value
      */
-    private String replaceConstants(String line, int lineNumber, Map<String, String> constants,
-            List<LocalizableMessage> warnings) {
+    private String replaceConstants(final String line, final int lineNumber, final Map<String, String> constants,
+            final List<LocalizableMessage> warnings) {
+        String newLine = line;
         int closePos = line.lastIndexOf(']');
         // Loop until we've scanned all closing brackets
         do {
             // Skip escaped closing brackets
-            while (closePos > 0 && line.charAt(closePos - 1) == '\\') {
-                closePos = line.lastIndexOf(']', closePos - 1);
+            while (closePos > 0 && newLine.charAt(closePos - 1) == '\\') {
+                closePos = newLine.lastIndexOf(']', closePos - 1);
             }
             if (closePos > 0) {
-                StringBuilder lineBuffer = new StringBuilder(line);
-                int openPos = line.lastIndexOf('[', closePos);
-                // Find the opening bracket. If it's escaped, then it's not a
-                // constant
-                if ((openPos > 0 && line.charAt(openPos - 1) != '\\') || (openPos == 0)) {
-                    String constantName = line.substring(openPos + 1, closePos).toLowerCase();
-                    String constantValue = constants.get(constantName);
+                final StringBuilder lineBuffer = new StringBuilder(newLine);
+                int openPos = newLine.lastIndexOf('[', closePos);
+                // Find the opening bracket.
+                // If it's escaped, then it's not a constant
+                if ((openPos > 0 && newLine.charAt(openPos - 1) != '\\') || (openPos == 0)) {
+                    final String constantName = newLine.substring(openPos + 1, closePos).toLowerCase();
+                    final String constantValue = constants.get(constantName);
                     if (constantValue == null) {
-                        LocalizableMessage message = WARN_ENTRY_GENERATOR_WARNING_UNDEFINED_CONSTANT.get(constantName,
-                                lineNumber);
-                        warnings.add(message);
+                        warnings.add(WARN_ENTRY_GENERATOR_WARNING_UNDEFINED_CONSTANT.get(constantName, lineNumber));
                     } else {
                         lineBuffer.replace(openPos, closePos + 1, constantValue);
                     }
@@ -817,16 +659,16 @@
                 if (openPos >= 0) {
                     closePos = openPos;
                 }
-                line = lineBuffer.toString();
-                closePos = line.lastIndexOf(']', closePos);
+                newLine = lineBuffer.toString();
+                closePos = newLine.lastIndexOf(']', closePos);
             }
         } while (closePos > 0);
-        return line;
+        return newLine;
     }
 
     /**
-     * Parses the information contained in the provided set of lines as a
-     * MakeLDIF branch definition.
+     * Parses the information contained in the provided set of lines as a branch
+     * definition.
      *
      * @param branchLines
      *            The set of lines containing the branch definition.
@@ -844,80 +686,52 @@
      *             If a problem occurs during initializing any of the branch
      *             elements or during processing.
      */
-    private Branch parseBranchDefinition(String[] branchLines, int startLineNumber, Map<String, TemplateTag> tags,
-            List<LocalizableMessage> warnings) throws DecodeException {
+    private Branch parseBranchDefinition(final String[] branchLines, final int startLineNumber,
+            final Map<String, TemplateTag> tags, final List<LocalizableMessage> warnings) throws DecodeException {
         // The first line must be "branch: " followed by the branch DN.
-        String dnString = branchLines[0].substring(8).trim();
+        final String dnString = branchLines[0].substring(BRANCH_LABEL.length()).trim();
         DN branchDN;
         try {
             branchDN = DN.valueOf(dnString, schema);
         } catch (Exception e) {
-            LocalizableMessage message = ERR_ENTRY_GENERATOR_CANNOT_DECODE_BRANCH_DN.get(dnString, startLineNumber);
-            throw DecodeException.fatalError(message);
+            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_CANNOT_DECODE_BRANCH_DN.get(
+                    dnString, startLineNumber));
         }
 
-        // Create a new branch that will be used for the verification process.
-        Branch branch = new Branch(this, branchDN, null);
+        final Branch branch = new Branch(this, branchDN, schema);
 
         for (int i = 1; i < branchLines.length; i++) {
-            String line = branchLines[i];
-            String lowerLine = line.toLowerCase();
-            int lineNumber = startLineNumber + i;
+            final String line = branchLines[i];
+            final String lowerLine = line.toLowerCase();
+            final int lineNumber = startLineNumber + i;
 
             if (lowerLine.startsWith("#")) {
                 // It's a comment, so we should ignore it.
                 continue;
-            } else if (lowerLine.startsWith("subordinatetemplate: ")) {
-                // It's a subordinate template, so we'll want to parse the name
-                // and the
-                // number of entries.
-                int colonPos = line.indexOf(':', 21);
-                if (colonPos <= 21) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_BRANCH_SUBORDINATE_TEMPLATE_NO_COLON.get(
-                            lineNumber, dnString);
-                    throw DecodeException.fatalError(message);
-                }
-
-                String templateName = line.substring(21, colonPos).trim();
-
-                int numEntries;
-                try {
-                    numEntries = Integer.parseInt(line.substring(colonPos + 1).trim());
-                    if (numEntries < 0) {
-                        LocalizableMessage message = ERR_ENTRY_GENERATOR_BRANCH_SUBORDINATE_INVALID_NUM_ENTRIES.get(
-                                lineNumber, dnString, numEntries, templateName);
-                        throw DecodeException.fatalError(message);
-                    } else if (numEntries == 0) {
-                        LocalizableMessage message = WARN_ENTRY_GENERATOR_BRANCH_SUBORDINATE_ZERO_ENTRIES.get(
-                                lineNumber, dnString, templateName);
-                        warnings.add(message);
-                    }
-
-                    branch.addSubordinateTemplate(templateName, numEntries);
-                } catch (NumberFormatException nfe) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_BRANCH_SUBORDINATE_CANT_PARSE_NUMENTRIES.get(
-                            templateName, lineNumber, dnString);
-                    throw DecodeException.fatalError(message);
-                }
+            } else if (lowerLine.startsWith(SUBORDINATE_TEMPLATE_LABEL)) {
+                final Pair<String, Integer> pair =
+                        parseSubordinateTemplate(lineNumber, line, Element.BRANCH, dnString, warnings);
+                final String templateName = pair.getFirst();
+                final int numEntries = pair.getSecond();
+                branch.addSubordinateTemplate(templateName, numEntries);
             } else {
-                TemplateLine templateLine =
-                        parseTemplateLine(line, lowerLine, lineNumber, branch, null, tags, warnings);
+                final TemplateLine templateLine =
+                        parseTemplateLine(line, lineNumber, branch, null, Element.BRANCH, tags, warnings);
                 branch.addExtraLine(templateLine);
             }
         }
-
         return branch;
     }
 
     /**
      * Parses the information contained in the provided set of lines as a
-     * MakeLDIF template definition.
+     * template definition.
      *
-     * @param templateLines
-     *            The set of lines containing the template definition.
      * @param startLineNumber
      *            The line number in the template file on which the first of the
      *            template lines appears.
+     * @param templateLines
+     *            The set of lines containing the template definition.
      * @param tags
      *            The set of defined tags from the template file. Note that this
      *            does not include the tags that are always registered by
@@ -931,121 +745,75 @@
      *             If a problem occurs during initializing any of the template
      *             elements or during processing.
      */
-    private Template parseTemplateDefinition(String[] templateLines, int startLineNumber,
-            Map<String, TemplateTag> tags, Map<String, Template> definedTemplates, List<LocalizableMessage> warnings)
-            throws DecodeException {
-        // The first line must be "template: " followed by the template name.
-        String templateName = templateLines[0].substring(10).trim();
+    private Template parseTemplateDefinition(final int startLineNumber, final String[] templateLines,
+            final TemplateData templateData, final List<LocalizableMessage> warnings) throws DecodeException {
+        final Map<String, TemplateTag> tags = templateData.tags;
+        final Map<String, Template> definedTemplates = templateData.templates;
 
-        // The next line may start with either "extends: ", "rdnAttr: ", or
-        // "subordinateTemplate: ". Keep reading until we find something that's
-        // not one of those.
-        int arrayLineNumber = 1;
+        // The first line must be "template: " followed by the template name.
+        final String templateName = templateLines[0].substring(TEMPLATE_LABEL.length()).trim();
+
+        // The next line may be with an "extends", a rdn attribute, or
+        // a subordinate template. Keep reading until we find something
+        // that's not one of those.
+        int lineCount = 1;
         Template parentTemplate = null;
-        AttributeType[] rdnAttributes = null;
-        ArrayList<String> subTemplateNames = new ArrayList<String>();
-        ArrayList<Integer> entriesPerTemplate = new ArrayList<Integer>();
-        for (; arrayLineNumber < templateLines.length; arrayLineNumber++) {
-            int lineNumber = startLineNumber + arrayLineNumber;
-            String line = templateLines[arrayLineNumber];
-            String lowerLine = line.toLowerCase();
+        final List<AttributeType> rdnAttributes = new ArrayList<AttributeType>();
+        final List<String> subordinatesTemplateNames = new ArrayList<String>();
+        final List<Integer> numberOfentriesPerTemplate = new ArrayList<Integer>();
+
+        for (; lineCount < templateLines.length; lineCount++) {
+            final int lineNumber = startLineNumber + lineCount;
+            final String line = templateLines[lineCount];
+            final String lowerLine = line.toLowerCase();
 
             if (lowerLine.startsWith("#")) {
                 // It's a comment. Ignore it.
                 continue;
-            } else if (lowerLine.startsWith("extends: ")) {
-                String parentTemplateName = line.substring(9).trim();
+            } else if (lowerLine.startsWith(EXTENDS_LABEL)) {
+                final String parentTemplateName = line.substring(EXTENDS_LABEL.length()).trim();
                 parentTemplate = definedTemplates.get(parentTemplateName.toLowerCase());
                 if (parentTemplate == null) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TEMPLATE_INVALID_PARENT_TEMPLATE.get(
-                            parentTemplateName, lineNumber, templateName);
-                    throw DecodeException.fatalError(message);
+                    throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_TEMPLATE_INVALID_PARENT_TEMPLATE.get(
+                            parentTemplateName, lineNumber, templateName));
                 }
-            } else if (lowerLine.startsWith("rdnattr: ")) {
+            } else if (lowerLine.startsWith(RDNATTR_LABEL)) {
                 // This is the set of RDN attributes. If there are multiple,
-                // they may
-                // be separated by plus signs.
-                ArrayList<AttributeType> attrList = new ArrayList<AttributeType>();
-                String rdnAttrNames = lowerLine.substring(9).trim();
-                StringTokenizer tokenizer = new StringTokenizer(rdnAttrNames, "+");
+                // they may be separated by plus signs.
+                final String rdnAttrNames = lowerLine.substring(RDNATTR_LABEL.length()).trim();
+                final StringTokenizer tokenizer = new StringTokenizer(rdnAttrNames, "+");
                 while (tokenizer.hasMoreTokens()) {
-                    attrList.add(schema.getAttributeType(tokenizer.nextToken()));
+                    rdnAttributes.add(schema.getAttributeType(tokenizer.nextToken()));
                 }
-
-                rdnAttributes = new AttributeType[attrList.size()];
-                attrList.toArray(rdnAttributes);
-            } else if (lowerLine.startsWith("subordinatetemplate: ")) {
-                // It's a subordinate template, so we'll want to parse the name
-                // and the
-                // number of entries.
-                int colonPos = line.indexOf(':', 21);
-                if (colonPos <= 21) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TEMPLATE_SUBORDINATE_TEMPLATE_NO_COLON.get(
-                            lineNumber, templateName);
-                    throw DecodeException.fatalError(message);
-                }
-
-                String subTemplateName = line.substring(21, colonPos).trim();
-
-                int numEntries;
-                try {
-                    numEntries = Integer.parseInt(line.substring(colonPos + 1).trim());
-                    if (numEntries < 0) {
-                        LocalizableMessage message = ERR_ENTRY_GENERATOR_TEMPLATE_SUBORDINATE_INVALID_NUM_ENTRIES.get(
-                                lineNumber, templateName, numEntries, subTemplateName);
-                        throw DecodeException.fatalError(message);
-                    } else if (numEntries == 0) {
-                        LocalizableMessage message = WARN_ENTRY_GENERATOR_TEMPLATE_SUBORDINATE_ZERO_ENTRIES.get(
-                                lineNumber, templateName, subTemplateName);
-                        warnings.add(message);
-                    }
-
-                    subTemplateNames.add(subTemplateName);
-                    entriesPerTemplate.add(numEntries);
-                } catch (NumberFormatException nfe) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TEMPLATE_SUBORDINATE_CANT_PARSE_NUMENTRIES.get(
-                            subTemplateName, lineNumber, templateName);
-                    throw DecodeException.fatalError(message);
-                }
+            } else if (lowerLine.startsWith(SUBORDINATE_TEMPLATE_LABEL)) {
+                final Pair<String, Integer> pair =
+                        parseSubordinateTemplate(lineNumber, line, Element.BRANCH, templateName, warnings);
+                subordinatesTemplateNames.add(pair.getFirst());
+                numberOfentriesPerTemplate.add(pair.getSecond());
             } else {
-                // It's something we don't recognize, so it must be a template
-                // line.
+                // Not recognized, it must be a template line.
                 break;
             }
         }
 
-        // Create a new template that will be used for the verification process.
-        String[] subordinateTemplateNames = new String[subTemplateNames.size()];
-        subTemplateNames.toArray(subordinateTemplateNames);
+        final List<TemplateLine> parentLines =
+                (parentTemplate == null) ? new ArrayList<TemplateLine>() : parentTemplate.getTemplateLines();
 
-        int[] numEntriesPerTemplate = new int[entriesPerTemplate.size()];
-        for (int i = 0; i < numEntriesPerTemplate.length; i++) {
-            numEntriesPerTemplate[i] = entriesPerTemplate.get(i);
-        }
+        final Template template = new Template(this, templateName, rdnAttributes, subordinatesTemplateNames,
+                numberOfentriesPerTemplate, parentLines);
 
-        TemplateLine[] parsedLines;
-        if (parentTemplate == null) {
-            parsedLines = new TemplateLine[0];
-        } else {
-            TemplateLine[] parentLines = parentTemplate.getTemplateLines();
-            parsedLines = new TemplateLine[parentLines.length];
-            System.arraycopy(parentLines, 0, parsedLines, 0, parentLines.length);
-        }
-
-        Template template = new Template(this, templateName, rdnAttributes, subordinateTemplateNames,
-                numEntriesPerTemplate, parsedLines);
-
-        for (; arrayLineNumber < templateLines.length; arrayLineNumber++) {
-            String line = templateLines[arrayLineNumber];
-            String lowerLine = line.toLowerCase();
-            int lineNumber = startLineNumber + arrayLineNumber;
+        // Add lines to template
+        for (; lineCount < templateLines.length; lineCount++) {
+            final String line = templateLines[lineCount];
+            final String lowerLine = line.toLowerCase();
 
             if (lowerLine.startsWith("#")) {
-                // It's a comment, so we should ignore it.
+                // It's a comment, ignore it.
                 continue;
             } else {
-                TemplateLine templateLine = parseTemplateLine(line, lowerLine, lineNumber, null, template, tags,
-                        warnings);
+                final int lineNumber = startLineNumber + lineCount;
+                final TemplateLine templateLine =
+                        parseTemplateLine(line, lineNumber, null, template, Element.TEMPLATE, tags, warnings);
                 template.addTemplateLine(templateLine);
             }
         }
@@ -1053,6 +821,52 @@
         return template;
     }
 
+    /**
+     * Parses a subordinate template for a template or a branch.
+     * <p>
+     * A subordinate template has a name and a number of entries.
+     *
+     * @param lineNumber
+     *            Line number of definition.
+     * @param line
+     *            Line containing the definition.
+     * @param element
+     *            indicates the kind of element to use in error messages.
+     * @param elementName
+     *            Name of the branch or template.
+     * @param warnings
+     *            A list into which any warnings identified may be placed.
+     * @return the pair (template name, number of entries in template)
+     */
+    private Pair<String, Integer> parseSubordinateTemplate(final int lineNumber, final String line,
+            final Element element, final String elementName, final List<LocalizableMessage> warnings)
+            throws DecodeException {
+        // It's a subordinate template, so we'll want to parse
+        // the template name and the number of entries.
+        final int colonPos = line.indexOf(':', SUBORDINATE_TEMPLATE_LABEL.length());
+        if (colonPos <= SUBORDINATE_TEMPLATE_LABEL.length()) {
+            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_SUBORDINATE_TEMPLATE_NO_COLON.get(
+                    lineNumber, element.getLabel(), elementName));
+        }
+
+        final String templateName = line.substring(SUBORDINATE_TEMPLATE_LABEL.length(), colonPos).trim();
+
+        try {
+            final int numEntries = Integer.parseInt(line.substring(colonPos + 1).trim());
+            if (numEntries < 0) {
+                throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_SUBORDINATE_INVALID_NUM_ENTRIES.get(
+                        lineNumber, element.getLabel(), elementName, numEntries, templateName));
+            } else if (numEntries == 0) {
+                warnings.add(WARN_ENTRY_GENERATOR_SUBORDINATE_ZERO_ENTRIES.get(
+                        lineNumber, element.getLabel(), elementName, templateName));
+            }
+            return Pair.of(templateName, numEntries);
+        } catch (NumberFormatException nfe) {
+            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_SUBORDINATE_CANT_PARSE_NUMENTRIES.get(
+                    templateName, lineNumber, element.getLabel(), elementName));
+        }
+    }
+
     private static final int PARSING_STATIC_TEXT = 0;
     private static final int PARSING_REPLACEMENT_TAG = 1;
     private static final int PARSING_ATTRIBUTE_TAG = 2;
@@ -1064,8 +878,6 @@
      *
      * @param line
      *            The text of the template line.
-     * @param lowerLine
-     *            The template line in all lowercase characters.
      * @param lineNumber
      *            The line number on which the template line appears.
      * @param branch
@@ -1083,37 +895,26 @@
      *             If a problem occurs during initializing any of the template
      *             elements or during processing.
      */
-    private TemplateLine parseTemplateLine(String line, String lowerLine, int lineNumber, Branch branch,
-            Template template, Map<String, TemplateTag> tags, List<LocalizableMessage> warnings)
-            throws DecodeException {
+    private TemplateLine parseTemplateLine(final String line, final int lineNumber, final Branch branch,
+            final Template template, final Element element, final Map<String, TemplateTag> tags,
+            final List<LocalizableMessage> warnings) throws DecodeException {
+        final String elementName = element == Element.BRANCH ? branch.getBranchDN().toString() : template.getName();
+
         // The first component must be the attribute type, followed by a colon.
-        int colonPos = lowerLine.indexOf(':');
+        final String lowerLine = line.toLowerCase();
+        final int colonPos = lowerLine.indexOf(':');
         if (colonPos < 0) {
-            if (branch == null) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_NO_COLON_IN_TEMPLATE_LINE.get(lineNumber,
-                        template.getName());
-                throw DecodeException.fatalError(message);
-            } else {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_NO_COLON_IN_BRANCH_EXTRA_LINE.get(lineNumber,
-                        String.valueOf(branch.getBranchDN()));
-                throw DecodeException.fatalError(message);
-            }
+            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_NO_COLON_IN_TEMPLATE_LINE.get(
+                    lineNumber, element.getLabel(), elementName));
         } else if (colonPos == 0) {
-            if (branch == null) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_NO_ATTR_IN_TEMPLATE_LINE.get(lineNumber,
-                        template.getName());
-                throw DecodeException.fatalError(message);
-            } else {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_NO_ATTR_IN_BRANCH_EXTRA_LINE.get(lineNumber,
-                        String.valueOf(branch.getBranchDN()));
-                throw DecodeException.fatalError(message);
-            }
+            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_NO_ATTR_IN_TEMPLATE_LINE.get(
+                    lineNumber, element.getLabel(), elementName));
         }
 
-        AttributeType attributeType = schema.getAttributeType(lowerLine.substring(0, colonPos));
+        final AttributeType attributeType = schema.getAttributeType(lowerLine.substring(0, colonPos));
 
         // First, check whether the value is an URL value: <attrName>:< <url>
-        int length = line.length();
+        final int length = line.length();
         int pos = colonPos + 1;
         boolean valueIsURL = false;
         boolean valueIsBase64 = false;
@@ -1132,24 +933,16 @@
         }
 
         if (pos >= length) {
-            // We've hit the end of the line with no value. We'll allow it, but
-            // add a
-            // warning.
-            if (branch == null) {
-                LocalizableMessage message = WARN_ENTRY_GENERATOR_NO_VALUE_IN_TEMPLATE_LINE.get(lineNumber,
-                        template.getName());
-                warnings.add(message);
-            } else {
-                LocalizableMessage message = WARN_ENTRY_GENERATOR_NO_VALUE_IN_BRANCH_EXTRA_LINE.get(lineNumber,
-                        String.valueOf(branch.getBranchDN()));
-                warnings.add(message);
-            }
+            // We've hit the end of the line with no value.
+            // We'll allow it, but add a warning.
+            warnings.add(WARN_ENTRY_GENERATOR_NO_VALUE_IN_TEMPLATE_LINE.get(
+                    lineNumber, element.getLabel(), elementName));
         }
 
         int phase = PARSING_STATIC_TEXT;
         int previousPhase = PARSING_STATIC_TEXT;
 
-        ArrayList<TemplateTag> tagList = new ArrayList<TemplateTag>();
+        final List<TemplateTag> tagList = new ArrayList<TemplateTag>();
         StringBuilder buffer = new StringBuilder();
 
         for (; pos < length; pos++) {
@@ -1199,7 +992,6 @@
                         parseReplacementTag(buffer.toString(), branch, template, lineNumber, tags, warnings);
                     tagList.add(t);
                     buffer = new StringBuilder();
-
                     phase = PARSING_STATIC_TEXT;
                     break;
                 default:
@@ -1242,13 +1034,10 @@
                 tagList.add(t);
             }
         } else {
-            LocalizableMessage message = ERR_ENTRY_GENERATOR_INCOMPLETE_TAG.get(lineNumber);
-            throw DecodeException.fatalError(message);
+            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_INCOMPLETE_TAG.get(lineNumber));
         }
 
-        TemplateTag[] tagArray = new TemplateTag[tagList.size()];
-        tagList.toArray(tagArray);
-        return new TemplateLine(attributeType, lineNumber, tagArray, valueIsURL, valueIsBase64);
+        return new TemplateLine(attributeType, lineNumber, tagList, valueIsURL, valueIsBase64);
     }
 
     /**
@@ -1274,53 +1063,45 @@
      * @throws DecodeException
      *             If some problem occurs during processing.
      */
-    private TemplateTag parseReplacementTag(String tagString, Branch branch, Template template, int lineNumber,
-            Map<String, TemplateTag> tags, List<LocalizableMessage> warnings) throws DecodeException {
+    private TemplateTag parseReplacementTag(final String tagString, final Branch branch, final Template template,
+            final int lineNumber, final Map<String, TemplateTag> tags, final List<LocalizableMessage> warnings)
+            throws DecodeException {
         // The components of the replacement tag will be separated by colons,
-        // with
-        // the first being the tag name and the remainder being arguments.
-        StringTokenizer tokenizer = new StringTokenizer(tagString, ":");
-        String tagName = tokenizer.nextToken().trim();
-        String lowerTagName = tagName.toLowerCase();
+        // with the first being the tag name and the remainder being arguments.
+        final StringTokenizer tokenizer = new StringTokenizer(tagString, ":");
+        final String tagName = tokenizer.nextToken().trim();
+        final String lowerTagName = tagName.toLowerCase();
 
-        TemplateTag t = getTag(lowerTagName);
-        if (t == null) {
-            t = tags.get(lowerTagName);
-            if (t == null) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_NO_SUCH_TAG.get(tagName, lineNumber);
-                throw DecodeException.fatalError(message);
+        TemplateTag tag = getTag(lowerTagName);
+        if (tag == null) {
+            tag = tags.get(lowerTagName);
+            if (tag == null) {
+                throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_NO_SUCH_TAG.get(tagName, lineNumber));
             }
         }
 
-        ArrayList<String> argList = new ArrayList<String>();
+        final List<String> args = new ArrayList<String>();
         while (tokenizer.hasMoreTokens()) {
-            argList.add(tokenizer.nextToken().trim());
+            args.add(tokenizer.nextToken().trim());
         }
-
-        String[] args = new String[argList.size()];
-        argList.toArray(args);
+        final String[] arguments = args.toArray(new String[args.size()]);
 
         TemplateTag newTag;
         try {
-            newTag = t.getClass().newInstance();
+            newTag = tag.getClass().newInstance();
         } catch (Exception e) {
-            LocalizableMessage message = ERR_ENTRY_GENERATOR_CANNOT_INSTANTIATE_NEW_TAG.get(tagName, lineNumber,
-                    String.valueOf(e));
-            throw DecodeException.fatalError(message, e);
+            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_CANNOT_INSTANTIATE_NEW_TAG.get(
+                    tagName, lineNumber, String.valueOf(e)), e);
         }
 
         if (branch == null) {
-            newTag.initializeForTemplate(schema, this, template, args, lineNumber, warnings);
+            newTag.initializeForTemplate(schema, this, template, arguments, lineNumber, warnings);
+        } else if (newTag.allowedInBranch()) {
+            newTag.initializeForBranch(schema, this, branch, arguments, lineNumber, warnings);
         } else {
-            if (newTag.allowedInBranch()) {
-                newTag.initializeForBranch(schema, this, branch, args, lineNumber, warnings);
-            } else {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_NOT_ALLOWED_IN_BRANCH.get(newTag.getName(),
-                        lineNumber);
-                throw DecodeException.fatalError(message);
-            }
+            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_TAG_NOT_ALLOWED_IN_BRANCH.get(newTag.getName(),
+                    lineNumber));
         }
-
         return newTag;
     }
 
@@ -1343,197 +1124,234 @@
      * @throws DecodeException
      *             If some other problem occurs during processing.
      */
-    private TemplateTag parseAttributeTag(String tagString, Branch branch, Template template, int lineNumber,
-            List<LocalizableMessage> warnings) throws DecodeException {
+    private TemplateTag parseAttributeTag(final String tagString, final Branch branch,
+            final Template template, final int lineNumber, final List<LocalizableMessage> warnings)
+            throws DecodeException {
         // The attribute tag must have at least one argument, which is the name
-        // of
-        // the attribute to reference. It may have a second argument, which is
-        // the
-        // number of characters to use from the attribute value. The arguments
-        // will
-        // be delimited by colons.
-        StringTokenizer tokenizer = new StringTokenizer(tagString, ":");
-        ArrayList<String> argList = new ArrayList<String>();
+        // of the attribute to reference. It may have a second argument, which
+        // is the number of characters to use from the attribute value. The
+        // arguments will be delimited by colons.
+        final StringTokenizer tokenizer = new StringTokenizer(tagString, ":");
+        final List<String> args = new ArrayList<String>();
         while (tokenizer.hasMoreTokens()) {
-            argList.add(tokenizer.nextToken());
+            args.add(tokenizer.nextToken());
         }
+        final String[] arguments = args.toArray(new String[args.size()]);
 
-        String[] args = new String[argList.size()];
-        argList.toArray(args);
-
-        AttributeValueTag tag = new AttributeValueTag();
+        final AttributeValueTag tag = new AttributeValueTag();
         if (branch == null) {
-            tag.initializeForTemplate(schema, this, template, args, lineNumber, warnings);
+            tag.initializeForTemplate(schema, this, template, arguments, lineNumber, warnings);
         } else {
-            tag.initializeForBranch(schema, this, branch, args, lineNumber, warnings);
+            tag.initializeForBranch(schema, this, branch, arguments, lineNumber, warnings);
         }
-
         return tag;
     }
 
     /**
-     * Retrieves a File object based on the provided path. If the given path is
-     * absolute, then that absolute path will be used. If it is relative, then
-     * it will first be evaluated relative to the current working directory. If
-     * that path doesn't exist, then it will be evaluated relative to the
-     * resource path. If that path doesn't exist, then it will be evaluated
-     * relative to the directory containing the template file.
+     * Retrieves a file based on the provided path.
+     * <p>
+     * To allow retrieval of a file located in a jar, you must use
+     * {@code getReader()} method instead of this one.
+     * <p>
+     * File is searched successively in two locations :
+     * <ul>
+     * <li>Using the provided path as is.</li>
+     * <li>Using resource path + provided path.</li>
+     * </ul>
      *
-     * @param path
-     *            The path provided for the file.
-     * @return The File object for the specified path, or <CODE>null</CODE> if
-     *         the specified file could not be found.
+     * @param filePath
+     *            The path provided for the file, which can be absolute or
+     *            relative.
+     * @return the file, or <code>null</code> if it could not be found.
      */
-    public File getFile(String path) {
-        // First, see if the file exists using the given path. This will work if
-        // the file is absolute, or it's relative to the current working
-        // directory.
-        File f = new File(path);
-        if (f.exists()) {
-            return f;
+    private File getFile(final String filePath) {
+        File file = new File(filePath);
+        // try raw path first
+        if (file.exists()) {
+            return file;
         }
-
-        // If the provided path was absolute, then use it anyway, even though we
-        // couldn't find the file.
-        if (f.isAbsolute()) {
-            return f;
-        }
-
-        // Try a path relative to the resource directory.
-        String newPath = resourcePath + File.separator + path;
-        f = new File(newPath);
-        if (f.exists()) {
-            return f;
-        }
-
-        // Try a path relative to the template directory, if it's available.
-        if (templatePath != null) {
-            newPath = templatePath = File.separator + path;
-            f = new File(newPath);
-            if (f.exists()) {
-                return f;
+        // try using resource path
+        if (resourcePath != null) {
+            file = new File(resourcePath + File.separator + filePath);
+            if (file.exists()) {
+                return file;
             }
         }
-
         return null;
     }
 
     /**
-     * Retrieves the lines of the specified file as a string array. If the
-     * result is already cached, then it will be used. If the result is not
-     * cached, then the file data will be cached so that the contents can be
-     * re-used if there are multiple references to the same file.
+     * Retrieves a reader based on the provided path.
+     * <p>
+     * The path represent a file path either on the file system or in a jar.
+     * File is searched successively in three locations :
+     * <ul>
+     * <li>Using the provided path on the file system.</li>
+     * <li>Using resource path + provided path on the file system.</li>
+     * <li>Using default resources path + provided path on the file system or in
+     * a jar.</li>
+     * </ul>
      *
-     * @param file
-     *            The file for which to retrieve the contents.
-     * @return An array containing the lines of the specified file.
+     * @param filePath
+     *            The path provided for the file, which can be absolute or
+     *            relative.
+     * @return A reader on the file, or <code>null</code> if it could not be
+     *         found. It is the responsability of caller to close the returned
+     *         reader.
+     */
+    @SuppressWarnings("resource")
+    BufferedReader getReader(final String filePath) {
+        BufferedReader reader = null;
+        File file = new File(filePath);
+        try {
+            if (file.exists()) {
+                // try raw path first
+                reader = new BufferedReader(new FileReader(file));
+            } else if (resourcePath != null) {
+                // try using resource path
+                file = new File(resourcePath + File.separator + filePath);
+                if (file.exists()) {
+                    reader = new BufferedReader(new FileReader(file));
+                }
+            }
+            if (reader == null) {
+                // try to find in default resources provided
+                final InputStream stream = TemplateFile.class.getClassLoader().getResourceAsStream(
+                        new File(DEFAULT_RESOURCES_PATH, filePath).getPath());
+                if (stream != null) {
+                    reader = new BufferedReader(new InputStreamReader(stream));
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // Should never happen as we test file existence first.
+            // In any case, nothing to do as we want to return null
+        }
+        return reader;
+    }
+
+    /**
+     * Retrieves the lines of the provided reader, possibly reading them from
+     * memory cache.
+     * <p>
+     * Lines are retrieved from reader at the first call, then cached in memory
+     * for next calls, using the provided identifier.
+     * <p>
+     * Use {@code readFile()} method to avoid caching.
+     *
+     * @param label
+     *            Label used as identifier to cache the line read.
+     * @param reader
+     *            Reader to parse for lines.
+     * @return a list of lines
      * @throws IOException
      *             If a problem occurs while reading the file.
      */
-    public String[] getFileLines(File file) throws IOException {
-        String absolutePath = file.getAbsolutePath();
-        String[] lines = fileLines.get(absolutePath);
+    String[] getLines(String identifier, final BufferedReader reader) throws IOException {
+        String[] lines = fileLines.get(identifier);
         if (lines == null) {
-            ArrayList<String> lineList = new ArrayList<String>();
-
-            BufferedReader reader = new BufferedReader(new FileReader(file));
-            while (true) {
-                String line = reader.readLine();
-                if (line == null) {
-                    break;
-                } else {
-                    lineList.add(line);
-                }
-            }
-
-            reader.close();
-
-            lines = new String[lineList.size()];
-            lineList.toArray(lines);
-            lineList.clear();
-            fileLines.put(absolutePath, lines);
+            lines = readLines(reader).toArray(new String[] {});
+            fileLines.put(identifier, lines);
         }
-
         return lines;
     }
 
     /**
-     * Generates the entries and writes them to the provided entry writer.
+     * Retrieves the lines from the provided reader.
      *
-     * @param entryWriter
-     *            The entry writer that should be used to write the entries.
-     * @return The result that indicates whether processing should continue.
+     * @param reader
+     *            The reader containing the lines.
+     * @return a list of lines
      * @throws IOException
-     *             If an error occurs while writing the entry.
-     * @throws DecodeException
-     *             If some other problem occurs.
+     *             If a problem occurs while reading the lines.
      */
-    public TagResult generateEntries(EntryWriter entryWriter) throws IOException, DecodeException {
-        for (Branch b : branches.values()) {
-            TagResult result = b.writeEntries(entryWriter);
-            if (!(result.keepProcessingTemplateFile())) {
-                return result;
+    private List<String> readLines(final BufferedReader reader) throws IOException {
+        final List<String> lines = new ArrayList<String>();
+        while (true) {
+            final String line = reader.readLine();
+            if (line == null) {
+                break;
             }
+            lines.add(line);
         }
+        return lines;
+    }
 
-        entryWriter.closeEntryWriter();
-        return TagResult.SUCCESS_RESULT;
+    /** Iterator on branches that are used to read entries. */
+    private Iterator<Branch> branchesIterator;
+
+    /** Branch from which entries are currently read. */
+    private Branch currentBranch;
+
+    /** Entry to return when calling {@code nextEntry} method. */
+    private TemplateEntry nextEntry;
+
+    /**
+     * Returns {@code true} if there is another generated entry
+     * to return.
+     *
+     * @return {@code true} if another entry can be returned.
+     */
+    boolean hasNext() {
+        if (nextEntry != null) {
+            return true;
+        }
+        while (currentBranch != null) {
+            if (currentBranch.hasNext()) {
+                nextEntry = currentBranch.nextEntry();
+                return true;
+            }
+            currentBranch = branchesIterator.hasNext() ? branchesIterator.next() : null;
+        }
+        return false;
     }
 
     /**
-     * Writer of generated entries.
+     * Returns the next generated entry.
+     *
+     * @return The next entry.
+     * @throws NoSuchElementException
+     *             If this reader does not contain any more entries.
      */
-    public interface EntryWriter {
-        /**
-         * Writes the provided entry to the appropriate target.
-         *
-         * @param entry
-         *            The entry to be written.
-         * @return <CODE>true</CODE> if the entry writer will accept additional
-         *         entries, or <CODE>false</CODE> if no more entries should be
-         *         written.
-         * @throws IOException
-         *             If a problem occurs while writing the entry to its
-         *             intended destination.
-         * @throws DecodeException
-         *             If some other problem occurs.
-         */
-        public boolean writeEntry(TemplateEntry entry) throws IOException, DecodeException;
-
-        /**
-         * Notifies the entry writer that no more entries will be provided and
-         * that any associated cleanup may be performed.
-         */
-        public void closeEntryWriter();
+    Entry nextEntry() {
+        if (!hasNext()) {
+            throw new NoSuchElementException();
+        }
+        final Entry entry = nextEntry.toEntry();
+        nextEntry = null;
+        return entry;
     }
 
     /**
      * Represents a branch that should be included in the generated results. A
      * branch may or may not have subordinate entries.
      */
-    static class Branch {
+    static final class Branch {
         /** The DN for this branch entry. */
-        private DN branchDN;
+        private final DN branchDN;
 
         /**
          * The number of entries that should be created below this branch for
          * each subordinate template.
          */
-        private int[] numEntriesPerTemplate;
+        private final List<Integer> numEntriesPerTemplate;
 
         /** The names of the subordinate templates for this branch. */
-        private String[] subordinateTemplateNames;
+        private final List<String> subordinateTemplateNames;
 
         /** The set of subordinate templates for this branch. */
-        private Template[] subordinateTemplates;
+        private List<Template> subordinateTemplates;
 
         /** The set of template lines that correspond to the RDN components. */
-        private TemplateLine[] rdnLines;
+        private final List<TemplateLine> rdnLines;
 
         /** The set of extra lines that should be included in this branch entry. */
-        private TemplateLine[] extraLines;
+        private final List<TemplateLine> extraLines;
 
-        private Schema schema;
+        /** Entry to return when calling {@code nextEntry} method. */
+        private TemplateEntry nextEntry;
+
+        /** Index of subordinate template currently read. */
+        private int currentSubTemplateIndex;
 
         /**
          * Creates a new branch with the provided information.
@@ -1544,9 +1362,12 @@
          *            The DN for this branch entry.
          * @param schema
          *            schema used to create attribute
+         * @throws DecodeException
+         *             if a problem occurs during initialization
          */
-        public Branch(TemplateFile templateFile, DN branchDN, Schema schema) {
-            this(templateFile, branchDN, schema, new String[0], new int[0], new TemplateLine[0]);
+        Branch(final TemplateFile templateFile, final DN branchDN, final Schema schema) throws DecodeException {
+            this(templateFile, branchDN, schema, new ArrayList<String>(), new ArrayList<Integer>(),
+                    new ArrayList<TemplateLine>());
         }
 
         /**
@@ -1567,56 +1388,37 @@
          * @param extraLines
          *            The set of extra lines that should be included in this
          *            branch entry.
+         * @throws DecodeException
+         *             if a problem occurs during initialization
          */
-        public Branch(TemplateFile templateFile, DN branchDN, Schema schema, String[] subordinateTemplateNames,
-                int[] numEntriesPerTemplate, TemplateLine[] extraLines) {
+        Branch(final TemplateFile templateFile, final DN branchDN, final Schema schema,
+                final List<String> subordinateTemplateNames, final List<Integer> numEntriesPerTemplate,
+                final List<TemplateLine> extraLines) throws DecodeException {
             this.branchDN = branchDN;
-            this.schema = schema;
             this.subordinateTemplateNames = subordinateTemplateNames;
             this.numEntriesPerTemplate = numEntriesPerTemplate;
             this.extraLines = extraLines;
 
-            subordinateTemplates = null;
-
-            // Get the RDN template lines based just on the entry DN.
-            Entry entry = LinkedHashMapEntry.FACTORY.newEntry(branchDN);
-
-            ArrayList<LocalizableMessage> warnings = new ArrayList<LocalizableMessage>();
-            ArrayList<TemplateLine> lineList = new ArrayList<TemplateLine>();
-
-            for (ObjectClass objectClass : Entries.getObjectClasses(entry, schema)) {
-                try {
-                    String[] valueStrings = new String[] { objectClass.getNameOrOID() };
-                    TemplateTag[] tags = new TemplateTag[1];
-                    tags[0] = new StaticTextTag();
-                    tags[0].initializeForBranch(schema, templateFile, this, valueStrings, 0, warnings);
-
-                    TemplateLine l = new TemplateLine(CoreSchema.getObjectClassAttributeType(), 0, tags);
-                    lineList.add(l);
-                } catch (Exception e) {
-                    // This should never happen.
-                    e.printStackTrace();
+            // The RDN template lines are based on the DN.
+            final List<LocalizableMessage> warnings = new ArrayList<LocalizableMessage>();
+            rdnLines = new ArrayList<TemplateLine>();
+            for (final AVA ava : branchDN.rdn()) {
+                final Attribute attribute = ava.toAttribute();
+                for (final ByteString value : attribute.toArray()) {
+                    final List<TemplateTag> tags =
+                            buildTagListForValue(value.toString(), templateFile, schema, warnings);
+                    rdnLines.add(new TemplateLine(attribute.getAttributeDescription().getAttributeType(), 0, tags));
                 }
             }
+        }
 
-            for (Attribute attribute : entry.getAllAttributes()) {
-                for (String value : attribute.toArray(new String[attribute.size()])) {
-                    try {
-                        String[] valueStrings = new String[] { value };
-                        TemplateTag[] tags = new TemplateTag[1];
-                        tags[0] = new StaticTextTag();
-                        tags[0].initializeForBranch(schema, templateFile, this, valueStrings, 0, warnings);
-                        lineList.add(
-                                new TemplateLine(attribute.getAttributeDescription().getAttributeType(), 0, tags));
-                    } catch (Exception e) {
-                        // This should never happen.
-                        e.printStackTrace();
-                    }
-                }
-            }
-
-            rdnLines = new TemplateLine[lineList.size()];
-            lineList.toArray(rdnLines);
+        private List<TemplateTag> buildTagListForValue(final String value, final TemplateFile templateFile,
+                final Schema schema, final List<LocalizableMessage> warnings) throws DecodeException {
+            final StaticTextTag tag = new StaticTextTag();
+            tag.initializeForBranch(schema, templateFile, this, new String[] { value }, 0, warnings);
+            final List<TemplateTag> tags = new ArrayList<TemplateTag>();
+            tags.add(tag);
+            return tags;
         }
 
         /**
@@ -1631,66 +1433,24 @@
          *             If any of the subordinate templates are not defined in
          *             the template file.
          */
-        public void completeBranchInitialization(Map<String, Template> templates) throws DecodeException {
-            if (subordinateTemplateNames == null) {
-                subordinateTemplateNames = new String[0];
-                subordinateTemplates = new Template[0];
-            } else {
-                subordinateTemplates = new Template[subordinateTemplateNames.length];
-                for (int i = 0; i < subordinateTemplates.length; i++) {
-                    subordinateTemplates[i] = templates.get(subordinateTemplateNames[i].toLowerCase());
-                    if (subordinateTemplates[i] == null) {
-                        LocalizableMessage message = ERR_ENTRY_GENERATOR_UNDEFINED_BRANCH_SUBORDINATE.get(
-                                branchDN.toString(), subordinateTemplateNames[i]);
-                        throw DecodeException.fatalError(message);
-                    }
+        private void completeBranchInitialization(final Map<String, Template> templates) throws DecodeException {
+            subordinateTemplates = new ArrayList<Template>();
+            for (int i = 0; i < subordinateTemplateNames.size(); i++) {
+                subordinateTemplates.add(templates.get(subordinateTemplateNames.get(i).toLowerCase()));
+                if (subordinateTemplates.get(i) == null) {
+                    throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_UNDEFINED_BRANCH_SUBORDINATE.get(
+                            branchDN.toString(), subordinateTemplateNames.get(i)));
                 }
             }
+
+            nextEntry = buildBranchEntry();
         }
 
-        /**
-         * Retrieves the DN for this branch entry.
-         *
-         * @return The DN for this branch entry.
-         */
-        public DN getBranchDN() {
+        DN getBranchDN() {
             return branchDN;
         }
 
         /**
-         * Retrieves the names of the subordinate templates for this branch.
-         *
-         * @return The names of the subordinate templates for this branch.
-         */
-        public String[] getSubordinateTemplateNames() {
-            return subordinateTemplateNames;
-        }
-
-        /**
-         * Retrieves the set of subordinate templates used to generate entries
-         * below this branch. Note that the subordinate templates will not be
-         * available until the <CODE>completeBranchInitialization</CODE> method
-         * has been called.
-         *
-         * @return The set of subordinate templates used to generate entries
-         *         below this branch.
-         */
-        public Template[] getSubordinateTemplates() {
-            return subordinateTemplates;
-        }
-
-        /**
-         * Retrieves the number of entries that should be created below this
-         * branch for each subordinate template.
-         *
-         * @return The number of entries that should be created below this
-         *         branch for each subordinate template.
-         */
-        public int[] getNumEntriesPerTemplate() {
-            return numEntriesPerTemplate;
-        }
-
-        /**
          * Adds a new subordinate template to this branch. Note that this should
          * not be used after <CODE>completeBranchInitialization</CODE> has been
          * called.
@@ -1700,29 +1460,9 @@
          * @param numEntries
          *            The number of entries to create based on the template.
          */
-        public void addSubordinateTemplate(String name, int numEntries) {
-            String[] newNames = new String[subordinateTemplateNames.length + 1];
-            int[] newCounts = new int[numEntriesPerTemplate.length + 1];
-
-            System.arraycopy(subordinateTemplateNames, 0, newNames, 0, subordinateTemplateNames.length);
-            System.arraycopy(numEntriesPerTemplate, 0, newCounts, 0, numEntriesPerTemplate.length);
-
-            newNames[subordinateTemplateNames.length] = name;
-            newCounts[numEntriesPerTemplate.length] = numEntries;
-
-            subordinateTemplateNames = newNames;
-            numEntriesPerTemplate = newCounts;
-        }
-
-        /**
-         * Retrieves the set of extra lines that should be included in this
-         * branch entry.
-         *
-         * @return The set of extra lines that should be included in this branch
-         *         entry.
-         */
-        public TemplateLine[] getExtraLines() {
-            return extraLines;
+        void addSubordinateTemplate(final String name, final int numEntries) {
+            subordinateTemplateNames.add(name);
+            numEntriesPerTemplate.add(numEntries);
         }
 
         /**
@@ -1732,12 +1472,8 @@
          * @param line
          *            The line to add to the set of extra lines for this branch.
          */
-        public void addExtraLine(TemplateLine line) {
-            TemplateLine[] newExtraLines = new TemplateLine[extraLines.length + 1];
-            System.arraycopy(extraLines, 0, newExtraLines, 0, extraLines.length);
-            newExtraLines[extraLines.length] = line;
-
-            extraLines = newExtraLines;
+        void addExtraLine(final TemplateLine line) {
+            extraLines.add(line);
         }
 
         /**
@@ -1747,73 +1483,76 @@
          *
          * @param attributeType
          *            The attribute type for which to make the determination.
-         * @return <CODE>true</CODE> if the branch does contain the specified
-         *         attribute type, or <CODE>false</CODE> if it does not.
+         * @return <code>true</code> if the branch does contain the specified
+         *         attribute type, or <code>false</code> if it does not.
          */
-        public boolean hasAttribute(AttributeType attributeType) {
+        boolean hasAttribute(final AttributeType attributeType) {
             if (branchDN.rdn().getAttributeValue(attributeType) != null) {
                 return true;
             }
-
-            for (TemplateLine l : extraLines) {
-                if (l.getAttributeType().equals(attributeType)) {
+            for (final TemplateLine line : extraLines) {
+                if (line.getAttributeType().equals(attributeType)) {
                     return true;
                 }
             }
-
             return false;
         }
 
         /**
-         * Writes the entry for this branch, as well as all appropriate
-         * subordinate entries.
+         * Returns the entry corresponding to this branch.
          *
-         * @param entryWriter
-         *            The entry writer to which the entries should be written.
-         * @return The result that indicates whether processing should continue.
-         * @throws IOException
-         *             If a problem occurs while attempting to write to the LDIF
-         *             writer.
-         * @throws DecodeException
-         *             If some other problem occurs.
+         * @return the entry, or null if it can't be generated
          */
-        public TagResult writeEntries(EntryWriter entryWriter) throws IOException, DecodeException {
-            // Create a new template entry and populate it based on the RDN
-            // attributes and extra lines.
-            TemplateEntry entry = new TemplateEntry(this);
-
-            for (TemplateLine l : rdnLines) {
-                TagResult r = l.generateLine(entry);
-                if (!(r.keepProcessingEntry() && r.keepProcessingParent() && r.keepProcessingTemplateFile())) {
-                    return r;
-                }
+        private TemplateEntry buildBranchEntry() {
+            final TemplateEntry entry = new TemplateEntry(this);
+            final List<TemplateLine> lines = new ArrayList<TemplateLine>(rdnLines);
+            lines.addAll(extraLines);
+            for (final TemplateLine line : lines) {
+                line.generateLine(entry);
             }
-
-            for (TemplateLine l : extraLines) {
-                TagResult r = l.generateLine(entry);
-                if (!(r.keepProcessingEntry() && r.keepProcessingParent() && r.keepProcessingTemplateFile())) {
-                    return r;
-                }
+            for (int i = 0; i < subordinateTemplates.size(); i++) {
+                subordinateTemplates.get(i).reset(entry.getDN(), numEntriesPerTemplate.get(i));
             }
+            return entry;
+        }
 
-            if (!entryWriter.writeEntry(entry)) {
-                return TagResult.STOP_PROCESSING;
+        /**
+         * Returns {@code true} if there is another generated entry to return.
+         *
+         * @return {@code true} if another entry can be returned.
+         */
+        boolean hasNext() {
+            if (nextEntry != null) {
+                return true;
             }
-
-            for (int i = 0; i < subordinateTemplates.length; i++) {
-                TagResult r = subordinateTemplates[i].writeEntries(entryWriter, branchDN, numEntriesPerTemplate[i]);
-                if (!(r.keepProcessingParent() && r.keepProcessingTemplateFile())) {
-                    if (r.keepProcessingTemplateFile()) {
-                        // We don't want to propagate a "stop processing parent"
-                        // all the way up the chain.
-                        return TagResult.SUCCESS_RESULT;
+            // get the next entry from current subtemplate
+            if (nextEntry == null) {
+                for (; currentSubTemplateIndex < subordinateTemplates.size(); currentSubTemplateIndex++) {
+                    if (subordinateTemplates.get(currentSubTemplateIndex).hasNext()) {
+                        nextEntry = subordinateTemplates.get(currentSubTemplateIndex).nextEntry();
+                        if (nextEntry != null) {
+                            return true;
+                        }
                     }
-
-                    return r;
                 }
             }
+            return false;
+        }
 
-            return TagResult.SUCCESS_RESULT;
+        /**
+         * Returns the next generated entry.
+         *
+         * @return The next entry.
+         * @throws NoSuchElementException
+         *             If this reader does not contain any more entries.
+         */
+        TemplateEntry nextEntry() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+            final TemplateEntry entry = nextEntry;
+            nextEntry = null;
+            return entry;
         }
     }
 
@@ -1827,54 +1566,25 @@
          * The attribute types that are used in the RDN for entries generated
          * using this template.
          */
-        private org.forgerock.opendj.ldap.schema.AttributeType[] rdnAttributes;
+        private final List<AttributeType> rdnAttributes;
 
         /** The number of entries to create for each subordinate template. */
-        private int[] numEntriesPerTemplate;
+        private final List<Integer> numEntriesPerTemplate;
 
         /** The name for this template. */
-        private String name;
+        private final String name;
 
         /** The names of the subordinate templates below this template. */
-        private String[] subordinateTemplateNames;
+        private final List<String> subTemplateNames;
 
         /** The subordinate templates below this template. */
-        private Template[] subordinateTemplates;
+        private List<Template> subTemplates;
 
         /** The template file that contains this template. */
-        private TemplateFile templateFile;
+        private final TemplateFile templateFile;
 
         /** The set of template lines for this template. */
-        private TemplateLine[] templateLines;
-
-        /**
-         * Creates a new template with the provided information.
-         *
-         * @param templateFile
-         *            The template file that contains this template.
-         * @param name
-         *            The name for this template.
-         * @param rdnAttributes
-         *            The set of attribute types that are used in the RDN for
-         *            entries generated using this template.
-         * @param subordinateTemplateNames
-         *            The names of the subordinate templates below this
-         *            template.
-         * @param numEntriesPerTemplate
-         *            The number of entries to create below each subordinate
-         *            template.
-         */
-        public Template(TemplateFile templateFile, String name, AttributeType[] rdnAttributes,
-                String[] subordinateTemplateNames, int[] numEntriesPerTemplate) {
-            this.templateFile = templateFile;
-            this.name = name;
-            this.rdnAttributes = rdnAttributes;
-            this.subordinateTemplateNames = subordinateTemplateNames;
-            this.numEntriesPerTemplate = numEntriesPerTemplate;
-
-            templateLines = new TemplateLine[0];
-            subordinateTemplates = null;
-        }
+        private final List<TemplateLine> templateLines;
 
         /**
          * Creates a new template with the provided information.
@@ -1895,16 +1605,15 @@
          * @param templateLines
          *            The set of template lines for this template.
          */
-        public Template(TemplateFile templateFile, String name, AttributeType[] rdnAttributes,
-                String[] subordinateTemplateNames, int[] numEntriesPerTemplate, TemplateLine[] templateLines) {
+        Template(final TemplateFile templateFile, final String name, final List<AttributeType> rdnAttributes,
+                final List<String> subordinateTemplateNames, final List<Integer> numEntriesPerTemplate,
+                final List<TemplateLine> templateLines) {
             this.templateFile = templateFile;
             this.name = name;
             this.rdnAttributes = rdnAttributes;
-            this.subordinateTemplateNames = subordinateTemplateNames;
+            this.subTemplateNames = subordinateTemplateNames;
             this.numEntriesPerTemplate = numEntriesPerTemplate;
             this.templateLines = templateLines;
-
-            subordinateTemplates = null;
         }
 
         /**
@@ -1920,117 +1629,47 @@
          *             If any of the subordinate templates are not defined in
          *             the template file.
          */
-        public void completeTemplateInitialization(Map<String, Template> templates) throws DecodeException {
-            // Make sure that all of the specified subordinate templates exist.
-            if (subordinateTemplateNames == null) {
-                subordinateTemplateNames = new String[0];
-                subordinateTemplates = new Template[0];
-            } else {
-                subordinateTemplates = new Template[subordinateTemplateNames.length];
-                for (int i = 0; i < subordinateTemplates.length; i++) {
-                    subordinateTemplates[i] = templates.get(subordinateTemplateNames[i].toLowerCase());
-                    if (subordinateTemplates[i] == null) {
-                        LocalizableMessage message = ERR_ENTRY_GENERATOR_UNDEFINED_TEMPLATE_SUBORDINATE.get(
-                                subordinateTemplateNames[i], name);
-                        throw DecodeException.fatalError(message);
-                    }
+        void completeTemplateInitialization(final Map<String, Template> templates) throws DecodeException {
+            subTemplates = new ArrayList<Template>();
+            for (final String subordinateName : subTemplateNames) {
+                final Template template = templates.get(subordinateName.toLowerCase());
+                if (template == null) {
+                    throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_UNDEFINED_TEMPLATE_SUBORDINATE.get(
+                            this.name, subordinateName));
                 }
+                subTemplates.add(template);
             }
+            ensureAllRDNAttributesAreDefined();
+        }
 
-            // Make sure that all of the RDN attributes are defined.
-            HashSet<AttributeType> rdnAttrs = new HashSet<AttributeType>(rdnAttributes.length);
-            for (AttributeType t : rdnAttributes) {
-                rdnAttrs.add(t);
+        private void ensureAllRDNAttributesAreDefined() throws DecodeException {
+            Set<AttributeType> rdnAttrs = new HashSet<AttributeType>(rdnAttributes);
+            List<AttributeType> templateAttrs = new ArrayList<AttributeType>();
+            for (TemplateLine line : templateLines) {
+                templateAttrs.add(line.getAttributeType());
             }
-
-            for (TemplateLine l : templateLines) {
-                if (rdnAttrs.remove(l.getAttributeType())) {
-                    if (rdnAttrs.isEmpty()) {
-                        break;
-                    }
-                }
-            }
-
+            rdnAttrs.removeAll(templateAttrs);
             if (!rdnAttrs.isEmpty()) {
                 AttributeType t = rdnAttrs.iterator().next();
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TEMPLATE_MISSING_RDN_ATTR.get(name, t.getNameOrOID());
-                throw DecodeException.fatalError(message);
+                throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_TEMPLATE_MISSING_RDN_ATTR.get(
+                        name, t.getNameOrOID()));
             }
         }
 
-        /**
-         * Retrieves the name for this template.
-         *
-         * @return The name for this template.
-         */
-        public String getName() {
+        String getName() {
             return name;
         }
 
-        /**
-         * Retrieves the set of attribute types that are used in the RDN for
-         * entries generated using this template.
-         *
-         * @return The set of attribute types that are used in the RDN for
-         *         entries generated using this template.
-         */
-        public AttributeType[] getRDNAttributes() {
+        List<AttributeType> getRDNAttributes() {
             return rdnAttributes;
         }
 
-        /**
-         * Retrieves the names of the subordinate templates used to generate
-         * entries below entries created by this template.
-         *
-         * @return The names of the subordinate templates used to generate
-         *         entries below entries created by this template.
-         */
-        public String[] getSubordinateTemplateNames() {
-            return subordinateTemplateNames;
-        }
-
-        /**
-         * Retrieves the subordinate templates used to generate entries below
-         * entries created by this template.
-         *
-         * @return The subordinate templates used to generate entries below
-         *         entries created by this template.
-         */
-        public Template[] getSubordinateTemplates() {
-            return subordinateTemplates;
-        }
-
-        /**
-         * Retrieves the number of entries that should be created for each
-         * subordinate template.
-         *
-         * @return The number of entries that should be created for each
-         *         subordinate template.
-         */
-        public int[] getNumEntriesPerTemplate() {
-            return numEntriesPerTemplate;
-        }
-
-        /**
-         * Retrieves the set of template lines for this template.
-         *
-         * @return The set of template lines for this template.
-         */
-        public TemplateLine[] getTemplateLines() {
+        List<TemplateLine> getTemplateLines() {
             return templateLines;
         }
 
-        /**
-         * Adds the provided template line to this template.
-         *
-         * @param line
-         *            The template line to add to this template.
-         */
-        public void addTemplateLine(TemplateLine line) {
-            TemplateLine[] newTemplateLines = new TemplateLine[templateLines.length + 1];
-            System.arraycopy(templateLines, 0, newTemplateLines, 0, templateLines.length);
-            newTemplateLines[templateLines.length] = line;
-            templateLines = newTemplateLines;
+        void addTemplateLine(final TemplateLine line) {
+            templateLines.add(line);
         }
 
         /**
@@ -2043,68 +1682,124 @@
          *         template lines that reference the provided attribute type, or
          *         <CODE>false</CODE> if not.
          */
-        public boolean hasAttribute(AttributeType attributeType) {
-            for (TemplateLine l : templateLines) {
-                if (l.getAttributeType().equals(attributeType)) {
+        boolean hasAttribute(final AttributeType attributeType) {
+            for (final TemplateLine line : templateLines) {
+                if (line.getAttributeType().equals(attributeType)) {
                     return true;
                 }
             }
+            return false;
+        }
 
+        /** parent DN of entries to generate for this template. */
+        private DN parentDN;
+
+        /** Number of entries to generate for this template. */
+        private int numberOfEntries;
+
+        /** Current count of generated entries for this template. */
+        private int entriesCount;
+
+        /** Indicates if current entry has been initialized. */
+        private boolean currentEntryIsInitialized;
+
+        /** Index of current subordinate template to use for current entry. */
+        private int subTemplateIndex;
+
+        /** Entry to return when calling {@code nextEntry} method. */
+        private TemplateEntry nextEntry;
+
+        /**
+         * Reset this template with provided parentDN and number of entries to
+         * generate.
+         * <p>
+         * After a reset, the template can be used again to generate some
+         * entries with a different parent DN and number of entries.
+         *
+         * @param parentDN
+         *            The parent DN of entires to generate for this template.
+         * @param numberOfEntries
+         *            The number of entries to generate for this template.
+         */
+        void reset(final DN parentDN, final int numberOfEntries) {
+            this.parentDN = parentDN;
+            this.numberOfEntries = numberOfEntries;
+            entriesCount = 0;
+            currentEntryIsInitialized = false;
+            subTemplateIndex = 0;
+            nextEntry = null;
+        }
+
+        /**
+         * Returns an entry for this template.
+         *
+         * @return the entry, or null if it can't be generated
+         */
+        private TemplateEntry buildTemplateEntry() {
+            templateFile.nextFirstAndLastNames();
+            final TemplateEntry templateEntry = new TemplateEntry(this, parentDN);
+
+            for (final TemplateLine line : templateLines) {
+                line.generateLine(templateEntry);
+            }
+            for (int i = 0; i < subTemplates.size(); i++) {
+                subTemplates.get(i).reset(templateEntry.getDN(), numEntriesPerTemplate.get(i));
+            }
+            return templateEntry;
+        }
+
+        /**
+         * Returns {@code true} if there is another generated entry to return.
+         *
+         * @return {@code true} if another entry can be returned.
+         */
+        boolean hasNext() {
+            if (nextEntry != null) {
+                return true;
+            }
+            while (entriesCount < numberOfEntries) {
+                // get the template entry
+                if (!currentEntryIsInitialized) {
+                    nextEntry = buildTemplateEntry();
+                    if (nextEntry != null) {
+                        currentEntryIsInitialized = true;
+                        return true;
+                    }
+                    return false;
+                }
+                // get the next entry from current subtemplate
+                if (nextEntry == null) {
+                    for (; subTemplateIndex < subTemplates.size(); subTemplateIndex++) {
+                        if (subTemplates.get(subTemplateIndex).hasNext()) {
+                            nextEntry = subTemplates.get(subTemplateIndex).nextEntry();
+                            if (nextEntry != null) {
+                                return true;
+                            }
+                        }
+                    }
+                }
+                // reset for next template entry
+                entriesCount++;
+                currentEntryIsInitialized = false;
+                subTemplateIndex = 0;
+            }
             return false;
         }
 
         /**
-         * Writes the entry for this template, as well as all appropriate
-         * subordinate entries.
+         * Returns the next generated entry.
          *
-         * @param entryWriter
-         *            The entry writer that will be used to write the entries.
-         * @param parentDN
-         *            The DN of the entry below which the subordinate entries
-         *            should be generated.
-         * @param count
-         *            The number of entries to generate based on this template.
-         * @return The result that indicates whether processing should continue.
-         * @throws IOException
-         *             If a problem occurs while attempting to write to the LDIF
-         *             writer.
-         * @throws DecodeException
-         *             If some other problem occurs.
+         * @return The next entry.
+         * @throws NoSuchElementException
+         *             If this reader does not contain any more entries.
          */
-        public TagResult writeEntries(EntryWriter entryWriter, DN parentDN, int count) throws IOException,
-                DecodeException {
-            for (int i = 0; i < count; i++) {
-                templateFile.nextFirstAndLastNames();
-                TemplateEntry templateEntry = new TemplateEntry(this, parentDN);
-
-                for (TemplateLine l : templateLines) {
-                    TagResult r = l.generateLine(templateEntry);
-                    if (!(r.keepProcessingEntry() && r.keepProcessingParent() && r.keepProcessingTemplateFile())) {
-                        return r;
-                    }
-                }
-
-                if (!entryWriter.writeEntry(templateEntry)) {
-                    return TagResult.STOP_PROCESSING;
-                }
-
-                for (int j = 0; j < subordinateTemplates.length; j++) {
-                    TagResult r = subordinateTemplates[j].writeEntries(entryWriter, templateEntry.getDN(),
-                            numEntriesPerTemplate[j]);
-                    if (!(r.keepProcessingParent() && r.keepProcessingTemplateFile())) {
-                        if (r.keepProcessingTemplateFile()) {
-                            // We don't want to propagate a
-                            // "stop processing parent"
-                            // all the way up the chain.
-                            return TagResult.SUCCESS_RESULT;
-                        }
-
-                        return r;
-                    }
-                }
+        TemplateEntry nextEntry() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
             }
-
-            return TagResult.SUCCESS_RESULT;
+            final TemplateEntry entry = nextEntry;
+            nextEntry = null;
+            return entry;
         }
     }
 
@@ -2116,12 +1811,6 @@
         /** Template entry that represents a null object. */
         static final TemplateEntry NULL_TEMPLATE_ENTRY = new TemplateEntry(null, null);
 
-        /**
-         * The branch used to generate this entry (if it is associated with a
-         * branch).
-         */
-        private Branch branch;
-
         /** The DN for this template entry, if it is known. */
         private DN dn;
 
@@ -2129,20 +1818,22 @@
          * The DN of the parent entry for this template entry, if it is
          * available.
          */
-        private DN parentDN;
+        private final DN parentDN;
 
         /**
          * The set of attributes associated with this template entry, mapped
          * from the lowercase name of the attribute to the list of generated
          * values.
+         * A list of template values is never empty in the map, it always has
+         * at least one element.
          */
-        private LinkedHashMap<AttributeType, ArrayList<TemplateValue>> attributes;
+        private final LinkedHashMap<AttributeType, List<TemplateValue>> attributes;
 
         /**
-         * The template used to generate this entry (if it is associated with a
-         * template).
+         * The template used to generate this entry if it is associated with a
+         * template.
          */
-        private Template template;
+        private final Template template;
 
         /**
          * Creates a new template entry that will be associated with the
@@ -2151,13 +1842,11 @@
          * @param branch
          *            The branch to use when creating this template entry.
          */
-        public TemplateEntry(Branch branch) {
-            this.branch = branch;
-
+        TemplateEntry(final Branch branch) {
             dn = branch.getBranchDN();
+            attributes = new LinkedHashMap<AttributeType, List<TemplateValue>>();
             template = null;
             parentDN = null;
-            attributes = new LinkedHashMap<AttributeType, ArrayList<TemplateValue>>();
         }
 
         /**
@@ -2169,43 +1858,13 @@
          * @param parentDN
          *            The DN of the parent entry for this template entry.
          */
-        public TemplateEntry(Template template, DN parentDN) {
+        TemplateEntry(final Template template, final DN parentDN) {
             this.template = template;
             this.parentDN = parentDN;
-
-            dn = null;
-            branch = null;
-            attributes = new LinkedHashMap<AttributeType, ArrayList<TemplateValue>>();
+            attributes = new LinkedHashMap<AttributeType, List<TemplateValue>>();
         }
 
-        /**
-         * Retrieves the branch used to generate this entry.
-         *
-         * @return The branch used to generate this entry, or <CODE>null</CODE>
-         *         if it is associated with a template instead of a branch.
-         */
-        public Branch getBranch() {
-            return branch;
-        }
-
-        /**
-         * Retrieves the template used to generate this entry.
-         *
-         * @return The template used to generate this entry, or
-         *         <CODE>null</CODE> if it is associated with a branch instead
-         *         of a template.
-         */
-        public Template getTemplate() {
-            return template;
-        }
-
-        /**
-         * Retrieves the DN of the parent entry for this template entry.
-         *
-         * @return The DN of the parent entry for this template entry, or
-         *         <CODE>null</CODE> if there is no parent DN.
-         */
-        public DN getParentDN() {
+        DN getParentDN() {
             return parentDN;
         }
 
@@ -2215,55 +1874,22 @@
          * @return The DN for this template entry if it is known, or
          *         <CODE>null</CODE> if it cannot yet be determined.
          */
-        public DN getDN() {
-            // TODO : building to review, particularly building RN with multiple
-            // AVA
-            // using StringBuilder because no facility using other way
+        DN getDN() {
             if (dn == null) {
-                RDN rdn;
-                AttributeType[] rdnAttrs = template.getRDNAttributes();
-                if (rdnAttrs.length == 1) {
-                    AttributeType type = rdnAttrs[0];
-                    TemplateValue templateValue = getValue(type);
+                final Collection<AVA> avas = new ArrayList<AVA>();
+                for (final AttributeType attrType : template.getRDNAttributes()) {
+                    final TemplateValue templateValue = getValue(attrType);
                     if (templateValue == null) {
                         return null;
                     }
-                    rdn = new RDN(type, templateValue.getValueAsString());
-                } else {
-                    StringBuilder rdnString = new StringBuilder();
-                    for (int i = 0; i < rdnAttrs.length; i++) {
-                        AttributeType type = rdnAttrs[i];
-                        TemplateValue templateValue = getValue(type);
-                        if (templateValue == null) {
-                            return null;
-                        }
-                        if (i > 0) {
-                            rdnString.append("+");
-                        }
-                        rdnString.append(new AVA(type, templateValue.getValueAsString()).toString());
-                    }
-                    rdn = RDN.valueOf(rdnString.toString());
+                    avas.add(new AVA(attrType, templateValue.getValueAsString()));
                 }
-                dn = parentDN.child(rdn);
+                dn = parentDN.child(new RDN(avas));
             }
             return dn;
         }
 
         /**
-         * Indicates whether this entry contains one or more values for the
-         * specified attribute type.
-         *
-         * @param attributeType
-         *            The attribute type for which to make the determination.
-         * @return <CODE>true</CODE> if this entry contains one or more values
-         *         for the specified attribute type, or <CODE>false</CODE> if
-         *         not.
-         */
-        public boolean hasAttribute(AttributeType attributeType) {
-            return attributes.containsKey(attributeType);
-        }
-
-        /**
          * Retrieves the value for the specified attribute, if defined. If the
          * specified attribute has multiple values, then the first will be
          * returned.
@@ -2273,13 +1899,12 @@
          * @return The value for the specified attribute, or <CODE>null</CODE>
          *         if there are no values for that attribute type.
          */
-        public TemplateValue getValue(AttributeType attributeType) {
-            ArrayList<TemplateValue> valueList = attributes.get(attributeType);
-            if ((valueList == null) || valueList.isEmpty()) {
-                return null;
-            } else {
-                return valueList.get(0);
+        TemplateValue getValue(final AttributeType attributeType) {
+            final List<TemplateValue> values = attributes.get(attributeType);
+            if ((values != null)) {
+                return values.get(0);
             }
+            return null;
         }
 
         /**
@@ -2292,41 +1917,32 @@
          *         <CODE>null</CODE> if there are no values for that attribute
          *         type.
          */
-        public List<TemplateValue> getValues(AttributeType attributeType) {
-            ArrayList<TemplateValue> valueList = attributes.get(attributeType);
-            return valueList;
+        List<TemplateValue> getValues(AttributeType attributeType) {
+            return attributes.get(attributeType);
         }
 
-        /**
-         * Adds the provided template value to this entry.
-         *
-         * @param value
-         *            The value to add to this entry.
-         */
-        public void addValue(TemplateValue value) {
-            ArrayList<TemplateValue> valueList = attributes.get(value.getAttributeType());
-            if (valueList == null) {
-                valueList = new ArrayList<TemplateValue>();
-                valueList.add(value);
-                attributes.put(value.getAttributeType(), valueList);
-            } else {
-                valueList.add(value);
+        void addValue(TemplateValue value) {
+            List<TemplateValue> values = attributes.get(value.getAttributeType());
+            if (values == null) {
+                values = new ArrayList<TemplateValue>();
+                attributes.put(value.getAttributeType(), values);
             }
+            values.add(value);
         }
 
         /**
-         * Returns an entry from this template entry.
+         * Returns an entry built from this template entry.
          *
          * @return an entry
          */
-        public Entry toEntry() {
-            Entry entry = LinkedHashMapEntry.FACTORY.newEntry(getDN());
-            AttributeFactory attributeFactory = LinkedAttribute.FACTORY;
+        Entry toEntry() {
+            final Entry entry = new LinkedHashMapEntry(getDN());
 
-            for (AttributeType attributeType : attributes.keySet()) {
-                ArrayList<TemplateValue> valueList = attributes.get(attributeType);
-                Attribute newAttribute = attributeFactory.newAttribute(AttributeDescription.create(attributeType));
-                for (TemplateValue value : valueList) {
+            for (final AttributeType attributeType : attributes.keySet()) {
+                final List<TemplateValue> valueList = attributes.get(attributeType);
+                final Attribute newAttribute =
+                        new LinkedAttribute(AttributeDescription.create(attributeType));
+                for (final TemplateValue value : valueList) {
                     newAttribute.add(value.getValueAsString());
                 }
                 entry.addAttribute(newAttribute);
@@ -2340,69 +1956,27 @@
      * any number of tags to be evaluated.
      */
     static class TemplateLine {
-        /** The attribute type for this template line. */
-        private AttributeType attributeType;
+
+        /** The attribute type to which this template line corresponds. */
+        private final AttributeType attributeType;
 
         /**
          * The line number on which this template line appears in the template
          * file.
          */
-        private int lineNumber;
+        private final int lineNumber;
 
         /** The set of tags for this template line. */
-        private TemplateTag[] tags;
+        private final List<TemplateTag> tags;
 
         /** Whether this line corresponds to an URL value or not. */
-        private boolean isURL;
+        private final boolean isURL;
 
         /** Whether this line corresponds to a base64 encoded value or not. */
-        private boolean isBase64;
+        private final boolean isBase64;
 
         /**
-         * Retrieves the attribute type for this template line.
-         *
-         * @return The attribute type for this template line.
-         */
-        public AttributeType getAttributeType() {
-            return attributeType;
-        }
-
-        /**
-         * Retrieves the line number on which this template line appears in the
-         * template file.
-         *
-         * @return The line number on which this template line appears in the
-         *         template file.
-         */
-        public int getLineNumber() {
-            return lineNumber;
-        }
-
-        /**
-         * Returns whether the value of this template line corresponds to an URL
-         * or not.
-         *
-         * @return <CODE>true</CODE> if the value of this template line
-         *         corresponds to an URL and <CODE>false</CODE> otherwise.
-         */
-        public boolean isURL() {
-            return isURL;
-        }
-
-        /**
-         * Returns whether the value of this template line corresponds to a
-         * Base64 encoded value or not.
-         *
-         * @return <CODE>true</CODE> if the value of this template line
-         *         corresponds to a Base64 encoded value and <CODE>false</CODE>
-         *         otherwise.
-         */
-        public boolean isBase64() {
-            return isBase64;
-        }
-
-        /**
-         * Creates a new template line with the provided information.
+         * Creates a new template line.
          *
          * @param attributeType
          *            The attribute type for this template line.
@@ -2412,12 +1986,12 @@
          * @param tags
          *            The set of tags for this template line.
          */
-        public TemplateLine(AttributeType attributeType, int lineNumber, TemplateTag[] tags) {
+        TemplateLine(final AttributeType attributeType, final int lineNumber, final List<TemplateTag> tags) {
             this(attributeType, lineNumber, tags, false, false);
         }
 
         /**
-         * Creates a new template line with the provided information.
+         * Creates a new template line with URL and base64 flags.
          *
          * @param attributeType
          *            The attribute type for this template line.
@@ -2432,8 +2006,8 @@
          *            Whether this template line's value is Base64 encoded or
          *            not.
          */
-        public TemplateLine(AttributeType attributeType, int lineNumber, TemplateTag[] tags, boolean isURL,
-                boolean isBase64) {
+        TemplateLine(final AttributeType attributeType, final int lineNumber, final List<TemplateTag> tags,
+                final boolean isURL, final boolean isBase64) {
             this.attributeType = attributeType;
             this.lineNumber = lineNumber;
             this.tags = tags;
@@ -2441,6 +2015,10 @@
             this.isBase64 = isBase64;
         }
 
+        AttributeType getAttributeType() {
+            return attributeType;
+        }
+
         /**
          * Generates the content for this template line and places it in the
          * provided template entry.
@@ -2449,19 +2027,16 @@
          *            The template entry being generated.
          * @return The result of generating the template line.
          */
-        public TagResult generateLine(TemplateEntry templateEntry) {
-            TemplateValue value = new TemplateValue(this);
-
-            for (TemplateTag t : tags) {
-                TagResult result = t.generateValue(templateEntry, value);
-                if (!(result.keepProcessingLine() && result.keepProcessingEntry()
-                        && result.keepProcessingParent() && result.keepProcessingTemplateFile())) {
+        TagResult generateLine(final TemplateEntry templateEntry) {
+            final TemplateValue value = new TemplateValue(this);
+            for (final TemplateTag tag : tags) {
+                final TagResult result = tag.generateValue(templateEntry, value);
+                if (result != TagResult.SUCCESS) {
                     return result;
                 }
             }
-
             templateEntry.addValue(value);
-            return TagResult.SUCCESS_RESULT;
+            return TagResult.SUCCESS;
         }
     }
 
@@ -2469,77 +2044,37 @@
      * Represents a value generated from a template line.
      */
     static class TemplateValue {
+
         /** The generated template value. */
-        private StringBuilder templateValue;
+        private final StringBuilder templateValue;
 
         /** The template line used to generate this value. */
-        private TemplateLine templateLine;
+        private final TemplateLine templateLine;
 
-        /**
-         * Creates a new template value with the provided information.
-         *
-         * @param templateLine
-         *            The template line used to generate this value.
-         */
-        public TemplateValue(TemplateLine templateLine) {
+        TemplateValue(final TemplateLine templateLine) {
             this.templateLine = templateLine;
             templateValue = new StringBuilder();
         }
 
-        /**
-         * Retrieves the template line used to generate this value.
-         *
-         * @return The template line used to generate this value.
-         */
-        public TemplateLine getTemplateLine() {
-            return templateLine;
-        }
-
-        /**
-         * Retrieves the attribute type for this template value.
-         *
-         * @return The attribute type for this template value.
-         */
-        public AttributeType getAttributeType() {
+        AttributeType getAttributeType() {
             return templateLine.getAttributeType();
         }
 
-        /**
-         * Retrieves the generated value.
-         *
-         * @return The generated value.
-         */
-        public StringBuilder getValue() {
-            return templateValue;
-        }
-
-        /**
-         * Retrieves the generated value as String.
-         *
-         * @return The generated value.
-         */
-        public String getValueAsString() {
+        /** Returns the generated value as String. */
+        String getValueAsString() {
             return templateValue.toString();
         }
 
-        /**
-         * Appends the provided string to this template value.
-         *
-         * @param s
-         *            The string to append.
-         */
-        public void append(String s) {
+        /** Appends the provided string to this template value. */
+        void append(final String s) {
             templateValue.append(s);
         }
 
         /**
          * Appends the string representation of the provided object to this
          * template value.
-         *
-         * @param o
-         *            The object to append.
          */
-        public void append(Object o) {
+        void append(final Object o) {
             templateValue.append(String.valueOf(o));
         }
     }
diff --git a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateTag.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateTag.java
index f91f423..636ed92 100644
--- a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateTag.java
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateTag.java
@@ -28,7 +28,7 @@
 
 import static com.forgerock.opendj.ldap.CoreMessages.*;
 
-import java.io.File;
+import java.io.BufferedReader;
 import java.io.IOException;
 import java.text.DecimalFormat;
 import java.util.List;
@@ -45,6 +45,8 @@
 import org.forgerock.opendj.ldif.TemplateFile.TemplateEntry;
 import org.forgerock.opendj.ldif.TemplateFile.TemplateValue;
 
+import com.forgerock.opendj.util.StaticUtils;
+
 /**
  * Represents a tag that may be used in a template line when generating entries.
  * It can be used to generate content.
@@ -56,26 +58,21 @@
 
     /**
      * Retrieves the name for this tag.
-     *
-     * @return The name for this tag.
      */
-    public abstract String getName();
+    abstract String getName();
 
     /**
      * Indicates whether this tag is allowed for use in the extra lines for
      * branches.
-     *
-     * @return <CODE>true</CODE> if this tag may be used in branch definitions,
-     *         or <CODE>false</CODE> if not.
      */
-    public abstract boolean allowedInBranch();
+    abstract boolean allowedInBranch();
 
     /**
      * Performs any initialization for this tag that may be needed while parsing
      * a branch definition.
      *
      * @param schema
-     *            schema used to create attributes
+     *            The schema used to create attributes.
      * @param templateFile
      *            The template file in which this tag is used.
      * @param branch
@@ -88,10 +85,8 @@
      * @param warnings
      *            A list into which any appropriate warning messages may be
      *            placed.
-     * @throws DecodeException
-     *             if a problem occurs
      */
-    public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
+    void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
             int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
         // No implementation required by default.
     }
@@ -101,12 +96,12 @@
      * a template definition.
      *
      * @param schema
-     *            schema used to create attributes
+     *            The schema used to create attributes.
      * @param templateFile
      *            The template file in which this tag is used.
      * @param template
      *            The template in which this tag is used.
-     * @param arguments
+     * @param tagArguments
      *            The set of arguments provided for this tag.
      * @param lineNumber
      *            The line number on which this tag appears in the template
@@ -114,11 +109,9 @@
      * @param warnings
      *            A list into which any appropriate warning messages may be
      *            placed.
-     * @throws DecodeException
-     *             if a problem occurs
      */
-    public abstract void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
-            String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException;
+    abstract void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
+            String[] tagArguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException;
 
     /**
      * Performs any initialization for this tag that may be needed when starting
@@ -127,11 +120,29 @@
      * @param parentEntry
      *            The entry below which the new entries will be generated.
      */
-    public void initializeForParent(TemplateEntry parentEntry) {
+    void initializeForParent(TemplateEntry parentEntry) {
         // No implementation required by default.
     }
 
     /**
+     * Check for an attribute type in a branch or in a template.
+     *
+     * @param attrType
+     *            The attribute type to check for.
+     * @param branch
+     *            The branch that contains the type, or {@code null}
+     * @param template
+     *            The template that contains the type, or {@code null}
+     * @return true if either the branch or the template has the provided
+     *         attribute
+     */
+    final boolean hasAttributeTypeInBranchOrTemplate(AttributeType attrType, Branch branch,
+            Template template) {
+        return (branch != null && branch.hasAttribute(attrType))
+                || (template != null && template.hasAttribute(attrType));
+    }
+
+    /**
      * Generates the content for this tag by appending it to the provided tag.
      *
      * @param templateEntry
@@ -141,242 +152,64 @@
      *            appended.
      * @return The result of generating content for this tag.
      */
-    public abstract TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue);
+    abstract TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue);
 
     /**
-     * Provides information about the result of tag processing.
+     * Represents the result of tag generation.
      */
-    static final class TagResult {
-        /**
-         * A tag result in which all components have a value of
-         * <CODE>true</CODE>.
-         */
-        public static final TagResult SUCCESS_RESULT = new TagResult(true, true, true, true);
-
-        /**
-         * A tag result that indicates the value should not be included in the
-         * entry, but all other processing should continue.
-         */
-        public static final TagResult OMIT_FROM_ENTRY = new TagResult(false, true, true, true);
-
-        /**
-         * A tag result in whihc all components have a value of
-         * <CODE>false</CODE>.
-         */
-        public static final TagResult STOP_PROCESSING = new TagResult(false, false, false, false);
-
-        /** Indicates whether to keep processing the associated line. */
-        private boolean keepProcessingLine;
-
-        /** Indicates whether to keep processing the associated entry. */
-        private boolean keepProcessingEntry;
-
-        /**
-         * Indicates whether to keep processing entries below the associated
-         * parent.
-         */
-        private boolean keepProcessingParent;
-
-        /** Indicates whether to keep processing entries for the template file. */
-        private boolean keepProcessingTemplateFile;
-
-        /**
-         * Creates a new tag result object with the provided information.
-         *
-         * @param keepProcessingLine
-         *            Indicates whether to continue processing for the current
-         *            line. If not, then the line will not be included in the
-         *            entry.
-         * @param keepProcessingEntry
-         *            Indicates whether to continue processing for the current
-         *            entry. If not, then the entry will not be included in the
-         *            data.
-         * @param keepProcessingParent
-         *            Indicates whether to continue processing entries below the
-         *            current parent in the template file.
-         * @param keepProcessingTemplateFile
-         *            Indicates whether to continue processing entries for the
-         *            template file.
-         */
-        private TagResult(boolean keepProcessingLine, boolean keepProcessingEntry, boolean keepProcessingParent,
-                boolean keepProcessingTemplateFile) {
-            this.keepProcessingLine = keepProcessingLine;
-            this.keepProcessingEntry = keepProcessingEntry;
-            this.keepProcessingParent = keepProcessingParent;
-            this.keepProcessingTemplateFile = keepProcessingTemplateFile;
-        }
-
-        /**
-         * Indicates whether to continue processing for the current line. If
-         * this is <CODE>false</CODE>, then the current line will not be
-         * included in the entry. It will have no impact on whehter the entry
-         * itself is included in the generated LDIF.
-         *
-         * @return <CODE>true</CODE> if the line should be included in the
-         *         entry, or <CODE>false</CODE> if not.
-         */
-        public boolean keepProcessingLine() {
-            return keepProcessingLine;
-        }
-
-        /**
-         * Indicates whether to continue processing for the current entry. If
-         * this is <CODE>false</CODE>, then the current entry will not be
-         * included in the generated LDIF, and processing will resume with the
-         * next entry below the current parent.
-         *
-         * @return <CODE>true</CODE> if the entry should be included in the
-         *         generated LDIF, or <CODE>false</CODE> if not.
-         */
-        public boolean keepProcessingEntry() {
-            return keepProcessingEntry;
-        }
-
-        /**
-         * Indicates whether to continue processing entries below the current
-         * parent. If this is <CODE>false</CODE>, then the current entry will
-         * not be included, and processing will resume below the next parent in
-         * the template file.
-         *
-         * @return <CODE>true</CODE> if processing for the current parent should
-         *         continue, or <CODE>false</CODE> if not.
-         */
-        public boolean keepProcessingParent() {
-            return keepProcessingParent;
-        }
-
-        /**
-         * Indicates whether to keep processing entries for the template file.
-         * If this is <CODE>false</CODE>, then LDIF processing will end
-         * immediately (and the current entry will not be included).
-         *
-         * @return <CODE>true</CODE> if processing for the template file should
-         *         continue, or <CODE>false</CODE> if not.
-         */
-        public boolean keepProcessingTemplateFile() {
-            return keepProcessingTemplateFile;
-        }
+    static enum TagResult {
+        SUCCESS, FAILURE
     }
 
     /**
-     * This class defines a tag that is used to reference the value of a
-     * specified attribute already defined in the entry.
+     * Tag used to reference the value of a specified attribute
+     * already defined in the entry.
      */
     static class AttributeValueTag extends TemplateTag {
+
         /** The attribute type that specifies which value should be used. */
         private AttributeType attributeType;
 
         /** The maximum number of characters to include from the value. */
         private int numCharacters;
 
-        /**
-         * Creates a new instance of this attribute value tag.
-         */
-        public AttributeValueTag() {
+        AttributeValueTag() {
             attributeType = null;
             numCharacters = 0;
         }
 
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+        @Override
+        String getName() {
             return "AttributeValue";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
+        @Override
+        boolean allowedInBranch() {
             return true;
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a branch definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param branch
-         *            The branch in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
+        void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
                 int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            if ((arguments.length < 1) || (arguments.length > 2)) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(),
-                        lineNumber, 1, 2, arguments.length);
-                throw DecodeException.fatalError(message);
-            }
-
-            String lowerName = arguments[0].toLowerCase();
-            attributeType = schema.getAttributeType(lowerName);
-            if (!branch.hasAttribute(attributeType)) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_UNDEFINED_ATTRIBUTE.get(arguments[0], lineNumber);
-                throw DecodeException.fatalError(message);
-            }
-
-            if (arguments.length == 2) {
-                try {
-                    numCharacters = Integer.parseInt(arguments[1]);
-                    if (numCharacters < 0) {
-                        LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INTEGER_BELOW_LOWER_BOUND.get(
-                                numCharacters, 0, getName(), lineNumber);
-                        throw DecodeException.fatalError(message);
-                    }
-                } catch (NumberFormatException nfe) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[1],
-                            getName(), lineNumber);
-                    throw DecodeException.fatalError(message);
-                }
-            } else {
-                numCharacters = 0;
-            }
+            initialize(schema, branch, null, arguments, lineNumber);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
-                String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
+        void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template, String[] arguments,
+                int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
+            initialize(schema, null, template, arguments, lineNumber);
+        }
+
+        private void initialize(Schema schema, Branch branch, Template template, String[] arguments, int lineNumber)
+                throws DecodeException {
             if ((arguments.length < 1) || (arguments.length > 2)) {
                 LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(),
                         lineNumber, 1, 2, arguments.length);
                 throw DecodeException.fatalError(message);
             }
 
-            String lowerName = arguments[0].toLowerCase();
-            attributeType = schema.getAttributeType(lowerName);
-            if (!template.hasAttribute(attributeType)) {
+            attributeType = schema.getAttributeType(arguments[0].toLowerCase());
+            if (!hasAttributeTypeInBranchOrTemplate(attributeType, branch, template)) {
                 LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_UNDEFINED_ATTRIBUTE.get(arguments[0], lineNumber);
                 throw DecodeException.fatalError(message);
             }
@@ -399,135 +232,60 @@
             }
         }
 
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
-            TemplateValue v = templateEntry.getValue(attributeType);
-            if (v == null) {
+        @Override
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+            TemplateValue value = templateEntry.getValue(attributeType);
+            if (value == null) {
                 // This is fine -- we just won't append anything.
-                return TagResult.SUCCESS_RESULT;
+                return TagResult.SUCCESS;
             }
 
             if (numCharacters > 0) {
-                String valueString = v.getValueAsString();
+                String valueString = value.getValueAsString();
                 if (valueString.length() > numCharacters) {
                     templateValue.append(valueString.substring(0, numCharacters));
                 } else {
                     templateValue.append(valueString);
                 }
             } else {
-                templateValue.append(v.getValue());
+                templateValue.append(value.getValueAsString());
             }
 
-            return TagResult.SUCCESS_RESULT;
+            return TagResult.SUCCESS;
         }
     }
 
     /**
-     * This class defines a tag that is used to include the DN of the current
-     * entry in the attribute value.
+     * Tag used to include the DN of the current entry in the attribute value.
      */
     static class DNTag extends TemplateTag {
+
         /** The number of DN components to include. */
         private int numComponents;
 
-        /**
-         * Creates a new instance of this DN tag.
-         */
-        public DNTag() {
-            numComponents = 0;
-        }
-
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+        @Override
+        String getName() {
             return "DN";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
+        @Override
+        final boolean allowedInBranch() {
             return true;
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a branch definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param branch
-         *            The branch in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
+        final void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
                 int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber);
+            initialize(arguments, lineNumber);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
+        final void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
                 String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber);
+            initialize(arguments, lineNumber);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed for this
-         * tag.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @throws DecodeException
-         *             If a problem occurs while initializing this tag.
-         */
-        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber)
+        private void initialize(String[] arguments, int lineNumber)
                 throws DecodeException {
             if (arguments.length == 0) {
                 numComponents = 0;
@@ -546,61 +304,52 @@
             }
         }
 
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+        @Override
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+            return generateValue(templateEntry, templateValue, ",");
+        }
+
+        final TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue,
+                String separator) {
             DN dn = templateEntry.getDN();
             if ((dn == null) || dn.isRootDN()) {
-                return TagResult.SUCCESS_RESULT;
+                return TagResult.SUCCESS;
             }
 
+            String dnAsString = "";
             if (numComponents == 0) {
-                templateValue.append(dn.toNormalizedString());
+                // Return the DN of the entry
+                dnAsString = dn.toString();
             } else if (numComponents > 0) {
-                int count = Math.min(numComponents, dn.size());
-                templateValue.append(dn.rdn());
-                for (int i = 1; i < count; i++) {
-                    templateValue.append(",");
-                    templateValue.append(dn.parent(i).rdn());
-                }
+                // Return the first numComponents RDNs of the DN
+                dnAsString = dn.localName(numComponents).toString();
             } else {
-                int size = dn.size();
-                int count = Math.min(Math.abs(numComponents), size);
-
-                templateValue.append(dn.parent(size - count).rdn());
-                for (int i = 1; i < count; i++) {
-                    templateValue.append(",");
-                    templateValue.append(dn.parent(size - count + i).rdn());
-                }
+                // numComponents is negative
+                // Return the last numComponents RDNs of the DN
+                dnAsString = dn.parent(dn.size() - Math.abs(numComponents)).toString();
             }
+            // If expected separator is not standard separator
+            // Then substitute expected to standard
+            if (!separator.equals(",")) {
+                dnAsString = dnAsString.replaceAll(",", separator);
+            }
+            templateValue.append(dnAsString);
 
-            return TagResult.SUCCESS_RESULT;
+            return TagResult.SUCCESS;
         }
     }
 
     /**
-     * This class defines a tag that is used provide values from a text file.
-     * The file should have one value per line. Access to the values may be
-     * either at random or in sequential order.
+     * Tag used to provide values from a text file. The file should have one
+     * value per line. Access to the values may be either at random or in
+     * sequential order.
      */
     static class FileTag extends TemplateTag {
         /**
          * Indicates whether the values should be selected sequentially or at
          * random.
          */
-        private boolean sequential;
-
-        /** The file containing the data. */
-        private File dataFile;
+        private boolean isSequential = false;
 
         /** The index used for sequential access. */
         private int nextIndex;
@@ -608,104 +357,33 @@
         /** The random number generator for this tag. */
         private Random random;
 
-        /** The array of lines read from the file. */
+        /** The lines read from the file. */
         private String[] fileLines;
 
-        /**
-         * Creates a new instance of this file tag.
-         */
-        public FileTag() {
-            sequential = false;
-            dataFile = null;
-            nextIndex = 0;
-            random = null;
-            fileLines = null;
-        }
-
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+        @Override
+        String getName() {
             return "File";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
+        @Override
+        boolean allowedInBranch() {
             return true;
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a branch definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param branch
-         *            The branch in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
+        void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
                 int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber, warnings);
+            initialize(templateFile, arguments, lineNumber);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
+        void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
                 String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber, warnings);
+            initialize(templateFile, arguments, lineNumber);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         * @throws DecodeException
-         *             If a problem occurs while initializing this tag.
-         */
-        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber,
-                List<LocalizableMessage> warnings) throws DecodeException {
+        private void initialize(TemplateFile templateFile, String[] arguments, int lineNumber)
+                throws DecodeException {
             random = templateFile.getRandom();
 
             // There must be at least one argument, and possibly two.
@@ -716,11 +394,27 @@
             }
 
             // The first argument should be the path to the file.
-            dataFile = templateFile.getFile(arguments[0]);
-            if ((dataFile == null) || (!dataFile.exists())) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_CANNOT_FIND_FILE.get(arguments[0], getName(),
-                        lineNumber);
-                throw DecodeException.fatalError(message);
+            final String filePath = arguments[0];
+            BufferedReader dataReader = null;
+            try {
+                dataReader = templateFile.getReader(filePath);
+                if (dataReader == null) {
+                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_CANNOT_FIND_FILE.get(filePath, getName(),
+                            lineNumber);
+                    throw DecodeException.fatalError(message);
+                }
+
+                // See if the file has already been read into memory. If not, then
+                // read it.
+                try {
+                    fileLines = templateFile.getLines(filePath, dataReader);
+                } catch (IOException ioe) {
+                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_CANNOT_READ_FILE.get(filePath, getName(),
+                            lineNumber, String.valueOf(ioe));
+                    throw DecodeException.fatalError(message, ioe);
+                }
+            } finally {
+                StaticUtils.closeSilently(dataReader);
             }
 
             // If there is a second argument, then it should be either
@@ -728,43 +422,23 @@
             // assume "random".
             if (arguments.length == 2) {
                 if (arguments[1].equalsIgnoreCase("sequential")) {
-                    sequential = true;
+                    isSequential = true;
                     nextIndex = 0;
                 } else if (arguments[1].equalsIgnoreCase("random")) {
-                    sequential = false;
+                    isSequential = false;
                 } else {
                     LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_FILE_ACCESS_MODE.get(arguments[1],
                             getName(), lineNumber);
                     throw DecodeException.fatalError(message);
                 }
             } else {
-                sequential = false;
-            }
-
-            // See if the file has already been read into memory. If not, then
-            // read it.
-            try {
-                fileLines = templateFile.getFileLines(dataFile);
-            } catch (IOException ioe) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_CANNOT_READ_FILE.get(arguments[0], getName(),
-                        lineNumber, String.valueOf(ioe));
-                throw DecodeException.fatalError(message, ioe);
+                isSequential = false;
             }
         }
 
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
-            if (sequential) {
+        @Override
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+            if (isSequential) {
                 templateValue.append(fileLines[nextIndex++]);
                 if (nextIndex >= fileLines.length) {
                     nextIndex = 0;
@@ -773,170 +447,55 @@
                 templateValue.append(fileLines[random.nextInt(fileLines.length)]);
             }
 
-            return TagResult.SUCCESS_RESULT;
+            return TagResult.SUCCESS;
         }
     }
 
     /**
-     * This class defines a tag that is used to include a first name in the
-     * attribute value.
+     * Tag used to include a first name in the attribute value.
      */
-    static class FirstNameTag extends TemplateTag {
-        /** The template file with which this tag is associated. */
-        private TemplateFile templateFile;
-
-        /**
-         * Creates a new instance of this first name tag.
-         */
-        public FirstNameTag() {
-            // No implementation required.
-        }
-
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+    static class FirstNameTag extends NameTag {
+        @Override
+        String getName() {
             return "First";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
-            return false;
-        }
-
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
-                String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            this.templateFile = templateFile;
-
-            if (arguments.length != 0) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber,
-                        0, arguments.length);
-                throw DecodeException.fatalError(message);
-            }
-        }
-
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
             templateValue.append(templateFile.getFirstName());
-            return TagResult.SUCCESS_RESULT;
+            return TagResult.SUCCESS;
         }
     }
 
     /**
-     * This class defines a tag that is used to include a GUID in the attribute
-     * value.
+     * Tag used to include a GUID in the attribute value.
      */
     static class GUIDTag extends TemplateTag {
-        /**
-         * Creates a new instance of this GUID tag.
-         */
-        public GUIDTag() {
-            // No implementation required.
-        }
 
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+        @Override
+        String getName() {
             return "GUID";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
+        @Override
+        boolean allowedInBranch() {
             return true;
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a branch definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param branch
-         *            The branch in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
+        void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
                 int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            if (arguments.length != 0) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber,
-                        0, arguments.length);
-                throw DecodeException.fatalError(message);
-            }
+            initialize(arguments, lineNumber);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
+
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
+        void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
                 String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
+            initialize(arguments, lineNumber);
+        }
+
+        private void initialize(String[] arguments, int lineNumber) throws DecodeException {
             if (arguments.length != 0) {
                 LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber,
                         0, arguments.length);
@@ -944,408 +503,140 @@
             }
         }
 
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+        @Override
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
             templateValue.append(UUID.randomUUID().toString());
-            return TagResult.SUCCESS_RESULT;
+            return TagResult.SUCCESS;
         }
     }
 
     /**
-     * This class defines a tag that is used to base presence of one attribute
-     * on the absence of another attribute and/or attribute value.
+     * Base tag to use to base presence of one attribute on the absence/presence
+     * of another attribute and/or attribute value.
      */
-    static class IfAbsentTag extends TemplateTag {
+    abstract static class IfTag extends TemplateTag {
         /** The attribute type for which to make the determination. */
-        private AttributeType attributeType;
+        AttributeType attributeType;
 
         /** The value for which to make the determination. */
-        private String assertionValue;
+        String assertionValue;
 
-        /**
-         * Creates a new instance of this ifabsent tag.
-         */
-        public IfAbsentTag() {
-            attributeType = null;
-            assertionValue = null;
+        @Override
+        final boolean allowedInBranch() {
+            return true;
         }
 
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+        @Override
+        final void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
+                int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
+            initialize(schema, branch, null, arguments, lineNumber);
+        }
+
+        @Override
+        final void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
+                String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
+            initialize(schema, null, template, arguments, lineNumber);
+        }
+
+        private void initialize(Schema schema, Branch branch, Template template, String[] arguments, int lineNumber)
+                throws DecodeException {
+            if ((arguments.length < 1) || (arguments.length > 2)) {
+                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(),
+                        lineNumber, 1, 2, arguments.length);
+                throw DecodeException.fatalError(message);
+            }
+
+            attributeType = schema.getAttributeType(arguments[0].toLowerCase());
+            if (!hasAttributeTypeInBranchOrTemplate(attributeType, branch, template)) {
+                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_UNDEFINED_ATTRIBUTE.get(arguments[0], lineNumber);
+                throw DecodeException.fatalError(message);
+            }
+
+            if (arguments.length == 2) {
+                assertionValue = arguments[1];
+            } else {
+                assertionValue = null;
+            }
+        }
+    }
+
+    /**
+     * Tag used to base presence of one attribute on the absence of another
+     * attribute and/or attribute value.
+     */
+    static class IfAbsentTag extends IfTag {
+        @Override
+        String getName() {
             return "IfAbsent";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
-            return true;
-        }
-
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a branch definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param branch
-         *            The branch in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
-                int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            if ((arguments.length < 1) || (arguments.length > 2)) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(),
-                        lineNumber, 1, 2, arguments.length);
-                throw DecodeException.fatalError(message);
-            }
-
-            String lowerName = arguments[0].toLowerCase();
-            AttributeType t = schema.getAttributeType(lowerName);
-            if (!branch.hasAttribute(t)) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_UNDEFINED_ATTRIBUTE.get(arguments[0], lineNumber);
-                throw DecodeException.fatalError(message);
-            }
-
-            if (arguments.length == 2) {
-                assertionValue = arguments[1];
-            } else {
-                assertionValue = null;
-            }
-        }
-
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
-        @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
-                String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            if ((arguments.length < 1) || (arguments.length > 2)) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(),
-                        lineNumber, 1, 2, arguments.length);
-                throw DecodeException.fatalError(message);
-            }
-
-            String lowerName = arguments[0].toLowerCase();
-            attributeType = schema.getAttributeType(lowerName);
-            if (!template.hasAttribute(attributeType)) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_UNDEFINED_ATTRIBUTE.get(arguments[0], lineNumber);
-                throw DecodeException.fatalError(message);
-            }
-
-            if (arguments.length == 2) {
-                assertionValue = arguments[1];
-            } else {
-                assertionValue = null;
-            }
-        }
-
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
             List<TemplateValue> values = templateEntry.getValues(attributeType);
-            if ((values == null) || values.isEmpty()) {
-                return TagResult.SUCCESS_RESULT;
+            if (values == null) {
+                return TagResult.SUCCESS;
             }
-
             if (assertionValue == null) {
-                return TagResult.OMIT_FROM_ENTRY;
-            } else {
-                for (TemplateValue v : values) {
-                    if (assertionValue.equals(v.getValueAsString())) {
-                        return TagResult.OMIT_FROM_ENTRY;
-                    }
-                }
-
-                return TagResult.SUCCESS_RESULT;
+                return TagResult.FAILURE;
             }
+            for (TemplateValue v : values) {
+                if (assertionValue.equals(v.getValueAsString())) {
+                    return TagResult.FAILURE;
+                }
+            }
+            return TagResult.SUCCESS;
         }
     }
 
     /**
-     * This class defines a tag that is used to base presence of one attribute
-     * on the presence of another attribute and/or attribute value.
+     * Tag used to base presence of one attribute on the presence of another
+     * attribute and/or attribute value.
      */
-    static class IfPresentTag extends TemplateTag {
-        /** The attribute type for which to make the determination. */
-        private AttributeType attributeType;
-
-        /** The value for which to make the determination. */
-        private String assertionValue;
-
-        /**
-         * Creates a new instance of this ifpresent tag.
-         */
-        public IfPresentTag() {
-            attributeType = null;
-            assertionValue = null;
-        }
-
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+    static class IfPresentTag extends IfTag {
+        @Override
+        String getName() {
             return "IfPresent";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
-            return true;
-        }
-
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a branch definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param branch
-         *            The branch in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
-                int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            if ((arguments.length < 1) || (arguments.length > 2)) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(),
-                        lineNumber, 1, 2, arguments.length);
-                throw DecodeException.fatalError(message);
-            }
-
-            String lowerName = arguments[0].toLowerCase();
-            AttributeType t = schema.getAttributeType(lowerName);
-            if (!branch.hasAttribute(t)) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_UNDEFINED_ATTRIBUTE.get(arguments[0], lineNumber);
-                throw DecodeException.fatalError(message);
-            }
-
-            if (arguments.length == 2) {
-                assertionValue = arguments[1];
-            } else {
-                assertionValue = null;
-            }
-        }
-
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
-        @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
-                String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            if ((arguments.length < 1) || (arguments.length > 2)) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(),
-                        lineNumber, 1, 2, arguments.length);
-                throw DecodeException.fatalError(message);
-            }
-
-            String lowerName = arguments[0].toLowerCase();
-            attributeType = schema.getAttributeType(lowerName);
-            if (!template.hasAttribute(attributeType)) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_UNDEFINED_ATTRIBUTE.get(arguments[0], lineNumber);
-                throw DecodeException.fatalError(message);
-            }
-
-            if (arguments.length == 2) {
-                assertionValue = arguments[1];
-            } else {
-                assertionValue = null;
-            }
-        }
-
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
             List<TemplateValue> values = templateEntry.getValues(attributeType);
             if ((values == null) || values.isEmpty()) {
-                return TagResult.OMIT_FROM_ENTRY;
+                return TagResult.FAILURE;
             }
 
             if (assertionValue == null) {
-                return TagResult.SUCCESS_RESULT;
-            } else {
-                for (TemplateValue v : values) {
-                    if (assertionValue.equals(v.getValueAsString())) {
-                        return TagResult.SUCCESS_RESULT;
-                    }
-                }
-
-                return TagResult.OMIT_FROM_ENTRY;
+                return TagResult.SUCCESS;
             }
+            for (TemplateValue v : values) {
+                if (assertionValue.equals(v.getValueAsString())) {
+                    return TagResult.SUCCESS;
+                }
+            }
+            return TagResult.FAILURE;
         }
     }
 
     /**
-     * This class defines a tag that is used to include a last name in the
-     * attribute value.
+     * Tag used to include a last name in the attribute value.
      */
-    static class LastNameTag extends TemplateTag {
-        /** The template file with which this tag is associated. */
-        private TemplateFile templateFile;
-
-        /**
-         * Creates a new instance of this last name tag.
-         */
-        public LastNameTag() {
-            // No implementation required.
-        }
-
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+    static class LastNameTag extends NameTag {
+        @Override
+        String getName() {
             return "Last";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
-            return false;
-        }
-
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
-                String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            this.templateFile = templateFile;
-
-            if (arguments.length != 0) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber,
-                        0, arguments.length);
-                throw DecodeException.fatalError(message);
-            }
-        }
-
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
             templateValue.append(templateFile.getLastName());
-            return TagResult.SUCCESS_RESULT;
+            return TagResult.SUCCESS;
         }
     }
 
     /**
-     * This class defines a tag that may be used to select a value from a
-     * pre-defined list, optionally defining weights for each value that can
-     * impact the likelihood of a given item being selected.
+     * Tag used to select a value from a pre-defined list, optionally defining
+     * weights for each value that can impact the likelihood of a given item
+     * being selected.
      * <p>
      * The items to include in the list should be specified as arguments to the
      * tag. If the argument ends with a semicolon followed by an integer, then
@@ -1365,139 +656,58 @@
         /** The random number generator for this tag. */
         private Random random;
 
-        /**
-         * Creates a new instance of this list tag.
-         */
-        public ListTag() {
-            // No implementation required.
-        }
-
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+        @Override
+        String getName() {
             return "List";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
+        @Override
+        boolean allowedInBranch() {
             return true;
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a branch definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param branch
-         *            The branch in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
+        void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
                 int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber, warnings);
+            initialize(templateFile, arguments, lineNumber, warnings);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
+        void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
                 String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber, warnings);
+            initialize(templateFile, arguments, lineNumber, warnings);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed for this
-         * tag.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         * @throws DecodeException
-         *             If a problem occurs while initializing this tag.
-         */
-        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber,
+        private void initialize(TemplateFile templateFile, String[] arguments, int lineNumber,
                 List<LocalizableMessage> warnings) throws DecodeException {
             if (arguments.length == 0) {
                 throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_TAG_LIST_NO_ARGUMENTS.get(lineNumber));
             }
-
             valueStrings = new String[arguments.length];
             valueWeights = new int[arguments.length];
             cumulativeWeight = 0;
             random = templateFile.getRandom();
 
             for (int i = 0; i < arguments.length; i++) {
-                String s = arguments[i];
-
+                String value = arguments[i];
                 int weight = 1;
-                int semicolonPos = s.lastIndexOf(';');
+                int semicolonPos = value.lastIndexOf(';');
                 if (semicolonPos >= 0) {
                     try {
-                        weight = Integer.parseInt(s.substring(semicolonPos + 1));
-                        s = s.substring(0, semicolonPos);
-                    } catch (Exception e) {
-                        warnings.add(WARN_ENTRY_GENERATOR_TAG_LIST_INVALID_WEIGHT.get(lineNumber, s));
+                        weight = Integer.parseInt(value.substring(semicolonPos + 1));
+                        value = value.substring(0, semicolonPos);
+                    } catch (NumberFormatException e) {
+                        warnings.add(WARN_ENTRY_GENERATOR_TAG_LIST_INVALID_WEIGHT.get(lineNumber, value));
                     }
                 }
-
                 cumulativeWeight += weight;
-                valueStrings[i] = s;
+                valueStrings[i] = value;
                 valueWeights[i] = cumulativeWeight;
             }
         }
 
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+        @Override
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
             int selectedWeight = random.nextInt(cumulativeWeight) + 1;
             for (int i = 0; i < valueWeights.length; i++) {
                 if (selectedWeight <= valueWeights[i]) {
@@ -1505,62 +715,48 @@
                     break;
                 }
             }
-
-            return TagResult.SUCCESS_RESULT;
+            return TagResult.SUCCESS;
         }
     }
 
     /**
-     * This class defines a tag that is used to include the DN of the parent
-     * entry in the attribute value.
+     * Base tag to use to include a first name or last name in the attribute
+     * value.
      */
-    static class ParentDNTag extends TemplateTag {
-        /**
-         * Creates a new instance of this parent DN tag.
-         */
-        public ParentDNTag() {
-            // No implementation required.
-        }
+    static abstract class NameTag extends TemplateTag {
+        /** The template file with which this tag is associated. */
+        TemplateFile templateFile;
 
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
-            return "ParentDN";
-        }
-
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
+        @Override
+        final boolean allowedInBranch() {
             return false;
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
+        final void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
+                String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
+            this.templateFile = templateFile;
+
+            if (arguments.length != 0) {
+                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber,
+                        0, arguments.length);
+                throw DecodeException.fatalError(message);
+            }
+        }
+    }
+
+    /**
+     * Base tag to use to include the DN of the parent entry in the attribute value.
+     */
+    static abstract class ParentTag extends TemplateTag {
+
+        @Override
+        final boolean allowedInBranch() {
+            return false;
+        }
+
+        @Override
+        final void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
                 String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
             if (arguments.length != 0) {
                 LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber,
@@ -1568,129 +764,66 @@
                 throw DecodeException.fatalError(message);
             }
         }
+    }
 
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+    /**
+     * Tag used to include the DN of the parent entry in the attribute value.
+     */
+    static class ParentDNTag extends ParentTag {
+
+        @Override
+        String getName() {
+            return "ParentDN";
+        }
+
+        @Override
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
             DN parentDN = templateEntry.getParentDN();
             if ((parentDN == null) || parentDN.isRootDN()) {
-                return TagResult.SUCCESS_RESULT;
+                return TagResult.SUCCESS;
             }
-
             templateValue.append(parentDN);
-            return TagResult.SUCCESS_RESULT;
+            return TagResult.SUCCESS;
         }
     }
 
     /**
-     * This class defines a tag that is used to indicate that a value should
-     * only be included in a percentage of the entries.
+     * Tag used to indicate that a value should only be included in a percentage
+     * of the entries.
      */
     static class PresenceTag extends TemplateTag {
-        /** The percentage of the entries in which this attribute value should */
-        /** appear. */
+        /**
+         * The percentage of the entries in which this attribute value should
+         * appear.
+         */
         private int percentage;
 
         /** The random number generator for this tag. */
         private Random random;
 
-        /**
-         * Creates a new instance of this presence tag.
-         */
-        public PresenceTag() {
-            percentage = 100;
-        }
-
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+        @Override
+        String getName() {
             return "Presence";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
+        @Override
+        boolean allowedInBranch() {
             return true;
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a branch definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param branch
-         *            The branch in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
+        void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
                 int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber);
+            initialize(templateFile, arguments, lineNumber);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
+        void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
                 String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber);
+            initialize(templateFile, arguments, lineNumber);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed for this
-         * tag.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @throws DecodeException
-         *             If a problem occurs while initializing this tag.
-         */
-        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber)
+        private void initialize(TemplateFile templateFile, String[] arguments, int lineNumber)
                 throws DecodeException {
             random = templateFile.getRandom();
 
@@ -1702,14 +835,14 @@
 
             try {
                 percentage = Integer.parseInt(arguments[0]);
-
                 if (percentage < 0) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INTEGER_BELOW_LOWER_BOUND.get(percentage, 0,
-                            getName(), lineNumber);
+                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INTEGER_BELOW_LOWER_BOUND.get(percentage,
+                            0, getName(), lineNumber);
                     throw DecodeException.fatalError(message);
-                } else if (percentage > 100) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INTEGER_ABOVE_UPPER_BOUND.get(percentage, 100,
-                            getName(), lineNumber);
+                }
+                if (percentage > 100) {
+                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INTEGER_ABOVE_UPPER_BOUND.get(percentage,
+                            100, getName(), lineNumber);
                     throw DecodeException.fatalError(message);
                 }
             } catch (NumberFormatException nfe) {
@@ -1719,31 +852,20 @@
             }
         }
 
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+        @Override
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
             int intValue = random.nextInt(100);
             if (intValue < percentage) {
-                return TagResult.SUCCESS_RESULT;
+                return TagResult.SUCCESS;
             } else {
-                return TagResult.OMIT_FROM_ENTRY;
+                return TagResult.FAILURE;
             }
         }
     }
 
     /**
-     * This class defines a tag that may be used to generate random values. It
-     * has a number of subtypes based on the type of information that should be
-     * generated, including:
+     * Tag used to generate random values. It has a number of subtypes based on
+     * the type of information that should be generated, including:
      * <UL>
      * <LI>alpha:length</LI>
      * <LI>alpha:minlength:maxlength</LI>
@@ -1764,32 +886,31 @@
      * </UL>
      */
     static class RandomTag extends TemplateTag {
-        /**
-         * The value that indicates that the value is to be generated from a
-         * fixed number of characters from a given character set.
-         */
-        public static final int RANDOM_TYPE_CHARS_FIXED = 1;
 
-        /**
-         * The value that indicates that the value is to be generated from a
-         * variable number of characters from a given character set.
-         */
-        public static final int RANDOM_TYPE_CHARS_VARIABLE = 2;
-
-        /**
-         * The value that indicates that the value should be a random number.
-         */
-        public static final int RANDOM_TYPE_NUMERIC = 3;
-
-        /**
-         * The value that indicates that the value should be a random month.
-         */
-        public static final int RANDOM_TYPE_MONTH = 4;
-
-        /**
-         * The value that indicates that the value should be a telephone number.
-         */
-        public static final int RANDOM_TYPE_TELEPHONE = 5;
+        static enum RandomType {
+            /**
+             * Generates values from a fixed number of characters from a given
+             * character set.
+             */
+            CHARS_FIXED,
+            /**
+             * Generates values from a variable number of characters from a given
+             * character set.
+             */
+            CHARS_VARIABLE,
+            /**
+             * Generates numbers.
+             */
+            NUMERIC,
+            /**
+             * Generates months (as text).
+             */
+            MONTH,
+            /**
+             * Generates telephone numbers.
+             */
+            TELEPHONE
+        }
 
         /**
          * The character set that will be used for alphabetic characters.
@@ -1831,7 +952,7 @@
 
         /** The number of characters between the minimum and maximum length */
         /** (inclusive). */
-        private int lengthRange;
+        private int lengthRange = 1;
 
         /** The maximum number of characters to include in the value. */
         private int maxLength;
@@ -1840,7 +961,7 @@
         private int minLength;
 
         /** The type of random value that should be generated. */
-        private int randomType;
+        private RandomType randomType;
 
         /** The maximum numeric value that should be generated. */
         private long maxValue;
@@ -1852,110 +973,34 @@
          * The number of values between the minimum and maximum value
          * (inclusive).
          */
-        private long valueRange;
+        private long valueRange = 1L;
 
         /** The random number generator for this tag. */
         private Random random;
 
-        /**
-         * Creates a new instance of this random tag.
-         */
-        public RandomTag() {
-            characterSet = null;
-            decimalFormat = null;
-            lengthRange = 1;
-            maxLength = 0;
-            minLength = 0;
-            randomType = 0;
-            maxValue = 0L;
-            minValue = 0L;
-            valueRange = 1L;
-        }
-
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+        @Override
+        String getName() {
             return "Random";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
+        @Override
+        boolean allowedInBranch() {
             return true;
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a branch definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param branch
-         *            The branch in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
+        void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
                 int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber, warnings);
+            initialize(templateFile, arguments, lineNumber, warnings);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
+        void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
                 String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber, warnings);
+            initialize(templateFile, arguments, lineNumber, warnings);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing either a branch or template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         * @throws DecodeException
-         *             If a problem occurs while initializing this tag.
-         */
-        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber,
+        private void initialize(TemplateFile templateFile, String[] arguments, int lineNumber,
                 List<LocalizableMessage> warnings) throws DecodeException {
             random = templateFile.getRandom();
 
@@ -1974,7 +1019,7 @@
                 decodeLength(arguments, 1, lineNumber, warnings);
             } else if (randomTypeString.equals("numeric")) {
                 if (numArgs == 2) {
-                    randomType = RANDOM_TYPE_CHARS_FIXED;
+                    randomType = RandomType.CHARS_FIXED;
                     characterSet = NUMERIC_CHARS;
 
                     try {
@@ -1994,7 +1039,7 @@
                         throw DecodeException.fatalError(message, nfe);
                     }
                 } else if ((numArgs == 3) || (numArgs == 4)) {
-                    randomType = RANDOM_TYPE_NUMERIC;
+                    randomType = RandomType.NUMERIC;
 
                     if (numArgs == 4) {
                         try {
@@ -2054,7 +1099,7 @@
                 characterSet = BASE64_CHARS;
                 decodeLength(arguments, 1, lineNumber, warnings);
             } else if (randomTypeString.equals("month")) {
-                randomType = RANDOM_TYPE_MONTH;
+                randomType = RandomType.MONTH;
 
                 if (numArgs == 1) {
                     maxLength = 0;
@@ -2077,7 +1122,7 @@
                     throw DecodeException.fatalError(message);
                 }
             } else if (randomTypeString.equals("telephone")) {
-                randomType = RANDOM_TYPE_TELEPHONE;
+                randomType = RandomType.TELEPHONE;
             } else {
                 LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_UNKNOWN_RANDOM_TYPE.get(lineNumber,
                         randomTypeString);
@@ -2108,7 +1153,7 @@
 
             if (numArgs == 2) {
                 // There is a fixed number of characters in the value.
-                randomType = RANDOM_TYPE_CHARS_FIXED;
+                randomType = RandomType.CHARS_FIXED;
 
                 try {
                     minLength = Integer.parseInt(arguments[startPos]);
@@ -2128,7 +1173,7 @@
                 }
             } else if (numArgs == 3) {
                 // There are minimum and maximum lengths.
-                randomType = RANDOM_TYPE_CHARS_VARIABLE;
+                randomType = RandomType.CHARS_VARIABLE;
 
                 try {
                     minLength = Integer.parseInt(arguments[startPos]);
@@ -2168,33 +1213,23 @@
             }
         }
 
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+        @Override
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
             switch (randomType) {
-            case RANDOM_TYPE_CHARS_FIXED:
+            case CHARS_FIXED:
                 for (int i = 0; i < minLength; i++) {
                     templateValue.append(characterSet[random.nextInt(characterSet.length)]);
                 }
                 break;
 
-            case RANDOM_TYPE_CHARS_VARIABLE:
+            case CHARS_VARIABLE:
                 int numChars = random.nextInt(lengthRange) + minLength;
                 for (int i = 0; i < numChars; i++) {
                     templateValue.append(characterSet[random.nextInt(characterSet.length)]);
                 }
                 break;
 
-            case RANDOM_TYPE_NUMERIC:
+            case NUMERIC:
                 long randomValue = ((random.nextLong() & 0x7FFFFFFFFFFFFFFFL) % valueRange) + minValue;
                 if (decimalFormat == null) {
                     templateValue.append(randomValue);
@@ -2203,7 +1238,7 @@
                 }
                 break;
 
-            case RANDOM_TYPE_MONTH:
+            case MONTH:
                 String month = MONTHS[random.nextInt(MONTHS.length)];
                 if ((maxLength == 0) || (month.length() <= maxLength)) {
                     templateValue.append(month);
@@ -2212,7 +1247,7 @@
                 }
                 break;
 
-            case RANDOM_TYPE_TELEPHONE:
+            case TELEPHONE:
                 templateValue.append("+1 ");
                 for (int i = 0; i < 3; i++) {
                     templateValue.append(NUMERIC_CHARS[random.nextInt(NUMERIC_CHARS.length)]);
@@ -2228,89 +1263,38 @@
                 break;
             }
 
-            return TagResult.SUCCESS_RESULT;
+            return TagResult.SUCCESS;
         }
     }
 
     /**
-     * This class defines a tag that is used to include the RDN of the current
-     * entry in the attribute value.
+     * Tag used to include the RDN of the current entry in the attribute value.
      */
     static class RDNTag extends TemplateTag {
-        /**
-         * Creates a new instance of this RDN tag.
-         */
-        public RDNTag() {
-            // No implementation required.
-        }
 
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+        @Override
+        String getName() {
             return "RDN";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
+        @Override
+        boolean allowedInBranch() {
             return true;
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a branch definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param branch
-         *            The branch in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
+        void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
                 int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            if (arguments.length != 0) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber,
-                        0, arguments.length);
-                throw DecodeException.fatalError(message);
-            }
+            initialize(arguments, lineNumber);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
-                String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
+        void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template, String[] arguments,
+                int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
+            initialize(arguments, lineNumber);
+        }
+
+        private void initialize(String[] arguments, int lineNumber) throws DecodeException {
             if (arguments.length != 0) {
                 LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber,
                         0, arguments.length);
@@ -2318,35 +1302,25 @@
             }
         }
 
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+        @Override
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
             DN dn = templateEntry.getDN();
             if ((dn == null) || dn.isRootDN()) {
-                return TagResult.SUCCESS_RESULT;
+                return TagResult.SUCCESS;
             } else {
                 templateValue.append(dn.rdn());
-                return TagResult.SUCCESS_RESULT;
+                return TagResult.SUCCESS;
             }
         }
     }
 
     /**
-     * This class defines a tag that is used to include a
-     * sequentially-incrementing integer in the generated values.
+     * Tag that is used to include a sequentially-incrementing integer in the
+     * generated values.
      */
     static class SequentialTag extends TemplateTag {
         /** Indicates whether to reset for each parent. */
-        private boolean resetOnNewParents;
+        private boolean resetOnNewParents = true;
 
         /** The initial value in the sequence. */
         private int initialValue;
@@ -2354,515 +1328,156 @@
         /** The next value in the sequence. */
         private int nextValue;
 
-        /**
-         * Creates a new instance of this sequential tag.
-         */
-        public SequentialTag() {
-            // No implementation required.
-        }
-
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+        @Override
+        String getName() {
             return "Sequential";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
+        @Override
+        boolean allowedInBranch() {
             return true;
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a branch definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param branch
-         *            The branch in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
+        void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
                 int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber);
+            initialize(arguments, lineNumber);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
+        void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
                 String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber);
+            initialize(arguments, lineNumber);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed for this
-         * tag.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @throws DecodeException
-         *             If a problem occurs while initializing this tag.
-         */
-        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber)
-                throws DecodeException {
-            switch (arguments.length) {
-            case 0:
-                initialValue = 0;
-                nextValue = 0;
-                resetOnNewParents = true;
-                break;
-            case 1:
-                try {
-                    initialValue = Integer.parseInt(arguments[0]);
-                } catch (NumberFormatException nfe) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[0],
-                            getName(), lineNumber);
-                    throw DecodeException.fatalError(message);
+        private void initialize(String[] arguments, int lineNumber) throws DecodeException {
+            if (arguments.length < 0 || arguments.length > 2) {
+                throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(
+                        getName(), lineNumber, 0, 2, arguments.length));
+            }
+            if (arguments.length > 0) {
+                initializeValue(arguments[0], lineNumber);
+                if (arguments.length > 1) {
+                    initializeReset(arguments[1], lineNumber);
                 }
-
-                nextValue = initialValue;
-                resetOnNewParents = true;
-                break;
-            case 2:
-                try {
-                    initialValue = Integer.parseInt(arguments[0]);
-                } catch (NumberFormatException nfe) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[0],
-                            getName(), lineNumber);
-                    throw DecodeException.fatalError(message);
-                }
-
-                if (arguments[1].equalsIgnoreCase("true")) {
-                    resetOnNewParents = true;
-                } else if (arguments[1].equalsIgnoreCase("false")) {
-                    resetOnNewParents = false;
-                } else {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_CANNOT_PARSE_AS_BOOLEAN.get(arguments[1],
-                            getName(), lineNumber);
-                    throw DecodeException.fatalError(message);
-                }
-
-                nextValue = initialValue;
-                break;
-            default:
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(),
-                        lineNumber, 0, 2, arguments.length);
-                throw DecodeException.fatalError(message);
             }
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed when
-         * starting to generate entries below a new parent.
-         *
-         * @param parentEntry
-         *            The entry below which the new entries will be generated.
-         */
-        public void initializeForParent(TemplateEntry parentEntry) {
+        private void initializeReset(String resetValue, int lineNumber) throws DecodeException {
+            if (resetValue.equalsIgnoreCase("true")) {
+                resetOnNewParents = true;
+            } else if (resetValue.equalsIgnoreCase("false")) {
+                resetOnNewParents = false;
+            } else {
+                throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_TAG_CANNOT_PARSE_AS_BOOLEAN.get(
+                        resetValue, getName(), lineNumber));
+            }
+        }
+
+        private void initializeValue(String value, int lineNumber) throws DecodeException {
+            try {
+                initialValue = Integer.parseInt(value);
+            } catch (NumberFormatException nfe) {
+                throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_TAG_CANNOT_PARSE_AS_INTEGER.get(
+                        value, getName(), lineNumber));
+            }
+            nextValue = initialValue;
+        }
+
+        @Override
+        void initializeForParent(TemplateEntry parentEntry) {
             if (resetOnNewParents) {
                 nextValue = initialValue;
             }
         }
 
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+        @Override
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
             templateValue.append(nextValue++);
-            return TagResult.SUCCESS_RESULT;
+            return TagResult.SUCCESS;
         }
     }
 
     /**
-     * This class defines a tag that is used to hold static text (i.e., text
-     * that appears outside of any tag).
+     * Tag that is used to hold static text (i.e., text that appears outside of
+     * any tag).
      */
     static class StaticTextTag extends TemplateTag {
 
-        /** The static text to include in the LDIF. */
-        private String text;
+        /** The static text to include. */
+        private String text = "";
 
-        /**
-         * Creates a new instance of this static text tag.
-         */
-        public StaticTextTag() {
-            text = "";
-        }
-
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+        @Override
+        String getName() {
             return "StaticText";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
+        @Override
+        boolean allowedInBranch() {
             return true;
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a branch definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param branch
-         *            The branch in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
+        void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
                 int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            if (arguments.length != 1) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber,
-                        1, arguments.length);
-                throw DecodeException.fatalError(message);
-            }
-
-            text = arguments[0];
+            initialize(arguments, lineNumber);
         }
 
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
+        void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
                 String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
+            initialize(arguments, lineNumber);
+        }
+
+        private void initialize(String[] arguments, int lineNumber) throws DecodeException {
             if (arguments.length != 1) {
                 LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber,
                         1, arguments.length);
                 throw DecodeException.fatalError(message);
             }
-
             text = arguments[0];
         }
 
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+        @Override
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
             templateValue.append(text);
-            return TagResult.SUCCESS_RESULT;
+            return TagResult.SUCCESS;
         }
     }
 
     /**
-     * This class defines a tag that is used to include the DN of the current
-     * entry in the attribute value, with underscores in place of the commas.
+     * Tag used to include the DN of the current entry in the attribute value,
+     * with underscores in place of the commas.
      */
-    static class UnderscoreDNTag extends TemplateTag {
-        /** The number of DN components to include. */
-        private int numComponents;
+    static class UnderscoreDNTag extends DNTag {
 
-        /**
-         * Creates a new instance of this DN tag.
-         */
-        public UnderscoreDNTag() {
-            numComponents = 0;
-        }
-
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+        @Override
+        String getName() {
             return "_DN";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
-            return true;
-        }
-
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a branch definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param branch
-         *            The branch in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
-                int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber);
-        }
-
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
-        @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
-                String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            initializeInternal(templateFile, arguments, lineNumber);
-        }
-
-        /**
-         * Performs any initialization for this tag that may be needed for this
-         * tag.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @throws DecodeException
-         *             TODO
-         */
-        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber)
-                throws DecodeException {
-            if (arguments.length == 0) {
-                numComponents = 0;
-            } else if (arguments.length == 1) {
-                try {
-                    numComponents = Integer.parseInt(arguments[0]);
-                } catch (NumberFormatException nfe) {
-                    LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[0],
-                            getName(), lineNumber);
-                    throw DecodeException.fatalError(message);
-                }
-            } else {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(),
-                        lineNumber, 0, 1, arguments.length);
-                throw DecodeException.fatalError(message);
-            }
-        }
-
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
-            DN dn = templateEntry.getDN();
-            if ((dn == null) || dn.isRootDN()) {
-                return TagResult.SUCCESS_RESULT;
-            }
-
-            if (numComponents == 0) {
-                templateValue.append(dn.rdn());
-                for (int i = 1; i < dn.size(); i++) {
-                    templateValue.append("_");
-                    templateValue.append(dn.parent(i).rdn());
-                }
-            } else if (numComponents > 0) {
-                int count = Math.min(numComponents, dn.size());
-                templateValue.append(dn.rdn());
-                for (int i = 1; i < count; i++) {
-                    templateValue.append(",");
-                    templateValue.append(dn.parent(i).rdn());
-                }
-            } else {
-                int size = dn.size();
-                int count = Math.min(Math.abs(numComponents), size);
-
-                templateValue.append(dn.parent(size - count).rdn());
-                for (int i = 1; i < count; i++) {
-                    templateValue.append(",");
-                    templateValue.append(dn.parent(size - count + i).rdn());
-                }
-            }
-
-            return TagResult.SUCCESS_RESULT;
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+            return generateValue(templateEntry, templateValue, "_");
         }
     }
 
     /**
-     * This class defines a tag that is used to include the DN of the parent
-     * entry in the attribute value, with underscores in place of commas.
+     * Tag used to include the DN of the parent entry in the attribute value,
+     * with underscores in place of commas.
      */
-    static class UnderscoreParentDNTag extends TemplateTag {
-        /**
-         * Creates a new instance of this underscore parent DN tag.
-         */
-        public UnderscoreParentDNTag() {
-            // No implementation required.
-        }
+    static class UnderscoreParentDNTag extends ParentTag {
 
-        /**
-         * Retrieves the name for this tag.
-         *
-         * @return The name for this tag.
-         */
-        public String getName() {
+        @Override
+        String getName() {
             return "_ParentDN";
         }
 
-        /**
-         * Indicates whether this tag is allowed for use in the extra lines for
-         * branches.
-         *
-         * @return <CODE>true</CODE> if this tag may be used in branch
-         *         definitions, or <CODE>false</CODE> if not.
-         */
-        public boolean allowedInBranch() {
-            return false;
-        }
-
-        /**
-         * Performs any initialization for this tag that may be needed while
-         * parsing a template definition.
-         *
-         * @param templateFile
-         *            The template file in which this tag is used.
-         * @param template
-         *            The template in which this tag is used.
-         * @param arguments
-         *            The set of arguments provided for this tag.
-         * @param lineNumber
-         *            The line number on which this tag appears in the template
-         *            file.
-         * @param warnings
-         *            A list into which any appropriate warning messages may be
-         *            placed.
-         */
         @Override
-        public void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
-                String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws DecodeException {
-            if (arguments.length != 0) {
-                LocalizableMessage message = ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber,
-                        0, arguments.length);
-                throw DecodeException.fatalError(message);
-            }
-        }
-
-        /**
-         * Generates the content for this tag by appending it to the provided
-         * tag.
-         *
-         * @param templateEntry
-         *            The entry for which this tag is being generated.
-         * @param templateValue
-         *            The template value to which the generated content should
-         *            be appended.
-         * @return The result of generating content for this tag.
-         */
-        public TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
+        TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue) {
             DN parentDN = templateEntry.getParentDN();
             if ((parentDN == null) || parentDN.isRootDN()) {
-                return TagResult.SUCCESS_RESULT;
+                return TagResult.SUCCESS;
             }
             templateValue.append(parentDN.rdn());
             for (int i = 1; i < parentDN.size(); i++) {
@@ -2870,7 +1485,7 @@
                 templateValue.append(parentDN.parent(i).rdn());
             }
 
-            return TagResult.SUCCESS_RESULT;
+            return TagResult.SUCCESS;
         }
     }
 }
diff --git a/opendj3/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties b/opendj3/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
index 47396b9..a09f7d8 100755
--- a/opendj3/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
+++ b/opendj3/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
@@ -1434,9 +1434,6 @@
 ERR_ENTRY_GENERATOR_TAG_CANNOT_PARSE_AS_BOOLEAN=Cannot parse value "%s" as \
  a Boolean value for tag %s on line %d of the template file.  The value must \
  be either 'true' or 'false'
-ERR_ENTRY_GENERATOR_UNDEFINED_BRANCH_SUBORDINATE=The branch with entry DN \
- '%s' references a subordinate template named '%s' which is not defined in the \
- template file
 ERR_ENTRY_GENERATOR_CANNOT_LOAD_TAG_CLASS=Unable to load class %s for use \
  as a MakeLDIF tag
 ERR_ENTRY_GENERATOR_CANNOT_INSTANTIATE_TAG=Cannot instantiate class %s as a \
@@ -1450,9 +1447,6 @@
  %d is missing an equal sign to delimit the constant name from the value
 ERR_ENTRY_GENERATOR_DEFINE_NAME_EMPTY=The constant definition on line %d \
  does not include a name for the constant
-ERR_ENTRY_GENERATOR_CONFLICTING_CONSTANT_NAME=The definition for constant \
- %s on line %d conflicts with an earlier constant definition included in the \
- template
 ERR_ENTRY_GENERATOR_WARNING_DEFINE_VALUE_EMPTY=Constant %s defined on line \
  %d has not been assigned a value
 ERR_ENTRY_GENERATOR_CONFLICTING_BRANCH_DN=The branch definition %s starting \
@@ -1468,18 +1462,21 @@
  template file
 ERR_ENTRY_GENERATOR_CANNOT_DECODE_BRANCH_DN=Unable to decode branch DN "%s" \
  on line %d of the template file
-ERR_ENTRY_GENERATOR_BRANCH_SUBORDINATE_TEMPLATE_NO_COLON=Subordinate \
- template definition on line %d for branch %s is missing a colon to separate \
+ERR_ENTRY_GENERATOR_UNDEFINED_BRANCH_SUBORDINATE=The branch with entry DN \
+ '%s' references a subordinate template named '%s' which is not defined in the \
+ template file
+ERR_ENTRY_GENERATOR_SUBORDINATE_TEMPLATE_NO_COLON=Subordinate \
+ template definition on line %d for %s %s is missing a colon to separate \
  the template name from the number of entries
-ERR_ENTRY_GENERATOR_BRANCH_SUBORDINATE_INVALID_NUM_ENTRIES=Subordinate \
- template definition on line %d for branch %s specified invalid number of \
+ERR_ENTRY_GENERATOR_SUBORDINATE_INVALID_NUM_ENTRIES=Subordinate \
+ template definition on line %d for %s %s specified invalid number of \
  entries %d for template %s
-WARN_ENTRY_GENERATOR_BRANCH_SUBORDINATE_ZERO_ENTRIES=Subordinate template \
- definition on line %d for branch %s specifies that zero entries of type %s \
+WARN_ENTRY_GENERATOR_SUBORDINATE_ZERO_ENTRIES=Subordinate template \
+ definition on line %d for %s %s specifies that zero entries of type %s \
  should be generated
-ERR_ENTRY_GENERATOR_BRANCH_SUBORDINATE_CANT_PARSE_NUMENTRIES=Unable to \
+ERR_ENTRY_GENERATOR_SUBORDINATE_CANT_PARSE_NUMENTRIES=Unable to \
  parse the number of entries for template %s as an integer for the subordinate \
- template definition on line %d for branch %s
+ template definition on line %d for %s %s
 ERR_ENTRY_GENERATOR_TEMPLATE_SUBORDINATE_TEMPLATE_NO_COLON=Subordinate \
  template definition on line %d for template %s is missing a colon to separate \
  the template name from the number of entries
@@ -1494,22 +1491,14 @@
  template definition on line %d for template %s
 ERR_ENTRY_GENERATOR_TEMPLATE_MISSING_RDN_ATTR=The template named %s \
  includes RDN attribute %s that is not assigned a value in that template
-ERR_ENTRY_GENERATOR_NO_COLON_IN_BRANCH_EXTRA_LINE=There is no colon to \
- separate the attribute name from the value pattern on line %d of the template \
- file in the definition for branch %s
-ERR_ENTRY_GENERATOR_NO_ATTR_IN_BRANCH_EXTRA_LINE=There is no attribute name \
- before the colon on line %d of the template file in the definition for branch \
- %s
-WARN_ENTRY_GENERATOR_NO_VALUE_IN_BRANCH_EXTRA_LINE=The value pattern for \
- line %d of the template file in the definition for branch %s is empty
 ERR_ENTRY_GENERATOR_NO_COLON_IN_TEMPLATE_LINE=There is no colon to separate \
  the attribute name from the value pattern on line %d of the template file in \
- the definition for template %s
+ the definition for %s %s
 ERR_ENTRY_GENERATOR_NO_ATTR_IN_TEMPLATE_LINE=There is no attribute name \
  before the colon on line %d of the template file in the definition for \
- template %s
+ template %s %s
 WARN_ENTRY_GENERATOR_NO_VALUE_IN_TEMPLATE_LINE=The value pattern for line \
- %d of the template file in the definition for template %s is empty
+ %d of the template file in the definition for %s %s is empty
 ERR_ENTRY_GENERATOR_NO_SUCH_TAG=An undefined tag %s is referenced on line \
  %d of the template file
 ERR_ENTRY_GENERATOR_CANNOT_INSTANTIATE_NEW_TAG=An unexpected error occurred \
@@ -1526,6 +1515,8 @@
  the template file references an unknown random type of %s
 ERR_ENTRY_GENERATOR_COULD_NOT_FIND_TEMPLATE_FILE=Could not find template \
  file %s
+ERR_ENTRY_GENERATOR_COULD_NOT_FIND_NAME_FILE=Could not find names resource \
+ file %s
 ERR_ENTRY_GENERATOR_TAG_CANNOT_FIND_FILE=Cannot find file %s referenced by \
  tag %s on line %d of the template file
 ERR_ENTRY_GENERATOR_TAG_INVALID_FILE_ACCESS_MODE=Invalid file access mode \
diff --git a/opendj3/opendj-core/src/main/resources/org/forgerock/opendj/ldif/example.template b/opendj3/opendj-core/src/main/resources/org/forgerock/opendj/ldif/example.template
index 0b3655f..3f5b1e3 100644
--- a/opendj3/opendj-core/src/main/resources/org/forgerock/opendj/ldif/example.template
+++ b/opendj3/opendj-core/src/main/resources/org/forgerock/opendj/ldif/example.template
@@ -3,8 +3,12 @@
 define numusers=10000
 
 branch: [suffix]
+objectClass: top
+objectClass: domainComponent
 
 branch: ou=People,[suffix]
+objectClass: top
+objectClass: organizationalUnit
 subordinateTemplate: person:[numusers]
 
 template: person
diff --git a/opendj3/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template b/opendj3/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template
new file mode 100644
index 0000000..0f2c423
--- /dev/null
+++ b/opendj3/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template
@@ -0,0 +1,62 @@
+define suffix=dc=example,dc=com
+define maildomain=example.com
+define numusers=10
+define numous=10
+define numgroup=5
+
+branch: [suffix]
+subordinateTemplate: ous:[numous]
+
+template: ous
+subordinateTemplate: People:1
+subordinateTemplate: Groups:1
+rdnAttr: ou
+objectclass: top
+objectclass: organizationalUnit
+ou: Organization_<sequential:1>
+description: This is {ou}
+
+template: People
+rdnAttr: ou
+subordinateTemplate: person:[numusers]
+objectclass: top
+objectclass: organizationalUnit
+ou: People
+
+template: Groups
+subordinateTemplate: groupOfName:[numgroup]
+rdnAttr: ou
+objectclass: top
+objectclass: organizationalUnit
+ou: Groups
+
+template: person
+rdnAttr: uid
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: inetOrgPerson
+givenName: <first>
+sn: <last>
+cn: {givenName} {sn}
+initials: {givenName:1}<random:chars:ABCDEFGHIJKLMNOPQRSTUVWXYZ:1>{sn:1}
+employeeNumber: <sequential:0>
+uid: user.{employeeNumber}
+mail: {uid}@[maildomain]
+userPassword: password
+telephoneNumber: <random:telephone>
+homePhone: <random:telephone>
+pager: <random:telephone>
+mobile: <random:telephone>
+street: <random:numeric:5> <file:streets> Street
+l: <file:cities>
+st: <file:states>
+postalCode: <random:numeric:5>
+postalAddress: {cn}${street}${l}, {st}  {postalCode}
+description: This is the description for {cn}.
+
+template: groupOfName
+rdnAttr: cn
+objectClass: top
+objectClass: groupOfNames
+cn: Group_<sequential:1>
\ No newline at end of file
diff --git a/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldap/RDNTestCase.java b/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldap/RDNTestCase.java
index 4961c05..82a7e29 100644
--- a/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldap/RDNTestCase.java
+++ b/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldap/RDNTestCase.java
@@ -32,8 +32,8 @@
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
+import java.util.Arrays;
 import java.util.Iterator;
-
 import org.forgerock.i18n.LocalizedIllegalArgumentException;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.forgerock.opendj.ldap.schema.Schema;
@@ -48,19 +48,19 @@
 public final class RDNTestCase extends TypesTestCase {
 
     // Domain component attribute type.
-    private static final AttributeType AT_DC;
+    private static final AttributeType ATTR_TYPE_DC;
 
     // Common name attribute type.
-    private static final AttributeType AT_CN;
+    private static final AttributeType ATTR_TYPE_CN;
 
     // Test attribute value.
-    private static final AVA AV_DC_ORG;
+    private static final AVA ATTR_VALUE_DC_ORG;
 
     static {
-        AT_DC = Schema.getCoreSchema().getAttributeType("dc");
-        AT_CN = Schema.getCoreSchema().getAttributeType("cn");
+        ATTR_TYPE_DC = Schema.getCoreSchema().getAttributeType("dc");
+        ATTR_TYPE_CN = Schema.getCoreSchema().getAttributeType("cn");
         // Set the avas.
-        AV_DC_ORG = new AVA(AT_DC, ByteString.valueOf("org"));
+        ATTR_VALUE_DC_ORG = new AVA(ATTR_TYPE_DC, ByteString.valueOf("org"));
     }
 
     // org bytestring.
@@ -211,13 +211,13 @@
      */
     @Test
     public void testConstructor() throws Exception {
-        final RDN rdn = new RDN(AT_DC, ORG);
+        final RDN rdn = new RDN(ATTR_TYPE_DC, ORG);
 
         assertEquals(rdn.size(), 1);
         assertEquals(rdn.isMultiValued(), false);
-        assertEquals(rdn.getFirstAVA().getAttributeType(), AT_DC);
-        assertEquals(rdn.getFirstAVA().getAttributeType().getNameOrOID(), AT_DC.getNameOrOID());
-        assertEquals(rdn.getFirstAVA(), AV_DC_ORG);
+        assertEquals(rdn.getFirstAVA().getAttributeType(), ATTR_TYPE_DC);
+        assertEquals(rdn.getFirstAVA().getAttributeType().getNameOrOID(), ATTR_TYPE_DC.getNameOrOID());
+        assertEquals(rdn.getFirstAVA(), ATTR_VALUE_DC_ORG);
     }
 
     /**
@@ -230,9 +230,51 @@
     public void testConstructorWithString() throws Exception {
         final RDN rdn = new RDN("dc", "org");
         assertEquals(rdn.size(), 1);
-        assertEquals(rdn.getFirstAVA().getAttributeType(), AT_DC);
+        assertEquals(rdn.getFirstAVA().getAttributeType(), ATTR_TYPE_DC);
         assertEquals(rdn.getFirstAVA().getAttributeType().getNameOrOID(), "dc");
-        assertEquals(rdn.getFirstAVA(), AV_DC_ORG);
+        assertEquals(rdn.getFirstAVA(), ATTR_VALUE_DC_ORG);
+    }
+
+    @Test
+    public void testConstructorWithAVA() throws Exception {
+        final RDN rdn = new RDN(new AVA("dc", "org"));
+        assertEquals(rdn.size(), 1);
+        assertEquals(rdn.getFirstAVA().getAttributeType(), ATTR_TYPE_DC);
+        assertEquals(rdn.getFirstAVA(), ATTR_VALUE_DC_ORG);
+    }
+
+    @Test
+    public void testConstructorWithMultipleAVAs() throws Exception {
+        AVA example = new AVA("dc", "example");
+        AVA org = new AVA("dc", "org");
+
+        final RDN rdn = new RDN(example, org);
+        assertEquals(rdn.size(), 2);
+        Iterator<AVA> rdnIt = rdn.iterator();
+        AVA firstAva = rdnIt.next();
+        assertEquals(firstAva.getAttributeType(), ATTR_TYPE_DC);
+        assertEquals(firstAva, example);
+
+        AVA secondAva = rdnIt.next();
+        assertEquals(secondAva.getAttributeType(), ATTR_TYPE_DC);
+        assertEquals(secondAva, org);
+    }
+
+    @Test
+    public void testConstructorWithCollectionOfAVAs() throws Exception {
+        AVA example = new AVA("dc", "example");
+        AVA org = new AVA("dc", "org");
+
+        final RDN rdn = new RDN(Arrays.asList(example, org));
+        assertEquals(rdn.size(), 2);
+        Iterator<AVA> rdnIt = rdn.iterator();
+        AVA firstAva = rdnIt.next();
+        assertEquals(firstAva.getAttributeType(), ATTR_TYPE_DC);
+        assertEquals(firstAva, example);
+
+        AVA secondAva = rdnIt.next();
+        assertEquals(secondAva.getAttributeType(), ATTR_TYPE_DC);
+        assertEquals(secondAva, org);
     }
 
     /**
@@ -298,7 +340,7 @@
      */
     @Test
     public void testDuplicateSingle() {
-        final RDN rdn1 = new RDN(AT_DC, ORG);
+        final RDN rdn1 = new RDN(ATTR_TYPE_DC, ORG);
         final RDN rdn2 = RDN.valueOf("dc=org");
 
         assertFalse(rdn1 == rdn2);
@@ -338,7 +380,7 @@
      */
     @Test
     public void testEqualityNonRDN() {
-        final RDN rdn = new RDN(AT_DC, ORG);
+        final RDN rdn = new RDN(ATTR_TYPE_DC, ORG);
 
         assertFalse(rdn.equals("this isn't an RDN"));
     }
@@ -351,7 +393,7 @@
      */
     @Test
     public void testEqualityNull() {
-        final RDN rdn = new RDN(AT_DC, ORG);
+        final RDN rdn = new RDN(ATTR_TYPE_DC, ORG);
 
         assertFalse(rdn.equals(null));
     }
@@ -368,8 +410,8 @@
         assertTrue(rdn.isMultiValued());
         assertEquals(rdn.size(), 2);
         final Iterator<AVA> it = rdn.iterator();
-        assertEquals(it.next().getAttributeType().getNameOrOID(), AT_DC.getNameOrOID());
-        assertEquals(it.next().getAttributeType().getNameOrOID(), AT_CN.getNameOrOID());
+        assertEquals(it.next().getAttributeType().getNameOrOID(), ATTR_TYPE_DC.getNameOrOID());
+        assertEquals(it.next().getAttributeType().getNameOrOID(), ATTR_TYPE_CN.getNameOrOID());
     }
 
     /**
diff --git a/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/EntryGeneratorTestCase.java b/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/EntryGeneratorTestCase.java
index 60cb5ef..4ec433b 100644
--- a/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/EntryGeneratorTestCase.java
+++ b/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/EntryGeneratorTestCase.java
@@ -29,16 +29,18 @@
 
 import static org.fest.assertions.Assertions.*;
 import static org.forgerock.opendj.ldap.TestCaseUtils.getTestFilePath;
-import static org.forgerock.opendj.ldif.EntryGenerator.*;
+import static org.forgerock.opendj.ldap.schema.CoreSchema.*;
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.ldap.Attribute;
+import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.DecodeException;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.SdkTestCase;
@@ -47,10 +49,13 @@
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+import com.forgerock.opendj.util.StaticUtils;
+
 @SuppressWarnings("javadoc")
 public class EntryGeneratorTestCase extends SdkTestCase {
 
-    private static final String TEMPLATE_FILE_PATH = "org/forgerock/opendj/ldif/example.template";
+    private static final String BASIC_TEMPLATE_PATH = "org/forgerock/opendj/ldif/example.template";
+    private static final String SUBTEMPLATES_TEMPLATE_PATH = "org/forgerock/opendj/ldif/people_and_groups.template";
     private String resourcePath;
     private Schema schema;
 
@@ -58,40 +63,92 @@
     public void setUp() throws Exception {
         // path of directory in src/main/resources must be obtained from a file
         // otherwise it may search in the wrong directory
-        resourcePath = new File(getTestFilePath(TEMPLATE_FILE_PATH)).getParent();
+        resourcePath = new File(getTestFilePath(BASIC_TEMPLATE_PATH)).getParent();
         schema = Schema.getDefaultSchema();
     }
 
-    @Test
-    public void testReaderWithTemplateFile() throws Exception {
-        String templatePath = getTestFilePath(TEMPLATE_FILE_PATH);
-        EntryGenerator reader = newReader(templatePath).setResourcePath(resourcePath).build();
+    /**
+     * This test is a facility to print generated entries to stdout and should
+     * always be disabled.
+     * Turn it on locally if you need to see the output.
+     */
+    @Test(enabled = false)
+    public void printEntriesToStdOut() throws Exception {
+        String path = SUBTEMPLATES_TEMPLATE_PATH;
+        EntryGenerator generator = null;
+        try {
+            generator = new EntryGenerator(getTestFilePath(path)).setResourcePath(resourcePath);
+            while (generator.hasNext()) {
+                System.out.println(generator.readEntry());
+            }
+        } finally {
+            StaticUtils.closeSilently(generator);
+        }
 
-        checkReader(reader, 10000);
-        reader.close();
     }
 
     @Test
-    public void testReaderWithTemplateStream() throws Exception {
+    public void testCreateWithDefaultTemplateFile() throws Exception {
+        EntryGenerator generator = null;
+        try {
+            generator = new EntryGenerator();
+            assertThat(generator.hasNext()).isTrue();
+        } finally {
+            StaticUtils.closeSilently(generator);
+        }
+    }
+
+    @Test(expectedExceptions = DecodeException.class,
+            expectedExceptionsMessageRegExp = ".*Could not find template file unknown.*")
+    public void testCreateWithMissingTemplateFile() throws Exception {
+        EntryGenerator generator = null;
+        try {
+            generator = new EntryGenerator("unknown/path");
+            generator.hasNext();
+        } finally {
+            StaticUtils.closeSilently(generator);
+        }
+    }
+
+    @Test
+    public void testCreateWithSetConstants() throws Exception {
+        EntryGenerator generator = null;
+        try {
+            generator = new EntryGenerator().setConstant("numusers", 1);
+            generator.readEntry();
+            generator.readEntry();
+            assertThat(generator.readEntry().getName().toString()).isEqualTo("uid=user.0,ou=People,dc=example,dc=com");
+            assertThat(generator.hasNext()).as("should have no more entries").isFalse();
+        } finally {
+            StaticUtils.closeSilently(generator);
+        }
+    }
+
+    @DataProvider(name = "generators")
+    public Object[][] createGenerators() throws Exception {
+        Object[][] generators = new Object[3][2];
+
+        String templatePath = getTestFilePath(BASIC_TEMPLATE_PATH);
+        generators[0][0] = new EntryGenerator(templatePath).setResourcePath(resourcePath);
+        generators[0][1] = 10000;
+
         InputStream stream = new FileInputStream(
-                new File(getTestFilePath(TEMPLATE_FILE_PATH)));
-        EntryGenerator reader = newReader(stream).setResourcePath(resourcePath).build();
+                new File(templatePath));
+        generators[1][0] = new EntryGenerator(stream).setResourcePath(resourcePath);
+        generators[1][1] = 10000;
 
-        checkReader(reader, 10000);
-        reader.close();
-    }
-
-
-    @Test
-    public void testReaderWithTemplateLines() throws Exception {
-        EntryGenerator reader = newReader(
+        generators[2][0] = new EntryGenerator(
                 "define suffix=dc=example,dc=com",
                 "define maildomain=example.com",
                 "define numusers=2",
                 "",
                 "branch: [suffix]",
+                "objectClass: top",
+                "objectClass: domainComponent",
                 "",
                 "branch: ou=People,[suffix]",
+                "objectClass: top",
+                "objectClass: organizationalUnit",
                 "subordinateTemplate: person:[numusers]",
                 "",
                 "template: person",
@@ -118,41 +175,196 @@
                 "postalCode: <random:numeric:5>",
                 "postalAddress: {cn}${street}${l}, {st}  {postalCode}",
                 "description: This is the description for {cn}.")
-                .setResourcePath(resourcePath).build();
+                .setResourcePath(resourcePath);
+        generators[2][1] = 2;
 
-        checkReader(reader, 2);
-        reader.close();
+        return generators;
     }
 
     /**
-     * Check the content of the reader for basic case.
+     * Test the generated DNs.
      *
      * Expecting 2 entries and then numberOfUsers entries.
      */
-    private void checkReader(EntryGenerator reader, int numberOfUsers) throws Exception {
-        assertThat(reader.hasNext()).isTrue();
-        assertThat(reader.readEntry().getName().toString()).isEqualTo("dc=example,dc=com");
-        assertThat(reader.hasNext()).isTrue();
-        assertThat(reader.readEntry().getName().toString()).isEqualTo("ou=People,dc=example,dc=com");
-        for (int i = 0; i < numberOfUsers; i++) {
-            assertThat(reader.hasNext()).isTrue();
-            assertThat(reader.readEntry().getName().toString()).
-                isEqualTo("uid=user." + i + ",ou=People,dc=example,dc=com");
+    @Test(dataProvider = "generators")
+    public void testGeneratedDNs(EntryGenerator generator, int numberOfUsers) throws Exception {
+        try {
+            assertThat(generator.hasNext()).isTrue();
+            assertThat(generator.readEntry().getName().toString()).isEqualTo("dc=example,dc=com");
+            assertThat(generator.hasNext()).isTrue();
+            assertThat(generator.readEntry().getName().toString()).isEqualTo("ou=People,dc=example,dc=com");
+            for (int i = 0; i < numberOfUsers; i++) {
+                assertThat(generator.hasNext()).isTrue();
+                assertThat(generator.readEntry().getName().toString()).
+                    isEqualTo("uid=user." + i + ",ou=People,dc=example,dc=com");
+            }
+            assertThat(generator.hasNext()).as("should have no more entries").isFalse();
+        } finally {
+            StaticUtils.closeSilently(generator);
         }
-        assertThat(reader.hasNext()).as("should have no more entries").isFalse();
     }
 
-    @Test(expectedExceptions = IOException.class,
-            expectedExceptionsMessageRegExp = ".*Could not find template file unknown.*")
-    public void testMissingTemplateFile() throws Exception {
-        newReader("unknown").setResourcePath(resourcePath).build();
+    /**
+     * Test the complete content of top entry.
+     */
+    @Test(dataProvider = "generators")
+    public void testTopEntry(EntryGenerator generator, int numberOfUsers) throws Exception {
+        try {
+            Entry topEntry = generator.readEntry();
+            assertThat(topEntry.getName().toString()).isEqualTo("dc=example,dc=com");
+
+            Attribute dcAttribute = topEntry.getAttribute(getDCAttributeType().getNameOrOID());
+            assertThat(dcAttribute).isNotNull();
+            assertThat(dcAttribute.firstValueAsString()).isEqualTo("example");
+
+            checkEntryObjectClasses(topEntry, "top", "domainComponent");
+        } finally {
+            StaticUtils.closeSilently(generator);
+
+        }
     }
 
-    @Test(expectedExceptions = DecodeException.class,
-            expectedExceptionsMessageRegExp = ".*Cannot find file streets.*")
-    public void testMissingResourceFile() throws Exception {
-        // fail to find first resource file which is 'streets'
-        newReader(getTestFilePath(TEMPLATE_FILE_PATH)).setResourcePath("unknown").build();
+    /**
+     * Test the complete content of second entry.
+     */
+    @Test(dataProvider = "generators")
+    public void testSecondEntry(EntryGenerator generator, int numberOfUsers) throws Exception {
+        try {
+            generator.readEntry(); // skip top entry
+            Entry entry = generator.readEntry();
+            assertThat(entry.getName().toString()).isEqualTo("ou=People,dc=example,dc=com");
+
+            Attribute dcAttribute = entry.getAttribute(getOUAttributeType().getNameOrOID());
+            assertThat(dcAttribute).isNotNull();
+            assertThat(dcAttribute.firstValueAsString()).isEqualTo("People");
+
+            checkEntryObjectClasses(entry, "top", "organizationalUnit");
+        } finally {
+            StaticUtils.closeSilently(generator);
+        }
+    }
+
+    /**
+     * Test the complete content of first user entry.
+     */
+    @Test(dataProvider = "generators")
+    public void testFirstUserEntry(EntryGenerator generator, int numberOfUsers) throws Exception {
+        try {
+            generator.readEntry(); // skip top entry
+            generator.readEntry(); // skip ou entry
+            Entry entry = generator.readEntry();
+            assertThat(entry.getName().toString()).isEqualTo("uid=user.0,ou=People,dc=example,dc=com");
+
+            checkPresenceOfAttributes(entry, "givenName", "sn", "cn", "initials",
+                    "employeeNumber", "uid", "mail", "userPassword", "telephoneNumber",
+                    "homePhone", "pager", "mobile", "street", "l", "st", "postalCode",
+                    "postalAddress", "description");
+            assertThat(entry.getAttribute("cn").firstValueAsString()).isEqualTo(
+                    entry.getAttribute("givenName").firstValueAsString() + " "
+                    + entry.getAttribute("sn").firstValueAsString());
+
+            checkEntryObjectClasses(entry, "top", "person", "organizationalPerson", "inetOrgPerson");
+        } finally {
+            StaticUtils.closeSilently(generator);
+        }
+    }
+
+    private void checkEntryObjectClasses(Entry entry, String...objectClasses) {
+        Attribute ocAttribute = entry.getAttribute(getObjectClassAttributeType().getNameOrOID());
+        assertThat(ocAttribute).isNotNull();
+        Iterator<ByteString> it = ocAttribute.iterator();
+        for (int i = 0; i < objectClasses.length; i++) {
+            assertThat(it.next().toString()).isEqualTo(objectClasses[i]);
+        }
+        assertThat(it.hasNext()).isFalse();
+    }
+
+    private void checkPresenceOfAttributes(Entry entry, String... attributes) {
+        for (int i = 0; i < attributes.length; i++) {
+            assertThat(entry.getAttribute(attributes[i])).isNotNull();
+        }
+    }
+
+    /**
+     * Test a template with subtemplates, ensuring all expected DNs are generated.
+     */
+    @Test
+    public void testTemplateWithSubTemplates() throws Exception {
+        int numberOfUsers = 10;
+        int numberOfGroups = 5;
+        int numberOfOUs = 10;
+        EntryGenerator generator = new EntryGenerator(
+                "define suffix=dc=example,dc=com",
+                "define maildomain=example.com",
+                "define numusers=" + numberOfUsers,
+                "define numous=" + numberOfOUs,
+                "define numgroup=" + numberOfGroups,
+                "",
+                "branch: [suffix]",
+                "subordinateTemplate: ous:[numous]",
+                "",
+                "template: ous",
+                "subordinateTemplate: People:1",
+                "subordinateTemplate: Groups:1",
+                "rdnAttr: ou",
+                "objectclass: top",
+                "objectclass: organizationalUnit",
+                "ou: Organization_<sequential:1>",
+                "",
+                "template: People",
+                "rdnAttr: ou",
+                "subordinateTemplate: person:[numusers]",
+                "objectclass: top",
+                "objectclass: organizationalUnit",
+                "ou: People",
+                "",
+                "template: Groups",
+                "subordinateTemplate: groupOfName:[numgroup]",
+                "rdnAttr: ou",
+                "objectclass: top",
+                "objectclass: organizationalUnit",
+                "ou: Groups",
+                "",
+                "template: person",
+                "rdnAttr: uid",
+                "objectClass: top",
+                "objectClass: inetOrgPerson",
+                "cn: <first> <last>",
+                "employeeNumber: <sequential:0>",
+                "uid: user.{employeeNumber}",
+                "",
+                "template: groupOfName",
+                "rdnAttr: cn",
+                "objectClass: top",
+                "objectClass: groupOfNames",
+                "cn: Group_<sequential:1>"
+        ).setResourcePath(resourcePath);
+
+        try {
+            assertThat(generator.readEntry().getName().toString()).isEqualTo("dc=example,dc=com");
+            int countUsers = 0;
+            int countGroups = 1;
+            for (int i = 1; i <= numberOfOUs; i++) {
+                String dnOU = "ou=Organization_" + i + ",dc=example,dc=com";
+                assertThat(generator.readEntry().getName().toString()).isEqualTo(dnOU);
+                assertThat(generator.readEntry().getName().toString()).isEqualTo("ou=People," + dnOU);
+                for (int j = countUsers; j < countUsers + numberOfUsers; j++) {
+                    assertThat(generator.readEntry().getName().toString()).isEqualTo(
+                            "uid=user." + j + ",ou=People," + dnOU);
+
+                }
+                countUsers += numberOfUsers;
+                assertThat(generator.readEntry().getName().toString()).isEqualTo("ou=Groups," + dnOU);
+                for (int j = countGroups; j < countGroups + numberOfGroups; j++) {
+                    assertThat(generator.readEntry().getName().toString()).isEqualTo(
+                            "cn=Group_" + j + ",ou=Groups," + dnOU);
+                }
+                countGroups += numberOfGroups;
+            }
+            assertThat(generator.hasNext()).isFalse();
+        } finally {
+            StaticUtils.closeSilently(generator);
+        }
     }
 
     /**
@@ -160,7 +372,7 @@
      * generating templates reports the correct line.
      */
     @Test()
-    public void testParseFileTemplate() throws Exception {
+    public void testMissingVariableErrorReport() throws Exception {
         String[] lines = {
         /* 0 */"template: template",
         /* 1 */"a: {missingVar}",
@@ -172,7 +384,7 @@
         // Test must show "missingVar" missing on line 1.
         // Previous behaviour showed "missingVar" on line 5.
 
-        TemplateFile templateFile = new TemplateFile(schema, resourcePath);
+        TemplateFile templateFile = new TemplateFile(schema, null, resourcePath);
         List<LocalizableMessage> warns = new ArrayList<LocalizableMessage>();
 
         try {
@@ -218,7 +430,7 @@
      */
     @Test(dataProvider = "validTemplates")
     public void testParsingEscapeCharInTemplate(String testName, String[] lines) throws Exception {
-        TemplateFile templateFile = new TemplateFile(schema, resourcePath);
+        TemplateFile templateFile = new TemplateFile(schema, null, resourcePath);
         List<LocalizableMessage> warns = new ArrayList<LocalizableMessage>();
         templateFile.parse(lines, warns);
         assertThat(warns).isEmpty();
@@ -367,14 +579,18 @@
     @Test(dataProvider = "templatesToTestEscapeChars", dependsOnMethods = { "testParsingEscapeCharInTemplate" })
     public void testEscapeCharsFromTemplate(String testName, String[] lines, String attrName, String expectedValue)
             throws Exception {
-        EntryGenerator reader = newReader(lines).setResourcePath(resourcePath).build();
-        Entry topEntry = reader.readEntry();
-        Entry entry = reader.readEntry();
-        reader.close();
+        EntryGenerator generator = null;
+        try {
+            generator = new EntryGenerator(lines).setResourcePath(resourcePath);
+            Entry topEntry = generator.readEntry();
+            Entry entry = generator.readEntry();
 
-        assertThat(topEntry).isNotNull();
-        assertThat(entry).isNotNull();
-        assertThat(entry.getAttribute(attrName).firstValueAsString()).isEqualTo(expectedValue);
+            assertThat(topEntry).isNotNull();
+            assertThat(entry).isNotNull();
+            assertThat(entry.getAttribute(attrName).firstValueAsString()).isEqualTo(expectedValue);
+        } finally {
+            StaticUtils.closeSilently(generator);
+        }
     }
 
     /**
@@ -394,15 +610,19 @@
             // The value below combines variable, randoms and escaped chars.
             // The resulting value is "Foo <?>{1}Bar" where ? is a letter
             // from [A-Z].
-            "cn: Foo \\<<random:chars:ABCDEFGHIJKLMNOPQRSTUVWXYZ:1>\\>\\{1\\}{sn}", "", };
+            "cn: Foo \\<<random:chars:ABCDEFGHIJKLMNOPQRSTUVWXYZ:1>\\>\\{1\\}{sn}",
+            "" };
+        EntryGenerator generator = null;
+        try {
+            generator = new EntryGenerator(lines).setResourcePath(resourcePath);
+            Entry topEntry = generator.readEntry();
+            Entry entry = generator.readEntry();
 
-        EntryGenerator reader = newReader(lines).setResourcePath(resourcePath).build();
-        Entry topEntry = reader.readEntry();
-        Entry entry = reader.readEntry();
-        reader.close();
-
-        assertThat(topEntry).isNotNull();
-        assertThat(entry).isNotNull();
-        assertThat(entry.getAttribute("cn").firstValueAsString()).matches("Foo <[A-Z]>\\{1\\}Bar");
+            assertThat(topEntry).isNotNull();
+            assertThat(entry).isNotNull();
+            assertThat(entry.getAttribute("cn").firstValueAsString()).matches("Foo <[A-Z]>\\{1\\}Bar");
+        } finally {
+            StaticUtils.closeSilently(generator);
+        }
     }
 }
diff --git a/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/TemplateTagTestcase.java b/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/TemplateTagTestcase.java
new file mode 100644
index 0000000..d838a2d
--- /dev/null
+++ b/opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/TemplateTagTestcase.java
@@ -0,0 +1,293 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * 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
+ *
+ *
+ *      Copyright 2013 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldif;
+
+import static org.fest.assertions.Assertions.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.SdkTestCase;
+import org.forgerock.opendj.ldap.schema.AttributeType;
+import org.forgerock.opendj.ldap.schema.Schema;
+import org.forgerock.opendj.ldif.TemplateFile.Template;
+import org.forgerock.opendj.ldif.TemplateFile.TemplateEntry;
+import org.forgerock.opendj.ldif.TemplateFile.TemplateLine;
+import org.forgerock.opendj.ldif.TemplateFile.TemplateValue;
+import org.forgerock.opendj.ldif.TemplateTag.AttributeValueTag;
+import org.forgerock.opendj.ldif.TemplateTag.IfAbsentTag;
+import org.forgerock.opendj.ldif.TemplateTag.IfPresentTag;
+import org.forgerock.opendj.ldif.TemplateTag.TagResult;
+import org.testng.annotations.Test;
+
+@SuppressWarnings("javadoc")
+public class TemplateTagTestcase extends SdkTestCase {
+
+    private static final int LINE_NUMBER = 10;
+    private static final TemplateFile NULL_TEMPLATE_FILE = null;
+    private static final List<LocalizableMessage> NULL_WARNINGS = null;
+    private static final TemplateValue NULL_VALUE = null;
+    private static final TemplateLine NULL_LINE = null;
+
+    @Test
+    public void testIfAbsentTag() throws Exception {
+        TemplateTag tag = new IfAbsentTag();
+        String org = "org";
+        tagWithArguments(tag, "dc", org); // dc=org should be absent
+
+        // org value is absent
+        assertThat(tag.generateValue(templateEntry("v1", "v2"), NULL_VALUE)).isEqualTo(TagResult.SUCCESS);
+
+        // org value is present
+        assertThat(tag.generateValue(templateEntry(org, "v"), NULL_VALUE)).isEqualTo(TagResult.FAILURE);
+        assertThat(tag.generateValue(templateEntry("v", org), NULL_VALUE)).isEqualTo(TagResult.FAILURE);
+        assertThat(tag.generateValue(templateEntry(org), NULL_VALUE)).isEqualTo(TagResult.FAILURE);
+    }
+
+    @Test
+    public void testIfAbsentTagWithNoValue() throws Exception {
+        TemplateTag tag = new IfAbsentTag();
+        tagWithArguments(tag, "dc"); // dc should be absent
+
+        assertThat(tag.generateValue(templateEntry("v"), NULL_VALUE)).isEqualTo(TagResult.FAILURE);
+    }
+
+    @Test(expectedExceptions = DecodeException.class,
+            expectedExceptionsMessageRegExp = ".*Invalid number of arguments.*")
+    public void testIfAbsentTagTooManyArguments() throws Exception {
+        tagWithArguments(new IfAbsentTag(), "dc", "org1", "org2"); // too many args
+    }
+
+    @Test(expectedExceptions = DecodeException.class,
+            expectedExceptionsMessageRegExp = ".*Invalid number of arguments.*")
+    public void testIfAbsentTagNotEnoughArguments() throws Exception {
+        tagWithArguments(new IfAbsentTag()); // zero args
+    }
+
+    @Test(expectedExceptions = DecodeException.class,
+            expectedExceptionsMessageRegExp = ".*Undefined attribute.*")
+    public void testIfAbsentTagNoAttribute() throws Exception {
+        tagWithArguments(new IfAbsentTag(), templateWithNoAttribute(), "dc");
+    }
+
+    @Test
+    public void testDNTagRootDN() throws Exception {
+        TemplateTag tag = new TemplateTag.DNTag();
+        tagWithArguments(tag);
+
+        TemplateValue value = new TemplateValue(NULL_LINE);
+        tag.generateValue(templateEntry(DN.rootDN()), value);
+
+        assertThat(value.getValueAsString()).isEqualTo("");
+    }
+
+    @Test
+    public void testDNTagZeroComponent() throws Exception {
+        TemplateTag tag = new TemplateTag.DNTag();
+        tagWithArguments(tag);
+
+        TemplateValue value = new TemplateValue(NULL_LINE);
+        tag.generateValue(templateEntry(DN.valueOf("ou=users,dc=example,dc=test")), value);
+
+        assertThat(value.getValueAsString()).isEqualTo("ou=users,dc=example,dc=test");
+    }
+
+    @Test
+    public void testUnderscoreDNTagZeroComponent() throws Exception {
+        TemplateTag tag = new TemplateTag.UnderscoreDNTag();
+        tagWithArguments(tag);
+
+        TemplateValue value = new TemplateValue(NULL_LINE);
+        tag.generateValue(templateEntry(DN.valueOf("ou=users,dc=example,dc=test")), value);
+
+        assertThat(value.getValueAsString()).isEqualTo("ou=users_dc=example_dc=test");
+    }
+
+    @Test
+    public void testDNTagOneComponent() throws Exception {
+        TemplateTag tag = new TemplateTag.DNTag();
+        tagWithArguments(tag, "1");
+
+        TemplateValue value = new TemplateValue(NULL_LINE);
+        tag.generateValue(templateEntry(DN.valueOf("ou=users,dc=example,dc=test")), value);
+
+        assertThat(value.getValueAsString()).isEqualTo("ou=users");
+    }
+
+    @Test
+    public void testDNTagTwoComponent() throws Exception {
+        TemplateTag tag = new TemplateTag.DNTag();
+        tagWithArguments(tag, "2");
+
+        TemplateValue value = new TemplateValue(NULL_LINE);
+        tag.generateValue(templateEntry(DN.valueOf("ou=users,dc=example,dc=test")), value);
+
+        assertThat(value.getValueAsString()).isEqualTo("ou=users,dc=example");
+    }
+
+    @Test
+    public void testDNTagMinusOneComponent() throws Exception {
+        TemplateTag tag = new TemplateTag.DNTag();
+        tagWithArguments(tag, "-1");
+
+        TemplateValue value = new TemplateValue(NULL_LINE);
+        tag.generateValue(templateEntry(DN.valueOf("ou=users,dc=example,dc=test")), value);
+
+        assertThat(value.getValueAsString()).isEqualTo("dc=test");
+    }
+
+    @Test
+    public void testDNTagMinusTwoComponents() throws Exception {
+        TemplateTag tag = new TemplateTag.DNTag();
+        tagWithArguments(tag, "-2");
+
+        TemplateValue value = new TemplateValue(NULL_LINE);
+        tag.generateValue(templateEntry(DN.valueOf("ou=users,dc=example,dc=test")), value);
+
+        assertThat(value.getValueAsString()).isEqualTo("dc=example,dc=test");
+    }
+
+    @Test
+    public void testIfPresentTag() throws Exception {
+        TemplateTag tag = new TemplateTag.IfPresentTag();
+        String org = "org";
+        tagWithArguments(tag, "dc", org); // dc=org should be present
+
+        // org value is absent
+        assertThat(tag.generateValue(templateEntry("v1", "v2"), NULL_VALUE)).isEqualTo(TagResult.FAILURE);
+
+        // org value is present
+        assertThat(tag.generateValue(templateEntry(org, "v"), NULL_VALUE)).isEqualTo(TagResult.SUCCESS);
+        assertThat(tag.generateValue(templateEntry("v", org), NULL_VALUE)).isEqualTo(TagResult.SUCCESS);
+        assertThat(tag.generateValue(templateEntry(org), NULL_VALUE)).isEqualTo(TagResult.SUCCESS);
+    }
+
+    @Test
+    public void testIfPresentTagWithNoValue() throws Exception {
+        TemplateTag tag = new TemplateTag.IfPresentTag();
+        tagWithArguments(tag, "dc"); // dc=org should be present
+
+        assertThat(tag.generateValue(templateEntry("org"), NULL_VALUE)).isEqualTo(TagResult.SUCCESS);
+    }
+
+    @Test(expectedExceptions = DecodeException.class,
+            expectedExceptionsMessageRegExp = ".*Invalid number of arguments.*")
+    public void testIfPresentTagTooManyArguments() throws Exception {
+        tagWithArguments(new IfPresentTag(), "1", "2", "3"); // too many args
+    }
+
+    @Test(expectedExceptions = DecodeException.class,
+            expectedExceptionsMessageRegExp = ".*Invalid number of arguments.*")
+    public void testIfPresentTagNotEnoughArguments() throws Exception {
+        tagWithArguments(new IfPresentTag()); // zero args
+    }
+
+    @Test(expectedExceptions = DecodeException.class,
+            expectedExceptionsMessageRegExp = ".*Undefined attribute.*")
+    public void testIfPresentTagNoAttribute() throws Exception {
+        tagWithArguments(new IfPresentTag(), templateWithNoAttribute(), "dc");
+    }
+
+    @Test(expectedExceptions = DecodeException.class,
+            expectedExceptionsMessageRegExp = ".*Invalid number of arguments.*")
+    public void testAttributeValueTagTooManyArguments() throws Exception {
+        tagWithArguments(new AttributeValueTag(), "dc", "2", "3"); // too many args
+    }
+
+    @Test(expectedExceptions = DecodeException.class,
+            expectedExceptionsMessageRegExp = ".*below the lowest allowed value.*")
+    public void testAttributeValueTagBelowLowerBound() throws Exception {
+        tagWithArguments(new AttributeValueTag(), "dc", "-1");
+    }
+
+    @Test(expectedExceptions = DecodeException.class,
+            expectedExceptionsMessageRegExp = ".*Cannot parse value.*")
+    public void testAttributeValueTagNotANumber() throws Exception {
+        tagWithArguments(new AttributeValueTag(), "dc", "notanumber");
+    }
+
+    @Test(expectedExceptions = DecodeException.class,
+            expectedExceptionsMessageRegExp = ".*Undefined attribute.*")
+    public void testAttributeValueTagNoAttribute() throws Exception {
+        tagWithArguments(new AttributeValueTag(), templateWithNoAttribute(), "dc");
+    }
+
+    /** Helper method to initialize tags with template having any attribute and some arguments*/
+    private void tagWithArguments(TemplateTag tag, String... arguments) throws DecodeException {
+        tagWithArguments(tag, templateWithAnyAttribute(), arguments);
+    }
+
+    /** Helper method to initialize tags with template and some arguments*/
+    private void tagWithArguments(TemplateTag tag, Template template, String... arguments)
+            throws DecodeException {
+        tag.initializeForTemplate(Schema.getDefaultSchema(), NULL_TEMPLATE_FILE, template,
+                arguments, LINE_NUMBER, NULL_WARNINGS);
+    }
+
+    /** Helper method to build a template entry containing the provided values. */
+    private TemplateEntry templateEntry(String... values) {
+        TemplateEntry templateEntry = mock(TemplateEntry.class);
+        List<TemplateValue> templateValues = new ArrayList<TemplateValue>();
+        for (String value : values) {
+            templateValues.add(templateValue(value));
+        }
+        when(templateEntry.getValues(any(AttributeType.class))).thenReturn(templateValues);
+        return templateEntry;
+    }
+
+    /** Helper method to build a template entry with the provided DN. */
+    private TemplateEntry templateEntry(DN dn) {
+        TemplateEntry templateEntry = mock(TemplateEntry.class);
+        when(templateEntry.getDN()).thenReturn(dn);
+        return templateEntry;
+    }
+
+    /** Helper method to build a template value from provided string. */
+    private TemplateValue templateValue(String value) {
+        TemplateValue templateVal = new TemplateFile.TemplateValue(null);
+        templateVal.append(value);
+        return templateVal;
+    }
+
+    /** Helper method to build a template that always return true on attribute type check */
+    private Template templateWithAnyAttribute() {
+        Template template = mock(Template.class);
+        when(template.hasAttribute(any(AttributeType.class))).thenReturn(true);
+        return template;
+    }
+
+    /** Helper method to build a template that always return false on attribute type check */
+    private Template templateWithNoAttribute() {
+        Template template = mock(Template.class);
+        when(template.hasAttribute(any(AttributeType.class))).thenReturn(false);
+        return template;
+    }
+}

--
Gitblit v1.10.0