001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS.
026 */
027package org.opends.server.tools.makeldif;
028
029import org.forgerock.i18n.LocalizableMessage;
030
031import java.io.BufferedReader;
032import java.io.File;
033import java.io.FileReader;
034import java.io.InputStream;
035import java.io.InputStreamReader;
036import java.io.IOException;
037import java.util.ArrayList;
038import java.util.HashMap;
039import java.util.LinkedHashMap;
040import java.util.List;
041import java.util.Map;
042import java.util.Random;
043import java.util.StringTokenizer;
044
045import org.opends.server.core.DirectoryServer;
046import org.opends.server.types.AttributeType;
047import org.opends.server.types.DN;
048import org.opends.server.types.InitializationException;
049
050import static org.opends.messages.ToolMessages.*;
051import static org.opends.server.util.StaticUtils.*;
052
053/**
054 * This class defines a template file, which is a collection of constant
055 * definitions, branches, and templates.
056 */
057public class TemplateFile
058{
059  /** The name of the file holding the list of first names. */
060  public static final String FIRST_NAME_FILE = "first.names";
061  /** The name of the file holding the list of last names. */
062  public static final String LAST_NAME_FILE = "last.names";
063
064
065  /**
066   * A map of the contents of various text files used during the parsing
067   * process, mapped from absolute path to the array of lines in the file.
068   */
069  private final HashMap<String, String[]> fileLines = new HashMap<>();
070
071  /** The index of the next first name value that should be used. */
072  private int firstNameIndex;
073  /** The index of the next last name value that should be used. */
074  private int lastNameIndex;
075
076  /**
077   * A counter used to keep track of the number of times that the larger of the
078   * first/last name list has been completed.
079   */
080  private int nameLoopCounter;
081  /**
082   * A counter that will be used in case we have exhausted all possible first
083   * and last name combinations.
084   */
085  private int nameUniquenessCounter;
086
087  /** The set of branch definitions for this template file. */
088  private final LinkedHashMap<DN, Branch> branches = new LinkedHashMap<>();
089  /** The set of constant definitions for this template file. */
090  private final LinkedHashMap<String, String> constants = new LinkedHashMap<>();
091  /** The set of registered tags for this template file. */
092  private final LinkedHashMap<String, Tag> registeredTags = new LinkedHashMap<>();
093  /** The set of template definitions for this template file. */
094  private final LinkedHashMap<String, Template> templates = new LinkedHashMap<>();
095
096  /** The random number generator for this template file. */
097  private Random random;
098
099  /** The next first name that should be used. */
100  private String firstName;
101  /** The next last name that should be used. */
102  private String lastName;
103
104  /**
105   * The resource path to use for filesystem elements that cannot be found
106   * anywhere else.
107   */
108  private String resourcePath;
109  /** The path to the directory containing the template file, if available. */
110  private String templatePath;
111
112  /** The set of first names to use when generating the LDIF. */
113  private String[] firstNames;
114  /** The set of last names to use when generating the LDIF. */
115  private String[] lastNames;
116
117
118
119  /**
120   * Creates a new, empty template file structure.
121   *
122   * @param  resourcePath  The path to the directory that may contain additional
123   *                       resource files needed during the LDIF generation
124   *                       process.
125   */
126  public TemplateFile(String resourcePath)
127  {
128    this(resourcePath, new Random());
129  }
130
131
132
133  /**
134   * Creates a new, empty template file structure.
135   *
136   *
137   * @param  resourcePath  The path to the directory that may contain additional
138   *                       resource files needed during the LDIF generation
139   *                       process.
140   * @param  random        The random number generator for this template file.
141   */
142  public TemplateFile(String resourcePath, Random random)
143  {
144    this.resourcePath = resourcePath;
145    this.random       = random;
146
147    firstNames            = new String[0];
148    lastNames             = new String[0];
149    nameUniquenessCounter = 1;
150
151    registerDefaultTags();
152
153    try
154    {
155      readNameFiles();
156    }
157    catch (IOException ioe)
158    {
159      // FIXME -- What to do here?
160      ioe.printStackTrace();
161      firstNames = new String[] { "John" };
162      lastNames  = new String[] { "Doe" };
163    }
164  }
165
166
167
168  /**
169   * Retrieves the set of tags that have been registered.  They will be in the
170   * form of a mapping between the name of the tag (in all lowercase characters)
171   * and the corresponding tag implementation.
172   *
173   * @return  The set of tags that have been registered.
174   */
175  public Map<String,Tag> getTags()
176  {
177    return registeredTags;
178  }
179
180
181
182  /**
183   * Retrieves the tag with the specified name.
184   *
185   * @param  lowerName  The name of the tag to retrieve, in all lowercase
186   *                    characters.
187   *
188   * @return  The requested tag, or <CODE>null</CODE> if no such tag has been
189   *          registered.
190   */
191  public Tag getTag(String lowerName)
192  {
193    return registeredTags.get(lowerName);
194  }
195
196
197
198  /**
199   * Registers the specified class as a tag that may be used in templates.
200   *
201   * @param  tagClass  The fully-qualified name of the class to register as a
202   *                   tag.
203   *
204   * @throws  MakeLDIFException  If a problem occurs while attempting to
205   *                             register the specified tag.
206   */
207  public void registerTag(String tagClass)
208         throws MakeLDIFException
209  {
210    Class c;
211    try
212    {
213      c = Class.forName(tagClass);
214    }
215    catch (Exception e)
216    {
217      LocalizableMessage message = ERR_MAKELDIF_CANNOT_LOAD_TAG_CLASS.get(tagClass);
218      throw new MakeLDIFException(message, e);
219    }
220
221    Tag t;
222    try
223    {
224      t = (Tag) c.newInstance();
225    }
226    catch (Exception e)
227    {
228      LocalizableMessage message = ERR_MAKELDIF_CANNOT_INSTANTIATE_TAG.get(tagClass);
229      throw new MakeLDIFException(message, e);
230    }
231
232    String lowerName = toLowerCase(t.getName());
233    if (registeredTags.containsKey(lowerName))
234    {
235      LocalizableMessage message =
236          ERR_MAKELDIF_CONFLICTING_TAG_NAME.get(tagClass, t.getName());
237      throw new MakeLDIFException(message);
238    }
239    else
240    {
241      registeredTags.put(lowerName, t);
242    }
243  }
244
245
246
247  /**
248   * Registers the set of tags that will always be available for use in
249   * templates.
250   */
251  private void registerDefaultTags()
252  {
253    Class[] defaultTagClasses = new Class[]
254    {
255      AttributeValueTag.class,
256      DNTag.class,
257      FileTag.class,
258      FirstNameTag.class,
259      GUIDTag.class,
260      IfAbsentTag.class,
261      IfPresentTag.class,
262      LastNameTag.class,
263      ListTag.class,
264      ParentDNTag.class,
265      PresenceTag.class,
266      RandomTag.class,
267      RDNTag.class,
268      SequentialTag.class,
269      StaticTextTag.class,
270      UnderscoreDNTag.class,
271      UnderscoreParentDNTag.class
272    };
273
274    for (Class c : defaultTagClasses)
275    {
276      try
277      {
278        Tag t = (Tag) c.newInstance();
279        registeredTags.put(toLowerCase(t.getName()), t);
280      }
281      catch (Exception e)
282      {
283        // This should never happen.
284        e.printStackTrace();
285      }
286    }
287  }
288
289
290
291  /**
292   * Retrieves the set of constants defined for this template file.
293   *
294   * @return  The set of constants defined for this template file.
295   */
296  public Map<String,String> getConstants()
297  {
298    return constants;
299  }
300
301
302
303  /**
304   * Retrieves the value of the constant with the specified name.
305   *
306   * @param  lowerName  The name of the constant to retrieve, in all lowercase
307   *                    characters.
308   *
309   * @return  The value of the constant with the specified name, or
310   *          <CODE>null</CODE> if there is no such constant.
311   */
312  public String getConstant(String lowerName)
313  {
314    return constants.get(lowerName);
315  }
316
317
318
319  /**
320   * Registers the provided constant for use in the template.
321   *
322   * @param  name   The name for the constant.
323   * @param  value  The value for the constant.
324   */
325  public void registerConstant(String name, String value)
326  {
327    constants.put(toLowerCase(name), value);
328  }
329
330
331
332  /**
333   * Retrieves the set of branches defined in this template file.
334   *
335   * @return  The set of branches defined in this template file.
336   */
337  public Map<DN,Branch> getBranches()
338  {
339    return branches;
340  }
341
342
343
344  /**
345   * Retrieves the branch registered with the specified DN.
346   *
347   * @param  branchDN  The DN for which to retrieve the corresponding branch.
348   *
349   * @return  The requested branch, or <CODE>null</CODE> if no such branch has
350   *          been registered.
351   */
352  public Branch getBranch(DN branchDN)
353  {
354    return branches.get(branchDN);
355  }
356
357
358
359  /**
360   * Registers the provided branch in this template file.
361   *
362   * @param  branch  The branch to be registered.
363   */
364  public void registerBranch(Branch branch)
365  {
366    branches.put(branch.getBranchDN(), branch);
367  }
368
369
370
371  /**
372   * Retrieves the set of templates defined in this template file.
373   *
374   * @return  The set of templates defined in this template file.
375   */
376  public Map<String,Template> getTemplates()
377  {
378    return templates;
379  }
380
381
382
383  /**
384   * Retrieves the template with the specified name.
385   *
386   * @param  lowerName  The name of the template to retrieve, in all lowercase
387   *                    characters.
388   *
389   * @return  The requested template, or <CODE>null</CODE> if there is no such
390   *          template.
391   */
392  public Template getTemplate(String lowerName)
393  {
394    return templates.get(lowerName);
395  }
396
397
398
399  /**
400   * Registers the provided template for use in this template file.
401   *
402   * @param  template  The template to be registered.
403   */
404  public void registerTemplate(Template template)
405  {
406    templates.put(toLowerCase(template.getName()), template);
407  }
408
409
410
411  /**
412   * Retrieves the random number generator for this template file.
413   *
414   * @return  The random number generator for this template file.
415   */
416  public Random getRandom()
417  {
418    return random;
419  }
420
421
422
423  /**
424   * Reads the contents of the first and last name files into the appropriate
425   * arrays and sets up the associated index pointers.
426   *
427   * @throws  IOException  If a problem occurs while reading either of the
428   *                       files.
429   */
430  private void readNameFiles()
431          throws IOException
432  {
433    File f = getFile(FIRST_NAME_FILE);
434    List<String> nameList = readLines(f);
435    firstNames = new String[nameList.size()];
436    nameList.toArray(firstNames);
437
438    f = getFile(LAST_NAME_FILE);
439    nameList = readLines(f);
440    lastNames = new String[nameList.size()];
441    nameList.toArray(lastNames);
442  }
443
444  private List<String> readLines(File f) throws IOException
445  {
446    try (BufferedReader reader = new BufferedReader(new FileReader(f)))
447    {
448      ArrayList<String> lines = new ArrayList<>();
449      while (true)
450      {
451        String line = reader.readLine();
452        if (line == null)
453        {
454          break;
455        }
456        lines.add(line);
457      }
458      return lines;
459    }
460  }
461
462
463
464  /**
465   * Updates the first and last name indexes to choose new values.  The
466   * algorithm used is designed to ensure that the combination of first and last
467   * names will never be repeated.  It depends on the number of first names and
468   * the number of last names being relatively prime.  This method should be
469   * called before beginning generation of each template entry.
470   */
471  public void nextFirstAndLastNames()
472  {
473    firstName = firstNames[firstNameIndex++];
474    lastName  = lastNames[lastNameIndex++];
475
476
477    // If we've already exhausted every possible combination, then append an
478    // integer to the last name.
479    if (nameUniquenessCounter > 1)
480    {
481      lastName += nameUniquenessCounter;
482    }
483
484    if (firstNameIndex >= firstNames.length)
485    {
486      // We're at the end of the first name list, so start over.  If the first
487      // name list is larger than the last name list, then we'll also need to
488      // set the last name index to the next loop counter position.
489      firstNameIndex = 0;
490      if (firstNames.length > lastNames.length)
491      {
492        lastNameIndex = ++nameLoopCounter;
493        if (lastNameIndex >= lastNames.length)
494        {
495          lastNameIndex = 0;
496          nameUniquenessCounter++;
497        }
498      }
499    }
500
501    if (lastNameIndex >= lastNames.length)
502    {
503      // We're at the end of the last name list, so start over.  If the last
504      // name list is larger than the first name list, then we'll also need to
505      // set the first name index to the next loop counter position.
506      lastNameIndex = 0;
507      if (lastNames.length > firstNames.length)
508      {
509        firstNameIndex = ++nameLoopCounter;
510        if (firstNameIndex >= firstNames.length)
511        {
512          firstNameIndex = 0;
513          nameUniquenessCounter++;
514        }
515      }
516    }
517  }
518
519
520
521  /**
522   * Retrieves the first name value that should be used for the current entry.
523   *
524   * @return  The first name value that should be used for the current entry.
525   */
526  public String getFirstName()
527  {
528    return firstName;
529  }
530
531
532
533  /**
534   * Retrieves the last name value that should be used for the current entry.
535   *
536   * @return  The last name value that should be used for the current entry.
537   */
538  public String getLastName()
539  {
540    return lastName;
541  }
542
543
544
545  /**
546   * Parses the contents of the specified file as a MakeLDIF template file
547   * definition.
548   *
549   * @param  filename  The name of the file containing the template data.
550   * @param  warnings  A list into which any warnings identified may be placed.
551   *
552   * @throws  IOException  If a problem occurs while attempting to read data
553   *                       from the specified file.
554   *
555   * @throws  InitializationException  If a problem occurs while initializing
556   *                                   any of the MakeLDIF components.
557   *
558   * @throws  MakeLDIFException  If any other problem occurs while parsing the
559   *                             template file.
560   */
561  public void parse(String filename, List<LocalizableMessage> warnings)
562         throws IOException, InitializationException, MakeLDIFException
563  {
564    templatePath = null;
565    File f = getFile(filename);
566    if (f == null || !f.exists())
567    {
568      LocalizableMessage message = ERR_MAKELDIF_COULD_NOT_FIND_TEMPLATE_FILE.get(filename);
569      throw new IOException(message.toString());
570    }
571    templatePath = f.getParentFile().getAbsolutePath();
572
573    List<String> fileLines = readLines(f);
574    String[] lines = new String[fileLines.size()];
575    fileLines.toArray(lines);
576    parse(lines, warnings);
577  }
578
579
580
581  /**
582   * Parses the data read from the provided input stream as a MakeLDIF template
583   * file definition.
584   *
585   * @param  inputStream  The input stream from which to read the template file
586   *                      data.
587   * @param  warnings     A list into which any warnings identified may be
588   *                      placed.
589   *
590   * @throws  IOException  If a problem occurs while attempting to read data
591   *                       from the provided input stream.
592   *
593   * @throws  InitializationException  If a problem occurs while initializing
594   *                                   any of the MakeLDIF components.
595   *
596   * @throws  MakeLDIFException  If any other problem occurs while parsing the
597   *                             template file.
598   */
599  public void parse(InputStream inputStream, List<LocalizableMessage> warnings)
600         throws IOException, InitializationException, MakeLDIFException
601  {
602    ArrayList<String> fileLines = new ArrayList<>();
603
604    try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)))
605    {
606      while (true)
607      {
608        String line = reader.readLine();
609        if (line == null)
610        {
611          break;
612        }
613        fileLines.add(line);
614      }
615    }
616
617    String[] lines = new String[fileLines.size()];
618    fileLines.toArray(lines);
619    parse(lines, warnings);
620  }
621
622
623
624  /**
625   * Parses the provided data as a MakeLDIF template file definition.
626   *
627   * @param  lines  The lines that make up the template file.
628   * @param  warnings  A list into which any warnings identified may be placed.
629   *
630   * @throws  InitializationException  If a problem occurs while initializing
631   *                                   any of the MakeLDIF components.
632   *
633   * @throws  MakeLDIFException  If any other problem occurs while parsing the
634   *                             template file.
635   */
636  public void parse(String[] lines, List<LocalizableMessage> warnings)
637         throws InitializationException, MakeLDIFException
638  {
639    // Create temporary variables that will be used to hold the data read.
640    LinkedHashMap<String,Tag> templateFileIncludeTags = new LinkedHashMap<>();
641    LinkedHashMap<String,String> templateFileConstants = new LinkedHashMap<>();
642    LinkedHashMap<DN,Branch> templateFileBranches = new LinkedHashMap<>();
643    LinkedHashMap<String,Template> templateFileTemplates = new LinkedHashMap<>();
644
645    for (int lineNumber=0; lineNumber < lines.length; lineNumber++)
646    {
647      String line = lines[lineNumber];
648
649      line = replaceConstants(line, lineNumber,
650                              templateFileConstants, warnings);
651
652      String lowerLine = toLowerCase(line);
653      if (line.length() == 0 || line.startsWith("#"))
654      {
655        // This is a comment or a blank line, so we'll ignore it.
656        continue;
657      }
658      else if (lowerLine.startsWith("include "))
659      {
660        // This should be an include definition.  The next element should be the
661        // name of the class.  Load and instantiate it and make sure there are
662        // no conflicts.
663        String className = line.substring(8).trim();
664
665        Class tagClass;
666        try
667        {
668          tagClass = Class.forName(className);
669        }
670        catch (Exception e)
671        {
672          LocalizableMessage message = ERR_MAKELDIF_CANNOT_LOAD_TAG_CLASS.get(className);
673          throw new MakeLDIFException(message, e);
674        }
675
676        Tag tag;
677        try
678        {
679          tag = (Tag) tagClass.newInstance();
680        }
681        catch (Exception e)
682        {
683          LocalizableMessage message = ERR_MAKELDIF_CANNOT_INSTANTIATE_TAG.get(className);
684          throw new MakeLDIFException(message, e);
685        }
686
687        String lowerName = toLowerCase(tag.getName());
688        if (registeredTags.containsKey(lowerName) ||
689            templateFileIncludeTags.containsKey(lowerName))
690        {
691          LocalizableMessage message =
692              ERR_MAKELDIF_CONFLICTING_TAG_NAME.get(className, tag.getName());
693          throw new MakeLDIFException(message);
694        }
695
696        templateFileIncludeTags.put(lowerName, tag);
697      }
698      else if (lowerLine.startsWith("define "))
699      {
700        // This should be a constant definition.  The rest of the line should
701        // contain the constant name, an equal sign, and the constant value.
702        int equalPos = line.indexOf('=', 7);
703        if (equalPos < 0)
704        {
705          LocalizableMessage message = ERR_MAKELDIF_DEFINE_MISSING_EQUALS.get(lineNumber);
706          throw new MakeLDIFException(message);
707        }
708
709        String name  = line.substring(7, equalPos).trim();
710        if (name.length() == 0)
711        {
712          LocalizableMessage message = ERR_MAKELDIF_DEFINE_NAME_EMPTY.get(lineNumber);
713          throw new MakeLDIFException(message);
714        }
715
716        String lowerName = toLowerCase(name);
717        if (templateFileConstants.containsKey(lowerName))
718        {
719          LocalizableMessage message =
720              ERR_MAKELDIF_CONFLICTING_CONSTANT_NAME.get(name, lineNumber);
721          throw new MakeLDIFException(message);
722        }
723
724        String value = line.substring(equalPos+1);
725        if (value.length() == 0)
726        {
727          LocalizableMessage message = ERR_MAKELDIF_WARNING_DEFINE_VALUE_EMPTY.get(
728                  name, lineNumber);
729          warnings.add(message);
730        }
731
732        templateFileConstants.put(lowerName, value);
733      }
734      else if (lowerLine.startsWith("branch: "))
735      {
736        int startLineNumber = lineNumber;
737        ArrayList<String> lineList = new ArrayList<>();
738        lineList.add(line);
739        while (true)
740        {
741          lineNumber++;
742          if (lineNumber >= lines.length)
743          {
744            break;
745          }
746
747          line = lines[lineNumber];
748          if (line.length() == 0)
749          {
750            break;
751          }
752          line = replaceConstants(line, lineNumber, templateFileConstants, warnings);
753          lineList.add(line);
754        }
755
756        String[] branchLines = new String[lineList.size()];
757        lineList.toArray(branchLines);
758
759        Branch b = parseBranchDefinition(branchLines, lineNumber,
760                                         templateFileIncludeTags,
761            warnings);
762        DN branchDN = b.getBranchDN();
763        if (templateFileBranches.containsKey(branchDN))
764        {
765          LocalizableMessage message = ERR_MAKELDIF_CONFLICTING_BRANCH_DN.get(branchDN, startLineNumber);
766          throw new MakeLDIFException(message);
767        }
768        else
769        {
770          templateFileBranches.put(branchDN, b);
771        }
772      }
773      else if (lowerLine.startsWith("template: "))
774      {
775        int startLineNumber = lineNumber;
776        ArrayList<String> lineList = new ArrayList<>();
777        lineList.add(line);
778        while (true)
779        {
780          lineNumber++;
781          if (lineNumber >= lines.length)
782          {
783            break;
784          }
785
786          line = lines[lineNumber];
787          if (line.length() == 0)
788          {
789            break;
790          }
791          line = replaceConstants(line, lineNumber, templateFileConstants, warnings);
792          lineList.add(line);
793        }
794
795        String[] templateLines = new String[lineList.size()];
796        lineList.toArray(templateLines);
797
798        Template t = parseTemplateDefinition(templateLines, startLineNumber,
799                                             templateFileIncludeTags,
800                                             templateFileTemplates, warnings);
801        String lowerName = toLowerCase(t.getName());
802        if (templateFileTemplates.containsKey(lowerName))
803        {
804          LocalizableMessage message = ERR_MAKELDIF_CONFLICTING_TEMPLATE_NAME.get(t.getName(), startLineNumber);
805          throw new MakeLDIFException(message);
806        }
807        templateFileTemplates.put(lowerName, t);
808      }
809      else
810      {
811        LocalizableMessage message =
812            ERR_MAKELDIF_UNEXPECTED_TEMPLATE_FILE_LINE.get(line, lineNumber);
813        throw new MakeLDIFException(message);
814      }
815    }
816
817
818    // If we've gotten here, then we're almost done.  We just need to finalize
819    // the branch and template definitions and then update the template file
820    // variables.
821    for (Branch b : templateFileBranches.values())
822    {
823      b.completeBranchInitialization(templateFileTemplates);
824    }
825
826    for (Template t : templateFileTemplates.values())
827    {
828      t.completeTemplateInitialization(templateFileTemplates);
829    }
830
831    registeredTags.putAll(templateFileIncludeTags);
832    constants.putAll(templateFileConstants);
833    branches.putAll(templateFileBranches);
834    templates.putAll(templateFileTemplates);
835  }
836
837
838  /**
839   * Parse a line and replace all constants within [ ] with their
840   * values.
841   *
842   * @param line        The line to parse.
843   * @param lineNumber  The line number in the template file.
844   * @param constants   The set of constants defined in the template file.
845   * @param warnings    A list into which any warnings identified may be
846   *                    placed.
847   * @return The line in which all constant variables have been replaced
848   *         with their value
849   */
850  private String replaceConstants(String line, int lineNumber,
851                                  Map<String,String> constants,
852                                  List<LocalizableMessage> warnings)
853  {
854    int closePos = line.lastIndexOf(']');
855    // Loop until we've scanned all closing brackets
856    do
857    {
858      // Skip escaped closing brackets
859      while (closePos > 0 &&
860          line.charAt(closePos - 1) == '\\')
861      {
862        closePos = line.lastIndexOf(']', closePos - 1);
863      }
864      if (closePos > 0)
865      {
866        StringBuilder lineBuffer = new StringBuilder(line);
867        int openPos = line.lastIndexOf('[', closePos);
868        // Find the opening bracket. If it's escaped, then it's not a constant
869        if ((openPos > 0 && line.charAt(openPos - 1) != '\\')
870            || openPos == 0)
871        {
872          String constantName =
873              toLowerCase(line.substring(openPos+1, closePos));
874          String constantValue = constants.get(constantName);
875          if (constantValue == null)
876          {
877            LocalizableMessage message = WARN_MAKELDIF_WARNING_UNDEFINED_CONSTANT.get(
878                constantName, lineNumber);
879            warnings.add(message);
880          }
881          else
882          {
883            lineBuffer.replace(openPos, closePos+1, constantValue);
884          }
885        }
886        if (openPos >= 0)
887        {
888          closePos = openPos;
889        }
890        line = lineBuffer.toString();
891        closePos = line.lastIndexOf(']', closePos);
892      }
893    } while (closePos > 0);
894    return line;
895  }
896
897  /**
898   * Parses the information contained in the provided set of lines as a MakeLDIF
899   * branch definition.
900   *
901   *
902   * @param  branchLines      The set of lines containing the branch definition.
903   * @param  startLineNumber  The line number in the template file on which the
904   *                          first of the branch lines appears.
905   * @param  tags             The set of defined tags from the template file.
906   *                          Note that this does not include the tags that are
907   *                          always registered by default.
908   * @param  warnings         A list into which any warnings identified may be
909   *                          placed.
910   *
911   * @return  The decoded branch definition.
912   *
913   * @throws  InitializationException  If a problem occurs while initializing
914   *                                   any of the branch elements.
915   *
916   * @throws  MakeLDIFException  If some other problem occurs during processing.
917   */
918  private Branch parseBranchDefinition(String[] branchLines,
919                                       int startLineNumber,
920                                       Map<String, Tag> tags,
921                                       List<LocalizableMessage> warnings)
922          throws InitializationException, MakeLDIFException
923  {
924    // The first line must be "branch: " followed by the branch DN.
925    String dnString = branchLines[0].substring(8).trim();
926    DN branchDN;
927    try
928    {
929      branchDN = DN.valueOf(dnString);
930    }
931    catch (Exception e)
932    {
933      LocalizableMessage message =
934          ERR_MAKELDIF_CANNOT_DECODE_BRANCH_DN.get(dnString, startLineNumber);
935      throw new MakeLDIFException(message);
936    }
937
938
939    // Create a new branch that will be used for the verification process.
940    Branch branch = new Branch(this, branchDN);
941
942    for (int i=1; i < branchLines.length; i++)
943    {
944      String line       = branchLines[i];
945      String lowerLine  = toLowerCase(line);
946      int    lineNumber = startLineNumber + i;
947
948      if (lowerLine.startsWith("#"))
949      {
950        // It's a comment, so we should ignore it.
951        continue;
952      }
953      else if (lowerLine.startsWith("subordinatetemplate: "))
954      {
955        // It's a subordinate template, so we'll want to parse the name and the
956        // number of entries.
957        int colonPos = line.indexOf(':', 21);
958        if (colonPos <= 21)
959        {
960          LocalizableMessage message = ERR_MAKELDIF_BRANCH_SUBORDINATE_TEMPLATE_NO_COLON.
961              get(lineNumber, dnString);
962          throw new MakeLDIFException(message);
963        }
964
965        String templateName = line.substring(21, colonPos).trim();
966
967        int numEntries;
968        try
969        {
970          numEntries = Integer.parseInt(line.substring(colonPos+1).trim());
971          if (numEntries < 0)
972          {
973            LocalizableMessage message =
974              ERR_MAKELDIF_BRANCH_SUBORDINATE_INVALID_NUM_ENTRIES.
975                  get(lineNumber, dnString, numEntries, templateName);
976            throw new MakeLDIFException(message);
977          }
978          else if (numEntries == 0)
979          {
980            LocalizableMessage message = WARN_MAKELDIF_BRANCH_SUBORDINATE_ZERO_ENTRIES.get(
981                    lineNumber, dnString,
982                                        templateName);
983            warnings.add(message);
984          }
985
986          branch.addSubordinateTemplate(templateName, numEntries);
987        }
988        catch (NumberFormatException nfe)
989        {
990          LocalizableMessage message =
991            ERR_MAKELDIF_BRANCH_SUBORDINATE_CANT_PARSE_NUMENTRIES.
992                get(templateName, lineNumber, dnString);
993          throw new MakeLDIFException(message);
994        }
995      }
996      else
997      {
998        TemplateLine templateLine = parseTemplateLine(line, lowerLine,
999                                                      lineNumber, branch, null,
1000                                                      tags, warnings);
1001        branch.addExtraLine(templateLine);
1002      }
1003    }
1004
1005    return branch;
1006  }
1007
1008
1009
1010  /**
1011   * Parses the information contained in the provided set of lines as a MakeLDIF
1012   * template definition.
1013   *
1014   *
1015   * @param  templateLines     The set of lines containing the template
1016   *                           definition.
1017   * @param  startLineNumber   The line number in the template file on which the
1018   *                           first of the template lines appears.
1019   * @param  tags              The set of defined tags from the template file.
1020   *                           Note that this does not include the tags that are
1021   *                           always registered by default.
1022   * @param  definedTemplates  The set of templates already defined in the
1023   *                           template file.
1024   * @param  warnings          A list into which any warnings identified may be
1025   *                           placed.
1026   *
1027   * @return  The decoded template definition.
1028   *
1029   * @throws  InitializationException  If a problem occurs while initializing
1030   *                                   any of the template elements.
1031   *
1032   * @throws  MakeLDIFException  If some other problem occurs during processing.
1033   */
1034  private Template parseTemplateDefinition(String[] templateLines,
1035                                           int startLineNumber,
1036                                           Map<String, Tag> tags,
1037                                           Map<String, Template>
1038                                               definedTemplates,
1039                                           List<LocalizableMessage> warnings)
1040          throws InitializationException, MakeLDIFException
1041  {
1042    // The first line must be "template: " followed by the template name.
1043    String templateName = templateLines[0].substring(10).trim();
1044
1045
1046    // The next line may start with either "extends: ", "rdnAttr: ", or
1047    // "subordinateTemplate: ".  Keep reading until we find something that's
1048    // not one of those.
1049    int                arrayLineNumber    = 1;
1050    Template           parentTemplate     = null;
1051    AttributeType[]    rdnAttributes      = null;
1052    ArrayList<String>  subTemplateNames   = new ArrayList<>();
1053    ArrayList<Integer> entriesPerTemplate = new ArrayList<>();
1054    for ( ; arrayLineNumber < templateLines.length; arrayLineNumber++)
1055    {
1056      int    lineNumber = startLineNumber + arrayLineNumber;
1057      String line       = templateLines[arrayLineNumber];
1058      String lowerLine  = toLowerCase(line);
1059
1060      if (lowerLine.startsWith("#"))
1061      {
1062        // It's a comment.  Ignore it.
1063        continue;
1064      }
1065      else if (lowerLine.startsWith("extends: "))
1066      {
1067        String parentTemplateName = line.substring(9).trim();
1068        parentTemplate = definedTemplates.get(parentTemplateName.toLowerCase());
1069        if (parentTemplate == null)
1070        {
1071          LocalizableMessage message = ERR_MAKELDIF_TEMPLATE_INVALID_PARENT_TEMPLATE.get(
1072              parentTemplateName, lineNumber, templateName);
1073          throw new MakeLDIFException(message);
1074        }
1075      }
1076      else if (lowerLine.startsWith("rdnattr: "))
1077      {
1078        // This is the set of RDN attributes.  If there are multiple, they may
1079        // be separated by plus signs.
1080        ArrayList<AttributeType> attrList = new ArrayList<>();
1081        String rdnAttrNames = lowerLine.substring(9).trim();
1082        StringTokenizer tokenizer = new StringTokenizer(rdnAttrNames, "+");
1083        while (tokenizer.hasMoreTokens())
1084        {
1085          attrList.add(DirectoryServer.getAttributeTypeOrDefault(tokenizer.nextToken()));
1086        }
1087
1088        rdnAttributes = new AttributeType[attrList.size()];
1089        attrList.toArray(rdnAttributes);
1090      }
1091      else if (lowerLine.startsWith("subordinatetemplate: "))
1092      {
1093        // It's a subordinate template, so we'll want to parse the name and the
1094        // number of entries.
1095        int colonPos = line.indexOf(':', 21);
1096        if (colonPos <= 21)
1097        {
1098          LocalizableMessage message = ERR_MAKELDIF_TEMPLATE_SUBORDINATE_TEMPLATE_NO_COLON.
1099              get(lineNumber, templateName);
1100          throw new MakeLDIFException(message);
1101        }
1102
1103        String subTemplateName = line.substring(21, colonPos).trim();
1104
1105        int numEntries;
1106        try
1107        {
1108          numEntries = Integer.parseInt(line.substring(colonPos+1).trim());
1109          if (numEntries < 0)
1110          {
1111            LocalizableMessage message =
1112              ERR_MAKELDIF_TEMPLATE_SUBORDINATE_INVALID_NUM_ENTRIES.
1113                  get(lineNumber, templateName, numEntries, subTemplateName);
1114            throw new MakeLDIFException(message);
1115          }
1116          else if (numEntries == 0)
1117          {
1118            LocalizableMessage message = WARN_MAKELDIF_TEMPLATE_SUBORDINATE_ZERO_ENTRIES
1119                    .get(lineNumber, templateName, subTemplateName);
1120            warnings.add(message);
1121          }
1122
1123          subTemplateNames.add(subTemplateName);
1124          entriesPerTemplate.add(numEntries);
1125        }
1126        catch (NumberFormatException nfe)
1127        {
1128          LocalizableMessage message =
1129            ERR_MAKELDIF_TEMPLATE_SUBORDINATE_CANT_PARSE_NUMENTRIES.
1130                get(subTemplateName, lineNumber, templateName);
1131          throw new MakeLDIFException(message);
1132        }
1133      }
1134      else
1135      {
1136        // It's something we don't recognize, so it must be a template line.
1137        break;
1138      }
1139    }
1140
1141    // Create a new template that will be used for the verification process.
1142    String[] subordinateTemplateNames = new String[subTemplateNames.size()];
1143    subTemplateNames.toArray(subordinateTemplateNames);
1144
1145    int[] numEntriesPerTemplate = new int[entriesPerTemplate.size()];
1146    for (int i=0; i < numEntriesPerTemplate.length; i++)
1147    {
1148      numEntriesPerTemplate[i] = entriesPerTemplate.get(i);
1149    }
1150
1151    TemplateLine[] parsedLines;
1152    if (parentTemplate == null)
1153    {
1154      parsedLines = new TemplateLine[0];
1155    }
1156    else
1157    {
1158      TemplateLine[] parentLines = parentTemplate.getTemplateLines();
1159      parsedLines = new TemplateLine[parentLines.length];
1160      System.arraycopy(parentLines, 0, parsedLines, 0, parentLines.length);
1161    }
1162
1163    Template template = new Template(this, templateName, rdnAttributes,
1164                                     subordinateTemplateNames,
1165                                     numEntriesPerTemplate, parsedLines);
1166
1167    for ( ; arrayLineNumber < templateLines.length; arrayLineNumber++)
1168    {
1169      String line       = templateLines[arrayLineNumber];
1170      String lowerLine  = toLowerCase(line);
1171      int    lineNumber = startLineNumber + arrayLineNumber;
1172
1173      if (lowerLine.startsWith("#"))
1174      {
1175        // It's a comment, so we should ignore it.
1176        continue;
1177      }
1178      else
1179      {
1180        TemplateLine templateLine = parseTemplateLine(line, lowerLine,
1181                                                      lineNumber, null,
1182                                                      template, tags, warnings);
1183        template.addTemplateLine(templateLine);
1184      }
1185    }
1186
1187    return template;
1188  }
1189
1190
1191
1192  /**
1193   * Parses the provided line as a template line.  Note that exactly one of the
1194   * branch or template arguments must be non-null and the other must be null.
1195   *
1196   * @param  line        The text of the template line.
1197   * @param  lowerLine   The template line in all lowercase characters.
1198   * @param  lineNumber  The line number on which the template line appears.
1199   * @param  branch      The branch with which the template line is associated.
1200   * @param  template    The template with which the template line is
1201   *                     associated.
1202   * @param  tags        The set of defined tags from the template file.  Note
1203   *                     that this does not include the tags that are always
1204   *                     registered by default.
1205   * @param  warnings    A list into which any warnings identified may be
1206   *                     placed.
1207   *
1208   * @return  The template line that has been parsed.
1209   *
1210   * @throws  InitializationException  If a problem occurs while initializing
1211   *                                   any of the template elements.
1212   *
1213   * @throws  MakeLDIFException  If some other problem occurs during processing.
1214   */
1215  private TemplateLine parseTemplateLine(String line, String lowerLine,
1216                                         int lineNumber, Branch branch,
1217                                         Template template,
1218                                         Map<String,Tag> tags,
1219                                         List<LocalizableMessage> warnings)
1220          throws InitializationException, MakeLDIFException
1221  {
1222    // The first component must be the attribute type, followed by a colon.
1223    int colonPos = lowerLine.indexOf(':');
1224    if (colonPos < 0)
1225    {
1226      if (branch == null)
1227      {
1228        LocalizableMessage message = ERR_MAKELDIF_NO_COLON_IN_TEMPLATE_LINE.get(
1229            lineNumber, template.getName());
1230        throw new MakeLDIFException(message);
1231      }
1232      else
1233      {
1234        LocalizableMessage message = ERR_MAKELDIF_NO_COLON_IN_BRANCH_EXTRA_LINE.get(
1235            lineNumber, branch.getBranchDN());
1236        throw new MakeLDIFException(message);
1237      }
1238    }
1239    else if (colonPos == 0)
1240    {
1241      if (branch == null)
1242      {
1243        LocalizableMessage message = ERR_MAKELDIF_NO_ATTR_IN_TEMPLATE_LINE.get(
1244            lineNumber, template.getName());
1245        throw new MakeLDIFException(message);
1246      }
1247      else
1248      {
1249        LocalizableMessage message = ERR_MAKELDIF_NO_ATTR_IN_BRANCH_EXTRA_LINE.get(
1250            lineNumber, branch.getBranchDN());
1251        throw new MakeLDIFException(message);
1252      }
1253    }
1254
1255    AttributeType attributeType = DirectoryServer.getAttributeTypeOrDefault(lowerLine.substring(0, colonPos));
1256
1257    // First, check whether the value is an URL value: <attrName>:< <url>
1258    int length = line.length();
1259    int pos    = colonPos + 1;
1260    boolean valueIsURL = false;
1261    boolean valueIsBase64 = false;
1262    if (pos < length)
1263    {
1264      if (lowerLine.charAt(pos) == '<')
1265      {
1266        valueIsURL = true;
1267        pos ++;
1268      }
1269      else if (lowerLine.charAt(pos) == ':')
1270      {
1271        valueIsBase64 = true;
1272        pos ++;
1273      }
1274    }
1275    //  Then, find the position of the first non-blank character in the line.
1276    while (pos < length && lowerLine.charAt(pos) == ' ')
1277    {
1278      pos++;
1279    }
1280
1281    if (pos >= length)
1282    {
1283      // We've hit the end of the line with no value.  We'll allow it, but add a
1284      // warning.
1285      if (branch == null)
1286      {
1287        LocalizableMessage message = WARN_MAKELDIF_NO_VALUE_IN_TEMPLATE_LINE.get(
1288                lineNumber, template.getName());
1289        warnings.add(message);
1290      }
1291      else
1292      {
1293        LocalizableMessage message = WARN_MAKELDIF_NO_VALUE_IN_BRANCH_EXTRA_LINE.get(
1294                lineNumber, branch.getBranchDN());
1295        warnings.add(message);
1296      }
1297    }
1298
1299
1300    // Define constants that specify what we're currently parsing.
1301    final int PARSING_STATIC_TEXT     = 0;
1302    final int PARSING_REPLACEMENT_TAG = 1;
1303    final int PARSING_ATTRIBUTE_TAG   = 2;
1304    final int PARSING_ESCAPED_CHAR    = 3;
1305
1306    int phase = PARSING_STATIC_TEXT;
1307    int previousPhase = PARSING_STATIC_TEXT;
1308
1309    ArrayList<Tag> tagList = new ArrayList<>();
1310    StringBuilder buffer = new StringBuilder();
1311
1312    for ( ; pos < length; pos++)
1313    {
1314      char c = line.charAt(pos);
1315      switch (phase)
1316      {
1317        case PARSING_STATIC_TEXT:
1318          switch (c)
1319          {
1320            case '\\':
1321              phase = PARSING_ESCAPED_CHAR;
1322              previousPhase = PARSING_STATIC_TEXT;
1323              break;
1324            case '<':
1325              if (buffer.length() > 0)
1326              {
1327                StaticTextTag t = new StaticTextTag();
1328                String[] args = new String[] { buffer.toString() };
1329                t.initializeForBranch(this, branch, args, lineNumber,
1330                    warnings);
1331                tagList.add(t);
1332                buffer = new StringBuilder();
1333              }
1334
1335              phase = PARSING_REPLACEMENT_TAG;
1336              break;
1337            case '{':
1338              if (buffer.length() > 0)
1339              {
1340                StaticTextTag t = new StaticTextTag();
1341                String[] args = new String[] { buffer.toString() };
1342                t.initializeForBranch(this, branch, args, lineNumber,
1343                                      warnings);
1344                tagList.add(t);
1345                buffer = new StringBuilder();
1346              }
1347
1348              phase = PARSING_ATTRIBUTE_TAG;
1349              break;
1350            default:
1351              buffer.append(c);
1352          }
1353          break;
1354
1355        case PARSING_REPLACEMENT_TAG:
1356          switch (c)
1357          {
1358            case '\\':
1359              phase = PARSING_ESCAPED_CHAR;
1360              previousPhase = PARSING_REPLACEMENT_TAG;
1361              break;
1362            case '>':
1363              Tag t = parseReplacementTag(buffer.toString(), branch, template,
1364                                          lineNumber, tags, warnings);
1365              tagList.add(t);
1366              buffer = new StringBuilder();
1367
1368              phase = PARSING_STATIC_TEXT;
1369              break;
1370            default:
1371              buffer.append(c);
1372              break;
1373          }
1374          break;
1375
1376        case PARSING_ATTRIBUTE_TAG:
1377          switch (c)
1378          {
1379            case '\\':
1380              phase = PARSING_ESCAPED_CHAR;
1381              previousPhase = PARSING_ATTRIBUTE_TAG;
1382              break;
1383            case '}':
1384              Tag t = parseAttributeTag(buffer.toString(), branch, template,
1385                                        lineNumber, warnings);
1386              tagList.add(t);
1387              buffer = new StringBuilder();
1388
1389              phase = PARSING_STATIC_TEXT;
1390              break;
1391            default:
1392              buffer.append(c);
1393              break;
1394          }
1395          break;
1396
1397        case PARSING_ESCAPED_CHAR:
1398          buffer.append(c);
1399          phase = previousPhase;
1400          break;
1401      }
1402    }
1403
1404    if (phase == PARSING_STATIC_TEXT)
1405    {
1406      if (buffer.length() > 0)
1407      {
1408        StaticTextTag t = new StaticTextTag();
1409        String[] args = new String[] { buffer.toString() };
1410        t.initializeForBranch(this, branch, args, lineNumber, warnings);
1411        tagList.add(t);
1412      }
1413    }
1414    else
1415    {
1416      LocalizableMessage message = ERR_MAKELDIF_INCOMPLETE_TAG.get(lineNumber);
1417      throw new InitializationException(message);
1418    }
1419
1420    Tag[] tagArray = new Tag[tagList.size()];
1421    tagList.toArray(tagArray);
1422    return new TemplateLine(attributeType, lineNumber, tagArray, valueIsURL,
1423        valueIsBase64);
1424  }
1425
1426
1427
1428  /**
1429   * Parses the provided string as a replacement tag.  Exactly one of the branch
1430   * or template must be null, and the other must be non-null.
1431   *
1432   * @param  tagString   The string containing the encoded tag.
1433   * @param  branch      The branch in which this tag appears.
1434   * @param  template    The template in which this tag appears.
1435   * @param  lineNumber  The line number on which this tag appears in the
1436   *                     template file.
1437   * @param  tags        The set of defined tags from the template file.  Note
1438   *                     that this does not include the tags that are always
1439   *                     registered by default.
1440   * @param  warnings    A list into which any warnings identified may be
1441   *                     placed.
1442   *
1443   * @return  The replacement tag parsed from the provided string.
1444   *
1445   * @throws  InitializationException  If a problem occurs while initializing
1446   *                                   the tag.
1447   *
1448   * @throws  MakeLDIFException  If some other problem occurs during processing.
1449   */
1450  private Tag parseReplacementTag(String tagString, Branch branch,
1451                                  Template template, int lineNumber,
1452                                  Map<String,Tag> tags,
1453                                  List<LocalizableMessage> warnings)
1454          throws InitializationException, MakeLDIFException
1455  {
1456    // The components of the replacement tag will be separated by colons, with
1457    // the first being the tag name and the remainder being arguments.
1458    StringTokenizer tokenizer = new StringTokenizer(tagString, ":");
1459    String          tagName      = tokenizer.nextToken().trim();
1460    String          lowerTagName = toLowerCase(tagName);
1461
1462    Tag t = getTag(lowerTagName);
1463    if (t == null)
1464    {
1465      t = tags.get(lowerTagName);
1466      if (t == null)
1467      {
1468        LocalizableMessage message = ERR_MAKELDIF_NO_SUCH_TAG.get(tagName, lineNumber);
1469        throw new MakeLDIFException(message);
1470      }
1471    }
1472
1473    ArrayList<String> argList = new ArrayList<>();
1474    while (tokenizer.hasMoreTokens())
1475    {
1476      argList.add(tokenizer.nextToken().trim());
1477    }
1478
1479    String[] args = new String[argList.size()];
1480    argList.toArray(args);
1481
1482
1483    Tag newTag;
1484    try
1485    {
1486      newTag = t.getClass().newInstance();
1487    }
1488    catch (Exception e)
1489    {
1490      throw new MakeLDIFException(ERR_MAKELDIF_CANNOT_INSTANTIATE_NEW_TAG.get(tagName, lineNumber, e), e);
1491    }
1492
1493
1494    if (branch == null)
1495    {
1496      newTag.initializeForTemplate(this, template, args, lineNumber, warnings);
1497    }
1498    else
1499    {
1500      if (newTag.allowedInBranch())
1501      {
1502        newTag.initializeForBranch(this, branch, args, lineNumber, warnings);
1503      }
1504      else
1505      {
1506        LocalizableMessage message = ERR_MAKELDIF_TAG_NOT_ALLOWED_IN_BRANCH.get(
1507            newTag.getName(), lineNumber);
1508        throw new MakeLDIFException(message);
1509      }
1510    }
1511
1512    return newTag;
1513  }
1514
1515
1516
1517  /**
1518   * Parses the provided string as an attribute tag.  Exactly one of the branch
1519   * or template must be null, and the other must be non-null.
1520   *
1521   * @param  tagString   The string containing the encoded tag.
1522   * @param  branch      The branch in which this tag appears.
1523   * @param  template    The template in which this tag appears.
1524   * @param  lineNumber  The line number on which this tag appears in the
1525   *                     template file.
1526   * @param  warnings    A list into which any warnings identified may be
1527   *                     placed.
1528   *
1529   * @return  The attribute tag parsed from the provided string.
1530   *
1531   * @throws  InitializationException  If a problem occurs while initializing
1532   *                                   the tag.
1533   *
1534   * @throws  MakeLDIFException  If some other problem occurs during processing.
1535   */
1536  private Tag parseAttributeTag(String tagString, Branch branch,
1537                                Template template, int lineNumber,
1538                                List<LocalizableMessage> warnings)
1539          throws InitializationException, MakeLDIFException
1540  {
1541    // The attribute tag must have at least one argument, which is the name of
1542    // the attribute to reference.  It may have a second argument, which is the
1543    // number of characters to use from the attribute value.  The arguments will
1544    // be delimited by colons.
1545    StringTokenizer   tokenizer = new StringTokenizer(tagString, ":");
1546    ArrayList<String> argList   = new ArrayList<>();
1547    while (tokenizer.hasMoreTokens())
1548    {
1549      argList.add(tokenizer.nextToken());
1550    }
1551
1552    String[] args = new String[argList.size()];
1553    argList.toArray(args);
1554
1555    AttributeValueTag tag = new AttributeValueTag();
1556    if (branch == null)
1557    {
1558      tag.initializeForTemplate(this, template, args, lineNumber, warnings);
1559    }
1560    else
1561    {
1562      tag.initializeForBranch(this, branch, args, lineNumber, warnings);
1563    }
1564
1565    return tag;
1566  }
1567
1568
1569
1570  /**
1571   * Retrieves a File object based on the provided path.  If the given path is
1572   * absolute, then that absolute path will be used.  If it is relative, then it
1573   * will first be evaluated relative to the current working directory.  If that
1574   * path doesn't exist, then it will be evaluated relative to the resource
1575   * path.  If that path doesn't exist, then it will be evaluated relative to
1576   * the directory containing the template file.
1577   *
1578   * @param  path  The path provided for the file.
1579   *
1580   * @return  The File object for the specified path, or <CODE>null</CODE> if
1581   *          the specified file could not be found.
1582   */
1583  public File getFile(String path)
1584  {
1585    // First, see if the file exists using the given path.  This will work if
1586    // the file is absolute, or it's relative to the current working directory.
1587    File f = new File(path);
1588    if (f.exists())
1589    {
1590      return f;
1591    }
1592
1593
1594    // If the provided path was absolute, then use it anyway, even though we
1595    // couldn't find the file.
1596    if (f.isAbsolute())
1597    {
1598      return f;
1599    }
1600
1601
1602    // Try a path relative to the resource directory.
1603    String newPath = resourcePath + File.separator + path;
1604    f = new File(newPath);
1605    if (f.exists())
1606    {
1607      return f;
1608    }
1609
1610
1611    // Try a path relative to the template directory, if it's available.
1612    if (templatePath != null)
1613    {
1614      newPath = templatePath = File.separator + path;
1615      f = new File(newPath);
1616      if (f.exists())
1617      {
1618        return f;
1619      }
1620    }
1621
1622    return null;
1623  }
1624
1625
1626
1627  /**
1628   * Retrieves the lines of the specified file as a string array.  If the result
1629   * is already cached, then it will be used.  If the result is not cached, then
1630   * the file data will be cached so that the contents can be re-used if there
1631   * are multiple references to the same file.
1632   *
1633   * @param  file  The file for which to retrieve the contents.
1634   *
1635   * @return  An array containing the lines of the specified file.
1636   *
1637   * @throws  IOException  If a problem occurs while reading the file.
1638   */
1639  public String[] getFileLines(File file) throws IOException
1640  {
1641    String absolutePath = file.getAbsolutePath();
1642    String[] lines = fileLines.get(absolutePath);
1643    if (lines == null)
1644    {
1645      List<String> lineList = readLines(file);
1646
1647      lines = new String[lineList.size()];
1648      lineList.toArray(lines);
1649      lineList.clear();
1650      fileLines.put(absolutePath, lines);
1651    }
1652
1653    return lines;
1654  }
1655
1656
1657
1658  /**
1659   * Generates the LDIF content and writes it to the provided LDIF writer.
1660   *
1661   * @param  entryWriter  The entry writer that should be used to write the
1662   *                      entries.
1663   *
1664   * @return  The result that indicates whether processing should continue.
1665   *
1666   * @throws  IOException  If an error occurs while writing to the LDIF file.
1667   *
1668   * @throws  MakeLDIFException  If some other problem occurs.
1669   */
1670  public TagResult generateLDIF(EntryWriter entryWriter)
1671         throws IOException, MakeLDIFException
1672  {
1673    for (Branch b : branches.values())
1674    {
1675      TagResult result = b.writeEntries(entryWriter);
1676      if (!result.keepProcessingTemplateFile())
1677      {
1678        return result;
1679      }
1680    }
1681
1682    entryWriter.closeEntryWriter();
1683    return TagResult.SUCCESS_RESULT;
1684  }
1685}
1686