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