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