Start.java revision 3233:b5d08bc0d224
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 jdk.javadoc.internal.tool; 27 28import java.io.File; 29import java.io.FileNotFoundException; 30import java.io.IOException; 31import java.io.PrintWriter; 32import java.nio.file.Path; 33import java.text.BreakIterator; 34import java.util.ArrayList; 35import java.util.Arrays; 36import java.util.Collection; 37import java.util.Collections; 38import java.util.List; 39import java.util.Locale; 40import java.util.Objects; 41import java.util.Set; 42import static javax.tools.DocumentationTool.Location.*; 43import javax.tools.JavaFileManager; 44import javax.tools.JavaFileObject; 45import javax.tools.StandardJavaFileManager; 46import javax.tools.StandardLocation; 47 48import com.sun.tools.javac.api.JavacTrees; 49import com.sun.tools.javac.file.BaseFileManager; 50import com.sun.tools.javac.file.JavacFileManager; 51import com.sun.tools.javac.main.CommandLine; 52import com.sun.tools.javac.platform.PlatformDescription; 53import com.sun.tools.javac.platform.PlatformUtils; 54import com.sun.tools.javac.util.ClientCodeException; 55import com.sun.tools.javac.util.Context; 56import com.sun.tools.javac.util.Log; 57import com.sun.tools.javac.util.Options; 58 59import jdk.javadoc.doclet.Doclet; 60import jdk.javadoc.doclet.Doclet.Option; 61import jdk.javadoc.doclet.DocletEnvironment; 62 63import static com.sun.tools.javac.main.Option.*; 64/** 65 * Main program of Javadoc. 66 * Previously named "Main". 67 * 68 * <p><b>This is NOT part of any supported API. 69 * If you write code that depends on this, you do so at your own risk. 70 * This code and its internal interfaces are subject to change or 71 * deletion without notice.</b> 72 * 73 * @author Robert Field 74 * @author Neal Gafter (rewrite) 75 */ 76public class Start extends ToolOption.Helper { 77 /** Context for this invocation. */ 78 private final Context context; 79 80 private static final String ProgramName = "javadoc"; 81 82 // meaning we allow all visibility of PROTECTED and PUBLIC 83 private static final String defaultModifier = "protected"; 84 85 private Messager messager; 86 87 private final String docletName; 88 89 private final ClassLoader classLoader; 90 91 private Class<?> docletClass; 92 93 private Doclet doclet; 94 95 // used to determine the locale for the messager 96 private Locale locale; 97 98 99 /** 100 * In API mode, exceptions thrown while calling the doclet are 101 * propagated using ClientCodeException. 102 */ 103 private boolean apiMode; 104 105 private JavaFileManager fileManager; 106 107 Start() { 108 this(null, null, null, null, null); 109 } 110 111 Start(PrintWriter writer) { 112 this(null, null, writer, null, null); 113 } 114 115 Start(Context context, String programName, PrintWriter writer, 116 String docletName, ClassLoader classLoader) { 117 this.context = context == null ? new Context() : context; 118 String pname = programName == null ? ProgramName : programName; 119 this.messager = writer == null 120 ? new Messager(this.context, pname) 121 : new Messager(this.context, pname, writer, writer); 122 this.docletName = docletName; 123 this.classLoader = classLoader; 124 this.docletClass = null; 125 this.locale = Locale.getDefault(); 126 } 127 128 public Start(Context context) { 129 this.docletClass = null; 130 this.context = Objects.requireNonNull(context); 131 this.apiMode = true; 132 this.docletName = null; 133 this.classLoader = null; 134 this.locale = Locale.getDefault(); 135 } 136 137 void initMessager() { 138 if (!apiMode) 139 return; 140 if (messager == null) { 141 Log log = context.get(Log.logKey); 142 if (log instanceof Messager) { 143 messager = (Messager) log; 144 } else { 145 PrintWriter out = context.get(Log.outKey); 146 messager = (out == null) 147 ? new Messager(context, ProgramName) 148 : new Messager(context, ProgramName, out, out); 149 } 150 } 151 } 152 153 /** 154 * Usage 155 */ 156 @Override 157 void usage() { 158 usage(true); 159 } 160 161 void usage(boolean exit) { 162 usage("main.usage", "-help", null, exit); 163 } 164 165 @Override 166 void Xusage() { 167 Xusage(true); 168 } 169 170 void Xusage(boolean exit) { 171 usage("main.Xusage", "-X", "main.Xusage.foot", exit); 172 } 173 174 private void usage(String main, String option, String foot, boolean exit) { 175 messager.notice(main); 176 // let doclet print usage information (does nothing on error) 177 if (docletClass != null) { 178 String name = doclet.getName(); 179 Set<Option> supportedOptions = doclet.getSupportedOptions(); 180 messager.notice("main.doclet.usage.header", name); 181 Option.Kind myKind = option.equals("-X") 182 ? Option.Kind.EXTENDED 183 : Option.Kind.STANDARD; 184 supportedOptions.stream() 185 .filter(opt -> opt.getKind() == myKind) 186 .forEach(opt -> messager.printNotice(opt.toString())); 187 } 188 if (foot != null) 189 messager.notice(foot); 190 191 if (exit) exit(); 192 } 193 194 /** 195 * Exit 196 */ 197 private void exit() { 198 messager.exit(); 199 } 200 201 /** 202 * Main program - external wrapper 203 */ 204 int begin(String... argv) { 205 // Preprocess @file arguments 206 try { 207 argv = CommandLine.parse(argv); 208 } catch (FileNotFoundException e) { 209 messager.error("main.cant.read", e.getMessage()); 210 exit(); 211 } catch (IOException e) { 212 e.printStackTrace(System.err); 213 exit(); 214 } 215 216 List<String> argList = Arrays.asList(argv); 217 boolean ok = begin(argList, Collections.<JavaFileObject> emptySet()); 218 return ok ? 0 : 1; 219 } 220 221 // Called by 199 API. 222 public boolean begin(Class<?> docletClass, 223 Iterable<String> options, 224 Iterable<? extends JavaFileObject> fileObjects) { 225 this.docletClass = docletClass; 226 List<String> opts = new ArrayList<>(); 227 for (String opt: options) 228 opts.add(opt); 229 return begin(opts, fileObjects); 230 } 231 232 private boolean begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) { 233 234 fileManager = context.get(JavaFileManager.class); 235 if (fileManager == null) { 236 JavacFileManager.preRegister(context); 237 fileManager = context.get(JavaFileManager.class); 238 if (fileManager instanceof BaseFileManager) { 239 ((BaseFileManager) fileManager).autoClose = true; 240 } 241 } 242 // locale and doclet needs to be determined first 243 docletClass = preProcess(fileManager, options); 244 245 if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) { 246 // no need to dispatch to old, safe to init now 247 initMessager(); 248 messager.setLocale(locale); 249 try { 250 doclet = (Doclet) docletClass.newInstance(); 251 } catch (InstantiationException | IllegalAccessException exc) { 252 if (!apiMode) { 253 error("main.could_not_instantiate_class", docletClass); 254 messager.exit(); 255 } 256 throw new ClientCodeException(exc); 257 } 258 } else { 259 if (this.apiMode) { 260 com.sun.tools.javadoc.Start ostart 261 = new com.sun.tools.javadoc.Start(context); 262 return ostart.begin(docletClass, options, fileObjects); 263 } 264 String[] array = options.toArray(new String[options.size()]); 265 return com.sun.tools.javadoc.Main.execute(array) == 0; 266 } 267 268 boolean failed = false; 269 try { 270 failed = !parseAndExecute(options, fileObjects); 271 } catch (Messager.ExitJavadoc exc) { 272 // ignore, we just exit this way 273 } catch (OutOfMemoryError ee) { 274 messager.error("main.out.of.memory"); 275 failed = true; 276 } catch (ClientCodeException e) { 277 // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl 278 throw e; 279 } catch (Error ee) { 280 ee.printStackTrace(System.err); 281 messager.error("main.fatal.error"); 282 failed = true; 283 } catch (Exception ee) { 284 ee.printStackTrace(System.err); 285 messager.error("main.fatal.exception"); 286 failed = true; 287 } finally { 288 if (fileManager != null 289 && fileManager instanceof BaseFileManager 290 && ((BaseFileManager) fileManager).autoClose) { 291 try { 292 fileManager.close(); 293 } catch (IOException ignore) {} 294 } 295 boolean haveErrorWarnings = messager.nerrors() > 0 || 296 (rejectWarnings && messager.nwarnings() > 0); 297 if (failed && !haveErrorWarnings) { 298 // the doclet failed, but nothing reported, flag it!. 299 messager.error("main.unknown.error"); 300 } 301 failed |= haveErrorWarnings; 302 messager.exitNotice(); 303 messager.flush(); 304 } 305 return !failed; 306 } 307 308 /** 309 * Main program - internal 310 */ 311 private boolean parseAndExecute(List<String> argList, 312 Iterable<? extends JavaFileObject> fileObjects) throws IOException { 313 long tm = System.currentTimeMillis(); 314 315 List<String> javaNames = new ArrayList<>(); 316 317 compOpts = Options.instance(context); 318 319 // Make sure no obsolete source/target messages are reported 320 compOpts.put("-Xlint:-options", "-Xlint:-options"); 321 322 doclet.init(locale, messager); 323 parseArgs(argList, javaNames); 324 325 if (fileManager instanceof BaseFileManager) { 326 ((BaseFileManager) fileManager).handleOptions(fileManagerOpts); 327 } 328 329 String platformString = compOpts.get("-release"); 330 331 if (platformString != null) { 332 if (compOpts.isSet("-source")) { 333 usageError("main.release.bootclasspath.conflict", "-source"); 334 } 335 if (fileManagerOpts.containsKey(BOOTCLASSPATH)) { 336 usageError("main.release.bootclasspath.conflict", BOOTCLASSPATH.getText()); 337 } 338 339 PlatformDescription platformDescription = 340 PlatformUtils.lookupPlatformDescription(platformString); 341 342 if (platformDescription == null) { 343 usageError("main.unsupported.release.version", platformString); 344 } 345 346 compOpts.put(SOURCE, platformDescription.getSourceVersion()); 347 348 context.put(PlatformDescription.class, platformDescription); 349 350 Collection<Path> platformCP = platformDescription.getPlatformPath(); 351 352 if (platformCP != null) { 353 if (fileManager instanceof StandardJavaFileManager) { 354 StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager; 355 356 sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP); 357 } else { 358 usageError("main.release.not.standard.file.manager", platformString); 359 } 360 } 361 } 362 363 compOpts.notifyListeners(); 364 365 if (javaNames.isEmpty() && subPackages.isEmpty() && isEmpty(fileObjects)) { 366 usageError("main.No_packages_or_classes_specified"); 367 } 368 369 JavadocTool comp = JavadocTool.make0(context); 370 if (comp == null) return false; 371 372 if (showAccess == null) { 373 setFilter(defaultModifier); 374 } 375 376 DocletEnvironment root = comp.getEnvironment( 377 encoding, 378 showAccess, 379 overviewpath, 380 javaNames, 381 fileObjects, 382 subPackages, 383 excludedPackages, 384 docClasses, 385 quiet); 386 387 // release resources 388 comp = null; 389 390 if (breakiterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) { 391 JavacTrees trees = JavacTrees.instance(context); 392 trees.setBreakIterator(BreakIterator.getSentenceInstance(locale)); 393 } 394 // pass off control to the doclet 395 boolean ok = root != null; 396 if (ok) ok = doclet.run(root); 397 398 // We're done. 399 if (compOpts.get("-verbose") != null) { 400 tm = System.currentTimeMillis() - tm; 401 messager.notice("main.done_in", Long.toString(tm)); 402 } 403 404 return ok; 405 } 406 407 Set<Doclet.Option> docletOptions = null; 408 int handleDocletOptions(int idx, List<String> args, boolean isToolOption) { 409 if (docletOptions == null) { 410 docletOptions = doclet.getSupportedOptions(); 411 } 412 String arg = args.get(idx); 413 414 for (Doclet.Option opt : docletOptions) { 415 if (opt.matches(arg)) { 416 if (args.size() - idx < opt.getArgumentCount()) { 417 usageError("main.requires_argument", arg); 418 } 419 opt.process(arg, args.listIterator(idx + 1)); 420 idx += opt.getArgumentCount(); 421 return idx; 422 } 423 } 424 // check if arg is accepted by the tool before emitting error 425 if (!isToolOption) 426 usageError("main.invalid_flag", arg); 427 return idx; 428 } 429 430 private Class<?> preProcess(JavaFileManager jfm, List<String> argv) { 431 // doclet specifying arguments 432 String userDocletPath = null; 433 String userDocletName = null; 434 435 // Step 1: loop through the args, set locale early on, if found. 436 for (int i = 0 ; i < argv.size() ; i++) { 437 String arg = argv.get(i); 438 if (arg.equals(ToolOption.LOCALE.opt)) { 439 oneArg(argv, i++); 440 String lname = argv.get(i); 441 locale = getLocale(lname); 442 } else if (arg.equals(ToolOption.DOCLET.opt)) { 443 oneArg(argv, i++); 444 if (userDocletName != null) { 445 usageError("main.more_than_one_doclet_specified_0_and_1", 446 userDocletName, argv.get(i)); 447 } 448 if (docletName != null) { 449 usageError("main.more_than_one_doclet_specified_0_and_1", 450 docletName, argv.get(i)); 451 } 452 userDocletName = argv.get(i); 453 } else if (arg.equals(ToolOption.DOCLETPATH.opt)) { 454 oneArg(argv, i++); 455 if (userDocletPath == null) { 456 userDocletPath = argv.get(i); 457 } else { 458 userDocletPath += File.pathSeparator + argv.get(i); 459 } 460 } 461 } 462 // Step 2: a doclet has already been provided, 463 // nothing more to do. 464 if (docletClass != null) { 465 return docletClass; 466 } 467 // Step 3: doclet name specified ? if so find a ClassLoader, 468 // and load it. 469 if (userDocletName != null) { 470 ClassLoader cl = classLoader; 471 if (cl == null) { 472 if (!fileManager.hasLocation(DOCLET_PATH)) { 473 List<File> paths = new ArrayList<>(); 474 if (userDocletPath != null) { 475 for (String pathname : userDocletPath.split(File.pathSeparator)) { 476 paths.add(new File(pathname)); 477 } 478 } 479 try { 480 ((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths); 481 } catch (IOException ioe) { 482 panic("main.doclet_no_classloader_found", ioe); 483 return null; // keep compiler happy 484 } 485 } 486 cl = fileManager.getClassLoader(DOCLET_PATH); 487 if (cl == null) { 488 // despite doclet specified on cmdline no classloader found! 489 panic("main.doclet_no_classloader_found", userDocletName); 490 return null; // keep compiler happy 491 } 492 try { 493 return cl.loadClass(userDocletName); 494 } catch (ClassNotFoundException cnfe) { 495 panic("main.doclet_class_not_found", userDocletName); 496 return null; // keep compiler happy 497 } 498 } 499 } 500 // Step 4: we have a doclet, try loading it, otherwise 501 // return back the standard doclet 502 if (docletName != null) { 503 try { 504 return Class.forName(docletName, true, getClass().getClassLoader()); 505 } catch (ClassNotFoundException cnfe) { 506 panic("main.doclet_class_not_found", userDocletName); 507 return null; // happy compiler, should not happen 508 } 509 } else { 510 return jdk.javadoc.internal.doclets.standard.Standard.class; 511 } 512 } 513 514 private void parseArgs(List<String> args, List<String> javaNames) { 515 for (int i = 0 ; i < args.size() ; i++) { 516 String arg = args.get(i); 517 ToolOption o = ToolOption.get(arg); 518 if (o != null) { 519 // handle a doclet argument that may be needed however 520 // don't increment the index, and allow the tool to consume args 521 handleDocletOptions(i, args, true); 522 523 if (o.hasArg) { 524 oneArg(args, i++); 525 o.process(this, args.get(i)); 526 } else { 527 setOption(arg); 528 o.process(this); 529 } 530 } else if (arg.startsWith("-XD")) { 531 // hidden javac options 532 String s = arg.substring("-XD".length()); 533 int eq = s.indexOf('='); 534 String key = (eq < 0) ? s : s.substring(0, eq); 535 String value = (eq < 0) ? s : s.substring(eq+1); 536 compOpts.put(key, value); 537 } else if (arg.startsWith("-")) { 538 i = handleDocletOptions(i, args, false); 539 } else { 540 javaNames.add(arg); 541 } 542 } 543 } 544 545 private <T> boolean isEmpty(Iterable<T> iter) { 546 return !iter.iterator().hasNext(); 547 } 548 549 /** 550 * Set one arg option. 551 * Error and exit if one argument is not provided. 552 */ 553 private void oneArg(List<String> args, int index) { 554 if ((index + 1) < args.size()) { 555 setOption(args.get(index), args.get(index+1)); 556 } else { 557 usageError("main.requires_argument", args.get(index)); 558 } 559 } 560 561 @Override 562 void usageError(String key, Object... args) { 563 error(key, args); 564 usage(true); 565 } 566 567 // a terminal call, will not return 568 void panic(String key, Object... args) { 569 error(key, args); 570 messager.exit(); 571 } 572 573 void error(String key, Object... args) { 574 messager.error(key, args); 575 } 576 577 /** 578 * indicate an option with no arguments was given. 579 */ 580 private void setOption(String opt) { 581 String[] option = { opt }; 582 options.add(Arrays.asList(option)); 583 } 584 585 /** 586 * indicate an option with one argument was given. 587 */ 588 private void setOption(String opt, String argument) { 589 String[] option = { opt, argument }; 590 options.add(Arrays.asList(option)); 591 } 592 593 /** 594 * indicate an option with the specified list of arguments was given. 595 */ 596 private void setOption(String opt, List<String> arguments) { 597 List<String> args = new ArrayList<>(arguments.size() + 1); 598 args.add(opt); 599 args.addAll(arguments); 600 options.add(args); 601 } 602 603 /** 604 * Get the locale if specified on the command line 605 * else return null and if locale option is not used 606 * then return default locale. 607 */ 608 private Locale getLocale(String localeName) { 609 Locale userlocale = null; 610 if (localeName == null || localeName.isEmpty()) { 611 return Locale.getDefault(); 612 } 613 int firstuscore = localeName.indexOf('_'); 614 int seconduscore = -1; 615 String language = null; 616 String country = null; 617 String variant = null; 618 if (firstuscore == 2) { 619 language = localeName.substring(0, firstuscore); 620 seconduscore = localeName.indexOf('_', firstuscore + 1); 621 if (seconduscore > 0) { 622 if (seconduscore != firstuscore + 3 623 || localeName.length() <= seconduscore + 1) { 624 usageError("main.malformed_locale_name", localeName); 625 return null; 626 } 627 country = localeName.substring(firstuscore + 1, 628 seconduscore); 629 variant = localeName.substring(seconduscore + 1); 630 } else if (localeName.length() == firstuscore + 3) { 631 country = localeName.substring(firstuscore + 1); 632 } else { 633 usageError("main.malformed_locale_name", localeName); 634 return null; 635 } 636 } else if (firstuscore == -1 && localeName.length() == 2) { 637 language = localeName; 638 } else { 639 usageError("main.malformed_locale_name", localeName); 640 return null; 641 } 642 userlocale = searchLocale(language, country, variant); 643 if (userlocale == null) { 644 usageError("main.illegal_locale_name", localeName); 645 return null; 646 } else { 647 return userlocale; 648 } 649 } 650 651 /** 652 * Search the locale for specified language, specified country and 653 * specified variant. 654 */ 655 private Locale searchLocale(String language, String country, 656 String variant) { 657 for (Locale loc : Locale.getAvailableLocales()) { 658 if (loc.getLanguage().equals(language) && 659 (country == null || loc.getCountry().equals(country)) && 660 (variant == null || loc.getVariant().equals(variant))) { 661 return loc; 662 } 663 } 664 return null; 665 } 666} 667