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