/* * Copyright (c) 1997, 2019, 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.doclets.toolkit; import java.io.*; import java.util.*; import javax.lang.model.element.Element; import javax.lang.model.element.ModuleElement; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.util.SimpleElementVisitor14; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import com.sun.source.util.DocTreePath; import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; import jdk.javadoc.doclet.Doclet; import jdk.javadoc.doclet.DocletEnvironment; import jdk.javadoc.doclet.Reporter; import jdk.javadoc.doclet.StandardDoclet; import jdk.javadoc.doclet.Taglet; import jdk.javadoc.internal.doclets.formats.html.HtmlDoclet; import jdk.javadoc.internal.doclets.toolkit.builders.BuilderFactory; import jdk.javadoc.internal.doclets.toolkit.taglets.TagletManager; import jdk.javadoc.internal.doclets.toolkit.util.DocFile; import jdk.javadoc.internal.doclets.toolkit.util.DocFileFactory; import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException; import jdk.javadoc.internal.doclets.toolkit.util.Extern; import jdk.javadoc.internal.doclets.toolkit.util.Group; import jdk.javadoc.internal.doclets.toolkit.util.MetaKeywords; import jdk.javadoc.internal.doclets.toolkit.util.SimpleDocletException; import jdk.javadoc.internal.doclets.toolkit.util.TypeElementCatalog; import jdk.javadoc.internal.doclets.toolkit.util.Utils; import jdk.javadoc.internal.doclets.toolkit.util.Utils.Pair; import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberCache; import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable; import static javax.tools.Diagnostic.Kind.*; /** * Configure the output based on the options. Doclets should sub-class * BaseConfiguration, to configure and add their own options. This class contains * all user options which are supported by the standard doclet. *

*

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. */ public abstract class BaseConfiguration { /** * The doclet that created this configuration. */ public final Doclet doclet; /** * The factory for builders. */ protected BuilderFactory builderFactory; /** * The taglet manager. */ public TagletManager tagletManager; /** * The path to the builder XML input file. */ public String builderXMLPath; /** * The default path to the builder XML. */ public static final String DEFAULT_BUILDER_XML = "resources/doclet.xml"; /** * Maintain backward compatibility with previous javadoc version */ public boolean backwardCompatibility = true; /** * The meta tag keywords instance. */ public MetaKeywords metakeywords; /** * The doclet environment. */ public DocletEnvironment docEnv; /** * An utility class for commonly used helpers */ public Utils utils; /** * All the temporary accessors to javac internals. */ public WorkArounds workArounds; /** * Sourcepath from where to read the source files. Default is classpath. */ public String sourcepath = ""; /** * Generate modules documentation if more than one module is present. */ public boolean showModules = false; /** * The catalog of classes specified on the command-line */ public TypeElementCatalog typeElementCatalog; /** * The package grouping instance. */ public final Group group = new Group(this); /** * The tracker of external package links. */ public Extern extern; public Reporter reporter; public Locale locale; public abstract Messages getMessages(); public abstract Resources getResources(); /** * Returns a string identifying the version of the doclet. * * @return a version string */ public abstract String getDocletVersion(); /** * This method should be defined in all those doclets (configurations), * which want to derive themselves from this BaseConfiguration. This method * can be used to finish up the options setup. * * @return true if successful and false otherwise */ public abstract boolean finishOptionSettings(); public CommentUtils cmtUtils; /** * A sorted set of included packages. */ public SortedSet packages = null; public OverviewElement overviewElement; public DocFileFactory docFileFactory; /** * A sorted map, giving the (specified|included|other) packages for each module. */ public SortedMap> modulePackages; /** * The list of known modules, that should be documented. */ public SortedSet modules; protected static final String sharedResourceBundleName = "jdk.javadoc.internal.doclets.toolkit.resources.doclets"; VisibleMemberCache visibleMemberCache = null; public PropertyUtils propertyUtils = null; /** * Constructs the configurations needed by the doclet. * * @apiNote * The {@code doclet} parameter is used when {@link Taglet#init(DocletEnvironment, Doclet) * initializing tags}. * Some doclets (such as the {@link StandardDoclet), may delegate to another * (such as the {@link HtmlDoclet}). In such cases, the primary doclet (i.e * {@code StandardDoclet}) should be provided here, and not any internal * class like {@code HtmlDoclet}. * * @param doclet the doclet for this run of javadoc */ public BaseConfiguration(Doclet doclet) { this.doclet = doclet; } public abstract BaseOptions getOptions(); private boolean initialized = false; protected void initConfiguration(DocletEnvironment docEnv) { if (initialized) { throw new IllegalStateException("configuration previously initialized"); } initialized = true; this.docEnv = docEnv; // Utils needs docEnv, safe to init now. utils = new Utils(this); BaseOptions options = getOptions(); if (!options.javafx) { options.javafx = isJavaFXMode(); } // Once docEnv and Utils have been initialized, others should be safe. metakeywords = new MetaKeywords(this); cmtUtils = new CommentUtils(this); workArounds = new WorkArounds(this); visibleMemberCache = new VisibleMemberCache(this); propertyUtils = new PropertyUtils(this); Splitter specifiedSplitter = new Splitter(docEnv, false); specifiedModuleElements = Collections.unmodifiableSet(specifiedSplitter.mset); specifiedPackageElements = Collections.unmodifiableSet(specifiedSplitter.pset); specifiedTypeElements = Collections.unmodifiableSet(specifiedSplitter.tset); Splitter includedSplitter = new Splitter(docEnv, true); includedModuleElements = Collections.unmodifiableSet(includedSplitter.mset); includedPackageElements = Collections.unmodifiableSet(includedSplitter.pset); includedTypeElements = Collections.unmodifiableSet(includedSplitter.tset); } /** * Return the builder factory for this doclet. * * @return the builder factory for this doclet. */ public BuilderFactory getBuilderFactory() { if (builderFactory == null) { builderFactory = new BuilderFactory(this); } return builderFactory; } public Reporter getReporter() { return this.reporter; } private Set specifiedModuleElements; public Set getSpecifiedModuleElements() { return specifiedModuleElements; } private Set specifiedPackageElements; public Set getSpecifiedPackageElements() { return specifiedPackageElements; } private Set specifiedTypeElements; public Set getSpecifiedTypeElements() { return specifiedTypeElements; } private Set includedModuleElements; public Set getIncludedModuleElements() { return includedModuleElements; } private Set includedPackageElements; public Set getIncludedPackageElements() { return includedPackageElements; } private Set includedTypeElements; public Set getIncludedTypeElements() { return includedTypeElements; } private void initModules() { // Build the modules structure used by the doclet modules = new TreeSet<>(utils.makeModuleComparator()); modules.addAll(getSpecifiedModuleElements()); modulePackages = new TreeMap<>(utils.makeModuleComparator()); for (PackageElement p : packages) { ModuleElement mdle = docEnv.getElementUtils().getModuleOf(p); if (mdle != null && !mdle.isUnnamed()) { Set s = modulePackages .computeIfAbsent(mdle, m -> new TreeSet<>(utils.makePackageComparator())); s.add(p); } } for (PackageElement p : getIncludedPackageElements()) { ModuleElement mdle = docEnv.getElementUtils().getModuleOf(p); if (mdle != null && !mdle.isUnnamed()) { Set s = modulePackages .computeIfAbsent(mdle, m -> new TreeSet<>(utils.makePackageComparator())); s.add(p); } } // add entries for modules which may not have exported packages modules.forEach((ModuleElement mdle) -> { modulePackages.computeIfAbsent(mdle, m -> Collections.emptySet()); }); modules.addAll(modulePackages.keySet()); showModules = !modules.isEmpty(); for (Set pkgs : modulePackages.values()) { packages.addAll(pkgs); } } private void initPackages() { packages = new TreeSet<>(utils.makePackageComparator()); // add all the included packages packages.addAll(includedPackageElements); } /* * when this is called all the option have been set, this method, * initializes certain components before anything else is started. */ protected boolean finishOptionSettings0() throws DocletException { BaseOptions options = getOptions(); extern = new Extern(this); initDestDirectory(); for (String link : options.linkList) { extern.link(link, reporter); } for (Pair linkOfflinePair : options.linkOfflineList) { extern.link(linkOfflinePair.first, linkOfflinePair.second, reporter); } typeElementCatalog = new TypeElementCatalog(includedTypeElements, this); initTagletManager(options.customTagStrs); options.groupPairs.stream().forEach((grp) -> { if (showModules) { group.checkModuleGroups(grp.first, grp.second); } else { group.checkPackageGroups(grp.first, grp.second); } }); overviewElement = new OverviewElement(workArounds.getUnnamedPackage(), getOverviewPath()); return true; } /** * Set the command-line options supported by this configuration. * * @return true if the options are set successfully * @throws DocletException if there is a problem while setting the options */ public boolean setOptions() throws DocletException { initPackages(); initModules(); if (!finishOptionSettings0() || !finishOptionSettings()) return false; return true; } private void initDestDirectory() throws DocletException { String destDirName = getOptions().destDirName; if (!destDirName.isEmpty()) { Resources resources = getResources(); DocFile destDir = DocFile.createFileForDirectory(this, destDirName); if (!destDir.exists()) { //Create the output directory (in case it doesn't exist yet) reporter.print(NOTE, resources.getText("doclet.dest_dir_create", destDirName)); destDir.mkdirs(); } else if (!destDir.isDirectory()) { throw new SimpleDocletException(resources.getText( "doclet.destination_directory_not_directory_0", destDir.getPath())); } else if (!destDir.canWrite()) { throw new SimpleDocletException(resources.getText( "doclet.destination_directory_not_writable_0", destDir.getPath())); } } DocFileFactory.getFactory(this).setDestDir(destDirName); } /** * Initialize the taglet manager. The strings to initialize the simple custom tags should * be in the following format: "[tag name]:[location str]:[heading]". * * @param customTagStrs the set two dimensional arrays of strings. These arrays contain * either -tag or -taglet arguments. */ private void initTagletManager(Set> customTagStrs) { tagletManager = tagletManager != null ? tagletManager : new TagletManager(this); JavaFileManager fileManager = getFileManager(); Messages messages = getMessages(); try { tagletManager.initTagletPath(fileManager); tagletManager.loadTaglets(fileManager); for (List args : customTagStrs) { if (args.get(0).equals("-taglet")) { tagletManager.addCustomTag(args.get(1), fileManager); continue; } List tokens = tokenize(args.get(1), TagletManager.SIMPLE_TAGLET_OPT_SEPARATOR, 3); switch (tokens.size()) { case 1: String tagName = args.get(1); if (tagletManager.isKnownCustomTag(tagName)) { //reorder a standard tag tagletManager.addNewSimpleCustomTag(tagName, null, ""); } else { //Create a simple tag with the heading that has the same name as the tag. StringBuilder heading = new StringBuilder(tagName + ":"); heading.setCharAt(0, Character.toUpperCase(tagName.charAt(0))); tagletManager.addNewSimpleCustomTag(tagName, heading.toString(), "a"); } break; case 2: //Add simple taglet without heading, probably to excluding it in the output. tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(1), ""); break; case 3: tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(2), tokens.get(1)); break; default: messages.error("doclet.Error_invalid_custom_tag_argument", args.get(1)); } } } catch (IOException e) { messages.error("doclet.taglet_could_not_set_location", e.toString()); } } /** * Given a string, return an array of tokens. The separator can be escaped * with the '\' character. The '\' character may also be escaped by the * '\' character. * * @param s the string to tokenize. * @param separator the separator char. * @param maxTokens the maximum number of tokens returned. If the * max is reached, the remaining part of s is appended * to the end of the last token. * @return an array of tokens. */ private List tokenize(String s, char separator, int maxTokens) { List tokens = new ArrayList<>(); StringBuilder token = new StringBuilder(); boolean prevIsEscapeChar = false; for (int i = 0; i < s.length(); i += Character.charCount(i)) { int currentChar = s.codePointAt(i); if (prevIsEscapeChar) { // Case 1: escaped character token.appendCodePoint(currentChar); prevIsEscapeChar = false; } else if (currentChar == separator && tokens.size() < maxTokens - 1) { // Case 2: separator tokens.add(token.toString()); token = new StringBuilder(); } else if (currentChar == '\\') { // Case 3: escape character prevIsEscapeChar = true; } else { // Case 4: regular character token.appendCodePoint(currentChar); } } if (token.length() > 0) { tokens.add(token.toString()); } return tokens; } /** * Return true if the given doc-file subdirectory should be excluded and * false otherwise. * * @param docfilesubdir the doc-files subdirectory to check. * @return true if the directory is excluded. */ public boolean shouldExcludeDocFileDir(String docfilesubdir) { Set excludedDocFileDirs = getOptions().excludedDocFileDirs; return excludedDocFileDirs.contains(docfilesubdir); } /** * Return true if the given qualifier should be excluded and false otherwise. * * @param qualifier the qualifier to check. * @return true if the qualifier should be excluded */ public boolean shouldExcludeQualifier(String qualifier) { Set excludedQualifiers = getOptions().excludedQualifiers; if (excludedQualifiers.contains("all") || excludedQualifiers.contains(qualifier) || excludedQualifiers.contains(qualifier + ".*")) { return true; } else { int index = -1; while ((index = qualifier.indexOf(".", index + 1)) != -1) { if (excludedQualifiers.contains(qualifier.substring(0, index + 1) + "*")) { return true; } } return false; } } /** * Return the qualified name of the Element if its qualifier is not excluded. * Otherwise return the unqualified Element name. * * @param te the TypeElement to check. * @return the class name */ public String getClassName(TypeElement te) { PackageElement pkg = utils.containingPackage(te); return shouldExcludeQualifier(utils.getPackageName(pkg)) ? utils.getSimpleName(te) : utils.getFullyQualifiedName(te); } /** * Return true if the TypeElement element is getting documented, depending upon * -nodeprecated option and the deprecation information. Return true if * -nodeprecated is not used. Return false if -nodeprecated is used and if * either TypeElement element is deprecated or the containing package is deprecated. * * @param te the TypeElement for which the page generation is checked * @return true if it is a generated doc. */ public boolean isGeneratedDoc(TypeElement te) { boolean nodeprecated = getOptions().noDeprecated; if (!nodeprecated) { return true; } return !(utils.isDeprecated(te) || utils.isDeprecated(utils.containingPackage(te))); } /** * Return the doclet specific instance of a writer factory. * * @return the {@link WriterFactory} for the doclet. */ public abstract WriterFactory getWriterFactory(); /** * Return the input stream to the builder XML. * * @return the input steam to the builder XML. * @throws DocFileIOException when the given XML file cannot be found or opened. */ public InputStream getBuilderXML() throws DocFileIOException { return builderXMLPath == null ? BaseConfiguration.class.getResourceAsStream(DEFAULT_BUILDER_XML) : DocFile.createFileForInput(this, builderXMLPath).openInputStream(); } /** * Return the Locale for this document. * * @return the current locale */ public abstract Locale getLocale(); /** * Return the path of the overview file and null if it does not exist. * * @return the path of the overview file. */ public abstract JavaFileObject getOverviewPath(); /** * Return the current file manager. * * @return JavaFileManager */ public abstract JavaFileManager getFileManager(); public abstract boolean showMessage(DocTreePath path, String key); public abstract boolean showMessage(Element e, String key); /* * Splits the elements in a collection to its individual * collection. */ @SuppressWarnings("preview") private static class Splitter { final Set mset = new LinkedHashSet<>(); final Set pset = new LinkedHashSet<>(); final Set tset = new LinkedHashSet<>(); Splitter(DocletEnvironment docEnv, boolean included) { Set inset = included ? docEnv.getIncludedElements() : docEnv.getSpecifiedElements(); for (Element e : inset) { new SimpleElementVisitor14() { @Override @DefinedBy(Api.LANGUAGE_MODEL) public Void visitModule(ModuleElement e, Void p) { mset.add(e); return null; } @Override @DefinedBy(Api.LANGUAGE_MODEL) public Void visitPackage(PackageElement e, Void p) { pset.add(e); return null; } @Override @DefinedBy(Api.LANGUAGE_MODEL) public Void visitType(TypeElement e, Void p) { tset.add(e); return null; } @Override @DefinedBy(Api.LANGUAGE_MODEL) protected Void defaultAction(Element e, Void p) { throw new AssertionError("unexpected element: " + e); } }.visit(e); } } } /** * Returns whether or not to allow JavaScript in comments. * Default is off; can be set true from a command-line option. * * @return the allowScriptInComments */ public boolean isAllowScriptInComments() { return getOptions().allowScriptInComments; } public synchronized VisibleMemberTable getVisibleMemberTable(TypeElement te) { return visibleMemberCache.getVisibleMemberTable(te); } /** * Determines if JavaFX is available in the compilation environment. * @return true if JavaFX is available */ public boolean isJavaFXMode() { TypeElement observable = utils.elementUtils.getTypeElement("javafx.beans.Observable"); if (observable != null) { ModuleElement javafxModule = utils.elementUtils.getModuleOf(observable); if (javafxModule == null || javafxModule.isUnnamed() || javafxModule.getQualifiedName().contentEquals("javafx.base")) { return true; } } return false; } }