Start.java revision 3560:bbf4cfc235bd
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 com.sun.tools.javadoc.main;
27
28import java.io.File;
29import java.io.FileNotFoundException;
30import java.io.IOException;
31import java.io.PrintWriter;
32import java.nio.file.Path;
33import java.util.ArrayList;
34import java.util.Collection;
35import java.util.Collections;
36import java.util.Objects;
37
38import javax.tools.JavaFileManager;
39import javax.tools.JavaFileObject;
40import javax.tools.StandardJavaFileManager;
41import javax.tools.StandardLocation;
42
43import com.sun.javadoc.*;
44import com.sun.tools.javac.file.JavacFileManager;
45import com.sun.tools.javac.main.CommandLine;
46import com.sun.tools.javac.main.Option;
47import com.sun.tools.javac.file.BaseFileManager;
48import com.sun.tools.javac.platform.PlatformDescription;
49import com.sun.tools.javac.platform.PlatformUtils;
50import com.sun.tools.javac.util.ClientCodeException;
51import com.sun.tools.javac.util.Context;
52import com.sun.tools.javac.util.List;
53import com.sun.tools.javac.util.ListBuffer;
54import com.sun.tools.javac.util.Log;
55import com.sun.tools.javac.util.Options;
56
57import static com.sun.tools.javac.code.Flags.*;
58
59/**
60 * Main program of Javadoc.
61 * Previously named "Main".
62 *
63 *  <p><b>This is NOT part of any supported API.
64 *  If you write code that depends on this, you do so at your own risk.
65 *  This code and its internal interfaces are subject to change or
66 *  deletion without notice.</b>
67 *
68 * @since 1.2
69 * @author Robert Field
70 * @author Neal Gafter (rewrite)
71 */
72@Deprecated
73public class Start extends ToolOption.Helper {
74    /** Context for this invocation. */
75    private final Context context;
76
77    private final String defaultDocletClassName;
78    private final ClassLoader docletParentClassLoader;
79
80    private static final String javadocName = "javadoc";
81
82    private static final String standardDocletClassName =
83        "com.sun.tools.doclets.standard.Standard";
84
85    private final long defaultFilter = PUBLIC | PROTECTED;
86
87    private final Messager messager;
88
89    private DocletInvoker docletInvoker;
90
91    /**
92     * In API mode, exceptions thrown while calling the doclet are
93     * propagated using ClientCodeException.
94     */
95    private boolean apiMode;
96
97    private JavaFileManager fileManager;
98
99    public Start(String programName,
100          PrintWriter errWriter,
101          PrintWriter warnWriter,
102          PrintWriter noticeWriter,
103          String defaultDocletClassName) {
104        this(programName, errWriter, warnWriter, noticeWriter, defaultDocletClassName, null);
105    }
106
107    public Start(PrintWriter pw) {
108        this(javadocName, pw, pw, pw, standardDocletClassName);
109    }
110
111    public Start(String programName,
112          PrintWriter errWriter,
113          PrintWriter warnWriter,
114          PrintWriter noticeWriter,
115          String defaultDocletClassName,
116          ClassLoader docletParentClassLoader) {
117        context = new Context();
118        messager = new Messager(context, programName, errWriter, warnWriter, noticeWriter);
119        this.defaultDocletClassName = defaultDocletClassName;
120        this.docletParentClassLoader = docletParentClassLoader;
121    }
122
123    public Start(String programName, String defaultDocletClassName) {
124        this(programName, defaultDocletClassName, null);
125    }
126
127    public Start(String programName, String defaultDocletClassName,
128          ClassLoader docletParentClassLoader) {
129        context = new Context();
130        messager = new Messager(context, programName);
131        this.defaultDocletClassName = defaultDocletClassName;
132        this.docletParentClassLoader = docletParentClassLoader;
133    }
134
135    public Start(String programName, ClassLoader docletParentClassLoader) {
136        this(programName, standardDocletClassName, docletParentClassLoader);
137    }
138
139    public Start(String programName) {
140        this(programName, standardDocletClassName);
141    }
142
143    public Start(ClassLoader docletParentClassLoader) {
144        this(javadocName, docletParentClassLoader);
145    }
146
147    public Start() {
148        this(javadocName);
149    }
150
151    public Start(Context context) {
152        this.context = Objects.requireNonNull(context);
153        apiMode = true;
154        defaultDocletClassName = standardDocletClassName;
155        docletParentClassLoader = null;
156
157        Log log = context.get(Log.logKey);
158        if (log instanceof Messager)
159            messager = (Messager) log;
160        else {
161            PrintWriter out = context.get(Log.errKey);
162            messager = (out == null) ? new Messager(context, javadocName)
163                    : new Messager(context, javadocName, out, out, out);
164        }
165    }
166
167    /**
168     * Usage
169     */
170    @Override
171    void usage() {
172        usage(true);
173    }
174
175    void usage(boolean exit) {
176        usage("main.usage", "-help", null, exit);
177    }
178
179    @Override
180    void Xusage() {
181        Xusage(true);
182    }
183
184    void Xusage(boolean exit) {
185        usage("main.Xusage", "-X", "main.Xusage.foot", exit);
186    }
187
188    private void usage(String main, String doclet, String foot, boolean exit) {
189        // RFE: it would be better to replace the following with code to
190        // write a header, then help for each option, then a footer.
191        messager.notice(main);
192
193        // let doclet print usage information (does nothing on error)
194        if (docletInvoker != null) {
195            // RFE: this is a pretty bad way to get the doclet to show
196            // help info. Moreover, the output appears on stdout,
197            // and <i>not</i> on any of the standard streams passed
198            // to javadoc, and in particular, not to the noticeWriter
199            // But, to fix this, we need to fix the Doclet API.
200            docletInvoker.optionLength(doclet);
201        }
202
203        if (foot != null)
204            messager.notice(foot);
205
206        if (exit) exit();
207    }
208
209    /**
210     * Exit
211     */
212    private void exit() {
213        messager.exit();
214    }
215
216
217    /**
218     * Main program - external wrapper
219     */
220    public int begin(String... argv) {
221        boolean ok = begin(null, argv, Collections.<JavaFileObject> emptySet());
222        return ok ? 0 : 1;
223    }
224
225    public boolean begin(Class<?> docletClass, Iterable<String> options, Iterable<? extends JavaFileObject> fileObjects) {
226        Collection<String> opts = new ArrayList<>();
227        for (String opt: options) opts.add(opt);
228        return begin(docletClass, opts.toArray(new String[opts.size()]), fileObjects);
229    }
230
231    private boolean begin(Class<?> docletClass, String[] options, Iterable<? extends JavaFileObject> fileObjects) {
232        boolean failed = false;
233
234        try {
235            failed = !parseAndExecute(docletClass, options, fileObjects);
236        } catch (Messager.ExitJavadoc exc) {
237            // ignore, we just exit this way
238        } catch (OutOfMemoryError ee) {
239            messager.error(Messager.NOPOS, "main.out.of.memory");
240            failed = true;
241        } catch (ClientCodeException e) {
242            // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl
243            throw e;
244        } catch (Error ee) {
245            ee.printStackTrace(System.err);
246            messager.error(Messager.NOPOS, "main.fatal.error");
247            failed = true;
248        } catch (Exception ee) {
249            ee.printStackTrace(System.err);
250            messager.error(Messager.NOPOS, "main.fatal.exception");
251            failed = true;
252        } finally {
253            if (fileManager != null
254                    && fileManager instanceof BaseFileManager
255                    && ((BaseFileManager) fileManager).autoClose) {
256                try {
257                    fileManager.close();
258                } catch (IOException ignore) {
259                }
260            }
261            messager.exitNotice();
262            messager.flush();
263        }
264        failed |= messager.nerrors() > 0;
265        failed |= rejectWarnings && messager.nwarnings() > 0;
266        return !failed;
267    }
268
269    /**
270     * Main program - internal
271     */
272    private boolean parseAndExecute(
273            Class<?> docletClass,
274            String[] argv,
275            Iterable<? extends JavaFileObject> fileObjects) throws IOException {
276        long tm = System.currentTimeMillis();
277
278        ListBuffer<String> javaNames = new ListBuffer<>();
279
280        // Preprocess @file arguments
281        try {
282            argv = CommandLine.parse(argv);
283        } catch (FileNotFoundException e) {
284            messager.error(Messager.NOPOS, "main.cant.read", e.getMessage());
285            exit();
286        } catch (IOException e) {
287            e.printStackTrace(System.err);
288            exit();
289        }
290
291
292        fileManager = context.get(JavaFileManager.class);
293
294        setDocletInvoker(docletClass, fileManager, argv);
295
296        compOpts = Options.instance(context);
297        // Make sure no obsolete source/target messages are reported
298        compOpts.put("-Xlint:-options", "-Xlint:-options");
299
300        // Parse arguments
301        for (int i = 0 ; i < argv.length ; i++) {
302            String arg = argv[i];
303
304            ToolOption o = ToolOption.get(arg);
305            if (o != null) {
306                // hack: this restriction should be removed
307                if (o == ToolOption.LOCALE && i > 0)
308                    usageError("main.locale_first");
309
310                if (o.hasArg) {
311                    oneArg(argv, i++);
312                    o.process(this, argv[i]);
313                } else {
314                    setOption(arg);
315                    o.process(this);
316                }
317            } else if (arg.equals("-XDaccessInternalAPI")) {
318                // pass this hidden option down to the doclet, if it wants it
319                if (docletInvoker.optionLength("-XDaccessInternalAPI") == 1) {
320                    setOption(arg);
321                }
322            } else if (arg.startsWith("-XD")) {
323                // hidden javac options
324                String s = arg.substring("-XD".length());
325                int eq = s.indexOf('=');
326                String key = (eq < 0) ? s : s.substring(0, eq);
327                String value = (eq < 0) ? s : s.substring(eq+1);
328                compOpts.put(key, value);
329            }
330            // call doclet for its options
331            // other arg starts with - is invalid
332            else if (arg.startsWith("-")) {
333                int optionLength;
334                optionLength = docletInvoker.optionLength(arg);
335                if (optionLength < 0) {
336                    // error already displayed
337                    exit();
338                } else if (optionLength == 0) {
339                    // option not found
340                    usageError("main.invalid_flag", arg);
341                } else {
342                    // doclet added option
343                    if ((i + optionLength) > argv.length) {
344                        usageError("main.requires_argument", arg);
345                    }
346                    ListBuffer<String> args = new ListBuffer<>();
347                    for (int j = 0; j < optionLength-1; ++j) {
348                        args.append(argv[++i]);
349                    }
350                    setOption(arg, args.toList());
351                }
352            } else {
353                javaNames.append(arg);
354            }
355        }
356
357        if (fileManager == null) {
358            JavacFileManager.preRegister(context);
359            fileManager = context.get(JavaFileManager.class);
360            if (fileManager instanceof BaseFileManager) {
361                ((BaseFileManager) fileManager).autoClose = true;
362            }
363        }
364        if (fileManager instanceof BaseFileManager) {
365            ((BaseFileManager) fileManager).handleOptions(fileManagerOpts);
366        }
367
368        String platformString = compOpts.get("-release");
369
370        if (platformString != null) {
371            if (compOpts.isSet("-source")) {
372                usageError("main.release.bootclasspath.conflict", "-source");
373            }
374            if (fileManagerOpts.containsKey(Option.BOOTCLASSPATH)) {
375                usageError("main.release.bootclasspath.conflict", Option.BOOTCLASSPATH.getText());
376            }
377
378            PlatformDescription platformDescription =
379                    PlatformUtils.lookupPlatformDescription(platformString);
380
381            if (platformDescription == null) {
382                usageError("main.unsupported.release.version", platformString);
383            }
384
385            compOpts.put(Option.SOURCE, platformDescription.getSourceVersion());
386
387            context.put(PlatformDescription.class, platformDescription);
388
389            Collection<Path> platformCP = platformDescription.getPlatformPath();
390
391            if (platformCP != null) {
392                if (fileManager instanceof StandardJavaFileManager) {
393                    StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
394
395                    sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP);
396                } else {
397                    usageError("main.release.not.standard.file.manager", platformString);
398                }
399            }
400        }
401
402        compOpts.notifyListeners();
403
404        if (javaNames.isEmpty() && subPackages.isEmpty() && isEmpty(fileObjects)) {
405            usageError("main.No_packages_or_classes_specified");
406        }
407
408        if (!docletInvoker.validOptions(options.toList())) {
409            // error message already displayed
410            exit();
411        }
412
413        JavadocTool comp = JavadocTool.make0(context);
414        if (comp == null) return false;
415
416        if (showAccess == null) {
417            setFilter(defaultFilter);
418        }
419
420        LanguageVersion languageVersion = docletInvoker.languageVersion();
421        RootDocImpl root = comp.getRootDocImpl(
422                docLocale,
423                encoding,
424                showAccess,
425                javaNames.toList(),
426                options.toList(),
427                fileObjects,
428                breakiterator,
429                subPackages.toList(),
430                excludedPackages.toList(),
431                docClasses,
432                // legacy?
433                languageVersion == null || languageVersion == LanguageVersion.JAVA_1_1,
434                quiet);
435
436        // release resources
437        comp = null;
438
439        // pass off control to the doclet
440        boolean ok = root != null;
441        if (ok) ok = docletInvoker.start(root);
442
443        // We're done.
444        if (compOpts.get("-verbose") != null) {
445            tm = System.currentTimeMillis() - tm;
446            messager.notice("main.done_in", Long.toString(tm));
447        }
448
449        return ok;
450    }
451
452    private <T> boolean isEmpty(Iterable<T> iter) {
453        return !iter.iterator().hasNext();
454    }
455
456    /**
457     * Init the doclet invoker.
458     * The doclet class may be given explicitly, or via the -doclet option in
459     * argv.
460     * If the doclet class is not given explicitly, it will be loaded from
461     * the file manager's DOCLET_PATH location, if available, or via the
462     * -doclet path option in argv.
463     * @param docletClass The doclet class. May be null.
464     * @param fileManager The file manager used to get the class loader to load
465     * the doclet class if required. May be null.
466     * @param argv Args containing -doclet and -docletpath, in case they are required.
467     */
468    private void setDocletInvoker(Class<?> docletClass, JavaFileManager fileManager, String[] argv) {
469        boolean exportInternalAPI = false;
470        String docletClassName = null;
471        String docletPath = null;
472
473        // Parse doclet specifying arguments
474        for (int i = 0 ; i < argv.length ; i++) {
475            String arg = argv[i];
476            if (arg.equals(ToolOption.DOCLET.opt)) {
477                oneArg(argv, i++);
478                if (docletClassName != null) {
479                    usageError("main.more_than_one_doclet_specified_0_and_1",
480                               docletClassName, argv[i]);
481                }
482                docletClassName = argv[i];
483            } else if (arg.equals(ToolOption.DOCLETPATH.opt)) {
484                oneArg(argv, i++);
485                if (docletPath == null) {
486                    docletPath = argv[i];
487                } else {
488                    docletPath += File.pathSeparator + argv[i];
489                }
490            } else if (arg.equals("-XDaccessInternalAPI")) {
491                exportInternalAPI = true;
492            }
493        }
494
495        if (docletClass != null) {
496            // TODO, check no -doclet, -docletpath
497            docletInvoker = new DocletInvoker(messager, docletClass, apiMode, exportInternalAPI);
498        } else {
499            if (docletClassName == null) {
500                docletClassName = defaultDocletClassName;
501            }
502
503            // attempt to find doclet
504            docletInvoker = new DocletInvoker(messager, fileManager,
505                    docletClassName, docletPath,
506                    docletParentClassLoader,
507                    apiMode,
508                    exportInternalAPI);
509        }
510    }
511
512    /**
513     * Set one arg option.
514     * Error and exit if one argument is not provided.
515     */
516    private void oneArg(String[] args, int index) {
517        if ((index + 1) < args.length) {
518            setOption(args[index], args[index+1]);
519        } else {
520            usageError("main.requires_argument", args[index]);
521        }
522    }
523
524    @Override
525    void usageError(String key, Object... args) {
526        messager.error(Messager.NOPOS, key, args);
527        usage(true);
528    }
529
530    /**
531     * indicate an option with no arguments was given.
532     */
533    private void setOption(String opt) {
534        String[] option = { opt };
535        options.append(option);
536    }
537
538    /**
539     * indicate an option with one argument was given.
540     */
541    private void setOption(String opt, String argument) {
542        String[] option = { opt, argument };
543        options.append(option);
544    }
545
546    /**
547     * indicate an option with the specified list of arguments was given.
548     */
549    private void setOption(String opt, List<String> arguments) {
550        String[] args = new String[arguments.length() + 1];
551        int k = 0;
552        args[k++] = opt;
553        for (List<String> i = arguments; i.nonEmpty(); i=i.tail) {
554            args[k++] = i.head;
555        }
556        options.append(args);
557    }
558}
559