/*
* Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.javadoc.internal.tool;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.text.BreakIterator;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.file.BaseFileManager;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.main.Arguments;
import com.sun.tools.javac.main.CommandLine;
import com.sun.tools.javac.main.OptionHelper;
import com.sun.tools.javac.main.OptionHelper.GrumpyHelper;
import com.sun.tools.javac.platform.PlatformDescription;
import com.sun.tools.javac.platform.PlatformUtils;
import com.sun.tools.javac.util.ClientCodeException;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Log.WriterKind;
import com.sun.tools.javac.util.Options;
import com.sun.tools.javac.util.StringUtils;
import jdk.javadoc.doclet.Doclet;
import jdk.javadoc.doclet.Doclet.Option;
import jdk.javadoc.doclet.DocletEnvironment;
import jdk.javadoc.internal.tool.Main.Result;
import static javax.tools.DocumentationTool.Location.*;
import static com.sun.tools.javac.main.Option.*;
import static jdk.javadoc.internal.tool.Main.Result.*;
/**
* Main program of Javadoc.
* Previously named "Main".
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
*
* @author Robert Field
* @author Neal Gafter (rewrite)
*/
public class Start extends ToolOption.Helper {
@SuppressWarnings("deprecation")
private static final Class<?> OldStdDoclet =
com.sun.tools.doclets.standard.Standard.class;
private static final Class<?> StdDoclet =
jdk.javadoc.doclet.StandardDoclet.class;
/** Context for this invocation. */
private final Context context;
private static final String ProgramName = "javadoc";
private Messager messager;
private final String docletName;
private final ClassLoader classLoader;
private Class<?> docletClass;
private Doclet doclet;
// used to determine the locale for the messager
private Locale locale;
/**
* In API mode, exceptions thrown while calling the doclet are
* propagated using ClientCodeException.
*/
private boolean apiMode;
private JavaFileManager fileManager;
Start() {
this(null, null, null, null, null, null);
}
Start(PrintWriter outWriter, PrintWriter errWriter) {
this(null, null, outWriter, errWriter, null, null);
}
Start(Context context, String programName,
PrintWriter outWriter, PrintWriter errWriter,
String docletName, ClassLoader classLoader) {
this.context = context == null ? new Context() : context;
String pname = programName == null ? ProgramName : programName;
this.messager = (outWriter == null && errWriter == null)
? new Messager(this.context, pname)
: new Messager(this.context, pname, outWriter, errWriter);
this.docletName = docletName;
this.classLoader = classLoader;
this.docletClass = null;
this.locale = Locale.getDefault();
}
public Start(Context context) {
this.docletClass = null;
this.context = Objects.requireNonNull(context);
this.apiMode = true;
this.docletName = null;
this.classLoader = null;
this.locale = Locale.getDefault();
}
void initMessager() {
if (!apiMode)
return;
if (messager == null) {
Log log = context.get(Log.logKey);
if (log instanceof Messager) {
messager = (Messager) log;
} else {
PrintWriter out = context.get(Log.errKey);
messager = (out == null)
? new Messager(context, ProgramName)
: new Messager(context, ProgramName, out, out);
}
}
}
/**
* Usage
*/
@Override
void usage() {
usage("main.usage", OptionKind.STANDARD, "main.usage.foot");
}
@Override
void Xusage() {
usage("main.Xusage", OptionKind.EXTENDED, "main.Xusage.foot");
}
private void usage(String headerKey, OptionKind kind, String footerKey) {
messager.notice(headerKey);
showToolOptions(kind);
// let doclet print usage information
if (docletClass != null) {
String name = doclet.getName();
messager.notice("main.doclet.usage.header", name);
showDocletOptions(kind == OptionKind.EXTENDED
? Option.Kind.EXTENDED
: Option.Kind.STANDARD);
}
if (footerKey != null)
messager.notice(footerKey);
}
void showToolOptions(OptionKind kind) {
Comparator<ToolOption> comp = new Comparator<ToolOption>() {
final Collator collator = Collator.getInstance(Locale.US);
{ collator.setStrength(Collator.PRIMARY); }
@Override
public int compare(ToolOption o1, ToolOption o2) {
return collator.compare(o1.primaryName, o2.primaryName);
}
};
Stream.of(ToolOption.values())
.filter(opt -> opt.kind == kind)
.sorted(comp)
.forEach(this::showToolOption);
}
void showToolOption(ToolOption option) {
List<String> names = option.getNames();
String parameters;
if (option.hasArg || option.primaryName.endsWith(":")) {
String sep = (option == ToolOption.J) || option.primaryName.endsWith(":") ? "" : " ";
parameters = sep + option.getParameters(messager);
} else {
parameters = "";
}
String description = option.getDescription(messager);
showUsage(names, parameters, description);
}
void showDocletOptions(Option.Kind kind) {
Comparator<Doclet.Option> comp = new Comparator<Doclet.Option>() {
final Collator collator = Collator.getInstance(Locale.US);
{ collator.setStrength(Collator.PRIMARY); }
@Override
public int compare(Doclet.Option o1, Doclet.Option o2) {
return collator.compare(o1.getNames().get(0), o2.getNames().get(0));
}
};
doclet.getSupportedOptions().stream()
.filter(opt -> opt.getKind() == kind)
.sorted(comp)
.forEach(this::showDocletOption);
}
void showDocletOption(Doclet.Option option) {
List<String> names = option.getNames();
String parameters;
String optname = names.get(0);
if (option.getArgumentCount() > 0 || optname.endsWith(":")) {
String sep = optname.endsWith(":") ? "" : " ";
parameters = sep + option.getParameters();
} else {
parameters = "";
}
String description = option.getDescription();
showUsage(names, parameters, description);
}
// The following constants are intended to format the output to
// be similar to that of the java launcher: i.e. "java -help".
/** The indent for the option synopsis. */
private static final String SMALL_INDENT = " ";
/** The automatic indent for the description. */
private static final String LARGE_INDENT = " ";
/** The space allowed for the synopsis, if the description is to be shown on the same line. */
private static final int DEFAULT_SYNOPSIS_WIDTH = 13;
/** The nominal maximum line length, when seeing if text will fit on a line. */
private static final int DEFAULT_MAX_LINE_LENGTH = 80;
/** The format for a single-line help entry. */
private static final String COMPACT_FORMAT = SMALL_INDENT + "%-" + DEFAULT_SYNOPSIS_WIDTH + "s %s";
void showUsage(List<String> names, String parameters, String description) {
String synopses = names.stream()
.map(s -> s + parameters)
.collect(Collectors.joining(", "));
// If option synopses and description fit on a single line of reasonable length,
// display using COMPACT_FORMAT
if (synopses.length() < DEFAULT_SYNOPSIS_WIDTH
&& !description.contains("\n")
&& (SMALL_INDENT.length() + DEFAULT_SYNOPSIS_WIDTH + 1 + description.length() <= DEFAULT_MAX_LINE_LENGTH)) {
messager.printNotice(String.format(COMPACT_FORMAT, synopses, description));
return;
}
// If option synopses fit on a single line of reasonable length, show that;
// otherwise, show 1 per line
if (synopses.length() <= DEFAULT_MAX_LINE_LENGTH) {
messager.printNotice(SMALL_INDENT + synopses);
} else {
for (String name: names) {
messager.printNotice(SMALL_INDENT + name + parameters);
}
}
// Finally, show the description
messager.printNotice(LARGE_INDENT + description.replace("\n", "\n" + LARGE_INDENT));
}
/**
* Main program - external wrapper. In order to maintain backward
* CLI compatibility, we dispatch to the old tool or the old doclet's
* Start mechanism, based on the options present on the command line
* with the following precedence:
* 1. presence of -Xold, dispatch to old tool
* 2. doclet variant, if old, dispatch to old Start
* 3. taglet variant, if old, dispatch to old Start
*
* Thus the presence of -Xold switches the tool, soon after command files
* if any, are expanded, this is performed here, noting that the messager
* is available at this point in time.
* The doclet/taglet tests are performed in the begin method, further on,
* this is to minimize argument processing and most importantly the impact
* of class loader creation, needed to detect the doclet/taglet class variants.
*/
@SuppressWarnings("deprecation")
Result begin(String... argv) {
// Preprocess @file arguments
try {
argv = CommandLine.parse(argv);
} catch (IOException e) {
error("main.cant.read", e.getMessage());
return ERROR;
}
if (argv.length > 0 && "-Xold".equals(argv[0])) {
warn("main.legacy_api");
String[] nargv = Arrays.copyOfRange(argv, 1, argv.length);
int rc = com.sun.tools.javadoc.Main.execute(
messager.programName,
messager.getWriter(WriterKind.ERROR),
messager.getWriter(WriterKind.WARNING),
messager.getWriter(WriterKind.NOTICE),
"com.sun.tools.doclets.standard.Standard",
nargv);
return (rc == 0) ? OK : ERROR;
}
return begin(Arrays.asList(argv), Collections.emptySet());
}
// Called by 199 API.
public boolean begin(Class<?> docletClass,
Iterable<String> options,
Iterable<? extends JavaFileObject> fileObjects) {
this.docletClass = docletClass;
List<String> opts = new ArrayList<>();
for (String opt: options)
opts.add(opt);
return begin(opts, fileObjects).isOK();
}
@SuppressWarnings("deprecation")
private Result begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) {
fileManager = context.get(JavaFileManager.class);
if (fileManager == null) {
JavacFileManager.preRegister(context);
fileManager = context.get(JavaFileManager.class);
if (fileManager instanceof BaseFileManager) {
((BaseFileManager) fileManager).autoClose = true;
}
}
// locale, doclet and maybe taglet, needs to be determined first
try {
docletClass = preprocess(fileManager, options);
} catch (ToolException te) {
if (!te.result.isOK()) {
if (te.message != null) {
messager.printError(te.message);
}
Throwable t = te.getCause();
dumpStack(t == null ? te : t);
}
return te.result;
} catch (OptionException oe) {
if (oe.message != null) {
messager.printError(oe.message);
}
oe.m.run();
Throwable t = oe.getCause();
dumpStack(t == null ? oe : t);
return oe.result;
}
if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) {
// no need to dispatch to old, safe to init now
initMessager();
messager.setLocale(locale);
try {
Object o = docletClass.getConstructor().newInstance();
doclet = (Doclet) o;
} catch (ReflectiveOperationException exc) {
if (apiMode) {
throw new ClientCodeException(exc);
}
error("main.could_not_instantiate_class", docletClass);
return ERROR;
}
} else {
if (apiMode) {
com.sun.tools.javadoc.main.Start ostart
= new com.sun.tools.javadoc.main.Start(context);
return ostart.begin(docletClass, options, fileObjects)
? OK
: ERROR;
}
warn("main.legacy_api");
String[] array = options.toArray(new String[options.size()]);
int rc = com.sun.tools.javadoc.Main.execute(
messager.programName,
messager.getWriter(WriterKind.ERROR),
messager.getWriter(WriterKind.WARNING),
messager.getWriter(WriterKind.NOTICE),
"com.sun.tools.doclets.standard.Standard",
array);
return (rc == 0) ? OK : ERROR;
}
Result result = OK;
try {
result = parseAndExecute(options, fileObjects);
} catch (com.sun.tools.javac.main.Option.InvalidValueException e) {
messager.printError(e.getMessage());
Throwable t = e.getCause();
dumpStack(t == null ? e : t);
return ERROR;
} catch (OptionException toe) {
if (toe.message != null)
messager.printError(toe.message);
toe.m.run();
Throwable t = toe.getCause();
dumpStack(t == null ? toe : t);
return toe.result;
} catch (ToolException exc) {
if (exc.message != null) {
messager.printError(exc.message);
}
Throwable t = exc.getCause();
if (result == ABNORMAL) {
reportInternalError(t == null ? exc : t);
} else {
dumpStack(t == null ? exc : t);
}
return exc.result;
} catch (OutOfMemoryError ee) {
error("main.out.of.memory");
result = SYSERR;
dumpStack(ee);
} catch (ClientCodeException e) {
// simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl
throw e;
} catch (Error | Exception ee) {
error("main.fatal.error", ee);
reportInternalError(ee);
result = ABNORMAL;
} finally {
if (fileManager != null
&& fileManager instanceof BaseFileManager
&& ((BaseFileManager) fileManager).autoClose) {
try {
fileManager.close();
} catch (IOException ignore) {}
}
boolean haveErrorWarnings = messager.hasErrors()
|| (rejectWarnings && messager.hasWarnings());
if (!result.isOK() && !haveErrorWarnings) {
// the doclet failed, but nothing reported, flag it!.
error("main.unknown.error");
}
if (haveErrorWarnings && result.isOK()) {
result = ERROR;
}
messager.printErrorWarningCounts();
messager.flush();
}
return result;
}
private void reportInternalError(Throwable t) {
messager.printErrorUsingKey("doclet.internal.report.bug");
dumpStack(true, t);
}
private void dumpStack(Throwable t) {
dumpStack(false, t);
}
private void dumpStack(boolean enabled, Throwable t) {
if (t != null && (enabled || dumpOnError)) {
t.printStackTrace(System.err);
}
}
/**
* Main program - internal
*/
@SuppressWarnings("unchecked")
private Result parseAndExecute(List<String> argList, Iterable<? extends JavaFileObject> fileObjects)
throws ToolException, OptionException, com.sun.tools.javac.main.Option.InvalidValueException {
long tm = System.currentTimeMillis();
List<String> javaNames = new ArrayList<>();
compOpts = Options.instance(context);
// Make sure no obsolete source/target messages are reported
try {
com.sun.tools.javac.main.Option.XLINT_CUSTOM.process(getOptionHelper(), "-Xlint:-options");
} catch (com.sun.tools.javac.main.Option.InvalidValueException ignore) {
}
doclet.init(locale, messager);
parseArgs(argList, javaNames);
Arguments arguments = Arguments.instance(context);
arguments.init(ProgramName);
arguments.allowEmpty();
if (!arguments.validate()) {
// Arguments does not always increase the error count in the
// case of errors, so increment the error count only if it has
// not been updated previously, preventing complaints by callers
if (!messager.hasErrors() && !messager.hasWarnings())
messager.nerrors++;
return CMDERR;
}
if (fileManager instanceof BaseFileManager) {
((BaseFileManager) fileManager).handleOptions(fileManagerOpts);
}
String platformString = compOpts.get("--release");
if (platformString != null) {
if (compOpts.isSet("-source")) {
String text = messager.getText("main.release.bootclasspath.conflict", "-source");
throw new ToolException(CMDERR, text);
}
if (fileManagerOpts.containsKey(BOOT_CLASS_PATH)) {
String text = messager.getText("main.release.bootclasspath.conflict",
BOOT_CLASS_PATH.getPrimaryName());
throw new ToolException(CMDERR, text);
}
PlatformDescription platformDescription =
PlatformUtils.lookupPlatformDescription(platformString);
if (platformDescription == null) {
String text = messager.getText("main.unsupported.release.version", platformString);
throw new IllegalArgumentException(text);
}
compOpts.put(SOURCE, platformDescription.getSourceVersion());
context.put(PlatformDescription.class, platformDescription);
Collection<Path> platformCP = platformDescription.getPlatformPath();
if (platformCP != null) {
if (fileManager instanceof StandardJavaFileManager) {
StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
try {
sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP);
} catch (IOException ioe) {
throw new ToolException(SYSERR, ioe.getMessage(), ioe);
}
} else {
String text = messager.getText("main.release.not.standard.file.manager",
platformString);
throw new ToolException(ABNORMAL, text);
}
}
}
compOpts.notifyListeners();
List<String> modules = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.MODULE,
s -> Collections.EMPTY_LIST);
if (modules.isEmpty()) {
List<String> subpkgs = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.SUBPACKAGES,
s -> Collections.EMPTY_LIST);
if (subpkgs.isEmpty()) {
if (javaNames.isEmpty() && isEmpty(fileObjects)) {
String text = messager.getText("main.No_modules_packages_or_classes_specified");
throw new ToolException(CMDERR, text);
}
}
}
JavadocTool comp = JavadocTool.make0(context);
if (comp == null) return ABNORMAL;
DocletEnvironment docEnv = comp.getEnvironment(jdtoolOpts,
javaNames,
fileObjects);
// release resources
comp = null;
if (breakiterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
JavacTrees trees = JavacTrees.instance(context);
trees.setBreakIterator(BreakIterator.getSentenceInstance(locale));
}
// pass off control to the doclet
Result returnStatus = docEnv != null && doclet.run(docEnv)
? OK
: ERROR;
// We're done.
if (compOpts.get("-verbose") != null) {
tm = System.currentTimeMillis() - tm;
messager.notice("main.done_in", Long.toString(tm));
}
return returnStatus;
}
boolean matches(List<String> names, String arg) {
for (String name : names) {
if (StringUtils.toLowerCase(name).equals(StringUtils.toLowerCase(arg)))
return true;
}
return false;
}
boolean matches(Doclet.Option option, String arg) {
if (matches(option.getNames(), arg))
return true;
int sep = arg.indexOf(':');
String targ = arg.substring(0, sep + 1);
return matches(option.getNames(), targ);
}
Set<? extends Doclet.Option> docletOptions = null;
int handleDocletOptions(int idx, List<String> args, boolean isToolOption)
throws OptionException {
if (docletOptions == null) {
docletOptions = doclet.getSupportedOptions();
}
String arg = args.get(idx);
String argBase, argVal;
if (arg.startsWith("--") && arg.contains("=")) {
int sep = arg.indexOf("=");
argBase = arg.substring(0, sep);
argVal = arg.substring(sep + 1);
} else {
argBase = arg;
argVal = null;
}
String text = null;
for (Doclet.Option opt : docletOptions) {
if (matches(opt, argBase)) {
if (argVal != null) {
switch (opt.getArgumentCount()) {
case 0:
text = messager.getText("main.unnecessary_arg_provided", argBase);
throw new OptionException(ERROR, this::usage, text);
case 1:
opt.process(arg, Arrays.asList(argVal));
break;
default:
text = messager.getText("main.only_one_argument_with_equals", argBase);
throw new OptionException(ERROR, this::usage, text);
}
} else {
if (args.size() - idx -1 < opt.getArgumentCount()) {
text = messager.getText("main.requires_argument", arg);
throw new OptionException(ERROR, this::usage, text);
}
opt.process(arg, args.subList(idx + 1, args.size()));
idx += opt.getArgumentCount();
}
return idx;
}
}
// check if arg is accepted by the tool before emitting error
if (!isToolOption) {
text = messager.getText("main.invalid_flag", arg);
throw new OptionException(ERROR, this::usage, text);
}
return idx;
}
private Class<?> preprocess(JavaFileManager jfm,
List<String> argv) throws ToolException, OptionException {
// doclet specifying arguments
String userDocletPath = null;
String userDocletName = null;
// taglet specifying arguments, since tagletpath is a doclet
// functionality, assume they are repeated and inspect all.
List<File> userTagletPath = new ArrayList<>();
List<String> userTagletNames = new ArrayList<>();
// Step 1: loop through the args, set locale early on, if found.
for (int i = 0 ; i < argv.size() ; i++) {
String arg = argv.get(i);
if (arg.equals(ToolOption.DUMPONERROR.primaryName)) {
dumpOnError = true;
} else if (arg.equals(ToolOption.LOCALE.primaryName)) {
checkOneArg(argv, i++);
String lname = argv.get(i);
locale = getLocale(lname);
} else if (arg.equals(ToolOption.DOCLET.primaryName)) {
checkOneArg(argv, i++);
if (userDocletName != null) {
if (apiMode) {
throw new IllegalArgumentException("More than one doclet specified (" +
userDocletName + " and " + argv.get(i) + ").");
}
String text = messager.getText("main.more_than_one_doclet_specified_0_and_1",
userDocletName, argv.get(i));
throw new ToolException(CMDERR, text);
}
if (docletName != null) {
if (apiMode) {
throw new IllegalArgumentException("More than one doclet specified (" +
docletName + " and " + argv.get(i) + ").");
}
String text = messager.getText("main.more_than_one_doclet_specified_0_and_1",
docletName, argv.get(i));
throw new ToolException(CMDERR, text);
}
userDocletName = argv.get(i);
} else if (arg.equals(ToolOption.DOCLETPATH.primaryName)) {
checkOneArg(argv, i++);
if (userDocletPath == null) {
userDocletPath = argv.get(i);
} else {
userDocletPath += File.pathSeparator + argv.get(i);
}
} else if ("-taglet".equals(arg)) {
userTagletNames.add(argv.get(i + 1));
} else if ("-tagletpath".equals(arg)) {
for (String pathname : argv.get(i + 1).split(File.pathSeparator)) {
userTagletPath.add(new File(pathname));
}
}
}
// Step 2: a doclet is provided, nothing more to do.
if (docletClass != null) {
return docletClass;
}
// Step 3: doclet name specified ? if so find a ClassLoader,
// and load it.
if (userDocletName != null) {
ClassLoader cl = classLoader;
if (cl == null) {
if (!fileManager.hasLocation(DOCLET_PATH)) {
List<File> paths = new ArrayList<>();
if (userDocletPath != null) {
for (String pathname : userDocletPath.split(File.pathSeparator)) {
paths.add(new File(pathname));
}
}
try {
((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths);
} catch (IOException ioe) {
if (apiMode) {
throw new IllegalArgumentException("Could not set location for " +
userDocletPath, ioe);
}
String text = messager.getText("main.doclet_could_not_set_location",
userDocletPath);
throw new ToolException(CMDERR, text, ioe);
}
}
cl = fileManager.getClassLoader(DOCLET_PATH);
if (cl == null) {
// despite doclet specified on cmdline no classloader found!
if (apiMode) {
throw new IllegalArgumentException("Could not obtain classloader to load "
+ userDocletPath);
}
String text = messager.getText("main.doclet_no_classloader_found",
userDocletName);
throw new ToolException(CMDERR, text);
}
}
try {
Class<?> klass = cl.loadClass(userDocletName);
return klass;
} catch (ClassNotFoundException cnfe) {
if (apiMode) {
throw new IllegalArgumentException("Cannot find doclet class " + userDocletName,
cnfe);
}
String text = messager.getText("main.doclet_class_not_found", userDocletName);
throw new ToolException(CMDERR, text, cnfe);
}
}
// Step 4: we have a doclet, try loading it
if (docletName != null) {
try {
return Class.forName(docletName, true, getClass().getClassLoader());
} catch (ClassNotFoundException cnfe) {
if (apiMode) {
throw new IllegalArgumentException("Cannot find doclet class " + userDocletName);
}
String text = messager.getText("main.doclet_class_not_found", userDocletName);
throw new ToolException(CMDERR, text, cnfe);
}
}
// Step 5: we don't have a doclet specified, do we have taglets ?
if (!userTagletNames.isEmpty() && hasOldTaglet(userTagletNames, userTagletPath)) {
// found a bogey, return the old doclet
return OldStdDoclet;
}
// finally
return StdDoclet;
}
/*
* This method returns true iff it finds a legacy taglet, but for
* all other conditions including errors it returns false, allowing
* nature to take its own course.
*/
@SuppressWarnings("deprecation")
private boolean hasOldTaglet(List<String> tagletNames, List<File> tagletPaths) throws ToolException {
if (!fileManager.hasLocation(TAGLET_PATH)) {
try {
((StandardJavaFileManager) fileManager).setLocation(TAGLET_PATH, tagletPaths);
} catch (IOException ioe) {
String text = messager.getText("main.doclet_could_not_set_location", tagletPaths);
throw new ToolException(CMDERR, text, ioe);
}
}
ClassLoader cl = fileManager.getClassLoader(TAGLET_PATH);
if (cl == null) {
// no classloader found!
String text = messager.getText("main.doclet_no_classloader_found", tagletNames.get(0));
throw new ToolException(CMDERR, text);
}
for (String tagletName : tagletNames) {
try {
Class<?> klass = cl.loadClass(tagletName);
if (com.sun.tools.doclets.Taglet.class.isAssignableFrom(klass)) {
return true;
}
} catch (ClassNotFoundException cnfe) {
String text = messager.getText("main.doclet_class_not_found", tagletName);
throw new ToolException(CMDERR, text, cnfe);
}
}
return false;
}
private void parseArgs(List<String> args, List<String> javaNames) throws ToolException,
OptionException, com.sun.tools.javac.main.Option.InvalidValueException {
for (int i = 0 ; i < args.size() ; i++) {
String arg = args.get(i);
ToolOption o = ToolOption.get(arg);
if (o != null) {
// handle a doclet argument that may be needed however
// don't increment the index, and allow the tool to consume args
handleDocletOptions(i, args, true);
if (o.hasArg) {
if (arg.startsWith("--") && arg.contains("=")) {
o.process(this, arg.substring(arg.indexOf('=') + 1));
} else {
checkOneArg(args, i++);
o.process(this, args.get(i));
}
} else if (o.hasSuffix) {
o.process(this, arg);
} else {
o.process(this);
}
} else if (arg.startsWith("-XD")) {
// hidden javac options
String s = arg.substring("-XD".length());
int eq = s.indexOf('=');
String key = (eq < 0) ? s : s.substring(0, eq);
String value = (eq < 0) ? s : s.substring(eq+1);
compOpts.put(key, value);
} else if (arg.startsWith("-")) {
i = handleDocletOptions(i, args, false);
} else {
javaNames.add(arg);
}
}
}
private <T> boolean isEmpty(Iterable<T> iter) {
return !iter.iterator().hasNext();
}
/**
* Check the one arg option.
* Error and exit if one argument is not provided.
*/
private void checkOneArg(List<String> args, int index) throws OptionException {
if ((index + 1) >= args.size() || args.get(index + 1).startsWith("-d")) {
String text = messager.getText("main.requires_argument", args.get(index));
throw new OptionException(CMDERR, this::usage, text);
}
}
void error(String key, Object... args) {
messager.printErrorUsingKey(key, args);
}
void warn(String key, Object... args) {
messager.printWarningUsingKey(key, args);
}
/**
* Get the locale if specified on the command line
* else return null and if locale option is not used
* then return default locale.
*/
private Locale getLocale(String localeName) throws ToolException {
Locale userlocale = null;
if (localeName == null || localeName.isEmpty()) {
return Locale.getDefault();
}
int firstuscore = localeName.indexOf('_');
int seconduscore = -1;
String language = null;
String country = null;
String variant = null;
if (firstuscore == 2) {
language = localeName.substring(0, firstuscore);
seconduscore = localeName.indexOf('_', firstuscore + 1);
if (seconduscore > 0) {
if (seconduscore != firstuscore + 3
|| localeName.length() <= seconduscore + 1) {
String text = messager.getText("main.malformed_locale_name", localeName);
throw new ToolException(CMDERR, text);
}
country = localeName.substring(firstuscore + 1,
seconduscore);
variant = localeName.substring(seconduscore + 1);
} else if (localeName.length() == firstuscore + 3) {
country = localeName.substring(firstuscore + 1);
} else {
String text = messager.getText("main.malformed_locale_name", localeName);
throw new ToolException(CMDERR, text);
}
} else if (firstuscore == -1 && localeName.length() == 2) {
language = localeName;
} else {
String text = messager.getText("main.malformed_locale_name", localeName);
throw new ToolException(CMDERR, text);
}
userlocale = searchLocale(language, country, variant);
if (userlocale == null) {
String text = messager.getText("main.illegal_locale_name", localeName);
throw new ToolException(CMDERR, text);
} else {
return userlocale;
}
}
/**
* Search the locale for specified language, specified country and
* specified variant.
*/
private Locale searchLocale(String language, String country,
String variant) {
for (Locale loc : Locale.getAvailableLocales()) {
if (loc.getLanguage().equals(language) &&
(country == null || loc.getCountry().equals(country)) &&
(variant == null || loc.getVariant().equals(variant))) {
return loc;
}
}
return null;
}
@Override
OptionHelper getOptionHelper() {
return new GrumpyHelper(messager) {
@Override
public String get(com.sun.tools.javac.main.Option option) {
return compOpts.get(option);
}
@Override
public void put(String name, String value) {
compOpts.put(name, value);
}
@Override
public void remove(String name) {
compOpts.remove(name);
}
@Override
public boolean handleFileManagerOption(com.sun.tools.javac.main.Option option, String value) {
fileManagerOpts.put(option, value);
return true;
}
};
}
@Override
String getLocalizedMessage(String msg, Object... args) {
return messager.getText(msg, args);
}
}