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

Nicolas Capponi
19.31.2013 869998098af41978080fadabbfab637030ec7aa3
Checkpoint commit for OPENDJ-1075 Port server make-ldif tool to the SDK
CR-2586

Refactor and clean-up of ported code.

* Reduce visibility of classes and methods to private or package
private
* Clean up of javadocs, remove useless javadocs
* Refactoring to reduce duplication and simplify code
* EntryGenerator class
- Support to override of constants with setConstant() method
- Remove internal Builder
- Remove the generation thread
* Add more defaults, including default template file and default
resource path
* Add tests in EntryGeneratorTestCase
* New test case TemplateTagTestCase
* Add possibility to build RDN object from a list of AVAs
* Port of Pair class from opendj server
* New template people_and_groups.template


3 files added
8 files modified
6203 ■■■■■ changed files
opendj3/opendj-core/src/main/java/com/forgerock/opendj/util/Pair.java 152 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java 27 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/EntryGenerator.java 450 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateFile.java 2405 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateTag.java 2345 ●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties 41 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/main/resources/org/forgerock/opendj/ldif/example.template 4 ●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template 62 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldap/RDNTestCase.java 78 ●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/EntryGeneratorTestCase.java 346 ●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/TemplateTagTestcase.java 293 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/main/java/com/forgerock/opendj/util/Pair.java
New file
@@ -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 + "]";
    }
}
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;
    }
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);
        }
    }
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));
        }
    }
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;
        }
    }
}
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 \
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
opendj3/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template
New file
@@ -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>
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());
    }
    /**
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);
        }
    }
}
opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/TemplateTagTestcase.java
New file
@@ -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;
    }
}