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