duke@1: /* jjg@1797: * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. duke@1: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. duke@1: * duke@1: * This code is free software; you can redistribute it and/or modify it duke@1: * under the terms of the GNU General Public License version 2 only, as ohair@554: * published by the Free Software Foundation. Oracle designates this duke@1: * particular file as subject to the "Classpath" exception as provided ohair@554: * by Oracle in the LICENSE file that accompanied this code. duke@1: * duke@1: * This code is distributed in the hope that it will be useful, but WITHOUT duke@1: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or duke@1: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License duke@1: * version 2 for more details (a copy is included in the LICENSE file that duke@1: * accompanied this code). duke@1: * duke@1: * You should have received a copy of the GNU General Public License version duke@1: * 2 along with this work; if not, write to the Free Software Foundation, duke@1: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. duke@1: * ohair@554: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohair@554: * or visit www.oracle.com if you need additional information or have any ohair@554: * questions. duke@1: */ duke@1: duke@1: package com.sun.tools.javadoc; duke@1: jjg@1357: import java.io.File; jjg@1357: import java.io.FileNotFoundException; jjg@1357: import java.io.IOException; jjg@1357: import java.io.PrintWriter; jjg@1413: import java.util.ArrayList; jjg@1413: import java.util.Collection; jjg@1413: import java.util.Collections; jjg@1413: jjg@1413: import javax.tools.JavaFileManager; jjg@1413: import javax.tools.JavaFileObject; jjg@1357: duke@1: import com.sun.javadoc.*; duke@1: import com.sun.tools.javac.main.CommandLine; jjg@1413: import com.sun.tools.javac.util.ClientCodeException; duke@1: import com.sun.tools.javac.util.Context; duke@1: import com.sun.tools.javac.util.List; duke@1: import com.sun.tools.javac.util.ListBuffer; jjg@1135: import com.sun.tools.javac.util.Log; duke@1: import com.sun.tools.javac.util.Options; duke@1: import static com.sun.tools.javac.code.Flags.*; duke@1: duke@1: /** duke@1: * Main program of Javadoc. duke@1: * Previously named "Main". duke@1: * jjg@1359: *

This is NOT part of any supported API. jjg@1359: * If you write code that depends on this, you do so at your own risk. jjg@1359: * This code and its internal interfaces are subject to change or jjg@1359: * deletion without notice. jjg@1359: * duke@1: * @since 1.2 duke@1: * @author Robert Field duke@1: * @author Neal Gafter (rewrite) duke@1: */ jjg@1411: public class Start extends ToolOption.Helper { jjg@1392: /** Context for this invocation. */ jjg@1392: private final Context context; duke@1: duke@1: private final String defaultDocletClassName; jjg@129: private final ClassLoader docletParentClassLoader; duke@1: duke@1: private static final String javadocName = "javadoc"; duke@1: duke@1: private static final String standardDocletClassName = duke@1: "com.sun.tools.doclets.standard.Standard"; duke@1: duke@1: private long defaultFilter = PUBLIC | PROTECTED; duke@1: jjg@1392: private final Messager messager; duke@1: duke@1: private DocletInvoker docletInvoker; duke@1: jjg@1413: /** jjg@1413: * In API mode, exceptions thrown while calling the doclet are jjg@1413: * propagated using ClientCodeException. jjg@1413: */ jjg@1413: private boolean apiMode; jjg@1413: duke@1: Start(String programName, duke@1: PrintWriter errWriter, duke@1: PrintWriter warnWriter, duke@1: PrintWriter noticeWriter, duke@1: String defaultDocletClassName) { jjg@129: this(programName, errWriter, warnWriter, noticeWriter, defaultDocletClassName, null); jjg@129: } jjg@129: jjg@129: Start(String programName, jjg@129: PrintWriter errWriter, jjg@129: PrintWriter warnWriter, jjg@129: PrintWriter noticeWriter, jjg@129: String defaultDocletClassName, jjg@129: ClassLoader docletParentClassLoader) { jjg@1392: context = new Context(); jjg@1392: messager = new Messager(context, programName, errWriter, warnWriter, noticeWriter); duke@1: this.defaultDocletClassName = defaultDocletClassName; jjg@129: this.docletParentClassLoader = docletParentClassLoader; duke@1: } duke@1: duke@1: Start(String programName, String defaultDocletClassName) { jjg@129: this(programName, defaultDocletClassName, null); jjg@129: } jjg@129: jjg@129: Start(String programName, String defaultDocletClassName, jjg@129: ClassLoader docletParentClassLoader) { jjg@1392: context = new Context(); jjg@1392: messager = new Messager(context, programName); duke@1: this.defaultDocletClassName = defaultDocletClassName; jjg@129: this.docletParentClassLoader = docletParentClassLoader; jjg@129: } jjg@129: jjg@129: Start(String programName, ClassLoader docletParentClassLoader) { jjg@129: this(programName, standardDocletClassName, docletParentClassLoader); duke@1: } duke@1: duke@1: Start(String programName) { duke@1: this(programName, standardDocletClassName); duke@1: } duke@1: jjg@129: Start(ClassLoader docletParentClassLoader) { jjg@129: this(javadocName, docletParentClassLoader); jjg@129: } jjg@129: duke@1: Start() { duke@1: this(javadocName); duke@1: } duke@1: jjg@1411: public Start(Context context) { jjg@1411: context.getClass(); // null check jjg@1411: this.context = context; jjg@1413: apiMode = true; jjg@1411: defaultDocletClassName = standardDocletClassName; jjg@1411: docletParentClassLoader = null; jjg@1411: jjg@1411: Log log = context.get(Log.logKey); jjg@1411: if (log instanceof Messager) jjg@1411: messager = (Messager) log; jjg@1411: else { jjg@1411: PrintWriter out = context.get(Log.outKey); jjg@1411: messager = (out == null) ? new Messager(context, javadocName) jjg@1411: : new Messager(context, javadocName, out, out, out); jjg@1411: } jjg@1411: } jjg@1411: duke@1: /** duke@1: * Usage duke@1: */ jjg@1411: @Override jjg@1411: void usage() { jjg@1411: usage(true); jjg@1411: } jjg@1411: jjg@1797: void usage(boolean exit) { jjg@1797: usage("main.usage", "-help", null, exit); jjg@1411: } jjg@1411: jjg@1411: @Override jjg@1411: void Xusage() { jjg@1411: Xusage(true); duke@1: } duke@1: jjg@1797: void Xusage(boolean exit) { jjg@1797: usage("main.Xusage", "-X", "main.Xusage.foot", exit); jjg@1797: } jjg@1797: jjg@1797: private void usage(String main, String doclet, String foot, boolean exit) { jjg@1797: // RFE: it would be better to replace the following with code to jjg@1797: // write a header, then help for each option, then a footer. jjg@1797: messager.notice(main); jjg@1797: jjg@1797: // let doclet print usage information (does nothing on error) jjg@1797: if (docletInvoker != null) { jjg@1797: // RFE: this is a pretty bad way to get the doclet to show jjg@1797: // help info. Moreover, the output appears on stdout, jjg@1797: // and not on any of the standard streams passed jjg@1797: // to javadoc, and in particular, not to the noticeWriter jjg@1797: // But, to fix this, we need to fix the Doclet API. jjg@1797: docletInvoker.optionLength(doclet); jjg@1797: } jjg@1797: jjg@1797: if (foot != null) jjg@1797: messager.notice(foot); jjg@1797: jjg@1411: if (exit) exit(); jjg@584: } jjg@584: jjg@584: /** duke@1: * Exit duke@1: */ duke@1: private void exit() { duke@1: messager.exit(); duke@1: } duke@1: duke@1: duke@1: /** duke@1: * Main program - external wrapper duke@1: */ jjg@127: int begin(String... argv) { jjg@1413: boolean ok = begin(null, argv, Collections. emptySet()); jjg@1413: return ok ? 0 : 1; jjg@1413: } jjg@1413: jjg@1413: public boolean begin(Class docletClass, Iterable options, Iterable fileObjects) { jjg@1413: Collection opts = new ArrayList(); jjg@1413: for (String opt: options) opts.add(opt); jjg@1413: return begin(docletClass, opts.toArray(new String[opts.size()]), fileObjects); jjg@1413: } jjg@1413: jjg@1413: private boolean begin(Class docletClass, String[] options, Iterable fileObjects) { duke@1: boolean failed = false; duke@1: duke@1: try { jjg@1413: failed = !parseAndExecute(docletClass, options, fileObjects); jjg@1411: } catch (Messager.ExitJavadoc exc) { duke@1: // ignore, we just exit this way duke@1: } catch (OutOfMemoryError ee) { jjg@1411: messager.error(Messager.NOPOS, "main.out.of.memory"); duke@1: failed = true; jjg@1413: } catch (ClientCodeException e) { jjg@1413: // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl jjg@1413: throw e; duke@1: } catch (Error ee) { jjg@1135: ee.printStackTrace(System.err); jjg@1411: messager.error(Messager.NOPOS, "main.fatal.error"); duke@1: failed = true; duke@1: } catch (Exception ee) { jjg@1135: ee.printStackTrace(System.err); jjg@1411: messager.error(Messager.NOPOS, "main.fatal.exception"); duke@1: failed = true; duke@1: } finally { duke@1: messager.exitNotice(); duke@1: messager.flush(); duke@1: } duke@1: failed |= messager.nerrors() > 0; duke@1: failed |= rejectWarnings && messager.nwarnings() > 0; jjg@1413: return !failed; duke@1: } duke@1: duke@1: /** duke@1: * Main program - internal duke@1: */ jjg@1413: private boolean parseAndExecute( jjg@1413: Class docletClass, jjg@1413: String[] argv, jjg@1413: Iterable fileObjects) throws IOException { duke@1: long tm = System.currentTimeMillis(); duke@1: duke@1: ListBuffer javaNames = new ListBuffer(); duke@1: duke@1: // Preprocess @file arguments duke@1: try { duke@1: argv = CommandLine.parse(argv); duke@1: } catch (FileNotFoundException e) { jjg@1411: messager.error(Messager.NOPOS, "main.cant.read", e.getMessage()); duke@1: exit(); duke@1: } catch (IOException e) { jjg@1135: e.printStackTrace(System.err); duke@1: exit(); duke@1: } duke@1: jjg@1413: jjg@1413: JavaFileManager fileManager = context.get(JavaFileManager.class); jjg@1413: setDocletInvoker(docletClass, fileManager, argv); jjg@584: jjg@1411: compOpts = Options.instance(context); darcy@1961: // Make sure no obsolete source/target messages are reported darcy@1961: compOpts.put("-Xlint:-options", "-Xlint:-options"); duke@1: duke@1: // Parse arguments duke@1: for (int i = 0 ; i < argv.length ; i++) { duke@1: String arg = argv[i]; jjg@1411: jjg@1411: ToolOption o = ToolOption.get(arg); jjg@1411: if (o != null) { jjg@1411: // hack: this restriction should be removed jjg@1411: if (o == ToolOption.LOCALE && i > 0) jjg@1411: usageError("main.locale_first"); jjg@1411: jjg@1411: if (o.hasArg) { jjg@1411: oneArg(argv, i++); jjg@1411: o.process(this, argv[i]); jjg@1411: } else { jjg@1411: setOption(arg); jjg@1411: o.process(this); duke@1: } jjg@1411: duke@1: } else if (arg.startsWith("-XD")) { jjg@1411: // hidden javac options duke@1: String s = arg.substring("-XD".length()); duke@1: int eq = s.indexOf('='); duke@1: String key = (eq < 0) ? s : s.substring(0, eq); duke@1: String value = (eq < 0) ? s : s.substring(eq+1); duke@1: compOpts.put(key, value); duke@1: } duke@1: // call doclet for its options duke@1: // other arg starts with - is invalid jjg@1411: else if (arg.startsWith("-")) { duke@1: int optionLength; duke@1: optionLength = docletInvoker.optionLength(arg); duke@1: if (optionLength < 0) { duke@1: // error already displayed duke@1: exit(); duke@1: } else if (optionLength == 0) { duke@1: // option not found duke@1: usageError("main.invalid_flag", arg); duke@1: } else { duke@1: // doclet added option duke@1: if ((i + optionLength) > argv.length) { duke@1: usageError("main.requires_argument", arg); duke@1: } duke@1: ListBuffer args = new ListBuffer(); duke@1: for (int j = 0; j < optionLength-1; ++j) { duke@1: args.append(argv[++i]); duke@1: } duke@1: setOption(arg, args.toList()); duke@1: } duke@1: } else { duke@1: javaNames.append(arg); duke@1: } duke@1: } jjg@1392: compOpts.notifyListeners(); duke@1: jjg@1413: if (javaNames.isEmpty() && subPackages.isEmpty() && isEmpty(fileObjects)) { duke@1: usageError("main.No_packages_or_classes_specified"); duke@1: } duke@1: duke@1: if (!docletInvoker.validOptions(options.toList())) { duke@1: // error message already displayed duke@1: exit(); duke@1: } duke@1: duke@1: JavadocTool comp = JavadocTool.make0(context); duke@1: if (comp == null) return false; duke@1: duke@1: if (showAccess == null) { duke@1: setFilter(defaultFilter); duke@1: } duke@1: duke@1: LanguageVersion languageVersion = docletInvoker.languageVersion(); duke@1: RootDocImpl root = comp.getRootDocImpl( jjg@1411: docLocale, jjg@1411: encoding, jjg@1411: showAccess, jjg@1411: javaNames.toList(), jjg@1411: options.toList(), jjg@1413: fileObjects, jjg@1411: breakiterator, jjg@1411: subPackages.toList(), jjg@1411: excludedPackages.toList(), duke@1: docClasses, duke@1: // legacy? jjg@1411: languageVersion == null || languageVersion == LanguageVersion.JAVA_1_1, jjg@1411: quiet); duke@1: jjg@1391: // release resources jjg@1391: comp = null; jjg@1391: duke@1: // pass off control to the doclet duke@1: boolean ok = root != null; duke@1: if (ok) ok = docletInvoker.start(root); duke@1: duke@1: // We're done. duke@1: if (compOpts.get("-verbose") != null) { duke@1: tm = System.currentTimeMillis() - tm; duke@1: messager.notice("main.done_in", Long.toString(tm)); duke@1: } duke@1: duke@1: return ok; duke@1: } duke@1: jjg@1413: private boolean isEmpty(Iterable iter) { jjg@1413: return !iter.iterator().hasNext(); jjg@1413: } jjg@1413: jjg@1413: /** jjg@1413: * Init the doclet invoker. jjg@1413: * The doclet class may be given explicitly, or via the -doclet option in jjg@1413: * argv. jjg@1413: * If the doclet class is not given explicitly, it will be loaded from jjg@1413: * the file manager's DOCLET_PATH location, if available, or via the jjg@1413: * -doclet path option in argv. jjg@1413: * @param docletClass The doclet class. May be null. jjg@1413: * @param fileManager The file manager used to get the class loader to load jjg@1413: * the doclet class if required. May be null. jjg@1413: * @param argv Args containing -doclet and -docletpath, in case they are required. jjg@1413: */ jjg@1413: private void setDocletInvoker(Class docletClass, JavaFileManager fileManager, String[] argv) { jjg@1413: if (docletClass != null) { jjg@1413: docletInvoker = new DocletInvoker(messager, docletClass, apiMode); jjg@1413: // TODO, check no -doclet, -docletpath jjg@1413: return; jjg@1413: } jjg@1413: duke@1: String docletClassName = null; duke@1: String docletPath = null; duke@1: duke@1: // Parse doclet specifying arguments duke@1: for (int i = 0 ; i < argv.length ; i++) { duke@1: String arg = argv[i]; jjg@1413: if (arg.equals(ToolOption.DOCLET.opt)) { duke@1: oneArg(argv, i++); duke@1: if (docletClassName != null) { duke@1: usageError("main.more_than_one_doclet_specified_0_and_1", duke@1: docletClassName, argv[i]); duke@1: } duke@1: docletClassName = argv[i]; jjg@1413: } else if (arg.equals(ToolOption.DOCLETPATH.opt)) { duke@1: oneArg(argv, i++); duke@1: if (docletPath == null) { duke@1: docletPath = argv[i]; duke@1: } else { duke@1: docletPath += File.pathSeparator + argv[i]; duke@1: } duke@1: } duke@1: } duke@1: duke@1: if (docletClassName == null) { duke@1: docletClassName = defaultDocletClassName; duke@1: } duke@1: duke@1: // attempt to find doclet jjg@1413: docletInvoker = new DocletInvoker(messager, fileManager, jjg@1413: docletClassName, docletPath, jjg@1413: docletParentClassLoader, jjg@1413: apiMode); duke@1: } duke@1: duke@1: /** duke@1: * Set one arg option. duke@1: * Error and exit if one argument is not provided. duke@1: */ duke@1: private void oneArg(String[] args, int index) { duke@1: if ((index + 1) < args.length) { duke@1: setOption(args[index], args[index+1]); duke@1: } else { duke@1: usageError("main.requires_argument", args[index]); duke@1: } duke@1: } duke@1: jjg@1411: @Override jjg@1411: void usageError(String key, Object... args) { jjg@1411: messager.error(Messager.NOPOS, key, args); jjg@1411: usage(true); duke@1: } duke@1: duke@1: /** duke@1: * indicate an option with no arguments was given. duke@1: */ duke@1: private void setOption(String opt) { duke@1: String[] option = { opt }; duke@1: options.append(option); duke@1: } duke@1: duke@1: /** duke@1: * indicate an option with one argument was given. duke@1: */ duke@1: private void setOption(String opt, String argument) { duke@1: String[] option = { opt, argument }; duke@1: options.append(option); duke@1: } duke@1: duke@1: /** duke@1: * indicate an option with the specified list of arguments was given. duke@1: */ duke@1: private void setOption(String opt, List arguments) { duke@1: String[] args = new String[arguments.length() + 1]; duke@1: int k = 0; duke@1: args[k++] = opt; duke@1: for (List i = arguments; i.nonEmpty(); i=i.tail) { duke@1: args[k++] = i.head; duke@1: } jjg@1411: options.append(args); duke@1: } duke@1: }