DocLint.java revision 2880:e00e00b022e9
1/* 2 * Copyright (c) 2012, 2015, 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 26package com.sun.tools.doclint; 27 28import java.io.File; 29import java.io.IOException; 30import java.io.PrintWriter; 31import java.util.ArrayList; 32import java.util.LinkedList; 33import java.util.List; 34import java.util.Queue; 35import java.util.regex.Pattern; 36 37import javax.lang.model.element.Name; 38import javax.tools.StandardLocation; 39 40import com.sun.source.doctree.DocCommentTree; 41import com.sun.source.tree.ClassTree; 42import com.sun.source.tree.CompilationUnitTree; 43import com.sun.source.tree.PackageTree; 44import com.sun.source.tree.MethodTree; 45import com.sun.source.tree.Tree; 46import com.sun.source.tree.VariableTree; 47import com.sun.source.util.JavacTask; 48import com.sun.source.util.Plugin; 49import com.sun.source.util.TaskEvent; 50import com.sun.source.util.TaskListener; 51import com.sun.source.util.TreePath; 52import com.sun.source.util.TreePathScanner; 53import com.sun.tools.javac.api.JavacTaskImpl; 54import com.sun.tools.javac.api.JavacTool; 55import com.sun.tools.javac.file.JavacFileManager; 56import com.sun.tools.javac.main.JavaCompiler; 57import com.sun.tools.javac.util.Context; 58import com.sun.tools.javac.util.DefinedBy; 59import com.sun.tools.javac.util.DefinedBy.Api; 60 61/** 62 * Multi-function entry point for the doc check utility. 63 * 64 * This class can be invoked in the following ways: 65 * <ul> 66 * <li>From the command line 67 * <li>From javac, as a plugin 68 * <li>Directly, via a simple API 69 * </ul> 70 * 71 * <p><b>This is NOT part of any supported API. 72 * If you write code that depends on this, you do so at your own 73 * risk. This code and its internal interfaces are subject to change 74 * or deletion without notice.</b></p> 75 */ 76public class DocLint implements Plugin { 77 78 public static final String XMSGS_OPTION = "-Xmsgs"; 79 public static final String XMSGS_CUSTOM_PREFIX = "-Xmsgs:"; 80 private static final String STATS = "-stats"; 81 public static final String XIMPLICIT_HEADERS = "-XimplicitHeaders:"; 82 public static final String XCUSTOM_TAGS_PREFIX = "-XcustomTags:"; 83 public static final String XHTML_VERSION_PREFIX = "-XhtmlVersion:"; 84 public static final String XCHECK_PACKAGE = "-XcheckPackage:"; 85 public static final String SEPARATOR = ","; 86 87 // <editor-fold defaultstate="collapsed" desc="Command-line entry point"> 88 public static void main(String... args) { 89 DocLint dl = new DocLint(); 90 try { 91 dl.run(args); 92 } catch (BadArgs e) { 93 System.err.println(e.getMessage()); 94 System.exit(1); 95 } catch (IOException e) { 96 System.err.println(dl.localize("dc.main.ioerror", e.getLocalizedMessage())); 97 System.exit(2); 98 } 99 } 100 101 // </editor-fold> 102 103 // <editor-fold defaultstate="collapsed" desc="Simple API"> 104 105 public class BadArgs extends Exception { 106 private static final long serialVersionUID = 0; 107 BadArgs(String code, Object... args) { 108 super(localize(code, args)); 109 this.code = code; 110 this.args = args; 111 } 112 113 final String code; 114 final Object[] args; 115 } 116 117 /** 118 * Simple API entry point. 119 * @param args Options and operands for doclint 120 * @throws BadArgs if an error is detected in any args 121 * @throws IOException if there are problems with any of the file arguments 122 */ 123 public void run(String... args) throws BadArgs, IOException { 124 PrintWriter out = new PrintWriter(System.out); 125 try { 126 run(out, args); 127 } finally { 128 out.flush(); 129 } 130 } 131 132 public void run(PrintWriter out, String... args) throws BadArgs, IOException { 133 env = new Env(); 134 processArgs(args); 135 136 boolean noFiles = javacFiles.isEmpty(); 137 if (needHelp) { 138 showHelp(out); 139 if (noFiles) 140 return; 141 } else if (noFiles) { 142 out.println(localize("dc.main.no.files.given")); 143 return; 144 } 145 146 JavacTool tool = JavacTool.create(); 147 148 JavacFileManager fm = new JavacFileManager(new Context(), false, null); 149 fm.setSymbolFileEnabled(false); 150 fm.setLocation(StandardLocation.PLATFORM_CLASS_PATH, javacBootClassPath); 151 fm.setLocation(StandardLocation.CLASS_PATH, javacClassPath); 152 fm.setLocation(StandardLocation.SOURCE_PATH, javacSourcePath); 153 154 JavacTask task = tool.getTask(out, fm, null, javacOpts, null, 155 fm.getJavaFileObjectsFromFiles(javacFiles)); 156 Iterable<? extends CompilationUnitTree> units = task.parse(); 157 ((JavacTaskImpl) task).enter(); 158 159 env.init(task); 160 checker = new Checker(env); 161 162 DeclScanner ds = new DeclScanner(env) { 163 @Override 164 void visitDecl(Tree tree, Name name) { 165 TreePath p = getCurrentPath(); 166 DocCommentTree dc = env.trees.getDocCommentTree(p); 167 168 checker.scan(dc, p); 169 } 170 }; 171 172 ds.scan(units, null); 173 174 reportStats(out); 175 176 Context ctx = ((JavacTaskImpl) task).getContext(); 177 JavaCompiler c = JavaCompiler.instance(ctx); 178 c.printCount("error", c.errorCount()); 179 c.printCount("warn", c.warningCount()); 180 } 181 182 void processArgs(String... args) throws BadArgs { 183 javacOpts = new ArrayList<>(); 184 javacFiles = new ArrayList<>(); 185 186 if (args.length == 0) 187 needHelp = true; 188 189 for (int i = 0; i < args.length; i++) { 190 String arg = args[i]; 191 if (arg.matches("-Xmax(errs|warns)") && i + 1 < args.length) { 192 if (args[++i].matches("[0-9]+")) { 193 javacOpts.add(arg); 194 javacOpts.add(args[i]); 195 } else { 196 throw new BadArgs("dc.bad.value.for.option", arg, args[i]); 197 } 198 } else if (arg.equals(STATS)) { 199 env.messages.setStatsEnabled(true); 200 } else if (arg.equals("-bootclasspath") && i + 1 < args.length) { 201 javacBootClassPath = splitPath(args[++i]); 202 } else if (arg.equals("-classpath") && i + 1 < args.length) { 203 javacClassPath = splitPath(args[++i]); 204 } else if (arg.equals("-cp") && i + 1 < args.length) { 205 javacClassPath = splitPath(args[++i]); 206 } else if (arg.equals("-sourcepath") && i + 1 < args.length) { 207 javacSourcePath = splitPath(args[++i]); 208 } else if (arg.equals(XMSGS_OPTION)) { 209 env.messages.setOptions(null); 210 } else if (arg.startsWith(XMSGS_CUSTOM_PREFIX)) { 211 env.messages.setOptions(arg.substring(arg.indexOf(":") + 1)); 212 } else if (arg.startsWith(XCUSTOM_TAGS_PREFIX)) { 213 env.setCustomTags(arg.substring(arg.indexOf(":") + 1)); 214 } else if (arg.startsWith(XHTML_VERSION_PREFIX)) { 215 String argsVersion = arg.substring(arg.indexOf(":") + 1); 216 HtmlVersion htmlVersion = HtmlVersion.getHtmlVersion(argsVersion); 217 if (htmlVersion != null) { 218 env.setHtmlVersion(htmlVersion); 219 } else { 220 throw new BadArgs("dc.bad.value.for.option", arg, argsVersion); 221 } 222 } else if (arg.equals("-h") || arg.equals("-help") || arg.equals("--help") 223 || arg.equals("-?") || arg.equals("-usage")) { 224 needHelp = true; 225 } else if (arg.startsWith("-")) { 226 throw new BadArgs("dc.bad.option", arg); 227 } else { 228 while (i < args.length) 229 javacFiles.add(new File(args[i++])); 230 } 231 } 232 } 233 234 void showHelp(PrintWriter out) { 235 String msg = localize("dc.main.usage"); 236 for (String line: msg.split("\n")) 237 out.println(line); 238 } 239 240 List<File> splitPath(String path) { 241 List<File> files = new ArrayList<>(); 242 for (String f: path.split(File.pathSeparator)) { 243 if (f.length() > 0) 244 files.add(new File(f)); 245 } 246 return files; 247 } 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 void reportStats(PrintWriter out) { 348 env.messages.reportStats(out); 349 } 350 351 // </editor-fold> 352 353 Env env; 354 Checker checker; 355 356 public static boolean isValidOption(String opt) { 357 if (opt.equals(XMSGS_OPTION)) 358 return true; 359 if (opt.startsWith(XMSGS_CUSTOM_PREFIX)) 360 return Messages.Options.isValidOptions(opt.substring(XMSGS_CUSTOM_PREFIX.length())); 361 if (opt.startsWith(XCHECK_PACKAGE)) { 362 return Env.validatePackages(opt.substring(opt.indexOf(":") + 1)); 363 } 364 return false; 365 } 366 367 private String localize(String code, Object... args) { 368 Messages m = (env != null) ? env.messages : new Messages(null); 369 return m.localize(code, args); 370 } 371 372 // <editor-fold defaultstate="collapsed" desc="DeclScanner"> 373 374 static abstract class DeclScanner extends TreePathScanner<Void, Void> { 375 final Env env; 376 377 public DeclScanner(Env env) { 378 this.env = env; 379 } 380 381 abstract void visitDecl(Tree tree, Name name); 382 383 @Override @DefinedBy(Api.COMPILER_TREE) 384 public Void visitPackage(PackageTree tree, Void ignore) { 385 visitDecl(tree, null); 386 return super.visitPackage(tree, ignore); 387 } 388 @Override @DefinedBy(Api.COMPILER_TREE) 389 public Void visitClass(ClassTree tree, Void ignore) { 390 visitDecl(tree, tree.getSimpleName()); 391 return super.visitClass(tree, ignore); 392 } 393 394 @Override @DefinedBy(Api.COMPILER_TREE) 395 public Void visitMethod(MethodTree tree, Void ignore) { 396 visitDecl(tree, tree.getName()); 397 //return super.visitMethod(tree, ignore); 398 return null; 399 } 400 401 @Override @DefinedBy(Api.COMPILER_TREE) 402 public Void visitVariable(VariableTree tree, Void ignore) { 403 visitDecl(tree, tree.getName()); 404 return super.visitVariable(tree, ignore); 405 } 406 407 @Override @DefinedBy(Api.COMPILER_TREE) 408 public Void visitCompilationUnit(CompilationUnitTree node, Void p) { 409 if (env.includePackages != null) { 410 String packageName = node.getPackageName() != null 411 ? node.getPackageName().toString() 412 : ""; 413 if (!env.includePackages.isEmpty()) { 414 boolean included = false; 415 for (Pattern pack : env.includePackages) { 416 if (pack.matcher(packageName).matches()) { 417 included = true; 418 break; 419 } 420 } 421 if (!included) 422 return null; 423 } 424 for (Pattern pack : env.excludePackages) { 425 if (pack.matcher(packageName).matches()) { 426 return null; 427 } 428 } 429 } 430 return super.visitCompilationUnit(node, p); 431 } 432 433 } 434 435 // </editor-fold> 436 437} 438