/* 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; } }