Import GNU Classpath (20121202).
[official-gcc.git] / libjava / classpath / tools / gnu / classpath / tools / gjdoc / RootDocImpl.java
blobdd76ffada96b58fae3e876e4e36977d5882d5fee
1 /* gnu.classpath.tools.gjdoc.RootDocImpl
2 Copyright (C) 2001, 2007, 2012 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
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.
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.
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.
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.
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. */
38 package gnu.classpath.tools.gjdoc;
40 import com.sun.javadoc.*;
41 import java.util.*;
42 import java.io.*;
43 import java.lang.reflect.*;
45 public class RootDocImpl
46 extends DocImpl
47 implements GjdocRootDoc {
49 private ErrorReporter reporter = new ErrorReporter();
51 private RandomAccessFile rawCommentCache;
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.
60 private String[][] customOptionArr;
62 /**
63 * All source files explicitly specified on the command line.
65 * @contains File
67 private List specifiedSourceFiles = new LinkedList();
69 /**
70 * The names of all packages explicitly specified on the
71 * command line.
73 * @contains String
75 private Set specifiedPackageNames = new LinkedHashSet();
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.
82 * @contains ClassDocImpl
84 private List classesList = new LinkedList(); //new LinkedList();
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.
91 * @contains String->ClassDocImpl
93 private Map classDocMap = new HashMap();
95 /**
96 * Stores all packages loaded in the course of preparing
97 * the documentation data. Maps the package name
98 * to its PackageDocImpl representation.
100 * @contains String->PackageDocImpl
102 private Map packageDocMap = new HashMap();
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.
111 private ClassDocImpl[] classes;
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.
118 private List specifiedClasses;
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.
125 private Set specifiedPackages;
129 * Temporarily stores a list of classes which are referenced
130 * by classes already loaded and which still have to be
131 * resolved.
133 private List scheduledClasses=new LinkedList();
135 private List sourcePath;
137 private String sourceEncoding;
139 private Parser parser = new Parser();
141 private Set unlocatableReportedSet = new HashSet();
143 private Set inaccessibleReportedSet = new HashSet();
145 //--------------------------------------------------------------------------
147 // Implementation of RootDoc interface
149 //--------------------------------------------------------------------------
152 * Return classes and interfaces to be documented.
154 public ClassDoc[] classes() { return classes; }
157 * Return a ClassDoc object for the specified class/interface
158 * name.
160 * @return a ClassDoc object describing the given class, or
161 * <code>null</code> if no corresponding ClassDoc object
162 * has been constructed.
164 public ClassDoc classNamed(String qualifiedName) {
165 return (ClassDoc)classDocMap.get(qualifiedName);
169 * Return an xxx
171 public String[][] options() { return customOptionArr; }
173 // Return a PackageDoc for the specified package name
174 public PackageDoc packageNamed(String name) {
175 return (PackageDoc)packageDocMap.get(name);
179 // classes and interfaces specified on the command line.
180 public ClassDoc[] specifiedClasses()
182 return (ClassDocImpl[]) specifiedClasses.toArray(new ClassDocImpl[0]);
185 // packages specified on the command line.
186 public PackageDoc[] specifiedPackages()
188 return (PackageDocImpl[])specifiedPackages.toArray(new PackageDocImpl[0]);
191 // Print error message, increment error count.
192 public void printError(java.lang.String msg) {
193 reporter.printError(msg);
196 // Print error message, increment error count.
197 public void printFatal(java.lang.String msg) {
198 reporter.printFatal(msg);
201 // Print a message.
202 public void printNotice(java.lang.String msg) {
203 reporter.printNotice(msg);
206 // Print warning message, increment warning count.
207 public void printWarning(java.lang.String msg) {
208 reporter.printWarning(msg);
211 public String name() {
212 return "RootDoc";
215 public ErrorReporter getReporter() {
216 return reporter;
219 public void build() throws ParseException, IOException {
221 //--- Create a temporary random access file for caching comment text.
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");
228 //--- Parse all files in "java.lang".
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");
239 else {
241 Debug.log(1, "Sourcepath is "+sourcePath);
243 // Core docs not included in source-path:
244 // we need to gather the information about java.lang
245 // classes via reflection...
249 //--- Parse all files in explicitly specified package directories.
251 for (Iterator it=specifiedPackageNames.iterator(); it.hasNext(); ) {
253 String specifiedPackageName = (String)it.next();
254 String displayPackageName = specifiedPackageName;
255 if (null == displayPackageName || 0 == displayPackageName.length()) {
256 displayPackageName = "<unnamed>";
258 printNotice("Loading classes for package "+displayPackageName+"...");
259 String relPath;
260 if (null != specifiedPackageName) {
261 relPath = specifiedPackageName.replace('.',File.separatorChar);
263 else {
264 relPath = "";
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);
274 else {
275 printError("Package '"+specifiedPackageName+"' not found.");
279 specifiedClasses = new LinkedList();
281 //--- Parse all explicitly specified source files.
283 for (Iterator it=specifiedSourceFiles.iterator(); it.hasNext(); ) {
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());
297 //--- Let the user know that all specified classes are loaded.
299 printNotice("Constructing Javadoc information...");
301 //--- Load all classes implicitly referenced by explicitly specified classes.
303 loadScheduledClasses(parser);
305 printNotice("Resolving references in comments...");
307 resolveComments();
309 //--- Resolve pending references in all ClassDocImpls
311 printNotice("Resolving references in classes...");
313 for (Iterator it = classDocMap.values().iterator(); it.hasNext(); ) {
314 ClassDoc cd=(ClassDoc)it.next();
315 if (cd instanceof ClassDocImpl) {
316 ((ClassDocImpl)cd).resolve();
320 //--- Resolve pending references in all PackageDocImpls
322 printNotice("Resolving references in packages...");
324 for (Iterator it = packageDocMap.values().iterator(); it.hasNext(); ) {
325 PackageDocImpl pd=(PackageDocImpl)it.next();
326 pd.resolve();
329 //--- Assemble the array with all specified packages
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);
339 ClassDoc[] packageClassDocs=specifiedPackageDoc.allClasses();
340 for (int i=0; i<packageClassDocs.length; ++i) {
341 ClassDocImpl specifiedPackageClassDoc=(ClassDocImpl)packageClassDocs[i];
343 specifiedPackageClassDoc.setIsIncluded(true);
344 classesList.add(specifiedPackageClassDoc);
349 //--- Resolve pending references in comment data of all classes
351 printNotice("Resolving references in class comments...");
353 for (Iterator it=classDocMap.values().iterator(); it.hasNext(); ) {
354 ClassDoc cd=(ClassDoc)it.next();
355 if (cd instanceof ClassDocImpl) {
356 ((ClassDocImpl)cd).resolveComments();
360 //--- Resolve pending references in comment data of all packages
362 printNotice("Resolving references in package comments...");
364 for (Iterator it=packageDocMap.values().iterator(); it.hasNext(); ) {
365 PackageDocImpl pd=(PackageDocImpl)it.next();
366 pd.resolveComments();
369 //--- Create array with all loaded classes
371 this.classes=(ClassDocImpl[])classesList.toArray(new ClassDocImpl[0]);
372 Arrays.sort(this.classes);
374 //--- Close comment cache
376 parser = null;
377 System.gc();
378 System.gc();
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;
390 catch (IOException e) {
391 printFatal("Cannot write to comment cache: "+e.getMessage());
392 return -1;
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();
405 catch (IOException e) {
406 e.printStackTrace();
407 printFatal("Cannot read from comment cache: "+e.getMessage());
408 return null;
412 List<File> findSourceFiles(String relPath) {
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);
423 return result;
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));
444 catch (IOException e) {
445 printWarning("Error while reading documentation for package "+packageName+": "+e.getMessage());
447 break;
450 if (!packageDocFound) {
451 printNotice("No description found for package "+packageName);
454 addPackageDoc(rc);
456 return rc;
459 public void addClassDoc(ClassDoc cd) {
460 classDocMap.put(cd.qualifiedName(), cd);
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]);
471 public void addPackageDoc(PackageDoc pd) {
472 packageDocMap.put(pd.name(), pd);
475 public PackageDocImpl getPackageDoc(String name) {
476 return (PackageDocImpl)packageDocMap.get(name);
479 public ClassDocImpl getClassDoc(String qualifiedName) {
480 return (ClassDocImpl)classDocMap.get(qualifiedName);
483 class ScheduledClass {
485 ClassDoc contextClass;
486 String qualifiedName;
487 ScheduledClass(ClassDoc contextClass, String qualifiedName) {
488 this.contextClass=contextClass;
489 this.qualifiedName=qualifiedName;
492 public String toString() { return "ScheduledClass{"+qualifiedName+"}"; }
495 public void scheduleClass(ClassDoc context, String qualifiedName) throws ParseException, IOException {
497 if (classDocMap.get(qualifiedName)==null) {
499 //Debug.log(9,"Scheduling "+qualifiedName+", context "+context+".");
500 //System.err.println("Scheduling " + qualifiedName + ", context " + context);
502 scheduledClasses.add(new ScheduledClass(context, qualifiedName));
507 * Load all classes that were implictly referenced by the classes
508 * (already loaded) that the user explicitly specified on the
509 * command line.
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.
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.
521 * The task of this method is to ensure that Gjdoc has all this
522 * information at hand when it exits.
526 public void loadScheduledClasses(Parser parser) throws ParseException, IOException {
528 // Because the referenced classes could in turn reference other
529 // classes, this method runs as long as there are still unloaded
530 // classes.
532 while (!scheduledClasses.isEmpty()) {
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.
539 ScheduledClass[] scheduledClassesArr = (ScheduledClass[])scheduledClasses.toArray(new ScheduledClass[0]);
540 scheduledClasses.clear();
542 // Load each class specified in our array copy
544 for (int i=0; i<scheduledClassesArr.length; ++i) {
546 // The name of the class we are looking for. This name
547 // needs not be fully qualified.
549 String scheduledClassName=scheduledClassesArr[i].qualifiedName;
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;
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;
562 try {
563 // Try to load the class
564 //printNotice("Trying to load " + scheduledClassName);
565 loadScheduledClass(parser, scheduledClassName, scheduledClassContext);
567 catch (ParseException e) {
569 /**********************************************************
571 // Check whether the following is necessary at all.
574 if (scheduledClassName.indexOf('.')>0) {
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.
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;
590 catch (Exception ee) {
591 // Ignore: try next level
596 **********************************************************/
598 // If we arrive here, the class could not be found
600 printWarning("Couldn't load class "+scheduledClassName+" referenced by "+scheduledClassContext);
602 //FIXME: shouldn't this be throw new Error("cannot load: "+scheduledClassName);
608 private void loadScheduledClass(Parser parser, String scheduledClassName, ClassDoc scheduledClassContext) throws ParseException, IOException {
610 ClassDoc loadedClass=(ClassDoc)scheduledClassContext.findClass(scheduledClassName);
612 if (loadedClass==null || loadedClass instanceof ClassDocProxy) {
614 ClassDoc classDoc = findScheduledClassFile(scheduledClassName, scheduledClassContext);
615 if (null != classDoc) {
617 if (classDoc instanceof ClassDocReflectedImpl) {
618 Main.getRootDoc().addClassDocRecursive(classDoc);
621 if (Main.DESCEND_SUPERCLASS
622 && null != classDoc.superclass()
623 && (classDoc.superclass() instanceof ClassDocProxy)) {
624 scheduleClass(classDoc, classDoc.superclass().qualifiedName());
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;
632 int numberOfProcessedFilesBefore = parser.getNumberOfProcessedFiles();
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;
642 sc = sc.superclass();
644 cc = cc.containingClass();
647 // Now that outer/super references have been resolved, try again
648 // to find the class.
650 loadedClass = (ClassDoc)scheduledClassContext.findClass(scheduledClassName);
652 int numberOfProcessedFilesAfter = parser.getNumberOfProcessedFiles();
654 boolean filesWereProcessed = numberOfProcessedFilesAfter > numberOfProcessedFilesBefore;
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);
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.
666 else if (null == loadedClass)
667 printWarning("Can't find scheduled class '"
668 + scheduledClassName
669 + "' in context '"
670 + scheduledClassContext.qualifiedName()
671 + "'");
677 private static interface ResolvedImport
679 public String match(String name);
680 public boolean mismatch(String name);
681 public ClassDoc tryFetch(String name);
684 private class ResolvedImportNotFound
685 implements ResolvedImport
687 private String importSpecifier;
688 private String name;
690 ResolvedImportNotFound(String importSpecifier)
692 this.importSpecifier = importSpecifier;
693 int ndx = importSpecifier.lastIndexOf('.');
694 if (ndx >= 0) {
695 this.name = importSpecifier.substring(ndx + 1);
697 else {
698 this.name = importSpecifier;
702 public String toString()
704 return "ResolvedImportNotFound{" + importSpecifier + "}";
707 public String match(String name)
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;
715 public boolean mismatch(String name)
717 return true; // FIXME!
720 public ClassDoc tryFetch(String name)
722 return null;
726 private class ResolvedImportPackageFile
727 implements ResolvedImport
729 private Set topLevelClassNames;
730 private File packageFile;
731 private String packageName;
732 private Map cache = new HashMap();
734 ResolvedImportPackageFile(File packageFile, String packageName)
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);
750 public String match(String name)
752 ClassDoc loadedClass = classNamed(packageName + "." + name);
753 if (null != loadedClass) {
754 return loadedClass.qualifiedName();
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);
765 if (topLevelClassNames.contains(topLevelName)) {
766 //System.err.println(this + ".match returns " + packageName + "." + name);
767 return packageName + "." + name;
769 // FIXME: inner classes
770 else {
771 return null;
776 public boolean mismatch(String name)
778 return null == match(name);
781 public ClassDoc tryFetch(String name)
783 ClassDoc loadedClass = classNamed(packageName + "." + name);
784 if (null != loadedClass) {
785 return loadedClass;
787 else if (null != match(name)) {
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);
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);
804 catch (Exception ignore) {
805 printWarning("Could not parse source file " + classFile);
807 cache.put(topLevelName, topLevelClass);
809 if (null == innerClassName) {
810 return topLevelClass;
812 else {
813 return getInnerClass(topLevelClass, innerClassName);
816 else {
817 return null;
821 public String toString()
823 return "ResolvedImportPackageFile{" + packageFile + "," + packageName + "}";
827 private ClassDoc getInnerClass(ClassDoc topLevelClass, String innerClassName)
829 StringTokenizer st = new StringTokenizer(innerClassName, ".");
830 outer:
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;
841 printWarning("Could not find inner class " + innerClassName + " in class " + topLevelClass.qualifiedName());
842 return null;
844 return topLevelClass;
847 private class ResolvedImportClassFile
848 implements ResolvedImport
850 private File classFile;
851 private String innerClassName;
852 private String name;
853 private ClassDoc classDoc;
854 private boolean alreadyFetched;
855 private String qualifiedName;
857 ResolvedImportClassFile(File classFile, String innerClassName, String name, String qualifiedName)
859 this.classFile = classFile;
860 this.innerClassName = innerClassName;
861 this.name = name;
862 this.qualifiedName = qualifiedName;
865 public String toString()
867 return "ResolvedImportClassFile{" + classFile + "," + innerClassName + "}";
870 public String match(String name)
872 String topLevelName = name;
873 int ndx = topLevelName.indexOf('.');
875 String _innerClassName = null;
876 if (ndx > 0) {
877 _innerClassName = topLevelName.substring(ndx + 1);
878 topLevelName = topLevelName.substring(0, ndx);
881 if (this.name.equals(topLevelName)) {
882 if (null == _innerClassName) {
883 return qualifiedName;
885 else {
886 return qualifiedName + "." + _innerClassName;
889 else {
890 return null;
894 public boolean mismatch(String name)
896 return null == match(name);
899 public ClassDoc tryFetch(String name)
901 if (null != match(name)) {
902 ClassDoc topLevelClass = null;
903 if (alreadyFetched) {
904 topLevelClass = classDoc;
906 else {
907 alreadyFetched = true;
908 try {
909 topLevelClass = parser.processSourceFile(classFile, false, sourceEncoding, null);
911 catch (Exception ignore) {
912 printWarning("Could not parse source file " + classFile);
915 if (null == topLevelClass) {
916 return null;
918 else {
919 return getInnerClass(topLevelClass, innerClassName);
922 else {
923 return null;
927 public String getName()
929 if (innerClassName != null) {
930 return name + innerClassName;
932 else {
933 return name;
938 private class ResolvedImportReflectionClass
939 implements ResolvedImport
941 private Class clazz;
942 private String name;
944 ResolvedImportReflectionClass(Class clazz)
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);
952 else {
953 this.name = className;
957 public String toString()
959 return "ResolvedImportReflectionClass{" + clazz.getName() + "}";
962 public String match(String name)
964 if ((this.name.equals(name)) || (clazz.getName().equals(name))) {
965 return clazz.getName();
967 else {
968 return null;
972 public boolean mismatch(String name)
974 return null == match(name);
977 public ClassDoc tryFetch(String name)
979 if (null != match(name)) {
980 return new ClassDocReflectedImpl(clazz);
982 // FIXME: inner classes?
983 else {
984 return null;
988 public String getName()
990 return name;
994 private class ResolvedImportReflectionPackage
995 implements ResolvedImport
997 private String packagePrefix;
999 ResolvedImportReflectionPackage(String packagePrefix)
1001 this.packagePrefix = packagePrefix;
1004 public String toString()
1006 return "ResolvedImportReflectionPackage{" + packagePrefix + ".*}";
1009 public String match(String name)
1011 try {
1012 Class clazz = Class.forName(packagePrefix + "." + name);
1013 return clazz.getName();
1015 catch (Exception e) {
1016 return null;
1020 public boolean mismatch(String name)
1022 return null == match(name);
1025 public ClassDoc tryFetch(String name)
1027 try {
1028 Class clazz = Class.forName(packagePrefix + name);
1029 return ClassDocReflectedImpl.newInstance(clazz);
1031 catch (Exception e) {
1032 return null;
1036 public String getName()
1038 return packagePrefix;
1042 private List unlocatablePrefixes = new LinkedList();
1044 private ResolvedImport resolveImport(String importSpecifier)
1046 ResolvedImport result = resolveImportFileSystem(importSpecifier);
1047 if (null == result && Main.getInstance().isReflectionEnabled()) {
1048 result = resolveImportReflection(importSpecifier);
1050 if (null == result) {
1051 result = new ResolvedImportNotFound(importSpecifier);
1053 return result;
1056 private ResolvedImport resolveImportReflection(String importSpecifier)
1058 String importedPackageOrClass = importSpecifier;
1059 if (importedPackageOrClass.endsWith(".*")) {
1060 importedPackageOrClass = importedPackageOrClass.substring(0, importedPackageOrClass.length() - 2);
1062 return new ResolvedImportReflectionPackage(importedPackageOrClass);
1064 //return null;
1066 else {
1067 try {
1068 Class importedClass = Class.forName(importSpecifier);
1069 return new ResolvedImportReflectionClass(importedClass);
1071 catch (Throwable ignore) {
1072 return null;
1077 private ResolvedImport resolveImportFileSystem(String importSpecifier)
1079 for (Iterator it = unlocatablePrefixes.iterator(); it.hasNext(); ) {
1080 String unlocatablePrefix = (String)it.next();
1081 if (importSpecifier.startsWith(unlocatablePrefix)) {
1082 return null;
1086 String longestUnlocatablePrefix = "";
1088 for (Iterator it=sourcePath.iterator(); it.hasNext(); ) {
1090 File _sourcePath = (File)it.next();
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));
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('.');
1112 innerClassName.append(token);
1114 return new ResolvedImportClassFile(classFile, innerClassName.toString(), token, importSpecifier);
1116 else {
1117 _sourcePath = new File(_sourcePath, token);
1121 if (st.hasMoreTokens()) {
1122 if (packageOrClassPrefix.length() > longestUnlocatablePrefix.length()) {
1123 longestUnlocatablePrefix = packageOrClassPrefix.toString();
1128 if (longestUnlocatablePrefix.length() > 0) {
1129 unlocatablePrefixes.add(longestUnlocatablePrefix);
1132 return null;
1135 private Map resolvedImportCache = new HashMap();
1137 private ResolvedImport getResolvedImport(String importSpecifier)
1139 ResolvedImport result
1140 = (ResolvedImport)resolvedImportCache.get(importSpecifier);
1141 if (null == result) {
1142 result = resolveImport(importSpecifier);
1143 resolvedImportCache.put(importSpecifier, result);
1145 return result;
1148 public String resolveClassName(String className, ClassDocImpl context)
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);
1156 if (null != resolvedScheduledClassName) {
1157 return resolvedScheduledClassName;
1160 return className;
1163 public ClassDoc findScheduledClassFile(String scheduledClassName,
1164 ClassDoc scheduledClassContext)
1165 throws ParseException, IOException
1167 String resolvedScheduledClassName = null;
1169 if (scheduledClassContext instanceof ClassDocImpl) {
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;
1183 else {
1184 if (!inaccessibleReportedSet.contains(scheduledClassName)) {
1185 inaccessibleReportedSet.add(scheduledClassName);
1186 printWarning("Error while loading class " + scheduledClassName);
1188 // FIXME: output resolved class name here
1189 return null;
1194 else {
1195 System.err.println("findScheduledClassFile for '" + scheduledClassName + "' in proxy for " + scheduledClassContext);
1198 // interpret as fully qualified name on file system
1200 ResolvedImport fqImport = resolveImportFileSystem(scheduledClassName);
1201 if (null != fqImport && fqImport instanceof ResolvedImportClassFile) {
1202 return fqImport.tryFetch(((ResolvedImportClassFile)fqImport).getName());
1205 // use reflection, assume fully qualified class name
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;
1215 catch (Throwable ignore) {
1216 unlocatableReflectedClassNames.add(scheduledClassName);
1219 else {
1220 unlocatableReflectedClassNames.add(scheduledClassName);
1224 if (null == resolvedScheduledClassName) {
1225 resolvedScheduledClassName = scheduledClassName;
1227 if (!unlocatableReportedSet.contains(resolvedScheduledClassName)) {
1228 unlocatableReportedSet.add(resolvedScheduledClassName);
1229 printWarning("Cannot locate class " + resolvedScheduledClassName + " referenced in class " + scheduledClassContext.qualifiedName());
1231 return null;
1234 private Set unlocatableReflectedClassNames = new HashSet();
1236 public static boolean recursiveClasses = false;
1238 public void addSpecifiedPackageName(String packageName) {
1239 specifiedPackageNames.add(packageName);
1242 public void addSpecifiedSourceFile(File sourceFile) {
1243 specifiedSourceFiles.add(sourceFile);
1246 public boolean hasSpecifiedPackagesOrClasses() {
1247 return !specifiedPackageNames.isEmpty()
1248 || !specifiedSourceFiles.isEmpty();
1251 public void setOptions(String[][] customOptionArr) {
1252 this.customOptionArr = customOptionArr;
1255 public void setSourcePath(List sourcePath) {
1256 this.sourcePath = sourcePath;
1259 public void finalize() throws Throwable {
1260 super.finalize();
1263 public void flush()
1265 try {
1266 rawCommentCache.close();
1268 catch (IOException e) {
1269 printError("Cannot close raw comment cache");
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;
1288 public void setSourceEncoding(String sourceEncoding)
1290 this.sourceEncoding = sourceEncoding;
1293 public RootDocImpl()
1295 super(null);
1298 public static String readHtmlBody(File file)
1299 throws IOException
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);
1311 fr.close();
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);
1327 return html.trim();
1330 public Parser getParser()
1332 return parser;