001/* 002 * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package org.jdrupes.mdoclet.internal.doclint; 027 028import java.io.File; 029import java.io.IOException; 030import java.io.PrintWriter; 031import java.util.ArrayList; 032import java.util.LinkedList; 033import java.util.List; 034import java.util.Queue; 035 036import javax.lang.model.element.Name; 037import javax.lang.model.util.Elements; 038import javax.lang.model.util.Types; 039import javax.tools.StandardLocation; 040 041import com.sun.source.doctree.DocCommentTree; 042import com.sun.source.tree.BlockTree; 043import com.sun.source.tree.ClassTree; 044import com.sun.source.tree.CompilationUnitTree; 045import com.sun.source.tree.LambdaExpressionTree; 046import com.sun.source.tree.ModuleTree; 047import com.sun.source.tree.PackageTree; 048import com.sun.source.tree.MethodTree; 049import com.sun.source.tree.Tree; 050import com.sun.source.tree.VariableTree; 051import com.sun.source.util.DocTrees; 052import com.sun.source.util.JavacTask; 053import com.sun.source.util.TaskEvent; 054import com.sun.source.util.TaskListener; 055import com.sun.source.util.TreePath; 056import com.sun.source.util.TreePathScanner; 057import com.sun.tools.javac.api.JavacTaskImpl; 058import com.sun.tools.javac.api.JavacTool; 059import com.sun.tools.javac.file.JavacFileManager; 060import com.sun.tools.javac.main.JavaCompiler; 061import com.sun.tools.javac.util.Context; 062import com.sun.tools.javac.util.DefinedBy; 063import com.sun.tools.javac.util.DefinedBy.Api; 064 065/** 066 * Multi-function entry point for the doc check utility. 067 * 068 * This class can be invoked in the following ways: 069 * <ul> 070 * <li>From the command line 071 * <li>From javac, as a plugin 072 * <li>Directly, via a simple API 073 * </ul> 074 */ 075public class DocLint extends com.sun.tools.doclint.DocLint { 076 077 public static final String XMSGS_OPTION = "-Xmsgs"; 078 public static final String XMSGS_CUSTOM_PREFIX = "-Xmsgs:"; 079 private static final String STATS = "-stats"; 080 public static final String XCUSTOM_TAGS_PREFIX = "-XcustomTags:"; 081 public static final String XCHECK_PACKAGE = "-XcheckPackage:"; 082 public static final String SEPARATOR = ","; 083 084 // <editor-fold defaultstate="collapsed" desc="Command-line entry point"> 085 public static void main(String... args) { 086 DocLint dl = new DocLint(); 087 try { 088 dl.run(args); 089 } catch (BadArgs e) { 090 System.err.println(e.getMessage()); 091 System.exit(1); 092 } catch (IOException e) { 093 System.err.println( 094 dl.localize("dc.main.ioerror", e.getLocalizedMessage())); 095 System.exit(2); 096 } 097 } 098 099 // </editor-fold> 100 101 // <editor-fold defaultstate="collapsed" desc="Simple API"> 102 103 public class BadArgs extends Exception { 104 private static final long serialVersionUID = 0; 105 106 BadArgs(String code, Object... args) { 107 super(localize(code, args)); 108 this.code = code; 109 this.args = args; 110 } 111 112 final String code; 113 final transient Object[] args; 114 } 115 116 /** 117 * Simple API entry point. 118 * @param args Options and operands for doclint 119 * @throws BadArgs if an error is detected in any args 120 * @throws IOException if there are problems with any of the file arguments 121 */ 122 public void run(String... args) throws BadArgs, IOException { 123 PrintWriter out = new PrintWriter(System.out); 124 try { 125 run(out, args); 126 } finally { 127 out.flush(); 128 } 129 } 130 131 public void run(PrintWriter out, String... args) 132 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 if (javacBootClassPath != null) { 151 fm.setLocation(StandardLocation.PLATFORM_CLASS_PATH, 152 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")) 206 && 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.equals("-h") || arg.equals("-help") 226 || arg.equals("--help") 227 || arg.equals("-?") || arg.equals("-usage")) { 228 needHelp = true; 229 } else if (arg.startsWith("-")) { 230 throw new BadArgs("dc.bad.option", arg); 231 } else { 232 while (i < args.length) 233 javacFiles.add(new File(args[i++])); 234 } 235 } 236 } 237 238 void showHelp(PrintWriter out) { 239 String msg = localize("dc.main.usage"); 240 for (String line : msg.split("\n")) 241 out.println(line); 242 } 243 244 List<File> splitPath(String path) { 245 List<File> files = new ArrayList<>(); 246 for (String f : path.split(File.pathSeparator)) { 247 if (f.length() > 0) 248 files.add(new File(f)); 249 } 250 return files; 251 } 252 253 List<File> javacBootClassPath; 254 List<File> javacClassPath; 255 List<File> javacSourcePath; 256 List<String> javacOpts; 257 List<File> javacFiles; 258 boolean needHelp = false; 259 260 // </editor-fold> 261 262 // <editor-fold defaultstate="collapsed" desc="javac Plugin"> 263 264 @Override 265 @DefinedBy(Api.COMPILER_TREE) 266 public String getName() { 267 return "doclint"; 268 } 269 270 @Override 271 @DefinedBy(Api.COMPILER_TREE) 272 public void init(JavacTask task, String... args) { 273 init(task, args, true); 274 } 275 276 // </editor-fold> 277 278 // <editor-fold defaultstate="collapsed" desc="Embedding API"> 279 280 /** 281 * Initialize DocLint for use with a {@code JavacTask}. 282 * {@link Env#strictReferenceChecks Strict reference checks} are <em>not</em> enabled by default. 283 * 284 * @param task the task 285 * @param args arguments to configure DocLint 286 * @param addTaskListener whether or not to register a {@code TaskListener} to invoke DocLint 287 */ 288 public void init(JavacTask task, String[] args, boolean addTaskListener) { 289 env = new Env(); 290 env.init(task); 291 processArgs(env, args); 292 293 checker = new Checker(env); 294 295 if (addTaskListener) { 296 final DeclScanner ds = new DeclScanner(env) { 297 @Override 298 void visitDecl(Tree tree, Name name) { 299 TreePath p = getCurrentPath(); 300 DocCommentTree dc = env.trees.getDocCommentTree(p); 301 checker.scan(dc, p); 302 } 303 }; 304 305 TaskListener tl = new TaskListener() { 306 @Override 307 @DefinedBy(Api.COMPILER_TREE) 308 public void started(TaskEvent e) { 309 switch (e.getKind()) { 310 case ANALYZE: 311 CompilationUnitTree tree; 312 while ((tree = todo.poll()) != null) 313 ds.scan(tree, null); 314 break; 315 } 316 } 317 318 @Override 319 @DefinedBy(Api.COMPILER_TREE) 320 public void finished(TaskEvent e) { 321 switch (e.getKind()) { 322 case PARSE: 323 todo.add(e.getCompilationUnit()); 324 break; 325 } 326 } 327 328 Queue<CompilationUnitTree> todo = new LinkedList<>(); 329 }; 330 331 task.addTaskListener(tl); 332 } 333 } 334 335 /** 336 * Initialize DocLint with the given utility objects and arguments. 337 * {@link Env#strictReferenceChecks Strict reference checks} <em>are</em> enabled by default. 338 * 339 * @param trees the {@code DocTrees} utility class 340 * @param elements the {@code Elements} utility class 341 * @param types the {@code Types} utility class 342 * @param args arguments to configure DocLint 343 */ 344 public void init(DocTrees trees, Elements elements, Types types, 345 String... args) { 346 env = new Env(); 347 env.init(trees, elements, types); 348 env.strictReferenceChecks = true; 349 processArgs(env, args); 350 351 checker = new Checker(env); 352 } 353 354 private void processArgs(Env env, String... args) { 355 for (String arg : args) { 356 if (arg.equals(XMSGS_OPTION)) { 357 env.messages.setOptions(null); 358 } else if (arg.startsWith(XMSGS_CUSTOM_PREFIX)) { 359 env.messages.setOptions(arg.substring(arg.indexOf(":") + 1)); 360 } else if (arg.startsWith(XCUSTOM_TAGS_PREFIX)) { 361 env.setCustomTags(arg.substring(arg.indexOf(":") + 1)); 362 } else if (arg.startsWith(XCHECK_PACKAGE)) { 363 env.setCheckPackages(arg.substring(arg.indexOf(":") + 1)); 364 } else 365 throw new IllegalArgumentException(arg); 366 } 367 } 368 369 public void scan(TreePath p) { 370 DocCommentTree dc = env.trees.getDocCommentTree(p); 371 checker.scan(dc, p); 372 } 373 374 public boolean shouldCheck(CompilationUnitTree unit) { 375 return env.shouldCheck(unit); 376 } 377 378 public void reportStats(PrintWriter out) { 379 env.messages.reportStats(out); 380 } 381 382 // </editor-fold> 383 384 Env env; 385 Checker checker; 386 387 public boolean isValidOption(String opt) { 388 if (opt.equals(XMSGS_OPTION)) 389 return true; 390 if (opt.startsWith(XMSGS_CUSTOM_PREFIX)) 391 return Messages.Options 392 .isValidOptions(opt.substring(XMSGS_CUSTOM_PREFIX.length())); 393 if (opt.startsWith(XCHECK_PACKAGE)) { 394 return Env.validatePackages(opt.substring(opt.indexOf(":") + 1)); 395 } 396 return false; 397 } 398 399 public boolean isGroupEnabled(Messages.Group group, 400 Env.AccessKind accessKind) { 401 return env.messages.isEnabled(group, accessKind); 402 } 403 404 private String localize(String code, Object... args) { 405 Messages m = (env != null) ? env.messages : new Messages(null); 406 return m.localize(code, args); 407 } 408 409 // <editor-fold defaultstate="collapsed" desc="DeclScanner"> 410 411 abstract static class DeclScanner extends TreePathScanner<Void, Void> { 412 final Env env; 413 414 public DeclScanner(Env env) { 415 this.env = env; 416 } 417 418 abstract void visitDecl(Tree tree, Name name); 419 420 @Override 421 @DefinedBy(Api.COMPILER_TREE) 422 public Void visitPackage(PackageTree tree, Void ignore) { 423 visitDecl(tree, null); 424 return super.visitPackage(tree, ignore); 425 } 426 427 @Override 428 @DefinedBy(Api.COMPILER_TREE) 429 public Void visitClass(ClassTree tree, Void ignore) { 430 visitDecl(tree, tree.getSimpleName()); 431 return super.visitClass(tree, ignore); 432 } 433 434 @Override 435 @DefinedBy(Api.COMPILER_TREE) 436 public Void visitMethod(MethodTree tree, Void ignore) { 437 visitDecl(tree, tree.getName()); 438 return null; 439 } 440 441 @Override 442 @DefinedBy(Api.COMPILER_TREE) 443 public Void visitModule(ModuleTree tree, Void ignore) { 444 visitDecl(tree, null); 445 return super.visitModule(tree, ignore); 446 } 447 448 @Override 449 @DefinedBy(Api.COMPILER_TREE) 450 public Void visitVariable(VariableTree tree, Void ignore) { 451 visitDecl(tree, tree.getName()); 452 return super.visitVariable(tree, ignore); 453 } 454 455 @Override 456 @DefinedBy(Api.COMPILER_TREE) 457 public Void visitCompilationUnit(CompilationUnitTree node, Void p) { 458 if (!env.shouldCheck(node)) { 459 return null; 460 } 461 return super.visitCompilationUnit(node, p); 462 } 463 464 @Override 465 @DefinedBy(Api.COMPILER_TREE) 466 public Void visitBlock(BlockTree tree, Void ignore) { 467 return null; 468 } 469 470 @Override 471 @DefinedBy(Api.COMPILER_TREE) 472 public Void visitLambdaExpression(LambdaExpressionTree tree, 473 Void ignore) { 474 return null; 475 } 476 477 } 478 479 // </editor-fold> 480 481}