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