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