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