Start.java revision 3294:9adfb22ff08f
1238384Sjkim/*
2238384Sjkim * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
3238384Sjkim * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4238384Sjkim *
5238384Sjkim * This code is free software; you can redistribute it and/or modify it
6238384Sjkim * under the terms of the GNU General Public License version 2 only, as
7238384Sjkim * published by the Free Software Foundation.  Oracle designates this
8238384Sjkim * particular file as subject to the "Classpath" exception as provided
9238384Sjkim * by Oracle in the LICENSE file that accompanied this code.
10238384Sjkim *
11238384Sjkim * This code is distributed in the hope that it will be useful, but WITHOUT
12238384Sjkim * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13238384Sjkim * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14238384Sjkim * version 2 for more details (a copy is included in the LICENSE file that
15238384Sjkim * accompanied this code).
16238384Sjkim *
17238384Sjkim * You should have received a copy of the GNU General Public License version
18238384Sjkim * 2 along with this work; if not, write to the Free Software Foundation,
19238384Sjkim * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20238384Sjkim *
21238384Sjkim * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22238384Sjkim * or visit www.oracle.com if you need additional information or have any
23238384Sjkim * questions.
24238384Sjkim */
25238384Sjkim
26238384Sjkimpackage jdk.javadoc.internal.tool;
27238384Sjkim
28238384Sjkimimport java.io.File;
29238384Sjkimimport java.io.FileNotFoundException;
30238384Sjkimimport java.io.IOException;
31280297Sjkimimport java.io.PrintWriter;
32280297Sjkimimport java.lang.reflect.Method;
33280297Sjkimimport java.nio.file.Path;
34280297Sjkimimport java.text.BreakIterator;
35280297Sjkimimport java.util.ArrayList;
36238384Sjkimimport java.util.Arrays;
37280297Sjkimimport java.util.Collection;
38280297Sjkimimport java.util.Collections;
39280297Sjkimimport java.util.List;
40352193Sjkimimport java.util.Locale;
41238384Sjkimimport java.util.Objects;
42280297Sjkimimport java.util.Set;
43238384Sjkim
44280297Sjkimimport static javax.tools.DocumentationTool.Location.*;
45280297Sjkim
46280297Sjkimimport javax.tools.JavaFileManager;
47280297Sjkimimport javax.tools.JavaFileObject;
48280297Sjkimimport javax.tools.StandardJavaFileManager;
49238384Sjkimimport javax.tools.StandardLocation;
50238384Sjkim
51238384Sjkimimport com.sun.tools.javac.api.JavacTrees;
52238384Sjkimimport com.sun.tools.javac.file.BaseFileManager;
53238384Sjkimimport com.sun.tools.javac.file.JavacFileManager;
54280297Sjkimimport com.sun.tools.javac.main.CommandLine;
55280297Sjkimimport com.sun.tools.javac.platform.PlatformDescription;
56238384Sjkimimport com.sun.tools.javac.platform.PlatformUtils;
57238384Sjkimimport com.sun.tools.javac.util.ClientCodeException;
58238384Sjkimimport com.sun.tools.javac.util.Context;
59238384Sjkimimport com.sun.tools.javac.util.Log;
60238384Sjkimimport com.sun.tools.javac.util.Options;
61238384Sjkim
62238384Sjkimimport jdk.javadoc.doclet.Doclet;
63238384Sjkimimport jdk.javadoc.doclet.Doclet.Option;
64238384Sjkimimport jdk.javadoc.doclet.DocletEnvironment;
65238384Sjkim
66238384Sjkimimport static com.sun.tools.javac.main.Option.*;
67238384Sjkim/**
68238384Sjkim * Main program of Javadoc.
69238384Sjkim * Previously named "Main".
70238384Sjkim *
71238384Sjkim *  <p><b>This is NOT part of any supported API.
72238384Sjkim *  If you write code that depends on this, you do so at your own risk.
73238384Sjkim *  This code and its internal interfaces are subject to change or
74238384Sjkim *  deletion without notice.</b>
75238384Sjkim *
76238384Sjkim * @author Robert Field
77238384Sjkim * @author Neal Gafter (rewrite)
78238384Sjkim */
79280297Sjkimpublic class Start extends ToolOption.Helper {
80280297Sjkim    /** Context for this invocation. */
81280297Sjkim    private final Context context;
82280297Sjkim
83280297Sjkim    private static final String ProgramName = "javadoc";
84238384Sjkim
85238384Sjkim    // meaning we allow all visibility of PROTECTED and PUBLIC
86238384Sjkim    private static final String defaultModifier = "protected";
87280297Sjkim
88280297Sjkim    private Messager messager;
89280297Sjkim
90280297Sjkim    private final String docletName;
91280297Sjkim
92280297Sjkim    private final ClassLoader classLoader;
93280297Sjkim
94280297Sjkim    private Class<?> docletClass;
95280297Sjkim
96280297Sjkim    private Doclet doclet;
97280297Sjkim
98280297Sjkim    // used to determine the locale for the messager
99280297Sjkim    private Locale locale;
100280297Sjkim
101280297Sjkim
102238384Sjkim    /**
103238384Sjkim     * In API mode, exceptions thrown while calling the doclet are
104280297Sjkim     * propagated using ClientCodeException.
105280297Sjkim     */
106238384Sjkim    private boolean apiMode;
107238384Sjkim
108238384Sjkim    private JavaFileManager fileManager;
109238384Sjkim
110238384Sjkim    Start() {
111238384Sjkim        this(null, null, null, null, null);
112238384Sjkim    }
113238384Sjkim
114238384Sjkim    Start(PrintWriter writer) {
115238384Sjkim        this(null, null, writer, null, null);
116238384Sjkim    }
117238384Sjkim
118238384Sjkim    Start(Context context, String programName, PrintWriter writer,
119238384Sjkim            String docletName, ClassLoader classLoader) {
120238384Sjkim        this.context = context == null ? new Context() : context;
121238384Sjkim        String pname = programName == null ? ProgramName : programName;
122238384Sjkim        this.messager = writer == null
123238384Sjkim                ? new Messager(this.context, pname)
124238384Sjkim                : new Messager(this.context, pname, writer, writer);
125238384Sjkim        this.docletName = docletName;
126238384Sjkim        this.classLoader = classLoader;
127238384Sjkim        this.docletClass = null;
128238384Sjkim        this.locale = Locale.getDefault();
129238384Sjkim    }
130238384Sjkim
131238384Sjkim    public Start(Context context) {
132238384Sjkim        this.docletClass = null;
133238384Sjkim        this.context = Objects.requireNonNull(context);
134238384Sjkim        this.apiMode = true;
135280297Sjkim        this.docletName = null;
136280297Sjkim        this.classLoader = null;
137280297Sjkim        this.locale = Locale.getDefault();
138280297Sjkim    }
139280297Sjkim
140280297Sjkim    void initMessager() {
141280297Sjkim        if (!apiMode)
142280297Sjkim            return;
143280297Sjkim        if (messager == null) {
144280297Sjkim            Log log = context.get(Log.logKey);
145280297Sjkim            if (log instanceof Messager) {
146280297Sjkim                messager = (Messager) log;
147280297Sjkim            } else {
148280297Sjkim                PrintWriter out = context.get(Log.outKey);
149280297Sjkim                messager = (out == null)
150280297Sjkim                        ? new Messager(context, ProgramName)
151280297Sjkim                        : new Messager(context, ProgramName, out, out);
152280297Sjkim            }
153280297Sjkim        }
154280297Sjkim    }
155280297Sjkim
156280297Sjkim    /**
157280297Sjkim     * Usage
158280297Sjkim     */
159280297Sjkim    @Override
160280297Sjkim    void usage() {
161280297Sjkim        usage(true);
162280297Sjkim    }
163280297Sjkim
164280297Sjkim    void usage(boolean exit) {
165280297Sjkim        usage("main.usage", "-help", null, exit);
166280297Sjkim    }
167280297Sjkim
168280297Sjkim    @Override
169280297Sjkim    void Xusage() {
170280297Sjkim        Xusage(true);
171280297Sjkim    }
172280297Sjkim
173280297Sjkim    void Xusage(boolean exit) {
174280297Sjkim        usage("main.Xusage", "-X", "main.Xusage.foot", exit);
175280297Sjkim    }
176280297Sjkim
177280297Sjkim    private void usage(String main, String option, String foot, boolean exit) {
178280297Sjkim        messager.notice(main);
179280297Sjkim        // let doclet print usage information (does nothing on error)
180280297Sjkim        if (docletClass != null) {
181280297Sjkim            String name = doclet.getName();
182280297Sjkim            Set<Option> supportedOptions = doclet.getSupportedOptions();
183280297Sjkim            messager.notice("main.doclet.usage.header", name);
184280297Sjkim            Option.Kind myKind = option.equals("-X")
185280297Sjkim                    ? Option.Kind.EXTENDED
186280297Sjkim                    : Option.Kind.STANDARD;
187280297Sjkim            supportedOptions.stream()
188280297Sjkim                    .filter(opt -> opt.getKind() == myKind)
189280297Sjkim                    .forEach(opt -> messager.printNotice(opt.toString()));
190280297Sjkim        }
191280297Sjkim        if (foot != null)
192280297Sjkim            messager.notice(foot);
193280297Sjkim
194280297Sjkim        if (exit) exit();
195280297Sjkim    }
196280297Sjkim
197280297Sjkim    /**
198280297Sjkim     * Exit
199280297Sjkim     */
200280297Sjkim    private void exit() {
201280297Sjkim        messager.exit();
202280297Sjkim    }
203280297Sjkim
204280297Sjkim    /**
205280297Sjkim     * Main program - external wrapper
206280297Sjkim     */
207280297Sjkim    int begin(String... argv) {
208280297Sjkim        // Preprocess @file arguments
209280297Sjkim        try {
210280297Sjkim            argv = CommandLine.parse(argv);
211280297Sjkim        } catch (FileNotFoundException e) {
212280297Sjkim            messager.error("main.cant.read", e.getMessage());
213280297Sjkim            exit();
214280297Sjkim        } catch (IOException e) {
215280297Sjkim            e.printStackTrace(System.err);
216280297Sjkim            exit();
217280297Sjkim        }
218280297Sjkim
219280297Sjkim        List<String> argList = Arrays.asList(argv);
220280297Sjkim        boolean ok = begin(argList, Collections.<JavaFileObject> emptySet());
221280297Sjkim        return ok ? 0 : 1;
222280297Sjkim    }
223280297Sjkim
224280297Sjkim    // Called by 199 API.
225280297Sjkim    public boolean begin(Class<?> docletClass,
226280297Sjkim            Iterable<String> options,
227280297Sjkim            Iterable<? extends JavaFileObject> fileObjects) {
228280297Sjkim        this.docletClass = docletClass;
229280297Sjkim        List<String> opts = new ArrayList<>();
230280297Sjkim        for (String opt: options)
231280297Sjkim            opts.add(opt);
232280297Sjkim        return begin(opts, fileObjects);
233280297Sjkim    }
234280297Sjkim
235280297Sjkim    private boolean begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) {
236280297Sjkim
237280297Sjkim        fileManager = context.get(JavaFileManager.class);
238280297Sjkim        if (fileManager == null) {
239280297Sjkim            JavacFileManager.preRegister(context);
240280297Sjkim            fileManager = context.get(JavaFileManager.class);
241280297Sjkim            if (fileManager instanceof BaseFileManager) {
242280297Sjkim                ((BaseFileManager) fileManager).autoClose = true;
243280297Sjkim            }
244280297Sjkim        }
245280297Sjkim        // locale and doclet needs to be determined first
246280297Sjkim        docletClass = preProcess(fileManager, options);
247280297Sjkim
248280297Sjkim        if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) {
249280297Sjkim            // no need to dispatch to old, safe to init now
250280297Sjkim            initMessager();
251280297Sjkim            messager.setLocale(locale);
252280297Sjkim            try {
253280297Sjkim                doclet = (Doclet) docletClass.newInstance();
254280297Sjkim            } catch (InstantiationException | IllegalAccessException exc) {
255280297Sjkim                exc.printStackTrace();
256280297Sjkim                if (!apiMode) {
257280297Sjkim                    error("main.could_not_instantiate_class", docletClass);
258280297Sjkim                    messager.exit();
259280297Sjkim                }
260280297Sjkim                throw new ClientCodeException(exc);
261280297Sjkim            }
262238384Sjkim        } else {
263238384Sjkim            if (this.apiMode) {
264238384Sjkim                com.sun.tools.javadoc.Start ostart
265280297Sjkim                        = new com.sun.tools.javadoc.Start(context);
266280297Sjkim                return ostart.begin(docletClass, options, fileObjects);
267238384Sjkim            }
268238384Sjkim            String[] array = options.toArray(new String[options.size()]);
269238384Sjkim            return com.sun.tools.javadoc.Main.execute(array) == 0;
270280297Sjkim        }
271280297Sjkim
272280297Sjkim        boolean failed = false;
273280297Sjkim        try {
274280297Sjkim            failed = !parseAndExecute(options, fileObjects);
275280297Sjkim        } catch (Messager.ExitJavadoc exc) {
276280297Sjkim            // ignore, we just exit this way
277280297Sjkim        } catch (OutOfMemoryError ee) {
278280297Sjkim            messager.error("main.out.of.memory");
279280297Sjkim            failed = true;
280280297Sjkim        } catch (ClientCodeException e) {
281280297Sjkim            // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl
282280297Sjkim            throw e;
283280297Sjkim        } catch (Error ee) {
284280297Sjkim            ee.printStackTrace(System.err);
285280297Sjkim            messager.error("main.fatal.error");
286280297Sjkim            failed = true;
287280297Sjkim        } catch (Exception ee) {
288280297Sjkim            ee.printStackTrace(System.err);
289280297Sjkim            messager.error("main.fatal.exception");
290280297Sjkim            failed = true;
291280297Sjkim        } finally {
292280297Sjkim            if (fileManager != null
293280297Sjkim                    && fileManager instanceof BaseFileManager
294280297Sjkim                    && ((BaseFileManager) fileManager).autoClose) {
295280297Sjkim                try {
296280297Sjkim                    fileManager.close();
297280297Sjkim                } catch (IOException ignore) {}
298280297Sjkim            }
299280297Sjkim            boolean haveErrorWarnings = messager.nerrors() > 0 ||
300280297Sjkim                    (rejectWarnings && messager.nwarnings() > 0);
301280297Sjkim            if (failed && !haveErrorWarnings) {
302280297Sjkim                // the doclet failed, but nothing reported, flag it!.
303280297Sjkim                messager.error("main.unknown.error");
304280297Sjkim            }
305280297Sjkim            failed |= haveErrorWarnings;
306280297Sjkim            messager.exitNotice();
307280297Sjkim            messager.flush();
308280297Sjkim        }
309280297Sjkim        return !failed;
310280297Sjkim    }
311280297Sjkim
312238384Sjkim    /**
313280297Sjkim     * Ensures that the module of the given class is readable to this
314280297Sjkim     * module.
315238384Sjkim     * @param targetClass class in module to be made readable
316280297Sjkim     */
317280297Sjkim    private void ensureReadable(Class<?> targetClass) {
318280297Sjkim        try {
319238384Sjkim            Method getModuleMethod = Class.class.getMethod("getModule");
320280297Sjkim            Object thisModule = getModuleMethod.invoke(this.getClass());
321280297Sjkim            Object targetModule = getModuleMethod.invoke(targetClass);
322280297Sjkim
323280297Sjkim            Class<?> moduleClass = getModuleMethod.getReturnType();
324280297Sjkim            Method addReadsMethod = moduleClass.getMethod("addReads", moduleClass);
325280297Sjkim            addReadsMethod.invoke(thisModule, targetModule);
326238384Sjkim        } catch (NoSuchMethodException e) {
327238384Sjkim            // ignore
328280297Sjkim        } catch (Exception e) {
329280297Sjkim            throw new InternalError(e);
330280297Sjkim        }
331280297Sjkim    }
332280297Sjkim
333280297Sjkim    /**
334280297Sjkim     * Main program - internal
335280297Sjkim     */
336280297Sjkim    private boolean parseAndExecute(List<String> argList,
337238384Sjkim            Iterable<? extends JavaFileObject> fileObjects) throws IOException {
338238384Sjkim        long tm = System.currentTimeMillis();
339238384Sjkim
340280297Sjkim        List<String> javaNames = new ArrayList<>();
341280297Sjkim
342352193Sjkim        compOpts = Options.instance(context);
343238384Sjkim
344352193Sjkim        // Make sure no obsolete source/target messages are reported
345280297Sjkim        compOpts.put("-Xlint:-options", "-Xlint:-options");
346280297Sjkim
347280297Sjkim        doclet.init(locale, messager);
348352193Sjkim        parseArgs(argList, javaNames);
349352193Sjkim
350280297Sjkim        if (fileManager instanceof BaseFileManager) {
351280297Sjkim            ((BaseFileManager) fileManager).handleOptions(fileManagerOpts);
352280297Sjkim        }
353280297Sjkim
354280297Sjkim        String platformString = compOpts.get("-release");
355280297Sjkim
356238384Sjkim        if (platformString != null) {
357238384Sjkim            if (compOpts.isSet("-source")) {
358238384Sjkim                usageError("main.release.bootclasspath.conflict", "-source");
359280297Sjkim            }
360352193Sjkim            if (fileManagerOpts.containsKey(BOOTCLASSPATH)) {
361352193Sjkim                usageError("main.release.bootclasspath.conflict", BOOTCLASSPATH.getText());
362352193Sjkim            }
363280297Sjkim
364238384Sjkim            PlatformDescription platformDescription =
365238384Sjkim                    PlatformUtils.lookupPlatformDescription(platformString);
366280297Sjkim
367280297Sjkim            if (platformDescription == null) {
368238384Sjkim                usageError("main.unsupported.release.version", platformString);
369238384Sjkim            }
370238384Sjkim
371238384Sjkim            compOpts.put(SOURCE, platformDescription.getSourceVersion());
372238384Sjkim
373238384Sjkim            context.put(PlatformDescription.class, platformDescription);
374238384Sjkim
375238384Sjkim            Collection<Path> platformCP = platformDescription.getPlatformPath();
376238384Sjkim
377280297Sjkim            if (platformCP != null) {
378280297Sjkim                if (fileManager instanceof StandardJavaFileManager) {
379280297Sjkim                    StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
380280297Sjkim
381280297Sjkim                    sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP);
382280297Sjkim                } else {
383238384Sjkim                    usageError("main.release.not.standard.file.manager", platformString);
384238384Sjkim                }
385280297Sjkim            }
386280297Sjkim        }
387280297Sjkim
388280297Sjkim        compOpts.notifyListeners();
389280297Sjkim
390280297Sjkim        if (javaNames.isEmpty() && subPackages.isEmpty() && isEmpty(fileObjects)) {
391238384Sjkim            usageError("main.No_packages_or_classes_specified");
392238384Sjkim        }
393238384Sjkim
394280297Sjkim        JavadocTool comp = JavadocTool.make0(context);
395280297Sjkim        if (comp == null) return false;
396280297Sjkim
397280297Sjkim        if (showAccess == null) {
398280297Sjkim            setFilter(defaultModifier);
399280297Sjkim        }
400238384Sjkim
401238384Sjkim        DocletEnvironment root = comp.getEnvironment(
402238384Sjkim                encoding,
403238384Sjkim                showAccess,
404280297Sjkim                overviewpath,
405280297Sjkim                javaNames,
406280297Sjkim                fileObjects,
407280297Sjkim                subPackages,
408280297Sjkim                excludedPackages,
409238384Sjkim                docClasses,
410280297Sjkim                quiet);
411280297Sjkim
412280297Sjkim        // release resources
413280297Sjkim        comp = null;
414280297Sjkim
415280297Sjkim        if (breakiterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
416238384Sjkim            JavacTrees trees = JavacTrees.instance(context);
417238384Sjkim            trees.setBreakIterator(BreakIterator.getSentenceInstance(locale));
418238384Sjkim        }
419238384Sjkim        // pass off control to the doclet
420280297Sjkim        boolean ok = root != null;
421280297Sjkim        if (ok) ok = doclet.run(root);
422280297Sjkim
423280297Sjkim        // We're done.
424280297Sjkim        if (compOpts.get("-verbose") != null) {
425238384Sjkim            tm = System.currentTimeMillis() - tm;
426280297Sjkim            messager.notice("main.done_in", Long.toString(tm));
427280297Sjkim        }
428280297Sjkim
429280297Sjkim        return ok;
430280297Sjkim    }
431238384Sjkim
432280297Sjkim    Set<Doclet.Option> docletOptions = null;
433280297Sjkim    int handleDocletOptions(int idx, List<String> args, boolean isToolOption) {
434280297Sjkim        if (docletOptions == null) {
435280297Sjkim            docletOptions = doclet.getSupportedOptions();
436280297Sjkim        }
437238384Sjkim        String arg = args.get(idx);
438238384Sjkim
439238384Sjkim        for (Doclet.Option opt : docletOptions) {
440238384Sjkim            if (opt.matches(arg)) {
441280297Sjkim                if (args.size() - idx < opt.getArgumentCount()) {
442280297Sjkim                    usageError("main.requires_argument", arg);
443280297Sjkim                }
444280297Sjkim                opt.process(arg, args.listIterator(idx + 1));
445280297Sjkim                idx += opt.getArgumentCount();
446280297Sjkim                return idx;
447238384Sjkim            }
448280297Sjkim        }
449280297Sjkim        // check if arg is accepted by the tool before emitting error
450280297Sjkim        if (!isToolOption)
451280297Sjkim            usageError("main.invalid_flag", arg);
452280297Sjkim        return idx;
453280297Sjkim    }
454280297Sjkim
455280297Sjkim    private Class<?> preProcess(JavaFileManager jfm, List<String> argv) {
456238384Sjkim        // doclet specifying arguments
457280297Sjkim        String userDocletPath = null;
458280297Sjkim        String userDocletName = null;
459280297Sjkim
460280297Sjkim        // Step 1: loop through the args, set locale early on, if found.
461280297Sjkim        for (int i = 0 ; i < argv.size() ; i++) {
462280297Sjkim            String arg = argv.get(i);
463280297Sjkim            if (arg.equals(ToolOption.LOCALE.opt)) {
464280297Sjkim                oneArg(argv, i++);
465238384Sjkim                String lname = argv.get(i);
466238384Sjkim                locale = getLocale(lname);
467238384Sjkim            } else if (arg.equals(ToolOption.DOCLET.opt)) {
468238384Sjkim                oneArg(argv, i++);
469280297Sjkim                if (userDocletName != null) {
470280297Sjkim                    usageError("main.more_than_one_doclet_specified_0_and_1",
471280297Sjkim                               userDocletName, argv.get(i));
472280297Sjkim                }
473280297Sjkim                if (docletName != null) {
474280297Sjkim                    usageError("main.more_than_one_doclet_specified_0_and_1",
475280297Sjkim                            docletName, argv.get(i));
476238384Sjkim                }
477280297Sjkim                userDocletName = argv.get(i);
478280297Sjkim            } else if (arg.equals(ToolOption.DOCLETPATH.opt)) {
479280297Sjkim                oneArg(argv, i++);
480280297Sjkim                if (userDocletPath == null) {
481280297Sjkim                    userDocletPath = argv.get(i);
482238384Sjkim                } else {
483280297Sjkim                    userDocletPath += File.pathSeparator + argv.get(i);
484280297Sjkim                }
485280297Sjkim            }
486280297Sjkim        }
487280297Sjkim        // Step 2: a doclet has already been provided,
488238384Sjkim        // nothing more to do.
489280297Sjkim        if (docletClass != null) {
490280297Sjkim            return docletClass;
491280297Sjkim        }
492280297Sjkim        // Step 3: doclet name specified ? if so find a ClassLoader,
493238384Sjkim        // and load it.
494280297Sjkim        if (userDocletName != null) {
495280297Sjkim            ClassLoader cl = classLoader;
496280297Sjkim            if (cl == null) {
497280297Sjkim                if (!fileManager.hasLocation(DOCLET_PATH)) {
498280297Sjkim                    List<File> paths = new ArrayList<>();
499280297Sjkim                    if (userDocletPath != null) {
500238384Sjkim                        for (String pathname : userDocletPath.split(File.pathSeparator)) {
501280297Sjkim                            paths.add(new File(pathname));
502280297Sjkim                        }
503280297Sjkim                    }
504280297Sjkim                    try {
505238384Sjkim                        ((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths);
506280297Sjkim                    } catch (IOException ioe) {
507280297Sjkim                        panic("main.doclet_no_classloader_found", ioe);
508280297Sjkim                        return null; // keep compiler happy
509280297Sjkim                    }
510280297Sjkim                }
511280297Sjkim                cl = fileManager.getClassLoader(DOCLET_PATH);
512280297Sjkim                if (cl == null) {
513280297Sjkim                    // despite doclet specified on cmdline no classloader found!
514280297Sjkim                    panic("main.doclet_no_classloader_found", userDocletName);
515238384Sjkim                    return null; // keep compiler happy
516238384Sjkim                }
517238384Sjkim                try {
518280297Sjkim                    Class<?> klass = cl.loadClass(userDocletName);
519280297Sjkim                    ensureReadable(klass);
520280297Sjkim                    return klass;
521280297Sjkim                } catch (ClassNotFoundException cnfe) {
522280297Sjkim                    panic("main.doclet_class_not_found", userDocletName);
523280297Sjkim                    return null; // keep compiler happy
524280297Sjkim                }
525280297Sjkim            }
526280297Sjkim        }
527280297Sjkim        // Step 4: we have a doclet, try loading it, otherwise
528280297Sjkim        // return back the standard doclet
529280297Sjkim        if (docletName != null) {
530280297Sjkim            try {
531238384Sjkim                return Class.forName(docletName, true, getClass().getClassLoader());
532238384Sjkim            } catch (ClassNotFoundException cnfe) {
533238384Sjkim                panic("main.doclet_class_not_found", userDocletName);
534280297Sjkim                return null; // happy compiler, should not happen
535280297Sjkim            }
536280297Sjkim        } else {
537280297Sjkim            return jdk.javadoc.internal.doclets.standard.Standard.class;
538280297Sjkim        }
539280297Sjkim    }
540280297Sjkim
541280297Sjkim    private void parseArgs(List<String> args, List<String> javaNames) {
542280297Sjkim        for (int i = 0 ; i < args.size() ; i++) {
543280297Sjkim            String arg = args.get(i);
544280297Sjkim            ToolOption o = ToolOption.get(arg);
545280297Sjkim            if (o != null) {
546238384Sjkim                // handle a doclet argument that may be needed however
547280297Sjkim                // don't increment the index, and allow the tool to consume args
548280297Sjkim                handleDocletOptions(i, args, true);
549238384Sjkim
550238384Sjkim                if (o.hasArg) {
551238384Sjkim                    oneArg(args, i++);
552280297Sjkim                    o.process(this, args.get(i));
553280297Sjkim                } else {
554280297Sjkim                    setOption(arg);
555280297Sjkim                    o.process(this);
556280297Sjkim                }
557280297Sjkim            } else if (arg.startsWith("-XD")) {
558280297Sjkim                // hidden javac options
559280297Sjkim                String s = arg.substring("-XD".length());
560238384Sjkim                int eq = s.indexOf('=');
561280297Sjkim                String key = (eq < 0) ? s : s.substring(0, eq);
562280297Sjkim                String value = (eq < 0) ? s : s.substring(eq+1);
563280297Sjkim                compOpts.put(key, value);
564280297Sjkim            } else if (arg.startsWith("-")) {
565280297Sjkim                i = handleDocletOptions(i, args, false);
566280297Sjkim            } else {
567238384Sjkim                javaNames.add(arg);
568280297Sjkim            }
569280297Sjkim        }
570280297Sjkim    }
571280297Sjkim
572238384Sjkim    private <T> boolean isEmpty(Iterable<T> iter) {
573280297Sjkim        return !iter.iterator().hasNext();
574280297Sjkim    }
575280297Sjkim
576238384Sjkim    /**
577280297Sjkim     * Set one arg option.
578280297Sjkim     * Error and exit if one argument is not provided.
579280297Sjkim     */
580238384Sjkim    private void oneArg(List<String> args, int index) {
581280297Sjkim        if ((index + 1) < args.size()) {
582280297Sjkim            setOption(args.get(index), args.get(index+1));
583280297Sjkim        } else {
584238384Sjkim            usageError("main.requires_argument", args.get(index));
585280297Sjkim        }
586280297Sjkim    }
587238384Sjkim
588280297Sjkim    @Override
589238384Sjkim    void usageError(String key, Object... args) {
590280297Sjkim        error(key, args);
591280297Sjkim        usage(true);
592280297Sjkim    }
593280297Sjkim
594280297Sjkim    // a terminal call, will not return
595238384Sjkim    void panic(String key, Object... args) {
596280297Sjkim        error(key, args);
597280297Sjkim        messager.exit();
598280297Sjkim    }
599238384Sjkim
600280297Sjkim    void error(String key, Object... args) {
601280297Sjkim        messager.error(key, args);
602280297Sjkim    }
603280297Sjkim
604280297Sjkim    /**
605280297Sjkim     * indicate an option with no arguments was given.
606238384Sjkim     */
607280297Sjkim    private void setOption(String opt) {
608280297Sjkim        String[] option = { opt };
609280297Sjkim        options.add(Arrays.asList(option));
610280297Sjkim    }
611280297Sjkim
612280297Sjkim    /**
613280297Sjkim     * indicate an option with one argument was given.
614238384Sjkim     */
615238384Sjkim    private void setOption(String opt, String argument) {
616280297Sjkim        String[] option = { opt, argument };
617280297Sjkim        options.add(Arrays.asList(option));
618280297Sjkim    }
619280297Sjkim
620280297Sjkim    /**
621238384Sjkim     * indicate an option with the specified list of arguments was given.
622238384Sjkim     */
623280297Sjkim    private void setOption(String opt, List<String> arguments) {
624280297Sjkim        List<String> args = new ArrayList<>(arguments.size() + 1);
625280297Sjkim        args.add(opt);
626280297Sjkim        args.addAll(arguments);
627280297Sjkim        options.add(args);
628238384Sjkim    }
629280297Sjkim
630280297Sjkim    /**
631280297Sjkim     * Get the locale if specified on the command line
632280297Sjkim     * else return null and if locale option is not used
633238384Sjkim     * then return default locale.
634280297Sjkim     */
635280297Sjkim    private Locale getLocale(String localeName) {
636280297Sjkim        Locale userlocale = null;
637280297Sjkim        if (localeName == null || localeName.isEmpty()) {
638280297Sjkim            return Locale.getDefault();
639280297Sjkim        }
640280297Sjkim        int firstuscore = localeName.indexOf('_');
641280297Sjkim        int seconduscore = -1;
642280297Sjkim        String language = null;
643280297Sjkim        String country = null;
644280297Sjkim        String variant = null;
645280297Sjkim        if (firstuscore == 2) {
646280297Sjkim            language = localeName.substring(0, firstuscore);
647280297Sjkim            seconduscore = localeName.indexOf('_', firstuscore + 1);
648280297Sjkim            if (seconduscore > 0) {
649280297Sjkim                if (seconduscore != firstuscore + 3
650280297Sjkim                        || localeName.length() <= seconduscore + 1) {
651280297Sjkim                    usageError("main.malformed_locale_name", localeName);
652280297Sjkim                    return null;
653280297Sjkim                }
654280297Sjkim                country = localeName.substring(firstuscore + 1,
655280297Sjkim                        seconduscore);
656280297Sjkim                variant = localeName.substring(seconduscore + 1);
657280297Sjkim            } else if (localeName.length() == firstuscore + 3) {
658280297Sjkim                country = localeName.substring(firstuscore + 1);
659280297Sjkim            } else {
660280297Sjkim                usageError("main.malformed_locale_name", localeName);
661280297Sjkim                return null;
662238384Sjkim            }
663280297Sjkim        } else if (firstuscore == -1 && localeName.length() == 2) {
664280297Sjkim            language = localeName;
665280297Sjkim        } else {
666280297Sjkim            usageError("main.malformed_locale_name", localeName);
667280297Sjkim            return null;
668280297Sjkim        }
669280297Sjkim        userlocale = searchLocale(language, country, variant);
670238384Sjkim        if (userlocale == null) {
671280297Sjkim            usageError("main.illegal_locale_name", localeName);
672280297Sjkim            return null;
673280297Sjkim        } else {
674238384Sjkim            return userlocale;
675280297Sjkim        }
676280297Sjkim    }
677238384Sjkim
678280297Sjkim    /**
679280297Sjkim     * Search the locale for specified language, specified country and
680280297Sjkim     * specified variant.
681280297Sjkim     */
682280297Sjkim    private Locale searchLocale(String language, String country,
683280297Sjkim                                String variant) {
684238384Sjkim        for (Locale loc : Locale.getAvailableLocales()) {
685280297Sjkim            if (loc.getLanguage().equals(language) &&
686280297Sjkim                (country == null || loc.getCountry().equals(country)) &&
687280297Sjkim                (variant == null || loc.getVariant().equals(variant))) {
688280297Sjkim                return loc;
689280297Sjkim            }
690238384Sjkim        }
691280297Sjkim        return null;
692280297Sjkim    }
693238384Sjkim}
694280297Sjkim