--- old/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/taglets/TagletManager.java Fri Jan 22 12:20:14 2016 +++ /dev/null Fri Jan 22 12:20:14 2016 @@ -1,761 +0,0 @@ -/* - * Copyright (c) 2001, 2013, 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 com.sun.tools.doclets.internal.toolkit.taglets; - -import java.io.*; -import java.lang.reflect.*; -import java.net.*; -import java.util.*; - -import javax.tools.DocumentationTool; -import javax.tools.JavaFileManager; - -import com.sun.javadoc.*; -import com.sun.tools.doclets.internal.toolkit.util.*; -import com.sun.tools.javac.util.StringUtils; - -/** - * Manages theTaglets used by doclets. - * - *

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. - * - * @author Jamie Ho - * @since 1.4 - */ - -public class TagletManager { - - /** - * The default separator for the simple tag option. - */ - public static final char SIMPLE_TAGLET_OPT_SEPARATOR = ':'; - - /** - * The alternate separator for simple tag options. Use this - * when you want the default separator to be in the name of the - * custom tag. - */ - public static final String ALT_SIMPLE_TAGLET_OPT_SEPARATOR = "-"; - - /** - * The map of custom tags. - */ - private LinkedHashMap customTags; - - /** - * The array of custom tags that can appear in packages. - */ - private Taglet[] packageTags; - - /** - * The array of custom tags that can appear in classes or interfaces. - */ - private Taglet[] typeTags; - - /** - * The array of custom tags that can appear in fields. - */ - private Taglet[] fieldTags; - - /** - * The array of custom tags that can appear in constructors. - */ - private Taglet[] constructorTags; - - /** - * The array of custom tags that can appear in methods. - */ - private Taglet[] methodTags; - - /** - * The array of custom tags that can appear in the overview. - */ - private Taglet[] overviewTags; - - /** - * The array of custom tags that can appear in comments. - */ - private Taglet[] inlineTags; - - /** - * The array of custom tags that can appear in the serialized form. - */ - private Taglet[] serializedFormTags; - - /** - * The message retriever that will be used to print error messages. - */ - private MessageRetriever message; - - /** - * Keep track of standard tags. - */ - private Set standardTags; - - /** - * Keep track of standard tags in lowercase to compare for better - * error messages when a tag like @docRoot is mistakenly spelled - * lowercase @docroot. - */ - private Set standardTagsLowercase; - - /** - * Keep track of overriden standard tags. - */ - private Set overridenStandardTags; - - /** - * Keep track of the tags that may conflict - * with standard tags in the future (any custom tag without - * a period in its name). - */ - private Set potentiallyConflictingTags; - - /** - * The set of unseen custom tags. - */ - private Set unseenCustomTags; - - /** - * True if we do not want to use @since tags. - */ - private boolean nosince; - - /** - * True if we want to use @version tags. - */ - private boolean showversion; - - /** - * True if we want to use @author tags. - */ - private boolean showauthor; - - /** - * True if we want to use JavaFX-related tags (@propertyGetter, - * @propertySetter, @propertyDescription, @defaultValue, @treatAsPrivate). - */ - private boolean javafx; - - /** - * Construct a new TagletManager. - * @param nosince true if we do not want to use @since tags. - * @param showversion true if we want to use @version tags. - * @param showauthor true if we want to use @author tags. - * @param message the message retriever to print warnings. - */ - public TagletManager(boolean nosince, boolean showversion, - boolean showauthor, boolean javafx, - MessageRetriever message) { - overridenStandardTags = new HashSet<>(); - potentiallyConflictingTags = new HashSet<>(); - standardTags = new HashSet<>(); - standardTagsLowercase = new HashSet<>(); - unseenCustomTags = new HashSet<>(); - customTags = new LinkedHashMap<>(); - this.nosince = nosince; - this.showversion = showversion; - this.showauthor = showauthor; - this.javafx = javafx; - this.message = message; - initStandardTaglets(); - initStandardTagsLowercase(); - } - - /** - * Add a new CustomTag. This is used to add a Taglet from within - * a Doclet. No message is printed to indicate that the Taglet is properly - * registered because these Taglets are typically added for every execution of the - * Doclet. We don't want to see this type of error message every time. - * @param customTag the new CustomTag to add. - */ - public void addCustomTag(Taglet customTag) { - if (customTag != null) { - String name = customTag.getName(); - if (customTags.containsKey(name)) { - customTags.remove(name); - } - customTags.put(name, customTag); - checkTagName(name); - } - } - - public Set getCustomTagNames() { - return customTags.keySet(); - } - - /** - * Add a new Taglet. Print a message to indicate whether or not - * the Taglet was registered properly. - * @param classname the name of the class representing the custom tag. - * @param tagletPath the path to the class representing the custom tag. - */ - public void addCustomTag(String classname, JavaFileManager fileManager, String tagletPath) { - try { - Class customTagClass = null; - // construct class loader - String cpString = null; // make sure env.class.path defaults to dot - - ClassLoader tagClassLoader; - if (fileManager != null && fileManager.hasLocation(DocumentationTool.Location.TAGLET_PATH)) { - tagClassLoader = fileManager.getClassLoader(DocumentationTool.Location.TAGLET_PATH); - } else { - // do prepends to get correct ordering - cpString = appendPath(System.getProperty("env.class.path"), cpString); - cpString = appendPath(System.getProperty("java.class.path"), cpString); - cpString = appendPath(tagletPath, cpString); - tagClassLoader = new URLClassLoader(pathToURLs(cpString)); - } - - customTagClass = tagClassLoader.loadClass(classname); - Method meth = customTagClass.getMethod("register", - Map.class); - Object[] list = customTags.values().toArray(); - Taglet lastTag = (list != null && list.length > 0) - ? (Taglet) list[list.length-1] : null; - meth.invoke(null, customTags); - list = customTags.values().toArray(); - Object newLastTag = (list != null&& list.length > 0) - ? list[list.length-1] : null; - if (lastTag != newLastTag) { - //New taglets must always be added to the end of the LinkedHashMap. - //If the current and previous last taglet are not equal, that - //means a new Taglet has been added. - message.notice("doclet.Notice_taglet_registered", classname); - if (newLastTag != null) { - checkTaglet(newLastTag); - } - } - } catch (Exception exc) { - message.error("doclet.Error_taglet_not_registered", exc.getClass().getName(), classname); - } - - } - - private String appendPath(String path1, String path2) { - if (path1 == null || path1.length() == 0) { - return path2 == null ? "." : path2; - } else if (path2 == null || path2.length() == 0) { - return path1; - } else { - return path1 + File.pathSeparator + path2; - } - } - - /** - * Utility method for converting a search path string to an array - * of directory and JAR file URLs. - * - * @param path the search path string - * @return the resulting array of directory and JAR file URLs - */ - private URL[] pathToURLs(String path) { - Set urls = new LinkedHashSet<>(); - for (String s: path.split(File.pathSeparator)) { - if (s.isEmpty()) continue; - try { - urls.add(new File(s).getAbsoluteFile().toURI().toURL()); - } catch (MalformedURLException e) { - message.error("doclet.MalformedURL", s); - } - } - return urls.toArray(new URL[urls.size()]); - } - - - /** - * Add a new SimpleTaglet. If this tag already exists - * and the header passed as an argument is null, move tag to the back of the - * list. If this tag already exists and the header passed as an argument is - * not null, overwrite previous tag with new one. Otherwise, add new - * SimpleTaglet to list. - * @param tagName the name of this tag - * @param header the header to output. - * @param locations the possible locations that this tag - * can appear in. - */ - public void addNewSimpleCustomTag(String tagName, String header, String locations) { - if (tagName == null || locations == null) { - return; - } - Taglet tag = customTags.get(tagName); - locations = StringUtils.toLowerCase(locations); - if (tag == null || header != null) { - customTags.remove(tagName); - customTags.put(tagName, new SimpleTaglet(tagName, header, locations)); - if (locations != null && locations.indexOf('x') == -1) { - checkTagName(tagName); - } - } else { - //Move to back - customTags.remove(tagName); - customTags.put(tagName, tag); - } - } - - /** - * Given a tag name, add it to the set of tags it belongs to. - */ - private void checkTagName(String name) { - if (standardTags.contains(name)) { - overridenStandardTags.add(name); - } else { - if (name.indexOf('.') == -1) { - potentiallyConflictingTags.add(name); - } - unseenCustomTags.add(name); - } - } - - /** - * Check the taglet to see if it is a legacy taglet. Also - * check its name for errors. - */ - private void checkTaglet(Object taglet) { - if (taglet instanceof Taglet) { - checkTagName(((Taglet) taglet).getName()); - } else if (taglet instanceof com.sun.tools.doclets.Taglet) { - com.sun.tools.doclets.Taglet legacyTaglet = (com.sun.tools.doclets.Taglet) taglet; - customTags.remove(legacyTaglet.getName()); - customTags.put(legacyTaglet.getName(), new LegacyTaglet(legacyTaglet)); - checkTagName(legacyTaglet.getName()); - } else { - throw new IllegalArgumentException("Given object is not a taglet."); - } - } - - /** - * Given a name of a seen custom tag, remove it from the set of unseen - * custom tags. - * @param name the name of the seen custom tag. - */ - public void seenCustomTag(String name) { - unseenCustomTags.remove(name); - } - - /** - * Given an array of Tags, check for spelling mistakes. - * @param doc the Doc object that holds the tags. - * @param tags the list of Tags to check. - * @param areInlineTags true if the array of tags are inline and false otherwise. - */ - public void checkTags(Doc doc, Tag[] tags, boolean areInlineTags) { - if (tags == null) { - return; - } - Taglet taglet; - for (Tag tag : tags) { - String name = tag.name(); - if (name.length() > 0 && name.charAt(0) == '@') { - name = name.substring(1, name.length()); - } - if (! (standardTags.contains(name) || customTags.containsKey(name))) { - if (standardTagsLowercase.contains(StringUtils.toLowerCase(name))) { - message.warning(tag.position(), "doclet.UnknownTagLowercase", tag.name()); - continue; - } else { - message.warning(tag.position(), "doclet.UnknownTag", tag.name()); - continue; - } - } - //Check if this tag is being used in the wrong location. - if ((taglet = customTags.get(name)) != null) { - if (areInlineTags && ! taglet.isInlineTag()) { - printTagMisuseWarn(taglet, tag, "inline"); - } - if ((doc instanceof RootDoc) && ! taglet.inOverview()) { - printTagMisuseWarn(taglet, tag, "overview"); - } else if ((doc instanceof PackageDoc) && ! taglet.inPackage()) { - printTagMisuseWarn(taglet, tag, "package"); - } else if ((doc instanceof ClassDoc) && ! taglet.inType()) { - printTagMisuseWarn(taglet, tag, "class"); - } else if ((doc instanceof ConstructorDoc) && ! taglet.inConstructor()) { - printTagMisuseWarn(taglet, tag, "constructor"); - } else if ((doc instanceof FieldDoc) && ! taglet.inField()) { - printTagMisuseWarn(taglet, tag, "field"); - } else if ((doc instanceof MethodDoc) && ! taglet.inMethod()) { - printTagMisuseWarn(taglet, tag, "method"); - } - } - } - } - - /** - * Given the taglet, the tag and the type of documentation that the tag - * was found in, print a tag misuse warning. - * @param taglet the taglet representing the misused tag. - * @param tag the misused tag. - * @param holderType the type of documentation that the misused tag was found in. - */ - private void printTagMisuseWarn(Taglet taglet, Tag tag, String holderType) { - Set locationsSet = new LinkedHashSet<>(); - if (taglet.inOverview()) { - locationsSet.add("overview"); - } - if (taglet.inPackage()) { - locationsSet.add("package"); - } - if (taglet.inType()) { - locationsSet.add("class/interface"); - } - if (taglet.inConstructor()) { - locationsSet.add("constructor"); - } - if (taglet.inField()) { - locationsSet.add("field"); - } - if (taglet.inMethod()) { - locationsSet.add("method"); - } - if (taglet.isInlineTag()) { - locationsSet.add("inline text"); - } - String[] locations = locationsSet.toArray(new String[]{}); - if (locations == null || locations.length == 0) { - //This known tag is excluded. - return; - } - StringBuilder combined_locations = new StringBuilder(); - for (int i = 0; i < locations.length; i++) { - if (i > 0) { - combined_locations.append(", "); - } - combined_locations.append(locations[i]); - } - message.warning(tag.position(), "doclet.tag_misuse", - "@" + taglet.getName(), holderType, combined_locations.toString()); - } - - /** - * Return the array of Taglets that can - * appear in packages. - * @return the array of Taglets that can - * appear in packages. - */ - public Taglet[] getPackageCustomTaglets() { - if (packageTags == null) { - initCustomTagletArrays(); - } - return packageTags; - } - - /** - * Return the array of Taglets that can - * appear in classes or interfaces. - * @return the array of Taglets that can - * appear in classes or interfaces. - */ - public Taglet[] getTypeCustomTaglets() { - if (typeTags == null) { - initCustomTagletArrays(); - } - return typeTags; - } - - /** - * Return the array of inline Taglets that can - * appear in comments. - * @return the array of Taglets that can - * appear in comments. - */ - public Taglet[] getInlineCustomTaglets() { - if (inlineTags == null) { - initCustomTagletArrays(); - } - return inlineTags; - } - - /** - * Return the array of Taglets that can - * appear in fields. - * @return the array of Taglets that can - * appear in field. - */ - public Taglet[] getFieldCustomTaglets() { - if (fieldTags == null) { - initCustomTagletArrays(); - } - return fieldTags; - } - - /** - * Return the array of Taglets that can - * appear in the serialized form. - * @return the array of Taglets that can - * appear in the serialized form. - */ - public Taglet[] getSerializedFormTaglets() { - if (serializedFormTags == null) { - initCustomTagletArrays(); - } - return serializedFormTags; - } - - /** - * @return the array of Taglets that can - * appear in the given Doc. - */ - public Taglet[] getCustomTaglets(Doc doc) { - if (doc instanceof ConstructorDoc) { - return getConstructorCustomTaglets(); - } else if (doc instanceof MethodDoc) { - return getMethodCustomTaglets(); - } else if (doc instanceof FieldDoc) { - return getFieldCustomTaglets(); - } else if (doc instanceof ClassDoc) { - return getTypeCustomTaglets(); - } else if (doc instanceof PackageDoc) { - return getPackageCustomTaglets(); - } else if (doc instanceof RootDoc) { - return getOverviewCustomTaglets(); - } - return null; - } - - /** - * Return the array of Taglets that can - * appear in constructors. - * @return the array of Taglets that can - * appear in constructors. - */ - public Taglet[] getConstructorCustomTaglets() { - if (constructorTags == null) { - initCustomTagletArrays(); - } - return constructorTags; - } - - /** - * Return the array of Taglets that can - * appear in methods. - * @return the array of Taglets that can - * appear in methods. - */ - public Taglet[] getMethodCustomTaglets() { - if (methodTags == null) { - initCustomTagletArrays(); - } - return methodTags; - } - - /** - * Return the array of Taglets that can - * appear in an overview. - * @return the array of Taglets that can - * appear in overview. - */ - public Taglet[] getOverviewCustomTaglets() { - if (overviewTags == null) { - initCustomTagletArrays(); - } - return overviewTags; - } - - /** - * Initialize the custom tag arrays. - */ - private void initCustomTagletArrays() { - Iterator it = customTags.values().iterator(); - ArrayList pTags = new ArrayList<>(customTags.size()); - ArrayList tTags = new ArrayList<>(customTags.size()); - ArrayList fTags = new ArrayList<>(customTags.size()); - ArrayList cTags = new ArrayList<>(customTags.size()); - ArrayList mTags = new ArrayList<>(customTags.size()); - ArrayList iTags = new ArrayList<>(customTags.size()); - ArrayList oTags = new ArrayList<>(customTags.size()); - ArrayList sTags = new ArrayList<>(); - Taglet current; - while (it.hasNext()) { - current = it.next(); - if (current.inPackage() && !current.isInlineTag()) { - pTags.add(current); - } - if (current.inType() && !current.isInlineTag()) { - tTags.add(current); - } - if (current.inField() && !current.isInlineTag()) { - fTags.add(current); - } - if (current.inConstructor() && !current.isInlineTag()) { - cTags.add(current); - } - if (current.inMethod() && !current.isInlineTag()) { - mTags.add(current); - } - if (current.isInlineTag()) { - iTags.add(current); - } - if (current.inOverview() && !current.isInlineTag()) { - oTags.add(current); - } - } - packageTags = pTags.toArray(new Taglet[] {}); - typeTags = tTags.toArray(new Taglet[] {}); - fieldTags = fTags.toArray(new Taglet[] {}); - constructorTags = cTags.toArray(new Taglet[] {}); - methodTags = mTags.toArray(new Taglet[] {}); - overviewTags = oTags.toArray(new Taglet[] {}); - inlineTags = iTags.toArray(new Taglet[] {}); - - //Init the serialized form tags - sTags.add(customTags.get("serialData")); - sTags.add(customTags.get("throws")); - if (!nosince) - sTags.add(customTags.get("since")); - sTags.add(customTags.get("see")); - serializedFormTags = sTags.toArray(new Taglet[] {}); - } - - /** - * Initialize standard Javadoc tags for ordering purposes. - */ - private void initStandardTaglets() { - if (javafx) { - initJavaFXTaglets(); - } - - Taglet temp; - addStandardTaglet(new ParamTaglet()); - addStandardTaglet(new ReturnTaglet()); - addStandardTaglet(new ThrowsTaglet()); - addStandardTaglet(new SimpleTaglet("exception", null, - SimpleTaglet.METHOD + SimpleTaglet.CONSTRUCTOR)); - addStandardTaglet(!nosince, new SimpleTaglet("since", message.getText("doclet.Since"), - SimpleTaglet.ALL)); - addStandardTaglet(showversion, new SimpleTaglet("version", message.getText("doclet.Version"), - SimpleTaglet.PACKAGE + SimpleTaglet.TYPE + SimpleTaglet.OVERVIEW)); - addStandardTaglet(showauthor, new SimpleTaglet("author", message.getText("doclet.Author"), - SimpleTaglet.PACKAGE + SimpleTaglet.TYPE + SimpleTaglet.OVERVIEW)); - addStandardTaglet(new SimpleTaglet("serialData", message.getText("doclet.SerialData"), - SimpleTaglet.EXCLUDED)); - customTags.put((temp = new SimpleTaglet("factory", message.getText("doclet.Factory"), - SimpleTaglet.METHOD)).getName(), temp); - addStandardTaglet(new SeeTaglet()); - //Standard inline tags - addStandardTaglet(new DocRootTaglet()); - addStandardTaglet(new InheritDocTaglet()); - addStandardTaglet(new ValueTaglet()); - addStandardTaglet(new LiteralTaglet()); - addStandardTaglet(new CodeTaglet()); - addStandardTaglet(new IndexTaglet()); - - // Keep track of the names of standard tags for error - // checking purposes. The following are not handled above. - // See, for example, com.sun.tools.javadoc.Comment - standardTags.add("deprecated"); - standardTags.add("link"); - standardTags.add("linkplain"); - standardTags.add("serial"); - standardTags.add("serialField"); - standardTags.add("Text"); - } - - /** - * Initialize JavaFX-related tags. - */ - private void initJavaFXTaglets() { - addStandardTaglet(new PropertyGetterTaglet()); - addStandardTaglet(new PropertySetterTaglet()); - addStandardTaglet(new SimpleTaglet("propertyDescription", - message.getText("doclet.PropertyDescription"), - SimpleTaglet.FIELD + SimpleTaglet.METHOD)); - addStandardTaglet(new SimpleTaglet("defaultValue", message.getText("doclet.DefaultValue"), - SimpleTaglet.FIELD + SimpleTaglet.METHOD)); - addStandardTaglet(new SimpleTaglet("treatAsPrivate", null, - SimpleTaglet.FIELD + SimpleTaglet.METHOD + SimpleTaglet.TYPE)); - } - - void addStandardTaglet(Taglet taglet) { - String name = taglet.getName(); - customTags.put(name, taglet); - standardTags.add(name); - } - - void addStandardTaglet(boolean enable, Taglet taglet) { - String name = taglet.getName(); - if (enable) - customTags.put(name, taglet); - standardTags.add(name); - } - - /** - * Initialize lowercase version of standard Javadoc tags. - */ - private void initStandardTagsLowercase() { - for (String standardTag : standardTags) { - standardTagsLowercase.add(StringUtils.toLowerCase(standardTag)); - } - } - - public boolean isKnownCustomTag(String tagName) { - return customTags.containsKey(tagName); - } - - /** - * Print a list of {@link Taglet}s that might conflict with - * standard tags in the future and a list of standard tags - * that have been overriden. - */ - public void printReport() { - printReportHelper("doclet.Notice_taglet_conflict_warn", potentiallyConflictingTags); - printReportHelper("doclet.Notice_taglet_overriden", overridenStandardTags); - printReportHelper("doclet.Notice_taglet_unseen", unseenCustomTags); - } - - private void printReportHelper(String noticeKey, Set names) { - if (names.size() > 0) { - String[] namesArray = names.toArray(new String[] {}); - String result = " "; - for (int i = 0; i < namesArray.length; i++) { - result += "@" + namesArray[i]; - if (i + 1 < namesArray.length) { - result += ", "; - } - } - message.notice(noticeKey, result); - } - } - - /** - * Given the name of a tag, return the corresponding taglet. - * Return null if the tag is unknown. - * - * @param name the name of the taglet to retrieve. - * @return return the corresponding taglet. Return null if the tag is - * unknown. - */ - public Taglet getTaglet(String name) { - if (name.indexOf("@") == 0) { - return customTags.get(name.substring(1)); - } else { - return customTags.get(name); - } - - } -} --- /dev/null Fri Jan 22 12:20:14 2016 +++ new/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletManager.java Fri Jan 22 12:20:14 2016 @@ -0,0 +1,773 @@ +/* + * Copyright (c) 2001, 2016, 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.taglets; + +import java.io.*; +import java.util.*; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.SimpleElementVisitor9; +import javax.tools.JavaFileManager; +import javax.tools.StandardJavaFileManager; + +import com.sun.source.doctree.DocTree; +import com.sun.tools.javac.util.DefinedBy; +import com.sun.tools.javac.util.DefinedBy.Api; + +import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; +import jdk.javadoc.internal.doclets.toolkit.util.MessageRetriever; +import jdk.javadoc.internal.doclets.toolkit.util.Utils; + +import static javax.tools.DocumentationTool.Location.*; + +import static com.sun.source.doctree.DocTree.Kind.*; + +/** + * Manages theTaglets used by doclets. + * + *

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. + * + * @author Jamie Ho + * @since 1.4 + */ + +public class TagletManager { + + /** + * The default separator for the simple tag option. + */ + public static final char SIMPLE_TAGLET_OPT_SEPARATOR = ':'; + + /** + * The alternate separator for simple tag options. Use this + * when you want the default separator to be in the name of the + * custom tag. + */ + public static final String ALT_SIMPLE_TAGLET_OPT_SEPARATOR = "-"; + + /** + * The map of custom tags. + */ + private final LinkedHashMap customTags; + + /** + * The array of custom tags that can appear in packages. + */ + private List packageTags; + + /** + * The array of custom tags that can appear in classes or interfaces. + */ + private List typeTags; + + /** + * The array of custom tags that can appear in fields. + */ + private List fieldTags; + + /** + * The array of custom tags that can appear in constructors. + */ + private List constructorTags; + + /** + * The array of custom tags that can appear in methods. + */ + private List methodTags; + + /** + * The array of custom tags that can appear in the overview. + */ + private List overviewTags; + + /** + * The array of custom tags that can appear in comments. + */ + private List inlineTags; + + /** + * The array of custom tags that can appear in the serialized form. + */ + private List serializedFormTags; + + /** + * The message retriever that will be used to print error messages. + */ + private final MessageRetriever message; + + /** + * Keep track of standard tags. + */ + private final Set standardTags; + + /** + * Keep track of standard tags in lowercase to compare for better + * error messages when a tag like @docRoot is mistakenly spelled + * lowercase @docroot. + */ + private final Set standardTagsLowercase; + + /** + * Keep track of overriden standard tags. + */ + private final Set overridenStandardTags; + + /** + * Keep track of the tags that may conflict + * with standard tags in the future (any custom tag without + * a period in its name). + */ + private final Set potentiallyConflictingTags; + + /** + * The set of unseen custom tags. + */ + private final Set unseenCustomTags; + + /** + * True if we do not want to use @since tags. + */ + private final boolean nosince; + + /** + * True if we want to use @version tags. + */ + private final boolean showversion; + + /** + * True if we want to use @author tags. + */ + private final boolean showauthor; + + /** + * True if we want to use JavaFX-related tags (@propertyGetter, + * @propertySetter, @propertyDescription, @defaultValue, @treatAsPrivate). + */ + private final boolean javafx; + + /** + * Construct a new TagletManager. + * @param nosince true if we do not want to use @since tags. + * @param showversion true if we want to use @version tags. + * @param showauthor true if we want to use @author tags. + * @param javafx indicates whether javafx is active. + * @param message the message retriever to print warnings. + */ + public TagletManager(boolean nosince, boolean showversion, + boolean showauthor, boolean javafx, + MessageRetriever message) { + overridenStandardTags = new HashSet<>(); + potentiallyConflictingTags = new HashSet<>(); + standardTags = new HashSet<>(); + standardTagsLowercase = new HashSet<>(); + unseenCustomTags = new HashSet<>(); + customTags = new LinkedHashMap<>(); + this.nosince = nosince; + this.showversion = showversion; + this.showauthor = showauthor; + this.javafx = javafx; + this.message = message; + initStandardTaglets(); + initStandardTagsLowercase(); + } + + /** + * Add a new CustomTag. This is used to add a Taglet from within + * a Doclet. No message is printed to indicate that the Taglet is properly + * registered because these Taglets are typically added for every execution of the + * Doclet. We don't want to see this type of error message every time. + * @param customTag the new CustomTag to add. + */ + public void addCustomTag(Taglet customTag) { + if (customTag != null) { + String name = customTag.getName(); + if (customTags.containsKey(name)) { + customTags.remove(name); + } + customTags.put(name, customTag); + checkTagName(name); + } + } + + public Set getCustomTagNames() { + return customTags.keySet(); + } + + /** + * Add a new Taglet. Print a message to indicate whether or not + * the Taglet was registered properly. + * @param classname the name of the class representing the custom tag. + * @param fileManager the filemanager to load classes and resources. + * @param tagletPath the path to the class representing the custom tag. + */ + public void addCustomTag(String classname, JavaFileManager fileManager, String tagletPath) { + try { + ClassLoader tagClassLoader; + if (!fileManager.hasLocation(TAGLET_PATH)) { + List paths = new ArrayList<>(); + if (tagletPath != null) { + for (String pathname : tagletPath.split(File.pathSeparator)) { + paths.add(new File(pathname)); + } + } + ((StandardJavaFileManager)fileManager).setLocation(TAGLET_PATH, paths); + } + tagClassLoader = fileManager.getClassLoader(TAGLET_PATH); + Class customTagClass = tagClassLoader.loadClass(classname); + Object instance = customTagClass.newInstance(); + Taglet newLegacy = new UserTaglet((jdk.javadoc.doclet.taglet.Taglet)instance); + String tname = newLegacy.getName(); + Taglet t = customTags.get(tname); + if (t != null) { + customTags.remove(tname); + } + customTags.put(tname, newLegacy); + message.notice("doclet.Notice_taglet_registered", classname); + } catch (Exception exc) { + message.error("doclet.Error_taglet_not_registered", exc.getClass().getName(), classname); + } + } + + /** + * Add a new SimpleTaglet. If this tag already exists + * and the header passed as an argument is null, move tag to the back of the + * list. If this tag already exists and the header passed as an argument is + * not null, overwrite previous tag with new one. Otherwise, add new + * SimpleTaglet to list. + * @param tagName the name of this tag + * @param header the header to output. + * @param locations the possible locations that this tag + * can appear in. + */ + public void addNewSimpleCustomTag(String tagName, String header, String locations) { + if (tagName == null || locations == null) { + return; + } + Taglet tag = customTags.get(tagName); + locations = Utils.toLowerCase(locations); + if (tag == null || header != null) { + customTags.remove(tagName); + customTags.put(tagName, new SimpleTaglet(tagName, header, locations)); + if (locations != null && locations.indexOf('x') == -1) { + checkTagName(tagName); + } + } else { + //Move to back + customTags.remove(tagName); + customTags.put(tagName, tag); + } + } + + /** + * Given a tag name, add it to the set of tags it belongs to. + */ + private void checkTagName(String name) { + if (standardTags.contains(name)) { + overridenStandardTags.add(name); + } else { + if (name.indexOf('.') == -1) { + potentiallyConflictingTags.add(name); + } + unseenCustomTags.add(name); + } + } + + /** + * Check the taglet to see if it is a legacy taglet. Also + * check its name for errors. + */ + private void checkTaglet(Object taglet) { + if (taglet instanceof Taglet) { + checkTagName(((Taglet) taglet).getName()); + } else if (taglet instanceof jdk.javadoc.doclet.taglet.Taglet) { + jdk.javadoc.doclet.taglet.Taglet legacyTaglet = (jdk.javadoc.doclet.taglet.Taglet) taglet; + customTags.remove(legacyTaglet.getName()); + customTags.put(legacyTaglet.getName(), new UserTaglet(legacyTaglet)); + checkTagName(legacyTaglet.getName()); + } else { + throw new IllegalArgumentException("Given object is not a taglet."); + } + } + + /** + * Given a name of a seen custom tag, remove it from the set of unseen + * custom tags. + * @param name the name of the seen custom tag. + */ + public void seenCustomTag(String name) { + unseenCustomTags.remove(name); + } + + /** + * Given an array of Tags, check for spelling mistakes. + * @param utils the utility class to use + * @param element the tags holder + * @param trees the trees containing the comments + * @param areInlineTags true if the array of tags are inline and false otherwise. + */ + public void checkTags(final Utils utils, Element element, + Iterable trees, boolean areInlineTags) { + if (trees == null) { + return; + } + CommentHelper ch = utils.getCommentHelper(element); + for (DocTree tag : trees) { + String name = tag.getKind().tagName; + if (name == null) { + continue; + } + if (name.length() > 0 && name.charAt(0) == '@') { + name = name.substring(1, name.length()); + } + if (! (standardTags.contains(name) || customTags.containsKey(name))) { + if (standardTagsLowercase.contains(Utils.toLowerCase(name))) { + message.warning(ch.getDocTreePath(tag), "doclet.UnknownTagLowercase", ch.getTagName(tag)); + continue; + } else { + message.warning(ch.getDocTreePath(tag), "doclet.UnknownTag", ch.getTagName(tag)); + continue; + } + } + final Taglet taglet = customTags.get(name); + // Check and verify tag usage + if (taglet != null) { + if (areInlineTags && !taglet.isInlineTag()) { + printTagMisuseWarn(ch, taglet, tag, "inline"); + } + // nothing more to do + if (element == null) { + return; + } + new SimpleElementVisitor9() { + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Void visitPackage(PackageElement e, Void p) { + if (!taglet.inPackage()) { + printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "package"); + } + return null; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Void visitType(TypeElement e, Void p) { + if (!taglet.inType()) { + printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "class"); + } + return null; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Void visitExecutable(ExecutableElement e, Void p) { + if (utils.isConstructor(e) && !taglet.inConstructor()) { + printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "constructor"); + } else if (!taglet.inMethod()) { + printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "method"); + } + return null; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Void visitVariable(VariableElement e, Void p) { + if (utils.isField(e) && !taglet.inField()) { + printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "field"); + } + return null; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Void visitUnknown(Element e, Void p) { + if (utils.isOverviewElement(e) && !taglet.inOverview()) { + printTagMisuseWarn(utils.getCommentHelper(e), taglet, tag, "overview"); + } + return null; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + protected Void defaultAction(Element e, Void p) { + return null; + } + }.visit(element); + } + } + } + + /** + * Given the taglet, the tag and the type of documentation that the tag + * was found in, print a tag misuse warning. + * @param taglet the taglet representing the misused tag. + * @param tag the misused tag. + * @param holderType the type of documentation that the misused tag was found in. + */ + private void printTagMisuseWarn(CommentHelper ch, Taglet taglet, DocTree tag, String holderType) { + Set locationsSet = new LinkedHashSet<>(); + if (taglet.inOverview()) { + locationsSet.add("overview"); + } + if (taglet.inPackage()) { + locationsSet.add("package"); + } + if (taglet.inType()) { + locationsSet.add("class/interface"); + } + if (taglet.inConstructor()) { + locationsSet.add("constructor"); + } + if (taglet.inField()) { + locationsSet.add("field"); + } + if (taglet.inMethod()) { + locationsSet.add("method"); + } + if (taglet.isInlineTag()) { + locationsSet.add("inline text"); + } + String[] locations = locationsSet.toArray(new String[]{}); + if (locations == null || locations.length == 0) { + //This known tag is excluded. + return; + } + StringBuilder combined_locations = new StringBuilder(); + for (int i = 0; i < locations.length; i++) { + if (i > 0) { + combined_locations.append(", "); + } + combined_locations.append(locations[i]); + } + message.warning(ch.getDocTreePath(tag), "doclet.tag_misuse", + "@" + taglet.getName(), holderType, combined_locations.toString()); + } + + /** + * Return the array of Taglets that can + * appear in packages. + * @return the array of Taglets that can + * appear in packages. + */ + public List getPackageCustomTaglets() { + if (packageTags == null) { + initCustomTaglets(); + } + return packageTags; + } + + /** + * Return the array of Taglets that can + * appear in classes or interfaces. + * @return the array of Taglets that can + * appear in classes or interfaces. + */ + public List getTypeCustomTaglets() { + if (typeTags == null) { + initCustomTaglets(); + } + return typeTags; + } + + /** + * Return the array of inline Taglets that can + * appear in comments. + * @return the array of Taglets that can + * appear in comments. + */ + public List getInlineCustomTaglets() { + if (inlineTags == null) { + initCustomTaglets(); + } + return inlineTags; + } + + /** + * Return the array of Taglets that can + * appear in fields. + * @return the array of Taglets that can + * appear in field. + */ + public List getFieldCustomTaglets() { + if (fieldTags == null) { + initCustomTaglets(); + } + return fieldTags; + } + + /** + * Return the array of Taglets that can + * appear in the serialized form. + * @return the array of Taglets that can + * appear in the serialized form. + */ + public List getSerializedFormTaglets() { + if (serializedFormTags == null) { + initCustomTaglets(); + } + return serializedFormTags; + } + + /** + * Returns the custom tags for a given element. + * + * @param e the element to get custom tags for + * @return the array of Taglets that can + * appear in the given element. + */ + public List getCustomTaglets(Element e) { + switch (e.getKind()) { + case CONSTRUCTOR: + return getConstructorCustomTaglets(); + case METHOD: + return getMethodCustomTaglets(); + case ENUM_CONSTANT: + case FIELD: + return getFieldCustomTaglets(); + case ANNOTATION_TYPE: + case INTERFACE: + case CLASS: + case ENUM: + return getTypeCustomTaglets(); + case PACKAGE: + return getPackageCustomTaglets(); + case OTHER: + return getOverviewCustomTaglets(); + default: + throw new AssertionError("unknown element: " + e + " ,kind: " + e.getKind()); + } + } + + /** + * Return a List of Taglets that can + * appear in constructors. + * @return the array of Taglets that can + * appear in constructors. + */ + public List getConstructorCustomTaglets() { + if (constructorTags == null) { + initCustomTaglets(); + } + return constructorTags; + } + + /** + * Return a List of Taglets that can + * appear in methods. + * @return the array of Taglets that can + * appear in methods. + */ + public List getMethodCustomTaglets() { + if (methodTags == null) { + initCustomTaglets(); + } + return methodTags; + } + + /** + * Return a List of Taglets that can + * appear in an overview. + * @return the array of Taglets that can + * appear in overview. + */ + public List getOverviewCustomTaglets() { + if (overviewTags == null) { + initCustomTaglets(); + } + return overviewTags; + } + + /** + * Initialize the custom tag Lists. + */ + private void initCustomTaglets() { + + packageTags = new ArrayList<>(); + typeTags = new ArrayList<>(); + fieldTags = new ArrayList<>(); + constructorTags = new ArrayList<>(); + methodTags = new ArrayList<>(); + inlineTags = new ArrayList<>(); + overviewTags = new ArrayList<>(); + + for (Taglet current : customTags.values()) { + if (current.inPackage() && !current.isInlineTag()) { + packageTags.add(current); + } + if (current.inType() && !current.isInlineTag()) { + typeTags.add(current); + } + if (current.inField() && !current.isInlineTag()) { + fieldTags.add(current); + } + if (current.inConstructor() && !current.isInlineTag()) { + constructorTags.add(current); + } + if (current.inMethod() && !current.isInlineTag()) { + methodTags.add(current); + } + if (current.isInlineTag()) { + inlineTags.add(current); + } + if (current.inOverview() && !current.isInlineTag()) { + overviewTags.add(current); + } + } + + //Init the serialized form tags + serializedFormTags = new ArrayList<>(); + serializedFormTags.add(customTags.get(SERIAL_DATA.tagName)); + serializedFormTags.add(customTags.get(THROWS.tagName)); + if (!nosince) + serializedFormTags.add(customTags.get(SINCE.tagName)); + serializedFormTags.add(customTags.get(SEE.tagName)); + } + + /** + * Initialize standard Javadoc tags for ordering purposes. + */ + private void initStandardTaglets() { + if (javafx) { + initJavaFXTaglets(); + } + + Taglet temp; + addStandardTaglet(new ParamTaglet()); + addStandardTaglet(new ReturnTaglet()); + addStandardTaglet(new ThrowsTaglet()); + addStandardTaglet(new SimpleTaglet(EXCEPTION.tagName, null, + SimpleTaglet.METHOD + SimpleTaglet.CONSTRUCTOR)); + addStandardTaglet(!nosince, new SimpleTaglet(SINCE.tagName, message.getText("doclet.Since"), + SimpleTaglet.ALL)); + addStandardTaglet(showversion, new SimpleTaglet(VERSION.tagName, message.getText("doclet.Version"), + SimpleTaglet.PACKAGE + SimpleTaglet.TYPE + SimpleTaglet.OVERVIEW)); + addStandardTaglet(showauthor, new SimpleTaglet(AUTHOR.tagName, message.getText("doclet.Author"), + SimpleTaglet.PACKAGE + SimpleTaglet.TYPE + SimpleTaglet.OVERVIEW)); + addStandardTaglet(new SimpleTaglet(SERIAL_DATA.tagName, message.getText("doclet.SerialData"), + SimpleTaglet.EXCLUDED)); + customTags.put((temp = new SimpleTaglet("factory", message.getText("doclet.Factory"), + SimpleTaglet.METHOD)).getName(), temp); + addStandardTaglet(new SeeTaglet()); + //Standard inline tags + addStandardTaglet(new DocRootTaglet()); + addStandardTaglet(new InheritDocTaglet()); + addStandardTaglet(new ValueTaglet()); + addStandardTaglet(new LiteralTaglet()); + addStandardTaglet(new CodeTaglet()); + addStandardTaglet(new IndexTaglet()); + + // Keep track of the names of standard tags for error + // checking purposes. The following are not handled above. + standardTags.add(DEPRECATED.tagName); + standardTags.add(LINK.tagName); + standardTags.add(LINK_PLAIN.tagName); + standardTags.add(SERIAL.tagName); + standardTags.add(SERIAL_FIELD.tagName); + } + + /** + * Initialize JavaFX-related tags. + */ + private void initJavaFXTaglets() { + addStandardTaglet(new PropertyGetterTaglet()); + addStandardTaglet(new PropertySetterTaglet()); + addStandardTaglet(new SimpleTaglet("propertyDescription", + message.getText("doclet.PropertyDescription"), + SimpleTaglet.FIELD + SimpleTaglet.METHOD)); + addStandardTaglet(new SimpleTaglet("defaultValue", message.getText("doclet.DefaultValue"), + SimpleTaglet.FIELD + SimpleTaglet.METHOD)); + addStandardTaglet(new SimpleTaglet("treatAsPrivate", null, + SimpleTaglet.FIELD + SimpleTaglet.METHOD + SimpleTaglet.TYPE)); + } + + void addStandardTaglet(Taglet taglet) { + String name = taglet.getName(); + customTags.put(name, taglet); + standardTags.add(name); + } + + void addStandardTaglet(boolean enable, Taglet taglet) { + String name = taglet.getName(); + if (enable) + customTags.put(name, taglet); + standardTags.add(name); + } + + /** + * Initialize lowercase version of standard Javadoc tags. + */ + private void initStandardTagsLowercase() { + for (String standardTag : standardTags) { + standardTagsLowercase.add(Utils.toLowerCase(standardTag)); + } + } + + public boolean isKnownCustomTag(String tagName) { + return customTags.containsKey(tagName); + } + + /** + * Print a list of {@link Taglet}s that might conflict with + * standard tags in the future and a list of standard tags + * that have been overriden. + */ + public void printReport() { + printReportHelper("doclet.Notice_taglet_conflict_warn", potentiallyConflictingTags); + printReportHelper("doclet.Notice_taglet_overriden", overridenStandardTags); + printReportHelper("doclet.Notice_taglet_unseen", unseenCustomTags); + } + + private void printReportHelper(String noticeKey, Set names) { + if (names.size() > 0) { + String[] namesArray = names.toArray(new String[] {}); + String result = " "; + for (int i = 0; i < namesArray.length; i++) { + result += "@" + namesArray[i]; + if (i + 1 < namesArray.length) { + result += ", "; + } + } + message.notice(noticeKey, result); + } + } + + /** + * Given the name of a tag, return the corresponding taglet. + * Return null if the tag is unknown. + * + * @param name the name of the taglet to retrieve. + * @return return the corresponding taglet. Return null if the tag is + * unknown. + */ + public Taglet getTaglet(String name) { + if (name.indexOf("@") == 0) { + return customTags.get(name.substring(1)); + } else { + return customTags.get(name); + } + + } +}