Start.java revision 3673:8bf23828bb2f
11553Srgrimes/* 21553Srgrimes * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. 31553Srgrimes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 41553Srgrimes * 51553Srgrimes * This code is free software; you can redistribute it and/or modify it 61553Srgrimes * under the terms of the GNU General Public License version 2 only, as 71553Srgrimes * published by the Free Software Foundation. Oracle designates this 81553Srgrimes * particular file as subject to the "Classpath" exception as provided 91553Srgrimes * by Oracle in the LICENSE file that accompanied this code. 101553Srgrimes * 111553Srgrimes * This code is distributed in the hope that it will be useful, but WITHOUT 121553Srgrimes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 131553Srgrimes * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 141553Srgrimes * version 2 for more details (a copy is included in the LICENSE file that 151553Srgrimes * accompanied this code). 161553Srgrimes * 171553Srgrimes * You should have received a copy of the GNU General Public License version 181553Srgrimes * 2 along with this work; if not, write to the Free Software Foundation, 191553Srgrimes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 201553Srgrimes * 211553Srgrimes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 221553Srgrimes * or visit www.oracle.com if you need additional information or have any 231553Srgrimes * questions. 241553Srgrimes */ 251553Srgrimes 261553Srgrimespackage jdk.javadoc.internal.tool; 271553Srgrimes 281553Srgrimesimport java.io.File; 291553Srgrimesimport java.io.FileNotFoundException; 301553Srgrimesimport java.io.IOException; 311553Srgrimesimport java.io.PrintWriter; 321553Srgrimesimport java.nio.file.Path; 331553Srgrimesimport java.text.BreakIterator; 341553Srgrimesimport java.text.Collator; 351553Srgrimesimport java.util.ArrayList; 361553Srgrimesimport java.util.Arrays; 371553Srgrimesimport java.util.Collection; 381553Srgrimesimport java.util.Collections; 391553Srgrimesimport java.util.Comparator; 401553Srgrimesimport java.util.List; 411553Srgrimesimport java.util.Locale; 4215648Sjoergimport java.util.Objects; 431553Srgrimesimport java.util.Set; 441553Srgrimesimport java.util.stream.Collectors; 451553Srgrimesimport java.util.stream.Stream; 461553Srgrimes 471553Srgrimesimport javax.tools.JavaFileManager; 481553Srgrimesimport javax.tools.JavaFileObject; 491553Srgrimesimport javax.tools.StandardJavaFileManager; 501553Srgrimesimport javax.tools.StandardLocation; 511553Srgrimes 521553Srgrimesimport com.sun.tools.javac.api.JavacTrees; 531553Srgrimesimport com.sun.tools.javac.file.BaseFileManager; 541553Srgrimesimport com.sun.tools.javac.file.JavacFileManager; 551553Srgrimesimport com.sun.tools.javac.main.CommandLine; 561553Srgrimesimport com.sun.tools.javac.main.OptionHelper; 571553Srgrimesimport com.sun.tools.javac.main.OptionHelper.GrumpyHelper; 581553Srgrimesimport com.sun.tools.javac.platform.PlatformDescription; 591553Srgrimesimport com.sun.tools.javac.platform.PlatformUtils; 601553Srgrimesimport com.sun.tools.javac.util.ClientCodeException; 611553Srgrimesimport com.sun.tools.javac.util.Context; 621553Srgrimesimport com.sun.tools.javac.util.Log; 631553Srgrimesimport com.sun.tools.javac.util.Options; 641553Srgrimes 651553Srgrimesimport jdk.javadoc.doclet.Doclet; 661553Srgrimesimport jdk.javadoc.doclet.Doclet.Option; 671553Srgrimesimport jdk.javadoc.doclet.DocletEnvironment; 6827748Simpimport jdk.javadoc.internal.doclets.toolkit.Resources; 691553Srgrimes 701553Srgrimesimport static javax.tools.DocumentationTool.Location.*; 7127748Simp 721553Srgrimesimport static com.sun.tools.javac.main.Option.*; 731553Srgrimes 741553Srgrimes/** 751553Srgrimes * Main program of Javadoc. 761553Srgrimes * Previously named "Main". 771553Srgrimes * 781553Srgrimes * <p><b>This is NOT part of any supported API. 791553Srgrimes * If you write code that depends on this, you do so at your own risk. 801553Srgrimes * This code and its internal interfaces are subject to change or 811553Srgrimes * deletion without notice.</b> 821553Srgrimes * 831553Srgrimes * @author Robert Field 841553Srgrimes * @author Neal Gafter (rewrite) 851553Srgrimes */ 861553Srgrimespublic class Start extends ToolOption.Helper { 871553Srgrimes 881553Srgrimes @SuppressWarnings("deprecation") 891553Srgrimes private static final Class<?> OldStdDoclet = 901553Srgrimes com.sun.tools.doclets.standard.Standard.class; 911553Srgrimes 921553Srgrimes private static final Class<?> StdDoclet = 931553Srgrimes jdk.javadoc.doclets.StandardDoclet.class; 941553Srgrimes /** Context for this invocation. */ 951553Srgrimes private final Context context; 961553Srgrimes 9727748Simp private static final String ProgramName = "javadoc"; 981553Srgrimes 991553Srgrimes private Messager messager; 1001553Srgrimes 1011553Srgrimes private final String docletName; 1021553Srgrimes 1031553Srgrimes private final ClassLoader classLoader; 1041553Srgrimes 1051553Srgrimes private Class<?> docletClass; 1061553Srgrimes 1071553Srgrimes private Doclet doclet; 1081553Srgrimes 1091553Srgrimes // used to determine the locale for the messager 1101553Srgrimes private Locale locale; 1111553Srgrimes 1121553Srgrimes 1131553Srgrimes /** 1141553Srgrimes * In API mode, exceptions thrown while calling the doclet are 1151553Srgrimes * propagated using ClientCodeException. 1161553Srgrimes */ 1171553Srgrimes private boolean apiMode; 1181553Srgrimes 1191553Srgrimes private JavaFileManager fileManager; 1201553Srgrimes 1211553Srgrimes Start() { 1221553Srgrimes this(null, null, null, null, null); 1231553Srgrimes } 1241553Srgrimes 1251553Srgrimes Start(PrintWriter writer) { 1261553Srgrimes this(null, null, writer, null, null); 1271553Srgrimes } 1281553Srgrimes 1291553Srgrimes Start(Context context, String programName, PrintWriter writer, 1301553Srgrimes String docletName, ClassLoader classLoader) { 1311553Srgrimes this.context = context == null ? new Context() : context; 1321553Srgrimes String pname = programName == null ? ProgramName : programName; 1331553Srgrimes this.messager = writer == null 1341553Srgrimes ? new Messager(this.context, pname) 1351553Srgrimes : new Messager(this.context, pname, writer, writer); 1361553Srgrimes this.docletName = docletName; 1371553Srgrimes this.classLoader = classLoader; 1381553Srgrimes this.docletClass = null; 1391553Srgrimes this.locale = Locale.getDefault(); 1401553Srgrimes } 1411553Srgrimes 1421553Srgrimes public Start(Context context) { 1431553Srgrimes this.docletClass = null; 1441553Srgrimes this.context = Objects.requireNonNull(context); 1451553Srgrimes this.apiMode = true; 1461553Srgrimes this.docletName = null; 1471553Srgrimes this.classLoader = null; 1481553Srgrimes this.locale = Locale.getDefault(); 14927748Simp } 15027748Simp 1511553Srgrimes void initMessager() { 1521553Srgrimes if (!apiMode) 15327748Simp return; 15427748Simp if (messager == null) { 15527748Simp Log log = context.get(Log.logKey); 1561553Srgrimes if (log instanceof Messager) { 1571553Srgrimes messager = (Messager) log; 1581553Srgrimes } else { 1591553Srgrimes PrintWriter out = context.get(Log.errKey); 1601553Srgrimes messager = (out == null) 1611553Srgrimes ? new Messager(context, ProgramName) 1621553Srgrimes : new Messager(context, ProgramName, out, out); 1631553Srgrimes } 1641553Srgrimes } 1651553Srgrimes } 1661553Srgrimes 1671553Srgrimes /** 1681553Srgrimes * Usage 1691553Srgrimes */ 1701553Srgrimes @Override 1711553Srgrimes void usage() { 1721553Srgrimes usage(true); 1731553Srgrimes } 1741553Srgrimes 17527748Simp void usage(boolean exit) { 17627748Simp usage("main.usage", "-help", "main.usage.foot"); 17727748Simp 17827748Simp if (exit) 1791553Srgrimes throw new Messager.ExitJavadoc(); 18027748Simp } 18127748Simp 18227748Simp @Override 1831553Srgrimes void Xusage() { 1841553Srgrimes Xusage(true); 1851553Srgrimes } 1861553Srgrimes 1871553Srgrimes void Xusage(boolean exit) { 1881553Srgrimes usage("main.Xusage", "-X", "main.Xusage.foot"); 1891553Srgrimes 1901553Srgrimes if (exit) 1911553Srgrimes throw new Messager.ExitJavadoc(); 1921553Srgrimes } 1931553Srgrimes 1941553Srgrimes private void usage(String header, String option, String footer) { 1951553Srgrimes messager.notice(header); 1961553Srgrimes showToolOptions(option.equals("-X") ? OptionKind.EXTENDED : OptionKind.STANDARD); 1971553Srgrimes 1981553Srgrimes // let doclet print usage information 1991553Srgrimes if (docletClass != null) { 2001553Srgrimes String name = doclet.getName(); 2011553Srgrimes messager.notice("main.doclet.usage.header", name); 2021553Srgrimes showDocletOptions(option.equals("-X") ? Option.Kind.EXTENDED : Option.Kind.STANDARD); 2031553Srgrimes } 2041553Srgrimes 2051553Srgrimes if (footer != null) 2061553Srgrimes messager.notice(footer); 2071553Srgrimes } 20827748Simp 20927748Simp void showToolOptions(OptionKind kind) { 21027635Simp Comparator<ToolOption> comp = new Comparator<ToolOption>() { 2111553Srgrimes final Collator collator = Collator.getInstance(Locale.US); 2121553Srgrimes { collator.setStrength(Collator.PRIMARY); } 2131553Srgrimes 2141553Srgrimes @Override 2151553Srgrimes public int compare(ToolOption o1, ToolOption o2) { 2161553Srgrimes return collator.compare(o1.primaryName, o2.primaryName); 2171553Srgrimes } 2181553Srgrimes }; 2191553Srgrimes 2201553Srgrimes Stream.of(ToolOption.values()) 2211553Srgrimes .filter(opt -> opt.kind == kind) 2221553Srgrimes .sorted(comp) 2231553Srgrimes .forEach(opt -> showToolOption(opt)); 2241553Srgrimes } 2251553Srgrimes 2261553Srgrimes void showToolOption(ToolOption option) { 2271553Srgrimes List<String> names = option.getNames(); 2281553Srgrimes String parameters; 2291553Srgrimes if (option.hasArg || option.primaryName.endsWith(":")) { 2301553Srgrimes String sep = (option == ToolOption.J) || option.primaryName.endsWith(":") ? "" : " "; 2311553Srgrimes parameters = sep + option.getParameters(messager); 2321553Srgrimes } else { 2331553Srgrimes parameters = ""; 2341553Srgrimes } 2351553Srgrimes String description = option.getDescription(messager); 2361553Srgrimes showUsage(names, parameters, description); 2371553Srgrimes } 2381553Srgrimes 2391553Srgrimes void showDocletOptions(Option.Kind kind) { 2401553Srgrimes Comparator<Doclet.Option> comp = new Comparator<Doclet.Option>() { 2411553Srgrimes final Collator collator = Collator.getInstance(Locale.US); 2421553Srgrimes { collator.setStrength(Collator.PRIMARY); } 2431553Srgrimes 2441553Srgrimes @Override 2451553Srgrimes public int compare(Doclet.Option o1, Doclet.Option o2) { 2461553Srgrimes return collator.compare(o1.getName(), o2.getName()); 2471553Srgrimes } 2481553Srgrimes }; 2491553Srgrimes 2501553Srgrimes doclet.getSupportedOptions().stream() 2511553Srgrimes .filter(opt -> opt.getKind() == kind) 2521553Srgrimes .sorted(comp) 2531553Srgrimes .forEach(opt -> showDocletOption(opt)); 2541553Srgrimes } 2551553Srgrimes 2561553Srgrimes void showDocletOption(Doclet.Option option) { 2571553Srgrimes List<String> names = Arrays.asList(option.getName()); 2581553Srgrimes String parameters; 2591553Srgrimes if (option.getArgumentCount() > 0 || option.getName().endsWith(":")) { 2601553Srgrimes String sep = option.getName().endsWith(":") ? "" : " "; 2611553Srgrimes parameters = sep + option.getParameters(); 2621553Srgrimes } else { 2631553Srgrimes parameters = ""; 2641553Srgrimes } 2651553Srgrimes String description = option.getDescription(); 2661553Srgrimes showUsage(names, parameters, description); 2671553Srgrimes } 2681553Srgrimes 2691553Srgrimes // The following constants are intended to format the output to 2701553Srgrimes // be similar to that of the java launcher: i.e. "java -help". 2711553Srgrimes 2721553Srgrimes /** The indent for the option synopsis. */ 2731553Srgrimes private static final String SMALL_INDENT = " "; 2741553Srgrimes /** The automatic indent for the description. */ 2751553Srgrimes private static final String LARGE_INDENT = " "; 2761553Srgrimes /** The space allowed for the synopsis, if the description is to be shown on the same line. */ 2771553Srgrimes private static final int DEFAULT_SYNOPSIS_WIDTH = 13; 2781553Srgrimes /** The nominal maximum line length, when seeing if text will fit on a line. */ 2791553Srgrimes private static final int DEFAULT_MAX_LINE_LENGTH = 80; 2801553Srgrimes /** The format for a single-line help entry. */ 2811553Srgrimes private static final String COMPACT_FORMAT = SMALL_INDENT + "%-" + DEFAULT_SYNOPSIS_WIDTH + "s %s"; 2821553Srgrimes 2831553Srgrimes void showUsage(List<String> names, String parameters, String description) { 2841553Srgrimes String synopses = names.stream() 2851553Srgrimes .map(s -> s + parameters) 2861553Srgrimes .collect(Collectors.joining(", ")); 2871553Srgrimes // If option synopses and description fit on a single line of reasonable length, 2881553Srgrimes // display using COMPACT_FORMAT 2891553Srgrimes if (synopses.length() < DEFAULT_SYNOPSIS_WIDTH 2901553Srgrimes && !description.contains("\n") 2911553Srgrimes && (SMALL_INDENT.length() + DEFAULT_SYNOPSIS_WIDTH + 1 + description.length() <= DEFAULT_MAX_LINE_LENGTH)) { 2921553Srgrimes messager.printNotice(String.format(COMPACT_FORMAT, synopses, description)); 2931553Srgrimes return; 2941553Srgrimes } 2951553Srgrimes 2961553Srgrimes // If option synopses fit on a single line of reasonable length, show that; 2971553Srgrimes // otherwise, show 1 per line 2981553Srgrimes if (synopses.length() <= DEFAULT_MAX_LINE_LENGTH) { 2991553Srgrimes messager.printNotice(SMALL_INDENT + synopses); 3001553Srgrimes } else { 3011553Srgrimes for (String name: names) { 3021553Srgrimes messager.printNotice(SMALL_INDENT + name + parameters); 3031553Srgrimes } 3041553Srgrimes } 3051553Srgrimes 3061553Srgrimes // Finally, show the description 3071553Srgrimes messager.printNotice(LARGE_INDENT + description.replace("\n", "\n" + LARGE_INDENT)); 3081553Srgrimes } 3091553Srgrimes 3101553Srgrimes 3111553Srgrimes /** 3121553Srgrimes * Main program - external wrapper. In order to maintain backward 3131553Srgrimes * CLI compatibility, we dispatch to the old tool or the old doclet's 3141553Srgrimes * Start mechanism, based on the options present on the command line 3151553Srgrimes * with the following precedence: 3161553Srgrimes * 1. presence of -Xold, dispatch to old tool 3171553Srgrimes * 2. doclet variant, if old, dispatch to old Start 3181553Srgrimes * 3. taglet variant, if old, dispatch to old Start 3191553Srgrimes * 3201553Srgrimes * Thus the presence of -Xold switches the tool, soon after command files 3211553Srgrimes * if any, are expanded, this is performed here, noting that the messager 3221553Srgrimes * is available at this point in time. 3231553Srgrimes * The doclet/taglet tests are performed in the begin method, further on, 3241553Srgrimes * this is to minimize argument processing and most importantly the impact 3251553Srgrimes * of class loader creation, needed to detect the doclet/taglet class variants. 3261553Srgrimes */ 3271553Srgrimes @SuppressWarnings("deprecation") 3281553Srgrimes int begin(String... argv) { 3291553Srgrimes // Preprocess @file arguments 3301553Srgrimes try { 3311553Srgrimes argv = CommandLine.parse(argv); 3321553Srgrimes } catch (FileNotFoundException e) { 3331553Srgrimes messager.error("main.cant.read", e.getMessage()); 3341553Srgrimes throw new Messager.ExitJavadoc(); 3351553Srgrimes } catch (IOException e) { 3361553Srgrimes e.printStackTrace(System.err); 3371553Srgrimes throw new Messager.ExitJavadoc(); 3381553Srgrimes } 3391553Srgrimes 3401553Srgrimes if (argv.length > 0 && "-Xold".equals(argv[0])) { 3411553Srgrimes messager.warning("main.legacy_api"); 3421553Srgrimes String[] nargv = Arrays.copyOfRange(argv, 1, argv.length); 3431553Srgrimes return com.sun.tools.javadoc.Main.execute(nargv); 3441553Srgrimes } 3451553Srgrimes boolean ok = begin(Arrays.asList(argv), Collections.<JavaFileObject> emptySet()); 3461553Srgrimes return ok ? 0 : 1; 3471553Srgrimes } 3481553Srgrimes 3491553Srgrimes // Called by 199 API. 3501553Srgrimes public boolean begin(Class<?> docletClass, 3511553Srgrimes Iterable<String> options, 3521553Srgrimes Iterable<? extends JavaFileObject> fileObjects) { 3531553Srgrimes this.docletClass = docletClass; 3541553Srgrimes List<String> opts = new ArrayList<>(); 3551553Srgrimes for (String opt: options) 3561553Srgrimes opts.add(opt); 3571553Srgrimes 3581553Srgrimes return begin(opts, fileObjects); 3591553Srgrimes } 3601553Srgrimes 3611553Srgrimes @SuppressWarnings("deprecation") 3621553Srgrimes private boolean begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) { 3631553Srgrimes fileManager = context.get(JavaFileManager.class); 3641553Srgrimes if (fileManager == null) { 3651553Srgrimes JavacFileManager.preRegister(context); 3661553Srgrimes fileManager = context.get(JavaFileManager.class); 367 if (fileManager instanceof BaseFileManager) { 368 ((BaseFileManager) fileManager).autoClose = true; 369 } 370 } 371 // locale, doclet and maybe taglet, needs to be determined first 372 docletClass = preProcess(fileManager, options); 373 if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) { 374 // no need to dispatch to old, safe to init now 375 initMessager(); 376 messager.setLocale(locale); 377 try { 378 Object o = docletClass.getConstructor().newInstance(); 379 doclet = (Doclet) o; 380 } catch (ReflectiveOperationException exc) { 381 exc.printStackTrace(); 382 if (!apiMode) { 383 error("main.could_not_instantiate_class", docletClass); 384 throw new Messager.ExitJavadoc(); 385 } 386 throw new ClientCodeException(exc); 387 } 388 } else { 389 if (this.apiMode) { 390 com.sun.tools.javadoc.main.Start ostart 391 = new com.sun.tools.javadoc.main.Start(context); 392 return ostart.begin(docletClass, options, fileObjects); 393 } 394 warn("main.legacy_api"); 395 String[] array = options.toArray(new String[options.size()]); 396 return com.sun.tools.javadoc.Main.execute(array) == 0; 397 } 398 399 boolean failed = false; 400 try { 401 failed = !parseAndExecute(options, fileObjects); 402 } catch (Messager.ExitJavadoc exc) { 403 // ignore, we just exit this way 404 } catch (OutOfMemoryError ee) { 405 messager.error("main.out.of.memory"); 406 failed = true; 407 } catch (ClientCodeException e) { 408 // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl 409 throw e; 410 } catch (Error ee) { 411 ee.printStackTrace(System.err); 412 messager.error("main.fatal.error"); 413 failed = true; 414 } catch (Exception ee) { 415 ee.printStackTrace(System.err); 416 messager.error("main.fatal.exception"); 417 failed = true; 418 } finally { 419 if (fileManager != null 420 && fileManager instanceof BaseFileManager 421 && ((BaseFileManager) fileManager).autoClose) { 422 try { 423 fileManager.close(); 424 } catch (IOException ignore) {} 425 } 426 boolean haveErrorWarnings = messager.nerrors() > 0 || 427 (rejectWarnings && messager.nwarnings() > 0); 428 if (failed && !haveErrorWarnings) { 429 // the doclet failed, but nothing reported, flag it!. 430 messager.error("main.unknown.error"); 431 } 432 failed |= haveErrorWarnings; 433 messager.exitNotice(); 434 messager.flush(); 435 } 436 return !failed; 437 } 438 439 /** 440 * Main program - internal 441 */ 442 @SuppressWarnings("unchecked") 443 private boolean parseAndExecute(List<String> argList, 444 Iterable<? extends JavaFileObject> fileObjects) throws IOException { 445 long tm = System.currentTimeMillis(); 446 447 List<String> javaNames = new ArrayList<>(); 448 449 compOpts = Options.instance(context); 450 451 // Make sure no obsolete source/target messages are reported 452 com.sun.tools.javac.main.Option.XLINT.process(getOptionHelper(), "-Xlint:-options"); 453 454 doclet.init(locale, messager); 455 parseArgs(argList, javaNames); 456 457 if (fileManager instanceof BaseFileManager) { 458 ((BaseFileManager) fileManager).handleOptions(fileManagerOpts); 459 } 460 461 String platformString = compOpts.get("--release"); 462 463 if (platformString != null) { 464 if (compOpts.isSet("-source")) { 465 usageError("main.release.bootclasspath.conflict", "-source"); 466 } 467 if (fileManagerOpts.containsKey(BOOT_CLASS_PATH)) { 468 usageError("main.release.bootclasspath.conflict", BOOT_CLASS_PATH.getPrimaryName()); 469 } 470 471 PlatformDescription platformDescription = 472 PlatformUtils.lookupPlatformDescription(platformString); 473 474 if (platformDescription == null) { 475 usageError("main.unsupported.release.version", platformString); 476 } 477 478 compOpts.put(SOURCE, platformDescription.getSourceVersion()); 479 480 context.put(PlatformDescription.class, platformDescription); 481 482 Collection<Path> platformCP = platformDescription.getPlatformPath(); 483 484 if (platformCP != null) { 485 if (fileManager instanceof StandardJavaFileManager) { 486 StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager; 487 488 sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP); 489 } else { 490 usageError("main.release.not.standard.file.manager", platformString); 491 } 492 } 493 } 494 495 compOpts.notifyListeners(); 496 List<String> modules = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.MODULE, 497 s -> Collections.EMPTY_LIST); 498 499 if (modules.isEmpty()) { 500 List<String> subpkgs = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.SUBPACKAGES, 501 s -> Collections.EMPTY_LIST); 502 if (subpkgs.isEmpty()) { 503 if (javaNames.isEmpty() && isEmpty(fileObjects)) { 504 usageError("main.No_modules_packages_or_classes_specified"); 505 } 506 } 507 } 508 509 JavadocTool comp = JavadocTool.make0(context); 510 if (comp == null) return false; 511 512 DocletEnvironment docEnv = comp.getEnvironment(jdtoolOpts, 513 javaNames, 514 fileObjects); 515 516 // release resources 517 comp = null; 518 519 if (breakiterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) { 520 JavacTrees trees = JavacTrees.instance(context); 521 trees.setBreakIterator(BreakIterator.getSentenceInstance(locale)); 522 } 523 // pass off control to the doclet 524 boolean ok = docEnv != null; 525 if (ok) ok = doclet.run(docEnv); 526 527 // We're done. 528 if (compOpts.get("-verbose") != null) { 529 tm = System.currentTimeMillis() - tm; 530 messager.notice("main.done_in", Long.toString(tm)); 531 } 532 533 return ok; 534 } 535 536 Set<Doclet.Option> docletOptions = null; 537 int handleDocletOptions(int idx, List<String> args, boolean isToolOption) { 538 if (docletOptions == null) { 539 docletOptions = doclet.getSupportedOptions(); 540 } 541 String arg = args.get(idx); 542 String argBase, argVal; 543 if (arg.startsWith("--") && arg.contains("=")) { 544 int sep = arg.indexOf("="); 545 argBase = arg.substring(0, sep); 546 argVal = arg.substring(sep + 1); 547 } else { 548 argBase = arg; 549 argVal = null; 550 } 551 552 for (Doclet.Option opt : docletOptions) { 553 if (opt.matches(argBase)) { 554 if (argVal != null) { 555 switch (opt.getArgumentCount()) { 556 case 0: 557 usageError("main.unnecessary_arg_provided", argBase); 558 break; 559 case 1: 560 opt.process(arg, Arrays.asList(argVal).listIterator()); 561 break; 562 default: 563 usageError("main.only_one_argument_with_equals", argBase); 564 break; 565 } 566 } else { 567 if (args.size() - idx -1 < opt.getArgumentCount()) { 568 usageError("main.requires_argument", arg); 569 } 570 opt.process(arg, args.listIterator(idx + 1)); 571 idx += opt.getArgumentCount(); 572 } 573 return idx; 574 } 575 } 576 // check if arg is accepted by the tool before emitting error 577 if (!isToolOption) 578 usageError("main.invalid_flag", arg); 579 return idx; 580 } 581 582 private Class<?> preProcess(JavaFileManager jfm, List<String> argv) { 583 // doclet specifying arguments 584 String userDocletPath = null; 585 String userDocletName = null; 586 587 // taglet specifying arguments, since tagletpath is a doclet 588 // functionality, assume they are repeated and inspect all. 589 List<File> userTagletPath = new ArrayList<>(); 590 List<String> userTagletNames = new ArrayList<>(); 591 592 // Step 1: loop through the args, set locale early on, if found. 593 for (int i = 0 ; i < argv.size() ; i++) { 594 String arg = argv.get(i); 595 if (arg.equals(ToolOption.LOCALE.primaryName)) { 596 checkOneArg(argv, i++); 597 String lname = argv.get(i); 598 locale = getLocale(lname); 599 } else if (arg.equals(ToolOption.DOCLET.primaryName)) { 600 checkOneArg(argv, i++); 601 if (userDocletName != null) { 602 usageError("main.more_than_one_doclet_specified_0_and_1", 603 userDocletName, argv.get(i)); 604 } 605 if (docletName != null) { 606 usageError("main.more_than_one_doclet_specified_0_and_1", 607 docletName, argv.get(i)); 608 } 609 userDocletName = argv.get(i); 610 } else if (arg.equals(ToolOption.DOCLETPATH.primaryName)) { 611 checkOneArg(argv, i++); 612 if (userDocletPath == null) { 613 userDocletPath = argv.get(i); 614 } else { 615 userDocletPath += File.pathSeparator + argv.get(i); 616 } 617 } else if ("-taglet".equals(arg)) { 618 userTagletNames.add(argv.get(i + 1)); 619 } else if ("-tagletpath".equals(arg)) { 620 for (String pathname : argv.get(i + 1).split(File.pathSeparator)) { 621 userTagletPath.add(new File(pathname)); 622 } 623 } 624 } 625 626 // Step 2: a doclet is provided, nothing more to do. 627 if (docletClass != null) { 628 return docletClass; 629 } 630 631 // Step 3: doclet name specified ? if so find a ClassLoader, 632 // and load it. 633 if (userDocletName != null) { 634 ClassLoader cl = classLoader; 635 if (cl == null) { 636 if (!fileManager.hasLocation(DOCLET_PATH)) { 637 List<File> paths = new ArrayList<>(); 638 if (userDocletPath != null) { 639 for (String pathname : userDocletPath.split(File.pathSeparator)) { 640 paths.add(new File(pathname)); 641 } 642 } 643 try { 644 ((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths); 645 } catch (IOException ioe) { 646 error("main.doclet_could_not_set_location", paths); 647 throw new Messager.ExitJavadoc(); 648 } 649 } 650 cl = fileManager.getClassLoader(DOCLET_PATH); 651 if (cl == null) { 652 // despite doclet specified on cmdline no classloader found! 653 error("main.doclet_no_classloader_found", userDocletName); 654 throw new Messager.ExitJavadoc(); 655 } 656 } 657 try { 658 Class<?> klass = cl.loadClass(userDocletName); 659 return klass; 660 } catch (ClassNotFoundException cnfe) { 661 error("main.doclet_class_not_found", userDocletName); 662 throw new Messager.ExitJavadoc(); 663 } 664 } 665 666 // Step 4: we have a doclet, try loading it 667 if (docletName != null) { 668 try { 669 return Class.forName(docletName, true, getClass().getClassLoader()); 670 } catch (ClassNotFoundException cnfe) { 671 error("main.doclet_class_not_found", userDocletName); 672 throw new Messager.ExitJavadoc(); 673 } 674 } 675 676 // Step 5: we don't have a doclet specified, do we have taglets ? 677 if (!userTagletNames.isEmpty() && hasOldTaglet(userTagletNames, userTagletPath)) { 678 // found a bogey, return the old doclet 679 return OldStdDoclet; 680 } 681 682 // finally 683 return StdDoclet; 684 } 685 686 /* 687 * This method returns true iff it finds a legacy taglet, but for 688 * all other conditions including errors it returns false, allowing 689 * nature to take its own course. 690 */ 691 @SuppressWarnings("deprecation") 692 private boolean hasOldTaglet(List<String> tagletNames, List<File> tagletPaths) { 693 if (!fileManager.hasLocation(TAGLET_PATH)) { 694 try { 695 ((StandardJavaFileManager) fileManager).setLocation(TAGLET_PATH, tagletPaths); 696 } catch (IOException ioe) { 697 error("main.doclet_could_not_set_location", tagletPaths); 698 throw new Messager.ExitJavadoc(); 699 } 700 } 701 ClassLoader cl = fileManager.getClassLoader(TAGLET_PATH); 702 if (cl == null) { 703 // no classloader found! 704 error("main.doclet_no_classloader_found", tagletNames.get(0)); 705 throw new Messager.ExitJavadoc(); 706 } 707 for (String tagletName : tagletNames) { 708 try { 709 Class<?> klass = cl.loadClass(tagletName); 710 if (com.sun.tools.doclets.Taglet.class.isAssignableFrom(klass)) { 711 return true; 712 } 713 } catch (ClassNotFoundException cnfe) { 714 error("main.doclet_class_not_found", tagletName); 715 throw new Messager.ExitJavadoc(); 716 } 717 } 718 return false; 719 } 720 721 private void parseArgs(List<String> args, List<String> javaNames) { 722 for (int i = 0 ; i < args.size() ; i++) { 723 String arg = args.get(i); 724 ToolOption o = ToolOption.get(arg); 725 if (o != null) { 726 // handle a doclet argument that may be needed however 727 // don't increment the index, and allow the tool to consume args 728 handleDocletOptions(i, args, true); 729 730 if (o.hasArg) { 731 if (arg.startsWith("--") && arg.contains("=")) { 732 o.process(this, arg.substring(arg.indexOf('=') + 1)); 733 } else { 734 checkOneArg(args, i++); 735 o.process(this, args.get(i)); 736 } 737 } else if (o.hasSuffix) { 738 o.process(this, arg); 739 } else { 740 o.process(this); 741 } 742 } else if (arg.startsWith("-XD")) { 743 // hidden javac options 744 String s = arg.substring("-XD".length()); 745 int eq = s.indexOf('='); 746 String key = (eq < 0) ? s : s.substring(0, eq); 747 String value = (eq < 0) ? s : s.substring(eq+1); 748 compOpts.put(key, value); 749 } else if (arg.startsWith("-")) { 750 i = handleDocletOptions(i, args, false); 751 } else { 752 javaNames.add(arg); 753 } 754 } 755 } 756 757 private <T> boolean isEmpty(Iterable<T> iter) { 758 return !iter.iterator().hasNext(); 759 } 760 761 /** 762 * Check the one arg option. 763 * Error and exit if one argument is not provided. 764 */ 765 private void checkOneArg(List<String> args, int index) { 766 if ((index + 1) >= args.size() || args.get(index + 1).startsWith("-d")) { 767 usageError("main.requires_argument", args.get(index)); 768 } 769 } 770 771 @Override 772 void usageError(String key, Object... args) { 773 error(key, args); 774 usage(true); 775 } 776 777 void error(String key, Object... args) { 778 messager.error(key, args); 779 } 780 781 void warn(String key, Object... args) { 782 messager.warning(key, args); 783 } 784 785 /** 786 * Get the locale if specified on the command line 787 * else return null and if locale option is not used 788 * then return default locale. 789 */ 790 private Locale getLocale(String localeName) { 791 Locale userlocale = null; 792 if (localeName == null || localeName.isEmpty()) { 793 return Locale.getDefault(); 794 } 795 int firstuscore = localeName.indexOf('_'); 796 int seconduscore = -1; 797 String language = null; 798 String country = null; 799 String variant = null; 800 if (firstuscore == 2) { 801 language = localeName.substring(0, firstuscore); 802 seconduscore = localeName.indexOf('_', firstuscore + 1); 803 if (seconduscore > 0) { 804 if (seconduscore != firstuscore + 3 805 || localeName.length() <= seconduscore + 1) { 806 usageError("main.malformed_locale_name", localeName); 807 return null; 808 } 809 country = localeName.substring(firstuscore + 1, 810 seconduscore); 811 variant = localeName.substring(seconduscore + 1); 812 } else if (localeName.length() == firstuscore + 3) { 813 country = localeName.substring(firstuscore + 1); 814 } else { 815 usageError("main.malformed_locale_name", localeName); 816 return null; 817 } 818 } else if (firstuscore == -1 && localeName.length() == 2) { 819 language = localeName; 820 } else { 821 usageError("main.malformed_locale_name", localeName); 822 return null; 823 } 824 userlocale = searchLocale(language, country, variant); 825 if (userlocale == null) { 826 usageError("main.illegal_locale_name", localeName); 827 return null; 828 } else { 829 return userlocale; 830 } 831 } 832 833 /** 834 * Search the locale for specified language, specified country and 835 * specified variant. 836 */ 837 private Locale searchLocale(String language, String country, 838 String variant) { 839 for (Locale loc : Locale.getAvailableLocales()) { 840 if (loc.getLanguage().equals(language) && 841 (country == null || loc.getCountry().equals(country)) && 842 (variant == null || loc.getVariant().equals(variant))) { 843 return loc; 844 } 845 } 846 return null; 847 } 848 849 @Override 850 OptionHelper getOptionHelper() { 851 return new GrumpyHelper(null) { 852 @Override 853 public String get(com.sun.tools.javac.main.Option option) { 854 return compOpts.get(option); 855 } 856 857 @Override 858 public void put(String name, String value) { 859 compOpts.put(name, value); 860 } 861 }; 862 } 863} 864