View Javadoc

1   package org.apache.maven.jellydoc;
2   
3   /* ====================================================================
4    *   Copyright 2001-2004 The Apache Software Foundation.
5    *
6    *   Licensed under the Apache License, Version 2.0 (the "License");
7    *   you may not use this file except in compliance with the License.
8    *   You may obtain a copy of the License at
9    *
10   *       http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing, software
13   *   distributed under the License is distributed on an "AS IS" BASIS,
14   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   *   See the License for the specific language governing permissions and
16   *   limitations under the License.
17   * ====================================================================
18   */
19  
20  import java.beans.Introspector;
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.IOException;
24  import java.io.StringReader;
25  
26  import org.cyberneko.html.parsers.SAXParser;
27  import org.dom4j.io.OutputFormat;
28  import org.dom4j.io.XMLWriter;
29  import org.xml.sax.Attributes;
30  import org.xml.sax.ContentHandler;
31  import org.xml.sax.InputSource;
32  import org.xml.sax.SAXException;
33  import org.xml.sax.helpers.AttributesImpl;
34  import org.xml.sax.helpers.DefaultHandler;
35  
36  import com.sun.javadoc.ClassDoc;
37  import com.sun.javadoc.Doc;
38  import com.sun.javadoc.DocErrorReporter;
39  import com.sun.javadoc.Doclet;
40  import com.sun.javadoc.MethodDoc;
41  import com.sun.javadoc.PackageDoc;
42  import com.sun.javadoc.Parameter;
43  import com.sun.javadoc.RootDoc;
44  import com.sun.javadoc.SeeTag;
45  import com.sun.javadoc.Tag;
46  
47  /**
48   * Main Doclet class to generate Tag Library ML.  
49   * 
50   * @author <a href="mailto:gopi@aztecsoft.com">Gopinath M.R.</a>
51   * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
52   * @author Rodney Waldhoff
53   */
54  
55  // #### somehow we need to handle taglib inheritence...
56  
57  public class TagXMLDoclet extends Doclet {
58  
59      private String xmlns = "jvx";
60      private String encodingFormat = "UTF-8";
61      private String localName = "javadoc";
62      private ContentHandler cm = null;
63      private String targetFileName = null;
64      private Attributes emptyAtts = new AttributesImpl();
65  
66      public TagXMLDoclet (RootDoc root) throws Exception
67      {
68          readOptions(root);
69          File targetFile = new File(targetFileName);
70          targetFile.getParentFile().mkdirs();
71          FileOutputStream writer = new FileOutputStream(targetFileName);
72          OutputFormat format = OutputFormat.createPrettyPrint();
73          format.setEncoding(encodingFormat);
74          XMLWriter xmlWriter = new XMLWriter(writer, format);
75          try {
76              cm = xmlWriter;
77              cm.startDocument();
78              javadocXML(root);
79              cm.endDocument();
80              xmlWriter.close();
81          } 
82          catch (IOException e) {
83              xmlWriter.close();
84              throw e;
85          }
86      }
87  
88      /**
89       * Generates the xml for the tag libraries
90       */
91      private void javadocXML(RootDoc root) throws SAXException {
92          cm.startElement(xmlns, localName, "tags", emptyAtts);
93          PackageDoc[] packageArray = root.specifiedPackages();
94  
95          // Generate for packages.
96          for (int i = 0; i < packageArray.length; ++i) {
97              packageXML(packageArray[i]);
98          }
99  
100         cm.endElement(xmlns, localName, "tags");
101     }
102 
103     /**
104      * Generates doc for a tag library
105      */
106     private void packageXML(PackageDoc packageDoc) throws SAXException {
107         
108         System.out.println( "processing package: " + packageDoc.name());
109         
110         ClassDoc[] classArray = packageDoc.ordinaryClasses();
111         
112         // lets see if we find a Tag
113         boolean foundTag = false;
114         for (int i = 0; i < classArray.length; ++i) {
115             ClassDoc classDoc = classArray[i];
116             if ( isTag( classArray[i] ) ) {
117                 foundTag = true;
118                 break;
119             }
120         }
121         if ( ! foundTag ) {
122             System.out.println( "no tags found");
123             return;
124         }
125         
126         AttributesImpl atts = new AttributesImpl();
127         atts.addAttribute(xmlns, localName, "name", "String", packageDoc.name());
128         
129         String name = packageDoc.name();
130         int idx = name.lastIndexOf('.');
131         if ( idx > 0 ) {
132             name = name.substring(idx+1);
133         }
134         atts.addAttribute(xmlns, localName, "prefix", "String", name);
135         
136         String uri = "jelly:" + name;
137         
138         atts.addAttribute(xmlns, localName, "uri", "String", uri );
139         cm.startElement(xmlns, localName, "library", atts);
140 
141         // generate Doc element.
142         docXML(packageDoc);
143 
144         // generate tags
145         for (int i = 0; i < classArray.length; ++i) {
146             if ( isTag( classArray[i] ) ) {
147                 tagXML(classArray[i]);
148             }
149         }
150         cm.endElement(xmlns, localName, "library");
151     }
152 
153     /**
154      * @return true if this class is a Jelly Tag
155      */
156     private boolean isTag(ClassDoc classDoc) {
157         ClassDoc[] interfaceArray = classDoc.interfaces();
158         for (int i = 0; i < interfaceArray.length; ++i) {
159             String name = interfaceArray[i].qualifiedName();
160             if ("org.apache.commons.jelly.Tag".equals(name)) {
161                 return true;
162             }
163         }
164         ClassDoc base = classDoc.superclass();
165         if ( base != null ) {
166             return isTag(base);
167         }
168         return false;
169     }
170     
171     /**
172      * Generates doc for a tag
173      */
174     private void tagXML(ClassDoc classDoc) throws SAXException {
175         if (classDoc.isAbstract()) {
176             return;
177         }
178         
179         AttributesImpl atts = new AttributesImpl();
180         atts.addAttribute(xmlns, localName, "className", "String", classDoc.name());
181         String name = classDoc.name();
182         if ( name.endsWith( "Tag" ) ) {
183             name = name.substring(0, name.length() - 3 );
184         }
185         name = Introspector.decapitalize(name);
186         
187         
188         System.out.println( "processing tag: " + name);
189 
190         atts.addAttribute(xmlns, localName, "name", "String", name);
191         cm.startElement(xmlns, localName, "tag", atts);
192 
193         // generate "doc" sub-element
194         docXML(classDoc);
195 
196         // generate the attributes
197         propertiesXML(classDoc);
198         
199         // generate "method" sub-elements
200         cm.endElement(xmlns, localName, "tag");
201     }
202 
203     /**
204      * Generates doc for a tag property
205      */
206     private void propertiesXML(ClassDoc classDoc) throws SAXException {
207         MethodDoc[] methodArray = classDoc.methods();
208         for (int i = 0; i < methodArray.length; ++i) {
209             propertyXML(methodArray[i]);
210         }
211         ClassDoc base = classDoc.superclass();
212         if ( base != null ) {
213             propertiesXML( base );
214         }
215     }
216 
217 
218     /**
219      * Generates doc for a tag property
220      */
221     private void propertyXML(MethodDoc methodDoc) throws SAXException {
222         if ( ! methodDoc.isPublic() || methodDoc.isStatic() ) {
223             return;
224         }
225         String name = methodDoc.name();
226         if ( ! name.startsWith( "set" ) ) {
227             return;
228         }
229         Parameter[] parameterArray = methodDoc.parameters();
230         if ( parameterArray == null || parameterArray.length != 1 ) {
231             return;
232         }
233         Parameter parameter = parameterArray[0];
234         
235         name = name.substring(3);
236         name = Introspector.decapitalize(name);
237         
238         if ( name.equals( "body") || name.equals( "context" ) || name.equals( "parent" ) ) {
239             return;
240         }
241         AttributesImpl atts = new AttributesImpl();
242         atts.addAttribute(xmlns, localName, "name", "String", name);
243         atts.addAttribute(xmlns, localName, "type", "String", parameter.typeName());
244         
245         cm.startElement(xmlns, localName, "attribute", atts);
246 
247         // maybe do more semantics, like use custom tags to denote if its required, optional etc.
248         
249         // generate "doc" sub-element
250         docXML(methodDoc);
251 
252         cm.endElement(xmlns, localName, "attribute");
253     }
254 
255     /**
256      * Generates doc for element "doc"
257      */
258     private void docXML(Doc doc) throws SAXException {
259         cm.startElement(xmlns, localName, "doc", emptyAtts);
260         // handle the "comment" part, including {@link} tags
261         {
262             Tag[] tags = doc.inlineTags();
263             for(int i=0;i<tags.length;i++) {
264                 // if tags[i] is an @link tag
265                 if(tags[i] instanceof SeeTag) {                    
266                     String label = ((SeeTag)tags[i]).label();
267                     // if the label is null or empty, use the class#member part of the link
268                     if(null == label || "".equals(label)) { 
269                         StringBuffer buf = new StringBuffer();
270                         String className = ((SeeTag)tags[i]).referencedClassName();
271                         if("".equals(className)) { className = null; }
272                         String memberName = ((SeeTag)tags[i]).referencedMemberName();
273                         if("".equals(memberName)) { memberName = null; }
274                         if(null != className) {
275                             buf.append(className);
276                             if(null != memberName) {
277                                 buf.append(".");
278                             }
279                         }
280                         if(null != memberName) {
281                             buf.append(memberName);
282                         }
283                         label = buf.toString();
284                     }
285                     parseHTML(label);
286                 } else {
287                     parseHTML(tags[i].text());
288                 }
289             }
290         }
291         // handle the "tags" part
292         {
293             Tag[] tags = doc.tags();
294             for (int i = 0; i < tags.length; ++i) {
295                 javadocTagXML(tags[i]);
296             }
297         }
298         cm.endElement(xmlns, localName, "doc");
299     }
300 
301     protected void parseHTML(String text) throws SAXException {
302         SAXParser parser = new SAXParser();
303         parser.setProperty(
304             "http://cyberneko.org/html/properties/names/elems",
305             "lower"
306         );
307         parser.setProperty(
308             "http://cyberneko.org/html/properties/names/attrs",
309             "lower"
310         );
311         parser.setContentHandler(
312             new DefaultHandler() {
313                 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
314                     if ( validDocElementName( localName ) ) {
315                         cm.startElement(namespaceURI, localName, qName, atts);
316                     }
317                 }
318                 public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
319                     if ( validDocElementName( localName ) ) {
320                         cm.endElement(namespaceURI, localName, qName);
321                     }
322                 }
323                 public void characters(char[] ch, int start, int length) throws SAXException {
324                     cm.characters(ch, start, length);
325                 }
326             }
327         );
328         try {
329             parser.parse( new InputSource(new StringReader( text )) );
330         }
331         catch (IOException e) {
332             System.err.println( "This should never happen!" + e );
333         }
334     }
335 
336     /**
337      * @return true if the given name is a valid HTML markup element.
338      */
339     protected boolean validDocElementName(String name) {
340         return ! name.equalsIgnoreCase( "html" ) && ! name.equalsIgnoreCase( "body" );
341     }
342     
343     /**
344      * Generates doc for all tag elements.
345      */
346     private void javadocTagXML(Tag tag) throws SAXException {
347         String name = tag.name().substring(1) + "tag";
348         if (! tag.text().equals("")) {
349             cm.startElement(xmlns, localName, name, emptyAtts);
350             cm.characters(tag.text().toCharArray(), 0, tag.text().length());
351             cm.endElement(xmlns, localName, name);
352         }
353     }
354 
355     public static boolean start(RootDoc root) {
356         try {
357             new TagXMLDoclet(root);
358             return true;
359         } catch (Exception e) {
360             e.printStackTrace();
361             System.exit(1);
362             return false;
363         }
364     }
365     
366     private void readOptions(RootDoc root)
367     {
368         String[][] options = root.options();
369         for (int i = 0; i < options.length; i++)
370         {
371             String[] opt = options[i];
372             if (opt[0].equals("-d"))
373             {
374                 targetFileName = opt[1] + "/taglib.xml";
375             }
376             if (opt[0].equals("-encoding"))
377             {
378                 encodingFormat = opt[1];
379             }
380         }
381     }
382     
383     public static int optionLength(String option)
384     {
385         if(option.equals("-d"))
386         {
387             return 2;
388         }
389         if(option.equals("-encoding"))
390         {
391             return 2;
392         }
393         return 0;
394     }
395     
396     public static boolean validOptions(String options[][], 
397         DocErrorReporter reporter)
398     {
399         boolean foundEncodingOption = false;
400         boolean foundDirOption = false;
401         for (int i = 0; i < options.length; i++)
402         {
403             String[] opt = options[i];
404             if (opt[0].equals("-d"))
405             {
406                 if (foundDirOption)
407                 {
408                     reporter.printError("Only one -d option allowed.");
409                     return false;
410                 }
411                 else
412                 { 
413                     foundDirOption = true;
414                 }
415             } 
416             if (opt[0].equals("-encoding"))
417             {
418                 if (foundEncodingOption)
419                 {
420                     reporter.printError("Only one -encoding option allowed.");
421                     return false;
422                 }
423                 else
424                 { 
425                     foundEncodingOption = true;
426                 }
427             } 
428         }
429         if (!foundDirOption)
430         {
431             reporter.printError("Usage: javadoc -d <directory> -doclet TagXMLDoclet ...");
432             return false;
433         }
434         return true;
435     }
436 }