Start.java revision 3673:8bf23828bb2f
11553Srgrimes/*
21553Srgrimes * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
31553Srgrimes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
41553Srgrimes *
51553Srgrimes * This code is free software; you can redistribute it and/or modify it
61553Srgrimes * under the terms of the GNU General Public License version 2 only, as
71553Srgrimes * published by the Free Software Foundation.  Oracle designates this
81553Srgrimes * particular file as subject to the "Classpath" exception as provided
91553Srgrimes * by Oracle in the LICENSE file that accompanied this code.
101553Srgrimes *
111553Srgrimes * This code is distributed in the hope that it will be useful, but WITHOUT
121553Srgrimes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
131553Srgrimes * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
141553Srgrimes * version 2 for more details (a copy is included in the LICENSE file that
151553Srgrimes * accompanied this code).
161553Srgrimes *
171553Srgrimes * You should have received a copy of the GNU General Public License version
181553Srgrimes * 2 along with this work; if not, write to the Free Software Foundation,
191553Srgrimes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
201553Srgrimes *
211553Srgrimes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
221553Srgrimes * or visit www.oracle.com if you need additional information or have any
231553Srgrimes * questions.
241553Srgrimes */
251553Srgrimes
261553Srgrimespackage jdk.javadoc.internal.tool;
271553Srgrimes
281553Srgrimesimport java.io.File;
291553Srgrimesimport java.io.FileNotFoundException;
301553Srgrimesimport java.io.IOException;
311553Srgrimesimport java.io.PrintWriter;
321553Srgrimesimport java.nio.file.Path;
331553Srgrimesimport java.text.BreakIterator;
341553Srgrimesimport java.text.Collator;
351553Srgrimesimport java.util.ArrayList;
361553Srgrimesimport java.util.Arrays;
371553Srgrimesimport java.util.Collection;
381553Srgrimesimport java.util.Collections;
391553Srgrimesimport java.util.Comparator;
401553Srgrimesimport java.util.List;
411553Srgrimesimport java.util.Locale;
4215648Sjoergimport java.util.Objects;
431553Srgrimesimport java.util.Set;
441553Srgrimesimport java.util.stream.Collectors;
451553Srgrimesimport java.util.stream.Stream;
461553Srgrimes
471553Srgrimesimport javax.tools.JavaFileManager;
481553Srgrimesimport javax.tools.JavaFileObject;
491553Srgrimesimport javax.tools.StandardJavaFileManager;
501553Srgrimesimport javax.tools.StandardLocation;
511553Srgrimes
521553Srgrimesimport com.sun.tools.javac.api.JavacTrees;
531553Srgrimesimport com.sun.tools.javac.file.BaseFileManager;
541553Srgrimesimport com.sun.tools.javac.file.JavacFileManager;
551553Srgrimesimport com.sun.tools.javac.main.CommandLine;
561553Srgrimesimport com.sun.tools.javac.main.OptionHelper;
571553Srgrimesimport com.sun.tools.javac.main.OptionHelper.GrumpyHelper;
581553Srgrimesimport com.sun.tools.javac.platform.PlatformDescription;
591553Srgrimesimport com.sun.tools.javac.platform.PlatformUtils;
601553Srgrimesimport com.sun.tools.javac.util.ClientCodeException;
611553Srgrimesimport com.sun.tools.javac.util.Context;
621553Srgrimesimport com.sun.tools.javac.util.Log;
631553Srgrimesimport com.sun.tools.javac.util.Options;
641553Srgrimes
651553Srgrimesimport jdk.javadoc.doclet.Doclet;
661553Srgrimesimport jdk.javadoc.doclet.Doclet.Option;
671553Srgrimesimport jdk.javadoc.doclet.DocletEnvironment;
6827748Simpimport jdk.javadoc.internal.doclets.toolkit.Resources;
691553Srgrimes
701553Srgrimesimport static javax.tools.DocumentationTool.Location.*;
7127748Simp
721553Srgrimesimport static com.sun.tools.javac.main.Option.*;
731553Srgrimes
741553Srgrimes/**
751553Srgrimes * Main program of Javadoc.
761553Srgrimes * Previously named "Main".
771553Srgrimes *
781553Srgrimes *  <p><b>This is NOT part of any supported API.
791553Srgrimes *  If you write code that depends on this, you do so at your own risk.
801553Srgrimes *  This code and its internal interfaces are subject to change or
811553Srgrimes *  deletion without notice.</b>
821553Srgrimes *
831553Srgrimes * @author Robert Field
841553Srgrimes * @author Neal Gafter (rewrite)
851553Srgrimes */
861553Srgrimespublic class Start extends ToolOption.Helper {
871553Srgrimes
881553Srgrimes    @SuppressWarnings("deprecation")
891553Srgrimes    private static final Class<?> OldStdDoclet =
901553Srgrimes            com.sun.tools.doclets.standard.Standard.class;
911553Srgrimes
921553Srgrimes    private static final Class<?> StdDoclet =
931553Srgrimes            jdk.javadoc.doclets.StandardDoclet.class;
941553Srgrimes    /** Context for this invocation. */
951553Srgrimes    private final Context context;
961553Srgrimes
9727748Simp    private static final String ProgramName = "javadoc";
981553Srgrimes
991553Srgrimes    private Messager messager;
1001553Srgrimes
1011553Srgrimes    private final String docletName;
1021553Srgrimes
1031553Srgrimes    private final ClassLoader classLoader;
1041553Srgrimes
1051553Srgrimes    private Class<?> docletClass;
1061553Srgrimes
1071553Srgrimes    private Doclet doclet;
1081553Srgrimes
1091553Srgrimes    // used to determine the locale for the messager
1101553Srgrimes    private Locale locale;
1111553Srgrimes
1121553Srgrimes
1131553Srgrimes    /**
1141553Srgrimes     * In API mode, exceptions thrown while calling the doclet are
1151553Srgrimes     * propagated using ClientCodeException.
1161553Srgrimes     */
1171553Srgrimes    private boolean apiMode;
1181553Srgrimes
1191553Srgrimes    private JavaFileManager fileManager;
1201553Srgrimes
1211553Srgrimes    Start() {
1221553Srgrimes        this(null, null, null, null, null);
1231553Srgrimes    }
1241553Srgrimes
1251553Srgrimes    Start(PrintWriter writer) {
1261553Srgrimes        this(null, null, writer, null, null);
1271553Srgrimes    }
1281553Srgrimes
1291553Srgrimes    Start(Context context, String programName, PrintWriter writer,
1301553Srgrimes            String docletName, ClassLoader classLoader) {
1311553Srgrimes        this.context = context == null ? new Context() : context;
1321553Srgrimes        String pname = programName == null ? ProgramName : programName;
1331553Srgrimes        this.messager = writer == null
1341553Srgrimes                ? new Messager(this.context, pname)
1351553Srgrimes                : new Messager(this.context, pname, writer, writer);
1361553Srgrimes        this.docletName = docletName;
1371553Srgrimes        this.classLoader = classLoader;
1381553Srgrimes        this.docletClass = null;
1391553Srgrimes        this.locale = Locale.getDefault();
1401553Srgrimes    }
1411553Srgrimes
1421553Srgrimes    public Start(Context context) {
1431553Srgrimes        this.docletClass = null;
1441553Srgrimes        this.context = Objects.requireNonNull(context);
1451553Srgrimes        this.apiMode = true;
1461553Srgrimes        this.docletName = null;
1471553Srgrimes        this.classLoader = null;
1481553Srgrimes        this.locale = Locale.getDefault();
14927748Simp    }
15027748Simp
1511553Srgrimes    void initMessager() {
1521553Srgrimes        if (!apiMode)
15327748Simp            return;
15427748Simp        if (messager == null) {
15527748Simp            Log log = context.get(Log.logKey);
1561553Srgrimes            if (log instanceof Messager) {
1571553Srgrimes                messager = (Messager) log;
1581553Srgrimes            } else {
1591553Srgrimes                PrintWriter out = context.get(Log.errKey);
1601553Srgrimes                messager = (out == null)
1611553Srgrimes                        ? new Messager(context, ProgramName)
1621553Srgrimes                        : new Messager(context, ProgramName, out, out);
1631553Srgrimes            }
1641553Srgrimes        }
1651553Srgrimes    }
1661553Srgrimes
1671553Srgrimes    /**
1681553Srgrimes     * Usage
1691553Srgrimes     */
1701553Srgrimes    @Override
1711553Srgrimes    void usage() {
1721553Srgrimes        usage(true);
1731553Srgrimes    }
1741553Srgrimes
17527748Simp    void usage(boolean exit) {
17627748Simp        usage("main.usage", "-help", "main.usage.foot");
17727748Simp
17827748Simp        if (exit)
1791553Srgrimes            throw new Messager.ExitJavadoc();
18027748Simp    }
18127748Simp
18227748Simp    @Override
1831553Srgrimes    void Xusage() {
1841553Srgrimes        Xusage(true);
1851553Srgrimes    }
1861553Srgrimes
1871553Srgrimes    void Xusage(boolean exit) {
1881553Srgrimes        usage("main.Xusage", "-X", "main.Xusage.foot");
1891553Srgrimes
1901553Srgrimes        if (exit)
1911553Srgrimes            throw new Messager.ExitJavadoc();
1921553Srgrimes    }
1931553Srgrimes
1941553Srgrimes    private void usage(String header, String option, String footer) {
1951553Srgrimes        messager.notice(header);
1961553Srgrimes        showToolOptions(option.equals("-X") ? OptionKind.EXTENDED : OptionKind.STANDARD);
1971553Srgrimes
1981553Srgrimes        // let doclet print usage information
1991553Srgrimes        if (docletClass != null) {
2001553Srgrimes            String name = doclet.getName();
2011553Srgrimes            messager.notice("main.doclet.usage.header", name);
2021553Srgrimes            showDocletOptions(option.equals("-X") ? Option.Kind.EXTENDED : Option.Kind.STANDARD);
2031553Srgrimes        }
2041553Srgrimes
2051553Srgrimes        if (footer != null)
2061553Srgrimes            messager.notice(footer);
2071553Srgrimes    }
20827748Simp
20927748Simp    void showToolOptions(OptionKind kind) {
21027635Simp        Comparator<ToolOption> comp = new Comparator<ToolOption>() {
2111553Srgrimes            final Collator collator = Collator.getInstance(Locale.US);
2121553Srgrimes            { collator.setStrength(Collator.PRIMARY); }
2131553Srgrimes
2141553Srgrimes            @Override
2151553Srgrimes            public int compare(ToolOption o1, ToolOption o2) {
2161553Srgrimes                return collator.compare(o1.primaryName, o2.primaryName);
2171553Srgrimes            }
2181553Srgrimes        };
2191553Srgrimes
2201553Srgrimes        Stream.of(ToolOption.values())
2211553Srgrimes                    .filter(opt -> opt.kind == kind)
2221553Srgrimes                    .sorted(comp)
2231553Srgrimes                    .forEach(opt -> showToolOption(opt));
2241553Srgrimes    }
2251553Srgrimes
2261553Srgrimes    void showToolOption(ToolOption option) {
2271553Srgrimes        List<String> names = option.getNames();
2281553Srgrimes        String parameters;
2291553Srgrimes        if (option.hasArg || option.primaryName.endsWith(":")) {
2301553Srgrimes            String sep = (option == ToolOption.J) || option.primaryName.endsWith(":") ? "" : " ";
2311553Srgrimes            parameters = sep + option.getParameters(messager);
2321553Srgrimes        } else {
2331553Srgrimes            parameters = "";
2341553Srgrimes        }
2351553Srgrimes        String description = option.getDescription(messager);
2361553Srgrimes        showUsage(names, parameters, description);
2371553Srgrimes    }
2381553Srgrimes
2391553Srgrimes    void showDocletOptions(Option.Kind kind) {
2401553Srgrimes        Comparator<Doclet.Option> comp = new Comparator<Doclet.Option>() {
2411553Srgrimes            final Collator collator = Collator.getInstance(Locale.US);
2421553Srgrimes            { collator.setStrength(Collator.PRIMARY); }
2431553Srgrimes
2441553Srgrimes            @Override
2451553Srgrimes            public int compare(Doclet.Option o1, Doclet.Option o2) {
2461553Srgrimes                return collator.compare(o1.getName(), o2.getName());
2471553Srgrimes            }
2481553Srgrimes        };
2491553Srgrimes
2501553Srgrimes        doclet.getSupportedOptions().stream()
2511553Srgrimes                .filter(opt -> opt.getKind() == kind)
2521553Srgrimes                .sorted(comp)
2531553Srgrimes                .forEach(opt -> showDocletOption(opt));
2541553Srgrimes    }
2551553Srgrimes
2561553Srgrimes    void showDocletOption(Doclet.Option option) {
2571553Srgrimes        List<String> names = Arrays.asList(option.getName());
2581553Srgrimes        String parameters;
2591553Srgrimes        if (option.getArgumentCount() > 0 || option.getName().endsWith(":")) {
2601553Srgrimes            String sep = option.getName().endsWith(":") ? "" : " ";
2611553Srgrimes            parameters = sep + option.getParameters();
2621553Srgrimes        } else {
2631553Srgrimes            parameters = "";
2641553Srgrimes        }
2651553Srgrimes        String description = option.getDescription();
2661553Srgrimes        showUsage(names, parameters, description);
2671553Srgrimes    }
2681553Srgrimes
2691553Srgrimes    // The following constants are intended to format the output to
2701553Srgrimes    // be similar to that of the java launcher: i.e. "java -help".
2711553Srgrimes
2721553Srgrimes    /** The indent for the option synopsis. */
2731553Srgrimes    private static final String SMALL_INDENT = "    ";
2741553Srgrimes    /** The automatic indent for the description. */
2751553Srgrimes    private static final String LARGE_INDENT = "                  ";
2761553Srgrimes    /** The space allowed for the synopsis, if the description is to be shown on the same line. */
2771553Srgrimes    private static final int DEFAULT_SYNOPSIS_WIDTH = 13;
2781553Srgrimes    /** The nominal maximum line length, when seeing if text will fit on a line. */
2791553Srgrimes    private static final int DEFAULT_MAX_LINE_LENGTH = 80;
2801553Srgrimes    /** The format for a single-line help entry. */
2811553Srgrimes    private static final String COMPACT_FORMAT = SMALL_INDENT + "%-" + DEFAULT_SYNOPSIS_WIDTH + "s %s";
2821553Srgrimes
2831553Srgrimes    void showUsage(List<String> names, String parameters, String description) {
2841553Srgrimes        String synopses = names.stream()
2851553Srgrimes                .map(s -> s + parameters)
2861553Srgrimes                .collect(Collectors.joining(", "));
2871553Srgrimes        // If option synopses and description fit on a single line of reasonable length,
2881553Srgrimes        // display using COMPACT_FORMAT
2891553Srgrimes        if (synopses.length() < DEFAULT_SYNOPSIS_WIDTH
2901553Srgrimes                && !description.contains("\n")
2911553Srgrimes                && (SMALL_INDENT.length() + DEFAULT_SYNOPSIS_WIDTH + 1 + description.length() <= DEFAULT_MAX_LINE_LENGTH)) {
2921553Srgrimes            messager.printNotice(String.format(COMPACT_FORMAT, synopses, description));
2931553Srgrimes            return;
2941553Srgrimes        }
2951553Srgrimes
2961553Srgrimes        // If option synopses fit on a single line of reasonable length, show that;
2971553Srgrimes        // otherwise, show 1 per line
2981553Srgrimes        if (synopses.length() <= DEFAULT_MAX_LINE_LENGTH) {
2991553Srgrimes            messager.printNotice(SMALL_INDENT + synopses);
3001553Srgrimes        } else {
3011553Srgrimes            for (String name: names) {
3021553Srgrimes                messager.printNotice(SMALL_INDENT + name + parameters);
3031553Srgrimes            }
3041553Srgrimes        }
3051553Srgrimes
3061553Srgrimes        // Finally, show the description
3071553Srgrimes        messager.printNotice(LARGE_INDENT + description.replace("\n", "\n" + LARGE_INDENT));
3081553Srgrimes    }
3091553Srgrimes
3101553Srgrimes
3111553Srgrimes    /**
3121553Srgrimes     * Main program - external wrapper. In order to maintain backward
3131553Srgrimes     * CLI  compatibility, we dispatch to the old tool or the old doclet's
3141553Srgrimes     * Start mechanism, based on the options present on the command line
3151553Srgrimes     * with the following precedence:
3161553Srgrimes     *   1. presence of -Xold, dispatch to old tool
3171553Srgrimes     *   2. doclet variant, if old, dispatch to old Start
3181553Srgrimes     *   3. taglet variant, if old, dispatch to old Start
3191553Srgrimes     *
3201553Srgrimes     * Thus the presence of -Xold switches the tool, soon after command files
3211553Srgrimes     * if any, are expanded, this is performed here, noting that the messager
3221553Srgrimes     * is available at this point in time.
3231553Srgrimes     * The doclet/taglet tests are performed in the begin method, further on,
3241553Srgrimes     * this is to minimize argument processing and most importantly the impact
3251553Srgrimes     * of class loader creation, needed to detect the doclet/taglet class variants.
3261553Srgrimes     */
3271553Srgrimes    @SuppressWarnings("deprecation")
3281553Srgrimes    int begin(String... argv) {
3291553Srgrimes        // Preprocess @file arguments
3301553Srgrimes        try {
3311553Srgrimes            argv = CommandLine.parse(argv);
3321553Srgrimes        } catch (FileNotFoundException e) {
3331553Srgrimes            messager.error("main.cant.read", e.getMessage());
3341553Srgrimes            throw new Messager.ExitJavadoc();
3351553Srgrimes        } catch (IOException e) {
3361553Srgrimes            e.printStackTrace(System.err);
3371553Srgrimes            throw new Messager.ExitJavadoc();
3381553Srgrimes        }
3391553Srgrimes
3401553Srgrimes        if (argv.length > 0 && "-Xold".equals(argv[0])) {
3411553Srgrimes            messager.warning("main.legacy_api");
3421553Srgrimes            String[] nargv = Arrays.copyOfRange(argv, 1, argv.length);
3431553Srgrimes            return com.sun.tools.javadoc.Main.execute(nargv);
3441553Srgrimes        }
3451553Srgrimes        boolean ok = begin(Arrays.asList(argv), Collections.<JavaFileObject> emptySet());
3461553Srgrimes        return ok ? 0 : 1;
3471553Srgrimes    }
3481553Srgrimes
3491553Srgrimes    // Called by 199 API.
3501553Srgrimes    public boolean begin(Class<?> docletClass,
3511553Srgrimes            Iterable<String> options,
3521553Srgrimes            Iterable<? extends JavaFileObject> fileObjects) {
3531553Srgrimes        this.docletClass = docletClass;
3541553Srgrimes        List<String> opts = new ArrayList<>();
3551553Srgrimes        for (String opt: options)
3561553Srgrimes            opts.add(opt);
3571553Srgrimes
3581553Srgrimes        return begin(opts, fileObjects);
3591553Srgrimes    }
3601553Srgrimes
3611553Srgrimes    @SuppressWarnings("deprecation")
3621553Srgrimes    private boolean begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) {
3631553Srgrimes        fileManager = context.get(JavaFileManager.class);
3641553Srgrimes        if (fileManager == null) {
3651553Srgrimes            JavacFileManager.preRegister(context);
3661553Srgrimes            fileManager = context.get(JavaFileManager.class);
367            if (fileManager instanceof BaseFileManager) {
368                ((BaseFileManager) fileManager).autoClose = true;
369            }
370        }
371        // locale, doclet and maybe taglet, needs to be determined first
372        docletClass = preProcess(fileManager, options);
373        if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) {
374            // no need to dispatch to old, safe to init now
375            initMessager();
376            messager.setLocale(locale);
377            try {
378                Object o = docletClass.getConstructor().newInstance();
379                doclet = (Doclet) o;
380            } catch (ReflectiveOperationException exc) {
381                exc.printStackTrace();
382                if (!apiMode) {
383                    error("main.could_not_instantiate_class", docletClass);
384                    throw new Messager.ExitJavadoc();
385                }
386                throw new ClientCodeException(exc);
387            }
388        } else {
389            if (this.apiMode) {
390                com.sun.tools.javadoc.main.Start ostart
391                        = new com.sun.tools.javadoc.main.Start(context);
392                return ostart.begin(docletClass, options, fileObjects);
393            }
394            warn("main.legacy_api");
395            String[] array = options.toArray(new String[options.size()]);
396            return com.sun.tools.javadoc.Main.execute(array) == 0;
397        }
398
399        boolean failed = false;
400        try {
401            failed = !parseAndExecute(options, fileObjects);
402        } catch (Messager.ExitJavadoc exc) {
403            // ignore, we just exit this way
404        } catch (OutOfMemoryError ee) {
405            messager.error("main.out.of.memory");
406            failed = true;
407        } catch (ClientCodeException e) {
408            // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl
409            throw e;
410        } catch (Error ee) {
411            ee.printStackTrace(System.err);
412            messager.error("main.fatal.error");
413            failed = true;
414        } catch (Exception ee) {
415            ee.printStackTrace(System.err);
416            messager.error("main.fatal.exception");
417            failed = true;
418        } finally {
419            if (fileManager != null
420                    && fileManager instanceof BaseFileManager
421                    && ((BaseFileManager) fileManager).autoClose) {
422                try {
423                    fileManager.close();
424                } catch (IOException ignore) {}
425            }
426            boolean haveErrorWarnings = messager.nerrors() > 0 ||
427                    (rejectWarnings && messager.nwarnings() > 0);
428            if (failed && !haveErrorWarnings) {
429                // the doclet failed, but nothing reported, flag it!.
430                messager.error("main.unknown.error");
431            }
432            failed |= haveErrorWarnings;
433            messager.exitNotice();
434            messager.flush();
435        }
436        return !failed;
437    }
438
439    /**
440     * Main program - internal
441     */
442    @SuppressWarnings("unchecked")
443    private boolean parseAndExecute(List<String> argList,
444            Iterable<? extends JavaFileObject> fileObjects) throws IOException {
445        long tm = System.currentTimeMillis();
446
447        List<String> javaNames = new ArrayList<>();
448
449        compOpts = Options.instance(context);
450
451        // Make sure no obsolete source/target messages are reported
452        com.sun.tools.javac.main.Option.XLINT.process(getOptionHelper(), "-Xlint:-options");
453
454        doclet.init(locale, messager);
455        parseArgs(argList, javaNames);
456
457        if (fileManager instanceof BaseFileManager) {
458            ((BaseFileManager) fileManager).handleOptions(fileManagerOpts);
459        }
460
461        String platformString = compOpts.get("--release");
462
463        if (platformString != null) {
464            if (compOpts.isSet("-source")) {
465                usageError("main.release.bootclasspath.conflict", "-source");
466            }
467            if (fileManagerOpts.containsKey(BOOT_CLASS_PATH)) {
468                usageError("main.release.bootclasspath.conflict", BOOT_CLASS_PATH.getPrimaryName());
469            }
470
471            PlatformDescription platformDescription =
472                    PlatformUtils.lookupPlatformDescription(platformString);
473
474            if (platformDescription == null) {
475                usageError("main.unsupported.release.version", platformString);
476            }
477
478            compOpts.put(SOURCE, platformDescription.getSourceVersion());
479
480            context.put(PlatformDescription.class, platformDescription);
481
482            Collection<Path> platformCP = platformDescription.getPlatformPath();
483
484            if (platformCP != null) {
485                if (fileManager instanceof StandardJavaFileManager) {
486                    StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
487
488                    sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP);
489                } else {
490                    usageError("main.release.not.standard.file.manager", platformString);
491                }
492            }
493        }
494
495        compOpts.notifyListeners();
496        List<String> modules = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.MODULE,
497                s -> Collections.EMPTY_LIST);
498
499        if (modules.isEmpty()) {
500            List<String> subpkgs = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.SUBPACKAGES,
501                    s -> Collections.EMPTY_LIST);
502            if (subpkgs.isEmpty()) {
503                if (javaNames.isEmpty() && isEmpty(fileObjects)) {
504                    usageError("main.No_modules_packages_or_classes_specified");
505                }
506            }
507        }
508
509        JavadocTool comp = JavadocTool.make0(context);
510        if (comp == null) return false;
511
512        DocletEnvironment docEnv = comp.getEnvironment(jdtoolOpts,
513                javaNames,
514                fileObjects);
515
516        // release resources
517        comp = null;
518
519        if (breakiterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
520            JavacTrees trees = JavacTrees.instance(context);
521            trees.setBreakIterator(BreakIterator.getSentenceInstance(locale));
522        }
523        // pass off control to the doclet
524        boolean ok = docEnv != null;
525        if (ok) ok = doclet.run(docEnv);
526
527        // We're done.
528        if (compOpts.get("-verbose") != null) {
529            tm = System.currentTimeMillis() - tm;
530            messager.notice("main.done_in", Long.toString(tm));
531        }
532
533        return ok;
534    }
535
536    Set<Doclet.Option> docletOptions = null;
537    int handleDocletOptions(int idx, List<String> args, boolean isToolOption) {
538        if (docletOptions == null) {
539            docletOptions = doclet.getSupportedOptions();
540        }
541        String arg = args.get(idx);
542        String argBase, argVal;
543        if (arg.startsWith("--") && arg.contains("=")) {
544            int sep = arg.indexOf("=");
545            argBase = arg.substring(0, sep);
546            argVal = arg.substring(sep + 1);
547        } else {
548            argBase = arg;
549            argVal = null;
550        }
551
552        for (Doclet.Option opt : docletOptions) {
553            if (opt.matches(argBase)) {
554                if (argVal != null) {
555                    switch (opt.getArgumentCount()) {
556                        case 0:
557                            usageError("main.unnecessary_arg_provided", argBase);
558                            break;
559                        case 1:
560                            opt.process(arg, Arrays.asList(argVal).listIterator());
561                            break;
562                        default:
563                            usageError("main.only_one_argument_with_equals", argBase);
564                            break;
565                    }
566                } else {
567                    if (args.size() - idx -1 < opt.getArgumentCount()) {
568                        usageError("main.requires_argument", arg);
569                    }
570                    opt.process(arg, args.listIterator(idx + 1));
571                    idx += opt.getArgumentCount();
572                }
573                return idx;
574            }
575        }
576        // check if arg is accepted by the tool before emitting error
577        if (!isToolOption)
578            usageError("main.invalid_flag", arg);
579        return idx;
580    }
581
582    private Class<?> preProcess(JavaFileManager jfm, List<String> argv) {
583        // doclet specifying arguments
584        String userDocletPath = null;
585        String userDocletName = null;
586
587        // taglet specifying arguments, since tagletpath is a doclet
588        // functionality, assume they are repeated and inspect all.
589        List<File> userTagletPath = new ArrayList<>();
590        List<String> userTagletNames = new ArrayList<>();
591
592        // Step 1: loop through the args, set locale early on, if found.
593        for (int i = 0 ; i < argv.size() ; i++) {
594            String arg = argv.get(i);
595            if (arg.equals(ToolOption.LOCALE.primaryName)) {
596                checkOneArg(argv, i++);
597                String lname = argv.get(i);
598                locale = getLocale(lname);
599            } else if (arg.equals(ToolOption.DOCLET.primaryName)) {
600                checkOneArg(argv, i++);
601                if (userDocletName != null) {
602                    usageError("main.more_than_one_doclet_specified_0_and_1",
603                            userDocletName, argv.get(i));
604                }
605                if (docletName != null) {
606                    usageError("main.more_than_one_doclet_specified_0_and_1",
607                            docletName, argv.get(i));
608                }
609                userDocletName = argv.get(i);
610            } else if (arg.equals(ToolOption.DOCLETPATH.primaryName)) {
611                checkOneArg(argv, i++);
612                if (userDocletPath == null) {
613                    userDocletPath = argv.get(i);
614                } else {
615                    userDocletPath += File.pathSeparator + argv.get(i);
616                }
617            } else if ("-taglet".equals(arg)) {
618                userTagletNames.add(argv.get(i + 1));
619            } else if ("-tagletpath".equals(arg)) {
620                for (String pathname : argv.get(i + 1).split(File.pathSeparator)) {
621                    userTagletPath.add(new File(pathname));
622                }
623            }
624        }
625
626        // Step 2: a doclet is provided, nothing more to do.
627        if (docletClass != null) {
628            return docletClass;
629        }
630
631        // Step 3: doclet name specified ? if so find a ClassLoader,
632        // and load it.
633        if (userDocletName != null) {
634            ClassLoader cl = classLoader;
635            if (cl == null) {
636                if (!fileManager.hasLocation(DOCLET_PATH)) {
637                    List<File> paths = new ArrayList<>();
638                    if (userDocletPath != null) {
639                        for (String pathname : userDocletPath.split(File.pathSeparator)) {
640                            paths.add(new File(pathname));
641                        }
642                    }
643                    try {
644                        ((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths);
645                    } catch (IOException ioe) {
646                        error("main.doclet_could_not_set_location", paths);
647                        throw new Messager.ExitJavadoc();
648                    }
649                }
650                cl = fileManager.getClassLoader(DOCLET_PATH);
651                if (cl == null) {
652                    // despite doclet specified on cmdline no classloader found!
653                    error("main.doclet_no_classloader_found", userDocletName);
654                    throw new Messager.ExitJavadoc();
655                }
656            }
657            try {
658                Class<?> klass = cl.loadClass(userDocletName);
659                return klass;
660            } catch (ClassNotFoundException cnfe) {
661                error("main.doclet_class_not_found", userDocletName);
662                throw new Messager.ExitJavadoc();
663            }
664        }
665
666        // Step 4: we have a doclet, try loading it
667        if (docletName != null) {
668            try {
669                return Class.forName(docletName, true, getClass().getClassLoader());
670            } catch (ClassNotFoundException cnfe) {
671                error("main.doclet_class_not_found", userDocletName);
672                throw new Messager.ExitJavadoc();
673            }
674        }
675
676        // Step 5: we don't have a doclet specified, do we have taglets ?
677        if (!userTagletNames.isEmpty() && hasOldTaglet(userTagletNames, userTagletPath)) {
678            // found a bogey, return the old doclet
679            return OldStdDoclet;
680        }
681
682        // finally
683        return StdDoclet;
684    }
685
686    /*
687     * This method returns true iff it finds a legacy taglet, but for
688     * all other conditions including errors it returns false, allowing
689     * nature to take its own course.
690     */
691    @SuppressWarnings("deprecation")
692    private boolean hasOldTaglet(List<String> tagletNames, List<File> tagletPaths) {
693        if (!fileManager.hasLocation(TAGLET_PATH)) {
694            try {
695                ((StandardJavaFileManager) fileManager).setLocation(TAGLET_PATH, tagletPaths);
696            } catch (IOException ioe) {
697                error("main.doclet_could_not_set_location", tagletPaths);
698                throw new Messager.ExitJavadoc();
699            }
700        }
701        ClassLoader cl = fileManager.getClassLoader(TAGLET_PATH);
702        if (cl == null) {
703            // no classloader found!
704            error("main.doclet_no_classloader_found", tagletNames.get(0));
705            throw new Messager.ExitJavadoc();
706        }
707        for (String tagletName : tagletNames) {
708            try {
709                Class<?> klass = cl.loadClass(tagletName);
710                if (com.sun.tools.doclets.Taglet.class.isAssignableFrom(klass)) {
711                    return true;
712                }
713            } catch (ClassNotFoundException cnfe) {
714                error("main.doclet_class_not_found", tagletName);
715                throw new Messager.ExitJavadoc();
716            }
717        }
718        return false;
719    }
720
721    private void parseArgs(List<String> args, List<String> javaNames) {
722        for (int i = 0 ; i < args.size() ; i++) {
723            String arg = args.get(i);
724            ToolOption o = ToolOption.get(arg);
725            if (o != null) {
726                // handle a doclet argument that may be needed however
727                // don't increment the index, and allow the tool to consume args
728                handleDocletOptions(i, args, true);
729
730                if (o.hasArg) {
731                    if (arg.startsWith("--") && arg.contains("=")) {
732                        o.process(this, arg.substring(arg.indexOf('=') + 1));
733                    } else {
734                        checkOneArg(args, i++);
735                        o.process(this, args.get(i));
736                    }
737                } else if (o.hasSuffix) {
738                    o.process(this, arg);
739                } else {
740                    o.process(this);
741                }
742            } else if (arg.startsWith("-XD")) {
743                // hidden javac options
744                String s = arg.substring("-XD".length());
745                int eq = s.indexOf('=');
746                String key = (eq < 0) ? s : s.substring(0, eq);
747                String value = (eq < 0) ? s : s.substring(eq+1);
748                compOpts.put(key, value);
749            } else if (arg.startsWith("-")) {
750                i = handleDocletOptions(i, args, false);
751            } else {
752                javaNames.add(arg);
753            }
754        }
755    }
756
757    private <T> boolean isEmpty(Iterable<T> iter) {
758        return !iter.iterator().hasNext();
759    }
760
761    /**
762     * Check the one arg option.
763     * Error and exit if one argument is not provided.
764     */
765    private void checkOneArg(List<String> args, int index) {
766        if ((index + 1) >= args.size() || args.get(index + 1).startsWith("-d")) {
767            usageError("main.requires_argument", args.get(index));
768        }
769    }
770
771    @Override
772    void usageError(String key, Object... args) {
773        error(key, args);
774        usage(true);
775    }
776
777    void error(String key, Object... args) {
778        messager.error(key, args);
779    }
780
781    void warn(String key, Object... args)  {
782        messager.warning(key, args);
783    }
784
785    /**
786     * Get the locale if specified on the command line
787     * else return null and if locale option is not used
788     * then return default locale.
789     */
790    private Locale getLocale(String localeName) {
791        Locale userlocale = null;
792        if (localeName == null || localeName.isEmpty()) {
793            return Locale.getDefault();
794        }
795        int firstuscore = localeName.indexOf('_');
796        int seconduscore = -1;
797        String language = null;
798        String country = null;
799        String variant = null;
800        if (firstuscore == 2) {
801            language = localeName.substring(0, firstuscore);
802            seconduscore = localeName.indexOf('_', firstuscore + 1);
803            if (seconduscore > 0) {
804                if (seconduscore != firstuscore + 3
805                        || localeName.length() <= seconduscore + 1) {
806                    usageError("main.malformed_locale_name", localeName);
807                    return null;
808                }
809                country = localeName.substring(firstuscore + 1,
810                        seconduscore);
811                variant = localeName.substring(seconduscore + 1);
812            } else if (localeName.length() == firstuscore + 3) {
813                country = localeName.substring(firstuscore + 1);
814            } else {
815                usageError("main.malformed_locale_name", localeName);
816                return null;
817            }
818        } else if (firstuscore == -1 && localeName.length() == 2) {
819            language = localeName;
820        } else {
821            usageError("main.malformed_locale_name", localeName);
822            return null;
823        }
824        userlocale = searchLocale(language, country, variant);
825        if (userlocale == null) {
826            usageError("main.illegal_locale_name", localeName);
827            return null;
828        } else {
829            return userlocale;
830        }
831    }
832
833    /**
834     * Search the locale for specified language, specified country and
835     * specified variant.
836     */
837    private Locale searchLocale(String language, String country,
838                                String variant) {
839        for (Locale loc : Locale.getAvailableLocales()) {
840            if (loc.getLanguage().equals(language) &&
841                (country == null || loc.getCountry().equals(country)) &&
842                (variant == null || loc.getVariant().equals(variant))) {
843                return loc;
844            }
845        }
846        return null;
847    }
848
849    @Override
850    OptionHelper getOptionHelper() {
851        return new GrumpyHelper(null) {
852            @Override
853            public String get(com.sun.tools.javac.main.Option option) {
854                return compOpts.get(option);
855            }
856
857            @Override
858            public void put(String name, String value) {
859                compOpts.put(name, value);
860            }
861        };
862    }
863}
864