1 /*
2  * Copyright (c) 1997, 2018, 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.main;
27 
28 import java.io.File;
29 import java.io.FileNotFoundException;
30 import java.io.IOException;
31 import java.io.PrintWriter;
32 import java.nio.file.Path;
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.Objects;
37 
38 import javax.tools.JavaFileManager;
39 import javax.tools.JavaFileObject;
40 
41 import com.sun.javadoc.*;
42 import com.sun.tools.javac.file.JavacFileManager;
43 import com.sun.tools.javac.file.BaseFileManager;
44 import com.sun.tools.javac.main.Arguments;
45 import com.sun.tools.javac.main.CommandLine;
46 import com.sun.tools.javac.main.DelegatingJavaFileManager;
47 import com.sun.tools.javac.main.Option;
48 import com.sun.tools.javac.main.OptionHelper;
49 import com.sun.tools.javac.main.OptionHelper.GrumpyHelper;
50 import com.sun.tools.javac.platform.PlatformDescription;
51 import com.sun.tools.javac.platform.PlatformUtils;
52 import com.sun.tools.javac.util.ClientCodeException;
53 import com.sun.tools.javac.util.Context;
54 import com.sun.tools.javac.util.List;
55 import com.sun.tools.javac.util.ListBuffer;
56 import com.sun.tools.javac.util.Log;
57 import com.sun.tools.javac.util.Options;
58 
59 import static com.sun.tools.javac.code.Flags.*;
60 
61 /**
62  * Main program of Javadoc.
63  * Previously named "Main".
64  *
65  *  <p><b>This is NOT part of any supported API.
66  *  If you write code that depends on this, you do so at your own risk.
67  *  This code and its internal interfaces are subject to change or
68  *  deletion without notice.</b>
69  *
70  * @since 1.2
71  * @author Robert Field
72  * @author Neal Gafter (rewrite)
73  */
74 @Deprecated(since="9", forRemoval=true)
75 @SuppressWarnings("removal")
76 public 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 
Start(String programName, PrintWriter errWriter, PrintWriter warnWriter, PrintWriter noticeWriter, String defaultDocletClassName)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 
Start(PrintWriter pw)110     public Start(PrintWriter pw) {
111         this(javadocName, pw, pw, pw, standardDocletClassName);
112     }
113 
Start(String programName, PrintWriter errWriter, PrintWriter warnWriter, PrintWriter noticeWriter, String defaultDocletClassName, ClassLoader docletParentClassLoader)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 
Start(String programName, String defaultDocletClassName)126     public Start(String programName, String defaultDocletClassName) {
127         this(programName, defaultDocletClassName, null);
128     }
129 
Start(String programName, String defaultDocletClassName, ClassLoader docletParentClassLoader)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 
Start(String programName, ClassLoader docletParentClassLoader)138     public Start(String programName, ClassLoader docletParentClassLoader) {
139         this(programName, standardDocletClassName, docletParentClassLoader);
140     }
141 
Start(String programName)142     public Start(String programName) {
143         this(programName, standardDocletClassName);
144     }
145 
Start(ClassLoader docletParentClassLoader)146     public Start(ClassLoader docletParentClassLoader) {
147         this(javadocName, docletParentClassLoader);
148     }
149 
Start()150     public Start() {
151         this(javadocName);
152     }
153 
Start(Context context)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
usage()174     void usage() {
175         usage(true);
176     }
177 
usage(boolean exit)178     void usage(boolean exit) {
179         usage("main.usage", "-help", "main.usage.foot", exit);
180     }
181 
182     @Override
Xusage()183     void Xusage() {
184         Xusage(true);
185     }
186 
Xusage(boolean exit)187     void Xusage(boolean exit) {
188         usage("main.Xusage", "-X", "main.Xusage.foot", exit);
189     }
190 
usage(String main, String doclet, String foot, boolean exit)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      */
exit()215     private void exit() {
216         messager.exit();
217     }
218 
219 
220     /**
221      * Main program - external wrapper
222      */
begin(String... argv)223     public int begin(String... argv) {
224         boolean ok = begin(null, argv, Collections.emptySet());
225         return ok ? 0 : 1;
226     }
227 
begin(Class<?> docletClass, Iterable<String> options, Iterable<? extends JavaFileObject> fileObjects)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 
begin(Class<?> docletClass, String[] options, Iterable<? extends JavaFileObject> fileObjects)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      */
parseAndExecute( Class<?> docletClass, String[] argv, Iterable<? extends JavaFileObject> fileObjects)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             JavaFileManager platformFM = platformDescription.getFileManager();
402             DelegatingJavaFileManager.installReleaseFileManager(context,
403                                                                 platformFM,
404                                                                 fileManager);
405         }
406 
407         compOpts.notifyListeners();
408 
409         if (javaNames.isEmpty() && subPackages.isEmpty() && isEmpty(fileObjects)) {
410             usageError("main.No_packages_or_classes_specified");
411         }
412 
413         if (!docletInvoker.validOptions(options.toList())) {
414             // error message already displayed
415             exit();
416         }
417 
418         JavadocTool comp = JavadocTool.make0(context);
419         if (comp == null) return false;
420 
421         if (showAccess == null) {
422             setFilter(defaultFilter);
423         }
424 
425         LanguageVersion languageVersion = docletInvoker.languageVersion();
426         RootDocImpl root = comp.getRootDocImpl(
427                 docLocale,
428                 encoding,
429                 showAccess,
430                 javaNames.toList(),
431                 options.toList(),
432                 fileObjects,
433                 breakiterator,
434                 subPackages.toList(),
435                 excludedPackages.toList(),
436                 docClasses,
437                 // legacy?
438                 languageVersion == null || languageVersion == LanguageVersion.JAVA_1_1,
439                 quiet);
440 
441         // release resources
442         comp = null;
443 
444         // pass off control to the doclet
445         boolean ok = root != null;
446         if (ok) ok = docletInvoker.start(root);
447 
448         // We're done.
449         if (compOpts.get("-verbose") != null) {
450             tm = System.currentTimeMillis() - tm;
451             messager.notice("main.done_in", Long.toString(tm));
452         }
453 
454         return ok;
455     }
456 
isEmpty(Iterable<T> iter)457     private <T> boolean isEmpty(Iterable<T> iter) {
458         return !iter.iterator().hasNext();
459     }
460 
461     /**
462      * Init the doclet invoker.
463      * The doclet class may be given explicitly, or via the -doclet option in
464      * argv.
465      * If the doclet class is not given explicitly, it will be loaded from
466      * the file manager's DOCLET_PATH location, if available, or via the
467      * -doclet path option in argv.
468      * @param docletClass The doclet class. May be null.
469      * @param fileManager The file manager used to get the class loader to load
470      * the doclet class if required. May be null.
471      * @param argv Args containing -doclet and -docletpath, in case they are required.
472      */
setDocletInvoker(Class<?> docletClass, JavaFileManager fileManager, String[] argv)473     private void setDocletInvoker(Class<?> docletClass, JavaFileManager fileManager, String[] argv) {
474         boolean exportInternalAPI = false;
475         String docletClassName = null;
476         String docletPath = null;
477 
478         // Parse doclet specifying arguments
479         for (int i = 0 ; i < argv.length ; i++) {
480             String arg = argv[i];
481             if (arg.equals(ToolOption.DOCLET.opt)) {
482                 oneArg(argv, i++);
483                 if (docletClassName != null) {
484                     usageError("main.more_than_one_doclet_specified_0_and_1",
485                                docletClassName, argv[i]);
486                 }
487                 docletClassName = argv[i];
488             } else if (arg.equals(ToolOption.DOCLETPATH.opt)) {
489                 oneArg(argv, i++);
490                 if (docletPath == null) {
491                     docletPath = argv[i];
492                 } else {
493                     docletPath += File.pathSeparator + argv[i];
494                 }
495             } else if (arg.equals("-XDaccessInternalAPI")) {
496                 exportInternalAPI = true;
497             }
498         }
499 
500         if (docletClass != null) {
501             // TODO, check no -doclet, -docletpath
502             docletInvoker = new DocletInvoker(messager, docletClass, apiMode, exportInternalAPI);
503         } else {
504             if (docletClassName == null) {
505                 docletClassName = defaultDocletClassName;
506             }
507 
508             // attempt to find doclet
509             docletInvoker = new DocletInvoker(messager, fileManager,
510                     docletClassName, docletPath,
511                     docletParentClassLoader,
512                     apiMode,
513                     exportInternalAPI);
514         }
515     }
516 
517     /**
518      * Set one arg option.
519      * Error and exit if one argument is not provided.
520      */
oneArg(String[] args, int index)521     private void oneArg(String[] args, int index) {
522         if ((index + 1) < args.length) {
523             setOption(args[index], args[index+1]);
524         } else {
525             usageError("main.requires_argument", args[index]);
526         }
527     }
528 
529     @Override
usageError(String key, Object... args)530     void usageError(String key, Object... args) {
531         messager.error(Messager.NOPOS, key, args);
532         usage(true);
533     }
534 
535     /**
536      * indicate an option with no arguments was given.
537      */
setOption(String opt)538     private void setOption(String opt) {
539         String[] option = { opt };
540         options.append(option);
541     }
542 
543     /**
544      * indicate an option with one argument was given.
545      */
setOption(String opt, String argument)546     private void setOption(String opt, String argument) {
547         String[] option = { opt, argument };
548         options.append(option);
549     }
550 
551     /**
552      * indicate an option with the specified list of arguments was given.
553      */
setOption(String opt, List<String> arguments)554     private void setOption(String opt, List<String> arguments) {
555         String[] args = new String[arguments.length() + 1];
556         int k = 0;
557         args[k++] = opt;
558         for (List<String> i = arguments; i.nonEmpty(); i=i.tail) {
559             args[k++] = i.head;
560         }
561         options.append(args);
562     }
563 
564     @Override
getOptionHelper()565     OptionHelper getOptionHelper() {
566         return new GrumpyHelper(messager) {
567             @Override
568             public String get(com.sun.tools.javac.main.Option option) {
569                 return compOpts.get(option);
570             }
571 
572             @Override
573             public void put(String name, String value) {
574                 compOpts.put(name, value);
575             }
576         };
577     }
578 }
579