mirror of
https://github.com/openjdk/jdk.git
synced 2025-09-19 02:24:40 +02:00

6861094: javac -Xprint <file> does not print comments 6985205: access to tree positions and doc comments may be lost across annotation processing rounds Reviewed-by: darcy
439 lines
17 KiB
Java
439 lines
17 KiB
Java
/*
|
|
* Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code 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
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package com.sun.tools.javadoc;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.util.Collection;
|
|
import java.util.EnumSet;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import javax.tools.JavaFileManager.Location;
|
|
import javax.tools.JavaFileObject;
|
|
import javax.tools.StandardJavaFileManager;
|
|
import javax.tools.StandardLocation;
|
|
|
|
import com.sun.tools.javac.code.Symbol.CompletionFailure;
|
|
import com.sun.tools.javac.comp.Annotate;
|
|
import com.sun.tools.javac.parser.DocCommentScanner;
|
|
import com.sun.tools.javac.tree.JCTree;
|
|
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
|
|
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
|
|
import com.sun.tools.javac.util.Abort;
|
|
import com.sun.tools.javac.util.Context;
|
|
import com.sun.tools.javac.util.List;
|
|
import com.sun.tools.javac.util.ListBuffer;
|
|
import com.sun.tools.javac.util.Position;
|
|
|
|
|
|
/**
|
|
* This class could be the main entry point for Javadoc when Javadoc is used as a
|
|
* component in a larger software system. It provides operations to
|
|
* construct a new javadoc processor, and to run it on a set of source
|
|
* files.
|
|
* @author Neal Gafter
|
|
*/
|
|
public class JavadocTool extends com.sun.tools.javac.main.JavaCompiler {
|
|
DocEnv docenv;
|
|
|
|
final Context context;
|
|
final Messager messager;
|
|
final JavadocClassReader reader;
|
|
final JavadocEnter enter;
|
|
final Annotate annotate;
|
|
|
|
/**
|
|
* Construct a new JavaCompiler processor, using appropriately
|
|
* extended phases of the underlying compiler.
|
|
*/
|
|
protected JavadocTool(Context context) {
|
|
super(context);
|
|
this.context = context;
|
|
messager = Messager.instance0(context);
|
|
reader = JavadocClassReader.instance0(context);
|
|
enter = JavadocEnter.instance0(context);
|
|
annotate = Annotate.instance(context);
|
|
}
|
|
|
|
/**
|
|
* For javadoc, the parser needs to keep comments. Overrides method from JavaCompiler.
|
|
*/
|
|
protected boolean keepComments() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Construct a new javadoc tool.
|
|
*/
|
|
public static JavadocTool make0(Context context) {
|
|
Messager messager = null;
|
|
try {
|
|
// force the use of Javadoc's class reader
|
|
JavadocClassReader.preRegister(context);
|
|
|
|
// force the use of Javadoc's own enter phase
|
|
JavadocEnter.preRegister(context);
|
|
|
|
// force the use of Javadoc's own member enter phase
|
|
JavadocMemberEnter.preRegister(context);
|
|
|
|
// force the use of Javadoc's own todo phase
|
|
JavadocTodo.preRegister(context);
|
|
|
|
// force the use of Messager as a Log
|
|
messager = Messager.instance0(context);
|
|
|
|
return new JavadocTool(context);
|
|
} catch (CompletionFailure ex) {
|
|
messager.error(Position.NOPOS, ex.getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public RootDocImpl getRootDocImpl(String doclocale,
|
|
String encoding,
|
|
ModifierFilter filter,
|
|
List<String> javaNames,
|
|
List<String[]> options,
|
|
boolean breakiterator,
|
|
List<String> subPackages,
|
|
List<String> excludedPackages,
|
|
boolean docClasses,
|
|
boolean legacyDoclet,
|
|
boolean quiet) throws IOException {
|
|
docenv = DocEnv.instance(context);
|
|
docenv.showAccess = filter;
|
|
docenv.quiet = quiet;
|
|
docenv.breakiterator = breakiterator;
|
|
docenv.setLocale(doclocale);
|
|
docenv.setEncoding(encoding);
|
|
docenv.docClasses = docClasses;
|
|
docenv.legacyDoclet = legacyDoclet;
|
|
reader.sourceCompleter = docClasses ? null : this;
|
|
|
|
ListBuffer<String> names = new ListBuffer<String>();
|
|
ListBuffer<JCCompilationUnit> classTrees = new ListBuffer<JCCompilationUnit>();
|
|
ListBuffer<JCCompilationUnit> packTrees = new ListBuffer<JCCompilationUnit>();
|
|
|
|
try {
|
|
StandardJavaFileManager fm = (StandardJavaFileManager) docenv.fileManager;
|
|
for (List<String> it = javaNames; it.nonEmpty(); it = it.tail) {
|
|
String name = it.head;
|
|
if (!docClasses && name.endsWith(".java") && new File(name).exists()) {
|
|
JavaFileObject fo = fm.getJavaFileObjects(name).iterator().next();
|
|
docenv.notice("main.Loading_source_file", name);
|
|
JCCompilationUnit tree = parse(fo);
|
|
classTrees.append(tree);
|
|
} else if (isValidPackageName(name)) {
|
|
names = names.append(name);
|
|
} else if (name.endsWith(".java")) {
|
|
docenv.error(null, "main.file_not_found", name);
|
|
} else {
|
|
docenv.error(null, "main.illegal_package_name", name);
|
|
}
|
|
}
|
|
|
|
if (!docClasses) {
|
|
// Recursively search given subpackages. If any packages
|
|
//are found, add them to the list.
|
|
Map<String,List<JavaFileObject>> packageFiles =
|
|
searchSubPackages(subPackages, names, excludedPackages);
|
|
|
|
// Parse the packages
|
|
for (List<String> packs = names.toList(); packs.nonEmpty(); packs = packs.tail) {
|
|
// Parse sources ostensibly belonging to package.
|
|
String packageName = packs.head;
|
|
parsePackageClasses(packageName, packageFiles.get(packageName), packTrees, excludedPackages);
|
|
}
|
|
|
|
if (messager.nerrors() != 0) return null;
|
|
|
|
// Enter symbols for all files
|
|
docenv.notice("main.Building_tree");
|
|
enter.main(classTrees.toList().appendList(packTrees.toList()));
|
|
}
|
|
} catch (Abort ex) {}
|
|
|
|
if (messager.nerrors() != 0)
|
|
return null;
|
|
|
|
if (docClasses)
|
|
return new RootDocImpl(docenv, javaNames, options);
|
|
else
|
|
return new RootDocImpl(docenv, listClasses(classTrees.toList()), names.toList(), options);
|
|
}
|
|
|
|
/** Is the given string a valid package name? */
|
|
boolean isValidPackageName(String s) {
|
|
int index;
|
|
while ((index = s.indexOf('.')) != -1) {
|
|
if (!isValidClassName(s.substring(0, index))) return false;
|
|
s = s.substring(index+1);
|
|
}
|
|
return isValidClassName(s);
|
|
}
|
|
|
|
/**
|
|
* search all directories in path for subdirectory name. Add all
|
|
* .java files found in such a directory to args.
|
|
*/
|
|
private void parsePackageClasses(String name,
|
|
Iterable<JavaFileObject> files,
|
|
ListBuffer<JCCompilationUnit> trees,
|
|
List<String> excludedPackages)
|
|
throws IOException {
|
|
if (excludedPackages.contains(name)) {
|
|
return;
|
|
}
|
|
|
|
boolean hasFiles = false;
|
|
docenv.notice("main.Loading_source_files_for_package", name);
|
|
|
|
if (files == null) {
|
|
Location location = docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH)
|
|
? StandardLocation.SOURCE_PATH : StandardLocation.CLASS_PATH;
|
|
ListBuffer<JavaFileObject> lb = new ListBuffer<JavaFileObject>();
|
|
for (JavaFileObject fo: docenv.fileManager.list(
|
|
location, name, EnumSet.of(JavaFileObject.Kind.SOURCE), false)) {
|
|
String binaryName = docenv.fileManager.inferBinaryName(location, fo);
|
|
String simpleName = getSimpleName(binaryName);
|
|
if (isValidClassName(simpleName)) {
|
|
lb.append(fo);
|
|
}
|
|
}
|
|
files = lb.toList();
|
|
}
|
|
|
|
for (JavaFileObject fo : files) {
|
|
// messager.notice("main.Loading_source_file", fn);
|
|
trees.append(parse(fo));
|
|
hasFiles = true;
|
|
}
|
|
|
|
if (!hasFiles) {
|
|
messager.warning(null, "main.no_source_files_for_package",
|
|
name.replace(File.separatorChar, '.'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively search all directories in path for subdirectory name.
|
|
* Add all packages found in such a directory to packages list.
|
|
*/
|
|
private Map<String,List<JavaFileObject>> searchSubPackages(
|
|
List<String> subPackages,
|
|
ListBuffer<String> packages,
|
|
List<String> excludedPackages)
|
|
throws IOException {
|
|
Map<String,List<JavaFileObject>> packageFiles =
|
|
new HashMap<String,List<JavaFileObject>>();
|
|
|
|
Map<String,Boolean> includedPackages = new HashMap<String,Boolean>();
|
|
includedPackages.put("", true);
|
|
for (String p: excludedPackages)
|
|
includedPackages.put(p, false);
|
|
|
|
if (docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH)) {
|
|
searchSubPackages(subPackages,
|
|
includedPackages,
|
|
packages, packageFiles,
|
|
StandardLocation.SOURCE_PATH,
|
|
EnumSet.of(JavaFileObject.Kind.SOURCE));
|
|
searchSubPackages(subPackages,
|
|
includedPackages,
|
|
packages, packageFiles,
|
|
StandardLocation.CLASS_PATH,
|
|
EnumSet.of(JavaFileObject.Kind.CLASS));
|
|
} else {
|
|
searchSubPackages(subPackages,
|
|
includedPackages,
|
|
packages, packageFiles,
|
|
StandardLocation.CLASS_PATH,
|
|
EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS));
|
|
}
|
|
return packageFiles;
|
|
}
|
|
|
|
private void searchSubPackages(List<String> subPackages,
|
|
Map<String,Boolean> includedPackages,
|
|
ListBuffer<String> packages,
|
|
Map<String, List<JavaFileObject>> packageFiles,
|
|
StandardLocation location, Set<JavaFileObject.Kind> kinds)
|
|
throws IOException {
|
|
for (String subPackage: subPackages) {
|
|
if (!isIncluded(subPackage, includedPackages))
|
|
continue;
|
|
|
|
for (JavaFileObject fo: docenv.fileManager.list(location, subPackage, kinds, true)) {
|
|
String binaryName = docenv.fileManager.inferBinaryName(location, fo);
|
|
String packageName = getPackageName(binaryName);
|
|
String simpleName = getSimpleName(binaryName);
|
|
if (isIncluded(packageName, includedPackages) && isValidClassName(simpleName)) {
|
|
List<JavaFileObject> list = packageFiles.get(packageName);
|
|
list = (list == null ? List.of(fo) : list.prepend(fo));
|
|
packageFiles.put(packageName, list);
|
|
if (!packages.contains(packageName))
|
|
packages.add(packageName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private String getPackageName(String name) {
|
|
int lastDot = name.lastIndexOf(".");
|
|
return (lastDot == -1 ? "" : name.substring(0, lastDot));
|
|
}
|
|
|
|
private String getSimpleName(String name) {
|
|
int lastDot = name.lastIndexOf(".");
|
|
return (lastDot == -1 ? name : name.substring(lastDot + 1));
|
|
}
|
|
|
|
private boolean isIncluded(String packageName, Map<String,Boolean> includedPackages) {
|
|
Boolean b = includedPackages.get(packageName);
|
|
if (b == null) {
|
|
b = isIncluded(getPackageName(packageName), includedPackages);
|
|
includedPackages.put(packageName, b);
|
|
}
|
|
return b;
|
|
}
|
|
|
|
/**
|
|
* Recursively search all directories in path for subdirectory name.
|
|
* Add all packages found in such a directory to packages list.
|
|
*/
|
|
private void searchSubPackage(String packageName,
|
|
ListBuffer<String> packages,
|
|
List<String> excludedPackages,
|
|
Collection<File> pathnames) {
|
|
if (excludedPackages.contains(packageName))
|
|
return;
|
|
|
|
String packageFilename = packageName.replace('.', File.separatorChar);
|
|
boolean addedPackage = false;
|
|
for (File pathname : pathnames) {
|
|
File f = new File(pathname, packageFilename);
|
|
String filenames[] = f.list();
|
|
// if filenames not null, then found directory
|
|
if (filenames != null) {
|
|
for (String filename : filenames) {
|
|
if (!addedPackage
|
|
&& (isValidJavaSourceFile(filename) ||
|
|
isValidJavaClassFile(filename))
|
|
&& !packages.contains(packageName)) {
|
|
packages.append(packageName);
|
|
addedPackage = true;
|
|
} else if (isValidClassName(filename) &&
|
|
(new File(f, filename)).isDirectory()) {
|
|
searchSubPackage(packageName + "." + filename,
|
|
packages, excludedPackages, pathnames);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if given file name is a valid class file name.
|
|
* @param file the name of the file to check.
|
|
* @return true if given file name is a valid class file name
|
|
* and false otherwise.
|
|
*/
|
|
private static boolean isValidJavaClassFile(String file) {
|
|
if (!file.endsWith(".class")) return false;
|
|
String clazzName = file.substring(0, file.length() - ".class".length());
|
|
return isValidClassName(clazzName);
|
|
}
|
|
|
|
/**
|
|
* Return true if given file name is a valid Java source file name.
|
|
* @param file the name of the file to check.
|
|
* @return true if given file name is a valid Java source file name
|
|
* and false otherwise.
|
|
*/
|
|
private static boolean isValidJavaSourceFile(String file) {
|
|
if (!file.endsWith(".java")) return false;
|
|
String clazzName = file.substring(0, file.length() - ".java".length());
|
|
return isValidClassName(clazzName);
|
|
}
|
|
|
|
/** Are surrogates supported?
|
|
*/
|
|
final static boolean surrogatesSupported = surrogatesSupported();
|
|
private static boolean surrogatesSupported() {
|
|
try {
|
|
boolean b = Character.isHighSurrogate('a');
|
|
return true;
|
|
} catch (NoSuchMethodError ex) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if given file name is a valid class name
|
|
* (including "package-info").
|
|
* @param clazzname the name of the class to check.
|
|
* @return true if given class name is a valid class name
|
|
* and false otherwise.
|
|
*/
|
|
public static boolean isValidClassName(String s) {
|
|
if (s.length() < 1) return false;
|
|
if (s.equals("package-info")) return true;
|
|
if (surrogatesSupported) {
|
|
int cp = s.codePointAt(0);
|
|
if (!Character.isJavaIdentifierStart(cp))
|
|
return false;
|
|
for (int j=Character.charCount(cp); j<s.length(); j+=Character.charCount(cp)) {
|
|
cp = s.codePointAt(j);
|
|
if (!Character.isJavaIdentifierPart(cp))
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!Character.isJavaIdentifierStart(s.charAt(0)))
|
|
return false;
|
|
for (int j=1; j<s.length(); j++)
|
|
if (!Character.isJavaIdentifierPart(s.charAt(j)))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* From a list of top level trees, return the list of contained class definitions
|
|
*/
|
|
List<JCClassDecl> listClasses(List<JCCompilationUnit> trees) {
|
|
ListBuffer<JCClassDecl> result = new ListBuffer<JCClassDecl>();
|
|
for (JCCompilationUnit t : trees) {
|
|
for (JCTree def : t.defs) {
|
|
if (def.getTag() == JCTree.CLASSDEF)
|
|
result.append((JCClassDecl)def);
|
|
}
|
|
}
|
|
return result.toList();
|
|
}
|
|
|
|
}
|