OSDN Git Service

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