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 XCUSTOM_TAGS_PREFIX = "-XcustomTags:";
  84     public static final String XHTML_VERSION_PREFIX = "-XhtmlVersion:";
  85     public static final String XCHECK_PACKAGE = "-XcheckPackage:";
  86     public static final String SEPARATOR = ",";
  87 
  88     // <editor-fold defaultstate="collapsed" desc="Command-line entry point">
  89     public static void main(String... args) {
  90         DocLint dl = new DocLint();
  91         try {
  92             dl.run(args);
  93         } catch (BadArgs e) {
  94             System.err.println(e.getMessage());
  95             System.exit(1);
  96         } catch (IOException e) {
  97             System.err.println(dl.localize("dc.main.ioerror", e.getLocalizedMessage()));
  98             System.exit(2);
  99         }
 100     }
 101 
 102     // </editor-fold>
 103 
 104     // <editor-fold defaultstate="collapsed" desc="Simple API">
 105 
 106     public class BadArgs extends Exception {
 107         private static final long serialVersionUID = 0;
 108         BadArgs(String code, Object... args) {
 109             super(localize(code, args));
 110             this.code = code;
 111             this.args = args;
 112         }
 113 
 114         final String code;
 115         final Object[] args;
 116     }
 117 
 118     /**
 119      * Simple API entry point.
 120      * @param args Options and operands for doclint
 121      * @throws BadArgs if an error is detected in any args
 122      * @throws IOException if there are problems with any of the file arguments
 123      */
 124     public void run(String... args) throws BadArgs, IOException {
 125         PrintWriter out = new PrintWriter(System.out);
 126         try {
 127             run(out, args);
 128         } finally {
 129             out.flush();
 130         }
 131     }
 132 
 133     public void run(PrintWriter out, String... args) throws BadArgs, IOException {
 134         env = new Env();
 135         processArgs(args);
 136 
 137         boolean noFiles = javacFiles.isEmpty();
 138         if (needHelp) {
 139             showHelp(out);
 140             if (noFiles)
 141                 return;
 142         } else if (noFiles) {
 143             out.println(localize("dc.main.no.files.given"));
 144             return;
 145         }
 146 
 147         JavacTool tool = JavacTool.create();
 148 
 149         JavacFileManager fm = new JavacFileManager(new Context(), false, null);
 150         fm.setSymbolFileEnabled(false);
 151         if (javacBootClassPath != null) {
 152             fm.setLocation(StandardLocation.PLATFORM_CLASS_PATH, javacBootClassPath);
 153         }
 154         if (javacClassPath != null) {
 155             fm.setLocation(StandardLocation.CLASS_PATH, javacClassPath);
 156         }
 157         if (javacSourcePath != null) {
 158             fm.setLocation(StandardLocation.SOURCE_PATH, javacSourcePath);
 159         }
 160 
 161         JavacTask task = tool.getTask(out, fm, null, javacOpts, null,
 162                 fm.getJavaFileObjectsFromFiles(javacFiles));
 163         Iterable<? extends CompilationUnitTree> units = task.parse();
 164         ((JavacTaskImpl) task).enter();
 165 
 166         env.init(task);
 167         checker = new Checker(env);
 168 
 169         DeclScanner ds = new DeclScanner(env) {
 170             @Override
 171             void visitDecl(Tree tree, Name name) {
 172                 TreePath p = getCurrentPath();
 173                 DocCommentTree dc = env.trees.getDocCommentTree(p);
 174 
 175                 checker.scan(dc, p);
 176             }
 177         };
 178 
 179         ds.scan(units, null);
 180 
 181         reportStats(out);
 182 
 183         Context ctx = ((JavacTaskImpl) task).getContext();
 184         JavaCompiler c = JavaCompiler.instance(ctx);
 185         c.printCount("error", c.errorCount());
 186         c.printCount("warn", c.warningCount());
 187     }
 188 
 189     void processArgs(String... args) throws BadArgs {
 190         javacOpts = new ArrayList<>();
 191         javacFiles = new ArrayList<>();
 192 
 193         if (args.length == 0)
 194             needHelp = true;
 195 
 196         for (int i = 0; i < args.length; i++) {
 197             String arg = args[i];
 198             if (arg.matches("-Xmax(errs|warns)") && i + 1 < args.length) {
 199                 if (args[++i].matches("[0-9]+")) {
 200                     javacOpts.add(arg);
 201                     javacOpts.add(args[i]);
 202                 } else {
 203                     throw new BadArgs("dc.bad.value.for.option", arg, args[i]);
 204                 }
 205             } else if ((arg.equals("-target") || arg.equals("-source")) && i + 1 < args.length) {
 206                 javacOpts.add(arg);
 207                 javacOpts.add(args[++i]);
 208             } else if (arg.equals(STATS)) {
 209                 env.messages.setStatsEnabled(true);
 210             } else if (arg.equals("-bootclasspath") && i + 1 < args.length) {
 211                 javacBootClassPath = splitPath(args[++i]);
 212             } else if (arg.equals("-classpath") && i + 1 < args.length) {
 213                 javacClassPath = splitPath(args[++i]);
 214             } else if (arg.equals("-cp") && i + 1 < args.length) {
 215                 javacClassPath = splitPath(args[++i]);
 216             } else if (arg.equals("-sourcepath") && i + 1 < args.length) {
 217                 javacSourcePath = splitPath(args[++i]);
 218             } else if (arg.equals(XMSGS_OPTION)) {
 219                 env.messages.setOptions(null);
 220             } else if (arg.startsWith(XMSGS_CUSTOM_PREFIX)) {
 221                 env.messages.setOptions(arg.substring(arg.indexOf(":") + 1));
 222             } else if (arg.startsWith(XCUSTOM_TAGS_PREFIX)) {
 223                 env.setCustomTags(arg.substring(arg.indexOf(":") + 1));
 224             } else if (arg.startsWith(XHTML_VERSION_PREFIX)) {
 225                 String argsVersion = arg.substring(arg.indexOf(":") + 1);
 226                 HtmlVersion htmlVersion = HtmlVersion.getHtmlVersion(argsVersion);
 227                 if (htmlVersion != null) {
 228                     env.setHtmlVersion(htmlVersion);
 229                 } else {
 230                     throw new BadArgs("dc.bad.value.for.option", arg, argsVersion);
 231                 }
 232             } else if (arg.equals("-h") || arg.equals("-help") || arg.equals("--help")
 233                     || arg.equals("-?") || arg.equals("-usage")) {
 234                 needHelp = true;
 235             } else if (arg.startsWith("-")) {
 236                 throw new BadArgs("dc.bad.option", arg);
 237             } else {
 238                 while (i < args.length)
 239                     javacFiles.add(new File(args[i++]));
 240             }
 241         }
 242     }
 243 
 244     void showHelp(PrintWriter out) {
 245         String msg = localize("dc.main.usage");
 246         for (String line: msg.split("\n"))
 247             out.println(line);
 248     }
 249 
 250     List<File> splitPath(String path) {
 251         List<File> files = new ArrayList<>();
 252         for (String f: path.split(File.pathSeparator)) {
 253             if (f.length() > 0)
 254                 files.add(new File(f));
 255         }
 256         return files;
 257     }
 258 
 259     List<File> javacBootClassPath;
 260     List<File> javacClassPath;
 261     List<File> javacSourcePath;
 262     List<String> javacOpts;
 263     List<File> javacFiles;
 264     boolean needHelp = false;
 265 
 266     // </editor-fold>
 267 
 268     // <editor-fold defaultstate="collapsed" desc="javac Plugin">
 269 
 270     @Override @DefinedBy(Api.COMPILER_TREE)
 271     public String getName() {
 272         return "doclint";
 273     }
 274 
 275     @Override @DefinedBy(Api.COMPILER_TREE)
 276     public void init(JavacTask task, String... args) {
 277         init(task, args, true);
 278     }
 279 
 280     // </editor-fold>
 281 
 282     // <editor-fold defaultstate="collapsed" desc="Embedding API">
 283 
 284     public void init(JavacTask task, String[] args, boolean addTaskListener) {
 285         env = new Env();
 286         for (String arg : args) {
 287             if (arg.equals(XMSGS_OPTION)) {
 288                 env.messages.setOptions(null);
 289             } else if (arg.startsWith(XMSGS_CUSTOM_PREFIX)) {
 290                 env.messages.setOptions(arg.substring(arg.indexOf(":") + 1));
 291             } else if (arg.startsWith(XCUSTOM_TAGS_PREFIX)) {
 292                 env.setCustomTags(arg.substring(arg.indexOf(":") + 1));
 293             } else if (arg.startsWith(XHTML_VERSION_PREFIX)) {
 294                 String argsVersion = arg.substring(arg.indexOf(":") + 1);
 295                 HtmlVersion htmlVersion = HtmlVersion.getHtmlVersion(argsVersion);
 296                 if (htmlVersion != null) {
 297                     env.setHtmlVersion(htmlVersion);
 298                 } else {
 299                     throw new IllegalArgumentException(argsVersion);
 300                 }
 301             } else if (arg.startsWith(XCHECK_PACKAGE)) {
 302                 env.setCheckPackages(arg.substring(arg.indexOf(":") + 1));
 303             } else
 304                 throw new IllegalArgumentException(arg);
 305         }
 306         env.init(task);
 307 
 308         checker = new Checker(env);
 309 
 310         if (addTaskListener) {
 311             final DeclScanner ds = new DeclScanner(env) {
 312                 @Override
 313                 void visitDecl(Tree tree, Name name) {
 314                     TreePath p = getCurrentPath();
 315                     DocCommentTree dc = env.trees.getDocCommentTree(p);
 316 
 317                     checker.scan(dc, p);
 318                 }
 319             };
 320 
 321             TaskListener tl = new TaskListener() {
 322                 @Override @DefinedBy(Api.COMPILER_TREE)
 323                 public void started(TaskEvent e) {
 324                     switch (e.getKind()) {
 325                         case ANALYZE:
 326                             CompilationUnitTree tree;
 327                             while ((tree = todo.poll()) != null)
 328                                 ds.scan(tree, null);
 329                             break;
 330                     }
 331                 }
 332 
 333                 @Override @DefinedBy(Api.COMPILER_TREE)
 334                 public void finished(TaskEvent e) {
 335                     switch (e.getKind()) {
 336                         case PARSE:
 337                             todo.add(e.getCompilationUnit());
 338                             break;
 339                     }
 340                 }
 341 
 342                 Queue<CompilationUnitTree> todo = new LinkedList<>();
 343             };
 344 
 345             task.addTaskListener(tl);
 346         }
 347     }
 348 
 349     public void scan(TreePath p) {
 350         DocCommentTree dc = env.trees.getDocCommentTree(p);
 351         checker.scan(dc, p);
 352     }
 353 
 354     public boolean shouldCheck(CompilationUnitTree unit) {
 355         return env.shouldCheck(unit);
 356     }
 357 
 358     public void reportStats(PrintWriter out) {
 359         env.messages.reportStats(out);
 360     }
 361 
 362     // </editor-fold>
 363 
 364     Env env;
 365     Checker checker;
 366 
 367     public static boolean isValidOption(String opt) {
 368         if (opt.equals(XMSGS_OPTION))
 369            return true;
 370         if (opt.startsWith(XMSGS_CUSTOM_PREFIX))
 371            return Messages.Options.isValidOptions(opt.substring(XMSGS_CUSTOM_PREFIX.length()));
 372         if (opt.startsWith(XCHECK_PACKAGE)) {
 373             return Env.validatePackages(opt.substring(opt.indexOf(":") + 1));
 374         }
 375         return false;
 376     }
 377 
 378     private String localize(String code, Object... args) {
 379         Messages m = (env != null) ? env.messages : new Messages(null);
 380         return m.localize(code, args);
 381     }
 382 
 383     // <editor-fold defaultstate="collapsed" desc="DeclScanner">
 384 
 385     static abstract class DeclScanner extends TreePathScanner<Void, Void> {
 386         final Env env;
 387 
 388         public DeclScanner(Env env) {
 389             this.env = env;
 390         }
 391 
 392         abstract void visitDecl(Tree tree, Name name);
 393 
 394         @Override @DefinedBy(Api.COMPILER_TREE)
 395         public Void visitPackage(PackageTree tree, Void ignore) {
 396             visitDecl(tree, null);
 397             return super.visitPackage(tree, ignore);
 398         }
 399 
 400         @Override @DefinedBy(Api.COMPILER_TREE)
 401         public Void visitClass(ClassTree tree, Void ignore) {
 402             visitDecl(tree, tree.getSimpleName());
 403             return super.visitClass(tree, ignore);
 404         }
 405 
 406         @Override @DefinedBy(Api.COMPILER_TREE)
 407         public Void visitMethod(MethodTree tree, Void ignore) {
 408             visitDecl(tree, tree.getName());
 409             return null;
 410         }
 411 
 412         @Override @DefinedBy(Api.COMPILER_TREE)
 413         public Void visitModule(ModuleTree tree, Void ignore) {
 414             visitDecl(tree, null);
 415             return super.visitModule(tree, ignore);
 416         }
 417 
 418         @Override @DefinedBy(Api.COMPILER_TREE)
 419         public Void visitVariable(VariableTree tree, Void ignore) {
 420             visitDecl(tree, tree.getName());
 421             return super.visitVariable(tree, ignore);
 422         }
 423 
 424         @Override @DefinedBy(Api.COMPILER_TREE)
 425         public Void visitCompilationUnit(CompilationUnitTree node, Void p) {
 426             if (!env.shouldCheck(node)) {
 427                 return null;
 428             }
 429             return super.visitCompilationUnit(node, p);
 430         }
 431 
 432         @Override @DefinedBy(Api.COMPILER_TREE)
 433         public Void visitBlock(BlockTree tree, Void ignore) {
 434             return null;
 435         }
 436 
 437         @Override @DefinedBy(Api.COMPILER_TREE)
 438         public Void visitLambdaExpression(LambdaExpressionTree tree, Void ignore) {
 439             return null;
 440         }
 441 
 442     }
 443 
 444     // </editor-fold>
 445 
 446 }