Start.java revision 3828:d30434bde0a8
1/* 2 * Copyright (c) 1997, 2016, 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.javadoc.main; 27 28import java.io.File; 29import java.io.FileNotFoundException; 30import java.io.IOException; 31import java.io.PrintWriter; 32import java.nio.file.Path; 33import java.util.ArrayList; 34import java.util.Collection; 35import java.util.Collections; 36import java.util.Objects; 37 38import javax.tools.JavaFileManager; 39import javax.tools.JavaFileObject; 40import javax.tools.StandardJavaFileManager; 41import javax.tools.StandardLocation; 42 43import com.sun.javadoc.*; 44import com.sun.tools.javac.file.JavacFileManager; 45import com.sun.tools.javac.main.CommandLine; 46import com.sun.tools.javac.main.Option; 47import com.sun.tools.javac.file.BaseFileManager; 48import com.sun.tools.javac.main.Arguments; 49import com.sun.tools.javac.main.OptionHelper; 50import com.sun.tools.javac.main.OptionHelper.GrumpyHelper; 51import com.sun.tools.javac.platform.PlatformDescription; 52import com.sun.tools.javac.platform.PlatformUtils; 53import com.sun.tools.javac.util.ClientCodeException; 54import com.sun.tools.javac.util.Context; 55import com.sun.tools.javac.util.List; 56import com.sun.tools.javac.util.ListBuffer; 57import com.sun.tools.javac.util.Log; 58import com.sun.tools.javac.util.Options; 59 60import static com.sun.tools.javac.code.Flags.*; 61 62/** 63 * Main program of Javadoc. 64 * Previously named "Main". 65 * 66 * <p><b>This is NOT part of any supported API. 67 * If you write code that depends on this, you do so at your own risk. 68 * This code and its internal interfaces are subject to change or 69 * deletion without notice.</b> 70 * 71 * @since 1.2 72 * @author Robert Field 73 * @author Neal Gafter (rewrite) 74 */ 75@Deprecated 76public 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 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 110 public Start(PrintWriter pw) { 111 this(javadocName, pw, pw, pw, standardDocletClassName); 112 } 113 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 126 public Start(String programName, String defaultDocletClassName) { 127 this(programName, defaultDocletClassName, null); 128 } 129 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 138 public Start(String programName, ClassLoader docletParentClassLoader) { 139 this(programName, standardDocletClassName, docletParentClassLoader); 140 } 141 142 public Start(String programName) { 143 this(programName, standardDocletClassName); 144 } 145 146 public Start(ClassLoader docletParentClassLoader) { 147 this(javadocName, docletParentClassLoader); 148 } 149 150 public Start() { 151 this(javadocName); 152 } 153 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 174 void usage() { 175 usage(true); 176 } 177 178 void usage(boolean exit) { 179 usage("main.usage", "-help", "main.usage.foot", exit); 180 } 181 182 @Override 183 void Xusage() { 184 Xusage(true); 185 } 186 187 void Xusage(boolean exit) { 188 usage("main.Xusage", "-X", "main.Xusage.foot", exit); 189 } 190 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 */ 215 private void exit() { 216 messager.exit(); 217 } 218 219 220 /** 221 * Main program - external wrapper 222 */ 223 public int begin(String... argv) { 224 boolean ok = begin(null, argv, Collections.emptySet()); 225 return ok ? 0 : 1; 226 } 227 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 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 */ 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 Collection<Path> platformCP = platformDescription.getPlatformPath(); 402 403 if (platformCP != null) { 404 if (fileManager instanceof StandardJavaFileManager) { 405 StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager; 406 407 sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP); 408 } else { 409 usageError("main.release.not.standard.file.manager", platformString); 410 } 411 } 412 } 413 414 compOpts.notifyListeners(); 415 416 if (javaNames.isEmpty() && subPackages.isEmpty() && isEmpty(fileObjects)) { 417 usageError("main.No_packages_or_classes_specified"); 418 } 419 420 if (!docletInvoker.validOptions(options.toList())) { 421 // error message already displayed 422 exit(); 423 } 424 425 JavadocTool comp = JavadocTool.make0(context); 426 if (comp == null) return false; 427 428 if (showAccess == null) { 429 setFilter(defaultFilter); 430 } 431 432 LanguageVersion languageVersion = docletInvoker.languageVersion(); 433 RootDocImpl root = comp.getRootDocImpl( 434 docLocale, 435 encoding, 436 showAccess, 437 javaNames.toList(), 438 options.toList(), 439 fileObjects, 440 breakiterator, 441 subPackages.toList(), 442 excludedPackages.toList(), 443 docClasses, 444 // legacy? 445 languageVersion == null || languageVersion == LanguageVersion.JAVA_1_1, 446 quiet); 447 448 // release resources 449 comp = null; 450 451 // pass off control to the doclet 452 boolean ok = root != null; 453 if (ok) ok = docletInvoker.start(root); 454 455 // We're done. 456 if (compOpts.get("-verbose") != null) { 457 tm = System.currentTimeMillis() - tm; 458 messager.notice("main.done_in", Long.toString(tm)); 459 } 460 461 return ok; 462 } 463 464 private <T> boolean isEmpty(Iterable<T> iter) { 465 return !iter.iterator().hasNext(); 466 } 467 468 /** 469 * Init the doclet invoker. 470 * The doclet class may be given explicitly, or via the -doclet option in 471 * argv. 472 * If the doclet class is not given explicitly, it will be loaded from 473 * the file manager's DOCLET_PATH location, if available, or via the 474 * -doclet path option in argv. 475 * @param docletClass The doclet class. May be null. 476 * @param fileManager The file manager used to get the class loader to load 477 * the doclet class if required. May be null. 478 * @param argv Args containing -doclet and -docletpath, in case they are required. 479 */ 480 private void setDocletInvoker(Class<?> docletClass, JavaFileManager fileManager, String[] argv) { 481 boolean exportInternalAPI = false; 482 String docletClassName = null; 483 String docletPath = null; 484 485 // Parse doclet specifying arguments 486 for (int i = 0 ; i < argv.length ; i++) { 487 String arg = argv[i]; 488 if (arg.equals(ToolOption.DOCLET.opt)) { 489 oneArg(argv, i++); 490 if (docletClassName != null) { 491 usageError("main.more_than_one_doclet_specified_0_and_1", 492 docletClassName, argv[i]); 493 } 494 docletClassName = argv[i]; 495 } else if (arg.equals(ToolOption.DOCLETPATH.opt)) { 496 oneArg(argv, i++); 497 if (docletPath == null) { 498 docletPath = argv[i]; 499 } else { 500 docletPath += File.pathSeparator + argv[i]; 501 } 502 } else if (arg.equals("-XDaccessInternalAPI")) { 503 exportInternalAPI = true; 504 } 505 } 506 507 if (docletClass != null) { 508 // TODO, check no -doclet, -docletpath 509 docletInvoker = new DocletInvoker(messager, docletClass, apiMode, exportInternalAPI); 510 } else { 511 if (docletClassName == null) { 512 docletClassName = defaultDocletClassName; 513 } 514 515 // attempt to find doclet 516 docletInvoker = new DocletInvoker(messager, fileManager, 517 docletClassName, docletPath, 518 docletParentClassLoader, 519 apiMode, 520 exportInternalAPI); 521 } 522 } 523 524 /** 525 * Set one arg option. 526 * Error and exit if one argument is not provided. 527 */ 528 private void oneArg(String[] args, int index) { 529 if ((index + 1) < args.length) { 530 setOption(args[index], args[index+1]); 531 } else { 532 usageError("main.requires_argument", args[index]); 533 } 534 } 535 536 @Override 537 void usageError(String key, Object... args) { 538 messager.error(Messager.NOPOS, key, args); 539 usage(true); 540 } 541 542 /** 543 * indicate an option with no arguments was given. 544 */ 545 private void setOption(String opt) { 546 String[] option = { opt }; 547 options.append(option); 548 } 549 550 /** 551 * indicate an option with one argument was given. 552 */ 553 private void setOption(String opt, String argument) { 554 String[] option = { opt, argument }; 555 options.append(option); 556 } 557 558 /** 559 * indicate an option with the specified list of arguments was given. 560 */ 561 private void setOption(String opt, List<String> arguments) { 562 String[] args = new String[arguments.length() + 1]; 563 int k = 0; 564 args[k++] = opt; 565 for (List<String> i = arguments; i.nonEmpty(); i=i.tail) { 566 args[k++] = i.head; 567 } 568 options.append(args); 569 } 570 571 @Override 572 OptionHelper getOptionHelper() { 573 return new GrumpyHelper(messager) { 574 @Override 575 public String get(com.sun.tools.javac.main.Option option) { 576 return compOpts.get(option); 577 } 578 579 @Override 580 public void put(String name, String value) { 581 compOpts.put(name, value); 582 } 583 }; 584 } 585} 586