duke@1: /* jfranck@2007: * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. duke@1: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. duke@1: * duke@1: * This code is free software; you can redistribute it and/or modify it duke@1: * under the terms of the GNU General Public License version 2 only, as ohair@554: * published by the Free Software Foundation. Oracle designates this duke@1: * particular file as subject to the "Classpath" exception as provided ohair@554: * by Oracle in the LICENSE file that accompanied this code. duke@1: * duke@1: * This code is distributed in the hope that it will be useful, but WITHOUT duke@1: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or duke@1: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License duke@1: * version 2 for more details (a copy is included in the LICENSE file that duke@1: * accompanied this code). duke@1: * duke@1: * You should have received a copy of the GNU General Public License version duke@1: * 2 along with this work; if not, write to the Free Software Foundation, duke@1: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. duke@1: * ohair@554: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohair@554: * or visit www.oracle.com if you need additional information or have any ohair@554: * questions. duke@1: */ duke@1: duke@1: package com.sun.tools.javadoc; duke@1: jjg@197: import java.io.File; jjg@197: import java.io.IOException; jjg@197: import java.util.Collection; jjg@197: import java.util.EnumSet; jjg@197: import java.util.HashMap; jjg@197: import java.util.Map; jjg@197: import java.util.Set; jjg@197: import javax.tools.JavaFileManager.Location; jjg@197: import javax.tools.JavaFileObject; jjg@197: import javax.tools.StandardJavaFileManager; jjg@197: import javax.tools.StandardLocation; duke@1: jjg@197: import com.sun.tools.javac.code.Symbol.CompletionFailure; jjg@197: import com.sun.tools.javac.tree.JCTree; jjg@197: import com.sun.tools.javac.tree.JCTree.JCClassDecl; jjg@197: import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; jjg@197: import com.sun.tools.javac.util.Abort; jjg@197: import com.sun.tools.javac.util.Context; jjg@197: import com.sun.tools.javac.util.List; jjg@197: import com.sun.tools.javac.util.ListBuffer; jjg@197: import com.sun.tools.javac.util.Position; duke@1: duke@1: duke@1: /** duke@1: * This class could be the main entry point for Javadoc when Javadoc is used as a duke@1: * component in a larger software system. It provides operations to duke@1: * construct a new javadoc processor, and to run it on a set of source duke@1: * files. jjg@1359: * jjg@1359: *

This is NOT part of any supported API. jjg@1359: * If you write code that depends on this, you do so at your own risk. jjg@1359: * This code and its internal interfaces are subject to change or jjg@1359: * deletion without notice. jjg@1359: * duke@1: * @author Neal Gafter duke@1: */ duke@1: public class JavadocTool extends com.sun.tools.javac.main.JavaCompiler { duke@1: DocEnv docenv; duke@1: duke@1: final Messager messager; jjg@1411: final JavadocClassReader javadocReader; jjg@1411: final JavadocEnter javadocEnter; duke@1: duke@1: /** duke@1: * Construct a new JavaCompiler processor, using appropriately duke@1: * extended phases of the underlying compiler. duke@1: */ duke@1: protected JavadocTool(Context context) { duke@1: super(context); duke@1: messager = Messager.instance0(context); jjg@1411: javadocReader = JavadocClassReader.instance0(context); jjg@1411: javadocEnter = JavadocEnter.instance0(context); duke@1: } duke@1: duke@1: /** duke@1: * For javadoc, the parser needs to keep comments. Overrides method from JavaCompiler. duke@1: */ duke@1: protected boolean keepComments() { duke@1: return true; duke@1: } duke@1: duke@1: /** duke@1: * Construct a new javadoc tool. duke@1: */ duke@1: public static JavadocTool make0(Context context) { duke@1: Messager messager = null; duke@1: try { duke@1: // force the use of Javadoc's class reader duke@1: JavadocClassReader.preRegister(context); duke@1: duke@1: // force the use of Javadoc's own enter phase duke@1: JavadocEnter.preRegister(context); duke@1: duke@1: // force the use of Javadoc's own member enter phase duke@1: JavadocMemberEnter.preRegister(context); duke@1: duke@1: // force the use of Javadoc's own todo phase duke@1: JavadocTodo.preRegister(context); duke@1: duke@1: // force the use of Messager as a Log duke@1: messager = Messager.instance0(context); duke@1: duke@1: return new JavadocTool(context); duke@1: } catch (CompletionFailure ex) { duke@1: messager.error(Position.NOPOS, ex.getMessage()); duke@1: return null; duke@1: } duke@1: } duke@1: duke@1: public RootDocImpl getRootDocImpl(String doclocale, duke@1: String encoding, duke@1: ModifierFilter filter, duke@1: List javaNames, duke@1: List options, jjg@1413: Iterable fileObjects, duke@1: boolean breakiterator, duke@1: List subPackages, duke@1: List excludedPackages, duke@1: boolean docClasses, duke@1: boolean legacyDoclet, duke@1: boolean quiet) throws IOException { duke@1: docenv = DocEnv.instance(context); duke@1: docenv.showAccess = filter; jjg@197: docenv.quiet = quiet; duke@1: docenv.breakiterator = breakiterator; duke@1: docenv.setLocale(doclocale); duke@1: docenv.setEncoding(encoding); duke@1: docenv.docClasses = docClasses; duke@1: docenv.legacyDoclet = legacyDoclet; jfranck@2007: javadocReader.sourceCompleter = docClasses ? null : thisCompleter; duke@1: duke@1: ListBuffer names = new ListBuffer(); duke@1: ListBuffer classTrees = new ListBuffer(); duke@1: ListBuffer packTrees = new ListBuffer(); duke@1: duke@1: try { jjg@1413: StandardJavaFileManager fm = docenv.fileManager instanceof StandardJavaFileManager jjg@1413: ? (StandardJavaFileManager) docenv.fileManager : null; duke@1: for (List it = javaNames; it.nonEmpty(); it = it.tail) { duke@1: String name = it.head; jjg@1413: if (!docClasses && fm != null && name.endsWith(".java") && new File(name).exists()) { jjg@197: JavaFileObject fo = fm.getJavaFileObjects(name).iterator().next(); duke@1: docenv.notice("main.Loading_source_file", name); jjg@197: JCCompilationUnit tree = parse(fo); jjg@197: classTrees.append(tree); duke@1: } else if (isValidPackageName(name)) { duke@1: names = names.append(name); duke@1: } else if (name.endsWith(".java")) { jjg@1413: if (fm == null) jjg@1413: throw new IllegalArgumentException(); jjg@1413: else jjg@1411: docenv.error(null, "main.file_not_found", name); duke@1: } else { duke@1: docenv.error(null, "main.illegal_package_name", name); duke@1: } duke@1: } jjg@1413: for (JavaFileObject fo: fileObjects) { jjg@1413: docenv.notice("main.Loading_source_file", fo.getName()); jjg@1413: JCCompilationUnit tree = parse(fo); jjg@1413: classTrees.append(tree); jjg@1413: } duke@1: duke@1: if (!docClasses) { duke@1: // Recursively search given subpackages. If any packages duke@1: //are found, add them to the list. jjg@197: Map> packageFiles = jjg@197: searchSubPackages(subPackages, names, excludedPackages); duke@1: duke@1: // Parse the packages duke@1: for (List packs = names.toList(); packs.nonEmpty(); packs = packs.tail) { duke@1: // Parse sources ostensibly belonging to package. jjg@197: String packageName = packs.head; jjg@197: parsePackageClasses(packageName, packageFiles.get(packageName), packTrees, excludedPackages); duke@1: } duke@1: duke@1: if (messager.nerrors() != 0) return null; duke@1: duke@1: // Enter symbols for all files duke@1: docenv.notice("main.Building_tree"); jjg@1411: javadocEnter.main(classTrees.toList().appendList(packTrees.toList())); duke@1: } duke@1: } catch (Abort ex) {} duke@1: jjg@197: if (messager.nerrors() != 0) jjg@197: return null; duke@1: duke@1: if (docClasses) duke@1: return new RootDocImpl(docenv, javaNames, options); duke@1: else duke@1: return new RootDocImpl(docenv, listClasses(classTrees.toList()), names.toList(), options); duke@1: } duke@1: duke@1: /** Is the given string a valid package name? */ duke@1: boolean isValidPackageName(String s) { duke@1: int index; duke@1: while ((index = s.indexOf('.')) != -1) { duke@1: if (!isValidClassName(s.substring(0, index))) return false; duke@1: s = s.substring(index+1); duke@1: } duke@1: return isValidClassName(s); duke@1: } duke@1: duke@1: /** duke@1: * search all directories in path for subdirectory name. Add all duke@1: * .java files found in such a directory to args. duke@1: */ duke@1: private void parsePackageClasses(String name, jjg@197: Iterable files, jjg@197: ListBuffer trees, jjg@197: List excludedPackages) jjg@197: throws IOException { duke@1: if (excludedPackages.contains(name)) { duke@1: return; duke@1: } jjg@197: duke@1: boolean hasFiles = false; duke@1: docenv.notice("main.Loading_source_files_for_package", name); jjg@197: jjg@197: if (files == null) { jjg@197: Location location = docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH) jjg@197: ? StandardLocation.SOURCE_PATH : StandardLocation.CLASS_PATH; jjg@197: ListBuffer lb = new ListBuffer(); jjg@197: for (JavaFileObject fo: docenv.fileManager.list( jjg@197: location, name, EnumSet.of(JavaFileObject.Kind.SOURCE), false)) { jjg@197: String binaryName = docenv.fileManager.inferBinaryName(location, fo); jjg@197: String simpleName = getSimpleName(binaryName); jjg@197: if (isValidClassName(simpleName)) { jjg@197: lb.append(fo); duke@1: } duke@1: } jjg@197: files = lb.toList(); duke@1: } jjg@197: jjg@197: for (JavaFileObject fo : files) { jjg@197: // messager.notice("main.Loading_source_file", fn); jjg@197: trees.append(parse(fo)); jjg@197: hasFiles = true; jjg@197: } jjg@197: jjg@197: if (!hasFiles) { jjg@1411: messager.warning(Messager.NOPOS, "main.no_source_files_for_package", jjg@197: name.replace(File.separatorChar, '.')); jjg@197: } duke@1: } duke@1: duke@1: /** duke@1: * Recursively search all directories in path for subdirectory name. duke@1: * Add all packages found in such a directory to packages list. duke@1: */ jjg@197: private Map> searchSubPackages( jjg@197: List subPackages, jjg@197: ListBuffer packages, jjg@197: List excludedPackages) jjg@197: throws IOException { jjg@197: Map> packageFiles = jjg@197: new HashMap>(); jjg@197: jjg@197: Map includedPackages = new HashMap(); jjg@197: includedPackages.put("", true); jjg@197: for (String p: excludedPackages) jjg@197: includedPackages.put(p, false); jjg@197: jjg@1094: StandardLocation path = docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH) jjg@1094: ? StandardLocation.SOURCE_PATH : StandardLocation.CLASS_PATH; jjg@1094: jjg@1094: searchSubPackages(subPackages, jjg@1094: includedPackages, jjg@1094: packages, packageFiles, jjg@1094: path, jjg@1094: EnumSet.of(JavaFileObject.Kind.SOURCE)); jjg@1094: jjg@197: return packageFiles; jjg@197: } jjg@197: duke@1: private void searchSubPackages(List subPackages, jjg@197: Map includedPackages, jjg@197: ListBuffer packages, jjg@197: Map> packageFiles, jjg@197: StandardLocation location, Set kinds) jjg@197: throws IOException { jjg@197: for (String subPackage: subPackages) { jjg@197: if (!isIncluded(subPackage, includedPackages)) jjg@197: continue; duke@1: jjg@197: for (JavaFileObject fo: docenv.fileManager.list(location, subPackage, kinds, true)) { jjg@197: String binaryName = docenv.fileManager.inferBinaryName(location, fo); jjg@197: String packageName = getPackageName(binaryName); jjg@197: String simpleName = getSimpleName(binaryName); jjg@197: if (isIncluded(packageName, includedPackages) && isValidClassName(simpleName)) { jjg@197: List list = packageFiles.get(packageName); jjg@197: list = (list == null ? List.of(fo) : list.prepend(fo)); jjg@197: packageFiles.put(packageName, list); jjg@197: if (!packages.contains(packageName)) jjg@197: packages.add(packageName); jjg@197: } jjg@197: } jjg@197: } jjg@197: } jjg@197: jjg@197: private String getPackageName(String name) { jjg@197: int lastDot = name.lastIndexOf("."); jjg@197: return (lastDot == -1 ? "" : name.substring(0, lastDot)); jjg@197: } jjg@197: jjg@197: private String getSimpleName(String name) { jjg@197: int lastDot = name.lastIndexOf("."); jjg@197: return (lastDot == -1 ? name : name.substring(lastDot + 1)); jjg@197: } jjg@197: jjg@197: private boolean isIncluded(String packageName, Map includedPackages) { jjg@197: Boolean b = includedPackages.get(packageName); jjg@197: if (b == null) { jjg@197: b = isIncluded(getPackageName(packageName), includedPackages); jjg@197: includedPackages.put(packageName, b); jjg@197: } jjg@197: return b; duke@1: } duke@1: duke@1: /** duke@1: * Recursively search all directories in path for subdirectory name. duke@1: * Add all packages found in such a directory to packages list. duke@1: */ duke@1: private void searchSubPackage(String packageName, duke@1: ListBuffer packages, duke@1: List excludedPackages, duke@1: Collection pathnames) { duke@1: if (excludedPackages.contains(packageName)) duke@1: return; duke@1: duke@1: String packageFilename = packageName.replace('.', File.separatorChar); duke@1: boolean addedPackage = false; duke@1: for (File pathname : pathnames) { duke@1: File f = new File(pathname, packageFilename); duke@1: String filenames[] = f.list(); duke@1: // if filenames not null, then found directory duke@1: if (filenames != null) { duke@1: for (String filename : filenames) { duke@1: if (!addedPackage duke@1: && (isValidJavaSourceFile(filename) || duke@1: isValidJavaClassFile(filename)) duke@1: && !packages.contains(packageName)) { duke@1: packages.append(packageName); duke@1: addedPackage = true; duke@1: } else if (isValidClassName(filename) && duke@1: (new File(f, filename)).isDirectory()) { duke@1: searchSubPackage(packageName + "." + filename, duke@1: packages, excludedPackages, pathnames); duke@1: } duke@1: } duke@1: } duke@1: } duke@1: } duke@1: duke@1: /** duke@1: * Return true if given file name is a valid class file name. duke@1: * @param file the name of the file to check. duke@1: * @return true if given file name is a valid class file name duke@1: * and false otherwise. duke@1: */ duke@1: private static boolean isValidJavaClassFile(String file) { duke@1: if (!file.endsWith(".class")) return false; duke@1: String clazzName = file.substring(0, file.length() - ".class".length()); duke@1: return isValidClassName(clazzName); duke@1: } duke@1: duke@1: /** duke@1: * Return true if given file name is a valid Java source file name. duke@1: * @param file the name of the file to check. duke@1: * @return true if given file name is a valid Java source file name duke@1: * and false otherwise. duke@1: */ duke@1: private static boolean isValidJavaSourceFile(String file) { duke@1: if (!file.endsWith(".java")) return false; duke@1: String clazzName = file.substring(0, file.length() - ".java".length()); duke@1: return isValidClassName(clazzName); duke@1: } duke@1: duke@1: /** Are surrogates supported? duke@1: */ duke@1: final static boolean surrogatesSupported = surrogatesSupported(); duke@1: private static boolean surrogatesSupported() { duke@1: try { duke@1: boolean b = Character.isHighSurrogate('a'); duke@1: return true; duke@1: } catch (NoSuchMethodError ex) { duke@1: return false; duke@1: } duke@1: } duke@1: duke@1: /** duke@1: * Return true if given file name is a valid class name duke@1: * (including "package-info"). jjg@1358: * @param s the name of the class to check. duke@1: * @return true if given class name is a valid class name duke@1: * and false otherwise. duke@1: */ duke@1: public static boolean isValidClassName(String s) { duke@1: if (s.length() < 1) return false; duke@1: if (s.equals("package-info")) return true; duke@1: if (surrogatesSupported) { duke@1: int cp = s.codePointAt(0); duke@1: if (!Character.isJavaIdentifierStart(cp)) duke@1: return false; duke@1: for (int j=Character.charCount(cp); j listClasses(List trees) { duke@1: ListBuffer result = new ListBuffer(); duke@1: for (JCCompilationUnit t : trees) { duke@1: for (JCTree def : t.defs) { jjg@1127: if (def.hasTag(JCTree.Tag.CLASSDEF)) duke@1: result.append((JCClassDecl)def); duke@1: } duke@1: } duke@1: return result.toList(); duke@1: } duke@1: duke@1: }