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