| /* gnu.classpath.tools.gjdoc.RootDocImpl |
| Copyright (C) 2001, 2007 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307 USA. */ |
| |
| package gnu.classpath.tools.gjdoc; |
| |
| import com.sun.javadoc.*; |
| import java.util.*; |
| import java.io.*; |
| import java.lang.reflect.*; |
| |
| public class RootDocImpl |
| extends DocImpl |
| implements GjdocRootDoc { |
| |
| private ErrorReporter reporter = new ErrorReporter(); |
| |
| private RandomAccessFile rawCommentCache; |
| |
| /** |
| * All options and their corresponding values which are not recognized |
| * by Gjdoc. These are passed to the Doclet as "custom options". |
| * Each element in this array is again a String array, with the |
| * option name as first element (including prefix dash) and possible |
| * option values as following elements. |
| */ |
| private String[][] customOptionArr; |
| |
| /** |
| * All source files explicitly specified on the command line. |
| * |
| * @contains File |
| */ |
| private List specifiedSourceFiles = new LinkedList(); |
| |
| /** |
| * The names of all packages explicitly specified on the |
| * command line. |
| * |
| * @contains String |
| */ |
| private Set specifiedPackageNames = new LinkedHashSet(); |
| |
| /** |
| * Stores all classes specified by the user: those given by |
| * individual class names on the command line, and those |
| * contained in the packages given on the command line. |
| * |
| * @contains ClassDocImpl |
| */ |
| private List classesList = new LinkedList(); //new LinkedList(); |
| |
| /** |
| * Stores all classes loaded in the course of preparing |
| * the documentation data. Maps the fully qualified name |
| * of a class to its ClassDocImpl representation. |
| * |
| * @contains String->ClassDocImpl |
| */ |
| private Map classDocMap = new HashMap(); |
| |
| /** |
| * Stores all packages loaded in the course of preparing |
| * the documentation data. Maps the package name |
| * to its PackageDocImpl representation. |
| * |
| * @contains String->PackageDocImpl |
| */ |
| private Map packageDocMap = new HashMap(); |
| |
| /** |
| * All classes specified by the user, both those explicitly |
| * individually specified on the command line and those contained |
| * in packages specified on the command line (as Array for quick |
| * retrieval by Doclet). This is created from classesList after |
| * all classes have been loaded. |
| */ |
| private ClassDocImpl[] classes; |
| |
| /** |
| * All classes which were individually specified on the command |
| * line (as Array for quick retrieval by Doclet). This is created |
| * from specifiedClassNames after all classes have been loaded. |
| */ |
| private List specifiedClasses; |
| |
| /** |
| * All packages which were specified on the command line (as Array |
| * for quick retrieval by Doclet). This is created from |
| * specifiedPackageNames after all classes have been loaded. |
| */ |
| private Set specifiedPackages; |
| |
| |
| /** |
| * Temporarily stores a list of classes which are referenced |
| * by classes already loaded and which still have to be |
| * resolved. |
| */ |
| private List scheduledClasses=new LinkedList(); |
| |
| private List sourcePath; |
| |
| private String sourceEncoding; |
| |
| private Parser parser = new Parser(); |
| |
| private Set unlocatableReportedSet = new HashSet(); |
| |
| private Set inaccessibleReportedSet = new HashSet(); |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Implementation of RootDoc interface |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Return classes and interfaces to be documented. |
| */ |
| public ClassDoc[] classes() { return classes; } |
| |
| /** |
| * Return a ClassDoc object for the specified class/interface |
| * name. |
| * |
| * @return a ClassDoc object describing the given class, or |
| * <code>null</code> if no corresponding ClassDoc object |
| * has been constructed. |
| */ |
| public ClassDoc classNamed(String qualifiedName) { |
| return (ClassDoc)classDocMap.get(qualifiedName); |
| } |
| |
| /** |
| * Return an xxx |
| */ |
| public String[][] options() { return customOptionArr; } |
| |
| // Return a PackageDoc for the specified package name |
| public PackageDoc packageNamed(String name) { |
| return (PackageDoc)packageDocMap.get(name); |
| } |
| |
| |
| // classes and interfaces specified on the command line. |
| public ClassDoc[] specifiedClasses() |
| { |
| return (ClassDocImpl[]) specifiedClasses.toArray(new ClassDocImpl[0]); |
| } |
| |
| // packages specified on the command line. |
| public PackageDoc[] specifiedPackages() |
| { |
| return (PackageDocImpl[])specifiedPackages.toArray(new PackageDocImpl[0]); |
| } |
| |
| // Print error message, increment error count. |
| public void printError(java.lang.String msg) { |
| reporter.printError(msg); |
| } |
| |
| // Print error message, increment error count. |
| public void printFatal(java.lang.String msg) { |
| reporter.printFatal(msg); |
| } |
| |
| // Print a message. |
| public void printNotice(java.lang.String msg) { |
| reporter.printNotice(msg); |
| } |
| |
| // Print warning message, increment warning count. |
| public void printWarning(java.lang.String msg) { |
| reporter.printWarning(msg); |
| } |
| |
| public String name() { |
| return "RootDoc"; |
| } |
| |
| public ErrorReporter getReporter() { |
| return reporter; |
| } |
| |
| public void build() throws ParseException, IOException { |
| |
| //--- Create a temporary random access file for caching comment text. |
| |
| //File rawCommentCacheFile=File.createTempFile("gjdoc_rawcomment",".cache"); |
| File rawCommentCacheFile = new File("gjdoc_rawcomment.cache"); |
| rawCommentCacheFile.deleteOnExit(); |
| rawCommentCache = new RandomAccessFile(rawCommentCacheFile, "rw"); |
| |
| //--- Parse all files in "java.lang". |
| |
| List javaLangSourceDirs = findSourceFiles("java/lang"); |
| if (!javaLangSourceDirs.isEmpty()) { |
| Iterator it = javaLangSourceDirs.iterator(); |
| while (it.hasNext()) { |
| File javaLangSourceDir = (File)it.next(); |
| parser.processSourceDir(javaLangSourceDir, |
| sourceEncoding, "java.lang"); |
| } |
| } |
| else { |
| |
| Debug.log(1, "Sourcepath is "+sourcePath); |
| |
| // Core docs not included in source-path: |
| // we need to gather the information about java.lang |
| // classes via reflection... |
| |
| } |
| |
| //--- Parse all files in explicitly specified package directories. |
| |
| for (Iterator it=specifiedPackageNames.iterator(); it.hasNext(); ) { |
| |
| String specifiedPackageName = (String)it.next(); |
| String displayPackageName = specifiedPackageName; |
| if (null == displayPackageName || 0 == displayPackageName.length()) { |
| displayPackageName = "<unnamed>"; |
| } |
| printNotice("Loading classes for package "+displayPackageName+"..."); |
| String relPath; |
| if (null != specifiedPackageName) { |
| relPath = specifiedPackageName.replace('.',File.separatorChar); |
| } |
| else { |
| relPath = ""; |
| } |
| List sourceDirs = findSourceFiles(relPath); |
| if (!sourceDirs.isEmpty()) { |
| Iterator sourceDirIt = sourceDirs.iterator(); |
| while (sourceDirIt.hasNext()) { |
| File sourceDir = (File)sourceDirIt.next(); |
| parser.processSourceDir(sourceDir, sourceEncoding, specifiedPackageName); |
| } |
| } |
| else { |
| printError("Package '"+specifiedPackageName+"' not found."); |
| } |
| } |
| |
| specifiedClasses = new LinkedList(); |
| |
| //--- Parse all explicitly specified source files. |
| |
| for (Iterator it=specifiedSourceFiles.iterator(); it.hasNext(); ) { |
| |
| File specifiedSourceFile = (File)it.next(); |
| printNotice("Loading source file "+specifiedSourceFile+" ..."); |
| ClassDocImpl classDoc = parser.processSourceFile(specifiedSourceFile, true, sourceEncoding, null); |
| if (null != classDoc) { |
| specifiedClasses.add(classDoc); |
| classesList.add(classDoc); |
| classDoc.setIsIncluded(true); |
| addPackageDoc(classDoc.containingPackage()); |
| } |
| } |
| |
| |
| //--- Let the user know that all specified classes are loaded. |
| |
| printNotice("Constructing Javadoc information..."); |
| |
| //--- Load all classes implicitly referenced by explicitly specified classes. |
| |
| loadScheduledClasses(parser); |
| |
| printNotice("Resolving references in comments..."); |
| |
| resolveComments(); |
| |
| //--- Resolve pending references in all ClassDocImpls |
| |
| printNotice("Resolving references in classes..."); |
| |
| for (Iterator it = classDocMap.values().iterator(); it.hasNext(); ) { |
| ClassDoc cd=(ClassDoc)it.next(); |
| if (cd instanceof ClassDocImpl) { |
| ((ClassDocImpl)cd).resolve(); |
| } |
| } |
| |
| //--- Resolve pending references in all PackageDocImpls |
| |
| printNotice("Resolving references in packages..."); |
| |
| for (Iterator it = packageDocMap.values().iterator(); it.hasNext(); ) { |
| PackageDocImpl pd=(PackageDocImpl)it.next(); |
| pd.resolve(); |
| } |
| |
| //--- Assemble the array with all specified packages |
| |
| specifiedPackages = new LinkedHashSet(); |
| for (Iterator it = specifiedPackageNames.iterator(); it.hasNext(); ) { |
| String specifiedPackageName = (String)it.next(); |
| PackageDoc specifiedPackageDoc = (PackageDoc)packageDocMap.get(specifiedPackageName); |
| if (null!=specifiedPackageDoc) { |
| ((PackageDocImpl)specifiedPackageDoc).setIsIncluded(true); |
| specifiedPackages.add(specifiedPackageDoc); |
| |
| ClassDoc[] packageClassDocs=specifiedPackageDoc.allClasses(); |
| for (int i=0; i<packageClassDocs.length; ++i) { |
| ClassDocImpl specifiedPackageClassDoc=(ClassDocImpl)packageClassDocs[i]; |
| |
| specifiedPackageClassDoc.setIsIncluded(true); |
| classesList.add(specifiedPackageClassDoc); |
| } |
| } |
| } |
| |
| //--- Resolve pending references in comment data of all classes |
| |
| printNotice("Resolving references in class comments..."); |
| |
| for (Iterator it=classDocMap.values().iterator(); it.hasNext(); ) { |
| ClassDoc cd=(ClassDoc)it.next(); |
| if (cd instanceof ClassDocImpl) { |
| ((ClassDocImpl)cd).resolveComments(); |
| } |
| } |
| |
| //--- Resolve pending references in comment data of all packages |
| |
| printNotice("Resolving references in package comments..."); |
| |
| for (Iterator it=packageDocMap.values().iterator(); it.hasNext(); ) { |
| PackageDocImpl pd=(PackageDocImpl)it.next(); |
| pd.resolveComments(); |
| } |
| |
| //--- Create array with all loaded classes |
| |
| this.classes=(ClassDocImpl[])classesList.toArray(new ClassDocImpl[0]); |
| Arrays.sort(this.classes); |
| |
| //--- Close comment cache |
| |
| parser = null; |
| System.gc(); |
| System.gc(); |
| } |
| |
| public long writeRawComment(String rawComment) { |
| try { |
| long pos=rawCommentCache.getFilePointer(); |
| //rawCommentCache.writeUTF(rawComment); |
| byte[] bytes = rawComment.getBytes("utf-8"); |
| rawCommentCache.writeInt(bytes.length); |
| rawCommentCache.write(bytes); |
| return pos; |
| } |
| catch (IOException e) { |
| printFatal("Cannot write to comment cache: "+e.getMessage()); |
| return -1; |
| } |
| } |
| |
| public String readRawComment(long pos) { |
| try { |
| rawCommentCache.seek(pos); |
| int sz = rawCommentCache.readInt(); |
| byte[] bytes = new byte[sz]; |
| rawCommentCache.read(bytes); |
| return new String(bytes, "utf-8"); |
| //return rawCommentCache.readUTF(); |
| } |
| catch (IOException e) { |
| e.printStackTrace(); |
| printFatal("Cannot read from comment cache: "+e.getMessage()); |
| return null; |
| } |
| } |
| |
| List findSourceFiles(String relPath) { |
| |
| List result = new LinkedList(); |
| for (Iterator it = sourcePath.iterator(); it.hasNext(); ) { |
| File path = (File)it.next(); |
| File file = new File(path, relPath); |
| if (file.exists()) { |
| result.add(file); |
| } |
| } |
| |
| return result; |
| } |
| |
| PackageDocImpl findOrCreatePackageDoc(String packageName) { |
| PackageDocImpl rc=(PackageDocImpl)getPackageDoc(packageName); |
| if (null==rc) { |
| rc=new PackageDocImpl(packageName); |
| if (specifiedPackageNames.contains(packageName)) { |
| String packageDirectoryName = packageName.replace('.', File.separatorChar); |
| List packageDirectories = findSourceFiles(packageDirectoryName); |
| Iterator it = packageDirectories.iterator(); |
| boolean packageDocFound = false; |
| while (it.hasNext()) { |
| File packageDirectory = (File)it.next(); |
| File packageDocFile = new File(packageDirectory, "package.html"); |
| rc.setPackageDirectory(packageDirectory); |
| packageDocFound = true; |
| if (null!=packageDocFile && packageDocFile.exists()) { |
| try { |
| rc.setRawCommentText(readHtmlBody(packageDocFile)); |
| } |
| catch (IOException e) { |
| printWarning("Error while reading documentation for package "+packageName+": "+e.getMessage()); |
| } |
| break; |
| } |
| } |
| if (!packageDocFound) { |
| printNotice("No description found for package "+packageName); |
| } |
| } |
| addPackageDoc(rc); |
| } |
| return rc; |
| } |
| |
| public void addClassDoc(ClassDoc cd) { |
| classDocMap.put(cd.qualifiedName(), cd); |
| } |
| |
| public void addClassDocRecursive(ClassDoc cd) { |
| classDocMap.put(cd.qualifiedName(), cd); |
| ClassDoc[] innerClasses = cd.innerClasses(false); |
| for (int i=0; i<innerClasses.length; ++i) { |
| addClassDocRecursive(innerClasses[i]); |
| } |
| } |
| |
| public void addPackageDoc(PackageDoc pd) { |
| packageDocMap.put(pd.name(), pd); |
| } |
| |
| public PackageDocImpl getPackageDoc(String name) { |
| return (PackageDocImpl)packageDocMap.get(name); |
| } |
| |
| public ClassDocImpl getClassDoc(String qualifiedName) { |
| return (ClassDocImpl)classDocMap.get(qualifiedName); |
| } |
| |
| class ScheduledClass { |
| |
| ClassDoc contextClass; |
| String qualifiedName; |
| ScheduledClass(ClassDoc contextClass, String qualifiedName) { |
| this.contextClass=contextClass; |
| this.qualifiedName=qualifiedName; |
| } |
| |
| public String toString() { return "ScheduledClass{"+qualifiedName+"}"; } |
| } |
| |
| public void scheduleClass(ClassDoc context, String qualifiedName) throws ParseException, IOException { |
| |
| if (classDocMap.get(qualifiedName)==null) { |
| |
| //Debug.log(9,"Scheduling "+qualifiedName+", context "+context+"."); |
| //System.err.println("Scheduling " + qualifiedName + ", context " + context); |
| |
| scheduledClasses.add(new ScheduledClass(context, qualifiedName)); |
| } |
| } |
| |
| /** |
| * Load all classes that were implictly referenced by the classes |
| * (already loaded) that the user explicitly specified on the |
| * command line. |
| * |
| * For example, if the user generates Documentation for his simple |
| * 'class Test {}', which of course 'extends java.lang.Object', |
| * then 'java.lang.Object' is implicitly referenced because it is |
| * the base class of Test. |
| * |
| * Gjdoc needs a ClassDocImpl representation of all classes |
| * implicitly referenced through derivation (base class), |
| * or implementation (interface), or field type, method argument |
| * type, or method return type. |
| * |
| * The task of this method is to ensure that Gjdoc has all this |
| * information at hand when it exits. |
| * |
| * |
| */ |
| public void loadScheduledClasses(Parser parser) throws ParseException, IOException { |
| |
| // Because the referenced classes could in turn reference other |
| // classes, this method runs as long as there are still unloaded |
| // classes. |
| |
| while (!scheduledClasses.isEmpty()) { |
| |
| // Make a copy of scheduledClasses and empty it. This |
| // prevents any Concurrent Modification issues. |
| // As the copy won't need to grow (as it won't change) |
| // we make it an Array for performance reasons. |
| |
| ScheduledClass[] scheduledClassesArr = (ScheduledClass[])scheduledClasses.toArray(new ScheduledClass[0]); |
| scheduledClasses.clear(); |
| |
| // Load each class specified in our array copy |
| |
| for (int i=0; i<scheduledClassesArr.length; ++i) { |
| |
| // The name of the class we are looking for. This name |
| // needs not be fully qualified. |
| |
| String scheduledClassName=scheduledClassesArr[i].qualifiedName; |
| |
| // The ClassDoc in whose context the scheduled class was looked for. |
| // This is necessary in order to resolve non-fully qualified |
| // class names. |
| ClassDoc scheduledClassContext=scheduledClassesArr[i].contextClass; |
| |
| // If there already is a class doc with this name, skip. There's |
| // nothing to do for us. |
| if (classDocMap.get(scheduledClassName)!=null) { |
| continue; |
| } |
| |
| try { |
| // Try to load the class |
| //printNotice("Trying to load " + scheduledClassName); |
| loadScheduledClass(parser, scheduledClassName, scheduledClassContext); |
| } |
| catch (ParseException e) { |
| |
| /********************************************************** |
| |
| // Check whether the following is necessary at all. |
| |
| |
| if (scheduledClassName.indexOf('.')>0) { |
| |
| // Maybe the dotted notation doesn't mean a package |
| // name but instead an inner class, as in 'Outer.Inner'. |
| // so let's assume this and try to load the outer class. |
| |
| String outerClass=""; |
| for (StringTokenizer st=new StringTokenizer(scheduledClassName,"."); st.hasMoreTokens(); ) { |
| if (outerClass.length()>0) outerClass+="."; |
| outerClass+=st.nextToken(); |
| if (!st.hasMoreTokens()) break; |
| try { |
| loadClass(outerClass); |
| //FIXME: shouldn't this be loadScheduledClass(outerClass, scheduledClassContext); ??? |
| continue; |
| } |
| catch (Exception ee) { |
| // Ignore: try next level |
| } |
| } |
| } |
| |
| **********************************************************/ |
| |
| // If we arrive here, the class could not be found |
| |
| printWarning("Couldn't load class "+scheduledClassName+" referenced by "+scheduledClassContext); |
| |
| //FIXME: shouldn't this be throw new Error("cannot load: "+scheduledClassName); |
| } |
| } |
| } |
| } |
| |
| private void loadScheduledClass(Parser parser, String scheduledClassName, ClassDoc scheduledClassContext) throws ParseException, IOException { |
| |
| ClassDoc loadedClass=(ClassDoc)scheduledClassContext.findClass(scheduledClassName); |
| |
| if (loadedClass==null || loadedClass instanceof ClassDocProxy) { |
| |
| ClassDoc classDoc = findScheduledClassFile(scheduledClassName, scheduledClassContext); |
| if (null != classDoc) { |
| |
| if (classDoc instanceof ClassDocReflectedImpl) { |
| Main.getRootDoc().addClassDocRecursive(classDoc); |
| } |
| |
| if (Main.DESCEND_SUPERCLASS |
| && null != classDoc.superclass() |
| && (classDoc.superclass() instanceof ClassDocProxy)) { |
| scheduleClass(classDoc, classDoc.superclass().qualifiedName()); |
| } |
| } |
| else { |
| // It might be an inner class of one of the outer/super classes. |
| // But we can only check that when they are all fully loaded. |
| boolean retryLater = false; |
| |
| int numberOfProcessedFilesBefore = parser.getNumberOfProcessedFiles(); |
| |
| ClassDoc cc = scheduledClassContext.containingClass(); |
| while (cc != null && !retryLater) { |
| ClassDoc sc = cc.superclass(); |
| while (sc != null && !retryLater) { |
| if (sc instanceof ClassDocProxy) { |
| ((ClassDocImpl)cc).resolve(); |
| retryLater = true; |
| } |
| sc = sc.superclass(); |
| } |
| cc = cc.containingClass(); |
| } |
| |
| // Now that outer/super references have been resolved, try again |
| // to find the class. |
| |
| loadedClass = (ClassDoc)scheduledClassContext.findClass(scheduledClassName); |
| |
| int numberOfProcessedFilesAfter = parser.getNumberOfProcessedFiles(); |
| |
| boolean filesWereProcessed = numberOfProcessedFilesAfter > numberOfProcessedFilesBefore; |
| |
| // Only re-schedule class if additional files have been processed |
| // If there haven't, there's no point in re-scheduling. |
| // Will avoid infinite loops of re-scheduling |
| if (null == loadedClass && retryLater && filesWereProcessed) |
| scheduleClass(scheduledClassContext, scheduledClassName); |
| |
| /* A warning needn't be emitted - this is normal, can happen |
| if the scheduled class is in a package which is not |
| included on the command line. |
| |
| else if (null == loadedClass) |
| printWarning("Can't find scheduled class '" |
| + scheduledClassName |
| + "' in context '" |
| + scheduledClassContext.qualifiedName() |
| + "'"); |
| */ |
| } |
| } |
| } |
| |
| private static interface ResolvedImport |
| { |
| public String match(String name); |
| public boolean mismatch(String name); |
| public ClassDoc tryFetch(String name); |
| } |
| |
| private class ResolvedImportNotFound |
| implements ResolvedImport |
| { |
| private String importSpecifier; |
| private String name; |
| |
| ResolvedImportNotFound(String importSpecifier) |
| { |
| this.importSpecifier = importSpecifier; |
| int ndx = importSpecifier.lastIndexOf('.'); |
| if (ndx >= 0) { |
| this.name = importSpecifier.substring(ndx + 1); |
| } |
| else { |
| this.name = importSpecifier; |
| } |
| } |
| |
| public String toString() |
| { |
| return "ResolvedImportNotFound{" + importSpecifier + "}"; |
| } |
| |
| public String match(String name) |
| { |
| if ((name.equals(this.name)) || (importSpecifier.equals(name))) |
| return this.name; |
| // FIXME: note that we don't handle on-demand imports here. |
| return null; |
| } |
| |
| public boolean mismatch(String name) |
| { |
| return true; // FIXME! |
| } |
| |
| public ClassDoc tryFetch(String name) |
| { |
| return null; |
| } |
| } |
| |
| private class ResolvedImportPackageFile |
| implements ResolvedImport |
| { |
| private Set topLevelClassNames; |
| private File packageFile; |
| private String packageName; |
| private Map cache = new HashMap(); |
| |
| ResolvedImportPackageFile(File packageFile, String packageName) |
| { |
| this.packageFile = packageFile; |
| this.packageName = packageName; |
| topLevelClassNames = new HashSet(); |
| File[] files = packageFile.listFiles(); |
| for (int i=0; i<files.length; ++i) { |
| if (!files[i].isDirectory() && files[i].getName().endsWith(".java")) { |
| String topLevelClassName = files[i].getName(); |
| topLevelClassName |
| = topLevelClassName.substring(0, topLevelClassName.length() - 5); |
| topLevelClassNames.add(topLevelClassName); |
| } |
| } |
| } |
| |
| public String match(String name) |
| { |
| ClassDoc loadedClass = classNamed(packageName + "." + name); |
| if (null != loadedClass) { |
| return loadedClass.qualifiedName(); |
| } |
| else { |
| String topLevelName = name; |
| int ndx = topLevelName.indexOf('.'); |
| String innerClassName = null; |
| if (ndx > 0) { |
| innerClassName = topLevelName.substring(ndx + 1); |
| topLevelName = topLevelName.substring(0, ndx); |
| } |
| |
| if (topLevelClassNames.contains(topLevelName)) { |
| //System.err.println(this + ".match returns " + packageName + "." + name); |
| return packageName + "." + name; |
| } |
| // FIXME: inner classes |
| else { |
| return null; |
| } |
| } |
| } |
| |
| public boolean mismatch(String name) |
| { |
| return null == match(name); |
| } |
| |
| public ClassDoc tryFetch(String name) |
| { |
| ClassDoc loadedClass = classNamed(packageName + "." + name); |
| if (null != loadedClass) { |
| return loadedClass; |
| } |
| else if (null != match(name)) { |
| |
| String topLevelName = name; |
| int ndx = topLevelName.indexOf('.'); |
| String innerClassName = null; |
| if (ndx > 0) { |
| innerClassName = topLevelName.substring(ndx + 1); |
| topLevelName = topLevelName.substring(0, ndx); |
| } |
| |
| ClassDoc topLevelClass = (ClassDoc)cache.get(topLevelName); |
| if (null == topLevelClass) { |
| File classFile = new File(packageFile, topLevelName + ".java"); |
| try { |
| // FIXME: inner classes |
| topLevelClass = parser.processSourceFile(classFile, false, sourceEncoding, null); |
| } |
| catch (Exception ignore) { |
| printWarning("Could not parse source file " + classFile); |
| } |
| cache.put(topLevelName, topLevelClass); |
| } |
| if (null == innerClassName) { |
| return topLevelClass; |
| } |
| else { |
| return getInnerClass(topLevelClass, innerClassName); |
| } |
| } |
| else { |
| return null; |
| } |
| } |
| |
| public String toString() |
| { |
| return "ResolvedImportPackageFile{" + packageFile + "," + packageName + "}"; |
| } |
| } |
| |
| private ClassDoc getInnerClass(ClassDoc topLevelClass, String innerClassName) |
| { |
| StringTokenizer st = new StringTokenizer(innerClassName, "."); |
| outer: |
| |
| while (st.hasMoreTokens()) { |
| String innerClassNameComponent = st.nextToken(); |
| ClassDoc[] innerClasses = topLevelClass.innerClasses(); |
| for (int i=0; i<innerClasses.length; ++i) { |
| if (innerClasses[i].name().equals(innerClassNameComponent)) { |
| topLevelClass = innerClasses[i]; |
| continue outer; |
| } |
| } |
| printWarning("Could not find inner class " + innerClassName + " in class " + topLevelClass.qualifiedName()); |
| return null; |
| } |
| return topLevelClass; |
| } |
| |
| private class ResolvedImportClassFile |
| implements ResolvedImport |
| { |
| private File classFile; |
| private String innerClassName; |
| private String name; |
| private ClassDoc classDoc; |
| private boolean alreadyFetched; |
| private String qualifiedName; |
| |
| ResolvedImportClassFile(File classFile, String innerClassName, String name, String qualifiedName) |
| { |
| this.classFile = classFile; |
| this.innerClassName = innerClassName; |
| this.name = name; |
| this.qualifiedName = qualifiedName; |
| } |
| |
| public String toString() |
| { |
| return "ResolvedImportClassFile{" + classFile + "," + innerClassName + "}"; |
| } |
| |
| public String match(String name) |
| { |
| String topLevelName = name; |
| int ndx = topLevelName.indexOf('.'); |
| |
| String _innerClassName = null; |
| if (ndx > 0) { |
| _innerClassName = topLevelName.substring(ndx + 1); |
| topLevelName = topLevelName.substring(0, ndx); |
| } |
| |
| if (this.name.equals(topLevelName)) { |
| if (null == _innerClassName) { |
| return qualifiedName; |
| } |
| else { |
| return qualifiedName + "." + _innerClassName; |
| } |
| } |
| else { |
| return null; |
| } |
| } |
| |
| public boolean mismatch(String name) |
| { |
| return null == match(name); |
| } |
| |
| public ClassDoc tryFetch(String name) |
| { |
| if (null != match(name)) { |
| ClassDoc topLevelClass = null; |
| if (alreadyFetched) { |
| topLevelClass = classDoc; |
| } |
| else { |
| alreadyFetched = true; |
| try { |
| topLevelClass = parser.processSourceFile(classFile, false, sourceEncoding, null); |
| } |
| catch (Exception ignore) { |
| printWarning("Could not parse source file " + classFile); |
| } |
| } |
| if (null == topLevelClass) { |
| return null; |
| } |
| else { |
| return getInnerClass(topLevelClass, innerClassName); |
| } |
| } |
| else { |
| return null; |
| } |
| } |
| |
| public String getName() |
| { |
| if (innerClassName != null) { |
| return name + innerClassName; |
| } |
| else { |
| return name; |
| } |
| } |
| } |
| |
| private class ResolvedImportReflectionClass |
| implements ResolvedImport |
| { |
| private Class clazz; |
| private String name; |
| |
| ResolvedImportReflectionClass(Class clazz) |
| { |
| this.clazz = clazz; |
| String className = clazz.getName(); |
| int ndx = className.lastIndexOf('.'); |
| if (ndx >= 0) { |
| this.name = className.substring(ndx + 1); |
| } |
| else { |
| this.name = className; |
| } |
| } |
| |
| public String toString() |
| { |
| return "ResolvedImportReflectionClass{" + clazz.getName() + "}"; |
| } |
| |
| public String match(String name) |
| { |
| if ((this.name.equals(name)) || (clazz.getName().equals(name))) { |
| return clazz.getName(); |
| } |
| else { |
| return null; |
| } |
| } |
| |
| public boolean mismatch(String name) |
| { |
| return null == match(name); |
| } |
| |
| public ClassDoc tryFetch(String name) |
| { |
| if (null != match(name)) { |
| return new ClassDocReflectedImpl(clazz); |
| } |
| // FIXME: inner classes? |
| else { |
| return null; |
| } |
| } |
| |
| public String getName() |
| { |
| return name; |
| } |
| } |
| |
| private class ResolvedImportReflectionPackage |
| implements ResolvedImport |
| { |
| private String packagePrefix; |
| |
| ResolvedImportReflectionPackage(String packagePrefix) |
| { |
| this.packagePrefix = packagePrefix; |
| } |
| |
| public String toString() |
| { |
| return "ResolvedImportReflectionPackage{" + packagePrefix + ".*}"; |
| } |
| |
| public String match(String name) |
| { |
| try { |
| Class clazz = Class.forName(packagePrefix + "." + name); |
| return clazz.getName(); |
| } |
| catch (Exception e) { |
| return null; |
| } |
| } |
| |
| public boolean mismatch(String name) |
| { |
| return null == match(name); |
| } |
| |
| public ClassDoc tryFetch(String name) |
| { |
| try { |
| Class clazz = Class.forName(packagePrefix + name); |
| return ClassDocReflectedImpl.newInstance(clazz); |
| } |
| catch (Exception e) { |
| return null; |
| } |
| } |
| |
| public String getName() |
| { |
| return packagePrefix; |
| } |
| } |
| |
| private List unlocatablePrefixes = new LinkedList(); |
| |
| private ResolvedImport resolveImport(String importSpecifier) |
| { |
| ResolvedImport result = resolveImportFileSystem(importSpecifier); |
| if (null == result && Main.getInstance().isReflectionEnabled()) { |
| result = resolveImportReflection(importSpecifier); |
| } |
| if (null == result) { |
| result = new ResolvedImportNotFound(importSpecifier); |
| } |
| return result; |
| } |
| |
| private ResolvedImport resolveImportReflection(String importSpecifier) |
| { |
| String importedPackageOrClass = importSpecifier; |
| if (importedPackageOrClass.endsWith(".*")) { |
| importedPackageOrClass = importedPackageOrClass.substring(0, importedPackageOrClass.length() - 2); |
| |
| return new ResolvedImportReflectionPackage(importedPackageOrClass); |
| |
| //return null; |
| } |
| else { |
| try { |
| Class importedClass = Class.forName(importSpecifier); |
| return new ResolvedImportReflectionClass(importedClass); |
| } |
| catch (Throwable ignore) { |
| return null; |
| } |
| } |
| } |
| |
| private ResolvedImport resolveImportFileSystem(String importSpecifier) |
| { |
| for (Iterator it = unlocatablePrefixes.iterator(); it.hasNext(); ) { |
| String unlocatablePrefix = (String)it.next(); |
| if (importSpecifier.startsWith(unlocatablePrefix)) { |
| return null; |
| } |
| } |
| |
| String longestUnlocatablePrefix = ""; |
| |
| for (Iterator it=sourcePath.iterator(); it.hasNext(); ) { |
| |
| File _sourcePath = (File)it.next(); |
| |
| StringBuffer packageOrClassPrefix = new StringBuffer(); |
| StringTokenizer st = new StringTokenizer(importSpecifier, "."); |
| while (st.hasMoreTokens() && _sourcePath.isDirectory()) { |
| String token = st.nextToken(); |
| if ("*".equals(token)) { |
| return new ResolvedImportPackageFile(_sourcePath, |
| packageOrClassPrefix.substring(0, packageOrClassPrefix.length() - 1)); |
| } |
| else { |
| packageOrClassPrefix.append(token); |
| packageOrClassPrefix.append('.'); |
| File classFile = new File(_sourcePath, token + ".java"); |
| //System.err.println(" looking for file " + classFile); |
| if (classFile.exists()) { |
| StringBuffer innerClassName = new StringBuffer(); |
| while (st.hasMoreTokens()) { |
| token = st.nextToken(); |
| if (innerClassName.length() > 0) { |
| innerClassName.append('.'); |
| } |
| innerClassName.append(token); |
| } |
| return new ResolvedImportClassFile(classFile, innerClassName.toString(), token, importSpecifier); |
| } |
| else { |
| _sourcePath = new File(_sourcePath, token); |
| } |
| } |
| } |
| if (st.hasMoreTokens()) { |
| if (packageOrClassPrefix.length() > longestUnlocatablePrefix.length()) { |
| longestUnlocatablePrefix = packageOrClassPrefix.toString(); |
| } |
| } |
| } |
| |
| if (longestUnlocatablePrefix.length() > 0) { |
| unlocatablePrefixes.add(longestUnlocatablePrefix); |
| } |
| |
| return null; |
| } |
| |
| private Map resolvedImportCache = new HashMap(); |
| |
| private ResolvedImport getResolvedImport(String importSpecifier) |
| { |
| ResolvedImport result |
| = (ResolvedImport)resolvedImportCache.get(importSpecifier); |
| if (null == result) { |
| result = resolveImport(importSpecifier); |
| resolvedImportCache.put(importSpecifier, result); |
| } |
| return result; |
| } |
| |
| public String resolveClassName(String className, ClassDocImpl context) |
| { |
| Iterator it = context.getImportSpecifierList().iterator(); |
| while (it.hasNext()) { |
| String importSpecifier = (String)it.next(); |
| ResolvedImport resolvedImport = getResolvedImport(importSpecifier); |
| String resolvedScheduledClassName = resolvedImport.match(className); |
| |
| if (null != resolvedScheduledClassName) { |
| return resolvedScheduledClassName; |
| } |
| } |
| return className; |
| } |
| |
| public ClassDoc findScheduledClassFile(String scheduledClassName, |
| ClassDoc scheduledClassContext) |
| throws ParseException, IOException |
| { |
| String resolvedScheduledClassName = null; |
| |
| if (scheduledClassContext instanceof ClassDocImpl) { |
| |
| //((ClassDocImpl)scheduledClassContext).resolveReferencedName(scheduledClassName); |
| Iterator it = ((ClassDocImpl)scheduledClassContext).getImportSpecifierList().iterator(); |
| while (it.hasNext()) { |
| String importSpecifier = (String)it.next(); |
| ResolvedImport resolvedImport = getResolvedImport(importSpecifier); |
| //System.err.println(" looking in import '" + resolvedImport + "'"); |
| resolvedScheduledClassName = resolvedImport.match(scheduledClassName); |
| if (null != resolvedScheduledClassName) { |
| ClassDoc result = resolvedImport.tryFetch(scheduledClassName); |
| if (null != result) { |
| return result; |
| } |
| else { |
| if (!inaccessibleReportedSet.contains(scheduledClassName)) { |
| inaccessibleReportedSet.add(scheduledClassName); |
| printWarning("Error while loading class " + scheduledClassName); |
| } |
| // FIXME: output resolved class name here |
| return null; |
| } |
| } |
| } |
| } |
| else { |
| System.err.println("findScheduledClassFile for '" + scheduledClassName + "' in proxy for " + scheduledClassContext); |
| } |
| |
| // interpret as fully qualified name on file system |
| |
| ResolvedImport fqImport = resolveImportFileSystem(scheduledClassName); |
| if (null != fqImport && fqImport instanceof ResolvedImportClassFile) { |
| return fqImport.tryFetch(((ResolvedImportClassFile)fqImport).getName()); |
| } |
| |
| // use reflection, assume fully qualified class name |
| |
| if (!unlocatableReflectedClassNames.contains(scheduledClassName)) { |
| if (Main.getInstance().isReflectionEnabled()) { |
| try { |
| Class clazz = Class.forName(scheduledClassName); |
| printWarning("Cannot locate class " + scheduledClassName + " on file system, falling back to reflection."); |
| ClassDoc result = new ClassDocReflectedImpl(clazz); |
| return result; |
| } |
| catch (Throwable ignore) { |
| unlocatableReflectedClassNames.add(scheduledClassName); |
| } |
| } |
| else { |
| unlocatableReflectedClassNames.add(scheduledClassName); |
| } |
| } |
| |
| if (null == resolvedScheduledClassName) { |
| resolvedScheduledClassName = scheduledClassName; |
| } |
| if (!unlocatableReportedSet.contains(resolvedScheduledClassName)) { |
| unlocatableReportedSet.add(resolvedScheduledClassName); |
| printWarning("Cannot locate class " + resolvedScheduledClassName + " referenced in class " + scheduledClassContext.qualifiedName()); |
| } |
| return null; |
| } |
| |
| private Set unlocatableReflectedClassNames = new HashSet(); |
| |
| public static boolean recursiveClasses = false; |
| |
| public void addSpecifiedPackageName(String packageName) { |
| specifiedPackageNames.add(packageName); |
| } |
| |
| public void addSpecifiedSourceFile(File sourceFile) { |
| specifiedSourceFiles.add(sourceFile); |
| } |
| |
| public boolean hasSpecifiedPackagesOrClasses() { |
| return !specifiedPackageNames.isEmpty() |
| || !specifiedSourceFiles.isEmpty(); |
| } |
| |
| public void setOptions(String[][] customOptionArr) { |
| this.customOptionArr = customOptionArr; |
| } |
| |
| public void setSourcePath(List sourcePath) { |
| this.sourcePath = sourcePath; |
| } |
| |
| public void finalize() throws Throwable { |
| super.finalize(); |
| } |
| |
| public void flush() |
| { |
| try { |
| rawCommentCache.close(); |
| } |
| catch (IOException e) { |
| printError("Cannot close raw comment cache"); |
| } |
| |
| rawCommentCache = null; |
| customOptionArr = null; |
| specifiedPackageNames = null; |
| classesList = null; |
| classDocMap = null; |
| packageDocMap = null; |
| classes = null; |
| specifiedClasses = null; |
| specifiedPackages = null; |
| scheduledClasses = null; |
| sourcePath = null; |
| parser = null; |
| unlocatableReportedSet = null; |
| inaccessibleReportedSet = null; |
| } |
| |
| public void setSourceEncoding(String sourceEncoding) |
| { |
| this.sourceEncoding = sourceEncoding; |
| } |
| |
| public RootDocImpl() |
| { |
| super(null); |
| } |
| |
| public static String readHtmlBody(File file) |
| throws IOException |
| { |
| FileReader fr=new FileReader(file); |
| long size = file.length(); |
| char[] packageDocBuf=new char[(int)(size)]; |
| int index = 0; |
| int i = fr.read(packageDocBuf, index, (int)size); |
| while (i > 0) { |
| index += i; |
| size -= i; |
| i = fr.read(packageDocBuf, index, (int)size); |
| } |
| fr.close(); |
| |
| // We only need the part between the begin and end body tag. |
| String html = new String(packageDocBuf); |
| int start = html.indexOf("<body"); |
| if (start == -1) |
| start = html.indexOf("<BODY"); |
| int end = html.indexOf("</body>"); |
| if (end == -1) |
| end = html.indexOf("</BODY>"); |
| if (start != -1 && end != -1) { |
| // Start is end of body tag. |
| start = html.indexOf('>', start) + 1; |
| if (start != -1 && start < end) |
| html = html.substring(start, end); |
| } |
| return html.trim(); |
| } |
| |
| public Parser getParser() |
| { |
| return parser; |
| } |
| } |