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

Nicolas Capponi
05.18.2013 5730a8c9ba7f5fe695db5f1c1bb229ad91c8fb46
Checkpoint commit for OPENDJ-1075 Port server make-ldif tool to the SDK
CR-2539

Initial port of entries generation from opendj server (opendj/trunk/opends/src/server/org/opends/server/tools/makeldif package)

* Behavior related to CLI is not adressed
* Lots of issues identified in the code review will be adressed in next commits

* Class organization
** MakeLDIFEntryReader class is the main entry point to generate entries
** TemplateFile class contains all classes related to template parsing and processing excluding tags
** TemplateTag class contains all tag-related classes
** MakeLDIFEntryReaderTestCase include initial tests from MakeLDIF and some additional tests
11 files added
2 files modified
28929 ■■■■■ changed files
opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/MakeLDIFEntryReader.java 463 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/MakeLDIFException.java 73 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateFile.java 2541 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateTag.java 2873 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties 143 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldap/TestCaseUtils.java 19 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/MakeLDIFEntryReaderTestCase.java 402 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/test/resources/MakeLDIF/cities 232 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/test/resources/MakeLDIF/example.template 34 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/test/resources/MakeLDIF/first.names 8605 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/test/resources/MakeLDIF/last.names 13419 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/test/resources/MakeLDIF/states 51 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/test/resources/MakeLDIF/streets 74 ●●●●● patch | view | raw | blame | history
opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/MakeLDIFEntryReader.java
New file
@@ -0,0 +1,463 @@
/*
 * 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 2006-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2013 ForgeRock AS
 */
package org.forgerock.opendj.ldif;
import static com.forgerock.opendj.ldap.CoreMessages.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
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.Entry;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.SchemaValidationPolicy;
import org.forgerock.opendj.ldif.TemplateFile.EntryWriter;
import org.forgerock.opendj.ldif.TemplateFile.TemplateEntry;
import com.forgerock.opendj.util.Validator;
/**
 * This reader generates entries from a template file, which can be provided
 * as a file, a list of lines, an array of lines, or an input stream.
 */
public final class MakeLDIFEntryReader implements EntryReader {
    private static final TemplateEntry POISON_ENTRY = TemplateFile.TemplateEntry.NULL_TEMPLATE_ENTRY;
    /** Template file that contains directives for generation of entries. */
    private final TemplateFile templateFile;
    /** Queue used to hold generated entries until they can be read. */
    private final BlockingQueue<TemplateEntry> entryQueue;
    /** The next available entry. */
    private TemplateEntry nextEntry;
    private final List<LocalizableMessage> warnings;
    private volatile IOException ioException;
    private volatile boolean generationIsFinished = false;
    private volatile boolean isClosed = false;
    /** Thread that provides generation of entries. */
    private Thread generationThread;
    /**
     * 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
     */
    private MakeLDIFEntryReader(TemplateFile templateFile, LinkedList<LocalizableMessage> warnings,
            BlockingQueue<TemplateEntry> entryQueue) {
        this.templateFile = templateFile;
        this.warnings = warnings;
        this.entryQueue = entryQueue;
    }
    /**
     * Returns a builder to create a reader based on a template file given by
     * the provided path.
     *
     * @param templatePath
     *            path of the template file
     * @return a builder allowing to create the reader
     */
    public static Builder newReader(final String templatePath) {
        return new Builder(templatePath);
    }
    /**
     * Returns a builder to create a reader based on a template file given by
     * the provided lines.
     *
     * @param templateLines
     *            lines defining the template file
     * @return a builder allowing to create the reader
     */
    public static Builder newReader(final String... templateLines) {
        return new Builder(templateLines);
    }
    /**
     * Returns a builder to create a reader based on a template file given by
     * the provided lines.
     *
     * @param templateLines
     *            lines defining the template file
     * @return a builder allowing to create the reader
     */
    public static Builder newReader(final List<String> templateLines) {
        return new Builder(templateLines.toArray(new String[templateLines.size()]));
    }
    /**
     * Returns a builder to create a reader based on a template file given by
     * the provided stream.
     *
     * @param templateStream
     *            input stream to read the template file
     * @return a builder allowing to create the reader
     */
    public static Builder newReader(final InputStream templateStream) {
        return new Builder(templateStream);
    }
    /**
     * Builder of {@code MakeLDIFEntryReader readers}.
     * <p>
     *
     * To build a reader with all default values:
     * <pre>
     * {@code reader = MakeLDIFEntryReader.newReader(...).build() }
     * </pre>
     * <p>
     *
     * To build a reader with some custom values, using the
     * <code>set</code> methods:
     * <pre>
     * {@code reader = MakeLDIFEntryReader.newReader(...).
     *    setResourcePath(path).
     *    setSchema(schema).
     *    build() }
     * </pre>
     */
    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 SchemaValidationPolicy schemaValidationPolicy;
        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 MakeLDIFEntryReader.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 MakeLDIFEntryReader.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 MakeLDIFEntryReader.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 MakeLDIFEntryReader.Builder}.
         */
        public Builder setSchema(final Schema schema) {
            this.schema = schema;
            return this;
        }
        /**
         * Specifies the schema validation which should be used when generating
         * entries. If attribute value validation is enabled then all checks
         * will be performed.
         * <p>
         * Schema validation is disabled by default.
         * <p>
         * <b>NOTE:</b> this method copies the provided policy so changes made
         * to it after this method has been called will have no effect.
         *
         * @param policy
         *            The schema validation which should be used when generating
         *            entries.
         * @return A reference to this {@code MakeLDIFEntryReader.Builder}.
         */
        public Builder setSchemaValidationPolicy(final SchemaValidationPolicy policy) {
            this.schemaValidationPolicy = SchemaValidationPolicy.copyOf(policy);
            return this;
        }
        /**
         * Return an instance of reader.
         *
         * @return a new instance of reader
         * @throws MakeLDIFException
         *             if a problem occurs during initialization of reader.
         */
        public MakeLDIFEntryReader build() throws MakeLDIFException {
            if (schema == null) {
                schema = Schema.getDefaultSchema();
            }
            if (schemaValidationPolicy != null) {
                schema = schemaValidationPolicy.checkAttributesAndObjectClasses().needsChecking() ? schema
                        .asStrictSchema() : schema.asNonStrictSchema();
            }
            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 new MakeLDIFException(ERR_MAKELDIF_MISSING_TEMPLATE_FILE.get());
                }
            } catch (IOException e) {
                throw new MakeLDIFException(ERR_MAKELDIF_IOEXCEPTION_DURING_PARSE.get(e.getMessage()), e);
            } catch (Exception e) {
                throw new MakeLDIFException(ERR_MAKELDIF_EXCEPTION_DURING_PARSE.get(e.getMessage()), e);
            }
            MakeLDIFEntryReader reader = new MakeLDIFEntryReader(templateFile,
                    warnings, new LinkedBlockingQueue<TemplateEntry>(maxNumberOfEntriesInQueue));
            reader.startEntriesGeneration();
            return reader;
        }
    }
    /**
     * Start generation of entries by launching a separate thread.
     */
    private void startEntriesGeneration() {
        generationThread =
                new Thread(new EntriesGenerator(new MakeEntryWriter(), templateFile), "MakeLDIF Generator Thread");
        generationThread.start();
    }
    /**
     * Checks if there are some warning(s) after the parsing of template file.
     *
     * @return true if there is at least one warning
     */
    public boolean hasWarning() {
        return !warnings.isEmpty();
    }
    /**
     * Returns the warnings generated by the parsing of template file.
     *
     * @return the list of warnings, which is empty if there is no warning
     */
    public List<LocalizableMessage> getWarnings() {
        return Collections.unmodifiableList(warnings);
    }
    @Override
    public void close() throws IOException {
        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;
    }
    @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;
        }
    }
    /**
     * Entry writer that store entries into the entry queue of this reader, and
     * record close and exception events.
     */
    private final class MakeEntryWriter implements EntryWriter {
        @Override
        public boolean writeEntry(final TemplateEntry entry) throws IOException, MakeLDIFException {
            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;
            try {
                writeEntry(POISON_ENTRY);
            } catch (IOException e) {
                // should never happen
            } catch (MakeLDIFException e) {
                // should never happen
            }
        }
        public void setIOException(final IOException ioe) {
            ioException = ioe;
        }
    }
    /**
     * Generator of entries, that writes entries to a provided
     * {@code EntryWriter writer}.
     */
    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;
        }
        /**
         * Run the generation of entries.
         */
        public void run() {
            generate();
        }
        /**
         * Generates entries to the entry writer.
         */
        void generate() {
            try {
                templateFile.generateEntries(entryWriter);
            } catch (MakeLDIFException e) {
                entryWriter.setIOException(new IOException(e));
                entryWriter.closeEntryWriter();
            } catch (IOException e) {
                entryWriter.setIOException(e);
                entryWriter.closeEntryWriter();
            }
        }
    }
}
opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/MakeLDIFException.java
New file
@@ -0,0 +1,73 @@
/*
 * 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 2006-2008 Sun Microsystems, Inc.
 *      Portions Copyright 2013 ForgeRock AS
 */
package org.forgerock.opendj.ldif;
import org.forgerock.i18n.LocalizableException;
import org.forgerock.i18n.LocalizableMessage;
/**
 * Exception that can be thrown if a problem occurs during MakeLDIF processing.
 */
