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 }