Start.java revision 3688:7752e4f2a338
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.FileNotFoundException;
30import java.io.IOException;
31import java.io.PrintWriter;
32import java.nio.file.Path;
33import java.text.BreakIterator;
34import java.text.Collator;
35import java.util.ArrayList;
36import java.util.Arrays;
37import java.util.Collection;
38import java.util.Collections;
39import java.util.Comparator;
40import java.util.List;
41import java.util.Locale;
42import java.util.Objects;
43import java.util.Set;
44import java.util.stream.Collectors;
45import java.util.stream.Stream;
46
47import javax.tools.JavaFileManager;
48import javax.tools.JavaFileObject;
49import javax.tools.StandardJavaFileManager;
50import javax.tools.StandardLocation;
51
52import com.sun.tools.javac.api.JavacTrees;
53import com.sun.tools.javac.file.BaseFileManager;
54import com.sun.tools.javac.file.JavacFileManager;
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.Options;
64
65import jdk.javadoc.doclet.Doclet;
66import jdk.javadoc.doclet.Doclet.Option;
67import jdk.javadoc.doclet.DocletEnvironment;
68import jdk.javadoc.internal.doclets.toolkit.Resources;
69
70import static javax.tools.DocumentationTool.Location.*;
71
72import static com.sun.tools.javac.main.Option.*;
73
74/**
75 * Main program of Javadoc.
76 * Previously named "Main".
77 *
78 *  <p><b>This is NOT part of any supported API.
79 *  If you write code that depends on this, you do so at your own risk.
80 *  This code and its internal interfaces are subject to change or
81 *  deletion without notice.</b>
82 *
83 * @author Robert Field
84 * @author Neal Gafter (rewrite)
85 */
86public class Start extends ToolOption.Helper {
87
88    @SuppressWarnings("deprecation")
89    private static final Class<?> OldStdDoclet =
90            com.sun.tools.doclets.standard.Standard.class;
91
92    private static final Class<?> StdDoclet =
93            jdk.javadoc.doclets.StandardDoclet.class;
94    /** Context for this invocation. */
95    private final Context context;
96
97    private static final String ProgramName = "javadoc";
98
99    private Messager messager;
100
101    private final String docletName;
102
103    private final ClassLoader classLoader;
104
105    private Class<?> docletClass;
106
107    private Doclet doclet;
108
109    // used to determine the locale for the messager
110    private Locale locale;
111
112
113    /**
114     * In API mode, exceptions thrown while calling the doclet are
115     * propagated using ClientCodeException.
116     */
117    private boolean apiMode;
118
119    private JavaFileManager fileManager;
120
121    Start() {
122        this(null, null, null, null, null, null);
123    }
124
125    Start(PrintWriter outWriter, PrintWriter errWriter) {
126        this(null, null, outWriter, errWriter, null, null);
127    }
128
129    Start(Context context, String programName,
130            PrintWriter outWriter, PrintWriter errWriter,
131            String docletName, ClassLoader classLoader) {
132        this.context = context == null ? new Context() : context;
133        String pname = programName == null ? ProgramName : programName;
134        this.messager = (outWriter == null && errWriter == null)
135                ? new Messager(this.context, pname)
136                : new Messager(this.context, pname, outWriter, errWriter);
137        this.docletName = docletName;
138        this.classLoader = classLoader;
139        this.docletClass = null;
140        this.locale = Locale.getDefault();
141    }
142
143    public Start(Context context) {
144        this.docletClass = null;
145        this.context = Objects.requireNonNull(context);
146        this.apiMode = true;
147        this.docletName = null;
148        this.classLoader = null;
149        this.locale = Locale.getDefault();
150    }
151
152    void initMessager() {
153        if (!apiMode)
154            return;
155        if (messager == null) {
156            Log log = context.get(Log.logKey);
157            if (log instanceof Messager) {
158                messager = (Messager) log;
159            } else {
160                PrintWriter out = context.get(Log.errKey);
161                messager = (out == null)
162                        ? new Messager(context, ProgramName)
163                        : new Messager(context, ProgramName, out, out);
164            }
165        }
166    }
167
168    /**
169     * Usage
170     */
171    @Override
172    void usage() {
173        usage(true);
174    }
175
176    void usage(boolean exit) {
177        usage("main.usage", "-help", "main.usage.foot");
178
179        if (exit)
180            throw new Messager.ExitJavadoc();
181    }
182
183    @Override
184    void Xusage() {
185        Xusage(true);
186    }
187
188    void Xusage(boolean exit) {
189        usage("main.Xusage", "-X", "main.Xusage.foot");
190
191        if (exit)
192            throw new Messager.ExitJavadoc();
193    }
194
195    private void usage(String header, String option, String footer) {
196        messager.notice(header);
197        showToolOptions(option.equals("-X") ? OptionKind.EXTENDED : OptionKind.STANDARD);
198
199        // let doclet print usage information
200        if (docletClass != null) {
201            String name = doclet.getName();
202            messager.notice("main.doclet.usage.header", name);
203            showDocletOptions(option.equals("-X") ? Option.Kind.EXTENDED : Option.Kind.STANDARD);
204        }
205
206        if (footer != null)
207            messager.notice(footer);
208    }
209
210    void showToolOptions(OptionKind kind) {
211        Comparator<ToolOption> comp = new Comparator<ToolOption>() {
212            final Collator collator = Collator.getInstance(Locale.US);
213            { collator.setStrength(Collator.PRIMARY); }
214
215            @Override
216            public int compare(ToolOption o1, ToolOption o2) {
217                return collator.compare(o1.primaryName, o2.primaryName);
218            }
219        };
220
221        Stream.of(ToolOption.values())
222                    .filter(opt -> opt.kind == kind)
223                    .sorted(comp)
224                    .forEach(opt -> showToolOption(opt));
225    }
226
227    void showToolOption(ToolOption option) {
228        List<String> names = option.getNames();
229        String parameters;
230        if (option.hasArg || option.primaryName.endsWith(":")) {
231            String sep = (option == ToolOption.J) || option.primaryName.endsWith(":") ? "" : " ";
232            parameters = sep + option.getParameters(messager);
233        } else {
234            parameters = "";
235        }
236        String description = option.getDescription(messager);
237        showUsage(names, parameters, description);
238    }
239
240    void showDocletOptions(Option.Kind kind) {
241        Comparator<Doclet.Option> comp = new Comparator<Doclet.Option>() {
242            final Collator collator = Collator.getInstance(Locale.US);
243            { collator.setStrength(Collator.PRIMARY); }
244
245            @Override
246            public int compare(Doclet.Option o1, Doclet.Option o2) {
247                return collator.compare(o1.getName(), o2.getName());
248            }
249        };
250
251        doclet.getSupportedOptions().stream()
252                .filter(opt -> opt.getKind() == kind)
253                .sorted(comp)
254                .forEach(opt -> showDocletOption(opt));
255    }
256
257    void showDocletOption(Doclet.Option option) {
258        List<String> names = Arrays.asList(option.getName());
259        String parameters;
260        if (option.getArgumentCount() > 0 || option.getName().endsWith(":")) {
261            String sep = option.getName().endsWith(":") ? "" : " ";
262            parameters = sep + option.getParameters();
263        } else {
264            parameters = "";
265        }
266        String description = option.getDescription();
267        showUsage(names, parameters, description);
268    }
269
270    // The following constants are intended to format the output to
271    // be similar to that of the java launcher: i.e. "java -help".
272
273    /** The indent for the option synopsis. */
274    private static final String SMALL_INDENT = "    ";
275    /** The automatic indent for the description. */
276    private static final String LARGE_INDENT = "                  ";
277    /** The space allowed for the synopsis, if the description is to be shown on the same line. */
278    private static final int DEFAULT_SYNOPSIS_WIDTH = 13;
279    /** The nominal maximum line length, when seeing if text will fit on a line. */
280    private static final int DEFAULT_MAX_LINE_LENGTH = 80;
281    /** The format for a single-line help entry. */
282    private static final String COMPACT_FORMAT = SMALL_INDENT + "%-" + DEFAULT_SYNOPSIS_WIDTH + "s %s";
283
284    void showUsage(List<String> names, String parameters, String description) {
285        String synopses = names.stream()
286                .map(s -> s + parameters)
287                .collect(Collectors.joining(", "));
288        // If option synopses and description fit on a single line of reasonable length,
289        // display using COMPACT_FORMAT
290        if (synopses.length() < DEFAULT_SYNOPSIS_WIDTH
291                && !description.contains("\n")
292                && (SMALL_INDENT.length() + DEFAULT_SYNOPSIS_WIDTH + 1 + description.length() <= DEFAULT_MAX_LINE_LENGTH)) {
293            messager.printNotice(String.format(COMPACT_FORMAT, synopses, description));
294            return;
295        }
296
297        // If option synopses fit on a single line of reasonable length, show that;
298        // otherwise, show 1 per line
299        if (synopses.length() <= DEFAULT_MAX_LINE_LENGTH) {
300            messager.printNotice(SMALL_INDENT + synopses);
301        } else {
302            for (String name: names) {
303                messager.printNotice(SMALL_INDENT + name + parameters);
304            }
305        }
306
307        // Finally, show the description
308        messager.printNotice(LARGE_INDENT + description.replace("\n", "\n" + LARGE_INDENT));
309    }
310
311
312    /**
313     * Main program - external wrapper. In order to maintain backward
314     * CLI  compatibility, we dispatch to the old tool or the old doclet's
315     * Start mechanism, based on the options present on the command line
316     * with the following precedence:
317     *   1. presence of -Xold, dispatch to old tool
318     *   2. doclet variant, if old, dispatch to old Start
319     *   3. taglet variant, if old, dispatch to old Start
320     *
321     * Thus the presence of -Xold switches the tool, soon after command files
322     * if any, are expanded, this is performed here, noting that the messager
323     * is available at this point in time.
324     * The doclet/taglet tests are performed in the begin method, further on,
325     * this is to minimize argument processing and most importantly the impact
326     * of class loader creation, needed to detect the doclet/taglet class variants.
327     */
328    @SuppressWarnings("deprecation")
329    int begin(String... argv) {
330        // Preprocess @file arguments
331        try {
332            argv = CommandLine.parse(argv);
333        } catch (FileNotFoundException e) {
334            messager.error("main.cant.read", e.getMessage());
335            throw new Messager.ExitJavadoc();
336        } catch (IOException e) {
337            e.printStackTrace(System.err);
338            throw new Messager.ExitJavadoc();
339        }
340
341        if (argv.length > 0 && "-Xold".equals(argv[0])) {
342            messager.warning("main.legacy_api");
343            String[] nargv = Arrays.copyOfRange(argv, 1, argv.length);
344            return com.sun.tools.javadoc.Main.execute(nargv);
345        }
346        boolean ok = begin(Arrays.asList(argv), Collections.<JavaFileObject> emptySet());
347        return ok ? 0 : 1;
348    }
349
350    // Called by 199 API.
351    public boolean begin(Class<?> docletClass,
352            Iterable<String> options,
353            Iterable<? extends JavaFileObject> fileObjects) {
354        this.docletClass = docletClass;
355        List<String> opts = new ArrayList<>();
356        for (String opt: options)
357            opts.add(opt);
358
359        return begin(opts, fileObjects);
360    }
361
362    @SuppressWarnings("deprecation")
363    private boolean begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) {
364        fileManager = context.get(JavaFileManager.class);
365        if (fileManager == null) {
366            JavacFileManager.preRegister(context);
367            fileManager = context.get(JavaFileManager.class);
368            if (fileManager instanceof BaseFileManager) {
369                ((BaseFileManager) fileManager).autoClose = true;
370            }
371        }
372        // locale, doclet and maybe taglet, needs to be determined first
373        docletClass = preProcess(fileManager, options);
374        if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) {
375            // no need to dispatch to old, safe to init now
376            initMessager();
377            messager.setLocale(locale);
378            try {
379                Object o = docletClass.getConstructor().newInstance();
380                doclet = (Doclet) o;
381            } catch (ReflectiveOperationException exc) {
382                exc.printStackTrace();
383                if (!apiMode) {
384                    error("main.could_not_instantiate_class", docletClass);
385                    throw new Messager.ExitJavadoc();
386                }
387                throw new ClientCodeException(exc);
388            }
389        } else {
390            if (this.apiMode) {
391                com.sun.tools.javadoc.main.Start ostart
392                        = new com.sun.tools.javadoc.main.Start(context);
393                return ostart.begin(docletClass, options, fileObjects);
394            }
395            warn("main.legacy_api");
396            String[] array = options.toArray(new String[options.size()]);
397            return com.sun.tools.javadoc.Main.execute(array) == 0;
398        }
399
400        boolean failed = false;
401        try {
402            failed = !parseAndExecute(options, fileObjects);
403        } catch (Messager.ExitJavadoc exc) {
404            // ignore, we just exit this way
405        } catch (OutOfMemoryError ee) {
406            messager.error("main.out.of.memory");
407            failed = true;
408        } catch (ClientCodeException e) {
409            // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl
410            throw e;
411        } catch (Error ee) {
412            ee.printStackTrace(System.err);
413            messager.error("main.fatal.error");
414            failed = true;
415        } catch (Exception ee) {
416            ee.printStackTrace(System.err);
417            messager.error("main.fatal.exception");
418            failed = true;
419        } finally {
420            if (fileManager != null
421                    && fileManager instanceof BaseFileManager
422                    && ((BaseFileManager) fileManager).autoClose) {
423                try {
424                    fileManager.close();
425                } catch (IOException ignore) {}
426            }
427            boolean haveErrorWarnings = messager.nerrors() > 0 ||
428                    (rejectWarnings && messager.nwarnings() > 0);
429            if (failed && !haveErrorWarnings) {
430                // the doclet failed, but nothing reported, flag it!.
431                messager.error("main.unknown.error");
432            }
433            failed |= haveErrorWarnings;
434            messager.exitNotice();
435            messager.flush();
436        }
437        return !failed;
438    }
439
440    /**
441     * Main program - internal
442     */
443    @SuppressWarnings("unchecked")
444    private boolean parseAndExecute(List<String> argList,
445            Iterable<? extends JavaFileObject> fileObjects) throws IOException {
446        long tm = System.currentTimeMillis();
447
448        List<String> javaNames = new ArrayList<>();
449
450        compOpts = Options.instance(context);
451
452        // Make sure no obsolete source/target messages are reported
453        com.sun.tools.javac.main.Option.XLINT.process(getOptionHelper(), "-Xlint:-options");
454
455        doclet.init(locale, messager);
456        parseArgs(argList, javaNames);
457
458        if (fileManager instanceof BaseFileManager) {
459            ((BaseFileManager) fileManager).handleOptions(fileManagerOpts);
460        }
461
462        String platformString = compOpts.get("--release");
463
464        if (platformString != null) {
465            if (compOpts.isSet("-source")) {
466                usageError("main.release.bootclasspath.conflict", "-source");
467            }
468            if (fileManagerOpts.containsKey(BOOT_CLASS_PATH)) {
469                usageError("main.release.bootclasspath.conflict", BOOT_CLASS_PATH.getPrimaryName());
470            }
471
472            PlatformDescription platformDescription =
473                    PlatformUtils.lookupPlatformDescription(platformString);
474
475            if (platformDescription == null) {
476                usageError("main.unsupported.release.version", platformString);
477            }
478
479            compOpts.put(SOURCE, platformDescription.getSourceVersion());
480
481            context.put(PlatformDescription.class, platformDescription);
482
483            Collection<Path> platformCP = platformDescription.getPlatformPath();
484
485            if (platformCP != null) {
486                if (fileManager instanceof StandardJavaFileManager) {
487                    StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
488
489                    sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP);
490                } else {
491                    usageError("main.release.not.standard.file.manager", platformString);
492                }
493            }
494        }
495
496        compOpts.notifyListeners();
497        List<String> modules = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.MODULE,
498                s -> Collections.EMPTY_LIST);
499
500        if (modules.isEmpty()) {
501            List<String> subpkgs = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.SUBPACKAGES,
502                    s -> Collections.EMPTY_LIST);
503            if (subpkgs.isEmpty()) {
504                if (javaNames.isEmpty() && isEmpty(fileObjects)) {
505                    usageError("main.No_modules_packages_or_classes_specified");
506                }
507            }
508        }
509
510        JavadocTool comp = JavadocTool.make0(context);
511        if (comp == null) return false;
512
513        DocletEnvironment docEnv = comp.getEnvironment(jdtoolOpts,
514                javaNames,
515                fileObjects);
516
517        // release resources
518        comp = null;
519
520        if (breakiterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
521            JavacTrees trees = JavacTrees.instance(context);
522            trees.setBreakIterator(BreakIterator.getSentenceInstance(locale));
523        }
524        // pass off control to the doclet
525        boolean ok = docEnv != null;
526        if (ok) ok = doclet.run(docEnv);
527
528        // We're done.
529        if (compOpts.get("-verbose") != null) {
530            tm = System.currentTimeMillis() - tm;
531            messager.notice("main.done_in", Long.toString(tm));
532        }
533
534        return ok;
535    }
536
537    Set<Doclet.Option> docletOptions = null;
538    int handleDocletOptions(int idx, List<String> args, boolean isToolOption) {
539        if (docletOptions == null) {
540            docletOptions = doclet.getSupportedOptions();
541        }
542        String arg = args.get(idx);
543        String argBase, argVal;
544        if (arg.startsWith("--") && arg.contains("=")) {
545            int sep = arg.indexOf("=");
546            argBase = arg.substring(0, sep);
547            argVal = arg.substring(sep + 1);
548        } else {
549            argBase = arg;
550            argVal = null;
551        }
552
553        for (Doclet.Option opt : docletOptions) {
554            if (opt.matches(argBase)) {
555                if (argVal != null) {
556                    switch (opt.getArgumentCount()) {
557                        case 0:
558                            usageError("main.unnecessary_arg_provided", argBase);
559                            break;
560                        case 1:
561                            opt.process(arg, Arrays.asList(argVal).listIterator());
562                            break;
563                        default:
564                            usageError("main.only_one_argument_with_equals", argBase);
565                            break;
566                    }
567                } else {
568                    if (args.size() - idx -1 < opt.getArgumentCount()) {
569                        usageError("main.requires_argument", arg);
570                    }
571                    opt.process(arg, args.listIterator(idx + 1));
572                    idx += opt.getArgumentCount();
573                }
574                return idx;
575            }
576        }
577        // check if arg is accepted by the tool before emitting error
578        if (!isToolOption)
579            usageError("main.invalid_flag", arg);
580        return idx;
581    }
582
583    private Class<?> preProcess(JavaFileManager jfm, List<String> argv) {
584        // doclet specifying arguments
585        String userDocletPath = null;
586        String userDocletName = null;
587
588        // taglet specifying arguments, since tagletpath is a doclet
589        // functionality, assume they are repeated and inspect all.
590        List<File> userTagletPath = new ArrayList<>();
591        List<String> userTagletNames = new ArrayList<>();
592
593        // Step 1: loop through the args, set locale early on, if found.
594        for (int i = 0 ; i < argv.size() ; i++) {
595            String arg = argv.get(i);
596            if (arg.equals(ToolOption.LOCALE.primaryName)) {
597                checkOneArg(argv, i++);
598                String lname = argv.get(i);
599                locale = getLocale(lname);
600            } else if (arg.equals(ToolOption.DOCLET.primaryName)) {
601                checkOneArg(argv, i++);
602                if (userDocletName != null) {
603                    usageError("main.more_than_one_doclet_specified_0_and_1",
604                            userDocletName, argv.get(i));
605                }
606                if (docletName != null) {
607                    usageError("main.more_than_one_doclet_specified_0_and_1",
608                            docletName, argv.get(i));
609                }
610                userDocletName = argv.get(i);
611            } else if (arg.equals(ToolOption.DOCLETPATH.primaryName)) {
612                checkOneArg(argv, i++);
613                if (userDocletPath == null) {
614                    userDocletPath = argv.get(i);
615                } else {
616                    userDocletPath += File.pathSeparator + argv.get(i);
617                }
618            } else if ("-taglet".equals(arg)) {
619                userTagletNames.add(argv.get(i + 1));
620            } else if ("-tagletpath".equals(arg)) {
621                for (String pathname : argv.get(i + 1).split(File.pathSeparator)) {
622                    userTagletPath.add(new File(pathname));
623                }
624            }
625        }
626
627        // Step 2: a doclet is provided, nothing more to do.
628        if (docletClass != null) {
629            return docletClass;
630        }
631
632        // Step 3: doclet name specified ? if so find a ClassLoader,
633        // and load it.
634        if (userDocletName != null) {
635            ClassLoader cl = classLoader;
636            if (cl == null) {
637                if (!fileManager.hasLocation(DOCLET_PATH)) {
638                    List<File> paths = new ArrayList<>();
639                    if (userDocletPath != null) {
640                        for (String pathname : userDocletPath.split(File.pathSeparator)) {
641                            paths.add(new File(pathname));
642                        }
643                    }
644                    try {
645                        ((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths);
646                    } catch (IOException ioe) {
647                        error("main.doclet_could_not_set_location", paths);
648                        throw new Messager.ExitJavadoc();
649                    }
650                }
651                cl = fileManager.getClassLoader(DOCLET_PATH);
652                if (cl == null) {
653                    // despite doclet specified on cmdline no classloader found!
654                    error("main.doclet_no_classloader_found", userDocletName);
655                    throw new Messager.ExitJavadoc();
656                }
657            }
658            try {
659                Class<?> klass = cl.loadClass(userDocletName);
660                return klass;
661            } catch (ClassNotFoundException cnfe) {
662                error("main.doclet_class_not_found", userDocletName);
663                throw new Messager.ExitJavadoc();
664            }
665        }
666
667        // Step 4: we have a doclet, try loading it
668        if (docletName != null) {
669            try {
670                return Class.forName(docletName, true, getClass().getClassLoader());
671            } catch (ClassNotFoundException cnfe) {
672                error("main.doclet_class_not_found", userDocletName);
673                throw new Messager.ExitJavadoc();
674            }
675        }
676
677        // Step 5: we don't have a doclet specified, do we have taglets ?
678        if (!userTagletNames.isEmpty() && hasOldTaglet(userTagletNames, userTagletPath)) {
679            // found a bogey, return the old doclet
680            return OldStdDoclet;
681        }
682
683        // finally
684        return StdDoclet;
685    }
686
687    /*
688     * This method returns true iff it finds a legacy taglet, but for
689     * all other conditions including errors it returns false, allowing
690     * nature to take its own course.
691     */
692    @SuppressWarnings("deprecation")
693    private boolean hasOldTaglet(List<String> tagletNames, List<File> tagletPaths) {
694        if (!fileManager.hasLocation(TAGLET_PATH)) {
695            try {
696                ((StandardJavaFileManager) fileManager).setLocation(TAGLET_PATH, tagletPaths);
697            } catch (IOException ioe) {
698                error("main.doclet_could_not_set_location", tagletPaths);
699                throw new Messager.ExitJavadoc();
700            }
701        }
702        ClassLoader cl = fileManager.getClassLoader(TAGLET_PATH);
703        if (cl == null) {
704            // no classloader found!
705            error("main.doclet_no_classloader_found", tagletNames.get(0));
706            throw new Messager.ExitJavadoc();
707        }
708        for (String tagletName : tagletNames) {
709            try {
710                Class<?> klass = cl.loadClass(tagletName);
711                if (com.sun.tools.doclets.Taglet.class.isAssignableFrom(klass)) {
712                    return true;
713                }
714            } catch (ClassNotFoundException cnfe) {
715                error("main.doclet_class_not_found", tagletName);
716                throw new Messager.ExitJavadoc();
717            }
718        }
719        return false;
720    }
721
722    private void parseArgs(List<String> args, List<String> javaNames) {
723        for (int i = 0 ; i < args.size() ; i++) {
724            String arg = args.get(i);
725            ToolOption o = ToolOption.get(arg);
726            if (o != null) {
727                // handle a doclet argument that may be needed however
728                // don't increment the index, and allow the tool to consume args
729                handleDocletOptions(i, args, true);
730
731                if (o.hasArg) {
732                    if (arg.startsWith("--") && arg.contains("=")) {
733                        o.process(this, arg.substring(arg.indexOf('=') + 1));
734                    } else {
735                        checkOneArg(args, i++);
736                        o.process(this, args.get(i));
737                    }
738                } else if (o.hasSuffix) {
739                    o.process(this, arg);
740                } else {
741                    o.process(this);
742                }
743            } else if (arg.startsWith("-XD")) {
744                // hidden javac options
745                String s = arg.substring("-XD".length());
746                int eq = s.indexOf('=');
747                String key = (eq < 0) ? s : s.substring(0, eq);
748                String value = (eq < 0) ? s : s.substring(eq+1);
749                compOpts.put(key, value);
750            } else if (arg.startsWith("-")) {
751                i = handleDocletOptions(i, args, false);
752            } else {
753                javaNames.add(arg);
754            }
755        }
756    }
757
758    private <T> boolean isEmpty(Iterable<T> iter) {
759        return !iter.iterator().hasNext();
760    }
761
762    /**
763     * Check the one arg option.
764     * Error and exit if one argument is not provided.
765     */
766    private void checkOneArg(List<String> args, int index) {
767        if ((index + 1) >= args.size() || args.get(index + 1).startsWith("-d")) {
768            usageError("main.requires_argument", args.get(index));
769        }
770    }
771
772    @Override
773    void usageError(String key, Object... args) {
774        error(key, args);
775        usage(true);
776    }
777
778    void error(String key, Object... args) {
779        messager.error(key, args);
780    }
781
782    void warn(String key, Object... args)  {
783        messager.warning(key, args);
784    }
785
786    /**
787     * Get the locale if specified on the command line
788     * else return null and if locale option is not used
789     * then return default locale.
790     */
791    private Locale getLocale(String localeName) {
792        Locale userlocale = null;
793        if (localeName == null || localeName.isEmpty()) {
794            return Locale.getDefault();
795        }
796        int firstuscore = localeName.indexOf('_');
797        int seconduscore = -1;
798        String language = null;
799        String country = null;
800        String variant = null;
801        if (firstuscore == 2) {
802            language = localeName.substring(0, firstuscore);
803            seconduscore = localeName.indexOf('_', firstuscore + 1);
804            if (seconduscore > 0) {
805                if (seconduscore != firstuscore + 3
806                        || localeName.length() <= seconduscore + 1) {
807                    usageError("main.malformed_locale_name", localeName);
808                    return null;
809                }
810                country = localeName.substring(firstuscore + 1,
811                        seconduscore);
812                variant = localeName.substring(seconduscore + 1);
813            } else if (localeName.length() == firstuscore + 3) {
814                country = localeName.substring(firstuscore + 1);
815            } else {
816                usageError("main.malformed_locale_name", localeName);
817                return null;
818            }
819        } else if (firstuscore == -1 && localeName.length() == 2) {
820            language = localeName;
821        } else {
822            usageError("main.malformed_locale_name", localeName);
823            return null;
824        }
825        userlocale = searchLocale(language, country, variant);
826        if (userlocale == null) {
827            usageError("main.illegal_locale_name", localeName);
828            return null;
829        } else {
830            return userlocale;
831        }
832    }
833
834    /**
835     * Search the locale for specified language, specified country and
836     * specified variant.
837     */
838    private Locale searchLocale(String language, String country,
839                                String variant) {
840        for (Locale loc : Locale.getAvailableLocales()) {
841            if (loc.getLanguage().equals(language) &&
842                (country == null || loc.getCountry().equals(country)) &&
843                (variant == null || loc.getVariant().equals(variant))) {
844                return loc;
845            }
846        }
847        return null;
848    }
849
850    @Override
851    OptionHelper getOptionHelper() {
852        return new GrumpyHelper(null) {
853            @Override
854            public String get(com.sun.tools.javac.main.Option option) {
855                return compOpts.get(option);
856            }
857
858            @Override
859            public void put(String name, String value) {
860                compOpts.put(name, value);
861            }
862        };
863    }
864}
865