jjg@1455: /* jjg@1455: * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. jjg@1455: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jjg@1455: * jjg@1455: * This code is free software; you can redistribute it and/or modify it jjg@1455: * under the terms of the GNU General Public License version 2 only, as jjg@1455: * published by the Free Software Foundation. Oracle designates this jjg@1455: * particular file as subject to the "Classpath" exception as provided jjg@1455: * by Oracle in the LICENSE file that accompanied this code. jjg@1455: * jjg@1455: * This code is distributed in the hope that it will be useful, but WITHOUT jjg@1455: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jjg@1455: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jjg@1455: * version 2 for more details (a copy is included in the LICENSE file that jjg@1455: * accompanied this code). jjg@1455: * jjg@1455: * You should have received a copy of the GNU General Public License version jjg@1455: * 2 along with this work; if not, write to the Free Software Foundation, jjg@1455: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jjg@1455: * jjg@1455: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jjg@1455: * or visit www.oracle.com if you need additional information or have any jjg@1455: * questions. jjg@1455: */ jjg@1455: jjg@1455: package com.sun.tools.doclint; jjg@1455: jjg@1455: import java.io.File; jjg@1455: import java.io.IOException; jjg@1455: import java.io.PrintWriter; jjg@1455: import java.util.ArrayList; jjg@1455: import java.util.List; jjg@1455: jjg@1455: import javax.lang.model.element.Name; jjg@1455: import javax.tools.StandardLocation; jjg@1455: jjg@1455: import com.sun.source.doctree.DocCommentTree; jjg@1455: import com.sun.source.tree.ClassTree; jjg@1455: import com.sun.source.tree.CompilationUnitTree; jjg@1455: import com.sun.source.tree.MethodTree; jjg@1455: import com.sun.source.tree.Tree; jjg@1455: import com.sun.source.tree.VariableTree; jjg@1455: import com.sun.source.util.JavacTask; jjg@1455: import com.sun.source.util.Plugin; jjg@1455: import com.sun.source.util.TaskEvent; jjg@1455: import com.sun.source.util.TaskListener; jjg@1455: import com.sun.source.util.TreePath; jjg@1455: import com.sun.source.util.TreePathScanner; jjg@1455: import com.sun.tools.javac.api.JavacTaskImpl; jjg@1455: import com.sun.tools.javac.api.JavacTool; jjg@1455: import com.sun.tools.javac.file.JavacFileManager; jjg@1455: import com.sun.tools.javac.main.JavaCompiler; jjg@1455: import com.sun.tools.javac.util.Context; jjg@1455: jjg@1455: /** jjg@1455: * Multi-function entry point for the doc check utility. jjg@1455: * jjg@1455: * This class can be invoked in the following ways: jjg@1455: * jjg@1455: * jjg@1455: *

This is NOT part of any supported API. jjg@1455: * If you write code that depends on this, you do so at your own jjg@1455: * risk. This code and its internal interfaces are subject to change jjg@1455: * or deletion without notice.

jjg@1455: */ jjg@1455: public class DocLint implements Plugin { jjg@1455: jjg@1455: public static final String XMSGS_OPTION = "-Xmsgs"; jjg@1455: public static final String XMSGS_CUSTOM_PREFIX = "-Xmsgs:"; jjg@1455: private static final String STATS = "-stats"; jjg@1455: jjg@1455: // jjg@1455: public static void main(String... args) { jjg@1455: try { jjg@1455: new DocLint().run(args); jjg@1455: } catch (BadArgs e) { jjg@1455: System.err.println(e.getMessage()); jjg@1455: System.exit(1); jjg@1455: } catch (IOException e) { jjg@1455: System.err.println(e); jjg@1455: System.exit(2); jjg@1455: } jjg@1455: } jjg@1455: jjg@1455: // jjg@1455: jjg@1455: // jjg@1455: jjg@1455: public static class BadArgs extends Exception { jjg@1455: private static final long serialVersionUID = 0; jjg@1455: BadArgs(String code, Object... args) { jjg@1455: this.code = code; jjg@1455: this.args = args; jjg@1455: } jjg@1455: jjg@1455: final String code; jjg@1455: final Object[] args; jjg@1455: } jjg@1455: jjg@1455: /** jjg@1455: * Simple API entry point. jjg@1455: */ jjg@1455: public void run(String... args) throws BadArgs, IOException { jjg@1455: PrintWriter out = new PrintWriter(System.out); jjg@1455: try { jjg@1455: run(out, args); jjg@1455: } finally { jjg@1455: out.flush(); jjg@1455: } jjg@1455: } jjg@1455: jjg@1455: public void run(PrintWriter out, String... args) throws BadArgs, IOException { jjg@1455: env = new Env(); jjg@1455: processArgs(args); jjg@1455: jjg@1455: if (needHelp) jjg@1455: showHelp(out); jjg@1455: jjg@1455: if (javacFiles.isEmpty()) { jjg@1455: if (!needHelp) jjg@1455: System.out.println("no files given"); jjg@1455: } jjg@1455: jjg@1455: JavacTool tool = JavacTool.create(); jjg@1455: jjg@1455: JavacFileManager fm = new JavacFileManager(new Context(), false, null); jjg@1455: fm.setSymbolFileEnabled(false); jjg@1455: fm.setLocation(StandardLocation.PLATFORM_CLASS_PATH, javacBootClassPath); jjg@1455: fm.setLocation(StandardLocation.CLASS_PATH, javacClassPath); jjg@1455: fm.setLocation(StandardLocation.SOURCE_PATH, javacSourcePath); jjg@1455: jjg@1455: JavacTask task = tool.getTask(out, fm, null, javacOpts, null, jjg@1455: fm.getJavaFileObjectsFromFiles(javacFiles)); jjg@1455: Iterable units = task.parse(); jjg@1455: ((JavacTaskImpl) task).enter(); jjg@1455: jjg@1455: env.init(task); jjg@1455: checker = new Checker(env); jjg@1455: jjg@1455: DeclScanner ds = new DeclScanner() { jjg@1455: @Override jjg@1455: void visitDecl(Tree tree, Name name) { jjg@1455: TreePath p = getCurrentPath(); jjg@1455: DocCommentTree dc = env.trees.getDocCommentTree(p); jjg@1455: jjg@1455: checker.scan(dc, p); jjg@1455: } jjg@1455: }; jjg@1455: jjg@1455: ds.scan(units, null); jjg@1455: jjg@1455: reportStats(out); jjg@1455: jjg@1455: Context ctx = ((JavacTaskImpl) task).getContext(); jjg@1455: JavaCompiler c = JavaCompiler.instance(ctx); jjg@1455: c.printCount("error", c.errorCount()); jjg@1455: c.printCount("warn", c.warningCount()); jjg@1455: } jjg@1455: jjg@1455: void processArgs(String... args) throws BadArgs { jjg@1455: javacOpts = new ArrayList(); jjg@1455: javacFiles = new ArrayList(); jjg@1455: jjg@1455: if (args.length == 0) jjg@1455: needHelp = true; jjg@1455: jjg@1455: for (int i = 0; i < args.length; i++) { jjg@1455: String arg = args[i]; jjg@1455: if (arg.matches("-Xmax(errs|warns)") && i + 1 < args.length) { jjg@1455: if (args[++i].matches("[0-9]+")) { jjg@1455: javacOpts.add(arg); jjg@1455: javacOpts.add(args[i]); jjg@1455: } else { jjg@1455: throw new BadArgs("dc.bad.value.for.option", arg, args[i]); jjg@1455: } jjg@1455: } else if (arg.equals(STATS)) { jjg@1455: env.messages.setStatsEnabled(true); jjg@1455: } else if (arg.matches("-bootclasspath") && i + 1 < args.length) { jjg@1455: javacBootClassPath = splitPath(args[++i]); jjg@1455: } else if (arg.matches("-classpath") && i + 1 < args.length) { jjg@1455: javacClassPath = splitPath(args[++i]); jjg@1455: } else if (arg.matches("-sourcepath") && i + 1 < args.length) { jjg@1455: javacSourcePath = splitPath(args[++i]); jjg@1455: } else if (arg.equals(XMSGS_OPTION)) { jjg@1455: env.messages.setOptions(null); jjg@1455: } else if (arg.startsWith(XMSGS_CUSTOM_PREFIX)) { jjg@1455: env.messages.setOptions(arg.substring(arg.indexOf(":") + 1)); jjg@1455: } else if (arg.equals("-h") || arg.equals("-help") || arg.equals("--help") jjg@1455: || arg.equals("-?") || arg.equals("-usage")) { jjg@1455: needHelp = true; jjg@1455: } else if (arg.startsWith("-")) { jjg@1455: throw new BadArgs("dc.bad.option", arg); jjg@1455: } else { jjg@1455: while (i < args.length) jjg@1455: javacFiles.add(new File(args[i++])); jjg@1455: } jjg@1455: } jjg@1455: } jjg@1455: jjg@1455: void showHelp(PrintWriter out) { jjg@1455: out.println("Usage:"); jjg@1455: out.println(" doclint [options] source-files..."); jjg@1455: out.println(""); jjg@1455: out.println("Options:"); jjg@1455: out.println(" -Xmsgs "); jjg@1455: out.println(" Same as -Xmsgs:all"); jjg@1455: out.println(" -Xmsgs:values"); jjg@1455: out.println(" Specify categories of issues to be checked, where 'values'"); jjg@1455: out.println(" is a comma-separated list of any of the following:"); jjg@1455: out.println(" reference show places where comments contain incorrect"); jjg@1455: out.println(" references to Java source code elements"); jjg@1455: out.println(" syntax show basic syntax errors within comments"); jjg@1455: out.println(" html show issues with HTML tags and attributes"); jjg@1455: out.println(" accessibility show issues for accessibility"); jjg@1455: out.println(" missing show issues with missing documentation"); jjg@1455: out.println(" all all of the above"); jjg@1455: out.println(" Precede a value with '-' to negate it"); jjg@1455: out.println(" Categories may be qualified by one of:"); jjg@1455: out.println(" /public /protected /package /private"); jjg@1455: out.println(" For positive categories (not beginning with '-')"); jjg@1455: out.println(" the qualifier applies to that access level and above."); jjg@1455: out.println(" For negative categories (beginning with '-')"); jjg@1455: out.println(" the qualifier applies to that access level and below."); jjg@1455: out.println(" If a qualifier is missing, the category applies to"); jjg@1455: out.println(" all access levels."); jjg@1455: out.println(" For example, -Xmsgs:all,-syntax/private"); jjg@1455: out.println(" This will enable all messages, except syntax errors"); jjg@1455: out.println(" in the doc comments of private methods."); jjg@1455: out.println(" If no -Xmsgs options are provided, the default is"); jjg@1455: out.println(" equivalent to -Xmsgs:all/protected, meaning that"); jjg@1455: out.println(" all messages are reported for protected and public"); jjg@1455: out.println(" declarations only. "); jjg@1455: out.println(" -h -help --help -usage -?"); jjg@1455: out.println(" Show this message."); jjg@1455: out.println(""); jjg@1455: out.println("The following javac options are also supported"); jjg@1455: out.println(" -bootclasspath, -classpath, -sourcepath, -Xmaxerrs, -Xmaxwarns"); jjg@1455: out.println(""); jjg@1455: out.println("To run doclint on part of a project, put the compiled classes for your"); jjg@1455: out.println("project on the classpath (or bootclasspath), then specify the source files"); jjg@1455: out.println("to be checked on the command line."); jjg@1455: } jjg@1455: jjg@1455: List splitPath(String path) { jjg@1455: List files = new ArrayList(); jjg@1455: for (String f: path.split(File.separator)) { jjg@1455: if (f.length() > 0) jjg@1455: files.add(new File(f)); jjg@1455: } jjg@1455: return files; jjg@1455: } jjg@1455: jjg@1455: List javacBootClassPath; jjg@1455: List javacClassPath; jjg@1455: List javacSourcePath; jjg@1455: List javacOpts; jjg@1455: List javacFiles; jjg@1455: boolean needHelp = false; jjg@1455: jjg@1455: // jjg@1455: jjg@1455: // jjg@1455: jjg@1455: @Override jjg@1455: public String getName() { jjg@1455: return "doclint"; jjg@1455: } jjg@1455: jjg@1455: @Override jjg@1455: public void call(JavacTask task, String... args) { jjg@1455: init(task, args, true); jjg@1455: } jjg@1455: jjg@1455: // jjg@1455: jjg@1455: // jjg@1455: jjg@1455: public void init(JavacTask task, String[] args, boolean addTaskListener) { jjg@1455: env = new Env(); jjg@1455: for (int i = 0; i < args.length; i++) { jjg@1455: String arg = args[i]; jjg@1455: if (arg.equals(XMSGS_OPTION)) { jjg@1455: env.messages.setOptions(null); jjg@1455: } else if (arg.startsWith(XMSGS_CUSTOM_PREFIX)) { jjg@1455: env.messages.setOptions(arg.substring(arg.indexOf(":") + 1)); jjg@1455: } else jjg@1455: throw new IllegalArgumentException(arg); jjg@1455: } jjg@1455: env.init(task); jjg@1455: jjg@1455: checker = new Checker(env); jjg@1455: jjg@1455: if (addTaskListener) { jjg@1455: final DeclScanner ds = new DeclScanner() { jjg@1455: @Override jjg@1455: void visitDecl(Tree tree, Name name) { jjg@1455: TreePath p = getCurrentPath(); jjg@1455: DocCommentTree dc = env.trees.getDocCommentTree(p); jjg@1455: jjg@1455: checker.scan(dc, p); jjg@1455: } jjg@1455: }; jjg@1455: jjg@1455: TaskListener tl = new TaskListener() { jjg@1455: @Override jjg@1455: public void started(TaskEvent e) { jjg@1455: return; jjg@1455: } jjg@1455: jjg@1455: @Override jjg@1455: public void finished(TaskEvent e) { jjg@1455: switch (e.getKind()) { jjg@1455: case ENTER: jjg@1455: ds.scan(e.getCompilationUnit(), null); jjg@1455: } jjg@1455: } jjg@1455: }; jjg@1455: jjg@1455: task.addTaskListener(tl); jjg@1455: } jjg@1455: } jjg@1455: jjg@1455: public void scan(TreePath p) { jjg@1455: DocCommentTree dc = env.trees.getDocCommentTree(p); jjg@1455: checker.scan(dc, p); jjg@1455: } jjg@1455: jjg@1455: public void reportStats(PrintWriter out) { jjg@1455: env.messages.reportStats(out); jjg@1455: } jjg@1455: jjg@1455: // jjg@1455: jjg@1455: Env env; jjg@1455: Checker checker; jjg@1455: jjg@1455: public static boolean isValidOption(String opt) { jjg@1455: if (opt.equals(XMSGS_OPTION)) jjg@1455: return true; jjg@1455: if (opt.startsWith(XMSGS_CUSTOM_PREFIX)) jjg@1455: return Messages.Options.isValidOptions(opt.substring(XMSGS_CUSTOM_PREFIX.length())); jjg@1455: return false; jjg@1455: } jjg@1455: jjg@1455: // jjg@1455: jjg@1455: static abstract class DeclScanner extends TreePathScanner { jjg@1455: abstract void visitDecl(Tree tree, Name name); jjg@1455: jjg@1455: @Override jjg@1455: public Void visitClass(ClassTree tree, Void ignore) { jjg@1455: visitDecl(tree, tree.getSimpleName()); jjg@1455: return super.visitClass(tree, ignore); jjg@1455: } jjg@1455: jjg@1455: @Override jjg@1455: public Void visitMethod(MethodTree tree, Void ignore) { jjg@1455: visitDecl(tree, tree.getName()); jjg@1455: //return super.visitMethod(tree, ignore); jjg@1455: return null; jjg@1455: } jjg@1455: jjg@1455: @Override jjg@1455: public Void visitVariable(VariableTree tree, Void ignore) { jjg@1455: visitDecl(tree, tree.getName()); jjg@1455: return super.visitVariable(tree, ignore); jjg@1455: } jjg@1455: } jjg@1455: jjg@1455: // jjg@1455: jjg@1455: }