View Javadoc

1   /*
2    * <license>
3    * Copyright (c) 2003-2004, Sun Microsystems, Inc.
4    * All rights reserved.
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions are met:
8    *
9    *     * Redistributions of source code must retain the above copyright
10   *       notice, this list of conditions and the following disclaimer.
11   *     * Redistributions in binary form must reproduce the above copyright
12   *       notice, this list of conditions and the following disclaimer in the
13   *       documentation and/or other materials provided with the distribution.
14   *     * Neither the name of Sun Microsystems, Inc. nor the names of its
15   *       contributors may be used to endorse or promote products derived from
16   *       this software without specific prior written permission.
17   *
18   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20   * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
21   * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24   * ROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29   * </license>
30   */
31  
32  package com.sun.tlddoc;
33  
34  import java.io.CharArrayReader;
35  import java.io.File;
36  import java.io.FileInputStream;
37  import java.io.FileNotFoundException;
38  import java.io.FileOutputStream;
39  import java.io.IOException;
40  import java.io.InputStream;
41  import java.io.OutputStream;
42  import java.util.ArrayList;
43  import java.util.Enumeration;
44  import java.util.HashMap;
45  import java.util.Iterator;
46  import java.util.Map;
47  import java.util.jar.JarEntry;
48  import java.util.jar.JarFile;
49  import java.util.jar.JarInputStream;
50  
51  import javax.xml.parsers.DocumentBuilder;
52  import javax.xml.parsers.DocumentBuilderFactory;
53  import javax.xml.parsers.ParserConfigurationException;
54  import javax.xml.transform.Transformer;
55  import javax.xml.transform.TransformerConfigurationException;
56  import javax.xml.transform.TransformerException;
57  import javax.xml.transform.TransformerFactory;
58  import javax.xml.transform.dom.DOMResult;
59  import javax.xml.transform.dom.DOMSource;
60  import javax.xml.transform.stream.StreamResult;
61  import javax.xml.transform.stream.StreamSource;
62  
63  import org.apache.xalan.processor.TransformerFactoryImpl;
64  import org.w3c.dom.Document;
65  import org.w3c.dom.Element;
66  import org.w3c.dom.Node;
67  import org.w3c.dom.NodeList;
68  import org.xml.sax.EntityResolver;
69  import org.xml.sax.InputSource;
70  import org.xml.sax.SAXException;
71  
72  import com.sun.tlddoc.tagfileparser.Attribute;
73  import com.sun.tlddoc.tagfileparser.Directive;
74  import com.sun.tlddoc.tagfileparser.ParseException;
75  import com.sun.tlddoc.tagfileparser.TagFile;
76  import com.sun.tlddoc.tagfileparser.TokenMgrError;
77  
78  
79  /***
80   * TLDDoc Generator. Takes a set of TLD files and generates a set of javadoc-style HTML pages that describe the various
81   * components of the input tag libraries.
82   * @author Mark Roth
83   */
84  public class TLDDocGenerator
85  {
86  
87      /***
88       * The set of tag libraries we are parsing. Each element is a TagLibrary instance.
89       */
90      private ArrayList tagLibraries = new ArrayList();
91  
92      /***
93       * The directory containing the stylesheets, or null if the default stylesheets are to be used.
94       */
95      private File xsltDirectory = null;
96  
97      /*** The output directory for generated files */
98      private File outputDirectory = new File("out");
99  
100     /*** The browser window title for the documentation */
101     private String windowTitle = Constants.DEFAULT_WINDOW_TITLE;
102 
103     /*** The title for the TLD index (first) page. */
104     private String docTitle = Constants.DEFAULT_DOC_TITLE;
105 
106     /*** True if no stdout is to be produced during generation */
107     private boolean quiet;
108 
109     /*** The summary TLD document, used as input into XSLT */
110     private Document summaryTLD;
111 
112     /*** Path to tlddoc resources */
113     private static final String RESOURCE_PATH = "/com/sun/tlddoc/resources";
114 
115     /***
116      * Helps uniquely generate subsitutute prefixes in the case of missing or duplicate short-names
117      */
118     private int substitutePrefix = 1;
119 
120     /***
121      * Creates a new TLDDocGenerator.
122      */
123     public TLDDocGenerator()
124     {
125     }
126 
127     /***
128      * Adds the given Tag Library to the list of Tag Libraries to generate documentation for.
129      * @param tagLibrary The tag library to add.
130      */
131     public void addTagLibrary(TagLibrary tagLibrary)
132     {
133         tagLibraries.add(tagLibrary);
134     }
135 
136     /***
137      * Adds the given individual TLD file
138      * @param tld The TLD file to add
139      */
140     public void addTLD(File tld)
141     {
142         addTagLibrary(new TLDFileTagLibrary(tld));
143     }
144 
145     /***
146      * Adds all the tag libraries found in the given web application.
147      * @param path The path to the root of the web application.
148      */
149     public void addWebApp(File path)
150     {
151         File webinf = new File(path, "WEB-INF");
152 
153         // Scan all subdirectories of /WEB-INF/ for .tld files
154         addWebAppTLDsIn(webinf);
155 
156         // Add all JAR files in /WEB-INF/lib that might potentially
157         // contain TLDs.
158         addWebAppJARsIn(new File(webinf, "lib"));
159 
160         // Add all implicit tag libraries in /WEB-INF/tags
161         addWebAppTagDirsIn(new File(webinf, "tags"));
162     }
163 
164     /***
165      * Adds all TLD files under the given directory, recursively.
166      * @param path The path to search (recursively) for TLDs in.
167      */
168     private void addWebAppTLDsIn(File path)
169     {
170         File[] files = path.listFiles();
171         for (int i = 0; i < files.length; i++)
172         {
173             if (files[i].isDirectory())
174             {
175                 addWebAppTLDsIn(files[i]);
176             }
177             else if (files[i].getName().toLowerCase().endsWith(".tld"))
178             {
179                 addTLD(files[i]);
180             }
181         }
182     }
183 
184     /***
185      * Adds all TLD files under the given directory, recursively.
186      * @param war The WAR file to search
187      * @param path The path to search (recursively) for TLDs in.
188      */
189     private void addWARTLDsIn(File war, String path) throws IOException
190     {
191         JarFile warFile = new JarFile(war);
192 
193         Enumeration entries = warFile.entries();
194         while (entries.hasMoreElements())
195         {
196             JarEntry jarEntry = (JarEntry) entries.nextElement();
197             String entryName = jarEntry.getName();
198             if (entryName.startsWith(path) && entryName.toLowerCase().endsWith(".tld"))
199             {
200                 addTagLibrary(new JARTLDFileTagLibrary(war, jarEntry.getName()));
201             }
202         }
203 
204         warFile.close();
205     }
206 
207     /***
208      * Adds all JAR files under the given directory, recursively.
209      * @param path The path to search (recursively) for JARs in.
210      */
211     private void addWebAppJARsIn(File path)
212     {
213         File[] files = path.listFiles();
214         for (int i = 0; i < files.length; i++)
215         {
216             if (files[i].isDirectory())
217             {
218                 addWebAppJARsIn(files[i]);
219             }
220             else if (files[i].getName().toLowerCase().endsWith(".jar"))
221             {
222                 addJAR(files[i]);
223             }
224         }
225     }
226 
227     /***
228      * Adds all JAR files under the given directory, recursively.
229      * @param war The WAR file to search
230      * @param path The path to search (recursively) for JARs in.
231      */
232     private void addWARJARsIn(File war, String path) throws IOException
233     {
234         JarFile warFile = new JarFile(war);
235 
236         Enumeration entries = warFile.entries();
237         while (entries.hasMoreElements())
238         {
239             JarEntry warEntry = (JarEntry) entries.nextElement();
240             String entryName = warEntry.getName();
241             if (entryName.startsWith(path) && entryName.toLowerCase().endsWith(".jar"))
242             {
243                 // Add all tag libraries found in the given JAR file that is
244                 // inside this WAR file:
245                 try
246                 {
247                     // Search for all TLD files in the JAR file
248                     JarInputStream in = new JarInputStream(warFile.getInputStream(warEntry));
249 
250                     JarEntry jarEntry;
251                     while ((jarEntry = in.getNextJarEntry()) != null)
252                     {
253                         if (jarEntry.getName().toLowerCase().endsWith(".tld"))
254                         {
255                             addTagLibrary(new WARJARTLDFileTagLibrary(war, entryName, jarEntry.getName()));
256                         }
257                     }
258                     in.close();
259                 }
260                 catch (IOException e)
261                 {
262                     println("WARNING: Could not access one or more "
263                         + "entries in "
264                         + war.getAbsolutePath()
265                         + " entry "
266                         + entryName
267                         + ".  Skipping JAR.  Reason: "
268                         + e.getMessage());
269                 }
270             }
271         }
272 
273         warFile.close();
274     }
275 
276     /***
277      * Adds all implicit tag libraries under the given directory, recursively.
278      * @param path The path to search (recursively) for tag file directories in
279      */
280     private void addWebAppTagDirsIn(File path)
281     {
282         if (path.exists() && path.isDirectory())
283         {
284             addTagDir(path);
285 
286             File[] files = path.listFiles();
287             if (files != null)
288             {
289                 for (int i = 0; i < files.length; i++)
290                 {
291                     if (files[i].isDirectory())
292                     {
293                         addWebAppTagDirsIn(files[i]);
294                     }
295                 }
296             }
297         }
298     }
299 
300     /***
301      * Adds all implicit tag libraries under the given directory, recursively.
302      * @param war The WAR file to search
303      * @param path The path to search (recursively) for tag file directories in
304      */
305     private void addWARTagDirsIn(File war, String path) throws IOException
306     {
307         JarFile warFile = new JarFile(war);
308 
309         Enumeration entries = warFile.entries();
310         while (entries.hasMoreElements())
311         {
312             JarEntry entry = (JarEntry) entries.nextElement();
313             if (entry.getName().startsWith(path) && entry.isDirectory())
314             {
315                 addTagLibrary(new WARTagDirImplicitTagLibrary(war, entry.getName()));
316             }
317         }
318 
319         warFile.close();
320     }
321 
322     /***
323      * Adds all the tag libraries found in the given JAR.
324      * @param jar The JAR file to add.
325      */
326     public void addJAR(File jar)
327     {
328         try
329         {
330             // Search for all TLD files in the JAR file
331             JarFile jarFile = new JarFile(jar);
332             Enumeration entries = jarFile.entries();
333             while (entries.hasMoreElements())
334             {
335                 JarEntry jarEntry = (JarEntry) entries.nextElement();
336                 if (jarEntry.getName().toLowerCase().endsWith(".tld"))
337                 {
338                     addTagLibrary(new JARTLDFileTagLibrary(jar, jarEntry.getName()));
339                 }
340             }
341             jarFile.close();
342         }
343         catch (IOException e)
344         {
345             println("WARNING: Could not access one or more entries in "
346                 + jar.getAbsolutePath()
347                 + ".  Skipping JAR.  Reason: "
348                 + e.getMessage());
349         }
350     }
351 
352     /***
353      * Adds all the tag libraries found in the given web application packaged as a WAR file.
354      * @param war The war containing the web application
355      */
356     public void addWAR(File path)
357     {
358         try
359         {
360             // Scan all subdirectories of /WEB-INF/ for .tld files
361             addWARTLDsIn(path, "WEB-INF/");
362 
363             // Add all JAR files in /WEB-INF/lib that might potentially
364             // contain TLDs.
365             addWARJARsIn(path, "WEB-INF/lib/");
366 
367             // Add all implicit tag libraries in /WEB-INF/tags
368             addWARTagDirsIn(path, "WEB-INF/tags/");
369         }
370         catch (IOException e)
371         {
372             println("WARNING: Could not access one or more entries in "
373                 + path.getAbsolutePath()
374                 + ".  Skipping WAR.  Reason: "
375                 + e.getMessage());
376         }
377     }
378 
379     /***
380      * Adds the given directory of tag files.
381      * @param tld The tag directory to add
382      */
383     public void addTagDir(File tagdir)
384     {
385         addTagLibrary(new TagDirImplicitTagLibrary(tagdir));
386     }
387 
388     /***
389      * Sets the directory from which to obtain the XSLT stylesheets. This allows the user to change the look and feel of
390      * the generated output. If not specified, the default stylesheets are used.
391      * @param dir The base directory for the stylesheets
392      */
393     public void setXSLTDirectory(File dir)
394     {
395         this.xsltDirectory = dir;
396     }
397 
398     /***
399      * Sets the output directory for generated files. If not specified, defaults to "."
400      * @param dir The base directory for generated files.
401      */
402     public void setOutputDirectory(File dir)
403     {
404         this.outputDirectory = dir;
405     }
406 
407     /***
408      * Sets the browser window title for the documentation
409      * @param title The browser window title
410      */
411     public void setWindowTitle(String title)
412     {
413         this.windowTitle = title;
414     }
415 
416     /***
417      * Sets the title for the TLD index (first) page.
418      * @param title The title for the TLD index
419      */
420     public void setDocTitle(String title)
421     {
422         this.docTitle = title;
423     }
424 
425     /***
426      * Sets quiet mode (produce no stdout during generation)
427      * @param quiet True if no output is to be produced, false otherwise.
428      */
429     public void setQuiet(boolean quiet)
430     {
431         this.quiet = quiet;
432     }
433 
434     /***
435      * Returns true if the generator is in quiet mode or false if not.
436      */
437 
438     /***
439      * Commences documentation generation.
440      */
441     public void generate() throws GeneratorException, TransformerException
442     {
443         try
444         {
445             this.outputDirectory.mkdirs();
446             copyStaticFiles();
447             createTLDSummaryDoc();
448             generateOverview();
449             generateTLDDetail();
450             outputSuccessMessage();
451         }
452         catch (IOException e)
453         {
454             throw new GeneratorException(e);
455         }
456         catch (SAXException e)
457         {
458             throw new GeneratorException(e);
459         }
460         catch (ParserConfigurationException e)
461         {
462             throw new GeneratorException(e);
463         }
464         catch (TransformerConfigurationException e)
465         {
466             throw new GeneratorException(e);
467         }
468         catch (TransformerException e)
469         {
470             throw new GeneratorException(e);
471         }
472     }
473 
474     // ///////////////////////////////////////////////////////////////////
475 
476     /***
477      * Copies all static files to target directory.
478      */
479     private void copyStaticFiles() throws IOException
480     {
481         copyResourceToFile(new File(this.outputDirectory, "stylesheet.css"), RESOURCE_PATH + "/stylesheet.css");
482     }
483 
484     /***
485      * Creates a summary document, comprising all input TLDs. This document is later used as input into XSLT to generate
486      * all non-static output pages. Stores the result as a DOM tree in the summaryTLD attribute.
487      */
488     private void createTLDSummaryDoc() throws IOException, SAXException, ParserConfigurationException,
489         TransformerConfigurationException, TransformerException, GeneratorException
490     {
491         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
492         factory.setValidating(false);
493         factory.setNamespaceAware(true);
494         factory.setExpandEntityReferences(false);
495         DocumentBuilder documentBuilder = factory.newDocumentBuilder();
496         documentBuilder.setEntityResolver(new EntityResolver()
497         {
498 
499             public InputSource resolveEntity(String publicId, String systemId)
500             {
501                 return new InputSource(new CharArrayReader(new char[0]));
502             }
503         });
504         summaryTLD = documentBuilder.newDocument();
505 
506         // Create root <tlds> element:
507         Element rootElement = summaryTLD.createElementNS(Constants.NS_J2EE, "tlds");
508         summaryTLD.appendChild(rootElement);
509         // JDK 1.4 does not add xmlns for some reason - add it manually:
510         rootElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", Constants.NS_J2EE);
511 
512         // Create configuration element:
513         Element configElement = summaryTLD.createElementNS(Constants.NS_J2EE, "config");
514         rootElement.appendChild(configElement);
515 
516         Element windowTitle = summaryTLD.createElementNS(Constants.NS_J2EE, "window-title");
517         windowTitle.appendChild(summaryTLD.createTextNode(this.windowTitle));
518         configElement.appendChild(windowTitle);
519 
520         Element docTitle = summaryTLD.createElementNS(Constants.NS_J2EE, "doc-title");
521         docTitle.appendChild(summaryTLD.createTextNode(this.docTitle));
522         configElement.appendChild(docTitle);
523 
524         // Append each <taglib> element from each TLD:
525         Iterator iter = tagLibraries.iterator();
526         println("Loading and translating "
527             + tagLibraries.size()
528             + " Tag Librar"
529             + ((tagLibraries.size() == 1) ? "y" : "ies")
530             + "...");
531         while (iter.hasNext())
532         {
533             TagLibrary tagLibrary = (TagLibrary) iter.next();
534             Document doc = tagLibrary.getTLDDocument(documentBuilder);
535 
536             // Convert document to JSP 2.0 TLD
537             doc = upgradeTLD(doc);
538 
539             // If this tag library has no tags, no validators,
540             // and no functions, omit it
541             int numTags = doc.getDocumentElement().getElementsByTagNameNS("*", "tag").getLength()
542                 + doc.getDocumentElement().getElementsByTagNameNS("*", "tag-file").getLength()
543                 + doc.getDocumentElement().getElementsByTagNameNS("*", "validator").getLength()
544                 + doc.getDocumentElement().getElementsByTagNameNS("*", "function").getLength();
545             if (numTags > 0)
546             {
547                 // Populate the root element with extra information
548                 populateTLD(tagLibrary, doc);
549 
550                 Element taglibNode = (Element) summaryTLD.importNode(doc.getDocumentElement(), true);
551                 if (!taglibNode.getNamespaceURI().equals(Constants.NS_J2EE))
552                 {
553                     throw new GeneratorException("Error: "
554                         + tagLibrary.getPathDescription()
555                         + " does not have xmlns=\""
556                         + Constants.NS_J2EE
557                         + "\"");
558                 }
559                 if (!taglibNode.getLocalName().equals("taglib"))
560                 {
561                     throw new GeneratorException("Error: "
562                         + tagLibrary.getPathDescription()
563                         + " does not have <taglib> as root.");
564                 }
565                 rootElement.appendChild(taglibNode);
566             }
567         }
568 
569         // If debug enabled, output the resulting document, as a test:
570         if (Constants.DEBUG_INPUT_DOCUMENT)
571         {
572 
573             // FG patch to make transformer work on java 1.5
574             // Transformer transformer = TransformerFactory.newInstance().newTransformer();
575             Transformer transformer = getTransformer(null);
576             transformer.transform(new DOMSource(summaryTLD), new StreamResult(System.out));
577         }
578     }
579 
580     /***
581      * Maven 1.0x doesn't play well with jdk 1.5 tranformer... and maven 1.1x doesn't play well with the maven 1.0 hack :/
582      * @param source
583      * @return
584      * @throws TransformerConfigurationException
585      */
586     private Transformer getTransformer(StreamSource source) throws TransformerConfigurationException
587     {
588         Transformer transformer = null;
589         try
590         {
591             transformer = source == null ? TransformerFactory.newInstance().newTransformer() : TransformerFactory
592                 .newInstance()
593                 .newTransformer(source);
594         }
595         catch (Exception e)
596         {
597             // maven 1.0x and java 1.5?
598         }
599         if (transformer == null)
600         {
601             transformer = source == null ? new TransformerFactoryImpl().newTransformer() : new TransformerFactoryImpl()
602                 .newTransformer(source);
603         }
604 
605         return transformer;
606     }
607 
608     /***
609      * Converts the given TLD to a JSP 2.0 TLD
610      */
611     private Document upgradeTLD(Document doc) throws TransformerConfigurationException, ParserConfigurationException,
612         TransformerException
613     {
614         Element root = doc.getDocumentElement();
615 
616         // We use getElementsByTagName instead of getElementsByTagNameNS
617         // here since JSP 1.1 TLDs have no namespace.
618         if (root.getElementsByTagName("jspversion").getLength() > 0)
619         {
620             // JSP 1.1 TLD - convert to JSP 1.2 TLD first.
621             doc = convertTLD(doc, RESOURCE_PATH + "/tld1_1-tld1_2.xsl");
622             root = doc.getDocumentElement();
623         }
624 
625         // We use getElementsByTagName instead of getElementsByTagNameNS
626         // here since JSP 1.2 TLDs have no namespace.
627         if (root.getElementsByTagName("jsp-version").getLength() > 0)
628         {
629             // JSP 1.2 TLD - convert to JSP 2.0 TLD first
630             doc = convertTLD(doc, RESOURCE_PATH + "/tld1_2-tld2_0.xsl");
631             root = doc.getDocumentElement();
632         }
633 
634         // Final conversion to remove unwanted elements
635         doc = convertTLD(doc, RESOURCE_PATH + "/tld2_0-tld2_0.xsl");
636 
637         // We should now have a JSP 2.0 TLD in doc.
638         return doc;
639     }
640 
641     /***
642      * Converts the given TLD using the given stylesheet
643      */
644     private Document convertTLD(Document doc, String stylesheet) throws TransformerConfigurationException,
645         ParserConfigurationException, TransformerException
646     {
647         InputStream xsl = getResourceAsStream(stylesheet);
648 
649         // FG patch to make transformer work on java 1.5
650         // Transformer transformer = TransformerFactory.newInstance().newTransformer(new StreamSource( xsl ) );
651         Transformer transformer = getTransformer(new StreamSource(xsl));
652 
653         Document result = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
654         transformer.transform(new DOMSource(doc), new DOMResult(result));
655         return result;
656     }
657 
658     /***
659      * Populates the root element with any additional information needed before adding this to our tree.
660      * @param tagLibrary The tag library being populated
661      * @param doc The TLD DOM to populate.
662      */
663     private void populateTLD(TagLibrary tagLibrary, Document doc)
664     {
665         Element root = doc.getDocumentElement();
666 
667         checkOrAddShortName(tagLibrary, doc, root);
668         checkOrAddAttributeType(tagLibrary, doc, root);
669         populateTagFileDetails(tagLibrary, doc, root);
670     }
671 
672     /***
673      * Find all tag-file elements and populate them with the actual parsed meta information, found in the tag file's
674      * attributes:
675      * @param tagLibrary The tag library being populated
676      * @param doc The document we're populating
677      * @param root The root element of the TLD DOM being populated.
678      */
679     private void populateTagFileDetails(TagLibrary tagLibrary, Document doc, Element root)
680     {
681         NodeList tagFileNodes = root.getElementsByTagNameNS("*", "tag-file");
682         for (int i = 0; i < tagFileNodes.getLength(); i++)
683         {
684             Element tagFileNode = (Element) tagFileNodes.item(i);
685             String path = findElementValue(tagFileNode, "path");
686             if (path == null)
687             {
688                 println("WARNING: "
689                     + tagLibrary.getPathDescription()
690                     + " contains a tag-file element with no path.  Skipping.");
691             }
692             else
693             {
694                 try
695                 {
696                     InputStream tagFileIn = tagLibrary.getResource(path);
697                     if (tagFileIn == null)
698                     {
699                         println("WARNING: Could not find tag file '"
700                             + path
701                             + "' for tag library "
702                             + tagLibrary.getPathDescription()
703                             + ".  Data will be incomplete for this tag.");
704                     }
705                     else
706                     {
707                         println("Parsing tag file: " + path);
708                         TagFile tagFile = TagFile.parse(tagFileIn);
709                         ArrayList directives = tagFile.getDirectives();
710                         for (int j = 0; j < directives.size(); j++)
711                         {
712                             Directive directive = (Directive) directives.get(j);
713                             String name = directive.getDirectiveName();
714                             if (name.equals("tag"))
715                             {
716                                 populateTagFileDetailsTagDirective(tagFileNode, doc, directive);
717                             }
718                             else if (name.equals("attribute"))
719                             {
720                                 populateTagFileDetailsAttributeDirective(tagFileNode, doc, directive);
721                             }
722                             else if (name.equals("variable"))
723                             {
724                                 populateTagFileDetailsVariableDirective(tagFileNode, doc, directive);
725                             }
726                         }
727 
728                         populateTagFileDetailsTagDefaults(tagFileNode, doc, path);
729                     }
730                 }
731                 catch (IOException e)
732                 {
733                     println("WARNING: Could not read tag file '"
734                         + path
735                         + "' for tag library "
736                         + tagLibrary.getPathDescription()
737                         + ".  Data will be incomplete for this tag."
738                         + "  Reason: "
739                         + e.getMessage());
740                 }
741                 catch (ParseException e)
742                 {
743                     println("WARNING: Could not parse tag file '"
744                         + path
745                         + "' for tag library "
746                         + tagLibrary.getPathDescription()
747                         + ".  Data will be incomplete for this tag."
748                         + "  Reason: "
749                         + e.getMessage());
750                 }
751                 catch (TokenMgrError e)
752                 {
753                     println("WARNING: Could not parse tag file '"
754                         + path
755                         + "' for tag library "
756                         + tagLibrary.getPathDescription()
757                         + ".  Data will be incomplete for this tag."
758                         + "  Reason: "
759                         + e.getMessage());
760                 }
761             }
762         }
763     }
764 
765     /***
766      * Populates the given tag-file node with information from the given tag directive.
767      * @param tagFileNode The tag-file element
768      * @param doc The document we're populating
769      * @param directive The tag directive to process.
770      */
771     private void populateTagFileDetailsTagDirective(Element tagFileNode, Document doc, Directive directive)
772     {
773         Iterator attributes = directive.getAttributes();
774         while (attributes.hasNext())
775         {
776             Attribute attribute = (Attribute) attributes.next();
777             String name = attribute.getName();
778             String value = attribute.getValue();
779             Element element;
780             if (name.equals("display-name")
781                 || name.equals("body-content")
782                 || name.equals("dynamic-attributes")
783                 || name.equals("description")
784                 || name.equals("example"))
785             {
786                 element = doc.createElementNS(Constants.NS_J2EE, name);
787                 element.appendChild(doc.createTextNode(value));
788                 tagFileNode.appendChild(element);
789             }
790             else if (name.equals("small-icon") || name.equals("large-icon"))
791             {
792                 NodeList icons = tagFileNode.getElementsByTagNameNS("*", "icon");
793                 Element icon;
794                 if (icons.getLength() == 0)
795                 {
796                     icon = doc.createElementNS(Constants.NS_J2EE, "icon");
797                     tagFileNode.appendChild(icon);
798                 }
799                 else
800                 {
801                     icon = (Element) icons.item(0);
802                 }
803                 element = doc.createElementNS(Constants.NS_J2EE, name);
804                 element.appendChild(doc.createTextNode(value));
805                 icon.appendChild(element);
806             }
807         }
808     }
809 
810     /***
811      * Populates the given tag-file node with default values, for those values not specified via tag directives.
812      * @param tagFileNode The tag-file element
813      * @param doc The document we're populating
814      * @param path The path to the tag file
815      */
816     private void populateTagFileDetailsTagDefaults(Element tagFileNode, Document doc, String path)
817     {
818         String displayName = path.substring(path.lastIndexOf('/') + 1);
819         displayName = displayName.substring(0, displayName.lastIndexOf('.'));
820         populateDefault(doc, tagFileNode, "display-name", displayName);
821         populateDefault(doc, tagFileNode, "body-content", "scriptless");
822         populateDefault(doc, tagFileNode, "dynamic-attributes", "false");
823     }
824 
825     /***
826      * Searches for the value of the given element. If no value is found, a default value is inserted.
827      * @param doc The document we're populating
828      * @param parent The element to examine
829      * @param tagName The name of the element we're looking for
830      * @param defaultValue The default value to insert, if not found.
831      */
832     private void populateDefault(Document doc, Element parent, String tagName, String defaultValue)
833     {
834         if (findElementValue(parent, tagName) == null)
835         {
836             Element element = doc.createElementNS(Constants.NS_J2EE, tagName);
837             element.appendChild(doc.createTextNode(defaultValue));
838             parent.appendChild(element);
839         }
840     }
841 
842     /***
843      * Populates the given tag-file node with information from the given attribute directive.
844      * @param tagFileNode The tag-file element
845      * @param doc The document we're populating
846      * @param directive The attribute directive to process.
847      */
848     private void populateTagFileDetailsAttributeDirective(Element tagFileNode, Document doc, Directive directive)
849     {
850         Iterator attributes = directive.getAttributes();
851         Element attributeNode = doc.createElementNS(Constants.NS_J2EE, "attribute");
852         tagFileNode.appendChild(attributeNode);
853         while (attributes.hasNext())
854         {
855             Attribute attribute = (Attribute) attributes.next();
856             String name = attribute.getName();
857             String value = attribute.getValue();
858             Element element;
859             if (name.equals("name")
860                 || name.equals("required")
861                 || name.equals("fragment")
862                 || name.equals("rtexprvalue")
863                 || name.equals("type")
864                 || name.equals("description"))
865             {
866                 element = doc.createElementNS(Constants.NS_J2EE, name);
867                 element.appendChild(doc.createTextNode(value));
868                 attributeNode.appendChild(element);
869             }
870         }
871         populateDefault(doc, attributeNode, "required", "false");
872         populateDefault(doc, attributeNode, "fragment", "false");
873         populateDefault(doc, attributeNode, "rtexprvalue", "false");
874 
875         // Default is String if this is not a fragment attribute, or
876         // javax.servlet.jsp.tagext.JspFragment if this is a fragment
877         // attribute.
878         String fragmentValue = findElementValue(attributeNode, "fragment");
879         boolean fragment = !((fragmentValue == null) || fragmentValue.equalsIgnoreCase("false"));
880         populateDefault(doc, attributeNode, "type", fragment
881             ? "javax.servlet.jsp.tagext.JspFragment"
882             : "java.lang.String");
883     }
884 
885     /***
886      * Populates the given tag-file node with information from the given variable directive.
887      * @param tagFileNode The tag-file element
888      * @param doc The document we're populating
889      * @param directive The variable directive to process.
890      */
891     private void populateTagFileDetailsVariableDirective(Element tagFileNode, Document doc, Directive directive)
892     {
893         Iterator attributes = directive.getAttributes();
894         Element variableNode = doc.createElementNS(Constants.NS_J2EE, "variable");
895         tagFileNode.appendChild(variableNode);
896         while (attributes.hasNext())
897         {
898             Attribute attribute = (Attribute) attributes.next();
899             String name = attribute.getName();
900             String value = attribute.getValue();
901             Element element;
902             if (name.equals("name-given")
903                 || name.equals("name-from-attribute")
904                 || name.equals("variable-class")
905                 || name.equals("declare")
906                 || name.equals("scope")
907                 || name.equals("description"))
908             {
909                 element = doc.createElementNS(Constants.NS_J2EE, name);
910                 element.appendChild(doc.createTextNode(value));
911                 variableNode.appendChild(element);
912             }
913         }
914         populateDefault(doc, variableNode, "variable-class", "java.lang.String");
915         populateDefault(doc, variableNode, "declare", "true");
916         populateDefault(doc, variableNode, "scope", "NESTED");
917     }
918 
919     /***
920      * Check to see if there's a short-name element. If not, the TLD is technically invalid, but supply one anyway, and
921      * give a warning.
922      * @param tagLibrary The tag library being populated
923      * @param doc The TLD DOM to populate.
924      * @param root The root element of the TLD DOM being populated.
925      */
926     private void checkOrAddShortName(TagLibrary tagLibrary, Document doc, Element root)
927     {
928         if (root.getElementsByTagNameNS("*", "short-name").getLength() == 0)
929         {
930             String prefix = "prefix" + substitutePrefix;
931             substitutePrefix++;
932             Element shortName = doc.createElementNS(Constants.NS_J2EE, "short-name");
933             shortName.appendChild(doc.createTextNode(prefix));
934             root.appendChild(shortName);
935             println("WARNING: "
936                 + tagLibrary.getPathDescription()
937                 + " is missing a short-name element.  Using "
938                 + prefix
939                 + ".");
940         }
941     }
942 
943     /***
944      * Check for empty attribute types and fill in the correct value. This is more difficult for the XSLT transform to
945      * do since the default is different depending on whether it is a fragment attribute or not.
946      * @param tagLibrary The tag library being populated
947      * @param doc The TLD DOM to populate.
948      * @param root The root element of the TLD DOM being populated.
949      */
950     private void checkOrAddAttributeType(TagLibrary tagLibrary, Document doc, Element root)
951     {
952         NodeList tagNodes = root.getElementsByTagNameNS("*", "tag");
953         for (int i = 0; i < tagNodes.getLength(); i++)
954         {
955             Element tagElement = (Element) tagNodes.item(i);
956             NodeList attributeNodes = tagElement.getElementsByTagNameNS("*", "attribute");
957             for (int j = 0; j < attributeNodes.getLength(); j++)
958             {
959                 Element attributeElement = (Element) attributeNodes.item(j);
960                 if (attributeElement.getElementsByTagNameNS("*", "type").getLength() == 0)
961                 {
962                     // No attribute type specified.
963                     String defaultType = "java.lang.String";
964 
965                     // Check if there is a fragment element set to true:
966                     String fragment = findElementValue(attributeElement, "fragment");
967                     if ((fragment != null)
968                         && (fragment.trim().equalsIgnoreCase("true") || fragment.trim().equalsIgnoreCase("yes")))
969                     {
970                         defaultType = "javax.servlet.jsp.tagext.JspFragment";
971                     }
972 
973                     // Create <type> element and append to attribute
974                     Element typeElement = doc.createElementNS(Constants.NS_J2EE, "type");
975                     typeElement.appendChild(doc.createTextNode(defaultType));
976                     attributeElement.appendChild(typeElement);
977                 }
978             }
979         }
980     }
981 
982     /***
983      * Generates all overview files, summarizing all TLDs.
984      */
985     private void generateOverview() throws IOException, TransformerException
986     {
987         generatePage(new File(this.outputDirectory, "index.html"), RESOURCE_PATH + "/index.html.xsl");
988         generatePage(new File(this.outputDirectory, "help-doc.html"), RESOURCE_PATH + "/help-doc.html.xsl");
989         generatePage(new File(this.outputDirectory, "overview-frame.html"), RESOURCE_PATH + "/overview-frame.html.xsl");
990         generatePage(new File(this.outputDirectory, "alltags-frame.html"), RESOURCE_PATH + "/alltags-frame.html.xsl");
991         generatePage(new File(this.outputDirectory, "alltags-noframe.html"), RESOURCE_PATH
992             + "/alltags-noframe.html.xsl");
993         generatePage(new File(this.outputDirectory, "overview-summary.html"), RESOURCE_PATH
994             + "/overview-summary.html.xsl");
995     }
996 
997     /***
998      * Generates all the detail folders for each TLD
999      */
1000     private void generateTLDDetail() throws IOException, GeneratorException, TransformerException
1001     {
1002         ArrayList shortNames = new ArrayList();
1003         Element root = summaryTLD.getDocumentElement();
1004         NodeList taglibs = root.getElementsByTagNameNS("*", "taglib");
1005         int size = taglibs.getLength();
1006         for (int i = 0; i < size; i++)
1007         {
1008             Element taglib = (Element) taglibs.item(i);
1009             String shortName = findElementValue(taglib, "short-name");
1010             String displayName = findElementValue(taglib, "display-name");
1011             if (shortNames.contains(shortName))
1012             {
1013                 throw new GeneratorException("Two tag libraries exist with "
1014                     + "the same short-name '"
1015                     + shortName
1016                     + "'.  This is not yet supported.");
1017             }
1018             String name = displayName;
1019             if (name == null)
1020                 name = shortName;
1021             println("Generating docs for " + name + "...");
1022             shortNames.add(shortName);
1023             File outDir = new File(this.outputDirectory, shortName);
1024             outDir.mkdir();
1025 
1026             // Generate information for each TLD:
1027             generateTLDDetail(outDir, shortName);
1028 
1029             // Generate information for each tag:
1030             NodeList tags = taglib.getElementsByTagNameNS("*", "tag");
1031             int numTags = tags.getLength();
1032             for (int j = 0; j < numTags; j++)
1033             {
1034                 Element tag = (Element) tags.item(j);
1035                 String tagName = findElementValue(tag, "name");
1036                 generateTagDetail(outDir, shortName, tagName);
1037             }
1038 
1039             // Generate information for each tag-file:
1040             NodeList tagFiles = taglib.getElementsByTagNameNS("*", "tag-file");
1041             int numTagFiles = tagFiles.getLength();
1042             for (int j = 0; j < numTagFiles; j++)
1043             {
1044                 Element tagFile = (Element) tagFiles.item(j);
1045                 String tagFileName = findElementValue(tagFile, "name");
1046                 generateTagDetail(outDir, shortName, tagFileName);
1047             }
1048 
1049             // Generate information for each function:
1050             NodeList functions = taglib.getElementsByTagNameNS("*", "function");
1051             int numFunctions = functions.getLength();
1052             for (int j = 0; j < numFunctions; j++)
1053             {
1054                 Element function = (Element) functions.item(j);
1055                 String functionName = findElementValue(function, "name");
1056                 generateFunctionDetail(outDir, shortName, functionName);
1057             }
1058         }
1059     }
1060 
1061     /***
1062      * Generates the detail content for the tag library with the given short-name. Files will be placed in outdir.
1063      */
1064     private void generateTLDDetail(File outDir, String shortName) throws IOException, TransformerException
1065     {
1066         HashMap parameters = new HashMap();
1067         parameters.put("tlddoc-shortName", shortName);
1068 
1069         generatePage(new File(outDir, "tld-frame.html"), RESOURCE_PATH + "/tld-frame.html.xsl", parameters);
1070         generatePage(new File(outDir, "tld-summary.html"), RESOURCE_PATH + "/tld-summary.html.xsl", parameters);
1071     }
1072 
1073     /***
1074      * Generates the detail content for the tag with the given name in the tag library with the given short-name. Files
1075      * will be placed in outdir.
1076      */
1077     private void generateTagDetail(File outDir, String shortName, String tagName) throws IOException,
1078         TransformerException
1079     {
1080         HashMap parameters = new HashMap();
1081         parameters.put("tlddoc-shortName", shortName);
1082         parameters.put("tlddoc-tagName", tagName);
1083 
1084         generatePage(new File(outDir, tagName + ".html"), RESOURCE_PATH + "/tag.html.xsl", parameters);
1085     }
1086 
1087     /***
1088      * Generates the detail content for the function with the given name in the tag library with the given short-name.
1089      * Files will be placed in outdir.
1090      */
1091     private void generateFunctionDetail(File outDir, String shortName, String functionName) throws IOException,
1092         TransformerException
1093     {
1094         HashMap parameters = new HashMap();
1095         parameters.put("tlddoc-shortName", shortName);
1096         parameters.put("tlddoc-functionName", functionName);
1097 
1098         generatePage(new File(outDir, functionName + ".fn.html"), RESOURCE_PATH + "/function.html.xsl", parameters);
1099     }
1100 
1101     /***
1102      * Searches through the given element and returns the value of the body of the element. Returns null if the element
1103      * was not found.
1104      */
1105     private String findElementValue(Element parent, String tagName)
1106     {
1107         String result = null;
1108         NodeList elements = parent.getElementsByTagNameNS("*", tagName);
1109         if (elements.getLength() >= 1)
1110         {
1111             Element child = (Element) elements.item(0);
1112             Node body = child.getFirstChild();
1113             if (body.getNodeType() == Node.TEXT_NODE)
1114             {
1115                 result = body.getNodeValue();
1116             }
1117         }
1118         return result;
1119     }
1120 
1121     /***
1122      * Generates the given page dynamically, by running the summary document through the given XSLT transform. Assumes
1123      * no parameters.
1124      * @param outFile The target file
1125      * @param inputXSL The stylesheet to use for the transformation
1126      */
1127     private void generatePage(File outFile, String inputXSL) throws IOException, TransformerException
1128     {
1129         generatePage(outFile, inputXSL, null);
1130     }
1131 
1132     /***
1133      * Generates the given page dynamically, by running the summary document through the given XSLT transform.
1134      * @param outFile The target file
1135      * @param inputXSL The stylesheet to use for the transformation
1136      * @param parameters String key and Object value pairs to pass to the transformation.
1137      */
1138     private void generatePage(File outFile, String inputXSL, Map parameters) throws IOException, TransformerException
1139     {
1140         InputStream xsl = getResourceAsStream(inputXSL);
1141 
1142         // FG patch to make transformer work on java 1.5
1143         // Transformer transformer = TransformerFactory.newInstance().newTransformer(new StreamSource( xsl ) );
1144         Transformer transformer = getTransformer(new StreamSource(xsl));
1145 
1146         if (parameters != null)
1147         {
1148             Iterator params = parameters.keySet().iterator();
1149             while (params.hasNext())
1150             {
1151                 String key = (String) params.next();
1152                 Object value = parameters.get(key);
1153                 transformer.setParameter(key, value);
1154             }
1155         }
1156         transformer.transform(new DOMSource(summaryTLD), new StreamResult(outFile));
1157     }
1158 
1159     /***
1160      * Copy the given resource to the given output file. The classloader that loaded TLDDocGenerator will be used to
1161      * find the resource. If xsltDirectory is not null, the files are copied from that directory instead.
1162      * @param outputFile The destination file
1163      * @param resource The resource to copy, starting with '/'
1164      */
1165     private void copyResourceToFile(File outputFile, String resource) throws IOException
1166     {
1167         InputStream in = getResourceAsStream(resource);
1168         OutputStream out = new FileOutputStream(outputFile);
1169         byte[] buffer = new byte[1024];
1170         int len;
1171         while ((len = in.read(buffer)) != -1)
1172         {
1173             out.write(buffer, 0, len);
1174         }
1175         out.close();
1176         in.close();
1177     }
1178 
1179     /***
1180      * Outputs the given message to stdout, only if !quiet
1181      */
1182     private void println(String message)
1183     {
1184         if (!quiet)
1185         {
1186             System.out.println(message);
1187         }
1188     }
1189 
1190     /***
1191      * If xsltDirectory is null, obtains an InputStream of the given resource from RESOURCE_PATH, using the class loader
1192      * that loaded TLDDocGenerator. Otherwise, finds the file in xsltDirectory and defaults to the default stylesheet if
1193      * it has not been overridden.
1194      * @param resource, must start with RESOURCE_PATH.
1195      * @return An InputStream for the given resource.
1196      */
1197     private InputStream getResourceAsStream(String resource)
1198     {
1199         InputStream result = null;
1200 
1201         if (xsltDirectory != null)
1202         {
1203             File resourceFile = new File(xsltDirectory, resource.substring(RESOURCE_PATH.length() + 1));
1204             try
1205             {
1206                 result = new FileInputStream(resourceFile);
1207             }
1208             catch (FileNotFoundException e)
1209             {
1210                 // result will be null and we'll default to default stylesheet
1211             }
1212         }
1213 
1214         if (result == null)
1215         {
1216             result = TLDDocGenerator.class.getResourceAsStream(resource);
1217         }
1218 
1219         return result;
1220     }
1221 
1222     /***
1223      * Displays a "success" message and plugs the tag library registry on java.net.
1224      */
1225     private void outputSuccessMessage()
1226     {
1227         println("\nDocumentation generated.  If your tag library is "
1228             + "intended for public use, \n"
1229             + "please add it to the repository at "
1230             + "http://taglibrarydoc.dev.java.net/");
1231     }
1232 }