1 /* 2 * Copyright (c) 1997, 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.javadoc.main; 27 28 import java.io.File; 29 import java.io.FileNotFoundException; 30 import java.io.IOException; 31 import java.io.PrintWriter; 32 import java.nio.file.Path; 33 import java.util.ArrayList; 34 import java.util.Collection; 35 import java.util.Collections; 36 import java.util.Objects; 37 38 import javax.tools.JavaFileManager; 39 import javax.tools.JavaFileObject; 40 41 import com.sun.javadoc.*; 42 import com.sun.tools.javac.file.JavacFileManager; 43 import com.sun.tools.javac.file.BaseFileManager; 44 import com.sun.tools.javac.main.Arguments; 45 import com.sun.tools.javac.main.CommandLine; 46 import com.sun.tools.javac.main.DelegatingJavaFileManager; 47 import com.sun.tools.javac.main.Option; 48 import com.sun.tools.javac.main.OptionHelper; 49 import com.sun.tools.javac.main.OptionHelper.GrumpyHelper; 50 import com.sun.tools.javac.platform.PlatformDescription; 51 import com.sun.tools.javac.platform.PlatformUtils; 52 import com.sun.tools.javac.util.ClientCodeException; 53 import com.sun.tools.javac.util.Context; 54 import com.sun.tools.javac.util.List; 55 import com.sun.tools.javac.util.ListBuffer; 56 import com.sun.tools.javac.util.Log; 57 import com.sun.tools.javac.util.Options; 58 59 import static com.sun.tools.javac.code.Flags.*; 60 61 /** 62 * Main program of Javadoc. 63 * Previously named "Main". 64 * 65 * <p><b>This is NOT part of any supported API. 66 * If you write code that depends on this, you do so at your own risk. 67 * This code and its internal interfaces are subject to change or 68 * deletion without notice.</b> 69 * 70 * @since 1.2 71 * @author Robert Field 72 * @author Neal Gafter (rewrite) 73 */ 74 @Deprecated(since="9", forRemoval=true) 75 @SuppressWarnings("removal") 76 public class Start extends ToolOption.Helper { 77 /** Context for this invocation. */ 78 private final Context context; 79 80 private final String defaultDocletClassName; 81 private final ClassLoader docletParentClassLoader; 82 83 private static final String javadocName = "javadoc"; 84 85 private static final String standardDocletClassName = 86 "com.sun.tools.doclets.standard.Standard"; 87 88 private final long defaultFilter = PUBLIC | PROTECTED; 89 90 private final Messager messager; 91 92 private DocletInvoker docletInvoker; 93 94 /** 95 * In API mode, exceptions thrown while calling the doclet are 96 * propagated using ClientCodeException. 97 */ 98 private boolean apiMode; 99 100 private JavaFileManager fileManager; 101 Start(String programName, PrintWriter errWriter, PrintWriter warnWriter, PrintWriter noticeWriter, String defaultDocletClassName)102 public Start(String programName, 103 PrintWriter errWriter, 104 PrintWriter warnWriter, 105 PrintWriter noticeWriter, 106 String defaultDocletClassName) { 107 this(programName, errWriter, warnWriter, noticeWriter, defaultDocletClassName, null); 108 } 109 Start(PrintWriter pw)110 public Start(PrintWriter pw) { 111 this(javadocName, pw, pw, pw, standardDocletClassName); 112 } 113 Start(String programName, PrintWriter errWriter, PrintWriter warnWriter, PrintWriter noticeWriter, String defaultDocletClassName, ClassLoader docletParentClassLoader)114 public Start(String programName, 115 PrintWriter errWriter, 116 PrintWriter warnWriter, 117 PrintWriter noticeWriter, 118 String defaultDocletClassName, 119 ClassLoader docletParentClassLoader) { 120 context = new Context(); 121 messager = new Messager(context, programName, errWriter, warnWriter, noticeWriter); 122 this.defaultDocletClassName = defaultDocletClassName; 123 this.docletParentClassLoader = docletParentClassLoader; 124 } 125 Start(String programName, String defaultDocletClassName)126 public Start(String programName, String defaultDocletClassName) { 127 this(programName, defaultDocletClassName, null); 128 } 129 Start(String programName, String defaultDocletClassName, ClassLoader docletParentClassLoader)130 public Start(String programName, String defaultDocletClassName, 131 ClassLoader docletParentClassLoader) { 132 context = new Context(); 133 messager = new Messager(context, programName); 134 this.defaultDocletClassName = defaultDocletClassName; 135 this.docletParentClassLoader = docletParentClassLoader; 136 } 137 Start(String programName, ClassLoader docletParentClassLoader)138 public Start(String programName, ClassLoader docletParentClassLoader) { 139 this(programName, standardDocletClassName, docletParentClassLoader); 140 } 141 Start(String programName)142 public Start(String programName) { 143 this(programName, standardDocletClassName); 144 } 145 Start(ClassLoader docletParentClassLoader)146 public Start(ClassLoader docletParentClassLoader) { 147 this(javadocName, docletParentClassLoader); 148 } 149 Start()150 public Start() { 151 this(javadocName); 152 } 153 Start(Context context)154 public Start(Context context) { 155 this.context = Objects.requireNonNull(context); 156 apiMode = true; 157 defaultDocletClassName = standardDocletClassName; 158 docletParentClassLoader = null; 159 160 Log log = context.get(Log.logKey); 161 if (log instanceof Messager) 162 messager = (Messager) log; 163 else { 164 PrintWriter out = context.get(Log.errKey); 165 messager = (out == null) ? new Messager(context, javadocName) 166 : new Messager(context, javadocName, out, out, out); 167 } 168 } 169 170 /** 171 * Usage 172 */ 173 @Override usage()174 void usage() { 175 usage(true); 176 } 177 usage(boolean exit)178 void usage(boolean exit) { 179 usage("main.usage", "-help", "main.usage.foot", exit); 180 } 181 182 @Override Xusage()183 void Xusage() { 184 Xusage(true); 185 } 186 Xusage(boolean exit)187 void Xusage(boolean exit) { 188 usage("main.Xusage", "-X", "main.Xusage.foot", exit); 189 } 190 usage(String main, String doclet, String foot, boolean exit)191 private void usage(String main, String doclet, String foot, boolean exit) { 192 // RFE: it would be better to replace the following with code to 193 // write a header, then help for each option, then a footer. 194 messager.notice(main); 195 196 // let doclet print usage information (does nothing on error) 197 if (docletInvoker != null) { 198 // RFE: this is a pretty bad way to get the doclet to show 199 // help info. Moreover, the output appears on stdout, 200 // and <i>not</i> on any of the standard streams passed 201 // to javadoc, and in particular, not to the noticeWriter 202 // But, to fix this, we need to fix the Doclet API. 203 docletInvoker.optionLength(doclet); 204 } 205 206 if (foot != null) 207 messager.notice(foot); 208 209 if (exit) exit(); 210 } 211 212 /** 213 * Exit 214 */ exit()215 private void exit() { 216 messager.exit(); 217 } 218 219 220 /** 221 * Main program - external wrapper 222 */ begin(String... argv)223 public int begin(String... argv) { 224 boolean ok = begin(null, argv, Collections.emptySet()); 225 return ok ? 0 : 1; 226 } 227 begin(Class<?> docletClass, Iterable<String> options, Iterable<? extends JavaFileObject> fileObjects)228 public boolean begin(Class<?> docletClass, Iterable<String> options, Iterable<? extends JavaFileObject> fileObjects) { 229 Collection<String> opts = new ArrayList<>(); 230 for (String opt: options) opts.add(opt); 231 return begin(docletClass, opts.toArray(new String[opts.size()]), fileObjects); 232 } 233 begin(Class<?> docletClass, String[] options, Iterable<? extends JavaFileObject> fileObjects)234 private boolean begin(Class<?> docletClass, String[] options, Iterable<? extends JavaFileObject> fileObjects) { 235 boolean failed = false; 236 237 try { 238 failed = !parseAndExecute(docletClass, options, fileObjects); 239 } catch (Messager.ExitJavadoc exc) { 240 // ignore, we just exit this way 241 } catch (OutOfMemoryError ee) { 242 messager.error(Messager.NOPOS, "main.out.of.memory"); 243 failed = true; 244 } catch (ClientCodeException e) { 245 // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl 246 throw e; 247 } catch (Error ee) { 248 ee.printStackTrace(System.err); 249 messager.error(Messager.NOPOS, "main.fatal.error"); 250 failed = true; 251 } catch (Exception ee) { 252 ee.printStackTrace(System.err); 253 messager.error(Messager.NOPOS, "main.fatal.exception"); 254 failed = true; 255 } finally { 256 if (fileManager != null 257 && fileManager instanceof BaseFileManager 258 && ((BaseFileManager) fileManager).autoClose) { 259 try { 260 fileManager.close(); 261 } catch (IOException ignore) { 262 } 263 } 264 messager.exitNotice(); 265 messager.flush(); 266 } 267 failed |= messager.nerrors() > 0; 268 failed |= rejectWarnings && messager.nwarnings() > 0; 269 return !failed; 270 } 271 272 /** 273 * Main program - internal 274 */ parseAndExecute( Class<?> docletClass, String[] argv, Iterable<? extends JavaFileObject> fileObjects)275 private boolean parseAndExecute( 276 Class<?> docletClass, 277 String[] argv, 278 Iterable<? extends JavaFileObject> fileObjects) throws IOException { 279 long tm = System.currentTimeMillis(); 280 281 ListBuffer<String> javaNames = new ListBuffer<>(); 282 283 // Preprocess @file arguments 284 try { 285 argv = CommandLine.parse(argv); 286 } catch (FileNotFoundException e) { 287 messager.error(Messager.NOPOS, "main.cant.read", e.getMessage()); 288 exit(); 289 } catch (IOException e) { 290 e.printStackTrace(System.err); 291 exit(); 292 } 293 294 295 fileManager = context.get(JavaFileManager.class); 296 297 setDocletInvoker(docletClass, fileManager, argv); 298 299 compOpts = Options.instance(context); 300 // Make sure no obsolete source/target messages are reported 301 compOpts.put("-Xlint:-options", "-Xlint:-options"); 302 303 // Parse arguments 304 for (int i = 0 ; i < argv.length ; i++) { 305 String arg = argv[i]; 306 307 ToolOption o = ToolOption.get(arg); 308 if (o != null) { 309 // hack: this restriction should be removed 310 if (o == ToolOption.LOCALE && i > 0) 311 usageError("main.locale_first"); 312 313 try { 314 if (o.hasArg) { 315 oneArg(argv, i++); 316 o.process(this, argv[i]); 317 } else { 318 setOption(arg); 319 o.process(this); 320 } 321 } catch (Option.InvalidValueException e) { 322 usageError("main.option.invalid.value", e.getMessage()); 323 } 324 } else if (arg.equals("-XDaccessInternalAPI")) { 325 // pass this hidden option down to the doclet, if it wants it 326 if (docletInvoker.optionLength("-XDaccessInternalAPI") == 1) { 327 setOption(arg); 328 } 329 } else if (arg.startsWith("-XD")) { 330 // hidden javac options 331 String s = arg.substring("-XD".length()); 332 int eq = s.indexOf('='); 333 String key = (eq < 0) ? s : s.substring(0, eq); 334 String value = (eq < 0) ? s : s.substring(eq+1); 335 compOpts.put(key, value); 336 } 337 // call doclet for its options 338 // other arg starts with - is invalid 339 else if (arg.startsWith("-")) { 340 int optionLength; 341 optionLength = docletInvoker.optionLength(arg); 342 if (optionLength < 0) { 343 // error already displayed 344 exit(); 345 } else if (optionLength == 0) { 346 // option not found 347 usageError("main.invalid_flag", arg); 348 } else { 349 // doclet added option 350 if ((i + optionLength) > argv.length) { 351 usageError("main.requires_argument", arg); 352 } 353 ListBuffer<String> args = new ListBuffer<>(); 354 for (int j = 0; j < optionLength-1; ++j) { 355 args.append(argv[++i]); 356 } 357 setOption(arg, args.toList()); 358 } 359 } else { 360 javaNames.append(arg); 361 } 362 } 363 364 if (fileManager == null) { 365 JavacFileManager.preRegister(context); 366 fileManager = context.get(JavaFileManager.class); 367 if (fileManager instanceof BaseFileManager) { 368 ((BaseFileManager) fileManager).autoClose = true; 369 } 370 } 371 if (fileManager instanceof BaseFileManager) { 372 ((BaseFileManager) fileManager).handleOptions(fileManagerOpts); 373 } 374 375 Arguments arguments = Arguments.instance(context); 376 arguments.init(messager.programName); 377 arguments.allowEmpty(); 378 arguments.validate(); 379 380 String platformString = compOpts.get("--release"); 381 382 if (platformString != null) { 383 if (compOpts.isSet("-source")) { 384 usageError("main.release.bootclasspath.conflict", "-source"); 385 } 386 if (fileManagerOpts.containsKey(Option.BOOT_CLASS_PATH)) { 387 usageError("main.release.bootclasspath.conflict", Option.BOOT_CLASS_PATH.getPrimaryName()); 388 } 389 390 PlatformDescription platformDescription = 391 PlatformUtils.lookupPlatformDescription(platformString); 392 393 if (platformDescription == null) { 394 usageError("main.unsupported.release.version", platformString); 395 } 396 397 compOpts.put(Option.SOURCE, platformDescription.getSourceVersion()); 398 399 context.put(PlatformDescription.class, platformDescription); 400 401 JavaFileManager platformFM = platformDescription.getFileManager(); 402 DelegatingJavaFileManager.installReleaseFileManager(context, 403 platformFM, 404 fileManager); 405 } 406 407 compOpts.notifyListeners(); 408 409 if (javaNames.isEmpty() && subPackages.isEmpty() && isEmpty(fileObjects)) { 410 usageError("main.No_packages_or_classes_specified"); 411 } 412 413 if (!docletInvoker.validOptions(options.toList())) { 414 // error message already displayed 415 exit(); 416 } 417 418 JavadocTool comp = JavadocTool.make0(context); 419 if (comp == null) return false; 420 421 if (showAccess == null) { 422 setFilter(defaultFilter); 423 } 424 425 LanguageVersion languageVersion = docletInvoker.languageVersion(); 426 RootDocImpl root = comp.getRootDocImpl( 427 docLocale, 428 encoding, 429 showAccess, 430 javaNames.toList(), 431 options.toList(), 432 fileObjects, 433 breakiterator, 434 subPackages.toList(), 435 excludedPackages.toList(), 436 docClasses, 437 // legacy? 438 languageVersion == null || languageVersion == LanguageVersion.JAVA_1_1, 439 quiet); 440 441 // release resources 442 comp = null; 443 444 // pass off control to the doclet 445 boolean ok = root != null; 446 if (ok) ok = docletInvoker.start(root); 447 448 // We're done. 449 if (compOpts.get("-verbose") != null) { 450 tm = System.currentTimeMillis() - tm; 451 messager.notice("main.done_in", Long.toString(tm)); 452 } 453 454 return ok; 455 } 456 isEmpty(Iterable<T> iter)457 private <T> boolean isEmpty(Iterable<T> iter) { 458 return !iter.iterator().hasNext(); 459 } 460 461 /** 462 * Init the doclet invoker. 463 * The doclet class may be given explicitly, or via the -doclet option in 464 * argv. 465 * If the doclet class is not given explicitly, it will be loaded from 466 * the file manager's DOCLET_PATH location, if available, or via the 467 * -doclet path option in argv. 468 * @param docletClass The doclet class. May be null. 469 * @param fileManager The file manager used to get the class loader to load 470 * the doclet class if required. May be null. 471 * @param argv Args containing -doclet and -docletpath, in case they are required. 472 */ setDocletInvoker(Class<?> docletClass, JavaFileManager fileManager, String[] argv)473 private void setDocletInvoker(Class<?> docletClass, JavaFileManager fileManager, String[] argv) { 474 boolean exportInternalAPI = false; 475 String docletClassName = null; 476 String docletPath = null; 477 478 // Parse doclet specifying arguments 479 for (int i = 0 ; i < argv.length ; i++) { 480 String arg = argv[i]; 481 if (arg.equals(ToolOption.DOCLET.opt)) { 482 oneArg(argv, i++); 483 if (docletClassName != null) { 484 usageError("main.more_than_one_doclet_specified_0_and_1", 485 docletClassName, argv[i]); 486 } 487 docletClassName = argv[i]; 488 } else if (arg.equals(ToolOption.DOCLETPATH.opt)) { 489 oneArg(argv, i++); 490 if (docletPath == null) { 491 docletPath = argv[i]; 492 } else { 493 docletPath += File.pathSeparator + argv[i]; 494 } 495 } else if (arg.equals("-XDaccessInternalAPI")) { 496 exportInternalAPI = true; 497 } 498 } 499 500 if (docletClass != null) { 501 // TODO, check no -doclet, -docletpath 502 docletInvoker = new DocletInvoker(messager, docletClass, apiMode, exportInternalAPI); 503 } else { 504 if (docletClassName == null) { 505 docletClassName = defaultDocletClassName; 506 } 507 508 // attempt to find doclet 509 docletInvoker = new DocletInvoker(messager, fileManager, 510 docletClassName, docletPath, 511 docletParentClassLoader, 512 apiMode, 513 exportInternalAPI); 514 } 515 } 516 517 /** 518 * Set one arg option. 519 * Error and exit if one argument is not provided. 520 */ oneArg(String[] args, int index)521 private void oneArg(String[] args, int index) { 522 if ((index + 1) < args.length) { 523 setOption(args[index], args[index+1]); 524 } else { 525 usageError("main.requires_argument", args[index]); 526 } 527 } 528 529 @Override usageError(String key, Object... args)530 void usageError(String key, Object... args) { 531 messager.error(Messager.NOPOS, key, args); 532 usage(true); 533 } 534 535 /** 536 * indicate an option with no arguments was given. 537 */ setOption(String opt)538 private void setOption(String opt) { 539 String[] option = { opt }; 540 options.append(option); 541 } 542 543 /** 544 * indicate an option with one argument was given. 545 */ setOption(String opt, String argument)546 private void setOption(String opt, String argument) { 547 String[] option = { opt, argument }; 548 options.append(option); 549 } 550 551 /** 552 * indicate an option with the specified list of arguments was given. 553 */ setOption(String opt, List<String> arguments)554 private void setOption(String opt, List<String> arguments) { 555 String[] args = new String[arguments.length() + 1]; 556 int k = 0; 557 args[k++] = opt; 558 for (List<String> i = arguments; i.nonEmpty(); i=i.tail) { 559 args[k++] = i.head; 560 } 561 options.append(args); 562 } 563 564 @Override getOptionHelper()565 OptionHelper getOptionHelper() { 566 return new GrumpyHelper(messager) { 567 @Override 568 public String get(com.sun.tools.javac.main.Option option) { 569 return compOpts.get(option); 570 } 571 572 @Override 573 public void put(String name, String value) { 574 compOpts.put(name, value); 575 } 576 }; 577 } 578 } 579