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