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