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