/* * 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. * * <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) */ @Deprecated 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<ClassDoc> 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<Object,Map<ProgramElementDoc,String>> memberNameMap = new HashMap<>(); /** * Map of class and it's ClassMembers object. */ private final Map<ClassDoc,ClassMembers> 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<ClassDoc, ProgramElementDoc[]> propertiesCache = new HashMap<>(); private static final Map<ProgramElementDoc, ProgramElementDoc> classPropertiesMap = new HashMap<>(); private static final Map<ProgramElementDoc, GetterSetter> 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<ClassDoc> 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<ProgramElementDoc> getInheritedPackagePrivateMethods(Configuration configuration) { List<ProgramElementDoc> 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<ProgramElementDoc> getLeafClassMembers(Configuration configuration) { List<ProgramElementDoc> 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<ProgramElementDoc> 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<ClassDoc> list) { List<ClassDoc> classes = new ArrayList<>(); List<ClassDoc> 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<ProgramElementDoc> list, String level) { for (ProgramElementDoc element : list) { Object key = getMemberKey(element); Map<ProgramElementDoc, String> memberLevelMap = memberNameMap.get(key); if (memberLevelMap == null) { memberLevelMap = new HashMap<>(); memberNameMap.put(key, memberLevelMap); } memberLevelMap.put(element, level); } } private void purgeMemberLevelMap(List<ProgramElementDoc> list, String level) { for (ProgramElementDoc element : list) { Object key = getMemberKey(element); Map<ProgramElementDoc, String> 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<ProgramElementDoc> 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<ProgramElementDoc> members = new ArrayList<>(); /** * Level/Depth of inheritance. */ private String level; /** * Return list of inherited members from mapping class. * * @return List Inherited members. */ public List<ProgramElementDoc> 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<ProgramElementDoc> cdmembers = getClassMembers(fromClass, true); List<ProgramElementDoc> 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<ProgramElementDoc> 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<AnnotationTypeElementDoc> 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<ProgramElementDoc> 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<?,String> memberLevelMap = (Map<?,String>) 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<MethodDoc> 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; } } }