Start.java revision 3769:9b74986367e3
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.IOException; 30import java.io.PrintWriter; 31import java.nio.file.Path; 32import java.text.BreakIterator; 33import java.text.Collator; 34import java.util.ArrayList; 35import java.util.Arrays; 36import java.util.Collection; 37import java.util.Collections; 38import java.util.Comparator; 39import java.util.List; 40import java.util.Locale; 41import java.util.Objects; 42import java.util.Set; 43import java.util.stream.Collectors; 44import java.util.stream.Stream; 45 46import javax.tools.JavaFileManager; 47import javax.tools.JavaFileObject; 48import javax.tools.StandardJavaFileManager; 49import javax.tools.StandardLocation; 50 51import com.sun.tools.javac.api.JavacTrees; 52import com.sun.tools.javac.file.BaseFileManager; 53import com.sun.tools.javac.file.JavacFileManager; 54import com.sun.tools.javac.main.Arguments; 55import com.sun.tools.javac.main.CommandLine; 56import com.sun.tools.javac.main.OptionHelper; 57import com.sun.tools.javac.main.OptionHelper.GrumpyHelper; 58import com.sun.tools.javac.platform.PlatformDescription; 59import com.sun.tools.javac.platform.PlatformUtils; 60import com.sun.tools.javac.util.ClientCodeException; 61import com.sun.tools.javac.util.Context; 62import com.sun.tools.javac.util.Log; 63import com.sun.tools.javac.util.Log.WriterKind; 64import com.sun.tools.javac.util.Options; 65 66import jdk.javadoc.doclet.Doclet; 67import jdk.javadoc.doclet.Doclet.Option; 68import jdk.javadoc.doclet.DocletEnvironment; 69import jdk.javadoc.internal.tool.Main.Result; 70 71import static javax.tools.DocumentationTool.Location.*; 72 73import static com.sun.tools.javac.main.Option.*; 74import static jdk.javadoc.internal.tool.Main.Result.*; 75 76/** 77 * Main program of Javadoc. 78 * Previously named "Main". 79 * 80 * <p><b>This is NOT part of any supported API. 81 * If you write code that depends on this, you do so at your own risk. 82 * This code and its internal interfaces are subject to change or 83 * deletion without notice.</b> 84 * 85 * @author Robert Field 86 * @author Neal Gafter (rewrite) 87 */ 88public class Start extends ToolOption.Helper { 89 90 @SuppressWarnings("deprecation") 91 private static final Class<?> OldStdDoclet = 92 com.sun.tools.doclets.standard.Standard.class; 93 94 private static final Class<?> StdDoclet = 95 jdk.javadoc.doclets.StandardDoclet.class; 96 /** Context for this invocation. */ 97 private final Context context; 98 99 private static final String ProgramName = "javadoc"; 100 101 private Messager messager; 102 103 private final String docletName; 104 105 private final ClassLoader classLoader; 106 107 private Class<?> docletClass; 108 109 private Doclet doclet; 110 111 // used to determine the locale for the messager 112 private Locale locale; 113 114 115 /** 116 * In API mode, exceptions thrown while calling the doclet are 117 * propagated using ClientCodeException. 118 */ 119 private boolean apiMode; 120 121 private JavaFileManager fileManager; 122 123 Start() { 124 this(null, null, null, null, null, null); 125 } 126 127 Start(PrintWriter outWriter, PrintWriter errWriter) { 128 this(null, null, outWriter, errWriter, null, null); 129 } 130 131 Start(Context context, String programName, 132 PrintWriter outWriter, PrintWriter errWriter, 133 String docletName, ClassLoader classLoader) { 134 this.context = context == null ? new Context() : context; 135 String pname = programName == null ? ProgramName : programName; 136 this.messager = (outWriter == null && errWriter == null) 137 ? new Messager(this.context, pname) 138 : new Messager(this.context, pname, outWriter, errWriter); 139 this.docletName = docletName; 140 this.classLoader = classLoader; 141 this.docletClass = null; 142 this.locale = Locale.getDefault(); 143 } 144 145 public Start(Context context) { 146 this.docletClass = null; 147 this.context = Objects.requireNonNull(context); 148 this.apiMode = true; 149 this.docletName = null; 150 this.classLoader = null; 151 this.locale = Locale.getDefault(); 152 } 153 154 void initMessager() { 155 if (!apiMode) 156 return; 157 if (messager == null) { 158 Log log = context.get(Log.logKey); 159 if (log instanceof Messager) { 160 messager = (Messager) log; 161 } else { 162 PrintWriter out = context.get(Log.errKey); 163 messager = (out == null) 164 ? new Messager(context, ProgramName) 165 : new Messager(context, ProgramName, out, out); 166 } 167 } 168 } 169 170 /** 171 * Usage 172 */ 173 @Override 174 void usage() { 175 usage("main.usage", OptionKind.STANDARD, "main.usage.foot"); 176 } 177 178 @Override 179 void Xusage() { 180 usage("main.Xusage", OptionKind.EXTENDED, "main.Xusage.foot"); 181 } 182 183 private void usage(String headerKey, OptionKind kind, String footerKey) { 184 messager.notice(headerKey); 185 showToolOptions(kind); 186 187 // let doclet print usage information 188 if (docletClass != null) { 189 String name = doclet.getName(); 190 messager.notice("main.doclet.usage.header", name); 191 showDocletOptions(kind == OptionKind.EXTENDED 192 ? Option.Kind.EXTENDED 193 : Option.Kind.STANDARD); 194 } 195 if (footerKey != null) 196 messager.notice(footerKey); 197 } 198 199 void showToolOptions(OptionKind kind) { 200 Comparator<ToolOption> comp = new Comparator<ToolOption>() { 201 final Collator collator = Collator.getInstance(Locale.US); 202 { collator.setStrength(Collator.PRIMARY); } 203 204 @Override 205 public int compare(ToolOption o1, ToolOption o2) { 206 return collator.compare(o1.primaryName, o2.primaryName); 207 } 208 }; 209 210 Stream.of(ToolOption.values()) 211 .filter(opt -> opt.kind == kind) 212 .sorted(comp) 213 .forEach(opt -> showToolOption(opt)); 214 } 215 216 void showToolOption(ToolOption option) { 217 List<String> names = option.getNames(); 218 String parameters; 219 if (option.hasArg || option.primaryName.endsWith(":")) { 220 String sep = (option == ToolOption.J) || option.primaryName.endsWith(":") ? "" : " "; 221 parameters = sep + option.getParameters(messager); 222 } else { 223 parameters = ""; 224 } 225 String description = option.getDescription(messager); 226 showUsage(names, parameters, description); 227 } 228 229 void showDocletOptions(Option.Kind kind) { 230 Comparator<Doclet.Option> comp = new Comparator<Doclet.Option>() { 231 final Collator collator = Collator.getInstance(Locale.US); 232 { collator.setStrength(Collator.PRIMARY); } 233 234 @Override 235 public int compare(Doclet.Option o1, Doclet.Option o2) { 236 return collator.compare(o1.getName(), o2.getName()); 237 } 238 }; 239 240 doclet.getSupportedOptions().stream() 241 .filter(opt -> opt.getKind() == kind) 242 .sorted(comp) 243 .forEach(opt -> showDocletOption(opt)); 244 } 245 246 void showDocletOption(Doclet.Option option) { 247 List<String> names = Arrays.asList(option.getName()); 248 String parameters; 249 if (option.getArgumentCount() > 0 || option.getName().endsWith(":")) { 250 String sep = option.getName().endsWith(":") ? "" : " "; 251 parameters = sep + option.getParameters(); 252 } else { 253 parameters = ""; 254 } 255 String description = option.getDescription(); 256 showUsage(names, parameters, description); 257 } 258 259 // The following constants are intended to format the output to 260 // be similar to that of the java launcher: i.e. "java -help". 261 262 /** The indent for the option synopsis. */ 263 private static final String SMALL_INDENT = " "; 264 /** The automatic indent for the description. */ 265 private static final String LARGE_INDENT = " "; 266 /** The space allowed for the synopsis, if the description is to be shown on the same line. */ 267 private static final int DEFAULT_SYNOPSIS_WIDTH = 13; 268 /** The nominal maximum line length, when seeing if text will fit on a line. */ 269 private static final int DEFAULT_MAX_LINE_LENGTH = 80; 270 /** The format for a single-line help entry. */ 271 private static final String COMPACT_FORMAT = SMALL_INDENT + "%-" + DEFAULT_SYNOPSIS_WIDTH + "s %s"; 272 273 void showUsage(List<String> names, String parameters, String description) { 274 String synopses = names.stream() 275 .map(s -> s + parameters) 276 .collect(Collectors.joining(", ")); 277 // If option synopses and description fit on a single line of reasonable length, 278 // display using COMPACT_FORMAT 279 if (synopses.length() < DEFAULT_SYNOPSIS_WIDTH 280 && !description.contains("\n") 281 && (SMALL_INDENT.length() + DEFAULT_SYNOPSIS_WIDTH + 1 + description.length() <= DEFAULT_MAX_LINE_LENGTH)) { 282 messager.printNotice(String.format(COMPACT_FORMAT, synopses, description)); 283 return; 284 } 285 286 // If option synopses fit on a single line of reasonable length, show that; 287 // otherwise, show 1 per line 288 if (synopses.length() <= DEFAULT_MAX_LINE_LENGTH) { 289 messager.printNotice(SMALL_INDENT + synopses); 290 } else { 291 for (String name: names) { 292 messager.printNotice(SMALL_INDENT + name + parameters); 293 } 294 } 295 296 // Finally, show the description 297 messager.printNotice(LARGE_INDENT + description.replace("\n", "\n" + LARGE_INDENT)); 298 } 299 300 301 /** 302 * Main program - external wrapper. In order to maintain backward 303 * CLI compatibility, we dispatch to the old tool or the old doclet's 304 * Start mechanism, based on the options present on the command line 305 * with the following precedence: 306 * 1. presence of -Xold, dispatch to old tool 307 * 2. doclet variant, if old, dispatch to old Start 308 * 3. taglet variant, if old, dispatch to old Start 309 * 310 * Thus the presence of -Xold switches the tool, soon after command files 311 * if any, are expanded, this is performed here, noting that the messager 312 * is available at this point in time. 313 * The doclet/taglet tests are performed in the begin method, further on, 314 * this is to minimize argument processing and most importantly the impact 315 * of class loader creation, needed to detect the doclet/taglet class variants. 316 */ 317 @SuppressWarnings("deprecation") 318 Result begin(String... argv) { 319 // Preprocess @file arguments 320 try { 321 argv = CommandLine.parse(argv); 322 } catch (IOException e) { 323 error("main.cant.read", e.getMessage()); 324 return ERROR; 325 } 326 327 if (argv.length > 0 && "-Xold".equals(argv[0])) { 328 warn("main.legacy_api"); 329 String[] nargv = Arrays.copyOfRange(argv, 1, argv.length); 330 int rc = com.sun.tools.javadoc.Main.execute( 331 messager.programName, 332 messager.getWriter(WriterKind.ERROR), 333 messager.getWriter(WriterKind.WARNING), 334 messager.getWriter(WriterKind.NOTICE), 335 "com.sun.tools.doclets.standard.Standard", 336 nargv); 337 return (rc == 0) ? OK : ERROR; 338 } 339 return begin(Arrays.asList(argv), Collections.<JavaFileObject> emptySet()); 340 } 341 342 // Called by 199 API. 343 public boolean begin(Class<?> docletClass, 344 Iterable<String> options, 345 Iterable<? extends JavaFileObject> fileObjects) { 346 this.docletClass = docletClass; 347 List<String> opts = new ArrayList<>(); 348 for (String opt: options) 349 opts.add(opt); 350 351 return begin(opts, fileObjects).isOK(); 352 } 353 354 @SuppressWarnings("deprecation") 355 private Result begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) { 356 fileManager = context.get(JavaFileManager.class); 357 if (fileManager == null) { 358 JavacFileManager.preRegister(context); 359 fileManager = context.get(JavaFileManager.class); 360 if (fileManager instanceof BaseFileManager) { 361 ((BaseFileManager) fileManager).autoClose = true; 362 } 363 } 364 365 // locale, doclet and maybe taglet, needs to be determined first 366 try { 367 docletClass = preprocess(fileManager, options); 368 } catch (ToolException te) { 369 if (!te.result.isOK()) { 370 if (te.message != null) { 371 messager.printError(te.message); 372 } 373 Throwable t = te.getCause(); 374 dumpStack(t == null ? te : t); 375 } 376 return te.result; 377 } catch (OptionException oe) { 378 if (oe.message != null) { 379 messager.printError(oe.message); 380 } 381 oe.m.run(); 382 Throwable t = oe.getCause(); 383 dumpStack(t == null ? oe : t); 384 return oe.result; 385 } 386 if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) { 387 // no need to dispatch to old, safe to init now 388 initMessager(); 389 messager.setLocale(locale); 390 try { 391 Object o = docletClass.getConstructor().newInstance(); 392 doclet = (Doclet) o; 393 } catch (ReflectiveOperationException exc) { 394 if (apiMode) { 395 throw new ClientCodeException(exc); 396 } 397 error("main.could_not_instantiate_class", docletClass); 398 return ERROR; 399 } 400 } else { 401 if (apiMode) { 402 com.sun.tools.javadoc.main.Start ostart 403 = new com.sun.tools.javadoc.main.Start(context); 404 return ostart.begin(docletClass, options, fileObjects) 405 ? OK 406 : ERROR; 407 } 408 warn("main.legacy_api"); 409 String[] array = options.toArray(new String[options.size()]); 410 int rc = com.sun.tools.javadoc.Main.execute( 411 messager.programName, 412 messager.getWriter(WriterKind.ERROR), 413 messager.getWriter(WriterKind.WARNING), 414 messager.getWriter(WriterKind.NOTICE), 415 "com.sun.tools.doclets.standard.Standard", 416 array); 417 return (rc == 0) ? OK : ERROR; 418 } 419 420 Result result = OK; 421 try { 422 result = parseAndExecute(options, fileObjects) 423 ? OK 424 : ERROR; 425 } catch (com.sun.tools.javac.main.Option.InvalidValueException e) { 426 messager.printError(e.getMessage()); 427 Throwable t = e.getCause(); 428 dumpStack(t == null ? e : t); 429 return ERROR; 430 } catch (OptionException toe) { 431 if (toe.message != null) 432 messager.printError(toe.message); 433 434 toe.m.run(); 435 Throwable t = toe.getCause(); 436 dumpStack(t == null ? toe : t); 437 return toe.result; 438 } catch (ToolException exc) { 439 if (exc.message != null) { 440 messager.printError(exc.message); 441 } 442 Throwable t = exc.getCause(); 443 if (result == ABNORMAL) { 444 reportInternalError(t == null ? exc : t); 445 } else { 446 dumpStack(t == null ? exc : t); 447 } 448 return exc.result; 449 } catch (OutOfMemoryError ee) { 450 error("main.out.of.memory"); 451 result = SYSERR; 452 dumpStack(ee); 453 } catch (ClientCodeException e) { 454 // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl 455 throw e; 456 } catch (Error | Exception ee) { 457 error("main.fatal.error", ee); 458 reportInternalError(ee); 459 result = ABNORMAL; 460 } finally { 461 if (fileManager != null 462 && fileManager instanceof BaseFileManager 463 && ((BaseFileManager) fileManager).autoClose) { 464 try { 465 fileManager.close(); 466 } catch (IOException ignore) {} 467 } 468 boolean haveErrorWarnings = messager.hasErrors() 469 || (rejectWarnings && messager.hasWarnings()); 470 if (!result.isOK() && !haveErrorWarnings) { 471 // the doclet failed, but nothing reported, flag it!. 472 error("main.unknown.error"); 473 } 474 if (haveErrorWarnings && result.isOK()) { 475 result = ERROR; 476 } 477 messager.printErrorWarningCounts(); 478 messager.flush(); 479 } 480 return result; 481 } 482 483 private void reportInternalError(Throwable t) { 484 messager.printErrorUsingKey("doclet.internal.report.bug"); 485 dumpStack(true, t); 486 } 487 488 private void dumpStack(Throwable t) { 489 dumpStack(false, t); 490 } 491 492 private void dumpStack(boolean enabled, Throwable t) { 493 if (t != null && (enabled || dumpOnError)) { 494 t.printStackTrace(System.err); 495 } 496 } 497 498 /** 499 * Main program - internal 500 */ 501 @SuppressWarnings("unchecked") 502 private boolean parseAndExecute(List<String> argList, Iterable<? extends JavaFileObject> fileObjects) 503 throws ToolException, OptionException, com.sun.tools.javac.main.Option.InvalidValueException { 504 long tm = System.currentTimeMillis(); 505 506 List<String> javaNames = new ArrayList<>(); 507 508 compOpts = Options.instance(context); 509 510 // Make sure no obsolete source/target messages are reported 511 try { 512 com.sun.tools.javac.main.Option.XLINT_CUSTOM.process(getOptionHelper(), "-Xlint:-options"); 513 } catch (com.sun.tools.javac.main.Option.InvalidValueException ignore) { 514 } 515 516 doclet.init(locale, messager); 517 parseArgs(argList, javaNames); 518 519 Arguments arguments = Arguments.instance(context); 520 arguments.init(ProgramName); 521 arguments.allowEmpty(); 522 arguments.validate(); 523 524 if (fileManager instanceof BaseFileManager) { 525 ((BaseFileManager) fileManager).handleOptions(fileManagerOpts); 526 } 527 528 String platformString = compOpts.get("--release"); 529 530 if (platformString != null) { 531 if (compOpts.isSet("-source")) { 532 String text = messager.getText("main.release.bootclasspath.conflict", "-source"); 533 throw new ToolException(CMDERR, text); 534 } 535 if (fileManagerOpts.containsKey(BOOT_CLASS_PATH)) { 536 String text = messager.getText("main.release.bootclasspath.conflict", 537 BOOT_CLASS_PATH.getPrimaryName()); 538 throw new ToolException(CMDERR, text); 539 } 540 541 PlatformDescription platformDescription = 542 PlatformUtils.lookupPlatformDescription(platformString); 543 544 if (platformDescription == null) { 545 String text = messager.getText("main.unsupported.release.version", platformString); 546 throw new IllegalArgumentException(text); 547 } 548 549 compOpts.put(SOURCE, platformDescription.getSourceVersion()); 550 551 context.put(PlatformDescription.class, platformDescription); 552 553 Collection<Path> platformCP = platformDescription.getPlatformPath(); 554 555 if (platformCP != null) { 556 if (fileManager instanceof StandardJavaFileManager) { 557 StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager; 558 try { 559 sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP); 560 } catch (IOException ioe) { 561 throw new ToolException(SYSERR, ioe.getMessage(), ioe); 562 } 563 } else { 564 String text = messager.getText("main.release.not.standard.file.manager", 565 platformString); 566 throw new ToolException(ABNORMAL, text); 567 } 568 } 569 } 570 571 compOpts.notifyListeners(); 572 List<String> modules = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.MODULE, 573 s -> Collections.EMPTY_LIST); 574 575 if (modules.isEmpty()) { 576 List<String> subpkgs = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.SUBPACKAGES, 577 s -> Collections.EMPTY_LIST); 578 if (subpkgs.isEmpty()) { 579 if (javaNames.isEmpty() && isEmpty(fileObjects)) { 580 String text = messager.getText("main.No_modules_packages_or_classes_specified"); 581 throw new ToolException(CMDERR, text); 582 } 583 } 584 } 585 586 JavadocTool comp = JavadocTool.make0(context); 587 if (comp == null) return false; 588 589 DocletEnvironment docEnv = comp.getEnvironment(jdtoolOpts, 590 javaNames, 591 fileObjects); 592 593 // release resources 594 comp = null; 595 596 if (breakiterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) { 597 JavacTrees trees = JavacTrees.instance(context); 598 trees.setBreakIterator(BreakIterator.getSentenceInstance(locale)); 599 } 600 // pass off control to the doclet 601 boolean ok = docEnv != null; 602 if (ok) ok = doclet.run(docEnv); 603 604 // We're done. 605 if (compOpts.get("-verbose") != null) { 606 tm = System.currentTimeMillis() - tm; 607 messager.notice("main.done_in", Long.toString(tm)); 608 } 609 610 return ok; 611 } 612 613 Set<Doclet.Option> docletOptions = null; 614 int handleDocletOptions(int idx, List<String> args, boolean isToolOption) 615 throws OptionException { 616 if (docletOptions == null) { 617 docletOptions = doclet.getSupportedOptions(); 618 } 619 String arg = args.get(idx); 620 String argBase, argVal; 621 if (arg.startsWith("--") && arg.contains("=")) { 622 int sep = arg.indexOf("="); 623 argBase = arg.substring(0, sep); 624 argVal = arg.substring(sep + 1); 625 } else { 626 argBase = arg; 627 argVal = null; 628 } 629 String text = null; 630 for (Doclet.Option opt : docletOptions) { 631 if (opt.matches(argBase)) { 632 if (argVal != null) { 633 switch (opt.getArgumentCount()) { 634 case 0: 635 text = messager.getText("main.unnecessary_arg_provided", argBase); 636 throw new OptionException(ERROR, this::usage, text); 637 case 1: 638 opt.process(arg, Arrays.asList(argVal).listIterator()); 639 break; 640 default: 641 text = messager.getText("main.only_one_argument_with_equals", argBase); 642 throw new OptionException(ERROR, this::usage, text); 643 } 644 } else { 645 if (args.size() - idx -1 < opt.getArgumentCount()) { 646 text = messager.getText("main.requires_argument", arg); 647 throw new OptionException(ERROR, this::usage, text); 648 } 649 opt.process(arg, args.listIterator(idx + 1)); 650 idx += opt.getArgumentCount(); 651 } 652 return idx; 653 } 654 } 655 // check if arg is accepted by the tool before emitting error 656 if (!isToolOption) { 657 text = messager.getText("main.invalid_flag", arg); 658 throw new OptionException(ERROR, this::usage, text); 659 } 660 return idx; 661 } 662 663 private Class<?> preprocess(JavaFileManager jfm, 664 List<String> argv) throws ToolException, OptionException { 665 // doclet specifying arguments 666 String userDocletPath = null; 667 String userDocletName = null; 668 669 // taglet specifying arguments, since tagletpath is a doclet 670 // functionality, assume they are repeated and inspect all. 671 List<File> userTagletPath = new ArrayList<>(); 672 List<String> userTagletNames = new ArrayList<>(); 673 674 // Step 1: loop through the args, set locale early on, if found. 675 for (int i = 0 ; i < argv.size() ; i++) { 676 String arg = argv.get(i); 677 if (arg.equals(ToolOption.DUMPONERROR.primaryName)) { 678 dumpOnError = true; 679 } else if (arg.equals(ToolOption.LOCALE.primaryName)) { 680 checkOneArg(argv, i++); 681 String lname = argv.get(i); 682 locale = getLocale(lname); 683 } else if (arg.equals(ToolOption.DOCLET.primaryName)) { 684 checkOneArg(argv, i++); 685 if (userDocletName != null) { 686 if (apiMode) { 687 throw new IllegalArgumentException("More than one doclet specified (" + 688 userDocletName + " and " + argv.get(i) + ")."); 689 } 690 String text = messager.getText("main.more_than_one_doclet_specified_0_and_1", 691 userDocletName, argv.get(i)); 692 throw new ToolException(CMDERR, text); 693 } 694 if (docletName != null) { 695 if (apiMode) { 696 throw new IllegalArgumentException("More than one doclet specified (" + 697 docletName + " and " + argv.get(i) + ")."); 698 } 699 String text = messager.getText("main.more_than_one_doclet_specified_0_and_1", 700 docletName, argv.get(i)); 701 throw new ToolException(CMDERR, text); 702 } 703 userDocletName = argv.get(i); 704 } else if (arg.equals(ToolOption.DOCLETPATH.primaryName)) { 705 checkOneArg(argv, i++); 706 if (userDocletPath == null) { 707 userDocletPath = argv.get(i); 708 } else { 709 userDocletPath += File.pathSeparator + argv.get(i); 710 } 711 } else if ("-taglet".equals(arg)) { 712 userTagletNames.add(argv.get(i + 1)); 713 } else if ("-tagletpath".equals(arg)) { 714 for (String pathname : argv.get(i + 1).split(File.pathSeparator)) { 715 userTagletPath.add(new File(pathname)); 716 } 717 } 718 } 719 720 // Step 2: a doclet is provided, nothing more to do. 721 if (docletClass != null) { 722 return docletClass; 723 } 724 725 // Step 3: doclet name specified ? if so find a ClassLoader, 726 // and load it. 727 if (userDocletName != null) { 728 ClassLoader cl = classLoader; 729 if (cl == null) { 730 if (!fileManager.hasLocation(DOCLET_PATH)) { 731 List<File> paths = new ArrayList<>(); 732 if (userDocletPath != null) { 733 for (String pathname : userDocletPath.split(File.pathSeparator)) { 734 paths.add(new File(pathname)); 735 } 736 } 737 try { 738 ((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths); 739 } catch (IOException ioe) { 740 if (apiMode) { 741 throw new IllegalArgumentException("Could not set location for " + 742 userDocletPath, ioe); 743 } 744 String text = messager.getText("main.doclet_could_not_set_location", 745 userDocletPath); 746 throw new ToolException(CMDERR, text, ioe); 747 } 748 } 749 cl = fileManager.getClassLoader(DOCLET_PATH); 750 if (cl == null) { 751 // despite doclet specified on cmdline no classloader found! 752 if (apiMode) { 753 throw new IllegalArgumentException("Could not obtain classloader to load " 754 + userDocletPath); 755 } 756 String text = messager.getText("main.doclet_no_classloader_found", 757 userDocletName); 758 throw new ToolException(CMDERR, text); 759 } 760 } 761 try { 762 Class<?> klass = cl.loadClass(userDocletName); 763 return klass; 764 } catch (ClassNotFoundException cnfe) { 765 if (apiMode) { 766 throw new IllegalArgumentException("Cannot find doclet class " + userDocletName, 767 cnfe); 768 } 769 String text = messager.getText("main.doclet_class_not_found", userDocletName); 770 throw new ToolException(CMDERR, text, cnfe); 771 } 772 } 773 774 // Step 4: we have a doclet, try loading it 775 if (docletName != null) { 776 try { 777 return Class.forName(docletName, true, getClass().getClassLoader()); 778 } catch (ClassNotFoundException cnfe) { 779 if (apiMode) { 780 throw new IllegalArgumentException("Cannot find doclet class " + userDocletName); 781 } 782 String text = messager.getText("main.doclet_class_not_found", userDocletName); 783 throw new ToolException(CMDERR, text, cnfe); 784 } 785 } 786 787 // Step 5: we don't have a doclet specified, do we have taglets ? 788 if (!userTagletNames.isEmpty() && hasOldTaglet(userTagletNames, userTagletPath)) { 789 // found a bogey, return the old doclet 790 return OldStdDoclet; 791 } 792 793 // finally 794 return StdDoclet; 795 } 796 797 /* 798 * This method returns true iff it finds a legacy taglet, but for 799 * all other conditions including errors it returns false, allowing 800 * nature to take its own course. 801 */ 802 @SuppressWarnings("deprecation") 803 private boolean hasOldTaglet(List<String> tagletNames, List<File> tagletPaths) throws ToolException { 804 if (!fileManager.hasLocation(TAGLET_PATH)) { 805 try { 806 ((StandardJavaFileManager) fileManager).setLocation(TAGLET_PATH, tagletPaths); 807 } catch (IOException ioe) { 808 String text = messager.getText("main.doclet_could_not_set_location", tagletPaths); 809 throw new ToolException(CMDERR, text, ioe); 810 } 811 } 812 ClassLoader cl = fileManager.getClassLoader(TAGLET_PATH); 813 if (cl == null) { 814 // no classloader found! 815 String text = messager.getText("main.doclet_no_classloader_found", tagletNames.get(0)); 816 throw new ToolException(CMDERR, text); 817 } 818 for (String tagletName : tagletNames) { 819 try { 820 Class<?> klass = cl.loadClass(tagletName); 821 if (com.sun.tools.doclets.Taglet.class.isAssignableFrom(klass)) { 822 return true; 823 } 824 } catch (ClassNotFoundException cnfe) { 825 String text = messager.getText("main.doclet_class_not_found", tagletName); 826 throw new ToolException(CMDERR, text, cnfe); 827 } 828 } 829 return false; 830 } 831 832 private void parseArgs(List<String> args, List<String> javaNames) throws ToolException, 833 OptionException, com.sun.tools.javac.main.Option.InvalidValueException { 834 for (int i = 0 ; i < args.size() ; i++) { 835 String arg = args.get(i); 836 ToolOption o = ToolOption.get(arg); 837 if (o != null) { 838 // handle a doclet argument that may be needed however 839 // don't increment the index, and allow the tool to consume args 840 handleDocletOptions(i, args, true); 841 if (o.hasArg) { 842 if (arg.startsWith("--") && arg.contains("=")) { 843 o.process(this, arg.substring(arg.indexOf('=') + 1)); 844 } else { 845 checkOneArg(args, i++); 846 o.process(this, args.get(i)); 847 } 848 } else if (o.hasSuffix) { 849 o.process(this, arg); 850 } else { 851 o.process(this); 852 } 853 } else if (arg.startsWith("-XD")) { 854 // hidden javac options 855 String s = arg.substring("-XD".length()); 856 int eq = s.indexOf('='); 857 String key = (eq < 0) ? s : s.substring(0, eq); 858 String value = (eq < 0) ? s : s.substring(eq+1); 859 compOpts.put(key, value); 860 } else if (arg.startsWith("-")) { 861 i = handleDocletOptions(i, args, false); 862 } else { 863 javaNames.add(arg); 864 } 865 } 866 } 867 868 private <T> boolean isEmpty(Iterable<T> iter) { 869 return !iter.iterator().hasNext(); 870 } 871 872 /** 873 * Check the one arg option. 874 * Error and exit if one argument is not provided. 875 */ 876 private void checkOneArg(List<String> args, int index) throws OptionException { 877 if ((index + 1) >= args.size() || args.get(index + 1).startsWith("-d")) { 878 String text = messager.getText("main.requires_argument", args.get(index)); 879 throw new OptionException(CMDERR, this::usage, text); 880 } 881 } 882 883 void error(String key, Object... args) { 884 messager.printErrorUsingKey(key, args); 885 } 886 887 void warn(String key, Object... args) { 888 messager.printWarningUsingKey(key, args); 889 } 890 891 /** 892 * Get the locale if specified on the command line 893 * else return null and if locale option is not used 894 * then return default locale. 895 */ 896 private Locale getLocale(String localeName) throws ToolException { 897 Locale userlocale = null; 898 if (localeName == null || localeName.isEmpty()) { 899 return Locale.getDefault(); 900 } 901 int firstuscore = localeName.indexOf('_'); 902 int seconduscore = -1; 903 String language = null; 904 String country = null; 905 String variant = null; 906 if (firstuscore == 2) { 907 language = localeName.substring(0, firstuscore); 908 seconduscore = localeName.indexOf('_', firstuscore + 1); 909 if (seconduscore > 0) { 910 if (seconduscore != firstuscore + 3 911 || localeName.length() <= seconduscore + 1) { 912 String text = messager.getText("main.malformed_locale_name", localeName); 913 throw new ToolException(CMDERR, text); 914 } 915 country = localeName.substring(firstuscore + 1, 916 seconduscore); 917 variant = localeName.substring(seconduscore + 1); 918 } else if (localeName.length() == firstuscore + 3) { 919 country = localeName.substring(firstuscore + 1); 920 } else { 921 String text = messager.getText("main.malformed_locale_name", localeName); 922 throw new ToolException(CMDERR, text); 923 } 924 } else if (firstuscore == -1 && localeName.length() == 2) { 925 language = localeName; 926 } else { 927 String text = messager.getText("main.malformed_locale_name", localeName); 928 throw new ToolException(CMDERR, text); 929 } 930 userlocale = searchLocale(language, country, variant); 931 if (userlocale == null) { 932 String text = messager.getText("main.illegal_locale_name", localeName); 933 throw new ToolException(CMDERR, text); 934 } else { 935 return userlocale; 936 } 937 } 938 939 /** 940 * Search the locale for specified language, specified country and 941 * specified variant. 942 */ 943 private Locale searchLocale(String language, String country, 944 String variant) { 945 for (Locale loc : Locale.getAvailableLocales()) { 946 if (loc.getLanguage().equals(language) && 947 (country == null || loc.getCountry().equals(country)) && 948 (variant == null || loc.getVariant().equals(variant))) { 949 return loc; 950 } 951 } 952 return null; 953 } 954 955 @Override 956 OptionHelper getOptionHelper() { 957 return new GrumpyHelper(messager) { 958 @Override 959 public String get(com.sun.tools.javac.main.Option option) { 960 return compOpts.get(option); 961 } 962 963 @Override 964 public void put(String name, String value) { 965 compOpts.put(name, value); 966 } 967 968 @Override 969 public void remove(String name) { 970 compOpts.remove(name); 971 } 972 973 @Override 974 public boolean handleFileManagerOption(com.sun.tools.javac.main.Option option, String value) { 975 fileManagerOpts.put(option, value); 976 return true; 977 } 978 }; 979 } 980 981 @Override 982 String getLocalizedMessage(String msg, Object... args) { 983 return messager.getText(msg, args); 984 } 985} 986