/*
 * Copyright (c) 1999, 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.util;

import java.util.*;
import java.util.regex.Pattern;

import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.DocTree;

import jdk.javadoc.internal.doclets.toolkit.Configuration;
import jdk.javadoc.internal.doclets.toolkit.Messages;

A data structure that encapsulates the visible members of a particular type for a given class tree. To use this data structure, you must specify the type of member you are interested in (nested class, field, constructor or method) and the leaf of the class tree. The data structure will map all visible members in the leaf and classes above the leaf in the tree.

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:Atul M Dambalkar, Jamie Ho (rewrite)
/** * A data structure that encapsulates the visible members of a particular * type for a given class tree. To use this data structure, you must specify * the type of member you are interested in (nested class, field, constructor * or method) and the leaf of the class tree. The data structure will map * all visible members in the leaf and classes above the leaf in the tree. * * <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 Atul M Dambalkar * @author Jamie Ho (rewrite) */
public class VisibleMemberMap { private boolean noVisibleMembers = true; public static enum Kind { INNER_CLASSES, ENUM_CONSTANTS, FIELDS, CONSTRUCTORS, METHODS, ANNOTATION_TYPE_FIELDS, ANNOTATION_TYPE_MEMBER_OPTIONAL, ANNOTATION_TYPE_MEMBER_REQUIRED, PROPERTIES; public static final EnumSet<Kind> summarySet = EnumSet.range(INNER_CLASSES, METHODS); public static final EnumSet<Kind> detailSet = EnumSet.range(ENUM_CONSTANTS, METHODS); public static String getNavLinkLabels(Kind kind) { switch (kind) { case INNER_CLASSES: return "doclet.navNested"; case ENUM_CONSTANTS: return "doclet.navEnum"; case FIELDS: return "doclet.navField"; case CONSTRUCTORS: return "doclet.navConstructor"; case METHODS: return "doclet.navMethod"; default: throw new AssertionError("unknown kind:" + kind); } } } public static final String STARTLEVEL = "start"; // properties aren't named setA* or getA* private static final Pattern GETTERSETTERPATTERN = Pattern.compile("[sg]et\\p{Upper}.*");
List of TypeElement objects for which ClassMembers objects are built.
/** * List of TypeElement objects for which ClassMembers objects are built. */
private final Set<TypeElement> visibleClasses;
Map for each member name on to a map which contains members with same name-signature. The mapped map will contain mapping for each MemberDoc onto it's respecive level string.
/** * Map for each member name on to a map which contains members with same * name-signature. The mapped map will contain mapping for each MemberDoc * onto it's respecive level string. */
private final Map<Object, Map<Element, String>> memberNameMap = new HashMap<>();
Map of class and it's ClassMembers object.
/** * Map of class and it's ClassMembers object. */
private final Map<TypeElement, ClassMembers> classMap = new HashMap<>();
Type whose visible members are requested. This is the leaf of the class tree being mapped.
/** * Type whose visible members are requested. This is the leaf of * the class tree being mapped. */
private final TypeElement typeElement;
Member kind: InnerClasses/Fields/Methods?
/** * Member kind: InnerClasses/Fields/Methods? */
private final Kind kind;
The configuration this VisibleMemberMap was created with.
/** * The configuration this VisibleMemberMap was created with. */
private final Configuration configuration; private final Messages messages; private final Utils utils; private final Comparator<Element> comparator; private final Map<TypeElement, List<Element>> propertiesCache; private final Map<Element, Element> classPropertiesMap; private final Map<Element, GetterSetter> getterSetterMap;
Construct a VisibleMemberMap of the given type for the given class.
Params:
  • typeElement – whose members are being mapped.
  • kind – the kind of member that is being mapped.
  • configuration – the configuration to use to construct this VisibleMemberMap. If the field configuration.nodeprecated is true the deprecated members are excluded from the map. If the field configuration.javafx is true the JavaFX features are used.
/** * Construct a VisibleMemberMap of the given type for the given class. * * @param typeElement whose members are being mapped. * @param kind the kind of member that is being mapped. * @param configuration the configuration to use to construct this * VisibleMemberMap. If the field configuration.nodeprecated is true the * deprecated members are excluded from the map. If the field * configuration.javafx is true the JavaFX features are used. */
public VisibleMemberMap(TypeElement typeElement, Kind kind, Configuration configuration) { this.typeElement = typeElement; this.kind = kind; this.configuration = configuration; this.messages = configuration.getMessages(); this.utils = configuration.utils; propertiesCache = configuration.propertiesCache; classPropertiesMap = configuration.classPropertiesMap; getterSetterMap = configuration.getterSetterMap; comparator = utils.makeGeneralPurposeComparator(); visibleClasses = new LinkedHashSet<>(); new ClassMembers(typeElement, STARTLEVEL).build(); }
Return the list of visible classes in this map.
Returns:the list of visible classes in this map.
/** * Return the list of visible classes in this map. * * @return the list of visible classes in this map. */
public SortedSet<TypeElement> getVisibleClasses() { SortedSet<TypeElement> vClasses = new TreeSet<>(comparator); vClasses.addAll(visibleClasses); return vClasses; }
Returns the property field documentation belonging to the given member.
Params:
  • element – the member for which the property documentation is needed.
Returns:the property field documentation, null if there is none.
/** * Returns the property field documentation belonging to the given member. * @param element the member for which the property documentation is needed. * @return the property field documentation, null if there is none. */
public Element getPropertyMemberDoc(Element element) { return classPropertiesMap.get(element); }
Returns the getter documentation belonging to the given property method.
Params:
  • propertyMethod – the method for which the getter is needed.
Returns:the getter documentation, null if there is none.
/** * Returns the getter documentation belonging to the given property method. * @param propertyMethod the method for which the getter is needed. * @return the getter documentation, null if there is none. */
public Element getGetterForProperty(Element propertyMethod) { return getterSetterMap.get(propertyMethod).getGetter(); }
Returns the setter documentation belonging to the given property method.
Params:
  • propertyMethod – the method for which the setter is needed.
Returns:the setter documentation, null if there is none.
/** * Returns the setter documentation belonging to the given property method. * @param propertyMethod the method for which the setter is needed. * @return the setter documentation, null if there is none. */
public Element getSetterForProperty(Element propertyMethod) { return getterSetterMap.get(propertyMethod).getSetter(); }
Return the package private members inherited by the class. Only return if parent is package private and not documented.
Returns:the package private members inherited by the class.
/** * Return the package private members inherited by the class. Only return * if parent is package private and not documented. * * @return the package private members inherited by the class. */
private List<Element> getInheritedPackagePrivateMethods() { List<Element> results = new ArrayList<>(); for (TypeElement currentClass : visibleClasses) { if (currentClass != typeElement && utils.isPackagePrivate(currentClass) && !utils.isLinkable(currentClass)) { // Document these members in the child class because // the parent is inaccessible. results.addAll(classMap.get(currentClass).members); } } return results; }
Returns a list of visible enclosed members of the type being mapped. This list may also contain appended members, inherited by inaccessible super types. These members are documented in the subtype when the super type is not documented.
Returns:a list of visible enclosed members
/** * Returns a list of visible enclosed members of the type being mapped. * This list may also contain appended members, inherited by inaccessible * super types. These members are documented in the subtype when the * super type is not documented. * * @return a list of visible enclosed members */
public List<Element> getLeafMembers() { List<Element> result = new ArrayList<>(); result.addAll(classMap.get(typeElement).members); result.addAll(getInheritedPackagePrivateMethods()); return result; }
Returns a list of enclosed members for the given type.
Params:
  • typeElement – the given type
Returns:a list of enclosed members
/** * Returns a list of enclosed members for the given type. * * @param typeElement the given type * * @return a list of enclosed members */
public List<Element> getMembers(TypeElement typeElement) { return classMap.get(typeElement).members; } public boolean hasMembers(TypeElement typeElement) { return !classMap.get(typeElement).members.isEmpty(); } private void fillMemberLevelMap(List<? extends Element> list, String level) { for (Element element : list) { Object key = getMemberKey(element); Map<Element, String> memberLevelMap = memberNameMap.get(key); if (memberLevelMap == null) { memberLevelMap = new HashMap<>(); memberNameMap.put(key, memberLevelMap); } memberLevelMap.put(element, level); } } private void purgeMemberLevelMap(Iterable<? extends Element> list, String level) { for (Element element : list) { Object key = getMemberKey(element); Map<Element, String> memberLevelMap = memberNameMap.get(key); if (memberLevelMap != null && level.equals(memberLevelMap.get(element))) memberLevelMap.remove(element); } }
Represents a class member.
/** * Represents a class member. */
private class ClassMember { private Set<Element> members; public ClassMember(Element element) { members = new HashSet<>(); members.add(element); } public boolean isEqual(ExecutableElement member) { for (Element element : members) { if (utils.executableMembersEqual(member, (ExecutableElement) element)) { members.add(member); return true; } } return false; } }
A data structure that represents the class members for a visible class.
/** * A data structure that represents the class members for * a visible class. */
private class ClassMembers {
The mapping class, whose inherited members are put in the members list.
/** * The mapping class, whose inherited members are put in the * {@link #members} list. */
private final TypeElement typeElement;
List of members from the mapping class.
/** * List of members from the mapping class. */
private List<Element> members = null;
Level/Depth of inheritance.
/** * Level/Depth of inheritance. */
private final String level; private ClassMembers(TypeElement mappingClass, String level) { this.typeElement = mappingClass; this.level = level; if (classMap.containsKey(mappingClass) && level.startsWith(classMap.get(mappingClass).level)) { //Remove lower level class so that it can be replaced with //same class found at higher level. purgeMemberLevelMap(getClassMembers(mappingClass, false), classMap.get(mappingClass).level); classMap.remove(mappingClass); visibleClasses.remove(mappingClass); } if (!classMap.containsKey(mappingClass)) { classMap.put(mappingClass, this); visibleClasses.add(mappingClass); } } private void build() { if (kind == Kind.CONSTRUCTORS) { addMembers(typeElement); } else { mapClass(); } } private void mapClass() { addMembers(typeElement); List<? extends TypeMirror> interfaces = typeElement.getInterfaces(); for (TypeMirror anInterface : interfaces) { String locallevel = level + 1; ClassMembers cm = new ClassMembers(utils.asTypeElement(anInterface), locallevel); cm.mapClass(); } if (utils.isClass(typeElement)) { TypeElement superclass = utils.getSuperClass(typeElement); if (!(superclass == null || typeElement.equals(superclass))) { ClassMembers cm = new ClassMembers(superclass, level + "c"); cm.mapClass(); } } }
Get all the valid members from the mapping class. Get the list of members for the class to be included into(ctii), also get the level string for ctii. If mapping class member is not already in the inherited member list and if it is visible in the ctii and not overridden, put such a member in the inherited member list. Adjust member-level-map, class-map.
/** * Get all the valid members from the mapping class. Get the list of * members for the class to be included into(ctii), also get the level * string for ctii. If mapping class member is not already in the * inherited member list and if it is visible in the ctii and not * overridden, put such a member in the inherited member list. * Adjust member-level-map, class-map. */
private void addMembers(TypeElement fromClass) { List<Element> result = new ArrayList<>(); for (Element element : getClassMembers(fromClass, true)) { if (memberIsVisible(element)) { if (!isOverridden(element, level)) { if (!utils.isHidden(element)) { result.add(element); } } } } if (members != null) { throw new AssertionError("members should not be null"); } members = Collections.unmodifiableList(result); if (!members.isEmpty()) { noVisibleMembers = false; } fillMemberLevelMap(getClassMembers(fromClass, false), level); }
Is given element visible in given typeElement in terms of inheritance? The given element is visible in the given typeElement if it is public or protected and if it is package-private if it's containing class is in the same package as the given typeElement.
/** * Is given element visible in given typeElement in terms of inheritance? The given element * is visible in the given typeElement if it is public or protected and if it is * package-private if it's containing class is in the same package as the given typeElement. */
private boolean memberIsVisible(Element element) { if (utils.getEnclosingTypeElement(element).equals(VisibleMemberMap.this.typeElement)) { //Member is in class that we are finding visible members for. //Of course it is visible. return true; } else if (utils.isPrivate(element)) { //Member is in super class or implemented interface. //Private, so not inherited. return false; } else if (utils.isPackagePrivate(element)) { //Member is package private. Only return true if its class is in //same package. return utils.containingPackage(element).equals(utils.containingPackage(VisibleMemberMap.this.typeElement)); } else { //Public members are always inherited. return true; } }
Return all available class members.
/** * Return all available class members. */
private List<? extends Element> getClassMembers(TypeElement te, boolean filter) { if (utils.isEnum(te) && kind == Kind.CONSTRUCTORS) { //If any of these rules are hit, return empty array because //we don't document these members ever. return Collections.emptyList(); } List<? extends Element> list; switch (kind) { case ANNOTATION_TYPE_FIELDS: list = (filter) ? utils.getAnnotationFields(te) : utils.getAnnotationFieldsUnfiltered(te); break; case ANNOTATION_TYPE_MEMBER_OPTIONAL: list = utils.isAnnotationType(te) ? filterAnnotations(te, false) : Collections.emptyList(); break; case ANNOTATION_TYPE_MEMBER_REQUIRED: list = utils.isAnnotationType(te) ? filterAnnotations(te, true) : Collections.emptyList(); break; case INNER_CLASSES: List<TypeElement> xlist = filter ? utils.getInnerClasses(te) : utils.getInnerClassesUnfiltered(te); list = new ArrayList<>(xlist); break; case ENUM_CONSTANTS: list = utils.getEnumConstants(te); break; case FIELDS: if (filter) { list = utils.isAnnotationType(te) ? utils.getAnnotationFields(te) : utils.getFields(te); } else { list = utils.isAnnotationType(te) ? utils.getAnnotationFieldsUnfiltered(te) : utils.getFieldsUnfiltered(te); } break; case CONSTRUCTORS: list = utils.getConstructors(te); break; case METHODS: list = filter ? utils.getMethods(te) : utils.getMethodsUnfiltered(te); checkOnPropertiesTags(list); break; case PROPERTIES: list = properties(te, filter); break; default: list = Collections.emptyList(); } // Deprected members should be excluded or not? if (configuration.nodeprecated) { return utils.excludeDeprecatedMembers(list); } return list; }
Filter the annotation type members and return either the required members or the optional members, depending on the value of the required parameter.
Params:
  • typeElement – The annotation type to process.
  • required –
Returns:the annotation type members and return either the required members or the optional members, depending on the value of the required parameter.
/** * Filter the annotation type members and return either the required * members or the optional members, depending on the value of the * required parameter. * * @param typeElement The annotation type to process. * @param required * @return the annotation type members and return either the required * members or the optional members, depending on the value of the * required parameter. */
private List<Element> filterAnnotations(TypeElement typeElement, boolean required) { List<Element> members = utils.getAnnotationMethods(typeElement); List<Element> targetMembers = new ArrayList<>(); for (Element member : members) { ExecutableElement ee = (ExecutableElement)member; if ((required && ee.getDefaultValue() == null) || ((!required) && ee.getDefaultValue() != null)) { targetMembers.add(member); } } return targetMembers; }
Is member overridden? The member is overridden if it is found in the same level hierarchy e.g. member at level "11" overrides member at level "111".
/** * Is member overridden? The member is overridden if it is found in the * same level hierarchy e.g. member at level "11" overrides member at * level "111". */
private boolean isOverridden(Element element, String level) { Object key = getMemberKey(element); Map<?, String> memberLevelMap = (Map<?, String>) memberNameMap.get(key); if (memberLevelMap == null) return false; for (String mappedlevel : memberLevelMap.values()) { if (mappedlevel.equals(STARTLEVEL) || (level.startsWith(mappedlevel) && !level.equals(mappedlevel))) { return true; } } return false; } private List<Element> properties(final TypeElement typeElement, final boolean filter) { final List<ExecutableElement> allMethods = filter ? utils.getMethods(typeElement) : utils.getMethodsUnfiltered(typeElement); final List<VariableElement> allFields = utils.getFieldsUnfiltered(typeElement); if (propertiesCache.containsKey(typeElement)) { return propertiesCache.get(typeElement); } final List<Element> result = new ArrayList<>(); for (final Element propertyMethod : allMethods) { ExecutableElement ee = (ExecutableElement)propertyMethod; if (!isPropertyMethod(ee)) { continue; } final ExecutableElement getter = getterForField(allMethods, ee); final ExecutableElement setter = setterForField(allMethods, ee); final VariableElement field = fieldForProperty(allFields, ee); addToPropertiesMap(setter, getter, ee, field); getterSetterMap.put(propertyMethod, new GetterSetter(getter, setter)); result.add(ee); } propertiesCache.put(typeElement, result); return result; } private void addToPropertiesMap(ExecutableElement setter, ExecutableElement getter, ExecutableElement propertyMethod, VariableElement field) { if (field == null || utils.getDocCommentTree(field) == null) { addToPropertiesMap(setter, propertyMethod); addToPropertiesMap(getter, propertyMethod); addToPropertiesMap(propertyMethod, propertyMethod); } else { addToPropertiesMap(getter, field); addToPropertiesMap(setter, field); addToPropertiesMap(propertyMethod, field); } } private void addToPropertiesMap(Element propertyMethod, Element commentSource) { if (null == propertyMethod || null == commentSource) { return; } DocCommentTree docTree = utils.getDocCommentTree(propertyMethod); /* The second condition is required for the property buckets. In * this case the comment is at the property method (not at the field) * and it needs to be listed in the map. */ if ((docTree == null) || propertyMethod.equals(commentSource)) { classPropertiesMap.put(propertyMethod, commentSource); } } private ExecutableElement getterForField(List<ExecutableElement> methods, ExecutableElement propertyMethod) { final String propertyMethodName = utils.getSimpleName(propertyMethod); final String fieldName = propertyMethodName.substring(0, propertyMethodName.lastIndexOf("Property")); final String fieldNameUppercased = "" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); final String getterNamePattern; final String fieldTypeName = propertyMethod.getReturnType().toString(); if ("boolean".equals(fieldTypeName) || fieldTypeName.endsWith("BooleanProperty")) { getterNamePattern = "(is|get)" + fieldNameUppercased; } else { getterNamePattern = "get" + fieldNameUppercased; } for (ExecutableElement method : methods) { if (Pattern.matches(getterNamePattern, utils.getSimpleName(method))) { if (method.getParameters().isEmpty() && utils.isPublic(method) || utils.isProtected(method)) { return method; } } } return null; } private ExecutableElement setterForField(List<ExecutableElement> methods, ExecutableElement propertyMethod) { final String propertyMethodName = utils.getSimpleName(propertyMethod); final String fieldName = propertyMethodName.substring(0, propertyMethodName.lastIndexOf("Property")); final String fieldNameUppercased = "" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); final String setter = "set" + fieldNameUppercased; for (ExecutableElement method : methods) { if (setter.equals(utils.getSimpleName(method))) { if (method.getParameters().size() == 1 && method.getReturnType().getKind() == TypeKind.VOID && (utils.isPublic(method) || utils.isProtected(method))) { return method; } } } return null; } private VariableElement fieldForProperty(List<VariableElement> fields, ExecutableElement property) { for (VariableElement field : fields) { final String fieldName = utils.getSimpleName(field); final String propertyName = fieldName + "Property"; if (propertyName.equals(utils.getSimpleName(property))) { return field; } } return null; } private boolean isPropertyMethod(ExecutableElement method) { if (!configuration.javafx) { return false; } if (!utils.getSimpleName(method).endsWith("Property")) { return false; } if (!memberIsVisible(method)) { return false; } if (GETTERSETTERPATTERN.matcher(utils.getSimpleName(method)).matches()) { return false; } if (!method.getTypeParameters().isEmpty()) { return false; } return method.getParameters().isEmpty() && method.getReturnType().getKind() != TypeKind.VOID; } private void checkOnPropertiesTags(List<? extends Element> members) { for (Element e: members) { ExecutableElement ee = (ExecutableElement)e; if (utils.isIncluded(ee)) { CommentHelper ch = utils.getCommentHelper(ee); for (DocTree tree: utils.getBlockTags(ee)) { String tagName = ch.getTagName(tree); if (tagName.equals("@propertySetter") || tagName.equals("@propertyGetter") || tagName.equals("@propertyDescription")) { if (!isPropertyGetterOrSetter(members, ee)) { messages.warning(ch.getDocTreePath(tree), "doclet.javafx_tag_misuse"); } break; } } } } } private boolean isPropertyGetterOrSetter(List<? extends Element> members, ExecutableElement method) { String propertyName = utils.propertyName(method); if (!propertyName.isEmpty()) { String propertyMethodName = propertyName + "Property"; for (Element member: members) { if (utils.getSimpleName(member).equals(propertyMethodName)) { return true; } } } return false; } } public class GetterSetter { private final Element getter; private final Element setter; public GetterSetter(Element getter, Element setter) { this.getter = getter; this.setter = setter; } public Element getGetter() { return getter; } public Element getSetter() { return setter; } }
Return true if this map has no visible members.
Returns:true if this map has no visible members.
/** * Return true if this map has no visible members. * * @return true if this map has no visible members. */
public boolean noVisibleMembers() { return noVisibleMembers; } private ClassMember getClassMember(ExecutableElement member) { for (Object key : memberNameMap.keySet()) { if (key instanceof String) { continue; } if (((ClassMember) key).isEqual(member)) { return (ClassMember) key; } } return new ClassMember(member); }
Return the key to the member map for the given member.
/** * Return the key to the member map for the given member. */
private Object getMemberKey(Element element) { if (utils.isConstructor(element)) { return utils.getSimpleName(element) + utils.flatSignature((ExecutableElement)element); } else if (utils.isMethod(element)) { return getClassMember((ExecutableElement) element); } else if (utils.isField(element) || utils.isEnumConstant(element) || utils.isAnnotationType(element)) { return utils.getSimpleName(element); } else { // it's a class or interface String classOrIntName = utils.getSimpleName(element); //Strip off the containing class name because we only want the member name. classOrIntName = classOrIntName.indexOf('.') != 0 ? classOrIntName.substring(classOrIntName.lastIndexOf('.')) : classOrIntName; return "clint" + classOrIntName; } } }