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