1 /*
   2  * Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.doclets.internal.toolkit.util;
  27 
  28 import java.util.*;
  29 import java.util.regex.Pattern;
  30 
  31 import com.sun.javadoc.*;
  32 import com.sun.tools.doclets.internal.toolkit.*;
  33 
  34 /**
  35  * A data structure that encapsulates the visible members of a particular
  36  * type for a given class tree.  To use this data structor, you must specify
  37  * the type of member you are interested in (nested class, field, constructor
  38  * or method) and the leaf of the class tree.  The data structure will map
  39  * all visible members in the leaf and classes above the leaf in the tree.
  40  *
  41  *  <p><b>This is NOT part of any supported API.
  42  *  If you write code that depends on this, you do so at your own risk.
  43  *  This code and its internal interfaces are subject to change or
  44  *  deletion without notice.</b>
  45  *
  46  * @author Atul M Dambalkar
  47  * @author Jamie Ho (rewrite)
  48  */
  49 public class VisibleMemberMap {
  50 
  51     private boolean noVisibleMembers = true;
  52 
  53     public static final int INNERCLASSES    = 0;
  54     public static final int ENUM_CONSTANTS  = 1;
  55     public static final int FIELDS          = 2;
  56     public static final int CONSTRUCTORS    = 3;
  57     public static final int METHODS         = 4;
  58     public static final int ANNOTATION_TYPE_FIELDS = 5;
  59     public static final int ANNOTATION_TYPE_MEMBER_OPTIONAL = 6;
  60     public static final int ANNOTATION_TYPE_MEMBER_REQUIRED = 7;
  61     public static final int PROPERTIES      = 8;
  62 
  63     /**
  64      * The total number of member types is {@value}.
  65      */
  66     public static final int NUM_MEMBER_TYPES = 9;
  67 
  68     public static final String STARTLEVEL = "start";
  69 
  70     /**
  71      * List of ClassDoc objects for which ClassMembers objects are built.
  72      */
  73     private final List<ClassDoc> visibleClasses = new ArrayList<ClassDoc>();
  74 
  75     /**
  76      * Map for each member name on to a map which contains members with same
  77      * name-signature. The mapped map will contain mapping for each MemberDoc
  78      * onto it's respecive level string.
  79      */
  80     private final Map<Object,Map<ProgramElementDoc,String>> memberNameMap = new HashMap<Object,Map<ProgramElementDoc,String>>();
  81 
  82     /**
  83      * Map of class and it's ClassMembers object.
  84      */
  85     private final Map<ClassDoc,ClassMembers> classMap = new HashMap<ClassDoc,ClassMembers>();
  86 
  87     /**
  88      * Type whose visible members are requested.  This is the leaf of
  89      * the class tree being mapped.
  90      */
  91     private final ClassDoc classdoc;
  92 
  93     /**
  94      * Member kind: InnerClasses/Fields/Methods?
  95      */
  96     private final int kind;
  97 
  98     /**
  99      * The configuration this VisibleMemberMap was created with.
 100      */
 101     private final Configuration configuration;
 102 
 103     private static final Map<ClassDoc, ProgramElementDoc[]> propertiesCache =
 104             new HashMap<ClassDoc, ProgramElementDoc[]>();
 105     private static final Map<ProgramElementDoc, ProgramElementDoc> classPropertiesMap =
 106             new HashMap<ProgramElementDoc, ProgramElementDoc>();
 107     private static final Map<ProgramElementDoc, GetterSetter> getterSetterMap =
 108             new HashMap<ProgramElementDoc, GetterSetter>();
 109 
 110     /**
 111      * Construct a VisibleMemberMap of the given type for the given
 112      * class.
 113      *
 114      * @param classdoc the class whose members are being mapped.
 115      * @param kind the kind of member that is being mapped.
 116      * @param configuration the configuration to use to construct this
 117      * VisibleMemberMap. If the field configuration.nodeprecated is true the
 118      * deprecated members are excluded from the map. If the field
 119      * configuration.javafx is true the JavaFX features are used.
 120      */
 121     public VisibleMemberMap(ClassDoc classdoc,
 122                             int kind,
 123                             Configuration configuration) {
 124         this.classdoc = classdoc;
 125         this.kind = kind;
 126         this.configuration = configuration;
 127         new ClassMembers(classdoc, STARTLEVEL).build();
 128     }
 129 
 130     /**
 131      * Return the list of visible classes in this map.
 132      *
 133      * @return the list of visible classes in this map.
 134      */
 135     public List<ClassDoc> getVisibleClassesList() {
 136         sort(visibleClasses);
 137         return visibleClasses;
 138     }
 139 
 140     /**
 141      * Returns the property field documentation belonging to the given member.
 142      * @param ped the member for which the property documentation is needed.
 143      * @return the property field documentation, null if there is none.
 144      */
 145     public ProgramElementDoc getPropertyMemberDoc(ProgramElementDoc ped) {
 146         return classPropertiesMap.get(ped);
 147     }
 148 
 149     /**
 150      * Returns the getter documentation belonging to the given property method.
 151      * @param propertyMethod the method for which the getter is needed.
 152      * @return the getter documentation, null if there is none.
 153      */
 154     public ProgramElementDoc getGetterForProperty(ProgramElementDoc propertyMethod) {
 155         return getterSetterMap.get(propertyMethod).getGetter();
 156     }
 157 
 158     /**
 159      * Returns the setter documentation belonging to the given property method.
 160      * @param propertyMethod the method for which the setter is needed.
 161      * @return the setter documentation, null if there is none.
 162      */
 163     public ProgramElementDoc getSetterForProperty(ProgramElementDoc propertyMethod) {
 164         return getterSetterMap.get(propertyMethod).getSetter();
 165     }
 166 
 167     /**
 168      * Return the package private members inherited by the class.  Only return
 169      * if parent is package private and not documented.
 170      *
 171      * @param configuration the current configuration of the doclet.
 172      * @return the package private members inherited by the class.
 173      */
 174     private List<ProgramElementDoc> getInheritedPackagePrivateMethods(Configuration configuration) {
 175         List<ProgramElementDoc> results = new ArrayList<ProgramElementDoc>();
 176         for (Iterator<ClassDoc> iter = visibleClasses.iterator(); iter.hasNext(); ) {
 177             ClassDoc currentClass = iter.next();
 178             if (currentClass != classdoc &&
 179                 currentClass.isPackagePrivate() &&
 180                 !Util.isLinkable(currentClass, configuration)) {
 181                 // Document these members in the child class because
 182                 // the parent is inaccessible.
 183                 results.addAll(getMembersFor(currentClass));
 184             }
 185         }
 186         return results;
 187     }
 188 
 189     /**
 190      * Return the visible members of the class being mapped.  Also append at the
 191      * end of the list members that are inherited by inaccessible parents. We
 192      * document these members in the child because the parent is not documented.
 193      *
 194      * @param configuration the current configuration of the doclet.
 195      */
 196     public List<ProgramElementDoc> getLeafClassMembers(Configuration configuration) {
 197         List<ProgramElementDoc> result = getMembersFor(classdoc);
 198         result.addAll(getInheritedPackagePrivateMethods(configuration));
 199         return result;
 200     }
 201 
 202     /**
 203      * Retrn the list of members for the given class.
 204      *
 205      * @param cd the class to retrieve the list of visible members for.
 206      *
 207      * @return the list of members for the given class.
 208      */
 209     public List<ProgramElementDoc> getMembersFor(ClassDoc cd) {
 210         ClassMembers clmembers = classMap.get(cd);
 211         if (clmembers == null) {
 212             return new ArrayList<ProgramElementDoc>();
 213         }
 214         return clmembers.getMembers();
 215     }
 216 
 217     /**
 218      * Sort the given mixed list of classes and interfaces to a list of
 219      * classes followed by interfaces traversed. Don't sort alphabetically.
 220      */
 221     private void sort(List<ClassDoc> list) {
 222         List<ClassDoc> classes = new ArrayList<ClassDoc>();
 223         List<ClassDoc> interfaces = new ArrayList<ClassDoc>();
 224         for (int i = 0; i < list.size(); i++) {
 225             ClassDoc cd = list.get(i);
 226             if (cd.isClass()) {
 227                 classes.add(cd);
 228             } else {
 229                 interfaces.add(cd);
 230             }
 231         }
 232         list.clear();
 233         list.addAll(classes);
 234         list.addAll(interfaces);
 235     }
 236 
 237     private void fillMemberLevelMap(List<ProgramElementDoc> list, String level) {
 238         for (int i = 0; i < list.size(); i++) {
 239             Object key = getMemberKey(list.get(i));
 240             Map<ProgramElementDoc,String> memberLevelMap = memberNameMap.get(key);
 241             if (memberLevelMap == null) {
 242                 memberLevelMap = new HashMap<ProgramElementDoc,String>();
 243                 memberNameMap.put(key, memberLevelMap);
 244             }
 245             memberLevelMap.put(list.get(i), level);
 246         }
 247     }
 248 
 249     private void purgeMemberLevelMap(List<ProgramElementDoc> list, String level) {
 250         for (int i = 0; i < list.size(); i++) {
 251             Object key = getMemberKey(list.get(i));
 252             Map<ProgramElementDoc, String> memberLevelMap = memberNameMap.get(key);
 253             if (memberLevelMap != null && level.equals(memberLevelMap.get(list.get(i))))
 254                 memberLevelMap.remove(list.get(i));
 255         }
 256     }
 257 
 258     /**
 259      * Represents a class member.  We should be able to just use a
 260      * ProgramElementDoc instead of this class, but that doesn't take
 261      * type variables in consideration when comparing.
 262      */
 263     private class ClassMember {
 264         private Set<ProgramElementDoc> members;
 265 
 266         public ClassMember(ProgramElementDoc programElementDoc) {
 267             members = new HashSet<ProgramElementDoc>();
 268             members.add(programElementDoc);
 269         }
 270 
 271         public void addMember(ProgramElementDoc programElementDoc) {
 272             members.add(programElementDoc);
 273         }
 274 
 275         public boolean isEqual(MethodDoc member) {
 276             for (Iterator<ProgramElementDoc> iter = members.iterator(); iter.hasNext(); ) {
 277                 MethodDoc member2 = (MethodDoc) iter.next();
 278                 if (Util.executableMembersEqual(member, member2)) {
 279                     members.add(member);
 280                         return true;
 281                 }
 282             }
 283             return false;
 284         }
 285     }
 286 
 287     /**
 288      * A data structure that represents the class members for
 289      * a visible class.
 290      */
 291     private class ClassMembers {
 292 
 293         /**
 294          * The mapping class, whose inherited members are put in the
 295          * {@link #members} list.
 296          */
 297         private ClassDoc mappingClass;
 298 
 299         /**
 300          * List of inherited members from the mapping class.
 301          */
 302         private List<ProgramElementDoc> members = new ArrayList<ProgramElementDoc>();
 303 
 304         /**
 305          * Level/Depth of inheritance.
 306          */
 307         private String level;
 308 
 309         /**
 310          * Return list of inherited members from mapping class.
 311          *
 312          * @return List Inherited members.
 313          */
 314         public List<ProgramElementDoc> getMembers() {
 315             return members;
 316         }
 317 
 318         private ClassMembers(ClassDoc mappingClass, String level) {
 319             this.mappingClass = mappingClass;
 320             this.level = level;
 321             if (classMap.containsKey(mappingClass) &&
 322                         level.startsWith(classMap.get(mappingClass).level)) {
 323                 //Remove lower level class so that it can be replaced with
 324                 //same class found at higher level.
 325                 purgeMemberLevelMap(getClassMembers(mappingClass, false),
 326                     classMap.get(mappingClass).level);
 327                 classMap.remove(mappingClass);
 328                 visibleClasses.remove(mappingClass);
 329             }
 330             if (!classMap.containsKey(mappingClass)) {
 331                 classMap.put(mappingClass, this);
 332                 visibleClasses.add(mappingClass);
 333             }
 334 
 335         }
 336 
 337         private void build() {
 338             if (kind == CONSTRUCTORS) {
 339                 addMembers(mappingClass);
 340             } else {
 341                 mapClass();
 342             }
 343         }
 344 
 345         private void mapClass() {
 346             addMembers(mappingClass);
 347             ClassDoc[] interfaces = mappingClass.interfaces();
 348             for (int i = 0; i < interfaces.length; i++) {
 349                 String locallevel = level + 1;
 350                 ClassMembers cm = new ClassMembers(interfaces[i], locallevel);
 351                 cm.mapClass();
 352             }
 353             if (mappingClass.isClass()) {
 354                 ClassDoc superclass = mappingClass.superclass();
 355                 if (!(superclass == null || mappingClass.equals(superclass))) {
 356                     ClassMembers cm = new ClassMembers(superclass,
 357                                                        level + "c");
 358                     cm.mapClass();
 359                 }
 360             }
 361         }
 362 
 363         /**
 364          * Get all the valid members from the mapping class. Get the list of
 365          * members for the class to be included into(ctii), also get the level
 366          * string for ctii. If mapping class member is not already in the
 367          * inherited member list and if it is visible in the ctii and not
 368          * overridden, put such a member in the inherited member list.
 369          * Adjust member-level-map, class-map.
 370          */
 371         private void addMembers(ClassDoc fromClass) {
 372             List<ProgramElementDoc> cdmembers = getClassMembers(fromClass, true);
 373             List<ProgramElementDoc> incllist = new ArrayList<ProgramElementDoc>();
 374             for (int i = 0; i < cdmembers.size(); i++) {
 375                 ProgramElementDoc pgmelem = cdmembers.get(i);
 376                 if (!found(members, pgmelem) &&
 377                     memberIsVisible(pgmelem) &&
 378                     !isOverridden(pgmelem, level) &&
 379                     !isTreatedAsPrivate(pgmelem)) {
 380                         incllist.add(pgmelem);
 381                 }
 382             }
 383             if (incllist.size() > 0) {
 384                 noVisibleMembers = false;
 385             }
 386             members.addAll(incllist);
 387             fillMemberLevelMap(getClassMembers(fromClass, false), level);
 388         }
 389 
 390         private boolean isTreatedAsPrivate(ProgramElementDoc pgmelem) {
 391             if (!configuration.javafx) {
 392                 return false;
 393             }
 394 
 395             Tag[] aspTags = pgmelem.tags("@treatAsPrivate");
 396             boolean result = (aspTags != null) && (aspTags.length > 0);
 397             return result;
 398         }
 399 
 400         /**
 401          * Is given doc item visible in given classdoc in terms fo inheritance?
 402          * The given doc item is visible in the given classdoc if it is public
 403          * or protected and if it is package-private if it's containing class
 404          * is in the same package as the given classdoc.
 405          */
 406         private boolean memberIsVisible(ProgramElementDoc pgmdoc) {
 407             if (pgmdoc.containingClass().equals(classdoc)) {
 408                 //Member is in class that we are finding visible members for.
 409                 //Of course it is visible.
 410                 return true;
 411             } else if (pgmdoc.isPrivate()) {
 412                 //Member is in super class or implemented interface.
 413                 //Private, so not inherited.
 414                 return false;
 415             } else if (pgmdoc.isPackagePrivate()) {
 416                 //Member is package private.  Only return true if its class is in
 417                 //same package.
 418                 return pgmdoc.containingClass().containingPackage().equals(
 419                     classdoc.containingPackage());
 420             } else {
 421                 //Public members are always inherited.
 422                 return true;
 423             }
 424         }
 425 
 426         /**
 427          * Return all available class members.
 428          */
 429         private List<ProgramElementDoc> getClassMembers(ClassDoc cd, boolean filter) {
 430             if (cd.isEnum() && kind == CONSTRUCTORS) {
 431                 //If any of these rules are hit, return empty array because
 432                 //we don't document these members ever.
 433                 return Arrays.asList(new ProgramElementDoc[] {});
 434             }
 435             ProgramElementDoc[] members = null;
 436             switch (kind) {
 437                 case ANNOTATION_TYPE_FIELDS:
 438                     members = cd.fields(filter);
 439                     break;
 440                 case ANNOTATION_TYPE_MEMBER_OPTIONAL:
 441                     members = cd.isAnnotationType() ?
 442                         filter((AnnotationTypeDoc) cd, false) :
 443                         new AnnotationTypeElementDoc[] {};
 444                     break;
 445                 case ANNOTATION_TYPE_MEMBER_REQUIRED:
 446                     members = cd.isAnnotationType() ?
 447                         filter((AnnotationTypeDoc) cd, true) :
 448                         new AnnotationTypeElementDoc[] {};
 449                     break;
 450                 case INNERCLASSES:
 451                     members = cd.innerClasses(filter);
 452                     break;
 453                 case ENUM_CONSTANTS:
 454                     members = cd.enumConstants();
 455                     break;
 456                 case FIELDS:
 457                     members = cd.fields(filter);
 458                     break;
 459                 case CONSTRUCTORS:
 460                     members = cd.constructors();
 461                     break;
 462                 case METHODS:
 463                     members = cd.methods(filter);
 464                     checkOnPropertiesTags((MethodDoc[])members);
 465                     break;
 466                 case PROPERTIES:
 467                     members = properties(cd, filter);
 468                     break;
 469                 default:
 470                     members = new ProgramElementDoc[0];
 471             }
 472             // Deprected members should be excluded or not?
 473             if (configuration.nodeprecated) {
 474                 return Util.excludeDeprecatedMembersAsList(members);
 475             }
 476             return Arrays.asList(members);
 477         }
 478 
 479         /**
 480          * Filter the annotation type members and return either the required
 481          * members or the optional members, depending on the value of the
 482          * required parameter.
 483          *
 484          * @param doc The annotation type to process.
 485          * @param required
 486          * @return the annotation type members and return either the required
 487          * members or the optional members, depending on the value of the
 488          * required parameter.
 489          */
 490         private AnnotationTypeElementDoc[] filter(AnnotationTypeDoc doc,
 491             boolean required) {
 492             AnnotationTypeElementDoc[] members = doc.elements();
 493             List<AnnotationTypeElementDoc> targetMembers = new ArrayList<AnnotationTypeElementDoc>();
 494             for (int i = 0; i < members.length; i++) {
 495                 if ((required && members[i].defaultValue() == null) ||
 496                      ((!required) && members[i].defaultValue() != null)){
 497                     targetMembers.add(members[i]);
 498                 }
 499             }
 500             return targetMembers.toArray(new AnnotationTypeElementDoc[]{});
 501         }
 502 
 503         private boolean found(List<ProgramElementDoc> list, ProgramElementDoc elem) {
 504             for (int i = 0; i < list.size(); i++) {
 505                 ProgramElementDoc pgmelem = list.get(i);
 506                 if (Util.matches(pgmelem, elem)) {
 507                     return true;
 508                 }
 509             }
 510             return false;
 511         }
 512 
 513 
 514         /**
 515          * Is member overridden? The member is overridden if it is found in the
 516          * same level hierarchy e.g. member at level "11" overrides member at
 517          * level "111".
 518          */
 519         private boolean isOverridden(ProgramElementDoc pgmdoc, String level) {
 520             Map<?,String> memberLevelMap = (Map<?,String>) memberNameMap.get(getMemberKey(pgmdoc));
 521             if (memberLevelMap == null)
 522                 return false;
 523             String mappedlevel = null;
 524             Iterator<String> iterator = memberLevelMap.values().iterator();
 525             while (iterator.hasNext()) {
 526                 mappedlevel = iterator.next();
 527                 if (mappedlevel.equals(STARTLEVEL) ||
 528                     (level.startsWith(mappedlevel) &&
 529                      !level.equals(mappedlevel))) {
 530                     return true;
 531                 }
 532             }
 533             return false;
 534         }
 535 
 536         private ProgramElementDoc[] properties(final ClassDoc cd, final boolean filter) {
 537             final MethodDoc[] allMethods = cd.methods(filter);
 538             final FieldDoc[] allFields = cd.fields(false);
 539 
 540             if (propertiesCache.containsKey(cd)) {
 541                 return propertiesCache.get(cd);
 542             }
 543 
 544             final List<MethodDoc> result = new ArrayList<MethodDoc>();
 545 
 546             for (final MethodDoc propertyMethod : allMethods) {
 547 
 548                 if (!isPropertyMethod(propertyMethod)) {
 549                     continue;
 550                 }
 551 
 552                 final MethodDoc getter = getterForField(allMethods, propertyMethod);
 553                 final MethodDoc setter = setterForField(allMethods, propertyMethod);
 554                 final FieldDoc field = fieldForProperty(allFields, propertyMethod);
 555 
 556                 addToPropertiesMap(setter, getter, propertyMethod, field);
 557                 getterSetterMap.put(propertyMethod, new GetterSetter(getter, setter));
 558                 result.add(propertyMethod);
 559             }
 560             final ProgramElementDoc[] resultAray =
 561                     result.toArray(new ProgramElementDoc[result.size()]);
 562             propertiesCache.put(cd, resultAray);
 563             return resultAray;
 564         }
 565 
 566         private void addToPropertiesMap(MethodDoc setter,
 567                                         MethodDoc getter,
 568                                         MethodDoc propertyMethod,
 569                                         FieldDoc field) {
 570             if ((field == null)
 571                     || (field.getRawCommentText() == null)
 572                     || field.getRawCommentText().length() == 0) {
 573                 addToPropertiesMap(setter, propertyMethod);
 574                 addToPropertiesMap(getter, propertyMethod);
 575                 addToPropertiesMap(propertyMethod, propertyMethod);
 576             } else {
 577                 addToPropertiesMap(getter, field);
 578                 addToPropertiesMap(setter, field);
 579                 addToPropertiesMap(propertyMethod, field);
 580             }
 581         }
 582 
 583         private void addToPropertiesMap(ProgramElementDoc propertyMethod,
 584                                         ProgramElementDoc commentSource) {
 585             if (null == propertyMethod || null == commentSource) {
 586                 return;
 587             }
 588             final String methodRawCommentText = propertyMethod.getRawCommentText();
 589 
 590             /* The second condition is required for the property buckets. In
 591              * this case the comment is at the property method (not at the field)
 592              * and it needs to be listed in the map.
 593              */
 594             if ((null == methodRawCommentText || 0 == methodRawCommentText.length())
 595                     || propertyMethod.equals(commentSource)) {
 596                 classPropertiesMap.put(propertyMethod, commentSource);
 597             }
 598         }
 599 
 600         private MethodDoc getterForField(MethodDoc[] methods,
 601                                          MethodDoc propertyMethod) {
 602             final String propertyMethodName = propertyMethod.name();
 603             final String fieldName =
 604                     propertyMethodName.substring(0,
 605                             propertyMethodName.lastIndexOf("Property"));
 606             final String fieldNameUppercased =
 607                     "" + Character.toUpperCase(fieldName.charAt(0))
 608                                             + fieldName.substring(1);
 609             final String getterNamePattern;
 610             final String fieldTypeName = propertyMethod.returnType().toString();
 611             if ("boolean".equals(fieldTypeName)
 612                     || fieldTypeName.endsWith("BooleanProperty")) {
 613                 getterNamePattern = "(is|get)" + fieldNameUppercased;
 614             } else {
 615                 getterNamePattern = "get" + fieldNameUppercased;
 616             }
 617 
 618             for (MethodDoc methodDoc : methods) {
 619                 if (Pattern.matches(getterNamePattern, methodDoc.name())) {
 620                     if (0 == methodDoc.parameters().length
 621                             && (methodDoc.isPublic() || methodDoc.isProtected())) {
 622                         return methodDoc;
 623                     }
 624                 }
 625             }
 626             return null;
 627         }
 628 
 629         private MethodDoc setterForField(MethodDoc[] methods,
 630                                          MethodDoc propertyMethod) {
 631             final String propertyMethodName = propertyMethod.name();
 632             final String fieldName =
 633                     propertyMethodName.substring(0,
 634                             propertyMethodName.lastIndexOf("Property"));
 635             final String fieldNameUppercased =
 636                     "" + Character.toUpperCase(fieldName.charAt(0))
 637                                              + fieldName.substring(1);
 638             final String setter = "set" + fieldNameUppercased;
 639 
 640             for (MethodDoc methodDoc : methods) {
 641                 if (setter.equals(methodDoc.name())) {
 642                     if (1 == methodDoc.parameters().length
 643                             && "void".equals(methodDoc.returnType().simpleTypeName())
 644                             && (methodDoc.isPublic() || methodDoc.isProtected())) {
 645                         return methodDoc;
 646                     }
 647                 }
 648             }
 649             return null;
 650         }
 651 
 652         private FieldDoc fieldForProperty(FieldDoc[] fields, MethodDoc property) {
 653 
 654             for (FieldDoc field : fields) {
 655                 final String fieldName = field.name();
 656                 final String propertyName = fieldName + "Property";
 657                 if (propertyName.equals(property.name())) {
 658                     return field;
 659                 }
 660             }
 661             return null;
 662         }
 663 
 664         // properties aren't named setA* or getA*
 665         private final Pattern pattern = Pattern.compile("[sg]et\\p{Upper}.*");
 666         private boolean isPropertyMethod(MethodDoc method) {
 667             if (!configuration.javafx) {
 668                return false;
 669             }
 670             if (!method.name().endsWith("Property")) {
 671                 return false;
 672             }
 673 
 674             if (! memberIsVisible(method)) {
 675                 return false;
 676             }
 677 
 678             if (pattern.matcher(method.name()).matches()) {
 679                 return false;
 680             }
 681             if (method.typeParameters().length > 0) {
 682                 return false;
 683             }
 684             return 0 == method.parameters().length
 685                     && !"void".equals(method.returnType().simpleTypeName());
 686         }
 687 
 688         private void checkOnPropertiesTags(MethodDoc[] members) {
 689             for (MethodDoc methodDoc: members) {
 690                 if (methodDoc.isIncluded()) {
 691                     for (Tag tag: methodDoc.tags()) {
 692                         String tagName = tag.name();
 693                         if (tagName.equals("@propertySetter")
 694                                 || tagName.equals("@propertyGetter")
 695                                 || tagName.equals("@propertyDescription")) {
 696                             if (!isPropertyGetterOrSetter(members, methodDoc)) {
 697                                 configuration.message.warning(tag.position(),
 698                                         "doclet.javafx_tag_misuse");
 699                             }
 700                             break;
 701                         }
 702                     }
 703                 }
 704             }
 705         }
 706 
 707         private boolean isPropertyGetterOrSetter(MethodDoc[] members,
 708                                                  MethodDoc methodDoc) {
 709             boolean found = false;
 710             String propertyName = Util.propertyNameFromMethodName(configuration, methodDoc.name());
 711             if (!propertyName.isEmpty()) {
 712                 String propertyMethodName = propertyName + "Property";
 713                 for (MethodDoc member: members) {
 714                     if (member.name().equals(propertyMethodName)) {
 715                         found = true;
 716                         break;
 717                     }
 718                 }
 719             }
 720             return found;
 721         }
 722     }
 723 
 724     private class GetterSetter {
 725         private final ProgramElementDoc getter;
 726         private final ProgramElementDoc setter;
 727 
 728         public GetterSetter(ProgramElementDoc getter, ProgramElementDoc setter) {
 729             this.getter = getter;
 730             this.setter = setter;
 731         }
 732 
 733         public ProgramElementDoc getGetter() {
 734             return getter;
 735         }
 736 
 737         public ProgramElementDoc getSetter() {
 738             return setter;
 739         }
 740     }
 741 
 742     /**
 743      * Return true if this map has no visible members.
 744      *
 745      * @return true if this map has no visible members.
 746      */
 747     public boolean noVisibleMembers() {
 748         return noVisibleMembers;
 749     }
 750 
 751     private ClassMember getClassMember(MethodDoc member) {
 752         for (Iterator<?> iter = memberNameMap.keySet().iterator(); iter.hasNext();) {
 753             Object key = iter.next();
 754             if (key instanceof String) {
 755                 continue;
 756             } else if (((ClassMember) key).isEqual(member)) {
 757                 return (ClassMember) key;
 758             }
 759         }
 760         return new ClassMember(member);
 761     }
 762 
 763     /**
 764      * Return the key to the member map for the given member.
 765      */
 766     private Object getMemberKey(ProgramElementDoc doc) {
 767         if (doc.isConstructor()) {
 768             return doc.name() + ((ExecutableMemberDoc)doc).signature();
 769         } else if (doc.isMethod()) {
 770             return getClassMember((MethodDoc) doc);
 771         } else if (doc.isField() || doc.isEnumConstant() || doc.isAnnotationTypeElement()) {
 772             return doc.name();
 773         } else { // it's a class or interface
 774             String classOrIntName = doc.name();
 775             //Strip off the containing class name because we only want the member name.
 776             classOrIntName = classOrIntName.indexOf('.') != 0 ? classOrIntName.substring(classOrIntName.lastIndexOf('.'), classOrIntName.length()) : classOrIntName;
 777             return "clint" + classOrIntName;
 778         }
 779     }
 780 }