--- old/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/VisibleMemberMap.java Fri Jan 22 12:20:49 2016 +++ /dev/null Fri Jan 22 12:20:49 2016 @@ -1,771 +0,0 @@ -/* - * 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 com.sun.tools.doclets.internal.toolkit.util; - -import java.util.*; -import java.util.regex.Pattern; - -import com.sun.javadoc.*; -import com.sun.tools.doclets.internal.toolkit.*; - -/** - * A data structure that encapsulates the visible members of a particular - * type for a given class tree. To use this data structor, 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 - * @author Jamie Ho (rewrite) - */ -public class VisibleMemberMap { - - private boolean noVisibleMembers = true; - - public static final int INNERCLASSES = 0; - public static final int ENUM_CONSTANTS = 1; - public static final int FIELDS = 2; - public static final int CONSTRUCTORS = 3; - public static final int METHODS = 4; - public static final int ANNOTATION_TYPE_FIELDS = 5; - public static final int ANNOTATION_TYPE_MEMBER_OPTIONAL = 6; - public static final int ANNOTATION_TYPE_MEMBER_REQUIRED = 7; - public static final int PROPERTIES = 8; - - /** - * The total number of member types is {@value}. - */ - public static final int NUM_MEMBER_TYPES = 9; - - public static final String STARTLEVEL = "start"; - - /** - * List of ClassDoc objects for which ClassMembers objects are built. - */ - private final List visibleClasses = new ArrayList<>(); - - /** - * 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> memberNameMap = new HashMap<>(); - - /** - * Map of class and it's ClassMembers object. - */ - private final Map classMap = new HashMap<>(); - - /** - * Type whose visible members are requested. This is the leaf of - * the class tree being mapped. - */ - private final ClassDoc classdoc; - - /** - * Member kind: InnerClasses/Fields/Methods? - */ - private final int kind; - - /** - * The configuration this VisibleMemberMap was created with. - */ - private final Configuration configuration; - private final Utils utils; - - private static final Map propertiesCache = new HashMap<>(); - private static final Map classPropertiesMap = new HashMap<>(); - private static final Map getterSetterMap = new HashMap<>(); - - /** - * Construct a VisibleMemberMap of the given type for the given - * class. - * - * @param classdoc the class 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(ClassDoc classdoc, - int kind, - Configuration configuration) { - this.classdoc = classdoc; - this.kind = kind; - this.configuration = configuration; - this.utils = configuration.utils; - new ClassMembers(classdoc, STARTLEVEL).build(); - } - - /** - * Return the list of visible classes in this map. - * - * @return the list of visible classes in this map. - */ - public List getVisibleClassesList() { - sort(visibleClasses); - return visibleClasses; - } - - /** - * Returns the property field documentation belonging to the given member. - * @param ped the member for which the property documentation is needed. - * @return the property field documentation, null if there is none. - */ - public ProgramElementDoc getPropertyMemberDoc(ProgramElementDoc ped) { - return classPropertiesMap.get(ped); - } - - /** - * 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 ProgramElementDoc getGetterForProperty(ProgramElementDoc propertyMethod) { - return getterSetterMap.get(propertyMethod).getGetter(); - } - - /** - * 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 ProgramElementDoc getSetterForProperty(ProgramElementDoc 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. - * - * @param configuration the current configuration of the doclet. - * @return the package private members inherited by the class. - */ - private List getInheritedPackagePrivateMethods(Configuration configuration) { - List results = new ArrayList<>(); - for (ClassDoc currentClass : visibleClasses) { - if (currentClass != classdoc && - currentClass.isPackagePrivate() && - !utils.isLinkable(currentClass, configuration)) { - // Document these members in the child class because - // the parent is inaccessible. - results.addAll(getMembersFor(currentClass)); - } - } - return results; - } - - /** - * Return the visible members of the class being mapped. Also append at the - * end of the list members that are inherited by inaccessible parents. We - * document these members in the child because the parent is not documented. - * - * @param configuration the current configuration of the doclet. - */ - public List getLeafClassMembers(Configuration configuration) { - List result = getMembersFor(classdoc); - result.addAll(getInheritedPackagePrivateMethods(configuration)); - return result; - } - - /** - * Retrn the list of members for the given class. - * - * @param cd the class to retrieve the list of visible members for. - * - * @return the list of members for the given class. - */ - public List getMembersFor(ClassDoc cd) { - ClassMembers clmembers = classMap.get(cd); - if (clmembers == null) { - return new ArrayList<>(); - } - return clmembers.getMembers(); - } - - /** - * Sort the given mixed list of classes and interfaces to a list of - * classes followed by interfaces traversed. Don't sort alphabetically. - */ - private void sort(List list) { - List classes = new ArrayList<>(); - List interfaces = new ArrayList<>(); - for (ClassDoc cd : list) { - if (cd.isClass()) { - classes.add(cd); - } - else { - interfaces.add(cd); - } - } - list.clear(); - list.addAll(classes); - list.addAll(interfaces); - } - - private void fillMemberLevelMap(List list, String level) { - for (ProgramElementDoc element : list) { - Object key = getMemberKey(element); - Map memberLevelMap = memberNameMap.get(key); - if (memberLevelMap == null) { - memberLevelMap = new HashMap<>(); - memberNameMap.put(key, memberLevelMap); - } - memberLevelMap.put(element, level); - } - } - - private void purgeMemberLevelMap(List list, String level) { - for (ProgramElementDoc element : list) { - Object key = getMemberKey(element); - Map memberLevelMap = memberNameMap.get(key); - if (memberLevelMap != null && level.equals(memberLevelMap.get(element))) - memberLevelMap.remove(element); - } - } - - /** - * Represents a class member. We should be able to just use a - * ProgramElementDoc instead of this class, but that doesn't take - * type variables in consideration when comparing. - */ - private class ClassMember { - private Set members; - - public ClassMember(ProgramElementDoc programElementDoc) { - members = new HashSet<>(); - members.add(programElementDoc); - } - - public void addMember(ProgramElementDoc programElementDoc) { - members.add(programElementDoc); - } - - public boolean isEqual(MethodDoc member) { - for (ProgramElementDoc element : members) { - if (utils.executableMembersEqual(member, (MethodDoc) element)) { - members.add(member); - return true; - } - } - return false; - } - } - - /** - * 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 - * {@link #members} list. - */ - private ClassDoc mappingClass; - - /** - * List of inherited members from the mapping class. - */ - private List members = new ArrayList<>(); - - /** - * Level/Depth of inheritance. - */ - private String level; - - /** - * Return list of inherited members from mapping class. - * - * @return List Inherited members. - */ - public List getMembers() { - return members; - } - - private ClassMembers(ClassDoc mappingClass, String level) { - this.mappingClass = 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 == CONSTRUCTORS) { - addMembers(mappingClass); - } else { - mapClass(); - } - } - - private void mapClass() { - addMembers(mappingClass); - ClassDoc[] interfaces = mappingClass.interfaces(); - for (ClassDoc anInterface : interfaces) { - String locallevel = level + 1; - ClassMembers cm = new ClassMembers(anInterface, locallevel); - cm.mapClass(); - } - if (mappingClass.isClass()) { - ClassDoc superclass = mappingClass.superclass(); - if (!(superclass == null || mappingClass.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. - */ - private void addMembers(ClassDoc fromClass) { - List cdmembers = getClassMembers(fromClass, true); - List incllist = new ArrayList<>(); - for (ProgramElementDoc pgmelem : cdmembers) { - if (!found(members, pgmelem) && - memberIsVisible(pgmelem) && - !isOverridden(pgmelem, level) && - !isTreatedAsPrivate(pgmelem)) { - incllist.add(pgmelem); - } - } - if (incllist.size() > 0) { - noVisibleMembers = false; - } - members.addAll(incllist); - fillMemberLevelMap(getClassMembers(fromClass, false), level); - } - - private boolean isTreatedAsPrivate(ProgramElementDoc pgmelem) { - if (!configuration.javafx) { - return false; - } - - Tag[] aspTags = pgmelem.tags("@treatAsPrivate"); - boolean result = (aspTags != null) && (aspTags.length > 0); - return result; - } - - /** - * Is given doc item visible in given classdoc in terms fo inheritance? - * The given doc item is visible in the given classdoc 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 classdoc. - */ - private boolean memberIsVisible(ProgramElementDoc pgmdoc) { - if (pgmdoc.containingClass().equals(classdoc)) { - //Member is in class that we are finding visible members for. - //Of course it is visible. - return true; - } else if (pgmdoc.isPrivate()) { - //Member is in super class or implemented interface. - //Private, so not inherited. - return false; - } else if (pgmdoc.isPackagePrivate()) { - //Member is package private. Only return true if its class is in - //same package. - return pgmdoc.containingClass().containingPackage().equals( - classdoc.containingPackage()); - } else { - //Public members are always inherited. - return true; - } - } - - /** - * Return all available class members. - */ - private List getClassMembers(ClassDoc cd, boolean filter) { - if (cd.isEnum() && kind == CONSTRUCTORS) { - //If any of these rules are hit, return empty array because - //we don't document these members ever. - return Arrays.asList(new ProgramElementDoc[] {}); - } - ProgramElementDoc[] members = null; - switch (kind) { - case ANNOTATION_TYPE_FIELDS: - members = cd.fields(filter); - break; - case ANNOTATION_TYPE_MEMBER_OPTIONAL: - members = cd.isAnnotationType() ? - filter((AnnotationTypeDoc) cd, false) : - new AnnotationTypeElementDoc[] {}; - break; - case ANNOTATION_TYPE_MEMBER_REQUIRED: - members = cd.isAnnotationType() ? - filter((AnnotationTypeDoc) cd, true) : - new AnnotationTypeElementDoc[] {}; - break; - case INNERCLASSES: - members = cd.innerClasses(filter); - break; - case ENUM_CONSTANTS: - members = cd.enumConstants(); - break; - case FIELDS: - members = cd.fields(filter); - break; - case CONSTRUCTORS: - members = cd.constructors(); - break; - case METHODS: - members = cd.methods(filter); - checkOnPropertiesTags((MethodDoc[])members); - break; - case PROPERTIES: - members = properties(cd, filter); - break; - default: - members = new ProgramElementDoc[0]; - } - // Deprected members should be excluded or not? - if (configuration.nodeprecated) { - return utils.excludeDeprecatedMembersAsList(members); - } - return Arrays.asList(members); - } - - /** - * Filter the annotation type members and return either the required - * members or the optional members, depending on the value of the - * required parameter. - * - * @param doc 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 AnnotationTypeElementDoc[] filter(AnnotationTypeDoc doc, - boolean required) { - AnnotationTypeElementDoc[] members = doc.elements(); - List targetMembers = new ArrayList<>(); - for (AnnotationTypeElementDoc member : members) { - if ((required && member.defaultValue() == null) || - ((!required) && member.defaultValue() != null)) { - targetMembers.add(member); - } - } - return targetMembers.toArray(new AnnotationTypeElementDoc[]{}); - } - - private boolean found(List list, ProgramElementDoc elem) { - for (ProgramElementDoc pgmelem : list) { - if (utils.matches(pgmelem, elem)) { - return true; - } - } - return false; - } - - - /** - * 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(ProgramElementDoc pgmdoc, String level) { - Map memberLevelMap = (Map) memberNameMap.get(getMemberKey(pgmdoc)); - 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 ProgramElementDoc[] properties(final ClassDoc cd, final boolean filter) { - final MethodDoc[] allMethods = cd.methods(filter); - final FieldDoc[] allFields = cd.fields(false); - - if (propertiesCache.containsKey(cd)) { - return propertiesCache.get(cd); - } - - final List result = new ArrayList<>(); - - for (final MethodDoc propertyMethod : allMethods) { - - if (!isPropertyMethod(propertyMethod)) { - continue; - } - - final MethodDoc getter = getterForField(allMethods, propertyMethod); - final MethodDoc setter = setterForField(allMethods, propertyMethod); - final FieldDoc field = fieldForProperty(allFields, propertyMethod); - - addToPropertiesMap(setter, getter, propertyMethod, field); - getterSetterMap.put(propertyMethod, new GetterSetter(getter, setter)); - result.add(propertyMethod); - } - final ProgramElementDoc[] resultAray = - result.toArray(new ProgramElementDoc[result.size()]); - propertiesCache.put(cd, resultAray); - return resultAray; - } - - private void addToPropertiesMap(MethodDoc setter, - MethodDoc getter, - MethodDoc propertyMethod, - FieldDoc field) { - if ((field == null) - || (field.getRawCommentText() == null) - || field.getRawCommentText().length() == 0) { - addToPropertiesMap(setter, propertyMethod); - addToPropertiesMap(getter, propertyMethod); - addToPropertiesMap(propertyMethod, propertyMethod); - } else { - addToPropertiesMap(getter, field); - addToPropertiesMap(setter, field); - addToPropertiesMap(propertyMethod, field); - } - } - - private void addToPropertiesMap(ProgramElementDoc propertyMethod, - ProgramElementDoc commentSource) { - if (null == propertyMethod || null == commentSource) { - return; - } - final String methodRawCommentText = propertyMethod.getRawCommentText(); - - /* 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 ((null == methodRawCommentText || 0 == methodRawCommentText.length()) - || propertyMethod.equals(commentSource)) { - classPropertiesMap.put(propertyMethod, commentSource); - } - } - - private MethodDoc getterForField(MethodDoc[] methods, - MethodDoc propertyMethod) { - final String propertyMethodName = propertyMethod.name(); - 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.returnType().toString(); - if ("boolean".equals(fieldTypeName) - || fieldTypeName.endsWith("BooleanProperty")) { - getterNamePattern = "(is|get)" + fieldNameUppercased; - } else { - getterNamePattern = "get" + fieldNameUppercased; - } - - for (MethodDoc methodDoc : methods) { - if (Pattern.matches(getterNamePattern, methodDoc.name())) { - if (0 == methodDoc.parameters().length - && (methodDoc.isPublic() || methodDoc.isProtected())) { - return methodDoc; - } - } - } - return null; - } - - private MethodDoc setterForField(MethodDoc[] methods, - MethodDoc propertyMethod) { - final String propertyMethodName = propertyMethod.name(); - 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 (MethodDoc methodDoc : methods) { - if (setter.equals(methodDoc.name())) { - if (1 == methodDoc.parameters().length - && "void".equals(methodDoc.returnType().simpleTypeName()) - && (methodDoc.isPublic() || methodDoc.isProtected())) { - return methodDoc; - } - } - } - return null; - } - - private FieldDoc fieldForProperty(FieldDoc[] fields, MethodDoc property) { - - for (FieldDoc field : fields) { - final String fieldName = field.name(); - final String propertyName = fieldName + "Property"; - if (propertyName.equals(property.name())) { - return field; - } - } - return null; - } - - // properties aren't named setA* or getA* - private final Pattern pattern = Pattern.compile("[sg]et\\p{Upper}.*"); - private boolean isPropertyMethod(MethodDoc method) { - if (!configuration.javafx) { - return false; - } - if (!method.name().endsWith("Property")) { - return false; - } - - if (! memberIsVisible(method)) { - return false; - } - - if (pattern.matcher(method.name()).matches()) { - return false; - } - if (method.typeParameters().length > 0) { - return false; - } - return 0 == method.parameters().length - && !"void".equals(method.returnType().simpleTypeName()); - } - - private void checkOnPropertiesTags(MethodDoc[] members) { - for (MethodDoc methodDoc: members) { - if (methodDoc.isIncluded()) { - for (Tag tag: methodDoc.tags()) { - String tagName = tag.name(); - if (tagName.equals("@propertySetter") - || tagName.equals("@propertyGetter") - || tagName.equals("@propertyDescription")) { - if (!isPropertyGetterOrSetter(members, methodDoc)) { - configuration.message.warning(tag.position(), - "doclet.javafx_tag_misuse"); - } - break; - } - } - } - } - } - - private boolean isPropertyGetterOrSetter(MethodDoc[] members, - MethodDoc methodDoc) { - boolean found = false; - String propertyName = utils.propertyNameFromMethodName(configuration, methodDoc.name()); - if (!propertyName.isEmpty()) { - String propertyMethodName = propertyName + "Property"; - for (MethodDoc member: members) { - if (member.name().equals(propertyMethodName)) { - found = true; - break; - } - } - } - return found; - } - } - - private class GetterSetter { - private final ProgramElementDoc getter; - private final ProgramElementDoc setter; - - public GetterSetter(ProgramElementDoc getter, ProgramElementDoc setter) { - this.getter = getter; - this.setter = setter; - } - - public ProgramElementDoc getGetter() { - return getter; - } - - public ProgramElementDoc getSetter() { - return setter; - } - } - - /** - * 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(MethodDoc member) { - for (Object key : memberNameMap.keySet()) { - if (key instanceof String) { - continue; - } else if (((ClassMember) key).isEqual(member)) { - return (ClassMember) key; - } - } - return new ClassMember(member); - } - - /** - * Return the key to the member map for the given member. - */ - private Object getMemberKey(ProgramElementDoc doc) { - if (doc.isConstructor()) { - return doc.name() + ((ExecutableMemberDoc)doc).signature(); - } else if (doc.isMethod()) { - return getClassMember((MethodDoc) doc); - } else if (doc.isField() || doc.isEnumConstant() || doc.isAnnotationTypeElement()) { - return doc.name(); - } else { // it's a class or interface - String classOrIntName = doc.name(); - //Strip off the containing class name because we only want the member name. - classOrIntName = classOrIntName.indexOf('.') != 0 ? classOrIntName.substring(classOrIntName.lastIndexOf('.'), classOrIntName.length()) : classOrIntName; - return "clint" + classOrIntName; - } - } -} --- /dev/null Fri Jan 22 12:20:49 2016 +++ new/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/VisibleMemberMap.java Fri Jan 22 12:20:48 2016 @@ -0,0 +1,790 @@ +/* + * 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; + +/** + * 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 + * @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 summarySet = EnumSet.range(INNER_CLASSES, METHODS); + public static final EnumSet 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. + */ + private final Set 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. + */ + private final Map> memberNameMap = new HashMap<>(); + + /** + * Map of class and it's ClassMembers object. + */ + private final Map classMap = new HashMap<>(); + + /** + * 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? + */ + private final Kind kind; + + /** + * The configuration this VisibleMemberMap was created with. + */ + private final Configuration configuration; + private final Utils utils; + private final Comparator comparator; + + private static final Map> propertiesCache = new HashMap<>(); + private static final Map classPropertiesMap = new HashMap<>(); + private static final Map getterSetterMap = new HashMap<>(); + + /** + * 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.utils = configuration.utils; + comparator = utils.makeGeneralPurposeComparator(); + visibleClasses = new LinkedHashSet<>(); + new ClassMembers(typeElement, STARTLEVEL).build(); + } + + /** + * Return the list of visible classes in this map. + * + * @return the list of visible classes in this map. + */ + public SortedSet getVisibleClasses() { + SortedSet vClasses = new TreeSet<>(comparator); + vClasses.addAll(visibleClasses); + return vClasses; + } + + /** + * 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. + * @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. + * @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. + * + * @return the package private members inherited by the class. + */ + private List getInheritedPackagePrivateMethods() { + List 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; + } + + /** + * Return the visible members of the class being mapped. Also append at the + * end of the list members that are inherited by inaccessible parents. We + * document these members in the child because the parent is not documented. + * + * @param configuration the current configuration of the doclet. + */ + public SortedSet getLeafClassMembers() { + SortedSet result = getMembersFor(typeElement); + result.addAll(getInheritedPackagePrivateMethods()); + return result; + } + + public Set getLeafClassMembersSourceOrder() { + Set result = new LinkedHashSet<>(classMap.get(typeElement).members); + result.addAll(getInheritedPackagePrivateMethods()); + return result; + } + + /** + * Retrn the list of members for the given class. + * + * @param typeElement the class to retrieve the list of visible members for. + * + * @return the list of members for the given class. + */ + public SortedSet getMembersFor(TypeElement typeElement) { + return asSortedSet(classMap.get(typeElement).members); + } + + public boolean hasMembersFor(TypeElement typeElement) { + return !classMap.get(typeElement).members.isEmpty(); + } + + private SortedSet asSortedSet(Collection in) { + if (in == null) { + return Collections.emptySortedSet(); + } + TreeSet out = new TreeSet<>(comparator); + out.addAll(in); + return out; + } + + private void fillMemberLevelMap(List list, String level) { + for (Element element : list) { + Object key = getMemberKey(element); + Map memberLevelMap = memberNameMap.get(key); + if (memberLevelMap == null) { + memberLevelMap = new HashMap<>(); + memberNameMap.put(key, memberLevelMap); + } + memberLevelMap.put(element, level); + } + } + + private void purgeMemberLevelMap(Iterable list, String level) { + for (Element element : list) { + Object key = getMemberKey(element); + Map memberLevelMap = memberNameMap.get(key); + if (memberLevelMap != null && level.equals(memberLevelMap.get(element))) + memberLevelMap.remove(element); + } + } + + /** + * Represents a class member. + */ + private class ClassMember { + private Set 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. + */ + private class ClassMembers { + + /** + * The mapping class, whose inherited members are put in the + * {@link #members} list. + */ + private final TypeElement typeElement; + + /** + * List of inherited members from the mapping class. + */ + private Set members = new LinkedHashSet<>(); + + /** + * 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 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. + */ + private void addMembers(TypeElement fromClass) { + List classMembers = getClassMembers(fromClass, true); + List incllist = new ArrayList<>(); + for (Element element : classMembers) { + if (!found(members, element)) { + if (memberIsVisible(element)) { + if (!isOverridden(element, level)) { + if (!isTreatedAsPrivate(element)) { + incllist.add(element); + } + } + } + } + } + if (!incllist.isEmpty()) { + noVisibleMembers = false; + } + members.addAll(incllist); + fillMemberLevelMap(getClassMembers(fromClass, false), level); + } + + private boolean isTreatedAsPrivate(Element pgmelem) { + if (!configuration.javafx) { + return false; + } + + List aspTags = utils.getBlockTags(pgmelem, "@treatAsPrivate"); + boolean result = (aspTags != null) && (!aspTags.isEmpty()); + return result; + } + + /** + * 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. + */ + private List 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 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 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. + * + * @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 filterAnnotations(TypeElement typeElement, boolean required) { + List members = utils.getAnnotationMethods(typeElement); + List 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; + } + + private boolean found(Iterable list, Element elem) { + for (Element pgmelem : list) { + if (utils.matches(pgmelem, elem)) { + return true; + } + } + return false; + } + + + /** + * 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 memberLevelMap = (Map) 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 properties(final TypeElement typeElement, final boolean filter) { + final List allMethods = filter + ? utils.getMethods(typeElement) + : utils.getMethodsUnfiltered(typeElement); + final List allFields = utils.getFieldsUnfiltered(typeElement); + + if (propertiesCache.containsKey(typeElement)) { + return propertiesCache.get(typeElement); + } + + final List 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 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 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 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 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)) { + configuration.message.warning(ch.getDocTreePath(tree), + "doclet.javafx_tag_misuse"); + } + break; + } + } + } + } + } + + private boolean isPropertyGetterOrSetter(List 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; + } + } + + private 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. + * + * @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. + */ + 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; + } + } +}