duke@1: /*
jjg@1358: * Copyright (c) 2001, 2012, 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 extends JavaFileObject> 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;
jjg@1411: javadocReader.sourceCompleter = docClasses ? null : this;
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: }