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 extends JavaFileObject> 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: }