1 /*
   2  * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.doclint;
  27 
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.io.PrintWriter;
  31 import java.util.ArrayList;
  32 import java.util.LinkedList;
  33 import java.util.List;
  34 import java.util.Queue;
  35 
  36 import javax.lang.model.element.Name;
  37 import javax.tools.StandardLocation;
  38 
  39 import com.sun.source.doctree.DocCommentTree;
  40 import com.sun.source.tree.BlockTree;
  41 import com.sun.source.tree.ClassTree;
  42 import com.sun.source.tree.CompilationUnitTree;
  43 import com.sun.source.tree.LambdaExpressionTree;
  44 import com.sun.source.tree.ModuleTree;
  45 import com.sun.source.tree.PackageTree;
  46 import com.sun.source.tree.MethodTree;
  47 import com.sun.source.tree.Tree;
  48 import com.sun.source.tree.VariableTree;
  49 import com.sun.source.util.JavacTask;
  50 import com.sun.source.util.Plugin;
  51 import com.sun.source.util.TaskEvent;
  52 import com.sun.source.util.TaskListener;
  53 import com.sun.source.util.TreePath;
  54 import com.sun.source.util.TreePathScanner;
  55 import com.sun.tools.javac.api.JavacTaskImpl;
  56 import com.sun.tools.javac.api.JavacTool;
  57 import com.sun.tools.javac.file.JavacFileManager;
  58 import com.sun.tools.javac.main.JavaCompiler;
  59 import com.sun.tools.javac.util.Context;
  60 import com.sun.tools.javac.util.DefinedBy;
  61 import com.sun.tools.javac.util.DefinedBy.Api;
  62 
  63 /**
  64  * Multi-function entry point for the doc check utility.
  65  *
  66  * This class can be invoked in the following ways:
  67  * <ul>
  68  * <li>From the command line
  69  * <li>From javac, as a plugin
  70  * <li>Directly, via a simple API
  71  * </ul>
  72  *
  73  * <p><b>This is NOT part of any supported API.
  74  * If you write code that depends on this, you do so at your own
  75  * risk.  This code and its internal interfaces are subject to change
  76  * or deletion without notice.</b></p>
  77  */
  78 public class DocLint implements Plugin {
  79 
  80     public static final String XMSGS_OPTION = "-Xmsgs";
  81     public static final String XMSGS_CUSTOM_PREFIX = "-Xmsgs:";
  82     private static final String STATS = "-stats";
  83     public static final String XIMPLICIT_HEADERS = "-XimplicitHeaders:";
  84     public static final String XCUSTOM_TAGS_PREFIX = "-XcustomTags:";
  85     public static final String XHTML_VERSION_PREFIX = "-XhtmlVersion:";
  86     public static final String XCHECK_PACKAGE = "-XcheckPackage:";
  87     public static final String SEPARATOR = ",";
  88 
  89     // <editor-fold defaultstate="collapsed" desc="Command-line entry point">
  90     public static void main(String... args) {
  91         DocLint dl = new DocLint();
  92         try {
  93             dl.run(args);
  94         } catch (BadArgs e) {
  95             System.err.println(e.getMessage());
  96             System.exit(1);
  97         } catch (IOException e) {
  98             System.err.println(dl.localize("dc.main.ioerror", e.getLocalizedMessage()));
  99             System.exit(2);
 100         }
 101     }
 102 
 103     // </editor-fold>
 104 
 105     // <editor-fold defaultstate="collapsed" desc="Simple API">
 106 
 107     public class BadArgs extends Exception {
 108         private static final long serialVersionUID = 0;
 109         BadArgs(String code, Object... args) {
 110             super(localize(code, args));
 111             this.code = code;
 112             this.args = args;
 113         }
 114 
 115         final String code;
 116         final Object[] args;
 117     }
 118 
 119     /**
 120      * Simple API entry point.
 121      * @param args Options and operands for doclint
 122      * @throws BadArgs if an error is detected in any args
 123      * @throws IOException if there are problems with any of the file arguments
 124      */
 125     public void run(String... args) throws BadArgs, IOException {
 126         PrintWriter out = new PrintWriter(System.out);
 127         try {
 128             run(out, args);
 129         } finally {
 130             out.flush();
 131         }
 132     }
 133 
 134     public void run(PrintWriter out, String... args) throws BadArgs, IOException {
 135         env = new Env();
 136         processArgs(args);
 137 
 138         boolean noFiles = javacFiles.isEmpty();
 139         if (needHelp) {
 140             showHelp(out);
 141             if (noFiles)
 142                 return;
 143         } else if (noFiles) {
 144             out.println(localize("dc.main.no.files.given"));
 145             return;
 146         }
 147 
 148         JavacTool tool = JavacTool.create();
 149 
 150         JavacFileManager fm = new JavacFileManager(new Context(), false, null);
 151         fm.setSymbolFileEnabled(false);
 152         if (javacBootClassPath != null) {
 153             fm.setLocation(StandardLocation.PLATFORM_CLASS_PATH, javacBootClassPath);
 154         }
 155         if (javacClassPath != null) {
 156             fm.setLocation(StandardLocation.CLASS_PATH, javacClassPath);
 157         }
 158         if (javacSourcePath != null) {
 159             fm.setLocation(StandardLocation.SOURCE_PATH, javacSourcePath);
 160         }
 161 
 162         JavacTask task = tool.getTask(out, fm, null, javacOpts, null,
 163                 fm.getJavaFileObjectsFromFiles(javacFiles));
 164         Iterable<? extends CompilationUnitTree> units = task.parse();
 165         ((JavacTaskImpl) task).enter();
 166 
 167         env.init(task);
 168         checker = new Checker(env);
 169 
 170         DeclScanner ds = new DeclScanner(env) {
 171             @Override
 172             void visitDecl(Tree tree, Name name) {
 173                 TreePath p = getCurrentPath();
 174                 DocCommentTree dc = env.trees.getDocCommentTree(p);
 175 
 176                 checker.scan(dc, p);
 177             }
 178         };
 179 
 180         ds.scan(units, null);
 181 
 182         reportStats(out);
 183 
 184         Context ctx = ((JavacTaskImpl) task).getContext();
 185         JavaCompiler c = JavaCompiler.instance(ctx);
 186         c.printCount("error", c.errorCount());
 187         c.printCount("warn", c.warningCount());
 188     }
 189 
 190     void processArgs(String... args) throws BadArgs {
 191         javacOpts = new ArrayList<>();
 192         javacFiles = new ArrayList<>();
 193 
 194         if (args.length == 0)
 195             needHelp = true;
 196 
 197         for (int i = 0; i < args.length; i++) {
 198             String arg = args[i];
 199             if (arg.matches("-Xmax(errs|warns)") && i + 1 < args.length) {
 200                 if (args[++i].matches("[0-9]+")) {
 201                     javacOpts.add(arg);
 202                     javacOpts.add(args[i]);
 203                 } else {
 204                     throw new BadArgs("dc.bad.value.for.option", arg, args[i]);
 205                 }
 206             } else if ((arg.equals("-target") || arg.equals("-source")) && i + 1 < args.length) {
 207                 javacOpts.add(arg);
 208                 javacOpts.add(args[++i]);
 209             } else if (arg.equals(STATS)) {
 210                 env.messages.setStatsEnabled(true);
 211             } else if (arg.equals("-bootclasspath") && i + 1 < args.length) {
 212                 javacBootClassPath = splitPath(args[++i]);
 213             } else if (arg.equals("-classpath") && i + 1 < args.length) {
 214                 javacClassPath = splitPath(args[++i]);
 215             } else if (arg.equals("-cp") && i + 1 < args.length) {
 216                 javacClassPath = splitPath(args[++i]);
 217             } else if (arg.equals("-sourcepath") && i + 1 < args.length) {
 218                 javacSourcePath = splitPath(args[++i]);
 219             } else if (arg.equals(XMSGS_OPTION)) {
 220                 env.messages.setOptions(null);
 221             } else if (arg.startsWith(XMSGS_CUSTOM_PREFIX)) {
 222                 env.messages.setOptions(arg.substring(arg.indexOf(":") + 1));
 223             } else if (arg.startsWith(XCUSTOM_TAGS_PREFIX)) {
 224                 env.setCustomTags(arg.substring(arg.indexOf(":") + 1));
 225             } else if (arg.startsWith(XHTML_VERSION_PREFIX)) {
 226                 String argsVersion = arg.substring(arg.indexOf(":") + 1);
 227                 HtmlVersion htmlVersion = HtmlVersion.getHtmlVersion(argsVersion);
 228                 if (htmlVersion != null) {
 229                     env.setHtmlVersion(htmlVersion);
 230                 } else {
 231                     throw new BadArgs("dc.bad.value.for.option", arg, argsVersion);
 232                 }
 233             } else if (arg.equals("-h") || arg.equals("-help") || arg.equals("--help")
 234                     || arg.equals("-?") || arg.equals("-usage")) {
 235                 needHelp = true;
 236             } else if (arg.startsWith("-")) {
 237                 throw new BadArgs("dc.bad.option", arg);
 238             } else {
 239                 while (i < args.length)
 240                     javacFiles.add(new File(args[i++]));
 241             }
 242         }
 243     }
 244 
 245     void showHelp(PrintWriter out) {
 246         String msg = localize("dc.main.usage");
 247         for (String line: msg.split("\n"))
 248             out.println(line);
 249     }
 250 
 251     List<File> splitPath(String path) {
 252         List<File> files = new ArrayList<>();
 253         for (String f: path.split(File.pathSeparator)) {
 254             if (f.length() > 0)
 255                 files.add(new File(f));
 256         }
 257         return files;
 258     }
 259 
 260     List<File> javacBootClassPath;
 261     List<File> javacClassPath;
 262     List<File> javacSourcePath;
 263     List<String> javacOpts;
 264     List<File> javacFiles;
 265     boolean needHelp = false;
 266 
 267     // </editor-fold>
 268 
 269     // <editor-fold defaultstate="collapsed" desc="javac Plugin">
 270 
 271     @Override @DefinedBy(Api.COMPILER_TREE)
 272     public String getName() {
 273         return "doclint";
 274     }
 275 
 276     @Override @DefinedBy(Api.COMPILER_TREE)
 277     public void init(JavacTask task, String... args) {
 278         init(task, args, true);
 279     }
 280 
 281     // </editor-fold>
 282 
 283     // <editor-fold defaultstate="collapsed" desc="Embedding API">
 284 
 285     public void init(JavacTask task, String[] args, boolean addTaskListener) {
 286         env = new Env();
 287         for (String arg : args) {
 288             if (arg.equals(XMSGS_OPTION)) {
 289                 env.messages.setOptions(null);
 290             } else if (arg.startsWith(XMSGS_CUSTOM_PREFIX)) {
 291                 env.messages.setOptions(arg.substring(arg.indexOf(":") + 1));
 292             } else if (arg.matches(XIMPLICIT_HEADERS + "[1-6]")) {
 293                 char ch = arg.charAt(arg.length() - 1);
 294                 env.setImplicitHeaders(Character.digit(ch, 10));
 295             } else if (arg.startsWith(XCUSTOM_TAGS_PREFIX)) {
 296                 env.setCustomTags(arg.substring(arg.indexOf(":") + 1));
 297             } else if (arg.startsWith(XHTML_VERSION_PREFIX)) {
 298                 String argsVersion = arg.substring(arg.indexOf(":") + 1);
 299                 HtmlVersion htmlVersion = HtmlVersion.getHtmlVersion(argsVersion);
 300                 if (htmlVersion != null) {
 301                     env.setHtmlVersion(htmlVersion);
 302                 } else {
 303                     throw new IllegalArgumentException(argsVersion);
 304                 }
 305             } else if (arg.startsWith(XCHECK_PACKAGE)) {
 306                 env.setCheckPackages(arg.substring(arg.indexOf(":") + 1));
 307             } else
 308                 throw new IllegalArgumentException(arg);
 309         }
 310         env.init(task);
 311 
 312         checker = new Checker(env);
 313 
 314         if (addTaskListener) {
 315             final DeclScanner ds = new DeclScanner(env) {
 316                 @Override
 317                 void visitDecl(Tree tree, Name name) {
 318                     TreePath p = getCurrentPath();
 319                     DocCommentTree dc = env.trees.getDocCommentTree(p);
 320 
 321                     checker.scan(dc, p);
 322                 }
 323             };
 324 
 325             TaskListener tl = new TaskListener() {
 326                 @Override @DefinedBy(Api.COMPILER_TREE)
 327                 public void started(TaskEvent e) {
 328                     switch (e.getKind()) {
 329                         case ANALYZE:
 330                             CompilationUnitTree tree;
 331                             while ((tree = todo.poll()) != null)
 332                                 ds.scan(tree, null);
 333                             break;
 334                     }
 335                 }
 336 
 337                 @Override @DefinedBy(Api.COMPILER_TREE)
 338                 public void finished(TaskEvent e) {
 339                     switch (e.getKind()) {
 340                         case PARSE:
 341                             todo.add(e.getCompilationUnit());
 342                             break;
 343                     }
 344                 }
 345 
 346                 Queue<CompilationUnitTree> todo = new LinkedList<>();
 347             };
 348 
 349             task.addTaskListener(tl);
 350         }
 351     }
 352 
 353     public void scan(TreePath p) {
 354         DocCommentTree dc = env.trees.getDocCommentTree(p);
 355         checker.scan(dc, p);
 356     }
 357 
 358     public boolean shouldCheck(CompilationUnitTree unit) {
 359         return env.shouldCheck(unit);
 360     }
 361 
 362     public void reportStats(PrintWriter out) {
 363         env.messages.reportStats(out);
 364     }
 365 
 366     // </editor-fold>
 367 
 368     Env env;
 369     Checker checker;
 370 
 371     public static boolean isValidOption(String opt) {
 372         if (opt.equals(XMSGS_OPTION))
 373            return true;
 374         if (opt.startsWith(XMSGS_CUSTOM_PREFIX))
 375            return Messages.Options.isValidOptions(opt.substring(XMSGS_CUSTOM_PREFIX.length()));
 376         if (opt.startsWith(XCHECK_PACKAGE)) {
 377             return Env.validatePackages(opt.substring(opt.indexOf(":") + 1));
 378         }
 379         return false;
 380     }
 381 
 382     private String localize(String code, Object... args) {
 383         Messages m = (env != null) ? env.messages : new Messages(null);
 384         return m.localize(code, args);
 385     }
 386 
 387     // <editor-fold defaultstate="collapsed" desc="DeclScanner">
 388 
 389     static abstract class DeclScanner extends TreePathScanner<Void, Void> {
 390         final Env env;
 391 
 392         public DeclScanner(Env env) {
 393             this.env = env;
 394         }
 395 
 396         abstract void visitDecl(Tree tree, Name name);
 397 
 398         @Override @DefinedBy(Api.COMPILER_TREE)
 399         public Void visitPackage(PackageTree tree, Void ignore) {
 400             visitDecl(tree, null);
 401             return super.visitPackage(tree, ignore);
 402         }
 403 
 404         @Override @DefinedBy(Api.COMPILER_TREE)
 405         public Void visitClass(ClassTree tree, Void ignore) {
 406             visitDecl(tree, tree.getSimpleName());
 407             return super.visitClass(tree, ignore);
 408         }
 409 
 410         @Override @DefinedBy(Api.COMPILER_TREE)
 411         public Void visitMethod(MethodTree tree, Void ignore) {
 412             visitDecl(tree, tree.getName());
 413             return null;
 414         }
 415 
 416         @Override @DefinedBy(Api.COMPILER_TREE)
 417         public Void visitModule(ModuleTree tree, Void ignore) {
 418             visitDecl(tree, null);
 419             return super.visitModule(tree, ignore);
 420         }
 421 
 422         @Override @DefinedBy(Api.COMPILER_TREE)
 423         public Void visitVariable(VariableTree tree, Void ignore) {
 424             visitDecl(tree, tree.getName());
 425             return super.visitVariable(tree, ignore);
 426         }
 427 
 428         @Override @DefinedBy(Api.COMPILER_TREE)
 429         public Void visitCompilationUnit(CompilationUnitTree node, Void p) {
 430             if (!env.shouldCheck(node)) {
 431                 return null;
 432             }
 433             return super.visitCompilationUnit(node, p);
 434         }
 435 
 436         @Override @DefinedBy(Api.COMPILER_TREE)
 437         public Void visitBlock(BlockTree tree, Void ignore) {
 438             return null;
 439         }
 440 
 441         @Override @DefinedBy(Api.COMPILER_TREE)
 442         public Void visitLambdaExpression(LambdaExpressionTree tree, Void ignore) {
 443             return null;
 444         }
 445 
 446     }
 447 
 448     // </editor-fold>
 449 
 450 }