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

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