4369782f9606dbd1a1c49ce075d0ee8c28faf3ea
[platform/upstream/gcc48.git] / libjava / classpath / tools / gnu / classpath / tools / doclets / AbstractDoclet.java
1 /* gnu.classpath.tools.doclets.AbstractDoclet
2    Copyright (C) 2004 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38 package gnu.classpath.tools.doclets;
39
40 import com.sun.javadoc.ClassDoc;
41 import com.sun.javadoc.ConstructorDoc;
42 import com.sun.javadoc.Doc;
43 import com.sun.javadoc.Doclet;
44 import com.sun.javadoc.ExecutableMemberDoc;
45 import com.sun.javadoc.FieldDoc;
46 import com.sun.javadoc.MethodDoc;
47 import com.sun.javadoc.PackageDoc;
48 import com.sun.javadoc.Parameter;
49 import com.sun.javadoc.RootDoc;
50 import com.sun.javadoc.Tag;
51 import com.sun.javadoc.Type;
52
53 import com.sun.tools.doclets.Taglet;
54
55 import gnu.classpath.tools.taglets.GnuExtendedTaglet;
56 import gnu.classpath.tools.taglets.AuthorTaglet;
57 import gnu.classpath.tools.taglets.CodeTaglet;
58 import gnu.classpath.tools.taglets.DeprecatedTaglet;
59 import gnu.classpath.tools.taglets.GenericTaglet;
60 import gnu.classpath.tools.taglets.SinceTaglet;
61 import gnu.classpath.tools.taglets.ValueTaglet;
62 import gnu.classpath.tools.taglets.VersionTaglet;
63 import gnu.classpath.tools.taglets.TagletContext;
64
65 import gnu.classpath.tools.IOToolkit;
66 import gnu.classpath.tools.FileSystemClassLoader;
67
68 import java.io.File;
69 import java.io.IOException;
70
71 import java.lang.reflect.Method;
72 import java.lang.reflect.Modifier;
73 import java.lang.reflect.InvocationTargetException;
74
75 import java.text.MessageFormat;
76
77 import java.util.ArrayList;
78 import java.util.Arrays;
79 import java.util.Collections;
80 import java.util.Comparator;
81 import java.util.HashMap;
82 import java.util.Iterator;
83 import java.util.LinkedHashMap;
84 import java.util.LinkedHashSet;
85 import java.util.LinkedList;
86 import java.util.List;
87 import java.util.Locale;
88 import java.util.Map;
89 import java.util.ResourceBundle;
90 import java.util.Set;
91 import java.util.SortedSet;
92 import java.util.StringTokenizer;
93 import java.util.TreeMap;
94 import java.util.TreeSet;
95
96 /**
97  *  An abstract Doclet implementation with helpers for common tasks
98  *  performed by Doclets.
99  */
100 public abstract class AbstractDoclet
101 {
102    /**
103     *  Mapping from tag type to Taglet for user Taglets specified on
104     *  the command line.
105     */
106    protected Map tagletMap = new LinkedHashMap();
107
108    /**
109     *  Stores the package groups specified in the user
110     *  options. Contains objects of type PackageGroup.
111     */
112    private List packageGroups = new LinkedList();
113
114    /**
115     *  The current classpath for loading taglet classes.
116     */
117    private String tagletPath;
118
119    /**
120     *  Keeps track of the tags mentioned by the user during option
121     *  processiong so that an error can be emitted if a tag is
122     *  mentioned more than once.
123     */
124    private List mentionedTags = new LinkedList();
125
126    public static int optionLength(String option) {
127       return instance.getOptionLength(option);
128    }
129
130    public static boolean validOptions(String[][] options) {
131       return true;
132    }
133
134    private static AbstractDoclet instance;
135
136    protected static void setInstance(AbstractDoclet instance)
137    {
138       AbstractDoclet.instance = instance;
139    }
140
141    protected abstract void run()
142       throws DocletConfigurationException, IOException;
143
144    public static boolean start(RootDoc rootDoc)
145    {
146       try {
147
148          instance.startInstance(rootDoc);
149          return true;
150       }
151       catch (DocletConfigurationException e) {
152          instance.printError(e.getMessage());
153          return false;
154       }
155       catch (Exception e) {
156          e.printStackTrace();
157          return false;
158       }
159    }
160
161    protected RootDoc getRootDoc()
162    {
163       return this.rootDoc;
164    }
165
166    private RootDoc rootDoc;
167
168    protected abstract InlineTagRenderer getInlineTagRenderer();
169
170    private void startInstance(RootDoc rootDoc)
171       throws DocletConfigurationException, IOException
172    {
173       this.rootDoc = rootDoc;
174
175       // Set the default Taglet order
176
177       registerTaglet(new VersionTaglet());
178       registerTaglet(new AuthorTaglet());
179       registerTaglet(new SinceTaglet(getInlineTagRenderer()));
180       registerTaglet(new StandardTaglet("serial"));
181       registerTaglet(new StandardTaglet("deprecated"));
182       registerTaglet(new StandardTaglet("see"));
183       registerTaglet(new StandardTaglet("param"));
184       registerTaglet(new StandardTaglet("return"));
185
186       registerTaglet(new ValueTaglet());
187       registerTaglet(new CodeTaglet());
188
189       // Process command line options
190
191       for (int i=0, ilim=rootDoc.options().length; i<ilim; ++i) {
192
193          String[] optionArr = rootDoc.options()[i];
194          String _optionTag = optionArr[0];
195
196          DocletOption option = (DocletOption)nameToOptionMap.get(_optionTag.toLowerCase());
197
198          if (null != option) {
199             option.set(optionArr);
200          }
201       }
202
203       // Enable/disable standard taglets based on user input
204
205       AuthorTaglet.setTagletEnabled(optionAuthor.getValue());
206       VersionTaglet.setTagletEnabled(optionVersion.getValue());
207       SinceTaglet.setTagletEnabled(!optionNoSince.getValue());
208       DeprecatedTaglet.setTagletEnabled(!optionNoDeprecated.getValue());
209
210       if (!getTargetDirectory().exists()) {
211          if (!getTargetDirectory().mkdirs()) {
212             throw new DocletConfigurationException("Cannot create target directory "
213                                                    + getTargetDirectory());
214          }
215       }
216
217       run();
218    }
219
220    public File getTargetDirectory()
221    {
222       return optionTargetDirectory.getValue();
223    }
224
225    private DocletOptionFile optionTargetDirectory =
226      new DocletOptionFile("-d",
227                           new File(System.getProperty("user.dir")));
228
229    private DocletOptionFlag optionNoEmailWarn =
230      new DocletOptionFlag("-noemailwarn");
231
232    private DocletOptionFlag optionAuthor =
233      new DocletOptionFlag("-author");
234
235    private DocletOptionFlag optionVersion =
236      new DocletOptionFlag("-version");
237
238    private DocletOptionFlag optionNoSince =
239      new DocletOptionFlag("-nosince");
240
241    private DocletOptionFlag optionNoDeprecated =
242      new DocletOptionFlag("-nodeprecated");
243
244    private DocletOptionGroup optionGroup =
245      new DocletOptionGroup("-group");
246
247    private DocletOptionPackageWildcard optionNoQualifier =
248      new DocletOptionPackageWildcard("-noqualifier", true);
249
250    private DocletOptionFlag optionDocFilesSubDirs =
251      new DocletOptionFlag("-docfilessubdirs");
252
253    private DocletOptionColonSeparated optionExcludeDocFilesSubDir =
254      new DocletOptionColonSeparated("-excludedocfilessubdir");
255
256    private DocletOptionTagletPath optionTagletPath =
257      new DocletOptionTagletPath("-tagletpath");
258
259    private DocletOptionTag optionTaglet =
260      new DocletOptionTag("-taglet");
261
262    private DocletOptionTag optionTag =
263      new DocletOptionTag("-tag");
264
265    private class DocletOptionTaglet
266       extends DocletOption
267    {
268       DocletOptionTaglet(String optionName)
269       {
270          super(optionName);
271       }
272
273       public int getLength()
274       {
275          return 2;
276       }
277
278       public boolean set(String[] optionArr)
279       {
280
281          boolean tagletLoaded = false;
282
283          String useTagletPath = AbstractDoclet.this.tagletPath;
284          if (null == useTagletPath) {
285             useTagletPath = System.getProperty("java.class.path");
286          }
287
288          try {
289             Class tagletClass;
290             try {
291                tagletClass
292                   = new FileSystemClassLoader(useTagletPath).loadClass(optionArr[1]);
293             }
294             catch (ClassNotFoundException e) {
295                // If not found on specified tagletpath, try default classloader
296                tagletClass
297                   = Class.forName(optionArr[1]);
298             }
299             Method registerTagletMethod
300                = tagletClass.getDeclaredMethod("register", new Class[] { java.util.Map.class });
301
302             if (!registerTagletMethod.getReturnType().equals(Void.TYPE)) {
303                printError("Taglet class '" + optionArr[1] + "' found, but register method doesn't return void.");
304             }
305             else if (registerTagletMethod.getExceptionTypes().length > 0) {
306                printError("Taglet class '" + optionArr[1] + "' found, but register method contains throws clause.");
307             }
308             else if ((registerTagletMethod.getModifiers() & (Modifier.STATIC | Modifier.PUBLIC | Modifier.ABSTRACT)) != (Modifier.STATIC | Modifier.PUBLIC)) {
309                printError("Taglet class '" + optionArr[1] + "' found, but register method isn't public static, or is abstract..");
310             }
311             else {
312                Map tempMap = new HashMap();
313                registerTagletMethod.invoke(null, new Object[] { tempMap });
314                tagletLoaded = true;
315                String name = (String)tempMap.keySet().iterator().next();
316                Taglet taglet = (Taglet)tempMap.get(name);
317                tagletMap.put(name, taglet);
318                mentionedTags.add(taglet);
319             }
320          }
321          catch (NoSuchMethodException e) {
322             printError("Taglet class '" + optionArr[1] + "' found, but doesn't contain the register method.");
323          }
324          catch (SecurityException e) {
325             printError("Taglet class '" + optionArr[1] + "' cannot be loaded: " + e.getMessage());
326          }
327          catch (InvocationTargetException e) {
328             printError("Taglet class '" + optionArr[1] + "' found, but register method throws exception: " + e.toString());
329          }
330          catch (IllegalAccessException e) {
331             printError("Taglet class '" + optionArr[1] + "' found, but there was a problem when accessing the register method: " + e.toString());
332          }
333          catch (IllegalArgumentException e) {
334             printError("Taglet class '" + optionArr[1] + "' found, but there was a problem when accessing the register method: " + e.toString());
335          }
336          catch (ClassNotFoundException e) {
337             printError("Taglet class '" + optionArr[1] + "' cannot be found.");
338          }
339          return tagletLoaded;
340       }
341    }
342
343    private class DocletOptionGroup
344       extends DocletOption
345    {
346       DocletOptionGroup(String optionName)
347       {
348          super(optionName);
349       }
350
351       public int getLength()
352       {
353          return 3;
354       }
355
356       public boolean set(String[] optionArr)
357       {
358          try {
359             PackageMatcher packageMatcher = new PackageMatcher();
360
361             StringTokenizer tokenizer = new StringTokenizer(optionArr[2], ":");
362             while (tokenizer.hasMoreTokens()) {
363                String packageWildcard = tokenizer.nextToken();
364                packageMatcher.addWildcard(packageWildcard);
365             }
366
367             SortedSet groupPackages = packageMatcher.filter(rootDoc.specifiedPackages());
368
369             packageGroups.add(new PackageGroup(optionArr[1], groupPackages));
370
371             return true;
372          }
373          catch (InvalidPackageWildcardException e) {
374             return false;
375          }
376       }
377    }
378
379
380    private class DocletOptionTagletPath
381       extends DocletOption
382    {
383       DocletOptionTagletPath(String optionName)
384       {
385          super(optionName);
386       }
387
388       public int getLength()
389       {
390          return 2;
391       }
392
393       public boolean set(String[] optionArr)
394       {
395          AbstractDoclet.this.tagletPath = optionArr[1];
396          return true;
397       }
398    }
399
400    private class DocletOptionTag
401       extends DocletOption
402    {
403       DocletOptionTag(String optionName)
404       {
405          super(optionName);
406       }
407
408       public int getLength()
409       {
410          return 2;
411       }
412
413       public boolean set(String[] optionArr)
414       {
415          String tagSpec = optionArr[1];
416          boolean validTagSpec = false;
417          int ndx1 = tagSpec.indexOf(':');
418          if (ndx1 < 0) {
419             Taglet taglet = (Taglet)tagletMap.get(tagSpec);
420             if (null == taglet) {
421                printError("There is no standard tag '" + tagSpec + "'.");
422             }
423             else {
424                if (mentionedTags.contains(taglet)) {
425                   printError("Tag '" + tagSpec + "' has been added or moved before.");
426                }
427                else {
428                   mentionedTags.add(taglet);
429
430                   // re-append taglet
431                   tagletMap.remove(tagSpec);
432                   tagletMap.put(tagSpec, taglet);
433                }
434             }
435          }
436          else {
437             int ndx2 = tagSpec.indexOf(':', ndx1 + 1);
438             if (ndx2 > ndx1 && ndx2 < tagSpec.length() - 1) {
439                String tagName = tagSpec.substring(0, ndx1);
440                String tagHead = null;
441                if (tagSpec.charAt(ndx2 + 1) == '\"') {
442                   if (tagSpec.charAt(tagSpec.length() - 1) == '\"') {
443                      tagHead = tagSpec.substring(ndx2 + 2, tagSpec.length() - 1);
444                      validTagSpec = true;
445                   }
446                }
447                else {
448                   tagHead = tagSpec.substring(ndx2 + 1);
449                   validTagSpec = true;
450                }
451
452                boolean tagScopeOverview = false;
453                boolean tagScopePackages = false;
454                boolean tagScopeTypes = false;
455                boolean tagScopeConstructors = false;
456                boolean tagScopeMethods = false;
457                boolean tagScopeFields = false;
458                boolean tagDisabled = false;
459
460             tag_option_loop:
461                for (int n=ndx1+1; n<ndx2; ++n) {
462                   switch (tagSpec.charAt(n)) {
463                   case 'X':
464                      tagDisabled = true;
465                      break;
466                   case 'a':
467                      tagScopeOverview = true;
468                      tagScopePackages = true;
469                      tagScopeTypes = true;
470                      tagScopeConstructors = true;
471                      tagScopeMethods = true;
472                      tagScopeFields = true;
473                      break;
474                   case 'o':
475                      tagScopeOverview = true;
476                      break;
477                   case 'p':
478                      tagScopePackages = true;
479                      break;
480                   case 't':
481                      tagScopeTypes = true;
482                      break;
483                   case 'c':
484                      tagScopeConstructors = true;
485                      break;
486                   case 'm':
487                      tagScopeMethods = true;
488                      break;
489                   case 'f':
490                      tagScopeFields = true;
491                      break;
492                   default:
493                      validTagSpec = false;
494                      break tag_option_loop;
495                   }
496                }
497
498                if (validTagSpec) {
499                   GenericTaglet taglet
500                      = new GenericTaglet(tagName,
501                                          tagHead,
502                                          tagScopeOverview,
503                                          tagScopePackages,
504                                          tagScopeTypes,
505                                          tagScopeConstructors,
506                                          tagScopeMethods,
507                                          tagScopeFields);
508                   taglet.setTagletEnabled(!tagDisabled);
509                   taglet.register(tagletMap);
510                   mentionedTags.add(taglet);
511                }
512             }
513          }
514          if (!validTagSpec) {
515             printError("Value for option -tag must be in format \"<tagname>:Xaoptcmf:<taghead>\".");
516          }
517          return validTagSpec;
518       }
519    }
520
521    private DocletOption[] commonOptions =
522       {
523          optionTargetDirectory,
524          optionAuthor,
525          optionVersion,
526          optionNoSince,
527          optionNoDeprecated,
528          optionGroup,
529          optionDocFilesSubDirs,
530          optionExcludeDocFilesSubDir,
531          optionTagletPath,
532          optionTaglet,
533          optionTag,
534       };
535
536    private void registerOptions()
537    {
538       if (!optionsRegistered) {
539          for (int i=0; i<commonOptions.length; ++i) {
540             DocletOption option = commonOptions[i];
541             registerOption(option);
542          }
543          DocletOption[] docletOptions = getOptions();
544          for (int i=0; i<docletOptions.length; ++i) {
545             DocletOption option = docletOptions[i];
546             registerOption(option);
547          }
548          optionsRegistered = true;
549       }
550    }
551
552    protected abstract DocletOption[] getOptions();
553
554    private boolean optionsRegistered = false;
555
556    private void registerOption(DocletOption option)
557    {
558       nameToOptionMap.put(option.getName(), option);
559    }
560
561    private Map nameToOptionMap = new HashMap();
562
563    private int getOptionLength(String optionName)
564    {
565       registerOptions();
566       DocletOption option = (DocletOption)nameToOptionMap.get(optionName.toLowerCase());
567       if (null != option) {
568          return option.getLength();
569       }
570       else {
571          return -1;
572       }
573    }
574
575    protected List getKnownDirectSubclasses(ClassDoc classDoc)
576    {
577       List result = new LinkedList();
578       if (!"java.lang.Object".equals(classDoc.qualifiedName())) {
579          ClassDoc[] classes = rootDoc.classes();
580          for (int i=0; i<classes.length; ++i) {
581             if (classDoc == classes[i].superclass()) {
582                result.add(classes[i]);
583             }
584          }
585       }
586       return result;
587    }
588
589    protected static class IndexKey
590       implements Comparable
591    {
592       private String name;
593       private String lowerName;
594
595       public IndexKey(String name)
596       {
597          this.name = name;
598          this.lowerName = name.toLowerCase();
599       }
600
601       public boolean equals(Object other)
602       {
603          return this.lowerName.equals(((IndexKey)other).lowerName);
604       }
605
606       public int hashCode()
607       {
608          return lowerName.hashCode();
609       }
610
611       public int compareTo(Object other)
612       {
613          return lowerName.compareTo(((IndexKey)other).lowerName);
614       }
615
616       public String getName()
617       {
618          return name;
619       }
620    }
621
622    private Map categorizedIndex;
623
624    protected Map getCategorizedIndex()
625    {
626       if (null == categorizedIndex) {
627          categorizedIndex = new LinkedHashMap();
628
629          Map indexMap = getIndexByName();
630          LinkedList keys = new LinkedList(); //indexMap.keySet().size());
631          keys.addAll(indexMap.keySet());
632          Collections.sort(keys);
633          Iterator it = keys.iterator(); //indexMap.keySet().iterator();
634          char previousCategoryLetter = '\0';
635          Character keyLetter = null;
636          while (it.hasNext()) {
637             IndexKey key = (IndexKey)it.next();
638             char firstChar = Character.toUpperCase(key.getName().charAt(0));
639             if (firstChar != previousCategoryLetter) {
640                keyLetter = new Character(firstChar);
641                previousCategoryLetter = firstChar;
642                categorizedIndex.put(keyLetter, new LinkedList());
643             }
644             List letterList = (List)categorizedIndex.get(keyLetter);
645             letterList.add(indexMap.get(key));
646          }
647       }
648
649       return categorizedIndex;
650    }
651
652
653    private Map indexByName;
654
655    protected Map getIndexByName()
656    {
657       if (null == indexByName) {
658          // Create index
659
660          // Collect index
661
662          indexByName = new HashMap(); //TreeMap();
663
664          // Add packages to index
665
666          PackageDoc[] packages = rootDoc.specifiedPackages();
667          for (int i=0, ilim=packages.length; i<ilim; ++i) {
668             PackageDoc c = packages[i];
669             if (c.name().length() > 0) {
670                indexByName.put(new IndexKey(c.name()), c);
671             }
672          }
673
674          // Add classes, fields and methods to index
675
676          ClassDoc[] sumclasses = rootDoc.classes();
677          for (int i=0, ilim=sumclasses.length; i<ilim; ++i) {
678             ClassDoc c = sumclasses[i];
679             if (null == c.containingClass()) {
680                indexByName.put(new IndexKey(c.name() + " " + c.containingPackage().name()), c);
681             }
682             else {
683                indexByName.put(new IndexKey(c.name().substring(c.containingClass().name().length() + 1)
684                                             + " " + c.containingClass().name() + " " + c.containingPackage().name()), c);
685             }
686             FieldDoc[] fields = c.fields();
687             for (int j=0, jlim=fields.length; j<jlim; ++j) {
688                indexByName.put(new IndexKey(fields[j].name() + " " + fields[j].containingClass().name() + " " + fields[j].containingPackage().name()), fields[j]);
689             }
690             MethodDoc[] methods = c.methods();
691             for (int j=0, jlim=methods.length; j<jlim; ++j) {
692                MethodDoc method = methods[j];
693                indexByName.put(new IndexKey(method.name() + method.signature() + " " + method.containingClass().name() + " " + method.containingPackage().name()), method);
694             }
695             ConstructorDoc[] constructors = c.constructors();
696             for (int j=0, jlim=constructors.length; j<jlim; ++j) {
697                ConstructorDoc constructor = constructors[j];
698                indexByName.put(new IndexKey(constructor.name() + constructor.signature() + " " + constructor.containingClass().name() + " " + constructor.containingPackage().name()), constructor);
699             }
700          }
701       }
702       return indexByName;
703    }
704
705    private void registerTaglet(Taglet taglet)
706    {
707       tagletMap.put(taglet.getName(), taglet);
708    }
709
710    protected void printTaglets(Tag[] tags, TagletContext context, TagletPrinter output, boolean inline)
711    {
712       for (Iterator it = tagletMap.keySet().iterator(); it.hasNext(); ) {
713          String tagName = (String)it.next();
714          Object o = tagletMap.get(tagName);
715          Taglet taglet = (Taglet)o;
716          Doc doc = context.getDoc();
717          if (inline == taglet.isInlineTag()
718              && ((doc == null
719                   && taglet.inOverview())
720                  || (doc != null
721                      && ((doc.isConstructor() && taglet.inConstructor())
722                          || (doc.isField() && taglet.inField())
723                          || (doc.isMethod() && taglet.inMethod())
724                          || (doc instanceof PackageDoc && taglet.inPackage())
725                          || ((doc.isClass() || doc.isInterface()) && taglet.inType()))))) {
726
727             List tagsOfThisType = new LinkedList();
728             for (int i=0; i<tags.length; ++i) {
729                if (tags[i].name().substring(1).equals(tagName)) {
730                   tagsOfThisType.add(tags[i]);
731                }
732             }
733
734             Tag[] tagletTags = (Tag[])tagsOfThisType.toArray(new Tag[tagsOfThisType.size()]);
735
736             String tagletString;
737             if (taglet instanceof StandardTaglet) {
738                tagletString = renderTag(tagName, tagletTags, context);
739             }
740             else if (taglet instanceof GnuExtendedTaglet) {
741                tagletString = ((GnuExtendedTaglet)taglet).toString(tagletTags, context);
742             }
743             else {
744                tagletString = taglet.toString(tagletTags);
745             }
746             if (null != tagletString) {
747                output.printTagletString(tagletString);
748             }
749          }
750       }
751    }
752
753    protected void printInlineTaglet(Tag tag, TagletContext context, TagletPrinter output)
754    {
755       Taglet taglet = (Taglet)tagletMap.get(tag.name().substring(1));
756       if (null != taglet) {
757          String tagletString;
758          if (taglet instanceof GnuExtendedTaglet) {
759             tagletString = ((GnuExtendedTaglet)taglet).toString(tag, context);
760          }
761          else {
762             tagletString = taglet.toString(tag);
763          }
764          if (null != tagletString) {
765             output.printTagletString(tagletString);
766          }
767       }
768       else {
769          printWarning("Unknown tag: " + tag.name());
770       }
771    }
772
773    protected void printMainTaglets(Tag[] tags, TagletContext context, TagletPrinter output)
774    {
775       printTaglets(tags, context, output, false);
776    }
777
778    /**
779     *  @param usedClassToPackagesMap  ClassDoc to (PackageDoc to (UsageType to (Set of Doc)))
780     */
781    private void addUsedBy(Map usedClassToPackagesMap,
782                           ClassDoc usedClass, UsageType usageType, Doc user, PackageDoc userPackage)
783    {
784       Map packageToUsageTypeMap = (Map)usedClassToPackagesMap.get(usedClass);
785       if (null == packageToUsageTypeMap) {
786          packageToUsageTypeMap = new HashMap();
787          usedClassToPackagesMap.put(usedClass, packageToUsageTypeMap);
788       }
789
790       Map usageTypeToUsersMap = (Map)packageToUsageTypeMap.get(userPackage);
791       if (null == usageTypeToUsersMap) {
792          usageTypeToUsersMap = new TreeMap();
793          packageToUsageTypeMap.put(userPackage, usageTypeToUsersMap);
794       }
795
796       Set userSet = (Set)usageTypeToUsersMap.get(usageType);
797       if (null == userSet) {
798          userSet = new TreeSet(); // FIXME: we need the collator from Main here
799          usageTypeToUsersMap.put(usageType, userSet);
800       }
801       userSet.add(user);
802    }
803
804    /**
805     *  Create the cross reference database.
806     */
807    private Map collectUsage() {
808
809       Map _usedClassToPackagesMap = new HashMap();
810
811       ClassDoc[] classes = rootDoc.classes();
812       for (int i = 0, ilim = classes.length; i < ilim; ++ i) {
813          ClassDoc clazz = classes[i];
814
815          if (clazz.isInterface()) {
816             // classes implementing
817             InterfaceRelation relation
818                = (InterfaceRelation)getInterfaceRelations().get(clazz);
819             Iterator it = relation.implementingClasses.iterator();
820             while (it.hasNext()) {
821                ClassDoc implementor = (ClassDoc)it.next();
822                addUsedBy(_usedClassToPackagesMap,
823                          clazz, UsageType.CLASS_IMPLEMENTING, implementor, implementor.containingPackage());
824             }
825          }
826          else {
827             // classes derived from
828             for (ClassDoc superclass = clazz.superclass(); superclass != null;
829                  superclass = superclass.superclass()) {
830                addUsedBy(_usedClassToPackagesMap,
831                          superclass, UsageType.CLASS_DERIVED_FROM, clazz, clazz.containingPackage());
832             }
833          }
834
835          FieldDoc[] fields = clazz.fields();
836          for (int j = 0, jlim = fields.length; j < jlim; ++ j) {
837             FieldDoc field = fields[j];
838
839             // fields of type
840             ClassDoc fieldType = field.type().asClassDoc();
841             if (null != fieldType) {
842                addUsedBy(_usedClassToPackagesMap,
843                          fieldType, UsageType.FIELD_OF_TYPE,
844                          field, clazz.containingPackage());
845             }
846          }
847
848          MethodDoc[] methods = clazz.methods();
849          for (int j = 0, jlim = methods.length; j < jlim; ++ j) {
850             MethodDoc method = methods[j];
851
852             // methods with return type
853
854             ClassDoc returnType = method.returnType().asClassDoc();
855             if (null != returnType) {
856                addUsedBy(_usedClassToPackagesMap,
857                          returnType, UsageType.METHOD_WITH_RETURN_TYPE,
858                          method, clazz.containingPackage());
859             }
860             Parameter[] parameters = method.parameters();
861             for (int k=0; k<parameters.length; ++k) {
862
863                // methods with parameter type
864
865                Parameter parameter = parameters[k];
866                ClassDoc parameterType = parameter.type().asClassDoc();
867                if (null != parameterType) {
868                   addUsedBy(_usedClassToPackagesMap,
869                             parameterType, UsageType.METHOD_WITH_PARAMETER_TYPE,
870                             method, clazz.containingPackage());
871                }
872             }
873
874             // methods which throw
875
876             ClassDoc[] thrownExceptions = method.thrownExceptions();
877             for (int k = 0, klim = thrownExceptions.length; k < klim; ++ k) {
878                ClassDoc thrownException = thrownExceptions[k];
879                addUsedBy(_usedClassToPackagesMap,
880                          thrownException, UsageType.METHOD_WITH_THROWN_TYPE,
881                          method, clazz.containingPackage());
882             }
883          }
884
885          ConstructorDoc[] constructors = clazz.constructors();
886          for (int j = 0, jlim = constructors.length; j < jlim; ++ j) {
887
888             ConstructorDoc constructor = constructors[j];
889
890             Parameter[] parameters = constructor.parameters();
891             for (int k = 0, klim = parameters.length; k < klim; ++ k) {
892
893                // constructors with parameter type
894
895                Parameter parameter = parameters[k];
896                ClassDoc parameterType = parameter.type().asClassDoc();
897                if (null != parameterType) {
898                   addUsedBy(_usedClassToPackagesMap,
899                             parameterType, UsageType.CONSTRUCTOR_WITH_PARAMETER_TYPE,
900                             constructor, clazz.containingPackage());
901                }
902             }
903
904             // constructors which throw
905
906             ClassDoc[] thrownExceptions = constructor.thrownExceptions();
907             for (int k = 0, klim = thrownExceptions.length; k < klim; ++ k) {
908                ClassDoc thrownException = thrownExceptions[k];
909                addUsedBy(_usedClassToPackagesMap,
910                          thrownException, UsageType.CONSTRUCTOR_WITH_THROWN_TYPE,
911                          constructor, clazz.containingPackage());
912             }
913          }
914       }
915       return _usedClassToPackagesMap;
916    }
917
918    private Map usedClassToPackagesMap = null;
919
920    protected Map getUsageOfClass(ClassDoc classDoc)
921    {
922       if (null == this.usedClassToPackagesMap) {
923          this.usedClassToPackagesMap = collectUsage();
924       }
925       return (Map)this.usedClassToPackagesMap.get(classDoc);
926    }
927
928    protected static class UsageType
929       implements Comparable
930    {
931       public static final UsageType CLASS_DERIVED_FROM = new UsageType("class-derived-from");
932       public static final UsageType CLASS_IMPLEMENTING = new UsageType("class-implementing");
933       public static final UsageType FIELD_OF_TYPE = new UsageType("field-of-type");
934       public static final UsageType METHOD_WITH_RETURN_TYPE = new UsageType("method-with-return-type");
935       public static final UsageType METHOD_WITH_PARAMETER_TYPE = new UsageType("method-with-parameter-type");
936       public static final UsageType METHOD_WITH_THROWN_TYPE = new UsageType("method-with-thrown-type");
937       public static final UsageType CONSTRUCTOR_WITH_PARAMETER_TYPE = new UsageType("constructor-with-parameter-type");
938       public static final UsageType CONSTRUCTOR_WITH_THROWN_TYPE = new UsageType("constructor-with-thrown-type");
939       private String id;
940
941       private UsageType(String id)
942       {
943          this.id = id;
944       }
945
946       public int compareTo(Object other)
947       {
948          return this.id.compareTo(((UsageType)other).id);
949       }
950
951       public String toString() {
952          return "UsageType{id=" + id + "}";
953       }
954
955       public String getId() {
956          return id;
957       }
958    }
959
960    private ResourceBundle resources;
961
962    protected String getString(String key)
963    {
964       if (null == resources) {
965          Locale currentLocale = Locale.getDefault();
966
967          resources
968             = ResourceBundle.getBundle("htmldoclet.HtmlDoclet", currentLocale);
969       }
970
971       return resources.getString(key);
972    }
973
974    protected String format(String key, String value1)
975    {
976       return MessageFormat.format(getString(key), new Object[] { value1 });
977    }
978
979    protected List getPackageGroups()
980    {
981       return packageGroups;
982    }
983
984    protected void copyDocFiles(File sourceDir, File targetDir)
985       throws IOException
986    {
987       File sourceDocFiles = new File(sourceDir, "doc-files");
988       File targetDocFiles = new File(targetDir, "doc-files");
989
990       if (sourceDocFiles.exists()) {
991          IOToolkit.copyDirectory(sourceDocFiles,
992                                  targetDocFiles,
993                                  optionDocFilesSubDirs.getValue(),
994                                  optionExcludeDocFilesSubDir.getComponents());
995       }
996    }
997
998    private Set sourcePaths;
999
1000    /**
1001     *  Try to determine the source directory for the given package by
1002     *  looking at the path specified by -sourcepath, or the current
1003     *  directory if -sourcepath hasn't been specified.
1004     *
1005     *  @throws IOException if the source directory couldn't be
1006     *  located.
1007     *
1008     *  @return List of File
1009     */
1010    protected List getPackageSourceDirs(PackageDoc packageDoc)
1011       throws IOException
1012    {
1013       if (null == sourcePaths) {
1014          for (int i=0; i<rootDoc.options().length; ++i) {
1015             if ("-sourcepath".equals(rootDoc.options()[i][0])
1016                 || "-s".equals(rootDoc.options()[i][0])) {
1017                sourcePaths = new LinkedHashSet();
1018                String sourcepathString = rootDoc.options()[i][1];
1019                StringTokenizer st = new StringTokenizer(sourcepathString, File.pathSeparator);
1020                while (st.hasMoreTokens()) {
1021                   sourcePaths.add(new File(st.nextToken()));
1022                }
1023             }
1024          }
1025          if (null == sourcePaths) {
1026             sourcePaths = new LinkedHashSet();
1027             sourcePaths.add(new File(System.getProperty("user.dir")));
1028          }
1029       }
1030
1031       String packageSubDir = packageDoc.name().replace('.', File.separatorChar);
1032       Iterator it = sourcePaths.iterator();
1033       List result = new LinkedList();
1034       while (it.hasNext()) {
1035          File pathComponent = (File)it.next();
1036          File packageDir = new File(pathComponent, packageSubDir);
1037          if (packageDir.exists()) {
1038             result.add(packageDir);
1039          }
1040       }
1041       if (result.isEmpty()) {
1042          throw new IOException("Couldn't locate source directory for package " + packageDoc.name());
1043       }
1044       else {
1045          return result;
1046       }
1047    }
1048
1049    protected File getSourceFile(ClassDoc classDoc)
1050       throws IOException
1051    {
1052       List packageDirs = getPackageSourceDirs(classDoc.containingPackage());
1053       Iterator it = packageDirs.iterator();
1054       while (it.hasNext()) {
1055          File packageDir = (File)it.next();
1056          File sourceFile = new File(packageDir, getOuterClassDoc(classDoc).name() + ".java");
1057          if (sourceFile.exists()) {
1058             return sourceFile;
1059          }
1060       }
1061
1062       throw new IOException("Couldn't locate source file for class " + classDoc.qualifiedTypeName());
1063    }
1064
1065    protected void printError(String error)
1066    {
1067       if (null != rootDoc) {
1068          rootDoc.printError(error);
1069       }
1070       else {
1071          System.err.println("ERROR: "+error);
1072       }
1073    }
1074
1075    protected void printWarning(String warning)
1076    {
1077       if (null != rootDoc) {
1078          rootDoc.printWarning(warning);
1079       }
1080       else {
1081          System.err.println("WARNING: "+warning);
1082       }
1083    }
1084
1085    protected void printNotice(String notice)
1086    {
1087       if (null != rootDoc) {
1088          rootDoc.printNotice(notice);
1089       }
1090       else {
1091          System.err.println(notice);
1092       }
1093    }
1094
1095    protected static ClassDoc getOuterClassDoc(ClassDoc classDoc)
1096    {
1097       while (null != classDoc.containingClass()) {
1098          classDoc = classDoc.containingClass();
1099       }
1100       return classDoc;
1101    }
1102
1103    private SortedSet allPackages;
1104
1105    protected Set getAllPackages()
1106    {
1107       if (null == this.allPackages) {
1108          allPackages = new TreeSet();
1109          PackageDoc[] specifiedPackages = rootDoc.specifiedPackages();
1110          for (int i=0; i<specifiedPackages.length; ++i) {
1111             allPackages.add(specifiedPackages[i]);
1112          }
1113          ClassDoc[] specifiedClasses = rootDoc.specifiedClasses();
1114          for (int i=0; i<specifiedClasses.length; ++i) {
1115             allPackages.add(specifiedClasses[i].containingPackage());
1116          }
1117       }
1118       return this.allPackages;
1119    }
1120
1121    protected boolean omitPackageQualifier(PackageDoc packageDoc)
1122    {
1123       if (!optionNoQualifier.isSpecified()) {
1124          return false;
1125       }
1126       else {
1127          return optionNoQualifier.match(packageDoc);
1128       }
1129    }
1130
1131    protected String possiblyQualifiedName(Type type)
1132    {
1133       if (null == type.asClassDoc()
1134           || !omitPackageQualifier(type.asClassDoc().containingPackage())) {
1135          return type.qualifiedTypeName();
1136       }
1137       else {
1138          return type.typeName();
1139       }
1140    }
1141
1142    protected static class InterfaceRelation
1143    {
1144       public Set superInterfaces;
1145       public Set subInterfaces;
1146       public Set implementingClasses;
1147
1148       public InterfaceRelation()
1149       {
1150          superInterfaces = new TreeSet();
1151          subInterfaces = new TreeSet();
1152          implementingClasses = new TreeSet();
1153       }
1154    }
1155
1156    private void addAllInterfaces(ClassDoc classDoc, Set allInterfaces)
1157    {
1158       ClassDoc[] interfaces = classDoc.interfaces();
1159       for (int i=0; i<interfaces.length; ++i) {
1160          allInterfaces.add(interfaces[i]);
1161          addAllInterfaces(interfaces[i], allInterfaces);
1162       }
1163    }
1164
1165    private Map allSubClasses;
1166
1167    protected Map getAllSubClasses()
1168    {
1169       if (null == allSubClasses) {
1170          allSubClasses = new HashMap();
1171
1172          ClassDoc[] classDocs = getRootDoc().classes();
1173          for (int i=0; i<classDocs.length; ++i) {
1174             if (!classDocs[i].isInterface()) {
1175                for (ClassDoc cd = classDocs[i].superclass();
1176                     null != cd;
1177                     cd = cd.superclass()) {
1178
1179                   if (!cd.qualifiedTypeName().equals("java.lang.Object")) {
1180                      List subClasses = (List)allSubClasses.get(cd);
1181                      if (null == subClasses) {
1182                         subClasses = new LinkedList();
1183                         allSubClasses.put(cd, subClasses);
1184                      }
1185                      subClasses.add(classDocs[i]);
1186                   }
1187                }
1188             }
1189          }
1190       }
1191       return allSubClasses;
1192    }
1193
1194    private Map interfaceRelations;
1195
1196    private void addToInterfaces(ClassDoc classDoc, ClassDoc[] interfaces)
1197    {
1198       for (int i=0; i<interfaces.length; ++i) {
1199          InterfaceRelation interfaceRelation
1200             = (InterfaceRelation)interfaceRelations.get(interfaces[i]);
1201          if (null == interfaceRelation) {
1202             interfaceRelation = new InterfaceRelation();
1203             interfaceRelations.put(interfaces[i], interfaceRelation);
1204          }
1205          interfaceRelation.implementingClasses.add(classDoc);
1206          addToInterfaces(classDoc, interfaces[i].interfaces());
1207       }
1208    }
1209
1210    protected Map getInterfaceRelations()
1211    {
1212       if (null == interfaceRelations) {
1213          interfaceRelations = new HashMap();
1214
1215          ClassDoc[] classDocs = getRootDoc().classes();
1216          for (int i=0; i<classDocs.length; ++i) {
1217             if (classDocs[i].isInterface()) {
1218                InterfaceRelation relation = new InterfaceRelation();
1219                addAllInterfaces(classDocs[i], relation.superInterfaces);
1220                interfaceRelations.put(classDocs[i], relation);
1221             }
1222          }
1223
1224          Iterator it = interfaceRelations.keySet().iterator();
1225          while (it.hasNext()) {
1226             ClassDoc interfaceDoc = (ClassDoc)it.next();
1227             InterfaceRelation relation
1228                = (InterfaceRelation)interfaceRelations.get(interfaceDoc);
1229             Iterator superIt = relation.superInterfaces.iterator();
1230             while (superIt.hasNext()) {
1231                ClassDoc superInterfaceDoc = (ClassDoc)superIt.next();
1232                InterfaceRelation superRelation
1233                   = (InterfaceRelation)interfaceRelations.get(superInterfaceDoc);
1234                if (null != superRelation) {
1235                   superRelation.subInterfaces.add(interfaceDoc);
1236                }
1237             }
1238          }
1239
1240          for (int i=0; i<classDocs.length; ++i) {
1241             if (!classDocs[i].isInterface()) {
1242                for (ClassDoc cd = classDocs[i]; null != cd; cd = cd.superclass()) {
1243                   addToInterfaces(classDocs[i], cd.interfaces());
1244                }
1245             }
1246          }
1247       }
1248
1249       return interfaceRelations;
1250    }
1251
1252    private Map sortedMethodMap = new HashMap();
1253
1254    protected MethodDoc[] getSortedMethods(ClassDoc classDoc)
1255    {
1256       MethodDoc[] result = (MethodDoc[])sortedMethodMap.get(classDoc);
1257       if (null == result) {
1258          MethodDoc[] methods = classDoc.methods();
1259          result = (MethodDoc[])methods.clone();
1260          Arrays.sort(result);
1261          return result;
1262       }
1263       return result;
1264    }
1265
1266    private Map sortedConstructorMap = new HashMap();
1267
1268    protected ConstructorDoc[] getSortedConstructors(ClassDoc classDoc)
1269    {
1270       ConstructorDoc[] result = (ConstructorDoc[])sortedConstructorMap.get(classDoc);
1271       if (null == result) {
1272          ConstructorDoc[] constructors = classDoc.constructors();
1273          result = (ConstructorDoc[])constructors.clone();
1274          Arrays.sort(result);
1275          return result;
1276       }
1277       return result;
1278    }
1279
1280    private Map sortedFieldMap = new HashMap();
1281
1282    protected FieldDoc[] getSortedFields(ClassDoc classDoc)
1283    {
1284       FieldDoc[] result = (FieldDoc[])sortedFieldMap.get(classDoc);
1285       if (null == result) {
1286          FieldDoc[] fields = classDoc.fields();
1287          result = (FieldDoc[])fields.clone();
1288          Arrays.sort(result);
1289          return result;
1290       }
1291       return result;
1292    }
1293
1294    private Map sortedInnerClassMap = new HashMap();
1295
1296    protected ClassDoc[] getSortedInnerClasses(ClassDoc classDoc)
1297    {
1298       ClassDoc[] result = (ClassDoc[])sortedInnerClassMap.get(classDoc);
1299       if (null == result) {
1300          ClassDoc[] innerClasses = classDoc.innerClasses();
1301          result = (ClassDoc[])innerClasses.clone();
1302          Arrays.sort(result);
1303          return result;
1304       }
1305       return result;
1306    }
1307
1308    protected abstract String renderTag(String tagName, Tag[] tags, TagletContext context);
1309
1310    protected abstract String getDocletVersion();
1311
1312    protected SortedSet getThrownExceptions(ExecutableMemberDoc execMemberDoc)
1313    {
1314       SortedSet result = new TreeSet();
1315       ClassDoc[] thrownExceptions = execMemberDoc.thrownExceptions();
1316       for (int j=0; j<thrownExceptions.length; ++j) {
1317          result.add(thrownExceptions[j]);
1318       }
1319       return result;
1320    }
1321
1322    protected boolean isUncheckedException(ClassDoc classDoc)
1323    {
1324       if (classDoc.isException()) {
1325          while (null != classDoc) {
1326             if (classDoc.qualifiedTypeName().equals("java.lang.RuntimeException")) {
1327                return true;
1328             }
1329             classDoc = classDoc.superclass();
1330          }
1331          return false;
1332       }
1333       else {
1334          return false;
1335       }
1336    }
1337
1338    protected FieldDoc findField(ClassDoc classDoc, String fieldName)
1339    {
1340       for (ClassDoc cd = classDoc; cd != null; cd = cd.superclass()) {
1341          FieldDoc[] fields = cd.fields(false);
1342          for (int i=0; i<fields.length; ++i) {
1343             if (fields[i].name().equals(fieldName)) {
1344                return fields[i];
1345             }
1346          }
1347       }
1348       return null;
1349    }
1350
1351    private Map implementedInterfacesCache = new HashMap();
1352
1353    protected Set getImplementedInterfaces(ClassDoc classDoc)
1354    {
1355       Set result = (Set)implementedInterfacesCache.get(classDoc);
1356       if (null == result) {
1357          result = new TreeSet();
1358
1359          for (ClassDoc cd = classDoc; cd != null; cd = cd.superclass()) {
1360             ClassDoc[] interfaces = cd.interfaces();
1361             for (int i=0; i<interfaces.length; ++i) {
1362                result.add(interfaces[i]);
1363                InterfaceRelation relation
1364                   = (InterfaceRelation)getInterfaceRelations().get(interfaces[i]);
1365                if (null != relation) {
1366                   result.addAll(relation.superInterfaces);
1367                }
1368             }
1369          }
1370
1371          implementedInterfacesCache.put(classDoc, result);
1372       }
1373
1374       return result;
1375    }
1376
1377    protected boolean isSinglePackage()
1378    {
1379       return getAllPackages().size() <= 1;
1380    }
1381
1382    protected PackageDoc getSinglePackage()
1383    {
1384       return (PackageDoc)getAllPackages().iterator().next();
1385    }
1386 }