/* * Copyright 1999-2005 Sun Microsystems, Inc. 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. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.tools.doclets.internal.toolkit.util; import com.sun.javadoc.*; import com.sun.tools.doclets.internal.toolkit.*; import java.util.*; /** * 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 code is not part of an API. * It is implementation that is subject to change. * Do not use it as an API * * @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_MEMBER_OPTIONAL = 5; public static final int ANNOTATION_TYPE_MEMBER_REQUIRED = 6; /** * The total number of member types is {@value}. */ public static final int NUM_MEMBER_TYPES = 7; 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; /** * Deprected members should be excluded or not? */ private final boolean nodepr; /** * Construct a VisibleMemberMap of the given type for the given * class. If nodepr is true, exclude the deprecated members from * the map. * * @param classdoc the class whose members are being mapped. * @param kind the kind of member that is being mapped. * @param nodepr if true, exclude the deprecated members from the map. */ public VisibleMemberMap(ClassDoc classdoc, int kind, boolean nodepr) { this.classdoc = classdoc; this.nodepr = nodepr; this.kind = kind; 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; } /** * Return the package private members inherited by the class. Only return * if parent is package private and not documented. * * @param configuation 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 (Iterator iter = visibleClasses.iterator(); iter.hasNext(); ) { ClassDoc currentClass = (ClassDoc) iter.next(); if (currentClass != classdoc && currentClass.isPackagePrivate() && !Util.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 configuation 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 = (ClassMembers)(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 (int i = 0; i < list.size(); i++) { ClassDoc cd = (ClassDoc)list.get(i); 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 (int i = 0; i < list.size(); i++) { Object key = getMemberKey((ProgramElementDoc)list.get(i)); Map memberLevelMap = (Map) memberNameMap.get(key); if (memberLevelMap == null) { memberLevelMap = new HashMap(); memberNameMap.put(key, memberLevelMap); } memberLevelMap.put(list.get(i), level); } } private void purgeMemberLevelMap(List list, String level) { for (int i = 0; i < list.size(); i++) { Object key = getMemberKey((ProgramElementDoc)list.get(i)); Map memberLevelMap = (Map) memberNameMap.get(key); if (level.equals(memberLevelMap.get(list.get(i)))) memberLevelMap.remove(list.get(i)); } } /** * 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 (Iterator iter = members.iterator(); iter.hasNext(); ) { MethodDoc member2 = (MethodDoc) iter.next(); if (Util.executableMembersEqual(member, member2)) { 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(((ClassMembers) 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), ((ClassMembers) 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 (int i = 0; i < interfaces.length; i++) { String locallevel = level + 1; ClassMembers cm = new ClassMembers(interfaces[i], 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 (int i = 0; i < cdmembers.size(); i++) { ProgramElementDoc pgmelem = (ProgramElementDoc)(cdmembers.get(i)); if (!found(members, pgmelem) && memberIsVisible(pgmelem) && !isOverridden(pgmelem, level)) { incllist.add(pgmelem); } } if (incllist.size() > 0) { noVisibleMembers = false; } members.addAll(incllist); fillMemberLevelMap(getClassMembers(fromClass, false), level); } /** * 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_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); break; default: members = new ProgramElementDoc[0]; } if (nodepr) { return Util.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 = ((AnnotationTypeDoc) doc).elements(); List targetMembers = new ArrayList(); for (int i = 0; i < members.length; i++) { if ((required && members[i].defaultValue() == null) || ((!required) && members[i].defaultValue() != null)){ targetMembers.add(members[i]); } } return (AnnotationTypeElementDoc[]) targetMembers.toArray(new AnnotationTypeElementDoc[]{}); } private boolean found(List list, ProgramElementDoc elem) { for (int i = 0; i < list.size(); i++) { ProgramElementDoc pgmelem = (ProgramElementDoc)list.get(i); if (Util.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; String mappedlevel = null; Iterator iterator = memberLevelMap.values().iterator(); while (iterator.hasNext()) { mappedlevel = (String)(iterator.next()); if (mappedlevel.equals(STARTLEVEL) || (level.startsWith(mappedlevel) && !level.equals(mappedlevel))) { return true; } } return false; } } /** * 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 (Iterator iter = memberNameMap.keySet().iterator(); iter.hasNext();) { Object key = iter.next(); 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; } } }