001    /*
002     * Copyright 1999-2008 Sun Microsystems, Inc.  All Rights Reserved.
003     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004     *
005     * This code is free software; you can redistribute it and/or modify it
006     * under the terms of the GNU General Public License version 2 only, as
007     * published by the Free Software Foundation.  Sun designates this
008     * particular file as subject to the "Classpath" exception as provided
009     * by Sun in the LICENSE file that accompanied this code.
010     *
011     * This code is distributed in the hope that it will be useful, but WITHOUT
012     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014     * version 2 for more details (a copy is included in the LICENSE file that
015     * accompanied this code).
016     *
017     * You should have received a copy of the GNU General Public License version
018     * 2 along with this work; if not, write to the Free Software Foundation,
019     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020     *
021     * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022     * CA 95054 USA or visit www.sun.com if you need additional information or
023     * have any questions.
024     */
025    
026    package com.sun.tools.doclets.internal.toolkit.util;
027    
028    import com.sun.javadoc.*;
029    import com.sun.tools.doclets.internal.toolkit.*;
030    import java.util.*;
031    
032    /**
033     * A data structure that encapsulates the visible members of a particular
034     * type for a given class tree.  To use this data structor, you must specify
035     * the type of member you are interested in (nested class, field, constructor
036     * or method) and the leaf of the class tree.  The data structure will map
037     * all visible members in the leaf and classes above the leaf in the tree.
038     *
039     * This code is not part of an API.
040     * It is implementation that is subject to change.
041     * Do not use it as an API
042     *
043     * @author Atul M Dambalkar
044     * @author Jamie Ho (rewrite)
045     */
046    public class VisibleMemberMap {
047    
048        private boolean noVisibleMembers = true;
049    
050        public static final int INNERCLASSES    = 0;
051        public static final int ENUM_CONSTANTS  = 1;
052        public static final int FIELDS          = 2;
053        public static final int CONSTRUCTORS    = 3;
054        public static final int METHODS         = 4;
055        public static final int ANNOTATION_TYPE_MEMBER_OPTIONAL = 5;
056        public static final int ANNOTATION_TYPE_MEMBER_REQUIRED = 6;
057    
058        /**
059         * The total number of member types is {@value}.
060         */
061        public static final int NUM_MEMBER_TYPES = 7;
062    
063        public static final String STARTLEVEL = "start";
064    
065        /**
066         * List of ClassDoc objects for which ClassMembers objects are built.
067         */
068        private final List<ClassDoc> visibleClasses = new ArrayList<ClassDoc>();
069    
070        /**
071         * Map for each member name on to a map which contains members with same
072         * name-signature. The mapped map will contain mapping for each MemberDoc
073         * onto it's respecive level string.
074         */
075        private final Map<Object,Map<ProgramElementDoc,String>> memberNameMap = new HashMap<Object,Map<ProgramElementDoc,String>>();
076    
077        /**
078         * Map of class and it's ClassMembers object.
079         */
080        private final Map<ClassDoc,ClassMembers> classMap = new HashMap<ClassDoc,ClassMembers>();
081    
082        /**
083         * Type whose visible members are requested.  This is the leaf of
084         * the class tree being mapped.
085         */
086        private final ClassDoc classdoc;
087    
088        /**
089         * Member kind: InnerClasses/Fields/Methods?
090         */
091        private final int kind;
092    
093        /**
094         * Deprected members should be excluded or not?
095         */
096        private final boolean nodepr;
097    
098        /**
099         * Construct a VisibleMemberMap of the given type for the given
100         * class.  If nodepr is true, exclude the deprecated members from
101         * the map.
102         *
103         * @param classdoc the class whose members are being mapped.
104         * @param kind the kind of member that is being mapped.
105         * @param nodepr if true, exclude the deprecated members from the map.
106         */
107        public VisibleMemberMap(ClassDoc classdoc, int kind, boolean nodepr) {
108            this.classdoc = classdoc;
109            this.nodepr = nodepr;
110            this.kind = kind;
111            new ClassMembers(classdoc, STARTLEVEL).build();
112        }
113    
114        /**
115         * Return the list of visible classes in this map.
116         *
117         * @return the list of visible classes in this map.
118         */
119        public List<ClassDoc> getVisibleClassesList() {
120            sort(visibleClasses);
121            return visibleClasses;
122        }
123    
124        /**
125         * Return the package private members inherited by the class.  Only return
126         * if parent is package private and not documented.
127         *
128         * @param configuation the current configuration of the doclet.
129         * @return the package private members inherited by the class.
130         */
131        private List<ProgramElementDoc> getInheritedPackagePrivateMethods(Configuration configuration) {
132            List<ProgramElementDoc> results = new ArrayList<ProgramElementDoc>();
133            for (Iterator<ClassDoc> iter = visibleClasses.iterator(); iter.hasNext(); ) {
134                ClassDoc currentClass = iter.next();
135                if (currentClass != classdoc &&
136                    currentClass.isPackagePrivate() &&
137                    !Util.isLinkable(currentClass, configuration)) {
138                    // Document these members in the child class because
139                    // the parent is inaccessible.
140                    results.addAll(getMembersFor(currentClass));
141                }
142            }
143            return results;
144        }
145    
146        /**
147         * Return the visible members of the class being mapped.  Also append at the
148         * end of the list members that are inherited by inaccessible parents. We
149         * document these members in the child because the parent is not documented.
150         *
151         * @param configuation the current configuration of the doclet.
152         */
153        public List<ProgramElementDoc> getLeafClassMembers(Configuration configuration) {
154            List<ProgramElementDoc> result = getMembersFor(classdoc);
155            result.addAll(getInheritedPackagePrivateMethods(configuration));
156            return result;
157        }
158    
159        /**
160         * Retrn the list of members for the given class.
161         *
162         * @param cd the class to retrieve the list of visible members for.
163         *
164         * @return the list of members for the given class.
165         */
166        public List<ProgramElementDoc> getMembersFor(ClassDoc cd) {
167            ClassMembers clmembers = classMap.get(cd);
168            if (clmembers == null) {
169                return new ArrayList<ProgramElementDoc>();
170            }
171            return clmembers.getMembers();
172        }
173    
174        /**
175         * Sort the given mixed list of classes and interfaces to a list of
176         * classes followed by interfaces traversed. Don't sort alphabetically.
177         */
178        private void sort(List<ClassDoc> list) {
179            List<ClassDoc> classes = new ArrayList<ClassDoc>();
180            List<ClassDoc> interfaces = new ArrayList<ClassDoc>();
181            for (int i = 0; i < list.size(); i++) {
182                ClassDoc cd = list.get(i);
183                if (cd.isClass()) {
184                    classes.add(cd);
185                } else {
186                    interfaces.add(cd);
187                }
188            }
189            list.clear();
190            list.addAll(classes);
191            list.addAll(interfaces);
192        }
193    
194        private void fillMemberLevelMap(List<ProgramElementDoc> list, String level) {
195            for (int i = 0; i < list.size(); i++) {
196                Object key = getMemberKey(list.get(i));
197                Map<ProgramElementDoc,String> memberLevelMap = memberNameMap.get(key);
198                if (memberLevelMap == null) {
199                    memberLevelMap = new HashMap<ProgramElementDoc,String>();
200                    memberNameMap.put(key, memberLevelMap);
201                }
202                memberLevelMap.put(list.get(i), level);
203            }
204        }
205    
206        private void purgeMemberLevelMap(List<ProgramElementDoc> list, String level) {
207            for (int i = 0; i < list.size(); i++) {
208                Object key = getMemberKey(list.get(i));
209                Map<ProgramElementDoc, String> memberLevelMap = memberNameMap.get(key);
210                if (level.equals(memberLevelMap.get(list.get(i))))
211                    memberLevelMap.remove(list.get(i));
212            }
213        }
214    
215        /**
216         * Represents a class member.  We should be able to just use a
217         * ProgramElementDoc instead of this class, but that doesn't take
218         * type variables in consideration when comparing.
219         */
220        private class ClassMember {
221            private Set<ProgramElementDoc> members;
222    
223            public ClassMember(ProgramElementDoc programElementDoc) {
224                members = new HashSet<ProgramElementDoc>();
225                members.add(programElementDoc);
226            }
227    
228            public void addMember(ProgramElementDoc programElementDoc) {
229                members.add(programElementDoc);
230            }
231    
232            public boolean isEqual(MethodDoc member) {
233                for (Iterator<ProgramElementDoc> iter = members.iterator(); iter.hasNext(); ) {
234                    MethodDoc member2 = (MethodDoc) iter.next();
235                    if (Util.executableMembersEqual(member, member2)) {
236                        members.add(member);
237                            return true;
238                    }
239                }
240                return false;
241            }
242        }
243    
244        /**
245         * A data structure that represents the class members for
246         * a visible class.
247         */
248        private class ClassMembers {
249    
250            /**
251             * The mapping class, whose inherited members are put in the
252             * {@link #members} list.
253             */
254            private ClassDoc mappingClass;
255    
256            /**
257             * List of inherited members from the mapping class.
258             */
259            private List<ProgramElementDoc> members = new ArrayList<ProgramElementDoc>();
260    
261            /**
262             * Level/Depth of inheritance.
263             */
264            private String level;
265    
266            /**
267             * Return list of inherited members from mapping class.
268             *
269             * @return List Inherited members.
270             */
271            public List<ProgramElementDoc> getMembers() {
272                return members;
273            }
274    
275            private ClassMembers(ClassDoc mappingClass, String level) {
276                this.mappingClass = mappingClass;
277                this.level = level;
278                if (classMap.containsKey(mappingClass) &&
279                            level.startsWith(classMap.get(mappingClass).level)) {
280                    //Remove lower level class so that it can be replaced with
281                    //same class found at higher level.
282                    purgeMemberLevelMap(getClassMembers(mappingClass, false),
283                        classMap.get(mappingClass).level);
284                    classMap.remove(mappingClass);
285                    visibleClasses.remove(mappingClass);
286                }
287                if (!classMap.containsKey(mappingClass)) {
288                    classMap.put(mappingClass, this);
289                    visibleClasses.add(mappingClass);
290                }
291    
292            }
293    
294            private void build() {
295                if (kind == CONSTRUCTORS) {
296                    addMembers(mappingClass);
297                } else {
298                    mapClass();
299                }
300            }
301    
302            private void mapClass() {
303                addMembers(mappingClass);
304                ClassDoc[] interfaces = mappingClass.interfaces();
305                for (int i = 0; i < interfaces.length; i++) {
306                    String locallevel = level + 1;
307                    ClassMembers cm = new ClassMembers(interfaces[i], locallevel);
308                    cm.mapClass();
309                }
310                if (mappingClass.isClass()) {
311                    ClassDoc superclass = mappingClass.superclass();
312                    if (!(superclass == null || mappingClass.equals(superclass))) {
313                        ClassMembers cm = new ClassMembers(superclass,
314                                                           level + "c");
315                        cm.mapClass();
316                    }
317                }
318            }
319    
320            /**
321             * Get all the valid members from the mapping class. Get the list of
322             * members for the class to be included into(ctii), also get the level
323             * string for ctii. If mapping class member is not already in the
324             * inherited member list and if it is visible in the ctii and not
325             * overridden, put such a member in the inherited member list.
326             * Adjust member-level-map, class-map.
327             */
328            private void addMembers(ClassDoc fromClass) {
329                List<ProgramElementDoc> cdmembers = getClassMembers(fromClass, true);
330                List<ProgramElementDoc> incllist = new ArrayList<ProgramElementDoc>();
331                for (int i = 0; i < cdmembers.size(); i++) {
332                    ProgramElementDoc pgmelem = cdmembers.get(i);
333                    if (!found(members, pgmelem) &&
334                        memberIsVisible(pgmelem) &&
335                        !isOverridden(pgmelem, level)) {
336                        incllist.add(pgmelem);
337                    }
338                }
339                if (incllist.size() > 0) {
340                    noVisibleMembers = false;
341                }
342                members.addAll(incllist);
343                fillMemberLevelMap(getClassMembers(fromClass, false), level);
344            }
345    
346            /**
347             * Is given doc item visible in given classdoc in terms fo inheritance?
348             * The given doc item is visible in the given classdoc if it is public
349             * or protected and if it is package-private if it's containing class
350             * is in the same package as the given classdoc.
351             */
352            private boolean memberIsVisible(ProgramElementDoc pgmdoc) {
353                if (pgmdoc.containingClass().equals(classdoc)) {
354                    //Member is in class that we are finding visible members for.
355                    //Of course it is visible.
356                    return true;
357                } else if (pgmdoc.isPrivate()) {
358                    //Member is in super class or implemented interface.
359                    //Private, so not inherited.
360                    return false;
361                } else if (pgmdoc.isPackagePrivate()) {
362                    //Member is package private.  Only return true if its class is in
363                    //same package.
364                    return pgmdoc.containingClass().containingPackage().equals(
365                        classdoc.containingPackage());
366                } else {
367                    //Public members are always inherited.
368                    return true;
369                }
370            }
371    
372            /**
373             * Return all available class members.
374             */
375            private List<ProgramElementDoc> getClassMembers(ClassDoc cd, boolean filter) {
376                if (cd.isEnum() && kind == CONSTRUCTORS) {
377                    //If any of these rules are hit, return empty array because
378                    //we don't document these members ever.
379                    return Arrays.asList(new ProgramElementDoc[] {});
380                }
381                ProgramElementDoc[] members = null;
382                switch (kind) {
383                    case ANNOTATION_TYPE_MEMBER_OPTIONAL:
384                        members = cd.isAnnotationType() ?
385                            filter((AnnotationTypeDoc) cd, false) :
386                            new AnnotationTypeElementDoc[] {};
387                        break;
388                    case ANNOTATION_TYPE_MEMBER_REQUIRED:
389                        members = cd.isAnnotationType() ?
390                            filter((AnnotationTypeDoc) cd, true) :
391                            new AnnotationTypeElementDoc[] {};
392                        break;
393                    case INNERCLASSES:
394                        members = cd.innerClasses(filter);
395                        break;
396                    case ENUM_CONSTANTS:
397                        members = cd.enumConstants();
398                        break;
399                    case FIELDS:
400                        members = cd.fields(filter);
401                        break;
402                    case CONSTRUCTORS:
403                        members = cd.constructors();
404                        break;
405                    case METHODS:
406                        members = cd.methods(filter);
407                        break;
408                    default:
409                        members = new ProgramElementDoc[0];
410                }
411                if (nodepr) {
412                    return Util.excludeDeprecatedMembersAsList(members);
413                }
414                return Arrays.asList(members);
415            }
416    
417            /**
418             * Filter the annotation type members and return either the required
419             * members or the optional members, depending on the value of the
420             * required parameter.
421             *
422             * @param doc The annotation type to process.
423             * @param required
424             * @return the annotation type members and return either the required
425             * members or the optional members, depending on the value of the
426             * required parameter.
427             */
428            private AnnotationTypeElementDoc[] filter(AnnotationTypeDoc doc,
429                boolean required) {
430                AnnotationTypeElementDoc[] members = doc.elements();
431                List<AnnotationTypeElementDoc> targetMembers = new ArrayList<AnnotationTypeElementDoc>();
432                for (int i = 0; i < members.length; i++) {
433                    if ((required && members[i].defaultValue() == null) ||
434                         ((!required) && members[i].defaultValue() != null)){
435                        targetMembers.add(members[i]);
436                    }
437                }
438                return targetMembers.toArray(new AnnotationTypeElementDoc[]{});
439            }
440    
441            private boolean found(List<ProgramElementDoc> list, ProgramElementDoc elem) {
442                for (int i = 0; i < list.size(); i++) {
443                    ProgramElementDoc pgmelem = list.get(i);
444                    if (Util.matches(pgmelem, elem)) {
445                        return true;
446                    }
447                }
448                return false;
449            }
450    
451    
452            /**
453             * Is member overridden? The member is overridden if it is found in the
454             * same level hierarchy e.g. member at level "11" overrides member at
455             * level "111".
456             */
457            private boolean isOverridden(ProgramElementDoc pgmdoc, String level) {
458                Map<?,String> memberLevelMap = (Map<?,String>) memberNameMap.get(getMemberKey(pgmdoc));
459                if (memberLevelMap == null)
460                    return false;
461                String mappedlevel = null;
462                Iterator<String> iterator = memberLevelMap.values().iterator();
463                while (iterator.hasNext()) {
464                    mappedlevel = iterator.next();
465                    if (mappedlevel.equals(STARTLEVEL) ||
466                        (level.startsWith(mappedlevel) &&
467                         !level.equals(mappedlevel))) {
468                        return true;
469                    }
470                }
471                return false;
472            }
473        }
474    
475        /**
476         * Return true if this map has no visible members.
477         *
478         * @return true if this map has no visible members.
479         */
480        public boolean noVisibleMembers() {
481            return noVisibleMembers;
482        }
483    
484        private ClassMember getClassMember(MethodDoc member) {
485            for (Iterator<?> iter = memberNameMap.keySet().iterator(); iter.hasNext();) {
486                Object key = iter.next();
487                if (key instanceof String) {
488                    continue;
489                } else if (((ClassMember) key).isEqual(member)) {
490                    return (ClassMember) key;
491                }
492            }
493            return new ClassMember(member);
494        }
495    
496        /**
497         * Return the key to the member map for the given member.
498         */
499        private Object getMemberKey(ProgramElementDoc doc) {
500            if (doc.isConstructor()) {
501                return doc.name() + ((ExecutableMemberDoc)doc).signature();
502            } else if (doc.isMethod()) {
503                return getClassMember((MethodDoc) doc);
504            } else if (doc.isField() || doc.isEnumConstant() || doc.isAnnotationTypeElement()) {
505                return doc.name();
506            } else { // it's a class or interface
507                String classOrIntName = doc.name();
508                //Strip off the containing class name because we only want the member name.
509                classOrIntName = classOrIntName.indexOf('.') != 0 ? classOrIntName.substring(classOrIntName.lastIndexOf('.'), classOrIntName.length()) : classOrIntName;
510                return "clint" + classOrIntName;
511            }
512        }
513    }