DocLint.java revision 2982:94c1f3391e37
1129207Scognet/* 2129207Scognet * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. 3129207Scognet * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4129207Scognet * 5129207Scognet * This code is free software; you can redistribute it and/or modify it 6129207Scognet * under the terms of the GNU General Public License version 2 only, as 7129207Scognet * published by the Free Software Foundation. Oracle designates this 8129207Scognet * particular file as subject to the "Classpath" exception as provided 9129207Scognet * by Oracle in the LICENSE file that accompanied this code. 10129207Scognet * 11129207Scognet * This code is distributed in the hope that it will be useful, but WITHOUT 12129207Scognet * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13129207Scognet * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14129207Scognet * version 2 for more details (a copy is included in the LICENSE file that 15129207Scognet * accompanied this code). 16129207Scognet * 17129207Scognet * You should have received a copy of the GNU General Public License version 18129207Scognet * 2 along with this work; if not, write to the Free Software Foundation, 19129207Scognet * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20129207Scognet * 21129207Scognet * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22129207Scognet * or visit www.oracle.com if you need additional information or have any 23129207Scognet * questions. 24129207Scognet */ 25129207Scognet 26129207Scognetpackage com.sun.tools.doclint; 27129207Scognet 28129207Scognetimport java.io.File; 29129207Scognetimport java.io.IOException; 30129207Scognetimport java.io.PrintWriter; 31129207Scognetimport java.util.ArrayList; 32129207Scognetimport java.util.LinkedList; 33129207Scognetimport java.util.List; 34129207Scognetimport java.util.Queue; 35129207Scognetimport java.util.regex.Pattern; 36129207Scognet 37129207Scognetimport javax.lang.model.element.Name; 38129207Scognetimport javax.tools.StandardLocation; 39129207Scognet 40129207Scognetimport com.sun.source.doctree.DocCommentTree; 41129207Scognetimport com.sun.source.tree.ClassTree; 42150874Scognetimport com.sun.source.tree.CompilationUnitTree; 43150874Scognetimport com.sun.source.tree.PackageTree; 44129207Scognetimport com.sun.source.tree.MethodTree; 45129207Scognetimport com.sun.source.tree.Tree; 46150874Scognetimport com.sun.source.tree.VariableTree; 47150874Scognetimport com.sun.source.util.JavacTask; 48129207Scognetimport com.sun.source.util.Plugin; 49150874Scognetimport com.sun.source.util.TaskEvent; 50150874Scognetimport com.sun.source.util.TaskListener; 51129207Scognetimport com.sun.source.util.TreePath; 52129207Scognetimport com.sun.source.util.TreePathScanner; 53129207Scognetimport com.sun.tools.javac.api.JavacTaskImpl; 54129207Scognetimport com.sun.tools.javac.api.JavacTool; 55129207Scognetimport com.sun.tools.javac.file.JavacFileManager; 56129207Scognetimport com.sun.tools.javac.main.JavaCompiler; 57129207Scognetimport com.sun.tools.javac.util.Context; 58150874Scognetimport com.sun.tools.javac.util.DefinedBy; 59150874Scognetimport com.sun.tools.javac.util.DefinedBy.Api; 60150874Scognet 61150874Scognet/** 62150874Scognet * Multi-function entry point for the doc check utility. 63150874Scognet * 64150874Scognet * This class can be invoked in the following ways: 65150874Scognet * <ul> 66150874Scognet * <li>From the command line 67150874Scognet * <li>From javac, as a plugin 68150874Scognet * <li>Directly, via a simple API 69150874Scognet * </ul> 70150874Scognet * 71150874Scognet * <p><b>This is NOT part of any supported API. 72150874Scognet * If you write code that depends on this, you do so at your own 73150874Scognet * risk. This code and its internal interfaces are subject to change 74150874Scognet * or deletion without notice.</b></p> 75150874Scognet */ 76150874Scognetpublic class DocLint implements Plugin { 77150874Scognet 78150874Scognet public static final String XMSGS_OPTION = "-Xmsgs"; 79150874Scognet public static final String XMSGS_CUSTOM_PREFIX = "-Xmsgs:"; 80150874Scognet private static final String STATS = "-stats"; 81150874Scognet public static final String XIMPLICIT_HEADERS = "-XimplicitHeaders:"; 82150874Scognet public static final String XCUSTOM_TAGS_PREFIX = "-XcustomTags:"; 83150874Scognet public static final String XHTML_VERSION_PREFIX = "-XhtmlVersion:"; 84150874Scognet public static final String XCHECK_PACKAGE = "-XcheckPackage:"; 85150874Scognet public static final String SEPARATOR = ","; 86150874Scognet 87150874Scognet // <editor-fold defaultstate="collapsed" desc="Command-line entry point"> 88150874Scognet public static void main(String... args) { 89150874Scognet DocLint dl = new DocLint(); 90150874Scognet try { 91150874Scognet dl.run(args); 92150874Scognet } catch (BadArgs e) { 93150874Scognet System.err.println(e.getMessage()); 94150874Scognet System.exit(1); 95150874Scognet } catch (IOException e) { 96150874Scognet System.err.println(dl.localize("dc.main.ioerror", e.getLocalizedMessage())); 97150874Scognet System.exit(2); 98150874Scognet } 99150874Scognet } 100150874Scognet 101150874Scognet // </editor-fold> 102150874Scognet 103150874Scognet // <editor-fold defaultstate="collapsed" desc="Simple API"> 104150874Scognet 105150874Scognet public class BadArgs extends Exception { 106129207Scognet private static final long serialVersionUID = 0; 107150874Scognet BadArgs(String code, Object... args) { 108129207Scognet super(localize(code, args)); 109150874Scognet this.code = code; 110150874Scognet this.args = args; 111150874Scognet } 112129207Scognet 113150874Scognet final String code; 114150874Scognet final Object[] args; 115129207Scognet } 116129207Scognet 117129207Scognet /** 118150874Scognet * Simple API entry point. 119129207Scognet * @param args Options and operands for doclint 120150874Scognet * @throws BadArgs if an error is detected in any args 121150874Scognet * @throws IOException if there are problems with any of the file arguments 122150874Scognet */ 123150874Scognet public void run(String... args) throws BadArgs, IOException { 124150874Scognet PrintWriter out = new PrintWriter(System.out); 125150874Scognet try { 126150874Scognet run(out, args); 127150874Scognet } finally { 128150874Scognet out.flush(); 129150874Scognet } 130150874Scognet } 131150874Scognet 132150874Scognet public void run(PrintWriter out, String... args) throws BadArgs, IOException { 133150874Scognet env = new Env(); 134150874Scognet processArgs(args); 135150874Scognet 136150874Scognet boolean noFiles = javacFiles.isEmpty(); 137150874Scognet if (needHelp) { 138150874Scognet showHelp(out); 139150874Scognet if (noFiles) 140150874Scognet return; 141150874Scognet } else if (noFiles) { 142150874Scognet out.println(localize("dc.main.no.files.given")); 143150874Scognet return; 144150874Scognet } 145150874Scognet 146150874Scognet JavacTool tool = JavacTool.create(); 147150874Scognet 148150874Scognet JavacFileManager fm = new JavacFileManager(new Context(), false, null); 149150874Scognet fm.setSymbolFileEnabled(false); 150150874Scognet fm.setLocation(StandardLocation.PLATFORM_CLASS_PATH, javacBootClassPath); 151150874Scognet fm.setLocation(StandardLocation.CLASS_PATH, javacClassPath); 152150874Scognet fm.setLocation(StandardLocation.SOURCE_PATH, javacSourcePath); 153150874Scognet 154150874Scognet JavacTask task = tool.getTask(out, fm, null, javacOpts, null, 155150874Scognet fm.getJavaFileObjectsFromFiles(javacFiles)); 156150874Scognet Iterable<? extends CompilationUnitTree> units = task.parse(); 157150874Scognet ((JavacTaskImpl) task).enter(); 158150874Scognet 159150874Scognet env.init(task); 160150874Scognet checker = new Checker(env); 161150874Scognet 162150874Scognet DeclScanner ds = new DeclScanner(env) { 163150874Scognet @Override 164150874Scognet void visitDecl(Tree tree, Name name) { 165150874Scognet TreePath p = getCurrentPath(); 166150874Scognet DocCommentTree dc = env.trees.getDocCommentTree(p); 167150874Scognet 168150874Scognet checker.scan(dc, p); 169129207Scognet } 170129207Scognet }; 171129207Scognet 172150874Scognet ds.scan(units, null); 173150874Scognet 174150874Scognet reportStats(out); 175150874Scognet 176150874Scognet Context ctx = ((JavacTaskImpl) task).getContext(); 177150874Scognet JavaCompiler c = JavaCompiler.instance(ctx); 178150874Scognet c.printCount("error", c.errorCount()); 179150874Scognet c.printCount("warn", c.warningCount()); 180150874Scognet } 181150874Scognet 182150874Scognet void processArgs(String... args) throws BadArgs { 183150874Scognet javacOpts = new ArrayList<>(); 184150874Scognet javacFiles = new ArrayList<>(); 185150874Scognet 186150874Scognet if (args.length == 0) 187129207Scognet needHelp = true; 188150874Scognet 189129207Scognet for (int i = 0; i < args.length; i++) { 190150874Scognet String arg = args[i]; 191150874Scognet if (arg.matches("-Xmax(errs|warns)") && i + 1 < args.length) { 192150874Scognet if (args[++i].matches("[0-9]+")) { 193150874Scognet javacOpts.add(arg); 194150874Scognet javacOpts.add(args[i]); 195129207Scognet } else { 196150874Scognet throw new BadArgs("dc.bad.value.for.option", arg, args[i]); 197150874Scognet } 198150874Scognet } else if (arg.equals(STATS)) { 199150874Scognet env.messages.setStatsEnabled(true); 200150874Scognet } else if (arg.equals("-bootclasspath") && i + 1 < args.length) { 201150874Scognet javacBootClassPath = splitPath(args[++i]); 202150874Scognet } else if (arg.equals("-classpath") && i + 1 < args.length) { 203150874Scognet javacClassPath = splitPath(args[++i]); 204150874Scognet } else if (arg.equals("-cp") && i + 1 < args.length) { 205150874Scognet javacClassPath = splitPath(args[++i]); 206150874Scognet } else if (arg.equals("-sourcepath") && i + 1 < args.length) { 207150874Scognet javacSourcePath = splitPath(args[++i]); 208150874Scognet } else if (arg.equals(XMSGS_OPTION)) { 209150874Scognet env.messages.setOptions(null); 210150874Scognet } else if (arg.startsWith(XMSGS_CUSTOM_PREFIX)) { 211150874Scognet env.messages.setOptions(arg.substring(arg.indexOf(":") + 1)); 212150874Scognet } else if (arg.startsWith(XCUSTOM_TAGS_PREFIX)) { 213150874Scognet env.setCustomTags(arg.substring(arg.indexOf(":") + 1)); 214150874Scognet } else if (arg.startsWith(XHTML_VERSION_PREFIX)) { 215150874Scognet String argsVersion = arg.substring(arg.indexOf(":") + 1); 216150874Scognet HtmlVersion htmlVersion = HtmlVersion.getHtmlVersion(argsVersion); 217150874Scognet if (htmlVersion != null) { 218150874Scognet env.setHtmlVersion(htmlVersion); 219150874Scognet } else { 220150874Scognet throw new BadArgs("dc.bad.value.for.option", arg, argsVersion); 221150874Scognet } 222150874Scognet } else if (arg.equals("-h") || arg.equals("-help") || arg.equals("--help") 223150874Scognet || arg.equals("-?") || arg.equals("-usage")) { 224150874Scognet needHelp = true; 225150874Scognet } else if (arg.startsWith("-")) { 226150874Scognet throw new BadArgs("dc.bad.option", arg); 227129207Scognet } else { 228129207Scognet while (i < args.length) 229129207Scognet javacFiles.add(new File(args[i++])); 230129207Scognet } 231129207Scognet } 232129207Scognet } 233129207Scognet 234129207Scognet void showHelp(PrintWriter out) { 235129207Scognet String msg = localize("dc.main.usage"); 236129207Scognet for (String line: msg.split("\n")) 237129207Scognet out.println(line); 238129207Scognet } 239129207Scognet 240129207Scognet List<File> splitPath(String path) { 241129207Scognet List<File> files = new ArrayList<>(); 242129207Scognet for (String f: path.split(File.pathSeparator)) { 243129207Scognet if (f.length() > 0) 244129207Scognet files.add(new File(f)); 245129207Scognet } 246129207Scognet return files; 247129207Scognet } 248 249 List<File> javacBootClassPath; 250 List<File> javacClassPath; 251 List<File> javacSourcePath; 252 List<String> javacOpts; 253 List<File> javacFiles; 254 boolean needHelp = false; 255 256 // </editor-fold> 257 258 // <editor-fold defaultstate="collapsed" desc="javac Plugin"> 259 260 @Override @DefinedBy(Api.COMPILER_TREE) 261 public String getName() { 262 return "doclint"; 263 } 264 265 @Override @DefinedBy(Api.COMPILER_TREE) 266 public void init(JavacTask task, String... args) { 267 init(task, args, true); 268 } 269 270 // </editor-fold> 271 272 // <editor-fold defaultstate="collapsed" desc="Embedding API"> 273 274 public void init(JavacTask task, String[] args, boolean addTaskListener) { 275 env = new Env(); 276 for (String arg : args) { 277 if (arg.equals(XMSGS_OPTION)) { 278 env.messages.setOptions(null); 279 } else if (arg.startsWith(XMSGS_CUSTOM_PREFIX)) { 280 env.messages.setOptions(arg.substring(arg.indexOf(":") + 1)); 281 } else if (arg.matches(XIMPLICIT_HEADERS + "[1-6]")) { 282 char ch = arg.charAt(arg.length() - 1); 283 env.setImplicitHeaders(Character.digit(ch, 10)); 284 } else if (arg.startsWith(XCUSTOM_TAGS_PREFIX)) { 285 env.setCustomTags(arg.substring(arg.indexOf(":") + 1)); 286 } else if (arg.startsWith(XHTML_VERSION_PREFIX)) { 287 String argsVersion = arg.substring(arg.indexOf(":") + 1); 288 HtmlVersion htmlVersion = HtmlVersion.getHtmlVersion(argsVersion); 289 if (htmlVersion != null) { 290 env.setHtmlVersion(htmlVersion); 291 } else { 292 throw new IllegalArgumentException(argsVersion); 293 } 294 } else if (arg.startsWith(XCHECK_PACKAGE)) { 295 env.setCheckPackages(arg.substring(arg.indexOf(":") + 1)); 296 } else 297 throw new IllegalArgumentException(arg); 298 } 299 env.init(task); 300 301 checker = new Checker(env); 302 303 if (addTaskListener) { 304 final DeclScanner ds = new DeclScanner(env) { 305 @Override 306 void visitDecl(Tree tree, Name name) { 307 TreePath p = getCurrentPath(); 308 DocCommentTree dc = env.trees.getDocCommentTree(p); 309 310 checker.scan(dc, p); 311 } 312 }; 313 314 TaskListener tl = new TaskListener() { 315 @Override @DefinedBy(Api.COMPILER_TREE) 316 public void started(TaskEvent e) { 317 switch (e.getKind()) { 318 case ANALYZE: 319 CompilationUnitTree tree; 320 while ((tree = todo.poll()) != null) 321 ds.scan(tree, null); 322 break; 323 } 324 } 325 326 @Override @DefinedBy(Api.COMPILER_TREE) 327 public void finished(TaskEvent e) { 328 switch (e.getKind()) { 329 case PARSE: 330 todo.add(e.getCompilationUnit()); 331 break; 332 } 333 } 334 335 Queue<CompilationUnitTree> todo = new LinkedList<>(); 336 }; 337 338 task.addTaskListener(tl); 339 } 340 } 341 342 public void scan(TreePath p) { 343 DocCommentTree dc = env.trees.getDocCommentTree(p); 344 checker.scan(dc, p); 345 } 346 347 public boolean shouldCheck(CompilationUnitTree unit) { 348 return env.shouldCheck(unit); 349 } 350 351 public void reportStats(PrintWriter out) { 352 env.messages.reportStats(out); 353 } 354 355 // </editor-fold> 356 357 Env env; 358 Checker checker; 359 360 public static boolean isValidOption(String opt) { 361 if (opt.equals(XMSGS_OPTION)) 362 return true; 363 if (opt.startsWith(XMSGS_CUSTOM_PREFIX)) 364 return Messages.Options.isValidOptions(opt.substring(XMSGS_CUSTOM_PREFIX.length())); 365 if (opt.startsWith(XCHECK_PACKAGE)) { 366 return Env.validatePackages(opt.substring(opt.indexOf(":") + 1)); 367 } 368 return false; 369 } 370 371 private String localize(String code, Object... args) { 372 Messages m = (env != null) ? env.messages : new Messages(null); 373 return m.localize(code, args); 374 } 375 376 // <editor-fold defaultstate="collapsed" desc="DeclScanner"> 377 378 static abstract class DeclScanner extends TreePathScanner<Void, Void> { 379 final Env env; 380 381 public DeclScanner(Env env) { 382 this.env = env; 383 } 384 385 abstract void visitDecl(Tree tree, Name name); 386 387 @Override @DefinedBy(Api.COMPILER_TREE) 388 public Void visitPackage(PackageTree tree, Void ignore) { 389 visitDecl(tree, null); 390 return super.visitPackage(tree, ignore); 391 } 392 @Override @DefinedBy(Api.COMPILER_TREE) 393 public Void visitClass(ClassTree tree, Void ignore) { 394 visitDecl(tree, tree.getSimpleName()); 395 return super.visitClass(tree, ignore); 396 } 397 398 @Override @DefinedBy(Api.COMPILER_TREE) 399 public Void visitMethod(MethodTree tree, Void ignore) { 400 visitDecl(tree, tree.getName()); 401 //return super.visitMethod(tree, ignore); 402 return null; 403 } 404 405 @Override @DefinedBy(Api.COMPILER_TREE) 406 public Void visitVariable(VariableTree tree, Void ignore) { 407 visitDecl(tree, tree.getName()); 408 return super.visitVariable(tree, ignore); 409 } 410 411 @Override @DefinedBy(Api.COMPILER_TREE) 412 public Void visitCompilationUnit(CompilationUnitTree node, Void p) { 413 if (!env.shouldCheck(node)) { 414 return null; 415 } 416 return super.visitCompilationUnit(node, p); 417 } 418 419 } 420 421 // </editor-fold> 422 423} 424