@SuppressWarnings("serial")
public class MakeLDIFException extends Exception implements LocalizableException {
    /** The I18N message associated with this exception. */
    private final LocalizableMessage message;
    /**
     * Creates a new MakeLDIF exception with the provided information.
     *
     * @param message
     *            The message for this exception.
     */
    public MakeLDIFException(final LocalizableMessage message) {
        super(String.valueOf(message));
        this.message = message;
    }
    /**
     * Creates a new MakeLDIF exception with the provided information.
     *
     * @param message
     *            The message for this exception.
     * @param cause
     *            The underlying cause for this exception.
     */
    public MakeLDIFException(final LocalizableMessage message, Throwable cause) {
        super(String.valueOf(message), cause);
        this.message = message;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public LocalizableMessage getMessageObject() {
        return this.message;
    }
}
opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateFile.java
New file
@@ -0,0 +1,2541 @@
/*
 * 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 2006-2009 Sun Microsystems, Inc.
 *      Portions Copyright 2013 ForgeRock AS
 */
package org.forgerock.opendj.ldif;
import static com.forgerock.opendj.ldap.CoreMessages.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
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.DN;
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;
import org.forgerock.opendj.ldif.TemplateTag.FileTag;
import org.forgerock.opendj.ldif.TemplateTag.FirstNameTag;
import org.forgerock.opendj.ldif.TemplateTag.GUIDTag;
import org.forgerock.opendj.ldif.TemplateTag.IfAbsentTag;
import org.forgerock.opendj.ldif.TemplateTag.IfPresentTag;
import org.forgerock.opendj.ldif.TemplateTag.LastNameTag;
import org.forgerock.opendj.ldif.TemplateTag.ListTag;
import org.forgerock.opendj.ldif.TemplateTag.ParentDNTag;
import org.forgerock.opendj.ldif.TemplateTag.PresenceTag;
import org.forgerock.opendj.ldif.TemplateTag.RDNTag;
import org.forgerock.opendj.ldif.TemplateTag.RandomTag;
import org.forgerock.opendj.ldif.TemplateTag.SequentialTag;
import org.forgerock.opendj.ldif.TemplateTag.StaticTextTag;
import org.forgerock.opendj.ldif.TemplateTag.TagResult;
import org.forgerock.opendj.ldif.TemplateTag.UnderscoreDNTag;
import org.forgerock.opendj.ldif.TemplateTag.UnderscoreParentDNTag;
import com.forgerock.opendj.util.StaticUtils;
/**
 * A template file allow to generate entries from a collection of constant
 * definitions, branches, and templates.
 *
 * @see MakeLDIFEntryReader
 */
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";
    /**
     * 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;
    /** The index of the next first name value that should be used. */
    private int firstNameIndex;
    /** The index of the next last name value that should be used. */
    private int lastNameIndex;
    /**
     * A counter used to keep track of the number of times that the larger of
     * the first/last name list has been completed.
     */
    private int nameLoopCounter;
    /**
     * A counter that will be used in case we have exhausted all possible first
     * and last name combinations.
     */
    private int nameUniquenessCounter;
    /** The set of branch definitions for this template file. */
    private Map<DN, Branch> branches;
    /** The set of constant definitions for this template file. */
    private Map<String, String> constants;
    /** The set of registered tags for this template file. */
    private Map<String, TemplateTag> registeredTags;
    /** The set of template definitions for this template file. */
    private Map<String, Template> templates;
    /** The random number generator for this template file. */
    private Random random;
    /** The next first name that should be used. */
    private String firstName;
    /** The next last name that should be used. */
    private String lastName;
    /**
     * The resource path to use for filesystem elements that cannot be found
     * anywhere else.
     */
    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;
    /** The set of last names to use when generating the LDIF. */
    private String[] lastNames;
    /** Schema used to create attributes. */
    private final Schema schema;
    /**
     * Creates a new, empty template file structure.
     *
     * @param schema
     *            LDAP Schema to use
     * @param resourcePath
     *            The path to the directory that may contain additional resource
     *            files needed during the generation process.
     */
    public TemplateFile(Schema schema, String resourcePath) {
        this(schema, resourcePath, new Random());
    }
    /**
     * Creates a new, empty template file structure.
     *
     * @param schema
     *            used to create attributes
     * @param resourcePath
     *            The path to the directory that may contain additional resource
     *            files needed during the generation process.
     * @param random
     *            The random number generator for this template file.
     */
    public TemplateFile(Schema schema, String resourcePath, Random random) {
        this.schema = schema;
        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();
    }
    /**
     * 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) {
        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 MakeLDIFException
     *             If a problem occurs while attempting to register the
     *             specified tag.
     */
    public void registerTag(String tagClass) throws MakeLDIFException {
        Class<?> c;
        try {
            c = Class.forName(tagClass);
        } catch (Exception e) {
            final LocalizableMessage message = ERR_MAKELDIF_CANNOT_LOAD_TAG_CLASS.get(tagClass);
            throw new MakeLDIFException(message, e);
        }
        TemplateTag t;
        try {
            t = (TemplateTag) c.newInstance();
        } catch (Exception e) {
            final LocalizableMessage message = ERR_MAKELDIF_CANNOT_INSTANTIATE_TAG.get(tagClass);
            throw new MakeLDIFException(message, e);
        }
        String lowerName = t.getName().toLowerCase();
        if (registeredTags.containsKey(lowerName)) {
            final LocalizableMessage message = ERR_MAKELDIF_CONFLICTING_TAG_NAME.get(tagClass, t.getName());
            throw new MakeLDIFException(message);
        } else {
            registeredTags.put(lowerName, t);
        }
    }
    /**
     * Registers the set of tags that will always be available for use in
     * templates.
     */
    private void registerDefaultTags() {
        Class<?>[] defaultTagClasses = new Class<?>[] { AttributeValueTag.class, DNTag.class, FileTag.class,
            FirstNameTag.class, GUIDTag.class, IfAbsentTag.class, IfPresentTag.class, LastNameTag.class,
            ListTag.class, ParentDNTag.class, PresenceTag.class, RandomTag.class, RDNTag.class,
            SequentialTag.class, StaticTextTag.class, UnderscoreDNTag.class, UnderscoreParentDNTag.class };
        for (Class<?> c : defaultTagClasses) {
            try {
                TemplateTag t = (TemplateTag) c.newInstance();
                registeredTags.put(t.getName().toLowerCase(), t);
            } catch (Exception e) {
                // this should never happen
                StaticUtils.DEFAULT_LOG.error(ERR_MAKELDIF_CANNOT_INSTANTIATE_TAG.get(c.getName()).toString());
            }
        }
    }
    /**
     * 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() {
        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;
        try {
            reader = new BufferedReader(new FileReader(file));
            while (true) {
                String line = reader.readLine();
                if (line == null) {
                    break;
                } else {
                    data.add(line);
                }
            }
        } finally {
            if (reader != null) {
                reader.close();
            }
        }
        return data;
    }
    /**
     * Updates the first and last name indexes to choose new values. The
     * algorithm used is designed to ensure that the combination of first and
     * last names will never be repeated. It depends on the number of first
     * 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() {
        firstName = firstNames[firstNameIndex++];
        lastName = lastNames[lastNameIndex++];
        // 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.
            firstNameIndex = 0;
            if (firstNames.length > lastNames.length) {
                lastNameIndex = ++nameLoopCounter;
                if (lastNameIndex >= lastNames.length) {
                    lastNameIndex = 0;
                    nameUniquenessCounter++;
                }
            }
        }
        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.
            lastNameIndex = 0;
            if (lastNames.length > firstNames.length) {
                firstNameIndex = ++nameLoopCounter;
                if (firstNameIndex >= firstNames.length) {
                    firstNameIndex = 0;
                    nameUniquenessCounter++;
                }
            }
        }
    }
    /**
     * 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() {
        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() {
        return lastName;
    }
    /**
     * Parses the contents of the specified file as a MakeLDIF template file
     * definition.
     *
     * @param filename
     *            The name of the file containing the template data.
     * @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
     *             specified file.
     * @throws MakeLDIFException
     *             If any other problem occurs while parsing the template file.
     */
    public void parse(String filename, List<LocalizableMessage> warnings) throws IOException, MakeLDIFException {
        ArrayList<String> fileLines = new ArrayList<String>();
        templatePath = null;
        File f = getFile(filename);
        if ((f == null) || (!f.exists())) {
            LocalizableMessage message = ERR_MAKELDIF_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);
            }
        }
        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
     * template file definition.
     *
     * @param inputStream
     *            The input stream from which to read the template file data.
     * @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
     *             provided input stream.
     * @throws MakeLDIFException
     *             If any other problem occurs while parsing the template.
     */
    public void parse(InputStream inputStream, List<LocalizableMessage> warnings)
            throws IOException, MakeLDIFException {
        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);
            }
        }
        reader.close();
        String[] lines = new String[fileLines.size()];
        fileLines.toArray(lines);
        parse(lines, warnings);
    }
    /**
     * Parses the provided data as a MakeLDIF template file definition.
     *
     * @param lines
     *            The lines that make up the template file.
     * @param warnings
     *            A list into which any warnings identified may be placed.
     * @throws MakeLDIFException
     *             If any other problem occurs while parsing the template lines.
     */
    public void parse(String[] lines, List<LocalizableMessage> warnings) throws MakeLDIFException {
        // 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>();
        for (int lineNumber = 0; lineNumber < lines.length; lineNumber++) {
            String line = lines[lineNumber];
            line = replaceConstants(line, lineNumber, templateFileConstants, warnings);
            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_MAKELDIF_CANNOT_LOAD_TAG_CLASS.get(className);
                    throw new MakeLDIFException(message, e);
                }
                TemplateTag tag;
                try {
                    tag = (TemplateTag) tagClass.newInstance();
                } catch (Exception e) {
                    LocalizableMessage message = ERR_MAKELDIF_CANNOT_INSTANTIATE_TAG.get(className);
                    throw new MakeLDIFException(message, e);
                }
                String lowerName = tag.getName().toLowerCase();
                if (registeredTags.containsKey(lowerName) || templateFileIncludeTags.containsKey(lowerName)) {
                    LocalizableMessage message = ERR_MAKELDIF_CONFLICTING_TAG_NAME.get(className, tag.getName());
                    throw new MakeLDIFException(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_MAKELDIF_DEFINE_MISSING_EQUALS.get(lineNumber);
                    throw new MakeLDIFException(message);
                }
                String name = line.substring(7, equalPos).trim();
                if (name.length() == 0) {
                    LocalizableMessage message = ERR_MAKELDIF_DEFINE_NAME_EMPTY.get(lineNumber);
                    throw new MakeLDIFException(message);
                }
                String lowerName = name.toLowerCase();
                if (templateFileConstants.containsKey(lowerName)) {
                    LocalizableMessage message = ERR_MAKELDIF_CONFLICTING_CONSTANT_NAME.get(name, lineNumber);
                    throw new MakeLDIFException(message);
                }
                String value = line.substring(equalPos + 1);
                if (value.length() == 0) {
                    LocalizableMessage message = ERR_MAKELDIF_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_MAKELDIF_CONFLICTING_BRANCH_DN.get(String.valueOf(branchDN),
                            startLineNumber);
                    throw new MakeLDIFException(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_MAKELDIF_CONFLICTING_TEMPLATE_NAME.get(
                            String.valueOf(t.getName()), startLineNumber);
                    throw new MakeLDIFException(message);
                } else {
                    templateFileTemplates.put(lowerName, t);
                }
            } else {
                LocalizableMessage message = ERR_MAKELDIF_UNEXPECTED_TEMPLATE_FILE_LINE.get(line, lineNumber);
                throw new MakeLDIFException(message);
            }
        }
        // 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);
        }
        for (Template t : templateFileTemplates.values()) {
            t.completeTemplateInitialization(templateFileTemplates);
        }
        registeredTags.putAll(templateFileIncludeTags);
        constants.putAll(templateFileConstants);
        branches.putAll(templateFileBranches);
        templates.putAll(templateFileTemplates);
    }
    /**
     * Parse a line and replace all constants within [ ] with their values.
     *
     * @param line
     *            The line to parse.
     * @param lineNumber
     *            The line number in the template file.
     * @param constants
     *            The set of constants defined in the template file.
     * @param warnings
     *            A list into which any warnings identified may be placed.
     * @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) {
        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);
            }
            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);
                    if (constantValue == null) {
                        LocalizableMessage message = WARN_MAKELDIF_WARNING_UNDEFINED_CONSTANT.get(constantName,
                                lineNumber);
                        warnings.add(message);
                    } else {
                        lineBuffer.replace(openPos, closePos + 1, constantValue);
                    }
                }
                if (openPos >= 0) {
                    closePos = openPos;
                }
                line = lineBuffer.toString();
                closePos = line.lastIndexOf(']', closePos);
            }
        } while (closePos > 0);
        return line;
    }
    /**
     * Parses the information contained in the provided set of lines as a
     * MakeLDIF branch definition.
     *
     * @param branchLines
     *            The set of lines containing the branch definition.
     * @param startLineNumber
     *            The line number in the template file on which the first of the
     *            branch lines appears.
     * @param tags
     *            The set of defined tags from the template file. Note that this
     *            does not include the tags that are always registered by
     *            default.
     * @param warnings
     *            A list into which any warnings identified may be placed.
     * @return The decoded branch definition.
     * @throws MakeLDIFException
     *             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 MakeLDIFException {
        // The first line must be "branch: " followed by the branch DN.
        String dnString = branchLines[0].substring(8).trim();
        DN branchDN;
        try {
            branchDN = DN.valueOf(dnString, schema);
        } catch (Exception e) {
            LocalizableMessage message = ERR_MAKELDIF_CANNOT_DECODE_BRANCH_DN.get(dnString, startLineNumber);
            throw new MakeLDIFException(message);
        }
        // Create a new branch that will be used for the verification process.
        Branch branch = new Branch(this, branchDN, null);
        for (int i = 1; i < branchLines.length; i++) {
            String line = branchLines[i];
            String lowerLine = line.toLowerCase();
            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_MAKELDIF_BRANCH_SUBORDINATE_TEMPLATE_NO_COLON.get(lineNumber,
                            dnString);
                    throw new MakeLDIFException(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_MAKELDIF_BRANCH_SUBORDINATE_INVALID_NUM_ENTRIES.get(
                                lineNumber, dnString, numEntries, templateName);
                        throw new MakeLDIFException(message);
                    } else if (numEntries == 0) {
                        LocalizableMessage message = WARN_MAKELDIF_BRANCH_SUBORDINATE_ZERO_ENTRIES.get(lineNumber,
                                dnString, templateName);
                        warnings.add(message);
                    }
                    branch.addSubordinateTemplate(templateName, numEntries);
                } catch (NumberFormatException nfe) {
                    LocalizableMessage message = ERR_MAKELDIF_BRANCH_SUBORDINATE_CANT_PARSE_NUMENTRIES.get(
                            templateName, lineNumber, dnString);
                    throw new MakeLDIFException(message);
                }
            } else {
                TemplateLine templateLine =
                        parseTemplateLine(line, lowerLine, lineNumber, branch, null, tags, warnings);
                branch.addExtraLine(templateLine);
            }
        }
        return branch;
    }
    /**
     * Parses the information contained in the provided set of lines as a
     * MakeLDIF 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 tags
     *            The set of defined tags from the template file. Note that this
     *            does not include the tags that are always registered by
     *            default.
     * @param definedTemplates
     *            The set of templates already defined in the template file.
     * @param warnings
     *            A list into which any warnings identified may be placed.
     * @return The decoded template definition.
     * @throws MakeLDIFException
     *             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 MakeLDIFException {
        // The first line must be "template: " followed by the template name.
        String templateName = templateLines[0].substring(10).trim();
        // 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;
        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();
            if (lowerLine.startsWith("#")) {
                // It's a comment. Ignore it.
                continue;
            } else if (lowerLine.startsWith("extends: ")) {
                String parentTemplateName = line.substring(9).trim();
                parentTemplate = definedTemplates.get(parentTemplateName.toLowerCase());
                if (parentTemplate == null) {
                    LocalizableMessage message = ERR_MAKELDIF_TEMPLATE_INVALID_PARENT_TEMPLATE.get(parentTemplateName,
                            lineNumber, templateName);
                    throw new MakeLDIFException(message);
                }
            } else if (lowerLine.startsWith("rdnattr: ")) {
                // 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, "+");
                while (tokenizer.hasMoreTokens()) {
                    attrList.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_MAKELDIF_TEMPLATE_SUBORDINATE_TEMPLATE_NO_COLON.get(lineNumber,
                            templateName);
                    throw new MakeLDIFException(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_MAKELDIF_TEMPLATE_SUBORDINATE_INVALID_NUM_ENTRIES.get(
                                lineNumber, templateName, numEntries, subTemplateName);
                        throw new MakeLDIFException(message);
                    } else if (numEntries == 0) {
                        LocalizableMessage message = WARN_MAKELDIF_TEMPLATE_SUBORDINATE_ZERO_ENTRIES.get(lineNumber,
                                templateName, subTemplateName);
                        warnings.add(message);
                    }
                    subTemplateNames.add(subTemplateName);
                    entriesPerTemplate.add(numEntries);
                } catch (NumberFormatException nfe) {
                    LocalizableMessage message = ERR_MAKELDIF_TEMPLATE_SUBORDINATE_CANT_PARSE_NUMENTRIES.get(
                            subTemplateName, lineNumber, templateName);
                    throw new MakeLDIFException(message);
                }
            } else {
                // It's something we don't recognize, so 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);
        int[] numEntriesPerTemplate = new int[entriesPerTemplate.size()];
        for (int i = 0; i < numEntriesPerTemplate.length; i++) {
            numEntriesPerTemplate[i] = entriesPerTemplate.get(i);
        }
        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;
            if (lowerLine.startsWith("#")) {
                // It's a comment, so we should ignore it.
                continue;
            } else {
                TemplateLine templateLine = parseTemplateLine(line, lowerLine, lineNumber, null, template, tags,
                        warnings);
                template.addTemplateLine(templateLine);
            }
        }
        return template;
    }
    private static final int PARSING_STATIC_TEXT = 0;
    private static final int PARSING_REPLACEMENT_TAG = 1;
    private static final int PARSING_ATTRIBUTE_TAG = 2;
    private static final int PARSING_ESCAPED_CHAR = 3;
    /**
     * Parses the provided line as a template line. Note that exactly one of the
     * branch or template arguments must be non-null and the other must be null.
     *
     * @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
     *            The branch with which the template line is associated.
     * @param template
     *            The template with which the template line is associated.
     * @param tags
     *            The set of defined tags from the template file. Note that this
     *            does not include the tags that are always registered by
     *            default.
     * @param warnings
     *            A list into which any warnings identified may be placed.
     * @return The template line that has been parsed.
     * @throws MakeLDIFException
     *             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 MakeLDIFException {
        // The first component must be the attribute type, followed by a colon.
        int colonPos = lowerLine.indexOf(':');
        if (colonPos < 0) {
            if (branch == null) {
                LocalizableMessage message = ERR_MAKELDIF_NO_COLON_IN_TEMPLATE_LINE.get(lineNumber, template.getName());
                throw new MakeLDIFException(message);
            } else {
                LocalizableMessage message = ERR_MAKELDIF_NO_COLON_IN_BRANCH_EXTRA_LINE.get(lineNumber,
                        String.valueOf(branch.getBranchDN()));
                throw new MakeLDIFException(message);
            }
        } else if (colonPos == 0) {
            if (branch == null) {
                LocalizableMessage message = ERR_MAKELDIF_NO_ATTR_IN_TEMPLATE_LINE.get(lineNumber, template.getName());
                throw new MakeLDIFException(message);
            } else {
                LocalizableMessage message = ERR_MAKELDIF_NO_ATTR_IN_BRANCH_EXTRA_LINE.get(lineNumber,
                        String.valueOf(branch.getBranchDN()));
                throw new MakeLDIFException(message);
            }
        }
        AttributeType attributeType = schema.getAttributeType(lowerLine.substring(0, colonPos));
        // First, check whether the value is an URL value: <attrName>:< <url>
        int length = line.length();
        int pos = colonPos + 1;
        boolean valueIsURL = false;
        boolean valueIsBase64 = false;
        if (pos < length) {
            if (lowerLine.charAt(pos) == '<') {
                valueIsURL = true;
                pos++;
            } else if (lowerLine.charAt(pos) == ':') {
                valueIsBase64 = true;
                pos++;
            }
        }
        // Then, find the position of the first non-blank character in the line.
        while ((pos < length) && (lowerLine.charAt(pos) == ' ')) {
            pos++;
        }
        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_MAKELDIF_NO_VALUE_IN_TEMPLATE_LINE
                        .get(lineNumber, template.getName());
                warnings.add(message);
            } else {
                LocalizableMessage message = WARN_MAKELDIF_NO_VALUE_IN_BRANCH_EXTRA_LINE.get(lineNumber,
                        String.valueOf(branch.getBranchDN()));
                warnings.add(message);
            }
        }
        int phase = PARSING_STATIC_TEXT;
        int previousPhase = PARSING_STATIC_TEXT;
        ArrayList<TemplateTag> tagList = new ArrayList<TemplateTag>();
        StringBuilder buffer = new StringBuilder();
        for (; pos < length; pos++) {
            char c = line.charAt(pos);
            switch (phase) {
            case PARSING_STATIC_TEXT:
                switch (c) {
                case '\\':
                    phase = PARSING_ESCAPED_CHAR;
                    previousPhase = PARSING_STATIC_TEXT;
                    break;
                case '<':
                    if (buffer.length() > 0) {
                        StaticTextTag t = new StaticTextTag();
                        String[] args = new String[] { buffer.toString() };
                        t.initializeForBranch(schema, this, branch, args, lineNumber, warnings);
                        tagList.add(t);
                        buffer = new StringBuilder();
                    }
                    phase = PARSING_REPLACEMENT_TAG;
                    break;
                case '{':
                    if (buffer.length() > 0) {
                        StaticTextTag t = new StaticTextTag();
                        String[] args = new String[] { buffer.toString() };
                        t.initializeForBranch(schema, this, branch, args, lineNumber, warnings);
                        tagList.add(t);
                        buffer = new StringBuilder();
                    }
                    phase = PARSING_ATTRIBUTE_TAG;
                    break;
                default:
                    buffer.append(c);
                }
                break;
            case PARSING_REPLACEMENT_TAG:
                switch (c) {
                case '\\':
                    phase = PARSING_ESCAPED_CHAR;
                    previousPhase = PARSING_REPLACEMENT_TAG;
                    break;
                case '>':
                    TemplateTag t =
                        parseReplacementTag(buffer.toString(), branch, template, lineNumber, tags, warnings);
                    tagList.add(t);
                    buffer = new StringBuilder();
                    phase = PARSING_STATIC_TEXT;
                    break;
                default:
                    buffer.append(c);
                    break;
                }
                break;
            case PARSING_ATTRIBUTE_TAG:
                switch (c) {
                case '\\':
                    phase = PARSING_ESCAPED_CHAR;
                    previousPhase = PARSING_ATTRIBUTE_TAG;
                    break;
                case '}':
                    TemplateTag t = parseAttributeTag(buffer.toString(), branch, template, lineNumber, warnings);
                    tagList.add(t);
                    buffer = new StringBuilder();
                    phase = PARSING_STATIC_TEXT;
                    break;
                default:
                    buffer.append(c);
                    break;
                }
                break;
            case PARSING_ESCAPED_CHAR:
                buffer.append(c);
                phase = previousPhase;
                break;
            }
        }
        if (phase == PARSING_STATIC_TEXT) {
            if (buffer.length() > 0) {
                StaticTextTag t = new StaticTextTag();
                String[] args = new String[] { buffer.toString() };
                t.initializeForBranch(schema, this, branch, args, lineNumber, warnings);
                tagList.add(t);
            }
        } else {
            LocalizableMessage message = ERR_MAKELDIF_INCOMPLETE_TAG.get(lineNumber);
            throw new MakeLDIFException(message);
        }
        TemplateTag[] tagArray = new TemplateTag[tagList.size()];
        tagList.toArray(tagArray);
        return new TemplateLine(attributeType, lineNumber, tagArray, valueIsURL, valueIsBase64);
    }
    /**
     * Parses the provided string as a replacement tag. Exactly one of the
     * branch or template must be null, and the other must be non-null.
     *
     * @param tagString
     *            The string containing the encoded tag.
     * @param branch
     *            The branch in which this tag appears.
     * @param template
     *            The template in which this tag appears.
     * @param lineNumber
     *            The line number on which this tag appears in the template
     *            file.
     * @param tags
     *            The set of defined tags from the template file. Note that this
     *            does not include the tags that are always registered by
     *            default.
     * @param warnings
     *            A list into which any warnings identified may be placed.
     * @return The replacement tag parsed from the provided string.
     * @throws MakeLDIFException
     *             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 MakeLDIFException {
        // 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();
        TemplateTag t = getTag(lowerTagName);
        if (t == null) {
            t = tags.get(lowerTagName);
            if (t == null) {
                LocalizableMessage message = ERR_MAKELDIF_NO_SUCH_TAG.get(tagName, lineNumber);
                throw new MakeLDIFException(message);
            }
        }
        ArrayList<String> argList = new ArrayList<String>();
        while (tokenizer.hasMoreTokens()) {
            argList.add(tokenizer.nextToken().trim());
        }
        String[] args = new String[argList.size()];
        argList.toArray(args);
        TemplateTag newTag;
        try {
            newTag = t.getClass().newInstance();
        } catch (Exception e) {
            LocalizableMessage message = ERR_MAKELDIF_CANNOT_INSTANTIATE_NEW_TAG.get(tagName, lineNumber,
                    String.valueOf(e));
            throw new MakeLDIFException(message, e);
        }
        if (branch == null) {
            newTag.initializeForTemplate(schema, this, template, args, lineNumber, warnings);
        } else {
            if (newTag.allowedInBranch()) {
                newTag.initializeForBranch(schema, this, branch, args, lineNumber, warnings);
            } else {
                LocalizableMessage message = ERR_MAKELDIF_TAG_NOT_ALLOWED_IN_BRANCH.get(newTag.getName(), lineNumber);
                throw new MakeLDIFException(message);
            }
        }
        return newTag;
    }
    /**
     * Parses the provided string as an attribute tag. Exactly one of the branch
     * or template must be null, and the other must be non-null.
     *
     * @param tagString
     *            The string containing the encoded tag.
     * @param branch
     *            The branch in which this tag appears.
     * @param template
     *            The template in which this tag appears.
     * @param lineNumber
     *            The line number on which this tag appears in the template
     *            file.
     * @param warnings
     *            A list into which any warnings identified may be placed.
     * @return The attribute tag parsed from the provided string.
     * @throws MakeLDIFException
     *             If some other problem occurs during processing.
     */
    private TemplateTag parseAttributeTag(String tagString, Branch branch, Template template, int lineNumber,
            List<LocalizableMessage> warnings) throws MakeLDIFException {
        // 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>();
        while (tokenizer.hasMoreTokens()) {
            argList.add(tokenizer.nextToken());
        }
        String[] args = new String[argList.size()];
        argList.toArray(args);
        AttributeValueTag tag = new AttributeValueTag();
        if (branch == null) {
            tag.initializeForTemplate(schema, this, template, args, lineNumber, warnings);
        } else {
            tag.initializeForBranch(schema, this, branch, args, 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.
     *
     * @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.
     */
    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;
        }
        // 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;
            }
        }
        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.
     *
     * @param file
     *            The file for which to retrieve the contents.
     * @return An array containing the lines of the specified file.
     * @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);
        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);
        }
        return lines;
    }
    /**
     * Generates the entries and writes them to the provided entry writer.
     *
     * @param entryWriter
     *            The entry writer that should be used to write the entries.
     * @return The result that indicates whether processing should continue.
     * @throws IOException
     *             If an error occurs while writing the entry.
     * @throws MakeLDIFException
     *             If some other problem occurs.
     */
    public TagResult generateEntries(EntryWriter entryWriter) throws IOException, MakeLDIFException {
        for (Branch b : branches.values()) {
            TagResult result = b.writeEntries(entryWriter);
            if (!(result.keepProcessingTemplateFile())) {
                return result;
            }
        }
        entryWriter.closeEntryWriter();
        return TagResult.SUCCESS_RESULT;
    }
    /**
     * Writer of generated 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 MakeLDIFException
         *             If some other problem occurs.
         */
        public boolean writeEntry(TemplateEntry entry) throws IOException, MakeLDIFException;
        /**
         * Notifies the entry writer that no more entries will be provided and
         * that any associated cleanup may be performed.
         */
        public void closeEntryWriter();
    }
    /**
     * Represents a branch that should be included in the generated results. A
     * branch may or may not have subordinate entries.
     */
    static class Branch {
        /** The DN for this branch entry. */
        private DN branchDN;
        /**
         * The number of entries that should be created below this branch for
         * each subordinate template.
         */
        private int[] numEntriesPerTemplate;
        /** The names of the subordinate templates for this branch. */
        private String[] subordinateTemplateNames;
        /** The set of subordinate templates for this branch. */
        private Template[] subordinateTemplates;
        /** The set of template lines that correspond to the RDN components. */
        private TemplateLine[] rdnLines;
        /** The set of extra lines that should be included in this branch entry. */
        private TemplateLine[] extraLines;
        private Schema schema;
        /**
         * Creates a new branch with the provided information.
         *
         * @param templateFile
         *            The template file in which this branch appears.
         * @param branchDN
         *            The DN for this branch entry.
         * @param schema
         *            schema used to create attribute
         */
        public Branch(TemplateFile templateFile, DN branchDN, Schema schema) {
            this(templateFile, branchDN, schema, new String[0], new int[0], new TemplateLine[0]);
        }
        /**
         * Creates a new branch with the provided information.
         *
         * @param templateFile
         *            The template file in which this branch appears.
         * @param branchDN
         *            The DN for this branch entry.
         * @param schema
         *            schema used to create attributes
         * @param subordinateTemplateNames
         *            The names of the subordinate templates used to generate
         *            entries below this branch.
         * @param numEntriesPerTemplate
         *            The number of entries that should be created below this
         *            branch for each subordinate template.
         * @param extraLines
         *            The set of extra lines that should be included in this
         *            branch entry.
         */
        public Branch(TemplateFile templateFile, DN branchDN, Schema schema, String[] subordinateTemplateNames,
                int[] numEntriesPerTemplate, TemplateLine[] extraLines) {
            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();
                }
            }
            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);
        }
        /**
         * Performs any necessary processing to ensure that the branch
         * initialization is completed. In particular, it should make sure that
         * all referenced subordinate templates actually exist in the template
         * file.
         *
         * @param templates
         *            The set of templates defined in the template file.
         * @throws MakeLDIFException
         *             If any of the subordinate templates are not defined in
         *             the template file.
         */
        public void completeBranchInitialization(Map<String, Template> templates) throws MakeLDIFException {
            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_MAKELDIF_UNDEFINED_BRANCH_SUBORDINATE.get(branchDN.toString(),
                                subordinateTemplateNames[i]);
                        throw new MakeLDIFException(message);
                    }
                }
            }
        }
        /**
         * Retrieves the DN for this branch entry.
         *
         * @return The DN for this branch entry.
         */
        public 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.
         *
         * @param name
         *            The name of the template to use to generate the entries.
         * @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;
        }
        /**
         * Adds the provided template line to the set of extra lines for this
         * branch.
         *
         * @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;
        }
        /**
         * Indicates whether this branch contains a reference to the specified
         * attribute type, either in the RDN components of the DN or in the
         * extra lines.
         *
         * @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.
         */
        public boolean hasAttribute(AttributeType attributeType) {
            if (branchDN.rdn().getAttributeValue(attributeType) != null) {
                return true;
            }
            for (TemplateLine l : extraLines) {
                if (l.getAttributeType().equals(attributeType)) {
                    return true;
                }
            }
            return false;
        }
        /**
         * Writes the entry for this branch, as well as all appropriate
         * subordinate entries.
         *
         * @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 MakeLDIFException
         *             If some other problem occurs.
         */
        public TagResult writeEntries(EntryWriter entryWriter) throws IOException, MakeLDIFException {
            // 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;
                }
            }
            for (TemplateLine l : extraLines) {
                TagResult r = l.generateLine(entry);
                if (!(r.keepProcessingEntry() && r.keepProcessingParent() && r.keepProcessingTemplateFile())) {
                    return r;
                }
            }
            if (!entryWriter.writeEntry(entry)) {
                return TagResult.STOP_PROCESSING;
            }
            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;
                    }
                    return r;
                }
            }
            return TagResult.SUCCESS_RESULT;
        }
    }
    /**
     * Represents a template, which is a pattern that may be used to generate
     * entries. A template may be used either below a branch or below another
     * template.
     */
    static class Template {
        /**
         * The attribute types that are used in the RDN for entries generated using
         * this template.
         */
        private org.forgerock.opendj.ldap.schema.AttributeType[] rdnAttributes;
        /** The number of entries to create for each subordinate template. */
        private int[] numEntriesPerTemplate;
        /** The name for this template. */
        private String name;
        /** The names of the subordinate templates below this template. */
        private String[] subordinateTemplateNames;
        /** The subordinate templates below this template. */
        private Template[] subordinateTemplates;
        /** The template file that contains this template. */
        private 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;
        }
        /**
         * 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.
         * @param templateLines
         *            The set of template lines for this template.
         */
        public Template(TemplateFile templateFile, String name, AttributeType[] rdnAttributes,
                String[] subordinateTemplateNames, int[] numEntriesPerTemplate, TemplateLine[] templateLines) {
            this.templateFile = templateFile;
            this.name = name;
            this.rdnAttributes = rdnAttributes;
            this.subordinateTemplateNames = subordinateTemplateNames;
            this.numEntriesPerTemplate = numEntriesPerTemplate;
            this.templateLines = templateLines;
            subordinateTemplates = null;
        }
        /**
         * Performs any necessary processing to ensure that the template
         * initialization is completed. In particular, it should make sure that
         * all referenced subordinate templates actually exist in the template
         * file, and that all of the RDN attributes are contained in the
         * template lines.
         *
         * @param templates
         *            The set of templates defined in the template file.
         * @throws MakeLDIFException
         *             If any of the subordinate templates are not defined in
         *             the template file.
         */
        public void completeTemplateInitialization(Map<String, Template> templates) throws MakeLDIFException {
            // 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_MAKELDIF_UNDEFINED_TEMPLATE_SUBORDINATE.get(
                                subordinateTemplateNames[i], name);
                        throw new MakeLDIFException(message);
                    }
                }
            }
            // 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);
            }
            for (TemplateLine l : templateLines) {
                if (rdnAttrs.remove(l.getAttributeType())) {
                    if (rdnAttrs.isEmpty()) {
                        break;
                    }
                }
            }
            if (!rdnAttrs.isEmpty()) {
                AttributeType t = rdnAttrs.iterator().next();
                LocalizableMessage message = ERR_MAKELDIF_TEMPLATE_MISSING_RDN_ATTR.get(name, t.getNameOrOID());
                throw new MakeLDIFException(message);
            }
        }
        /**
         * Retrieves the name for this template.
         *
         * @return The name for this template.
         */
        public 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() {
            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() {
            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;
        }
        /**
         * Indicates whether this template contains any template lines that
         * reference the provided attribute type.
         *
         * @param attributeType
         *            The attribute type for which to make the determination.
         * @return <CODE>true</CODE> if this template contains one or more
         *         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)) {
                    return true;
                }
            }
            return false;
        }
        /**
         * Writes the entry for this template, as well as all appropriate
         * subordinate entries.
         *
         * @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 MakeLDIFException
         *             If some other problem occurs.
         */
        public TagResult writeEntries(EntryWriter entryWriter, DN parentDN, int count) throws IOException,
                MakeLDIFException {
            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;
                    }
                }
            }
            return TagResult.SUCCESS_RESULT;
        }
    }
    /**
     * Represents an entry that is generated using a branch or a template.
     */
    static class TemplateEntry {
        /** 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;
        /**
         * The DN of the parent entry for this template entry, if it is
         * available.
         */
        private 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.
         */
        private LinkedHashMap<AttributeType, ArrayList<TemplateValue>> attributes;
        /**
         * The template used to generate this entry (if it is associated with a
         * template).
         */
        private Template template;
        /**
         * Creates a new template entry that will be associated with the
         * provided branch.
         *
         * @param branch
         *            The branch to use when creating this template entry.
         */
        public TemplateEntry(Branch branch) {
            this.branch = branch;
            dn = branch.getBranchDN();
            template = null;
            parentDN = null;
            attributes = new LinkedHashMap<AttributeType, ArrayList<TemplateValue>>();
        }
        /**
         * Creates a new template entry that will be associated with the
         * provided template.
         *
         * @param template
         *            The template used to generate this entry.
         * @param parentDN
         *            The DN of the parent entry for this template entry.
         */
        public TemplateEntry(Template template, DN parentDN) {
            this.template = template;
            this.parentDN = parentDN;
            dn = null;
            branch = null;
            attributes = new LinkedHashMap<AttributeType, ArrayList<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() {
            return parentDN;
        }
        /**
         * Retrieves the DN for this template entry, if it is known.
         *
         * @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
            if (dn == null) {
                RDN rdn;
                AttributeType[] rdnAttrs = template.getRDNAttributes();
                if (rdnAttrs.length == 1) {
                    AttributeType type = rdnAttrs[0];
                    TemplateValue templateValue = getValue(type);
                    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());
                }
                dn = parentDN.child(rdn);
            }
            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.
         *
         * @param attributeType
         *            The attribute type for which to retrieve the value.
         * @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);
            }
        }
        /**
         * Retrieves the set of values for the specified attribute, if defined.
         *
         * @param attributeType
         *            The attribute type for which to retrieve the set of
         *            values.
         * @return The set of values for the specified attribute, or
         *         <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;
        }
        /**
         * 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);
            }
        }
        /**
         * Returns an entry from this template entry.
         *
         * @return an entry
         */
        public Entry toEntry() {
            Entry entry = LinkedHashMapEntry.FACTORY.newEntry(getDN());
            AttributeFactory attributeFactory = LinkedAttribute.FACTORY;
            for (AttributeType attributeType : attributes.keySet()) {
                ArrayList<TemplateValue> valueList = attributes.get(attributeType);
                Attribute newAttribute = attributeFactory.newAttribute(AttributeDescription.create(attributeType));
                for (TemplateValue value : valueList) {
                    newAttribute.add(value.getValueAsString());
                }
                entry.addAttribute(newAttribute);
            }
            return entry;
        }
    }
    /**
     * Represents a line that may appear in a template or branch. It may contain
     * any number of tags to be evaluated.
     */
    static class TemplateLine {
        /** The attribute type for this template line. */
        private AttributeType attributeType;
        /**
         * The line number on which this template line appears in the template
         * file.
         */
        private int lineNumber;
        /** The set of tags for this template line. */
        private TemplateTag[] tags;
        /** Whether this line corresponds to an URL value or not. */
        private boolean isURL;
        /** Whether this line corresponds to a base64 encoded value or not. */
        private 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.
         *
         * @param attributeType
         *            The attribute type for this template line.
         * @param lineNumber
         *            The line number on which this template line appears in the
         *            template file.
         * @param tags
         *            The set of tags for this template line.
         */
        public TemplateLine(AttributeType attributeType, int lineNumber, TemplateTag[] tags) {
            this(attributeType, lineNumber, tags, false, false);
        }
        /**
         * Creates a new template line with the provided information.
         *
         * @param attributeType
         *            The attribute type for this template line.
         * @param lineNumber
         *            The line number on which this template line appears in the
         *            template file.
         * @param tags
         *            The set of tags for this template line.
         * @param isURL
         *            Whether this template line's value is an URL or not.
         * @param isBase64
         *            Whether this template line's value is Base64 encoded or
         *            not.
         */
        public TemplateLine(AttributeType attributeType, int lineNumber, TemplateTag[] tags, boolean isURL,
                boolean isBase64) {
            this.attributeType = attributeType;
            this.lineNumber = lineNumber;
            this.tags = tags;
            this.isURL = isURL;
            this.isBase64 = isBase64;
        }
        /**
         * Generates the content for this template line and places it in the
         * provided template entry.
         *
         * @param templateEntry
         *            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())) {
                    return result;
                }
            }
            templateEntry.addValue(value);
            return TagResult.SUCCESS_RESULT;
        }
    }
    /**
     * Represents a value generated from a template line.
     */
    static class TemplateValue {
        /** The generated template value. */
        private StringBuilder templateValue;
        /** The template line used to generate this value. */
        private 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) {
            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() {
            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() {
            return templateValue.toString();
        }
        /**
         * Appends the provided string to this template value.
         *
         * @param s
         *            The string to append.
         */
        public void append(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) {
            templateValue.append(String.valueOf(o));
        }
    }
}
opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateTag.java
New file
@@ -0,0 +1,2873 @@
/*
 * 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 2006-2009 Sun Microsystems, Inc.
 *      Portions Copyright 2013 ForgeRock AS
 */
package org.forgerock.opendj.ldif;
import static com.forgerock.opendj.ldap.CoreMessages.*;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldif.TemplateFile.Branch;
import org.forgerock.opendj.ldif.TemplateFile.Template;
import org.forgerock.opendj.ldif.TemplateFile.TemplateEntry;
import org.forgerock.opendj.ldif.TemplateFile.TemplateValue;
/**
 * Represents a tag that may be used in a template line when generating entries.
 * It can be used to generate content.
 *
 * @see TemplateFile
 * @see MakeLDIFEntryReader
 */
abstract class TemplateTag {
    /**
     * Retrieves the name for this tag.
     *
     * @return The name for this tag.
     */
    public 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();
    /**
     * Performs any initialization for this tag that may be needed while parsing
     * a branch definition.
     *
     * @param schema
     *            schema used to create attributes
     * @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.
     * @throws MakeLDIFException
     *             if a problem occurs
     */
    public void initializeForBranch(Schema schema, TemplateFile templateFile, Branch branch, String[] arguments,
            int lineNumber, List<LocalizableMessage> warnings) throws MakeLDIFException {
        // No implementation required by default.
    }
    /**
     * Performs any initialization for this tag that may be needed while parsing
     * a template definition.
     *
     * @param schema
     *            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
     *            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 MakeLDIFException
     *             if a problem occurs
     */
    public abstract void initializeForTemplate(Schema schema, TemplateFile templateFile, Template template,
            String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws MakeLDIFException;
    /**
     * 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) {
        // No implementation required by default.
    }
    /**
     * 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 abstract TagResult generateValue(TemplateEntry templateEntry, TemplateValue templateValue);
    /**
     * Provides information about the result of tag processing.
     */
    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;
        }
    }
    /**
     * This class defines a tag that is 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() {
            attributeType = null;
            numCharacters = 0;
        }
        /**
         * Retrieves the name for this tag.
         *
         * @return The name for this tag.
         */
        public 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() {
            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 MakeLDIFException {
            if ((arguments.length < 1) || (arguments.length > 2)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(), lineNumber,
                        1, 2, arguments.length);
                throw new MakeLDIFException(message);
            }
            String lowerName = arguments[0].toLowerCase();
            attributeType = schema.getAttributeType(lowerName);
            if (!branch.hasAttribute(attributeType)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_UNDEFINED_ATTRIBUTE.get(arguments[0], lineNumber);
                throw new MakeLDIFException(message);
            }
            if (arguments.length == 2) {
                try {
                    numCharacters = Integer.parseInt(arguments[1]);
                    if (numCharacters < 0) {
                        LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(numCharacters, 0,
                                getName(), lineNumber);
                        throw new MakeLDIFException(message);
                    }
                } catch (NumberFormatException nfe) {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[1], getName(),
                            lineNumber);
                    throw new MakeLDIFException(message);
                }
            } else {
                numCharacters = 0;
            }
        }
        /**
         * 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 MakeLDIFException {
            if ((arguments.length < 1) || (arguments.length > 2)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(), lineNumber,
                        1, 2, arguments.length);
                throw new MakeLDIFException(message);
            }
            String lowerName = arguments[0].toLowerCase();
            attributeType = schema.getAttributeType(lowerName);
            if (!template.hasAttribute(attributeType)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_UNDEFINED_ATTRIBUTE.get(arguments[0], lineNumber);
                throw new MakeLDIFException(message);
            }
            if (arguments.length == 2) {
                try {
                    numCharacters = Integer.parseInt(arguments[1]);
                    if (numCharacters < 0) {
                        LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(numCharacters, 0,
                                getName(), lineNumber);
                        throw new MakeLDIFException(message);
                    }
                } catch (NumberFormatException nfe) {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[1], getName(),
                            lineNumber);
                    throw new MakeLDIFException(message);
                }
            } else {
                numCharacters = 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) {
            TemplateValue v = templateEntry.getValue(attributeType);
            if (v == null) {
                // This is fine -- we just won't append anything.
                return TagResult.SUCCESS_RESULT;
            }
            if (numCharacters > 0) {
                String valueString = v.getValueAsString();
                if (valueString.length() > numCharacters) {
                    templateValue.append(valueString.substring(0, numCharacters));
                } else {
                    templateValue.append(valueString);
                }
            } else {
                templateValue.append(v.getValue());
            }
            return TagResult.SUCCESS_RESULT;
        }
    }
    /**
     * This class defines a tag that is 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() {
            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 MakeLDIFException {
            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 MakeLDIFException {
            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 MakeLDIFException
         *             If a problem occurs while initializing this tag.
         */
        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber)
                throws MakeLDIFException {
            if (arguments.length == 0) {
                numComponents = 0;
            } else if (arguments.length == 1) {
                try {
                    numComponents = Integer.parseInt(arguments[0]);
                } catch (NumberFormatException nfe) {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[0], getName(),
                            lineNumber);
                    throw new MakeLDIFException(message);
                }
            } else {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(), lineNumber,
                        0, 1, arguments.length);
                throw new MakeLDIFException(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.toNormalizedString());
            } 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;
        }
    }
    /**
     * 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.
     */
    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;
        /** The index used for sequential access. */
        private int nextIndex;
        /** The random number generator for this tag. */
        private Random random;
        /** The array of 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() {
            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() {
            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 MakeLDIFException {
            initializeInternal(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,
                String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws MakeLDIFException {
            initializeInternal(templateFile, arguments, lineNumber, warnings);
        }
        /**
         * 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 MakeLDIFException
         *             If a problem occurs while initializing this tag.
         */
        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber,
                List<LocalizableMessage> warnings) throws MakeLDIFException {
            random = templateFile.getRandom();
            // There must be at least one argument, and possibly two.
            if ((arguments.length < 1) || (arguments.length > 2)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(), lineNumber,
                        1, 2, arguments.length);
                throw new MakeLDIFException(message);
            }
            // The first argument should be the path to the file.
            dataFile = templateFile.getFile(arguments[0]);
            if ((dataFile == null) || (!dataFile.exists())) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_FIND_FILE.get(arguments[0], getName(), lineNumber);
                throw new MakeLDIFException(message);
            }
            // If there is a second argument, then it should be either
            // "sequential" or "random". If there isn't one, then we should assume "random".
            if (arguments.length == 2) {
                if (arguments[1].equalsIgnoreCase("sequential")) {
                    sequential = true;
                    nextIndex = 0;
                } else if (arguments[1].equalsIgnoreCase("random")) {
                    sequential = false;
                } else {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_FILE_ACCESS_MODE.get(arguments[1], getName(),
                            lineNumber);
                    throw new MakeLDIFException(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_MAKELDIF_TAG_CANNOT_READ_FILE.get(arguments[0], getName(), lineNumber,
                        String.valueOf(ioe));
                throw new MakeLDIFException(message, ioe);
            }
        }
        /**
         * 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) {
                templateValue.append(fileLines[nextIndex++]);
                if (nextIndex >= fileLines.length) {
                    nextIndex = 0;
                }
            } else {
                templateValue.append(fileLines[random.nextInt(fileLines.length)]);
            }
            return TagResult.SUCCESS_RESULT;
        }
    }
    /**
     * This class defines a tag that is 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() {
            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 MakeLDIFException {
            this.templateFile = templateFile;
            if (arguments.length != 0) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber, 0,
                        arguments.length);
                throw new MakeLDIFException(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) {
            templateValue.append(templateFile.getFirstName());
            return TagResult.SUCCESS_RESULT;
        }
    }
    /**
     * This class defines a tag that is 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() {
            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() {
            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 MakeLDIFException {
            if (arguments.length != 0) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber, 0,
                        arguments.length);
                throw new MakeLDIFException(message);
            }
        }
        /**
         * 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 MakeLDIFException {
            if (arguments.length != 0) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber, 0,
                        arguments.length);
                throw new MakeLDIFException(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) {
            templateValue.append(UUID.randomUUID().toString());
            return TagResult.SUCCESS_RESULT;
        }
    }
    /**
     * This class defines a tag that is used to base presence of one attribute
     * on the absence of another attribute and/or attribute value.
     */
    static class IfAbsentTag 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 ifabsent tag.
         */
        public IfAbsentTag() {
            attributeType = null;
            assertionValue = null;
        }
        /**
         * Retrieves the name for this tag.
         *
         * @return The name for this tag.
         */
        public 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 MakeLDIFException {
            if ((arguments.length < 1) || (arguments.length > 2)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(), lineNumber,
                        1, 2, arguments.length);
                throw new MakeLDIFException(message);
            }
            String lowerName = arguments[0].toLowerCase();
            AttributeType t = schema.getAttributeType(lowerName);
            if (!branch.hasAttribute(t)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_UNDEFINED_ATTRIBUTE.get(arguments[0], lineNumber);
                throw new MakeLDIFException(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 MakeLDIFException {
            if ((arguments.length < 1) || (arguments.length > 2)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(), lineNumber,
                        1, 2, arguments.length);
                throw new MakeLDIFException(message);
            }
            String lowerName = arguments[0].toLowerCase();
            attributeType = schema.getAttributeType(lowerName);
            if (!template.hasAttribute(attributeType)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_UNDEFINED_ATTRIBUTE.get(arguments[0], lineNumber);
                throw new MakeLDIFException(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) {
            List<TemplateValue> values = templateEntry.getValues(attributeType);
            if ((values == null) || values.isEmpty()) {
                return TagResult.SUCCESS_RESULT;
            }
            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;
            }
        }
    }
    /**
     * This class defines a tag that is 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() {
            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 MakeLDIFException {
            if ((arguments.length < 1) || (arguments.length > 2)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(), lineNumber,
                        1, 2, arguments.length);
                throw new MakeLDIFException(message);
            }
            String lowerName = arguments[0].toLowerCase();
            AttributeType t = schema.getAttributeType(lowerName);
            if (!branch.hasAttribute(t)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_UNDEFINED_ATTRIBUTE.get(arguments[0], lineNumber);
                throw new MakeLDIFException(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 MakeLDIFException {
            if ((arguments.length < 1) || (arguments.length > 2)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(), lineNumber,
                        1, 2, arguments.length);
                throw new MakeLDIFException(message);
            }
            String lowerName = arguments[0].toLowerCase();
            attributeType = schema.getAttributeType(lowerName);
            if (!template.hasAttribute(attributeType)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_UNDEFINED_ATTRIBUTE.get(arguments[0], lineNumber);
                throw new MakeLDIFException(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) {
            List<TemplateValue> values = templateEntry.getValues(attributeType);
            if ((values == null) || values.isEmpty()) {
                return TagResult.OMIT_FROM_ENTRY;
            }
            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;
            }
        }
    }
    /**
     * This class defines a tag that is 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() {
            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 MakeLDIFException {
            this.templateFile = templateFile;
            if (arguments.length != 0) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber, 0,
                        arguments.length);
                throw new MakeLDIFException(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) {
            templateValue.append(templateFile.getLastName());
            return TagResult.SUCCESS_RESULT;
        }
    }
    /**
     * 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.
     * <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
     * that will be the weight for that particular item. If no weight is given,
     * then the weight for that item will be assumed to be one.
     */
    static class ListTag extends TemplateTag {
        /** The ultimate cumulative weight. */
        private int cumulativeWeight;
        /** The set of cumulative weights for the list items. */
        private int[] valueWeights;
        /** The set of values in the list. */
        private String[] valueStrings;
        /** 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() {
            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() {
            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 MakeLDIFException {
            initializeInternal(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,
                String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws MakeLDIFException {
            initializeInternal(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 MakeLDIFException
         *             If a problem occurs while initializing this tag.
         */
        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber,
                List<LocalizableMessage> warnings) throws MakeLDIFException {
            if (arguments.length == 0) {
                throw new MakeLDIFException(ERR_MAKELDIF_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];
                int weight = 1;
                int semicolonPos = s.lastIndexOf(';');
                if (semicolonPos >= 0) {
                    try {
                        weight = Integer.parseInt(s.substring(semicolonPos + 1));
                        s = s.substring(0, semicolonPos);
                    } catch (Exception e) {
                        warnings.add(WARN_MAKELDIF_TAG_LIST_INVALID_WEIGHT.get(lineNumber, s));
                    }
                }
                cumulativeWeight += weight;
                valueStrings[i] = s;
                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) {
            int selectedWeight = random.nextInt(cumulativeWeight) + 1;
            for (int i = 0; i < valueWeights.length; i++) {
                if (selectedWeight <= valueWeights[i]) {
                    templateValue.append(valueStrings[i]);
                    break;
                }
            }
            return TagResult.SUCCESS_RESULT;
        }
    }
    /**
     * This class defines a tag that is used to include the DN of the parent
     * entry in the attribute value.
     */
    static class ParentDNTag extends TemplateTag {
        /**
         * Creates a new instance of this parent DN tag.
         */
        public ParentDNTag() {
            // No implementation required.
        }
        /**
         * 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() {
            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 MakeLDIFException {
            if (arguments.length != 0) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber, 0,
                        arguments.length);
                throw new MakeLDIFException(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 parentDN = templateEntry.getParentDN();
            if ((parentDN == null) || parentDN.isRootDN()) {
                return TagResult.SUCCESS_RESULT;
            }
            templateValue.append(parentDN);
            return TagResult.SUCCESS_RESULT;
        }
    }
    /**
     * This class defines a tag that is 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. */
        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() {
            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() {
            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 MakeLDIFException {
            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 MakeLDIFException {
            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 MakeLDIFException
         *             If a problem occurs while initializing this tag.
         */
        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber)
                throws MakeLDIFException {
            random = templateFile.getRandom();
            if (arguments.length != 1) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber, 1,
                        arguments.length);
                throw new MakeLDIFException(message);
            }
            try {
                percentage = Integer.parseInt(arguments[0]);
                if (percentage < 0) {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(percentage, 0,
                            getName(), lineNumber);
                    throw new MakeLDIFException(message);
                } else if (percentage > 100) {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_ABOVE_UPPER_BOUND.get(percentage, 100,
                            getName(), lineNumber);
                    throw new MakeLDIFException(message);
                }
            } catch (NumberFormatException nfe) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[0], getName(),
                        lineNumber);
                throw new MakeLDIFException(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) {
            int intValue = random.nextInt(100);
            if (intValue < percentage) {
                return TagResult.SUCCESS_RESULT;
            } else {
                return TagResult.OMIT_FROM_ENTRY;
            }
        }
    }
    /**
     * 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:
     * <UL>
     * <LI>alpha:length</LI>
     * <LI>alpha:minlength:maxlength</LI>
     * <LI>numeric:length</LI>
     * <LI>numeric:minvalue:maxvalue</LI>
     * <LI>numeric:minvalue:maxvalue:format</LI>
     * <LI>alphanumeric:length</LI>
     * <LI>alphanumeric:minlength:maxlength</LI>
     * <LI>chars:characters:length</LI>
     * <LI>chars:characters:minlength:maxlength</LI>
     * <LI>hex:length</LI>
     * <LI>hex:minlength:maxlength</LI>
     * <LI>base64:length</LI>
     * <LI>base64:minlength:maxlength</LI>
     * <LI>month</LI>
     * <LI>month:maxlength</LI>
     * <LI>telephone</LI>
     * </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;
        /**
         * The character set that will be used for alphabetic characters.
         */
        public static final char[] ALPHA_CHARS = "abcdefghijklmnopqrstuvwxyz".toCharArray();
        /**
         * The character set that will be used for numeric characters.
         */
        public static final char[] NUMERIC_CHARS = "01234567890".toCharArray();
        /**
         * The character set that will be used for alphanumeric characters.
         */
        public static final char[] ALPHANUMERIC_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
        /**
         * The character set that will be used for hexadecimal characters.
         */
        public static final char[] HEX_CHARS = "01234567890abcdef".toCharArray();
        /**
         * The character set that will be used for base64 characters.
         */
        public static final char[] BASE64_CHARS = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
            + "01234567890+/").toCharArray();
        /**
         * The set of month names that will be used.
         */
        public static final String[] MONTHS = { "January", "February", "March", "April", "May", "June", "July",
            "August", "September", "October", "November", "December" };
        /** The character set that should be used to generate the values. */
        private char[] characterSet;
        /** The decimal format used to format numeric values. */
        private DecimalFormat decimalFormat;
        /** The number of characters between the minimum and maximum length */
        /** (inclusive). */
        private int lengthRange;
        /** The maximum number of characters to include in the value. */
        private int maxLength;
        /** The minimum number of characters to include in the value. */
        private int minLength;
        /** The type of random value that should be generated. */
        private int randomType;
        /** The maximum numeric value that should be generated. */
        private long maxValue;
        /** The minimum numeric value that should be generated. */
        private long minValue;
        /**
         * The number of values between the minimum and maximum value
         * (inclusive).
         */
        private long valueRange;
        /** 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() {
            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() {
            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 MakeLDIFException {
            initializeInternal(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,
                String[] arguments, int lineNumber, List<LocalizableMessage> warnings) throws MakeLDIFException {
            initializeInternal(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 MakeLDIFException
         *             If a problem occurs while initializing this tag.
         */
        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber,
                List<LocalizableMessage> warnings) throws MakeLDIFException {
            random = templateFile.getRandom();
            // There must be at least one argument, to specify the type of
            // random value to generate.
            if ((arguments == null) || (arguments.length == 0)) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_NO_RANDOM_TYPE_ARGUMENT.get(lineNumber);
                throw new MakeLDIFException(message);
            }
            int numArgs = arguments.length;
            String randomTypeString = arguments[0].toLowerCase();
            if (randomTypeString.equals("alpha")) {
                characterSet = ALPHA_CHARS;
                decodeLength(arguments, 1, lineNumber, warnings);
            } else if (randomTypeString.equals("numeric")) {
                if (numArgs == 2) {
                    randomType = RANDOM_TYPE_CHARS_FIXED;
                    characterSet = NUMERIC_CHARS;
                    try {
                        minLength = Integer.parseInt(arguments[1]);
                        if (minLength < 0) {
                            LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(minLength, 0,
                                    getName(), lineNumber);
                            throw new MakeLDIFException(message);
                        } else if (minLength == 0) {
                            LocalizableMessage message = WARN_MAKELDIF_TAG_WARNING_EMPTY_VALUE.get(lineNumber);
                            warnings.add(message);
                        }
                    } catch (NumberFormatException nfe) {
                        LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[1],
                                getName(), lineNumber);
                        throw new MakeLDIFException(message, nfe);
                    }
                } else if ((numArgs == 3) || (numArgs == 4)) {
                    randomType = RANDOM_TYPE_NUMERIC;
                    if (numArgs == 4) {
                        try {
                            decimalFormat = new DecimalFormat(arguments[3]);
                        } catch (Exception e) {
                            LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_FORMAT_STRING.get(arguments[3],
                                    getName(), lineNumber);
                            throw new MakeLDIFException(message, e);
                        }
                    } else {
                        decimalFormat = null;
                    }
                    try {
                        minValue = Long.parseLong(arguments[1]);
                    } catch (NumberFormatException nfe) {
                        LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[1],
                                getName(), lineNumber);
                        throw new MakeLDIFException(message, nfe);
                    }
                    try {
                        maxValue = Long.parseLong(arguments[2]);
                        if (maxValue < minValue) {
                            LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(maxValue,
                                    minValue, getName(), lineNumber);
                            throw new MakeLDIFException(message);
                        }
                        valueRange = maxValue - minValue + 1;
                    } catch (NumberFormatException nfe) {
                        LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[2],
                                getName(), lineNumber);
                        throw new MakeLDIFException(message, nfe);
                    }
                } else {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(),
                            lineNumber, 2, 4, numArgs);
                    throw new MakeLDIFException(message);
                }
            } else if (randomTypeString.equals("alphanumeric")) {
                characterSet = ALPHANUMERIC_CHARS;
                decodeLength(arguments, 1, lineNumber, warnings);
            } else if (randomTypeString.equals("chars")) {
                if ((numArgs < 3) || (numArgs > 4)) {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(),
                            lineNumber, 3, 4, numArgs);
                    throw new MakeLDIFException(message);
                }
                characterSet = arguments[1].toCharArray();
                decodeLength(arguments, 2, lineNumber, warnings);
            } else if (randomTypeString.equals("hex")) {
                characterSet = HEX_CHARS;
                decodeLength(arguments, 1, lineNumber, warnings);
            } else if (randomTypeString.equals("base64")) {
                characterSet = BASE64_CHARS;
                decodeLength(arguments, 1, lineNumber, warnings);
            } else if (randomTypeString.equals("month")) {
                randomType = RANDOM_TYPE_MONTH;
                if (numArgs == 1) {
                    maxLength = 0;
                } else if (numArgs == 2) {
                    try {
                        maxLength = Integer.parseInt(arguments[1]);
                        if (maxLength <= 0) {
                            LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(maxLength, 1,
                                    getName(), lineNumber);
                            throw new MakeLDIFException(message);
                        }
                    } catch (NumberFormatException nfe) {
                        LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[1],
                                getName(), lineNumber);
                        throw new MakeLDIFException(message, nfe);
                    }
                } else {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(),
                            lineNumber, 1, 2, numArgs);
                    throw new MakeLDIFException(message);
                }
            } else if (randomTypeString.equals("telephone")) {
                randomType = RANDOM_TYPE_TELEPHONE;
            } else {
                LocalizableMessage message = ERR_MAKELDIF_TAG_UNKNOWN_RANDOM_TYPE.get(lineNumber, randomTypeString);
                throw new MakeLDIFException(message);
            }
        }
        /**
         * Decodes the information in the provided argument list as either a
         * single integer specifying the number of characters, or two integers
         * specifying the minimum and maximum number of characters.
         *
         * @param arguments
         *            The set of arguments to be processed.
         * @param startPos
         *            The position at which the first legth value should appear
         *            in the argument list.
         * @param lineNumber
         *            The line number on which the tag appears in the template
         *            file.
         * @param warnings
         *            A list into which any appropriate warning messages may be
         *            placed.
         */
        private void decodeLength(String[] arguments, int startPos, int lineNumber, List<LocalizableMessage> warnings)
                throws MakeLDIFException {
            int numArgs = arguments.length - startPos + 1;
            if (numArgs == 2) {
                // There is a fixed number of characters in the value.
                randomType = RANDOM_TYPE_CHARS_FIXED;
                try {
                    minLength = Integer.parseInt(arguments[startPos]);
                    if (minLength < 0) {
                        LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(minLength, 0,
                                getName(), lineNumber);
                        throw new MakeLDIFException(message);
                    } else if (minLength == 0) {
                        LocalizableMessage message = WARN_MAKELDIF_TAG_WARNING_EMPTY_VALUE.get(lineNumber);
                        warnings.add(message);
                    }
                } catch (NumberFormatException nfe) {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[startPos],
                            getName(), lineNumber);
                    throw new MakeLDIFException(message, nfe);
                }
            } else if (numArgs == 3) {
                // There are minimum and maximum lengths.
                randomType = RANDOM_TYPE_CHARS_VARIABLE;
                try {
                    minLength = Integer.parseInt(arguments[startPos]);
                    if (minLength < 0) {
                        LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(minLength, 0,
                                getName(), lineNumber);
                        throw new MakeLDIFException(message);
                    }
                } catch (NumberFormatException nfe) {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[startPos],
                            getName(), lineNumber);
                    throw new MakeLDIFException(message, nfe);
                }
                try {
                    maxLength = Integer.parseInt(arguments[startPos + 1]);
                    lengthRange = maxLength - minLength + 1;
                    if (maxLength < minLength) {
                        LocalizableMessage message = ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND.get(maxLength,
                                minLength, getName(), lineNumber);
                        throw new MakeLDIFException(message);
                    } else if (maxLength == 0) {
                        LocalizableMessage message = WARN_MAKELDIF_TAG_WARNING_EMPTY_VALUE.get(lineNumber);
                        warnings.add(message);
                    }
                } catch (NumberFormatException nfe) {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[startPos + 1],
                            getName(), lineNumber);
                    throw new MakeLDIFException(message, nfe);
                }
            } else {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(), lineNumber,
                        startPos + 1, startPos + 2, numArgs);
                throw new MakeLDIFException(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) {
            switch (randomType) {
            case RANDOM_TYPE_CHARS_FIXED:
                for (int i = 0; i < minLength; i++) {
                    templateValue.append(characterSet[random.nextInt(characterSet.length)]);
                }
                break;
            case RANDOM_TYPE_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:
                long randomValue = ((random.nextLong() & 0x7FFFFFFFFFFFFFFFL) % valueRange) + minValue;
                if (decimalFormat == null) {
                    templateValue.append(randomValue);
                } else {
                    templateValue.append(decimalFormat.format(randomValue));
                }
                break;
            case RANDOM_TYPE_MONTH:
                String month = MONTHS[random.nextInt(MONTHS.length)];
                if ((maxLength == 0) || (month.length() <= maxLength)) {
                    templateValue.append(month);
                } else {
                    templateValue.append(month.substring(0, maxLength));
                }
                break;
            case RANDOM_TYPE_TELEPHONE:
                templateValue.append("+1 ");
                for (int i = 0; i < 3; i++) {
                    templateValue.append(NUMERIC_CHARS[random.nextInt(NUMERIC_CHARS.length)]);
                }
                templateValue.append(' ');
                for (int i = 0; i < 3; i++) {
                    templateValue.append(NUMERIC_CHARS[random.nextInt(NUMERIC_CHARS.length)]);
                }
                templateValue.append(' ');
                for (int i = 0; i < 4; i++) {
                    templateValue.append(NUMERIC_CHARS[random.nextInt(NUMERIC_CHARS.length)]);
                }
                break;
            }
            return TagResult.SUCCESS_RESULT;
        }
    }
    /**
     * This class defines a tag that is 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() {
            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() {
            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 MakeLDIFException {
            if (arguments.length != 0) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber, 0,
                        arguments.length);
                throw new MakeLDIFException(message);
            }
        }
        /**
         * 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 MakeLDIFException {
            if (arguments.length != 0) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber, 0,
                        arguments.length);
                throw new MakeLDIFException(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;
            } else {
                templateValue.append(dn.rdn());
                return TagResult.SUCCESS_RESULT;
            }
        }
    }
    /**
     * This class defines a 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;
        /** The initial value in the sequence. */
        private int initialValue;
        /** 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() {
            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() {
            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 MakeLDIFException {
            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 MakeLDIFException {
            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 MakeLDIFException
         *             If a problem occurs while initializing this tag.
         */
        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber)
                throws MakeLDIFException {
            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_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[0], getName(),
                            lineNumber);
                    throw new MakeLDIFException(message);
                }
                nextValue = initialValue;
                resetOnNewParents = true;
                break;
            case 2:
                try {
                    initialValue = Integer.parseInt(arguments[0]);
                } catch (NumberFormatException nfe) {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[0], getName(),
                            lineNumber);
                    throw new MakeLDIFException(message);
                }
                if (arguments[1].equalsIgnoreCase("true")) {
                    resetOnNewParents = true;
                } else if (arguments[1].equalsIgnoreCase("false")) {
                    resetOnNewParents = false;
                } else {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_BOOLEAN.get(arguments[1], getName(),
                            lineNumber);
                    throw new MakeLDIFException(message);
                }
                nextValue = initialValue;
                break;
            default:
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(), lineNumber,
                        0, 2, arguments.length);
                throw new MakeLDIFException(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) {
            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) {
            templateValue.append(nextValue++);
            return TagResult.SUCCESS_RESULT;
        }
    }
    /**
     * This class defines a 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;
        /**
         * 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() {
            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() {
            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 MakeLDIFException {
            if (arguments.length != 1) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber, 1,
                        arguments.length);
                throw new MakeLDIFException(message);
            }
            text = arguments[0];
        }
        /**
         * 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 MakeLDIFException {
            if (arguments.length != 1) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber, 1,
                        arguments.length);
                throw new MakeLDIFException(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) {
            templateValue.append(text);
            return TagResult.SUCCESS_RESULT;
        }
    }
    /**
     * 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.
     */
    static class UnderscoreDNTag extends TemplateTag {
        /** The number of DN components to include. */
        private int numComponents;
        /**
         * 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() {
            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 MakeLDIFException {
            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 MakeLDIFException {
            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 MakeLDIFException
         *             TODO
         */
        private void initializeInternal(TemplateFile templateFile, String[] arguments, int lineNumber)
                throws MakeLDIFException {
            if (arguments.length == 0) {
                numComponents = 0;
            } else if (arguments.length == 1) {
                try {
                    numComponents = Integer.parseInt(arguments[0]);
                } catch (NumberFormatException nfe) {
                    LocalizableMessage message = ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER.get(arguments[0], getName(),
                            lineNumber);
                    throw new MakeLDIFException(message);
                }
            } else {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(getName(), lineNumber,
                        0, 1, arguments.length);
                throw new MakeLDIFException(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;
        }
    }
    /**
     * 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.
     */
    static class UnderscoreParentDNTag extends TemplateTag {
        /**
         * Creates a new instance of this underscore parent DN tag.
         */
        public UnderscoreParentDNTag() {
            // No implementation required.
        }
        /**
         * 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() {
            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 MakeLDIFException {
            if (arguments.length != 0) {
                LocalizableMessage message = ERR_MAKELDIF_TAG_INVALID_ARGUMENT_COUNT.get(getName(), lineNumber, 0,
                        arguments.length);
                throw new MakeLDIFException(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 parentDN = templateEntry.getParentDN();
            if ((parentDN == null) || parentDN.isRootDN()) {
                return TagResult.SUCCESS_RESULT;
            }
            templateValue.append(parentDN.rdn());
            for (int i = 1; i < parentDN.size(); i++) {
                templateValue.append("_");
                templateValue.append(parentDN.parent(i).rdn());
            }
            return TagResult.SUCCESS_RESULT;
        }
    }
}
opendj3/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
@@ -1312,9 +1312,6 @@
ERR_LDIF_ATTRIBUTE_NAME_MISMATCH=Unable to parse LDIF change record \
 starting at line %d with distinguished name "%s" because it contained a \
 an unexpected attribute "%s" when "%s" was expected
WARN_LDIF_MALFORMED_ATTRIBUTE_VALUE=Rejecting the LDIF change record \
 starting at line %d with distinguished name "%s" because it includes the \
 value "%s" for attribute "%s" which is invalid according to the syntax: %s
WARN_LDIF_DUPLICATE_ATTRIBUTE_VALUE=Rejecting the LDIF change record \
 starting at line %d with distinguished name "%s" because it includes a \
 duplicate attribute "%s" with value "%s"
@@ -1385,9 +1382,6 @@
WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_CLASS=The definition for \
 the object class "%s" declared a superior object class "%s" which has been \
 removed from the schema because it is invalid
WARN_ATTR_SYNTAX_DSR_INVALID_SUPERIOR_RULE=The definition for \
 the DIT structure rule "%s" declared a superior rule "%s" which has been \
 removed from the schema because it is invalid
ERR_CONNECTION_POOL_CLOSING=No connection could be obtained from connection \
 pool "%s" because it is closing
REJECTED_CHANGE_FAIL_ADD_DUPE=The entry "%s" could not be added because there \
@@ -1423,4 +1417,139 @@
HBCF_CONNECTION_CLOSED_BY_CLIENT=Connection closed by client
HBCF_HEARTBEAT_FAILED=Heartbeat failed
HBCF_HEARTBEAT_TIMEOUT=Heartbeat timed out after %d ms
ERR_MAKELDIF_TAG_INVALID_ARGUMENT_COUNT=Invalid number of arguments \
 provided for tag %s on line number %d of the template file:  expected %d, got \
 %d
ERR_MAKELDIF_TAG_INVALID_ARGUMENT_RANGE_COUNT=Invalid number of \
 arguments provided for tag %s on line number %d of the template file: \
 expected between %d and %d, got %d
ERR_MAKELDIF_TAG_UNDEFINED_ATTRIBUTE=Undefined attribute %s \
 referenced on line %d of the template file
ERR_MAKELDIF_TAG_INTEGER_BELOW_LOWER_BOUND=Value %d is below the \
 lowest allowed value of %d for tag %s on line %d of the template file
ERR_MAKELDIF_TAG_CANNOT_PARSE_AS_INTEGER=Cannot parse value "%s" as \
 an integer for tag %s on line %d of the template file
ERR_MAKELDIF_TAG_INTEGER_ABOVE_UPPER_BOUND=Value %d is above the \
 largest allowed value of %d for tag %s on line %d of the template file
ERR_MAKELDIF_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_MAKELDIF_UNDEFINED_BRANCH_SUBORDINATE=The branch with entry DN \
 '%s' references a subordinate template named '%s' which is not defined in the \
 template file
ERR_MAKELDIF_CANNOT_LOAD_TAG_CLASS=Unable to load class %s for use \
 as a MakeLDIF tag
ERR_MAKELDIF_CANNOT_INSTANTIATE_TAG=Cannot instantiate class %s as a \
 MakeLDIF tag
ERR_MAKELDIF_CONFLICTING_TAG_NAME=Cannot register the tag defined in \
 class %s because the tag name %s conflicts with the name of another tag that \
 has already been registered
WARN_MAKELDIF_WARNING_UNDEFINED_CONSTANT=Possible reference to an \
 undefined constant %s on line %d
ERR_MAKELDIF_DEFINE_MISSING_EQUALS=The constant definition on line \
 %d is missing an equal sign to delimit the constant name from the value
ERR_MAKELDIF_DEFINE_NAME_EMPTY=The constant definition on line %d \
 does not include a name for the constant
ERR_MAKELDIF_CONFLICTING_CONSTANT_NAME=The definition for constant \
 %s on line %d conflicts with an earlier constant definition included in the \
 template
ERR_MAKELDIF_WARNING_DEFINE_VALUE_EMPTY=Constant %s defined on line \
 %d has not been assigned a value
ERR_MAKELDIF_CONFLICTING_BRANCH_DN=The branch definition %s starting \
 on line %d conflicts with an earlier branch definition contained in the \
 template file
ERR_MAKELDIF_CONFLICTING_TEMPLATE_NAME=The template definition %s \
 starting on line %d conflicts with an earlier template definition contained \
 in the template file
ERR_MAKELDIF_UNEXPECTED_TEMPLATE_FILE_LINE=Unexpected template line \
 "%s" encountered on line %d of the template file
ERR_MAKELDIF_UNDEFINED_TEMPLATE_SUBORDINATE=The template named %s \
 references a subordinate template named %s which is not defined in the \
 template file
ERR_MAKELDIF_CANNOT_DECODE_BRANCH_DN=Unable to decode branch DN "%s" \
 on line %d of the template file
ERR_MAKELDIF_BRANCH_SUBORDINATE_TEMPLATE_NO_COLON=Subordinate \
 template definition on line %d for branch %s is missing a colon to separate \
 the template name from the number of entries
ERR_MAKELDIF_BRANCH_SUBORDINATE_INVALID_NUM_ENTRIES=Subordinate \
 template definition on line %d for branch %s specified invalid number of \
 entries %d for template %s
WARN_MAKELDIF_BRANCH_SUBORDINATE_ZERO_ENTRIES=Subordinate template \
 definition on line %d for branch %s specifies that zero entries of type %s \
 should be generated
ERR_MAKELDIF_BRANCH_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
ERR_MAKELDIF_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
ERR_MAKELDIF_TEMPLATE_SUBORDINATE_INVALID_NUM_ENTRIES=Subordinate \
 template definition on line %d for template %s specified invalid number of \
 entries %d for subordinate template %s
WARN_MAKELDIF_TEMPLATE_SUBORDINATE_ZERO_ENTRIES=Subordinate template \
 definition on line %d for template %s specifies that zero entries of type %s \
 should be generated
ERR_MAKELDIF_TEMPLATE_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 template %s
ERR_MAKELDIF_TEMPLATE_MISSING_RDN_ATTR=The template named %s \
 includes RDN attribute %s that is not assigned a value in that template
ERR_MAKELDIF_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_MAKELDIF_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_MAKELDIF_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_MAKELDIF_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
ERR_MAKELDIF_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
WARN_MAKELDIF_NO_VALUE_IN_TEMPLATE_LINE=The value pattern for line \
 %d of the template file in the definition for template %s is empty
ERR_MAKELDIF_NO_SUCH_TAG=An undefined tag %s is referenced on line \
 %d of the template file
ERR_MAKELDIF_CANNOT_INSTANTIATE_NEW_TAG=An unexpected error occurred \
 while trying to create a new instance of tag %s referenced on line %d of the \
 template file:  %s
ERR_MAKELDIF_TAG_INVALID_FORMAT_STRING=Cannot parse value "%s" as an \
 valid format string for tag %s on line %d of the template file
ERR_MAKELDIF_TAG_NO_RANDOM_TYPE_ARGUMENT=The random tag on line %d \
 of the template file does not include an argument to specify the type of \
 random value that should be generated
WARN_MAKELDIF_TAG_WARNING_EMPTY_VALUE=The value generated from the \
 random tag on line %d of the template file will always be an empty string
ERR_MAKELDIF_TAG_UNKNOWN_RANDOM_TYPE=The random tag on line %d of \
 the template file references an unknown random type of %s
ERR_MAKELDIF_COULD_NOT_FIND_TEMPLATE_FILE=Could not find template \
 file %s
ERR_MAKELDIF_TAG_CANNOT_FIND_FILE=Cannot find file %s referenced by \
 tag %s on line %d of the template file
ERR_MAKELDIF_TAG_INVALID_FILE_ACCESS_MODE=Invalid file access mode \
 %s for tag %s on line %d of the template file.  It must be either \
 "sequential" or "random"
ERR_MAKELDIF_TAG_CANNOT_READ_FILE=An error occurred while trying to \
 read file %s referenced by tag %s on line %d of the template file:  %s
ERR_MAKELDIF_INCOMPLETE_TAG=Line %d of the template file contains an \
 incomplete tag that starts with either '<' or '{' but does get closed
ERR_MAKELDIF_TAG_NOT_ALLOWED_IN_BRANCH=Tag %s referenced on line %d \
 of the template file is not allowed for use in branch definitions
ERR_MAKELDIF_TEMPLATE_INVALID_PARENT_TEMPLATE=The parent template %s \
 referenced on line %d for template %s is invalid because the referenced \
 parent template is not defined before the template that extends it
ERR_MAKELDIF_TAG_LIST_NO_ARGUMENTS=The list tag on line %d of the \
 template file does not contain any arguments to specify the list values.  At \
 least one list value must be provided
WARN_MAKELDIF_TAG_LIST_INVALID_WEIGHT=The list tag on line %d of \
 the template file contains item '%s' that includes a semicolon but that \
 semicolon is not followed by an integer.  The semicolon will be assumed to be \
 part of the value and not a delimiter to separate the value from its relative \
 weight
ERR_MAKELDIF_IOEXCEPTION_DURING_PARSE=An error occurred while \
 attempting to read the template file:  %s
ERR_MAKELDIF_EXCEPTION_DURING_PARSE=An error occurred while \
 attempting to parse the template file:  %s
ERR_MAKELDIF_MISSING_TEMPLATE_FILE=Unexpected error when initializing config : no template file provided as input
opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldap/TestCaseUtils.java
@@ -76,6 +76,25 @@
    }
    /**
     * Return the canonical file path for a test file.
     * <p>
     * For example, the path to file "src/test/resources/somedir/somefile" is
     * obtained with <code>getTestFilePath("somedir/somefile")</code>.
     *
     * @param relativePathFromClasspath
     *            the relative path to any directory that is declared
     *            in the classpath (typically the src/test/resources
     *            directory)
     * @return the canonical path
     * @throws Exception
     *             if file is not found or can't be read
     */
    public static String getTestFilePath(String relativePathFromClasspath) throws Exception {
        return new File(TestCaseUtils.class.getClassLoader().getResource(relativePathFromClasspath).toURI())
                .getCanonicalPath();
    }
    /**
     * Finds a free server socket port on the local host.
     *
     * @return The free port.
opendj3/opendj-core/src/test/java/org/forgerock/opendj/ldif/MakeLDIFEntryReaderTestCase.java
New file
@@ -0,0 +1,402 @@
/*
 * 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 com.forgerock.opendj.ldap.CoreMessages.*;
import static org.fest.assertions.Assertions.*;
import static org.forgerock.opendj.ldap.TestCaseUtils.getTestFilePath;
import static org.forgerock.opendj.ldif.MakeLDIFEntryReader.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.SdkTestCase;
import org.forgerock.opendj.ldap.schema.Schema;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@SuppressWarnings("javadoc")
public class MakeLDIFEntryReaderTestCase extends SdkTestCase {
    private static final String TEMPLATE_FILE_PATH = "MakeLDIF/example.template";
    private String resourcePath;
    private Schema schema;
    @BeforeClass
    public void setUp() throws Exception {
        resourcePath = getTestFilePath("MakeLDIF");
        schema = Schema.getDefaultSchema();
    }
    @Test
    public void testReaderWithTemplateFile() throws Exception {
        String templatePath = getTestFilePath(TEMPLATE_FILE_PATH);
        MakeLDIFEntryReader reader = newReader(templatePath).setResourcePath(resourcePath).build();
        checkReader(reader);
        reader.close();
    }
    @Test
    public void testReaderWithTemplateStream() throws Exception {
        InputStream stream = new FileInputStream(
                new File(getTestFilePath(TEMPLATE_FILE_PATH)));
        MakeLDIFEntryReader reader = newReader(stream).setResourcePath(resourcePath).build();
        checkReader(reader);
        reader.close();
    }
    @Test
    public void testReaderWithTemplateLines() throws Exception {
        MakeLDIFEntryReader reader = newReader(
                "define suffix=dc=example,dc=com",
                "define maildomain=example.com",
                "define numusers=2",
                "",
                "branch: [suffix]",
                "",
                "branch: ou=People,[suffix]",
                "subordinateTemplate: person:[numusers]",
                "",
                "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}.")
                .setResourcePath(resourcePath).build();
        checkReader(reader);
        reader.close();
    }
    /**
     * Check the content of the reader for basic case.
     * Expecting 4 entries with 2 users.
     */
    private void checkReader(MakeLDIFEntryReader reader) 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");
        assertThat(reader.hasNext()).isTrue();
        assertThat(reader.readEntry().getName().toString()).isEqualTo("uid=user.0,ou=People,dc=example,dc=com");
        assertThat(reader.hasNext()).isTrue();
        assertThat(reader.readEntry().getName().toString()).isEqualTo("uid=user.1,ou=People,dc=example,dc=com");
        assertThat(reader.hasNext()).as("should have no more entries").isFalse();
    }
    @Test(expectedExceptions = MakeLDIFException.class,
            expectedExceptionsMessageRegExp = ".*Could not find template file unknown.*")
    public void testMissingTemplateFile() throws Exception {
        newReader("unknown").setResourcePath(resourcePath).build();
    }
    @Test(expectedExceptions = MakeLDIFException.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 to show that reporting an error about an uninitialized variable when
     * generating templates reports the correct line.
     */
    @Test()
    public void testParseFileTemplate() throws Exception {
        String[] lines = {
        /* 0 */"template: template",
        /* 1 */"a: {missingVar}",
        /* 2 */"a: b",
        /* 3 */"a: c",
        /* 4 */"",
        /* 5 */"template: template2", };
        // Test must show "missingVar" missing on line 1.
        // Previous behaviour showed "missingVar" on line 5.
        TemplateFile templateFile = new TemplateFile(schema, resourcePath);
        List<LocalizableMessage> warns = new ArrayList<LocalizableMessage>();
        try {
            templateFile.parse(lines, warns);
            failWasExpected(MakeLDIFException.class);
        } catch (MakeLDIFException e) {
            LocalizableMessage expected = ERR_MAKELDIF_TAG_UNDEFINED_ATTRIBUTE.get("missingVar", 1);
            assertThat(e.getMessage()).isEqualTo(expected.toString());
        }
    }
    @DataProvider(name = "validTemplates")
    public Object[][] createTestTemplates() {
        return new Object[][] {
            { "CurlyBracket",
              new String[] { "template: templateWithEscape", "rdnAttr: uid", "uid: testEntry",
                  "cn: I\\{Foo\\}F" }
            },
            { "AngleBracket",
              new String[] { "template: templateWithEscape", "rdnAttr: uid", "uid: testEntry",
                  "sn: \\<Bar\\>" }
            },
            { "SquareBracket",
              new String[] { "template: templateWithEscape", "rdnAttr: uid", "uid: testEntry",
                  "description: \\[TEST\\]" } },
            { "BackSlash",
              new String[] { "template: templateWithEscape", "rdnAttr: uid", "uid: testEntry",
                  "description: Foo \\\\ Bar" } },
            { "EscapedAlpha",
              new String[] { "template: templateWithEscape", "rdnAttr: uid", "uid: testEntry",
                  "description: Foo \\\\Bar" } },
            { "Normal Variable",
              new String[] { "template: templateNormal", "rdnAttr: uid", "uid: testEntry", "sn: {uid}" } },
            { "Constant",
              new String[] { "define foo=Test123", "", "template: templateConstant", "rdnAttr: uid",
                  "uid: testEntry", "sn: {uid}", "cn: [foo]" }
            },
        };
    }
    /**
     * Test for parsing escaped character in templates
     */
    @Test(dataProvider = "validTemplates")
    public void testParsingEscapeCharInTemplate(String testName, String[] lines) throws Exception {
        TemplateFile templateFile = new TemplateFile(schema, resourcePath);
        List<LocalizableMessage> warns = new ArrayList<LocalizableMessage>();
        templateFile.parse(lines, warns);
        assertThat(warns).isEmpty();
    }
    @DataProvider(name = "templatesToTestEscapeChars")
    public Object[][] createTemplatesToTestSpecialChars() {
        return new Object[][] {
            {
                "Curly",
                new String[] {
                    "branch: dc=test", "subordinateTemplate: templateWithEscape:1",
                    "",
                    "template: templateWithEscape",
                    "rdnAttr: uid",
                    "objectclass: inetOrgPerson",
                    "uid: testEntry",
                    "cn: I\\{ Foo \\}F" },
                "cn", // Attribute to test
                "I{ Foo }F", // Expected value
            },
            {
                "Angle",
                new String[] {
                    "branch: dc=test",
                    "subordinateTemplate: templateWithEscape:1",
                    "",
                    "template: templateWithEscape",
                    "rdnAttr: uid",
                    "objectclass: inetOrgPerson",
                    "uid: testEntry",
                    "sn: \\< Bar \\>" },
                "sn", // Attribute to test
                "< Bar >", // Expected value
            },
            {
                "Square",
                new String[] {
                    "branch: dc=test",
                    "subordinateTemplate: templateWithEscape:1",
                    "",
                    "template: templateWithEscape",
                    "rdnAttr: uid",
                    "objectclass: inetOrgPerson",
                    "uid: testEntry",
                    "description: \\[TEST\\]" },
                "description", // Attribute to test
                "[TEST]", // Expected value
            },
            {
                "BackSlash",
                new String[] {
                    "branch: dc=test",
                    "subordinateTemplate: templateWithEscape:1",
                    "",
                    "template: templateWithEscape",
                    "rdnAttr: uid",
                    "objectclass: inetOrgPerson",
                    "uid: testEntry",
                    "displayName: Foo \\\\ Bar" },
                "displayname", // Attribute to test
                "Foo \\ Bar", // Expected value
            },
            {
                "MultipleSquare",
                new String[] {
                    "define top=dc=com",
                    "define container=ou=group",
                    "",
                    "branch: dc=test,[top]",
                    "subordinateTemplate: templateWithEscape:1",
                    "",
                    "template: templateWithEscape",
                    "rdnAttr: uid",
                    "objectclass: inetOrgPerson",
                    "uid: testEntry",
                    "manager: cn=Bar,[container],dc=test,[top]",
                    "" },
                "manager", // Attribute to test
                "cn=Bar,ou=group,dc=test,dc=com", // Expected value
            },
            {
                "MixedSquare",
                new String[] {
                    "define top=dc=com",
                    "define container=ou=group",
                    "", "branch: dc=test,[top]",
                    "subordinateTemplate: templateWithEscape:1",
                    "",
                    "template: templateWithEscape",
                    "rdnAttr: uid",
                    "objectclass: inetOrgPerson",
                    "uid: testEntry",
                    "description: test [container] \\[[top]\\]",
                    "", },
                "description", // Attribute to test
                "test ou=group [dc=com]", // Expected value
            },
            {
                "NoConstantBecauseEscaped",
                new String[] {
                    "define top=dc=com",
                    "define container=ou=group",
                    "", "branch: dc=test,[top]",
                    "subordinateTemplate: templateWithEscape:1",
                    "",
                    "template: templateWithEscape",
                    "rdnAttr: uid",
                    "objectclass: inetOrgPerson",
                    "uid: testEntry",
                    "description: test \\[top]",
                    "", },
                "description", // Attribute to test
                "test [top]", // Expected value
            },
            {
                "NoConstantBecauseStrangeChar",
                new String[] {
                    "define top=dc=com",
                    "define container=ou=group",
                    "",
                    "branch: dc=test,[top]",
                    "subordinateTemplate: templateWithEscape:1",
                    "",
                    "template: templateWithEscape",
                    "rdnAttr: uid",
                    "objectclass: inetOrgPerson",
                    "uid: testEntry",
                    "description: test [group \\[top]",
                    "", },
                "description", // Attribute to test
                "test [group [top]", // Expected value
            },
        /*
         * If adding a test, please copy and reuse template code down below {
         * "", new String[]{ "template: templateWithEscape", "rdnAttr: uid",
         * "uid: testEntry", "cn: I\\{Foo\\}F"}, "", // Attribute to test "", //
         * Expected value }
         */
        };
    }
    /**
     * Test for escaped characters in templates
     */
    @Test(dataProvider = "templatesToTestEscapeChars", dependsOnMethods = { "testParsingEscapeCharInTemplate" })
    public void testEscapeCharsFromTemplate(String testName, String[] lines, String attrName, String expectedValue)
            throws Exception {
        MakeLDIFEntryReader 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(attrName).firstValueAsString()).isEqualTo(expectedValue);
    }
    /**
     * Test template that combines escaped characters and variables
     */
    @Test(dependsOnMethods = { "testParsingEscapeCharInTemplate" })
    public void testCombineEscapeCharInTemplate() throws Exception {
        String[] lines = {
            "branch: dc=test",
            "subordinateTemplate: templateWithEscape:1",
            "",
            "template: templateWithEscape",
            "rdnAttr: uid",
            "objectclass: inetOrgPerson",
            "uid: testEntry",
            "sn: Bar",
            // 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}", "", };
        MakeLDIFEntryReader 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");
    }
}
opendj3/opendj-core/src/test/resources/MakeLDIF/cities
New file
@@ -0,0 +1,232 @@
Abilene
Albany
Albuquerque
Alexandria
Alpena
Altoona
Amarillo
Anchorage
Anniston
Ardmore
Atlanta
Augusta
Austin
Bakersfield
Baltimore
Bangor
Baton Rouge
Beaumont
Bend
Billings
Biloxi
Binghamton
Birmingham
Bismarck
Bloomington
Bluefield
Boise
Boston
Bowling Green
Bryan
Buffalo
Burlington
Butte
Cadillac
Casper
Cedar Rapids
Champaign
Charleston
Charlotte
Charlottesville
Chattanooga
Cheyenne
Chicago
Chico
Cincinnati
Clarksburg
Cleveland
College Station
Colorado Springs
Columbia
Columbus
Corpus Christi
Dallas
Davenport
Dayton
Denver
Des Moines
Detroit
Dothan
Duluth
Durham
Eau Claire
Elmira
El Paso
Erie
Eugene
Eureka
Evansville
Fairbanks
Fargo
Flint
Florence
Fort Myers
Fort Smith
Fort Wayne
Fort Worth
Fresno
Gainesville
Glendive
Grand Junction
Grand Rapids
Great Falls
Green Bay
Greenville
Hampton Roads
Harlingen
Harrisburg
Harrisonburg
Hartford
Hattiesburg
Helena
Honolulu
Houston
Huntington
Huntsville
Idaho Falls
Indianapolis
Jackson
Jacksonville
Jefferson City
Johnstown
Jonesboro
Joplin
Kansas City
Kirksville
Klamath Falls
Knoxville
La Crosse
Lafayette
Lake Charles
Lansing
Laredo
Las Vegas
Lawton
Lexington
Lima
Lincoln
Little Rock
Los Angeles
Louisville
Lubbock
Lynchburg
Macon
Madison
Mankato
Marquette
Mason City
Medford
Memphis
Meridian
Miami
Milwaukee
Minneapolis
Missoula
Mobile
Moline
Monroe
Monterey Bay Area
Montgomery
Naples
Nashville
New Haven
New Orleans
New York
North Platte
Odessa
Oklahoma City
Omaha
Orlando
Ottumwa
Paducah
Palm Springs
Panama City
Parkersburg
Pensacola
Peoria
Philadelphia
Phoenix
Pittsburgh
Pocatello
Port Arthur
Portland
Presque Isle
Providence
Pueblo
Quincy
Raleigh
Rapid City
Redding
Reno
Rhinelander
Richmond
Riverton
Roanoke
Rochester
Rockford
Sacramento
Saginaw
Saint Joseph
Saint Louis
Saint Paul
Salem
Salisbury
Salt Lake City
San Angelo
San Antonio
San Diego
Santa Barbara
Santa Fe
Savannah
Scranton
Seattle
Shreveport
Sioux City
Sioux Falls
South Bend
Spartanburg
Spokane
Springfield
Steubenville
Superior
Syracuse
Tallahassee
Tampa Bay
Terre Haute
Toledo
Topeka
Traverse City
Tucson
Tulsa
Tupelo
Tuscaloosa
Twin Falls
Tyler
Urbana
Utica
Victoria
Waterloo
Watertown
Wausau
Weston
West Palm Beach
Wheeling
Wichita
Wichita Falls
Wichita FallsLawton
Wilkes Barre
Wilmington
Winston
Youngstown
Yuma
Zanesville
opendj3/opendj-core/src/test/resources/MakeLDIF/example.template
New file
@@ -0,0 +1,34 @@
define suffix=dc=example,dc=com
define maildomain=example.com
define numusers=2
branch: [suffix]
branch: ou=People,[suffix]
subordinateTemplate: person:[numusers]
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}.
opendj3/opendj-core/src/test/resources/MakeLDIF/first.names
New file
Diff too large
opendj3/opendj-core/src/test/resources/MakeLDIF/last.names
New file
Diff too large
opendj3/opendj-core/src/test/resources/MakeLDIF/states
New file
@@ -0,0 +1,51 @@
AL
AK
AZ
AR
CA
CO
CT
DE
DC
FL
GA
HI
ID
IL
IN
IA
KS
KY
LA
ME
MD
MA
MI
MN
MS
MO
MT
NE
NV
NH
NJ
NM
NY
NC
ND
OH
OK
OR
PA
RI
SC
SD
TN
TX
UT
VT
VA
WA
WV
WI
WY
opendj3/opendj-core/src/test/resources/MakeLDIF/streets
New file
@@ -0,0 +1,74 @@
Adams
Ash
Birch
Broadway
Cedar
Center
Central
Cherry
Chestnut
Church
College
Davis
Dogwood
East
Eighth
Eleventh
Elm
Fifteenth
Fifth
First
Forest
Fourteenth
Fourth
Franklin
Green
Hickory
Highland
Hill
Hillcrest
Jackson
Jefferson
Johnson
Lake
Lakeview
Laurel
Lee
Lincoln
Locust
Madison
Main
Maple
Meadow
Mill
Miller
Ninth
North
Oak
Park
Pine
Poplar
Railroad
Ridge
River
Second
Seventh
Sixth
South
Spring
Spruce
Sunset
Sycamore
Taylor
Tenth
Third
Thirteenth
Twelfth
Valley
Walnut
Washington
West
Williams
Willow
Wilson
Woodland