Imported Upstream version 4.8.1
[platform/upstream/gcc48.git] / libjava / classpath / tools / gnu / classpath / tools / gjdoc / RootDocImpl.java
1 /* gnu.classpath.tools.gjdoc.RootDocImpl
2    Copyright (C) 2001, 2007, 2012 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.gjdoc;
39
40 import com.sun.javadoc.*;
41 import java.util.*;
42 import java.io.*;
43 import java.lang.reflect.*;
44
45 public class RootDocImpl
46    extends DocImpl
47    implements GjdocRootDoc {
48
49    private ErrorReporter reporter = new ErrorReporter();
50
51    private RandomAccessFile rawCommentCache;
52
53    /**
54     *  All options and their corresponding values which are not recognized
55     *  by Gjdoc. These are passed to the Doclet as "custom options".
56     *  Each element in this array is again a String array, with the
57     *  option name as first element (including prefix dash) and possible
58     *  option values as following elements.
59     */
60    private String[][] customOptionArr;
61
62    /**
63     *  All source files explicitly specified on the command line.
64     *
65     *  @contains File
66     */
67    private List specifiedSourceFiles = new LinkedList();
68
69    /**
70     *  The names of all packages explicitly specified on the
71     *  command line.
72     *
73     *  @contains String
74     */
75    private Set specifiedPackageNames = new LinkedHashSet();
76
77    /**
78     *  Stores all classes specified by the user: those given by
79     *  individual class names on the command line, and those
80     *  contained in the packages given on the command line.
81     *
82     *  @contains ClassDocImpl
83     */
84    private List classesList = new LinkedList(); //new LinkedList();
85
86    /**
87     *  Stores all classes loaded in the course of preparing
88     *  the documentation data. Maps the fully qualified name
89     *  of a class to its ClassDocImpl representation.
90     *
91     *  @contains String->ClassDocImpl
92     */
93    private Map classDocMap = new HashMap();
94
95    /**
96     *  Stores all packages loaded in the course of preparing
97     *  the documentation data. Maps the package name
98     *  to its PackageDocImpl representation.
99     *
100     *  @contains String->PackageDocImpl
101     */
102    private Map packageDocMap = new HashMap();
103
104    /**
105     *  All classes specified by the user, both those explicitly
106     *  individually specified on the command line and those contained
107     *  in packages specified on the command line (as Array for quick
108     *  retrieval by Doclet).  This is created from classesList after
109     *  all classes have been loaded.
110     */
111    private ClassDocImpl[] classes;
112
113    /**
114     *  All classes which were individually specified on the command
115     *  line (as Array for quick retrieval by Doclet). This is created
116     *  from specifiedClassNames after all classes have been loaded.
117     */
118    private List specifiedClasses;
119
120    /**
121     *  All packages which were specified on the command line (as Array
122     *  for quick retrieval by Doclet). This is created from
123     *  specifiedPackageNames after all classes have been loaded.
124     */
125    private Set specifiedPackages;
126
127
128    /**
129     *  Temporarily stores a list of classes which are referenced
130     *  by classes already loaded and which still have to be
131     *  resolved.
132     */
133    private List scheduledClasses=new LinkedList();
134
135    private List sourcePath;
136
137    private String sourceEncoding;
138
139    private Parser parser = new Parser();
140
141    private Set unlocatableReportedSet = new HashSet();
142
143    private Set inaccessibleReportedSet = new HashSet();
144
145    //--------------------------------------------------------------------------
146    //
147    // Implementation of RootDoc interface
148    //
149    //--------------------------------------------------------------------------
150
151    /**
152     *  Return classes and interfaces to be documented.
153     */
154    public ClassDoc[] classes() { return classes; }
155
156    /**
157     *  Return a ClassDoc object for the specified class/interface
158     *  name.
159     *
160     *  @return a ClassDoc object describing the given class, or
161     *  <code>null</code> if no corresponding ClassDoc object
162     *  has been constructed.
163     */
164    public ClassDoc classNamed(String qualifiedName) {
165       return (ClassDoc)classDocMap.get(qualifiedName);
166    }
167
168    /**
169     *  Return an xxx
170     */
171    public String[][] options() { return customOptionArr; }
172
173    // Return a PackageDoc for the specified package name
174    public PackageDoc packageNamed(String name) {
175       return (PackageDoc)packageDocMap.get(name);
176    }
177
178
179   // classes and interfaces specified on the command line.
180   public ClassDoc[] specifiedClasses()
181   {
182     return (ClassDocImpl[]) specifiedClasses.toArray(new ClassDocImpl[0]);
183   }
184
185    // packages specified on the command line.
186   public PackageDoc[] specifiedPackages()
187   {
188     return (PackageDocImpl[])specifiedPackages.toArray(new PackageDocImpl[0]);
189   }
190
191    // Print error message, increment error count.
192    public void printError(java.lang.String msg) {
193       reporter.printError(msg);
194    }
195
196    // Print error message, increment error count.
197    public void printFatal(java.lang.String msg) {
198       reporter.printFatal(msg);
199    }
200
201    // Print a message.
202    public void printNotice(java.lang.String msg) {
203       reporter.printNotice(msg);
204    }
205
206    // Print warning message, increment warning count.
207    public void printWarning(java.lang.String msg) {
208       reporter.printWarning(msg);
209    }
210
211    public String name() {
212       return "RootDoc";
213    }
214
215    public ErrorReporter getReporter() {
216       return reporter;
217    }
218
219    public void build() throws ParseException, IOException {
220
221       //--- Create a temporary random access file for caching comment text.
222
223       //File rawCommentCacheFile=File.createTempFile("gjdoc_rawcomment",".cache");
224       File rawCommentCacheFile = new File("gjdoc_rawcomment.cache");
225       rawCommentCacheFile.deleteOnExit();
226       rawCommentCache = new RandomAccessFile(rawCommentCacheFile, "rw");
227
228       //--- Parse all files in "java.lang".
229
230       List javaLangSourceDirs = findSourceFiles("java/lang");
231       if (!javaLangSourceDirs.isEmpty()) {
232          Iterator it = javaLangSourceDirs.iterator();
233          while (it.hasNext()) {
234             File javaLangSourceDir = (File)it.next();
235             parser.processSourceDir(javaLangSourceDir,
236                                     sourceEncoding, "java.lang");
237          }
238       }
239       else {
240
241          Debug.log(1, "Sourcepath is "+sourcePath);
242
243          // Core docs not included in source-path:
244          // we need to gather the information about java.lang
245          // classes via reflection...
246
247       }
248
249       //--- Parse all files in explicitly specified package directories.
250
251       for (Iterator it=specifiedPackageNames.iterator(); it.hasNext(); ) {
252
253          String specifiedPackageName = (String)it.next();
254          String displayPackageName = specifiedPackageName;
255          if (null == displayPackageName || 0 == displayPackageName.length()) {
256             displayPackageName = "<unnamed>";
257          }
258          printNotice("Loading classes for package "+displayPackageName+"...");
259          String relPath;
260          if (null != specifiedPackageName) {
261             relPath = specifiedPackageName.replace('.',File.separatorChar);
262          }
263          else {
264             relPath = "";
265          }
266          List sourceDirs = findSourceFiles(relPath);
267          if (!sourceDirs.isEmpty()) {
268             Iterator sourceDirIt = sourceDirs.iterator();
269             while (sourceDirIt.hasNext()) {
270                File sourceDir = (File)sourceDirIt.next();
271                parser.processSourceDir(sourceDir, sourceEncoding, specifiedPackageName);
272             }
273          }
274          else {
275             printError("Package '"+specifiedPackageName+"' not found.");
276          }
277       }
278
279       specifiedClasses = new LinkedList();
280
281       //--- Parse all explicitly specified source files.
282
283       for (Iterator it=specifiedSourceFiles.iterator(); it.hasNext(); ) {
284
285          File specifiedSourceFile = (File)it.next();
286          printNotice("Loading source file "+specifiedSourceFile+" ...");
287          ClassDocImpl classDoc = parser.processSourceFile(specifiedSourceFile, true, sourceEncoding, null);
288          if (null != classDoc) {
289            specifiedClasses.add(classDoc);
290            classesList.add(classDoc);
291            classDoc.setIsIncluded(true);
292            addPackageDoc(classDoc.containingPackage());
293          }
294       }
295
296
297       //--- Let the user know that all specified classes are loaded.
298
299       printNotice("Constructing Javadoc information...");
300
301       //--- Load all classes implicitly referenced by explicitly specified classes.
302
303       loadScheduledClasses(parser);
304
305       printNotice("Resolving references in comments...");
306
307       resolveComments();
308
309       //--- Resolve pending references in all ClassDocImpls
310
311       printNotice("Resolving references in classes...");
312
313       for (Iterator it = classDocMap.values().iterator(); it.hasNext(); ) {
314          ClassDoc cd=(ClassDoc)it.next();
315          if (cd instanceof ClassDocImpl) {
316             ((ClassDocImpl)cd).resolve();
317          }
318       }
319
320       //--- Resolve pending references in all PackageDocImpls
321
322       printNotice("Resolving references in packages...");
323
324       for (Iterator it = packageDocMap.values().iterator(); it.hasNext(); ) {
325          PackageDocImpl pd=(PackageDocImpl)it.next();
326          pd.resolve();
327       }
328
329       //--- Assemble the array with all specified packages
330
331       specifiedPackages = new LinkedHashSet();
332       for (Iterator it = specifiedPackageNames.iterator(); it.hasNext(); ) {
333          String specifiedPackageName = (String)it.next();
334          PackageDoc specifiedPackageDoc = (PackageDoc)packageDocMap.get(specifiedPackageName);
335          if (null!=specifiedPackageDoc) {
336             ((PackageDocImpl)specifiedPackageDoc).setIsIncluded(true);
337             specifiedPackages.add(specifiedPackageDoc);
338
339             ClassDoc[] packageClassDocs=specifiedPackageDoc.allClasses();
340             for (int i=0; i<packageClassDocs.length; ++i) {
341                ClassDocImpl specifiedPackageClassDoc=(ClassDocImpl)packageClassDocs[i];
342
343                specifiedPackageClassDoc.setIsIncluded(true);
344                classesList.add(specifiedPackageClassDoc);
345             }
346          }
347       }
348
349       //--- Resolve pending references in comment data of all classes
350
351       printNotice("Resolving references in class comments...");
352
353       for (Iterator it=classDocMap.values().iterator(); it.hasNext(); ) {
354          ClassDoc cd=(ClassDoc)it.next();
355          if (cd instanceof ClassDocImpl) {
356             ((ClassDocImpl)cd).resolveComments();
357          }
358       }
359
360       //--- Resolve pending references in comment data of all packages
361
362       printNotice("Resolving references in package comments...");
363
364       for (Iterator it=packageDocMap.values().iterator(); it.hasNext(); ) {
365          PackageDocImpl pd=(PackageDocImpl)it.next();
366          pd.resolveComments();
367       }
368
369       //--- Create array with all loaded classes
370
371       this.classes=(ClassDocImpl[])classesList.toArray(new ClassDocImpl[0]);
372       Arrays.sort(this.classes);
373
374       //--- Close comment cache
375
376       parser = null;
377       System.gc();
378       System.gc();
379    }
380
381    public long writeRawComment(String rawComment) {
382       try {
383          long pos=rawCommentCache.getFilePointer();
384          //rawCommentCache.writeUTF(rawComment);
385          byte[] bytes = rawComment.getBytes("utf-8");
386          rawCommentCache.writeInt(bytes.length);
387          rawCommentCache.write(bytes);
388          return pos;
389       }
390       catch (IOException e) {
391          printFatal("Cannot write to comment cache: "+e.getMessage());
392          return -1;
393       }
394    }
395
396    public String readRawComment(long pos) {
397       try {
398          rawCommentCache.seek(pos);
399          int sz = rawCommentCache.readInt();
400          byte[] bytes = new byte[sz];
401          rawCommentCache.read(bytes);
402          return new String(bytes, "utf-8");
403          //return rawCommentCache.readUTF();
404       }
405       catch (IOException e) {
406          e.printStackTrace();
407          printFatal("Cannot read from comment cache: "+e.getMessage());
408          return null;
409       }
410    }
411
412    List<File> findSourceFiles(String relPath) {
413
414       List<File> result = new LinkedList<File>();
415       for (Iterator<File> it = sourcePath.iterator(); it.hasNext(); ) {
416          File path = it.next();
417          File file = new File(path, relPath);
418          if (file.exists()) {
419             result.add(file);
420          }
421       }
422
423       return result;
424    }
425
426    PackageDocImpl findOrCreatePackageDoc(String packageName) {
427       PackageDocImpl rc=(PackageDocImpl)getPackageDoc(packageName);
428       if (null==rc) {
429          rc=new PackageDocImpl(packageName);
430          if (specifiedPackageNames.contains(packageName)) {
431             String packageDirectoryName = packageName.replace('.', File.separatorChar);
432             List packageDirectories = findSourceFiles(packageDirectoryName);
433             Iterator it = packageDirectories.iterator();
434             boolean packageDocFound = false;
435             while (it.hasNext()) {
436                File packageDirectory = (File)it.next();
437                File packageDocFile = new File(packageDirectory, "package.html");
438                rc.setPackageDirectory(packageDirectory);
439                packageDocFound = true;
440                if (null!=packageDocFile && packageDocFile.exists()) {
441                   try {
442                      rc.setRawCommentText(readHtmlBody(packageDocFile));
443                   }
444                   catch (IOException e) {
445                      printWarning("Error while reading documentation for package "+packageName+": "+e.getMessage());
446                   }
447                   break;
448                }
449             }
450             if (!packageDocFound) {
451                printNotice("No description found for package "+packageName);
452             }
453          }
454          addPackageDoc(rc);
455       }
456       return rc;
457    }
458
459    public void addClassDoc(ClassDoc cd) {
460       classDocMap.put(cd.qualifiedName(), cd);
461    }
462
463    public void addClassDocRecursive(ClassDoc cd) {
464       classDocMap.put(cd.qualifiedName(), cd);
465       ClassDoc[] innerClasses = cd.innerClasses(false);
466       for (int i=0; i<innerClasses.length; ++i) {
467          addClassDocRecursive(innerClasses[i]);
468       }
469    }
470
471    public void addPackageDoc(PackageDoc pd) {
472       packageDocMap.put(pd.name(), pd);
473    }
474
475    public PackageDocImpl getPackageDoc(String name) {
476       return (PackageDocImpl)packageDocMap.get(name);
477    }
478
479    public ClassDocImpl getClassDoc(String qualifiedName) {
480       return (ClassDocImpl)classDocMap.get(qualifiedName);
481    }
482
483    class ScheduledClass {
484
485       ClassDoc contextClass;
486       String qualifiedName;
487       ScheduledClass(ClassDoc contextClass, String qualifiedName) {
488          this.contextClass=contextClass;
489          this.qualifiedName=qualifiedName;
490       }
491
492       public String toString() { return "ScheduledClass{"+qualifiedName+"}"; }
493    }
494
495    public void scheduleClass(ClassDoc context, String qualifiedName) throws ParseException, IOException {
496
497       if (classDocMap.get(qualifiedName)==null) {
498
499          //Debug.log(9,"Scheduling "+qualifiedName+", context "+context+".");
500          //System.err.println("Scheduling " + qualifiedName + ", context " + context);
501
502          scheduledClasses.add(new ScheduledClass(context, qualifiedName));
503       }
504    }
505
506    /**
507     *  Load all classes that were implictly referenced by the classes
508     *  (already loaded) that the user explicitly specified on the
509     *  command line.
510     *
511     *  For example, if the user generates Documentation for his simple
512     *  'class Test {}', which of course 'extends java.lang.Object',
513     *  then 'java.lang.Object' is implicitly referenced because it is
514     *  the base class of Test.
515     *
516     *  Gjdoc needs a ClassDocImpl representation of all classes
517     *  implicitly referenced through derivation (base class),
518     *  or implementation (interface), or field type, method argument
519     *  type, or method return type.
520     *
521     *  The task of this method is to ensure that Gjdoc has all this
522     *  information at hand when it exits.
523     *
524     *
525     */
526    public void loadScheduledClasses(Parser parser) throws ParseException, IOException {
527
528       // Because the referenced classes could in turn reference other
529       // classes, this method runs as long as there are still unloaded
530       // classes.
531
532       while (!scheduledClasses.isEmpty()) {
533
534          // Make a copy of scheduledClasses and empty it. This
535          // prevents any Concurrent Modification issues.
536          // As the copy won't need to grow (as it won't change)
537          // we make it an Array for performance reasons.
538
539          ScheduledClass[] scheduledClassesArr = (ScheduledClass[])scheduledClasses.toArray(new ScheduledClass[0]);
540          scheduledClasses.clear();
541
542          // Load each class specified in our array copy
543
544          for (int i=0; i<scheduledClassesArr.length; ++i) {
545
546             // The name of the class we are looking for. This name
547             // needs not be fully qualified.
548
549             String scheduledClassName=scheduledClassesArr[i].qualifiedName;
550
551             // The ClassDoc in whose context the scheduled class was looked for.
552             // This is necessary in order to resolve non-fully qualified
553             // class names.
554             ClassDoc scheduledClassContext=scheduledClassesArr[i].contextClass;
555
556             // If there already is a class doc with this name, skip. There's
557             // nothing to do for us.
558             if (classDocMap.get(scheduledClassName)!=null) {
559                continue;
560             }
561
562             try {
563                // Try to load the class
564                //printNotice("Trying to load " + scheduledClassName);
565                loadScheduledClass(parser, scheduledClassName, scheduledClassContext);
566             }
567             catch (ParseException e) {
568
569                /**********************************************************
570
571                // Check whether the following is necessary at all.
572
573
574                if (scheduledClassName.indexOf('.')>0) {
575
576                // Maybe the dotted notation doesn't mean a package
577                // name but instead an inner class, as in 'Outer.Inner'.
578                // so let's assume this and try to load the outer class.
579
580                   String outerClass="";
581                   for (StringTokenizer st=new StringTokenizer(scheduledClassName,"."); st.hasMoreTokens(); ) {
582                      if (outerClass.length()>0) outerClass+=".";
583                      outerClass+=st.nextToken();
584                      if (!st.hasMoreTokens()) break;
585                      try {
586                         loadClass(outerClass);
587                         //FIXME: shouldn't this be loadScheduledClass(outerClass, scheduledClassContext); ???
588                         continue;
589                      }
590                      catch (Exception ee) {
591                      // Ignore: try next level
592                      }
593                   }
594                }
595
596                **********************************************************/
597
598                // If we arrive here, the class could not be found
599
600                printWarning("Couldn't load class "+scheduledClassName+" referenced by "+scheduledClassContext);
601
602                //FIXME: shouldn't this be throw new Error("cannot load: "+scheduledClassName);
603             }
604          }
605       }
606    }
607
608    private void loadScheduledClass(Parser parser, String scheduledClassName, ClassDoc scheduledClassContext) throws ParseException, IOException {
609
610       ClassDoc loadedClass=(ClassDoc)scheduledClassContext.findClass(scheduledClassName);
611
612       if (loadedClass==null || loadedClass instanceof ClassDocProxy) {
613
614          ClassDoc classDoc = findScheduledClassFile(scheduledClassName, scheduledClassContext);
615          if (null != classDoc) {
616
617             if (classDoc instanceof ClassDocReflectedImpl) {
618                Main.getRootDoc().addClassDocRecursive(classDoc);
619             }
620
621             if (Main.DESCEND_SUPERCLASS
622                 && null != classDoc.superclass()
623                 && (classDoc.superclass() instanceof ClassDocProxy)) {
624                scheduleClass(classDoc, classDoc.superclass().qualifiedName());
625             }
626          }
627          else {
628             // It might be an inner class of one of the outer/super classes.
629             // But we can only check that when they are all fully loaded.
630             boolean retryLater = false;
631
632             int numberOfProcessedFilesBefore = parser.getNumberOfProcessedFiles();
633
634             ClassDoc cc = scheduledClassContext.containingClass();
635             while (cc != null && !retryLater) {
636                ClassDoc sc = cc.superclass();
637                while (sc != null && !retryLater) {
638                   if (sc instanceof ClassDocProxy) {
639                      ((ClassDocImpl)cc).resolve();
640                      retryLater = true;
641                   }
642                   sc = sc.superclass();
643                }
644                cc = cc.containingClass();
645             }
646
647             // Now that outer/super references have been resolved, try again
648             // to find the class.
649
650             loadedClass = (ClassDoc)scheduledClassContext.findClass(scheduledClassName);
651
652             int numberOfProcessedFilesAfter = parser.getNumberOfProcessedFiles();
653
654             boolean filesWereProcessed = numberOfProcessedFilesAfter > numberOfProcessedFilesBefore;
655
656             // Only re-schedule class if additional files have been processed
657             // If there haven't, there's no point in re-scheduling.
658             // Will avoid infinite loops of re-scheduling
659             if (null == loadedClass && retryLater && filesWereProcessed)
660                scheduleClass(scheduledClassContext, scheduledClassName);
661
662             /* A warning needn't be emitted - this is normal, can happen
663                if the scheduled class is in a package which is not
664                included on the command line.
665
666                else if (null == loadedClass)
667                printWarning("Can't find scheduled class '"
668                + scheduledClassName
669                + "' in context '"
670                + scheduledClassContext.qualifiedName()
671                + "'");
672             */
673          }
674       }
675    }
676
677    private static interface ResolvedImport
678    {
679       public String match(String name);
680       public boolean mismatch(String name);
681       public ClassDoc tryFetch(String name);
682    }
683
684    private class ResolvedImportNotFound
685       implements ResolvedImport
686    {
687       private String importSpecifier;
688       private String name;
689
690       ResolvedImportNotFound(String importSpecifier)
691       {
692          this.importSpecifier = importSpecifier;
693          int ndx = importSpecifier.lastIndexOf('.');
694          if (ndx >= 0) {
695             this.name = importSpecifier.substring(ndx + 1);
696          }
697          else {
698             this.name = importSpecifier;
699          }
700       }
701
702       public String toString()
703       {
704          return "ResolvedImportNotFound{" + importSpecifier + "}";
705       }
706
707       public String match(String name)
708       {
709          if ((name.equals(this.name)) || (importSpecifier.equals(name)))
710             return this.name;
711          // FIXME: note that we don't handle on-demand imports here.
712          return null;
713       }
714
715       public boolean mismatch(String name)
716       {
717          return true; // FIXME!
718       }
719
720       public ClassDoc tryFetch(String name)
721       {
722          return null;
723       }
724    }
725
726    private class ResolvedImportPackageFile
727       implements ResolvedImport
728    {
729       private Set topLevelClassNames;
730       private File packageFile;
731       private String packageName;
732       private Map cache = new HashMap();
733
734       ResolvedImportPackageFile(File packageFile, String packageName)
735       {
736          this.packageFile = packageFile;
737          this.packageName = packageName;
738          topLevelClassNames = new HashSet();
739          File[] files = packageFile.listFiles();
740          for (int i=0; i<files.length; ++i) {
741             if (!files[i].isDirectory() && files[i].getName().endsWith(".java")) {
742                String topLevelClassName = files[i].getName();
743                topLevelClassName
744                   = topLevelClassName.substring(0, topLevelClassName.length() - 5);
745                topLevelClassNames.add(topLevelClassName);
746             }
747          }
748       }
749
750       public String match(String name)
751       {
752          ClassDoc loadedClass = classNamed(packageName + "." + name);
753          if (null != loadedClass) {
754             return loadedClass.qualifiedName();
755          }
756          else {
757             String topLevelName = name;
758             int ndx = topLevelName.indexOf('.');
759             String innerClassName = null;
760             if (ndx > 0) {
761                innerClassName = topLevelName.substring(ndx + 1);
762                topLevelName = topLevelName.substring(0, ndx);
763             }
764
765             if (topLevelClassNames.contains(topLevelName)) {
766                //System.err.println(this + ".match returns " + packageName + "." + name);
767                return packageName + "." + name;
768             }
769             // FIXME: inner classes
770             else {
771                return null;
772             }
773          }
774       }
775
776       public boolean mismatch(String name)
777       {
778          return null == match(name);
779       }
780
781       public ClassDoc tryFetch(String name)
782       {
783          ClassDoc loadedClass = classNamed(packageName + "." + name);
784          if (null != loadedClass) {
785             return loadedClass;
786          }
787          else if (null != match(name)) {
788
789             String topLevelName = name;
790             int ndx = topLevelName.indexOf('.');
791             String innerClassName = null;
792             if (ndx > 0) {
793                innerClassName = topLevelName.substring(ndx + 1);
794                topLevelName = topLevelName.substring(0, ndx);
795             }
796
797             ClassDoc topLevelClass = (ClassDoc)cache.get(topLevelName);
798             if (null == topLevelClass) {
799                File classFile = new File(packageFile, topLevelName + ".java");
800                try {
801                   // FIXME: inner classes
802                   topLevelClass = parser.processSourceFile(classFile, false, sourceEncoding, null);
803                }
804                catch (Exception ignore) {
805                   printWarning("Could not parse source file " + classFile);
806                }
807                cache.put(topLevelName, topLevelClass);
808             }
809             if (null == innerClassName) {
810                return topLevelClass;
811             }
812             else {
813                return getInnerClass(topLevelClass, innerClassName);
814             }
815          }
816          else {
817             return null;
818          }
819       }
820
821       public String toString()
822       {
823          return "ResolvedImportPackageFile{" + packageFile + "," + packageName + "}";
824       }
825    }
826
827    private ClassDoc getInnerClass(ClassDoc topLevelClass, String innerClassName)
828    {
829       StringTokenizer st = new StringTokenizer(innerClassName, ".");
830    outer:
831
832       while (st.hasMoreTokens()) {
833          String innerClassNameComponent = st.nextToken();
834          ClassDoc[] innerClasses = topLevelClass.innerClasses();
835          for (int i=0; i<innerClasses.length; ++i) {
836             if (innerClasses[i].name().equals(innerClassNameComponent)) {
837                topLevelClass = innerClasses[i];
838                continue outer;
839             }
840          }
841          printWarning("Could not find inner class " + innerClassName + " in class " + topLevelClass.qualifiedName());
842          return null;
843       }
844       return topLevelClass;
845    }
846
847    private class ResolvedImportClassFile
848       implements ResolvedImport
849    {
850       private File classFile;
851       private String innerClassName;
852       private String name;
853       private ClassDoc classDoc;
854       private boolean alreadyFetched;
855       private String qualifiedName;
856
857       ResolvedImportClassFile(File classFile, String innerClassName, String name, String qualifiedName)
858       {
859          this.classFile = classFile;
860          this.innerClassName = innerClassName;
861          this.name = name;
862          this.qualifiedName = qualifiedName;
863       }
864
865       public String toString()
866       {
867          return "ResolvedImportClassFile{" + classFile + "," + innerClassName +  "}";
868       }
869
870       public String match(String name)
871       {
872          String topLevelName = name;
873          int ndx = topLevelName.indexOf('.');
874
875          String _innerClassName = null;
876          if (ndx > 0) {
877             _innerClassName = topLevelName.substring(ndx + 1);
878             topLevelName = topLevelName.substring(0, ndx);
879          }
880
881          if (this.name.equals(topLevelName)) {
882             if (null == _innerClassName) {
883                return qualifiedName;
884             }
885             else {
886                return qualifiedName + "." + _innerClassName;
887             }
888          }
889          else {
890             return null;
891          }
892       }
893
894       public boolean mismatch(String name)
895       {
896          return null == match(name);
897       }
898
899       public ClassDoc tryFetch(String name)
900       {
901          if (null != match(name)) {
902             ClassDoc topLevelClass = null;
903             if (alreadyFetched) {
904                topLevelClass = classDoc;
905             }
906             else {
907                alreadyFetched = true;
908                try {
909                   topLevelClass = parser.processSourceFile(classFile, false, sourceEncoding, null);
910                }
911                catch (Exception ignore) {
912                   printWarning("Could not parse source file " + classFile);
913                }
914             }
915             if (null == topLevelClass) {
916                return null;
917             }
918             else {
919                return getInnerClass(topLevelClass, innerClassName);
920             }
921          }
922          else {
923             return null;
924          }
925       }
926
927       public String getName()
928       {
929          if (innerClassName != null) {
930             return name + innerClassName;
931          }
932          else {
933             return name;
934          }
935       }
936    }
937
938    private class ResolvedImportReflectionClass
939       implements ResolvedImport
940    {
941       private Class clazz;
942       private String name;
943
944       ResolvedImportReflectionClass(Class clazz)
945       {
946          this.clazz = clazz;
947          String className = clazz.getName();
948          int ndx = className.lastIndexOf('.');
949          if (ndx >= 0) {
950             this.name = className.substring(ndx + 1);
951          }
952          else {
953             this.name = className;
954          }
955       }
956
957       public String toString()
958       {
959          return "ResolvedImportReflectionClass{" + clazz.getName() + "}";
960       }
961
962       public String match(String name)
963       {
964          if ((this.name.equals(name)) || (clazz.getName().equals(name))) {
965             return clazz.getName();
966          }
967          else {
968             return null;
969          }
970       }
971
972       public boolean mismatch(String name)
973       {
974          return null == match(name);
975       }
976
977       public ClassDoc tryFetch(String name)
978       {
979          if (null != match(name)) {
980             return new ClassDocReflectedImpl(clazz);
981          }
982          // FIXME: inner classes?
983          else {
984             return null;
985          }
986       }
987
988       public String getName()
989       {
990          return name;
991       }
992    }
993
994    private class ResolvedImportReflectionPackage
995       implements ResolvedImport
996    {
997       private String packagePrefix;
998
999       ResolvedImportReflectionPackage(String packagePrefix)
1000       {
1001          this.packagePrefix = packagePrefix;
1002       }
1003
1004       public String toString()
1005       {
1006          return "ResolvedImportReflectionPackage{" + packagePrefix + ".*}";
1007       }
1008
1009       public String match(String name)
1010       {
1011          try {
1012             Class clazz = Class.forName(packagePrefix + "." + name);
1013             return clazz.getName();
1014          }
1015          catch (Exception e) {
1016             return null;
1017          }
1018       }
1019
1020       public boolean mismatch(String name)
1021       {
1022          return null == match(name);
1023       }
1024
1025       public ClassDoc tryFetch(String name)
1026       {
1027          try {
1028             Class clazz = Class.forName(packagePrefix + name);
1029             return ClassDocReflectedImpl.newInstance(clazz);
1030          }
1031          catch (Exception e) {
1032             return null;
1033          }
1034       }
1035
1036       public String getName()
1037       {
1038          return packagePrefix;
1039       }
1040    }
1041
1042    private List unlocatablePrefixes = new LinkedList();
1043
1044    private ResolvedImport resolveImport(String importSpecifier)
1045    {
1046       ResolvedImport result = resolveImportFileSystem(importSpecifier);
1047       if (null == result && Main.getInstance().isReflectionEnabled()) {
1048          result = resolveImportReflection(importSpecifier);
1049       }
1050       if (null == result) {
1051          result = new ResolvedImportNotFound(importSpecifier);
1052       }
1053       return result;
1054    }
1055
1056    private ResolvedImport resolveImportReflection(String importSpecifier)
1057    {
1058       String importedPackageOrClass = importSpecifier;
1059       if (importedPackageOrClass.endsWith(".*")) {
1060          importedPackageOrClass = importedPackageOrClass.substring(0, importedPackageOrClass.length() - 2);
1061
1062          return new ResolvedImportReflectionPackage(importedPackageOrClass);
1063
1064          //return null;
1065       }
1066       else {
1067          try {
1068             Class importedClass = Class.forName(importSpecifier);
1069             return new ResolvedImportReflectionClass(importedClass);
1070          }
1071          catch (Throwable ignore) {
1072             return null;
1073          }
1074       }
1075    }
1076
1077    private ResolvedImport resolveImportFileSystem(String importSpecifier)
1078    {
1079       for (Iterator it = unlocatablePrefixes.iterator(); it.hasNext(); ) {
1080          String unlocatablePrefix = (String)it.next();
1081          if (importSpecifier.startsWith(unlocatablePrefix)) {
1082             return null;
1083          }
1084       }
1085
1086       String longestUnlocatablePrefix = "";
1087
1088       for (Iterator it=sourcePath.iterator(); it.hasNext(); ) {
1089
1090          File _sourcePath = (File)it.next();
1091
1092          StringBuffer packageOrClassPrefix = new StringBuffer();
1093          StringTokenizer st = new StringTokenizer(importSpecifier, ".");
1094          while (st.hasMoreTokens() && _sourcePath.isDirectory()) {
1095             String token = st.nextToken();
1096             if ("*".equals(token)) {
1097                return new ResolvedImportPackageFile(_sourcePath,
1098                                                     packageOrClassPrefix.substring(0, packageOrClassPrefix.length() - 1));
1099             }
1100             else {
1101                packageOrClassPrefix.append(token);
1102                packageOrClassPrefix.append('.');
1103                File classFile = new File(_sourcePath, token + ".java");
1104                //System.err.println("  looking for file " + classFile);
1105                if (classFile.exists()) {
1106                   StringBuffer innerClassName = new StringBuffer();
1107                   while (st.hasMoreTokens()) {
1108                      token = st.nextToken();
1109                      if (innerClassName.length() > 0) {
1110                         innerClassName.append('.');
1111                      }
1112                      innerClassName.append(token);
1113                   }
1114                   return new ResolvedImportClassFile(classFile, innerClassName.toString(), token, importSpecifier);
1115                }
1116                else {
1117                   _sourcePath = new File(_sourcePath, token);
1118                }
1119             }
1120          }
1121          if (st.hasMoreTokens()) {
1122             if (packageOrClassPrefix.length() > longestUnlocatablePrefix.length()) {
1123                longestUnlocatablePrefix = packageOrClassPrefix.toString();
1124             }
1125          }
1126       }
1127
1128       if (longestUnlocatablePrefix.length() > 0) {
1129          unlocatablePrefixes.add(longestUnlocatablePrefix);
1130       }
1131
1132       return null;
1133    }
1134
1135    private Map resolvedImportCache = new HashMap();
1136
1137    private ResolvedImport getResolvedImport(String importSpecifier)
1138    {
1139       ResolvedImport result
1140          = (ResolvedImport)resolvedImportCache.get(importSpecifier);
1141       if (null == result) {
1142          result = resolveImport(importSpecifier);
1143          resolvedImportCache.put(importSpecifier, result);
1144       }
1145       return result;
1146    }
1147
1148    public String resolveClassName(String className, ClassDocImpl context)
1149    {
1150       Iterator it = context.getImportSpecifierList().iterator();
1151       while (it.hasNext()) {
1152          String importSpecifier = (String)it.next();
1153          ResolvedImport resolvedImport = getResolvedImport(importSpecifier);
1154          String resolvedScheduledClassName = resolvedImport.match(className);
1155
1156          if (null != resolvedScheduledClassName) {
1157             return resolvedScheduledClassName;
1158          }
1159       }
1160       return className;
1161    }
1162
1163    public ClassDoc findScheduledClassFile(String scheduledClassName,
1164                                           ClassDoc scheduledClassContext)
1165       throws ParseException, IOException
1166    {
1167       String resolvedScheduledClassName = null;
1168
1169       if (scheduledClassContext instanceof ClassDocImpl) {
1170
1171          //((ClassDocImpl)scheduledClassContext).resolveReferencedName(scheduledClassName);
1172          Iterator it = ((ClassDocImpl)scheduledClassContext).getImportSpecifierList().iterator();
1173          while (it.hasNext()) {
1174             String importSpecifier = (String)it.next();
1175             ResolvedImport resolvedImport = getResolvedImport(importSpecifier);
1176             //System.err.println("  looking in import '" +  resolvedImport + "'");
1177             resolvedScheduledClassName = resolvedImport.match(scheduledClassName);
1178             if (null != resolvedScheduledClassName) {
1179                ClassDoc result = resolvedImport.tryFetch(scheduledClassName);
1180                if (null != result) {
1181                   return result;
1182                }
1183                else {
1184                   if (!inaccessibleReportedSet.contains(scheduledClassName)) {
1185                      inaccessibleReportedSet.add(scheduledClassName);
1186                      printWarning("Error while loading class " + scheduledClassName);
1187                   }
1188                   // FIXME: output resolved class name here
1189                   return null;
1190                }
1191             }
1192          }
1193       }
1194       else {
1195          System.err.println("findScheduledClassFile for '" + scheduledClassName + "' in proxy for " + scheduledClassContext);
1196       }
1197
1198       // interpret as fully qualified name on file system
1199
1200       ResolvedImport fqImport = resolveImportFileSystem(scheduledClassName);
1201       if (null != fqImport && fqImport instanceof ResolvedImportClassFile) {
1202          return fqImport.tryFetch(((ResolvedImportClassFile)fqImport).getName());
1203       }
1204
1205       // use reflection, assume fully qualified class name
1206
1207       if (!unlocatableReflectedClassNames.contains(scheduledClassName)) {
1208          if (Main.getInstance().isReflectionEnabled()) {
1209             try {
1210                Class clazz = Class.forName(scheduledClassName);
1211                printWarning("Cannot locate class " + scheduledClassName + " on file system, falling back to reflection.");
1212                ClassDoc result = new ClassDocReflectedImpl(clazz);
1213                return result;
1214             }
1215             catch (Throwable ignore) {
1216                unlocatableReflectedClassNames.add(scheduledClassName);
1217             }
1218          }
1219          else {
1220             unlocatableReflectedClassNames.add(scheduledClassName);
1221          }
1222       }
1223
1224       if (null == resolvedScheduledClassName) {
1225          resolvedScheduledClassName = scheduledClassName;
1226       }
1227       if (!unlocatableReportedSet.contains(resolvedScheduledClassName)) {
1228          unlocatableReportedSet.add(resolvedScheduledClassName);
1229          printWarning("Cannot locate class " + resolvedScheduledClassName + " referenced in class " + scheduledClassContext.qualifiedName());
1230       }
1231       return null;
1232    }
1233
1234    private Set unlocatableReflectedClassNames = new HashSet();
1235
1236    public static boolean recursiveClasses = false;
1237
1238    public void addSpecifiedPackageName(String packageName) {
1239       specifiedPackageNames.add(packageName);
1240    }
1241
1242    public void addSpecifiedSourceFile(File sourceFile) {
1243       specifiedSourceFiles.add(sourceFile);
1244    }
1245
1246    public boolean hasSpecifiedPackagesOrClasses() {
1247       return !specifiedPackageNames.isEmpty()
1248          ||  !specifiedSourceFiles.isEmpty();
1249    }
1250
1251    public void setOptions(String[][] customOptionArr) {
1252       this.customOptionArr = customOptionArr;
1253    }
1254
1255    public void setSourcePath(List sourcePath) {
1256       this.sourcePath = sourcePath;
1257    }
1258
1259    public void finalize() throws Throwable {
1260       super.finalize();
1261    }
1262
1263    public void flush()
1264    {
1265       try {
1266          rawCommentCache.close();
1267       }
1268       catch (IOException e) {
1269          printError("Cannot close raw comment cache");
1270       }
1271
1272       rawCommentCache = null;
1273       customOptionArr = null;
1274       specifiedPackageNames = null;
1275       classesList = null;
1276       classDocMap = null;
1277       packageDocMap = null;
1278       classes = null;
1279       specifiedClasses = null;
1280       specifiedPackages = null;
1281       scheduledClasses = null;
1282       sourcePath = null;
1283       parser = null;
1284       unlocatableReportedSet = null;
1285       inaccessibleReportedSet = null;
1286    }
1287
1288    public void setSourceEncoding(String sourceEncoding)
1289    {
1290       this.sourceEncoding = sourceEncoding;
1291    }
1292
1293    public RootDocImpl()
1294    {
1295       super(null);
1296    }
1297
1298    public static String readHtmlBody(File file)
1299       throws IOException
1300    {
1301       FileReader fr=new FileReader(file);
1302       long size = file.length();
1303       char[] packageDocBuf=new char[(int)(size)];
1304       int index = 0;
1305       int i = fr.read(packageDocBuf, index, (int)size);
1306       while (i > 0) {
1307          index += i;
1308          size -= i;
1309          i = fr.read(packageDocBuf, index, (int)size);
1310       }
1311       fr.close();
1312
1313       // We only need the part between the begin and end body tag.
1314       String html = new String(packageDocBuf);
1315       int start = html.indexOf("<body");
1316       if (start == -1)
1317          start = html.indexOf("<BODY");
1318       int end = html.indexOf("</body>");
1319       if (end == -1)
1320          end = html.indexOf("</BODY>");
1321       if (start != -1 && end != -1) {
1322          // Start is end of body tag.
1323          start = html.indexOf('>', start) + 1;
1324          if (start != -1 && start < end)
1325             html = html.substring(start, end);
1326       }
1327       return html.trim();
1328    }
1329
1330    public Parser getParser()
1331    {
1332       return parser;
1333    }
1334 }