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.text.BreakIterator;
  32 import java.text.Collator;
  33 import java.util.ArrayList;
  34 import java.util.Arrays;
  35 import java.util.Collections;
  36 import java.util.Comparator;
  37 import java.util.IllformedLocaleException;
  38 import java.util.List;
  39 import java.util.Locale;
  40 import java.util.MissingResourceException;
  41 import java.util.Objects;
  42 import java.util.ResourceBundle;
  43 import java.util.Set;
  44 import java.util.stream.Collectors;
  45 
  46 import javax.tools.JavaFileManager;
  47 import javax.tools.JavaFileObject;
  48 import javax.tools.StandardJavaFileManager;
  49 
  50 import com.sun.tools.javac.api.JavacTrees;
  51 import com.sun.tools.javac.file.BaseFileManager;
  52 import com.sun.tools.javac.file.JavacFileManager;
  53 import com.sun.tools.javac.jvm.Target;
  54 import com.sun.tools.javac.main.Arguments;
  55 import com.sun.tools.javac.main.CommandLine;
  56 import com.sun.tools.javac.util.ClientCodeException;
  57 import com.sun.tools.javac.util.Context;
  58 import com.sun.tools.javac.util.Log;
  59 import com.sun.tools.javac.util.StringUtils;
  60 
  61 import jdk.javadoc.doclet.Doclet;
  62 import jdk.javadoc.doclet.Doclet.Option;
  63 import jdk.javadoc.doclet.DocletEnvironment;
  64 import jdk.javadoc.doclet.StandardDoclet;
  65 import jdk.javadoc.internal.tool.Main.Result;
  66 import jdk.javadoc.internal.tool.ToolOptions.ToolOption;
  67 
  68 import static javax.tools.DocumentationTool.Location.*;
  69 
  70 import static jdk.javadoc.internal.tool.Main.Result.*;
  71 
  72 /**
  73  * Main program of Javadoc.
  74  * Previously named "Main".
  75  *
  76  *  <p><b>This is NOT part of any supported API.
  77  *  If you write code that depends on this, you do so at your own risk.
  78  *  This code and its internal interfaces are subject to change or
  79  *  deletion without notice.</b>
  80  */
  81 public class Start {
  82 
  83     /** Context for this invocation. */
  84     private final Context context;
  85 
  86     private static final String ProgramName = "javadoc";
  87 
  88     private Messager messager;
  89 
  90     private final String docletName;
  91 
  92     private final ClassLoader classLoader;
  93 
  94     private Class<?> docletClass;
  95 
  96     private Doclet doclet;
  97 
  98     // used to determine the locale for the messager
  99     private Locale locale;
 100 
 101 
 102     /**
 103      * In API mode, exceptions thrown while calling the doclet are
 104      * propagated using ClientCodeException.
 105      */
 106     private boolean apiMode;
 107 
 108     private JavaFileManager fileManager;
 109 
 110     private final ToolOptions options;
 111 
 112     Start() {
 113         this(null, null, null, null, null, null);
 114     }
 115 
 116     Start(PrintWriter outWriter, PrintWriter errWriter) {
 117         this(null, null, outWriter, errWriter, null, null);
 118     }
 119 
 120     Start(Context context, String programName,
 121             PrintWriter outWriter, PrintWriter errWriter,
 122             String docletName, ClassLoader classLoader) {
 123         this.context = context == null ? new Context() : context;
 124         String pname = programName == null ? ProgramName : programName;
 125         this.messager = (outWriter == null && errWriter == null)
 126                 ? new Messager(this.context, pname)
 127                 : new Messager(this.context, pname, outWriter, errWriter);
 128         this.docletName = docletName;
 129         this.classLoader = classLoader;
 130         this.docletClass = null;
 131         this.locale = Locale.getDefault();
 132 
 133         options = getToolOptions();
 134     }
 135 
 136     public Start(Context context) {
 137         this.docletClass = null;
 138         this.context = Objects.requireNonNull(context);
 139         this.apiMode = true;
 140         this.docletName = null;
 141         this.classLoader = null;
 142         this.locale = Locale.getDefault();
 143 
 144         Log log = context.get(Log.logKey);
 145         if (log instanceof Messager) {
 146             messager = (Messager) log;
 147         } else {
 148             PrintWriter out = context.get(Log.errKey);
 149             messager = (out == null)
 150                     ? new Messager(context, ProgramName)
 151                     : new Messager(context, ProgramName, out, out);
 152         }
 153 
 154         options = getToolOptions();
 155     }
 156 
 157     private ToolOptions getToolOptions() {
 158         ToolOptions.ShowHelper helper =  new ToolOptions.ShowHelper() {
 159             @Override
 160             public void usage() {
 161                 showUsage("main.usage", ToolOption.Kind.STANDARD, "main.usage.foot");
 162             }
 163 
 164             @Override
 165             public void Xusage() {
 166                 showUsage("main.Xusage", ToolOption.Kind.EXTENDED, "main.Xusage.foot");
 167             }
 168 
 169             @Override
 170             public void version() {
 171                 showVersion("javadoc.version", "release");
 172             }
 173 
 174             @Override
 175             public void fullVersion() {
 176                 showVersion("javadoc.fullversion", "full");
 177             }
 178         };
 179         return new ToolOptions(context, messager, helper);
 180     }
 181 
 182     private void showUsage() {
 183         showUsage("main.usage", ToolOption.Kind.STANDARD, "main.usage.foot");
 184     }
 185 
 186     private void showUsage(String headerKey, ToolOption.Kind kind, String footerKey) {
 187         messager.notice(headerKey);
 188         showToolOptions(kind);
 189 
 190         // let doclet print usage information
 191         if (docletClass != null) {
 192             String name = doclet.getName();
 193             messager.notice("main.doclet.usage.header", name);
 194             showDocletOptions(kind == ToolOption.Kind.EXTENDED
 195                     ? Option.Kind.EXTENDED
 196                     : Option.Kind.STANDARD);
 197         }
 198         if (footerKey != null)
 199             messager.notice(footerKey);
 200     }
 201 
 202     private static final String versionRBName = "jdk.javadoc.internal.tool.resources.version";
 203     private static ResourceBundle versionRB;
 204 
 205     private void showVersion(String labelKey, String versionKey) {
 206         messager.notice(labelKey, messager.programName, getVersion(versionKey));
 207     }
 208 
 209     private static String getVersion(String key) {
 210         if (versionRB == null) {
 211             try {
 212                 versionRB = ResourceBundle.getBundle(versionRBName);
 213             } catch (MissingResourceException e) {
 214                 return Log.getLocalizedString("version.not.available");
 215             }
 216         }
 217         try {
 218             return versionRB.getString(key);
 219         } catch (MissingResourceException e) {
 220             return Log.getLocalizedString("version.not.available");
 221         }
 222     }
 223 
 224     private void showToolOptions(ToolOption.Kind kind) {
 225         Comparator<ToolOption> comp = new Comparator<ToolOption>() {
 226             final Collator collator = Collator.getInstance(Locale.US);
 227             { collator.setStrength(Collator.PRIMARY); }
 228 
 229             @Override
 230             public int compare(ToolOption o1, ToolOption o2) {
 231                 return collator.compare(o1.primaryName, o2.primaryName);
 232             }
 233         };
 234 
 235         options.getSupportedOptions().stream()
 236                     .filter(opt -> opt.kind == kind)
 237                     .sorted(comp)
 238                     .forEach(this::showToolOption);
 239     }
 240 
 241     private void showToolOption(ToolOption option) {
 242         List<String> names = option.getNames();
 243         String primaryName = option.primaryName;
 244         String parameters;
 245         if (option.hasArg || primaryName.endsWith(":")) {
 246             String sep = primaryName.equals(ToolOptions.J) || primaryName.endsWith(":") ? "" : " ";
 247             parameters = sep + option.getParameters(messager);
 248         } else {
 249             parameters = "";
 250         }
 251         String description = option.getDescription(messager);
 252         showOption(names, parameters, description);
 253     }
 254 
 255     private 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     private void showDocletOption(Doclet.Option option) {
 273         List<String> names = option.getNames();
 274         String parameters;
 275         String primaryName = names.get(0);
 276         if (option.getArgumentCount() > 0 || primaryName.endsWith(":")) {
 277             String sep = primaryName.endsWith(":") ? "" : " ";
 278             parameters = sep + option.getParameters();
 279         } else {
 280             parameters = "";
 281         }
 282         String description = option.getDescription();
 283         showOption(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 showOption(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                     || (this.options.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 || options.dumpOnError())) {
 466             t.printStackTrace(System.err);
 467         }
 468     }
 469 
 470     /**
 471      * Main program - internal
 472      */
 473     private Result parseAndExecute(List<String> argList, Iterable<? extends JavaFileObject> fileObjects)
 474             throws ToolException, OptionException, com.sun.tools.javac.main.Option.InvalidValueException {
 475         long tm = System.currentTimeMillis();
 476 
 477         List<String> javaNames = new ArrayList<>();
 478 
 479         // Make sure no obsolete source/target messages are reported
 480         try {
 481             options.processCompilerOption(com.sun.tools.javac.main.Option.XLINT_CUSTOM, "-Xlint:-options");
 482         } catch (com.sun.tools.javac.main.Option.InvalidValueException ignore) {
 483         }
 484 
 485         Arguments arguments = Arguments.instance(context);
 486         arguments.init(ProgramName);
 487         arguments.allowEmpty();
 488 
 489         doclet.init(locale, messager);
 490         parseArgs(argList, javaNames);
 491 
 492         if (!arguments.handleReleaseOptions(extra -> true)) {
 493             // Arguments does not always increase the error count in the
 494             // case of errors, so increment the error count only if it has
 495             // not been updated previously, preventing complaints by callers
 496             if (!messager.hasErrors() && !messager.hasWarnings())
 497                 messager.nerrors++;
 498             return CMDERR;
 499         }
 500 
 501         if (!arguments.validate()) {
 502             // Arguments does not always increase the error count in the
 503             // case of errors, so increment the error count only if it has
 504             // not been updated previously, preventing complaints by callers
 505             if (!messager.hasErrors() && !messager.hasWarnings())
 506                 messager.nerrors++;
 507             return CMDERR;
 508         }
 509 
 510         if (fileManager instanceof BaseFileManager) {
 511             ((BaseFileManager) fileManager).handleOptions(options.fileManagerOptions());
 512         }
 513 
 514         String mr = com.sun.tools.javac.main.Option.MULTIRELEASE.primaryName;
 515         if (fileManager.isSupportedOption(mr) == 1) {
 516             Target target = Target.instance(context);
 517             List<String> list = List.of(target.multiReleaseValue());
 518             fileManager.handleOption(mr, list.iterator());
 519         }
 520         options.compilerOptions().notifyListeners();
 521 
 522         if (options.modules().isEmpty()) {
 523             if (options.subpackages().isEmpty()) {
 524                 if (javaNames.isEmpty() && isEmpty(fileObjects)) {
 525                     String text = messager.getText("main.No_modules_packages_or_classes_specified");
 526                     throw new ToolException(CMDERR, text);
 527                 }
 528             }
 529         }
 530 
 531         JavadocTool comp = JavadocTool.make0(context);
 532         if (comp == null) return ABNORMAL;
 533 
 534         DocletEnvironment docEnv = comp.getEnvironment(options, javaNames, fileObjects);
 535 
 536         // release resources
 537         comp = null;
 538 
 539         if (options.breakIterator() || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
 540             JavacTrees trees = JavacTrees.instance(context);
 541             trees.setBreakIterator(BreakIterator.getSentenceInstance(locale));
 542         }
 543         // pass off control to the doclet
 544         Result returnStatus = docEnv != null && doclet.run(docEnv)
 545                 ? OK
 546                 : ERROR;
 547 
 548         // We're done.
 549         if (options.verbose()) {
 550             tm = System.currentTimeMillis() - tm;
 551             messager.notice("main.done_in", Long.toString(tm));
 552         }
 553 
 554         return returnStatus;
 555     }
 556 
 557     boolean matches(List<String> names, String arg) {
 558         for (String name : names) {
 559             if (StringUtils.toLowerCase(name).equals(StringUtils.toLowerCase(arg)))
 560                 return true;
 561         }
 562         return false;
 563     }
 564 
 565     boolean matches(Doclet.Option option, String arg) {
 566         if (matches(option.getNames(), arg))
 567              return true;
 568         int sep = arg.indexOf(':');
 569         String targ = arg.substring(0, sep + 1);
 570         return matches(option.getNames(), targ);
 571     }
 572 
 573     private Set<? extends Doclet.Option> docletOptions = null;
 574     int handleDocletOption(int idx, List<String> args, boolean isToolOption)
 575             throws OptionException {
 576         if (docletOptions == null) {
 577             docletOptions = doclet.getSupportedOptions();
 578         }
 579         String arg = args.get(idx);
 580         String argBase, argVal;
 581         if (arg.startsWith("--") && arg.contains("=")) {
 582             int sep = arg.indexOf("=");
 583             argBase = arg.substring(0, sep);
 584             argVal = arg.substring(sep + 1);
 585         } else {
 586             argBase = arg;
 587             argVal = null;
 588         }
 589         String text = null;
 590         for (Doclet.Option opt : docletOptions) {
 591             if (matches(opt, argBase)) {
 592                 if (argVal != null) {
 593                     switch (opt.getArgumentCount()) {
 594                         case 0:
 595                             text = messager.getText("main.unnecessary_arg_provided", argBase);
 596                             throw new OptionException(ERROR, this::showUsage, text);
 597                         case 1:
 598                             opt.process(arg, Arrays.asList(argVal));
 599                             break;
 600                         default:
 601                             text = messager.getText("main.only_one_argument_with_equals", argBase);
 602                             throw new OptionException(ERROR, this::showUsage, text);
 603                     }
 604                 } else {
 605                     if (args.size() - idx -1 < opt.getArgumentCount()) {
 606                         text = messager.getText("main.requires_argument", arg);
 607                         throw new OptionException(ERROR, this::showUsage, text);
 608                     }
 609                     opt.process(arg, args.subList(idx + 1, args.size()));
 610                     idx += opt.getArgumentCount();
 611                 }
 612                 return idx;
 613             }
 614         }
 615         // check if arg is accepted by the tool before emitting error
 616         if (!isToolOption) {
 617             text = messager.getText("main.invalid_flag", arg);
 618             throw new OptionException(ERROR, this::showUsage, text);
 619         }
 620         return idx;
 621     }
 622 
 623     private Doclet preprocess(JavaFileManager jfm, List<String> argv)
 624             throws ToolException, OptionException {
 625         // doclet specifying arguments
 626         String userDocletPath = null;
 627         String userDocletName = null;
 628 
 629         // taglet specifying arguments, since tagletpath is a doclet
 630         // functionality, assume they are repeated and inspect all.
 631         List<File> userTagletPath = new ArrayList<>();
 632         List<String> userTagletNames = new ArrayList<>();
 633 
 634         // Step 1: loop through the args, set locale early on, if found.
 635         for (int i = 0 ; i < argv.size() ; i++) {
 636             String arg = argv.get(i);
 637             if (arg.equals(ToolOptions.DUMP_ON_ERROR)) {
 638                 options.setDumpOnError(true);
 639             } else if (arg.equals(ToolOptions.LOCALE)) {
 640                 checkOneArg(argv, i++);
 641                 String lname = argv.get(i);
 642                 locale = getLocale(lname);
 643             } else if (arg.equals(ToolOptions.DOCLET)) {
 644                 checkOneArg(argv, i++);
 645                 if (userDocletName != null) {
 646                     if (apiMode) {
 647                         throw new IllegalArgumentException("More than one doclet specified (" +
 648                                 userDocletName + " and " + argv.get(i) + ").");
 649                     }
 650                     String text = messager.getText("main.more_than_one_doclet_specified_0_and_1",
 651                             userDocletName, argv.get(i));
 652                     throw new ToolException(CMDERR, text);
 653                 }
 654                 if (docletName != null) {
 655                     if (apiMode) {
 656                         throw new IllegalArgumentException("More than one doclet specified (" +
 657                                 docletName + " and " + argv.get(i) + ").");
 658                     }
 659                     String text = messager.getText("main.more_than_one_doclet_specified_0_and_1",
 660                             docletName, argv.get(i));
 661                     throw new ToolException(CMDERR, text);
 662                 }
 663                 userDocletName = argv.get(i);
 664             } else if (arg.equals(ToolOptions.DOCLET_PATH)) {
 665                 checkOneArg(argv, i++);
 666                 if (userDocletPath == null) {
 667                     userDocletPath = argv.get(i);
 668                 } else {
 669                     userDocletPath += File.pathSeparator + argv.get(i);
 670                 }
 671             } else if ("-taglet".equals(arg)) {
 672                 userTagletNames.add(argv.get(i + 1));
 673             } else if ("-tagletpath".equals(arg)) {
 674                 for (String pathname : argv.get(i + 1).split(File.pathSeparator)) {
 675                     userTagletPath.add(new File(pathname));
 676                 }
 677             }
 678         }
 679 
 680         // Step 3: doclet name specified ? if so find a ClassLoader,
 681         // and load it.
 682         if (docletClass == null) {
 683             if (userDocletName != null) {
 684                 ClassLoader cl = classLoader;
 685                 if (cl == null) {
 686                     if (!fileManager.hasLocation(DOCLET_PATH)) {
 687                         List<File> paths = new ArrayList<>();
 688                         if (userDocletPath != null) {
 689                             for (String pathname : userDocletPath.split(File.pathSeparator)) {
 690                                 paths.add(new File(pathname));
 691                             }
 692                         }
 693                         try {
 694                             ((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths);
 695                         } catch (IOException ioe) {
 696                             if (apiMode) {
 697                                 throw new IllegalArgumentException("Could not set location for " +
 698                                         userDocletPath, ioe);
 699                             }
 700                             String text = messager.getText("main.doclet_could_not_set_location",
 701                                     userDocletPath);
 702                             throw new ToolException(CMDERR, text, ioe);
 703                         }
 704                     }
 705                     cl = fileManager.getClassLoader(DOCLET_PATH);
 706                     if (cl == null) {
 707                         // despite doclet specified on cmdline no classloader found!
 708                         if (apiMode) {
 709                             throw new IllegalArgumentException("Could not obtain classloader to load "
 710 
 711                                     + userDocletPath);
 712                         }
 713                         String text = messager.getText("main.doclet_no_classloader_found",
 714                                 userDocletName);
 715                         throw new ToolException(CMDERR, text);
 716                     }
 717                 }
 718                 docletClass = loadDocletClass(userDocletName, cl);
 719             } else if (docletName != null){
 720                 docletClass = loadDocletClass(docletName, getClass().getClassLoader());
 721             } else {
 722                 docletClass = StandardDoclet.class;
 723             }
 724         }
 725 
 726         if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) {
 727             messager.setLocale(locale);
 728             try {
 729                 Object o = docletClass.getConstructor().newInstance();
 730                 doclet = (Doclet) o;
 731             } catch (ReflectiveOperationException exc) {
 732                 if (apiMode) {
 733                     throw new ClientCodeException(exc);
 734                 }
 735                 String text = messager.getText("main.could_not_instantiate_class", docletClass.getName());
 736                 throw new ToolException(ERROR, text);
 737             }
 738         } else {
 739             String text = messager.getText("main.not_a_doclet", docletClass.getName());
 740             throw new ToolException(ERROR, text);
 741         }
 742         return doclet;
 743     }
 744 
 745     private Class<?> loadDocletClass(String docletName, ClassLoader classLoader) throws ToolException {
 746         try {
 747             return classLoader == null ? Class.forName(docletName) : classLoader.loadClass(docletName);
 748         } catch (ClassNotFoundException cnfe) {
 749             if (apiMode) {
 750                 throw new IllegalArgumentException("Cannot find doclet class " + docletName);
 751             }
 752             String text = messager.getText("main.doclet_class_not_found", docletName);
 753             throw new ToolException(CMDERR, text, cnfe);
 754         }
 755     }
 756 
 757     private void parseArgs(List<String> args, List<String> javaNames) throws ToolException,
 758             OptionException, com.sun.tools.javac.main.Option.InvalidValueException {
 759         for (int i = 0 ; i < args.size() ; i++) {
 760             String arg = args.get(i);
 761             ToolOption o = options.getOption(arg);
 762             if (o != null) {
 763                 // handle a doclet argument that may be needed however
 764                 // don't increment the index, and allow the tool to consume args
 765                 handleDocletOption(i, args, true);
 766                 if (o.hasArg) {
 767                     if (arg.startsWith("--") && arg.contains("=")) {
 768                         o.process(arg.substring(arg.indexOf('=') + 1));
 769                     } else {
 770                         checkOneArg(args, i++);
 771                         o.process(args.get(i));
 772                     }
 773                 } else if (o.hasSuffix) {
 774                     o.process(arg);
 775                 } else {
 776                     o.process();
 777                 }
 778             } else if (arg.startsWith("-XD")) {
 779                 // hidden javac options
 780                 String s = arg.substring("-XD".length());
 781                 int eq = s.indexOf('=');
 782                 String key = (eq < 0) ? s : s.substring(0, eq);
 783                 String value = (eq < 0) ? s : s.substring(eq+1);
 784                 options.compilerOptions().put(key, value);
 785             } else if (arg.startsWith("-")) {
 786                 i = handleDocletOption(i, args, false);
 787             } else {
 788                 javaNames.add(arg);
 789             }
 790         }
 791     }
 792 
 793     private <T> boolean isEmpty(Iterable<T> iter) {
 794         return !iter.iterator().hasNext();
 795     }
 796 
 797     /**
 798      * Check the one arg option.
 799      * Error and exit if one argument is not provided.
 800      */
 801     private void checkOneArg(List<String> args, int index) throws OptionException {
 802         if ((index + 1) >= args.size() || args.get(index + 1).startsWith("-d")) {
 803             String text = messager.getText("main.requires_argument", args.get(index));
 804             throw new OptionException(CMDERR, this::showUsage, text);
 805         }
 806     }
 807 
 808     void error(String key, Object... args) {
 809         messager.printErrorUsingKey(key, args);
 810     }
 811 
 812     void warn(String key, Object... args)  {
 813         messager.printWarningUsingKey(key, args);
 814     }
 815 
 816     /**
 817      * Get the locale if specified on the command line
 818      * else return null and if locale option is not used
 819      * then return default locale.
 820      */
 821     private Locale getLocale(String localeName) throws ToolException {
 822         try {
 823             // Tolerate, at least for a while, the older syntax accepted by javadoc,
 824             // using _ as the separator
 825             localeName = localeName.replace("_", "-");
 826             Locale l =  new Locale.Builder().setLanguageTag(localeName).build();
 827             // Ensure that a non-empty language is available for the <HTML lang=...> element
 828             return (l.getLanguage().isEmpty()) ? Locale.ENGLISH : l;
 829         } catch (IllformedLocaleException e) {
 830             String text = messager.getText("main.malformed_locale_name", localeName);
 831             throw new ToolException(CMDERR, text);
 832         }
 833     }
 834 
 835     /**
 836      * Search the locale for specified language, specified country and
 837      * specified variant.
 838      */
 839     private Locale searchLocale(String language, String country,
 840                                 String variant) {
 841         for (Locale loc : Locale.getAvailableLocales()) {
 842             if (loc.getLanguage().equals(language) &&
 843                 (country == null || loc.getCountry().equals(country)) &&
 844                 (variant == null || loc.getVariant().equals(variant))) {
 845                 return loc;
 846             }
 847         }
 848         return null;
 849     }
 850 
 851 
 852 }