1 /*
   2  * Copyright (c) 1999, 2017, 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 jdk.javadoc.internal.doclets.toolkit.util;
  27 
  28 import java.util.*;
  29 import java.util.regex.Pattern;
  30 
  31 import javax.lang.model.element.Element;
  32 import javax.lang.model.element.ElementKind;
  33 import javax.lang.model.element.ExecutableElement;
  34 import javax.lang.model.element.TypeElement;
  35 import javax.lang.model.element.VariableElement;
  36 import javax.lang.model.type.TypeKind;
  37 import javax.lang.model.type.TypeMirror;
  38 import javax.lang.model.util.ElementFilter;
  39 
  40 import com.sun.source.doctree.DocCommentTree;
  41 import com.sun.source.doctree.DocTree;
  42 
  43 import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
  44 import jdk.javadoc.internal.doclets.toolkit.Messages;
  45 
  46 /**
  47  * A data structure that encapsulates the visible members of a particular
  48  * type for a given class tree.  To use this data structure, you must specify
  49  * the type of member you are interested in (nested class, field, constructor
  50  * or method) and the leaf of the class tree.  The data structure will map
  51  * all visible members in the leaf and classes above the leaf in the tree.
  52  *
  53  *  <p><b>This is NOT part of any supported API.
  54  *  If you write code that depends on this, you do so at your own risk.
  55  *  This code and its internal interfaces are subject to change or
  56  *  deletion without notice.</b>
  57  *
  58  * @author Atul M Dambalkar
  59  * @author Jamie Ho (rewrite)
  60  */
  61 public class VisibleMemberMap {
  62 
  63     private boolean noVisibleMembers = true;
  64 
  65     public static enum Kind {
  66         INNER_CLASSES,
  67         ENUM_CONSTANTS,
  68         FIELDS,
  69         CONSTRUCTORS,
  70         METHODS,
  71         ANNOTATION_TYPE_FIELDS,
  72         ANNOTATION_TYPE_MEMBER_OPTIONAL,
  73         ANNOTATION_TYPE_MEMBER_REQUIRED,
  74         PROPERTIES;
  75 
  76         public static final EnumSet<Kind> summarySet = EnumSet.range(INNER_CLASSES, METHODS);
  77         public static final EnumSet<Kind> detailSet = EnumSet.range(ENUM_CONSTANTS, METHODS);
  78         public static String getNavLinkLabels(Kind kind) {
  79             switch (kind) {
  80                 case INNER_CLASSES:
  81                     return "doclet.navNested";
  82                 case ENUM_CONSTANTS:
  83                     return "doclet.navEnum";
  84                 case FIELDS:
  85                     return "doclet.navField";
  86                 case CONSTRUCTORS:
  87                     return "doclet.navConstructor";
  88                 case METHODS:
  89                     return "doclet.navMethod";
  90                 default:
  91                     throw new AssertionError("unknown kind:" + kind);
  92             }
  93         }
  94     }
  95 
  96     public static final String STARTLEVEL = "start";
  97 
  98     // properties aren't named setA* or getA*
  99     private static final Pattern GETTERSETTERPATTERN = Pattern.compile("[sg]et\\p{Upper}.*");
 100     /**
 101      * List of TypeElement objects for which ClassMembers objects are built.
 102      */
 103     private final Set<TypeElement> visibleClasses;
 104 
 105     /**
 106      * Map for each member name on to a map which contains members with same
 107      * name-signature. The mapped map will contain mapping for each MemberDoc
 108      * onto it's respecive level string.
 109      */
 110     private final Map<Object, Map<Element, String>> memberNameMap = new HashMap<>();
 111 
 112     /**
 113      * Map of class and it's ClassMembers object.
 114      */
 115     private final Map<TypeElement, ClassMembers> classMap = new HashMap<>();
 116 
 117     /**
 118      * Type whose visible members are requested.  This is the leaf of
 119      * the class tree being mapped.
 120      */
 121     private final TypeElement typeElement;
 122 
 123     /**
 124      * Member kind: InnerClasses/Fields/Methods?
 125      */
 126     public final Kind kind;
 127 
 128     /**
 129      * The configuration this VisibleMemberMap was created with.
 130      */
 131     private final BaseConfiguration configuration;
 132     private final Messages messages;
 133     private final Utils utils;
 134     private final Comparator<Element> comparator;
 135 
 136     private final Map<TypeElement, List<Element>> propertiesCache;
 137     private final Map<Element, Element> classPropertiesMap;
 138     private final Map<Element, GetterSetter> getterSetterMap;
 139 
 140     /**
 141      * Construct a VisibleMemberMap of the given type for the given class.
 142      *
 143      * @param typeElement whose members are being mapped.
 144      * @param kind the kind of member that is being mapped.
 145      * @param configuration the configuration to use to construct this
 146      * VisibleMemberMap. If the field configuration.nodeprecated is true the
 147      * deprecated members are excluded from the map. If the field
 148      * configuration.javafx is true the JavaFX features are used.
 149      */
 150     public VisibleMemberMap(TypeElement typeElement,
 151                             Kind kind,
 152                             BaseConfiguration configuration) {
 153         this.typeElement = typeElement;
 154         this.kind = kind;
 155         this.configuration = configuration;
 156         this.messages = configuration.getMessages();
 157         this.utils = configuration.utils;
 158         propertiesCache = configuration.propertiesCache;
 159         classPropertiesMap = configuration.classPropertiesMap;
 160         getterSetterMap = configuration.getterSetterMap;
 161         comparator  = utils.makeGeneralPurposeComparator();
 162         visibleClasses = new LinkedHashSet<>();
 163         new ClassMembers(typeElement, STARTLEVEL).build();
 164     }
 165 
 166     /**
 167      * Return the list of visible classes in this map.
 168      *
 169      * @return the list of visible classes in this map.
 170      */
 171     public SortedSet<TypeElement> getVisibleClasses() {
 172         SortedSet<TypeElement> vClasses = new TreeSet<>(comparator);
 173         vClasses.addAll(visibleClasses);
 174         return vClasses;
 175     }
 176 
 177     /**
 178      * Returns the first method where the given method is visible.
 179      * @param e the method whose visible enclosing type is to be found.
 180      * @return the method found or null
 181      */
 182     public ExecutableElement getVisibleMethod(ExecutableElement e) {
 183         if (kind != Kind.METHODS || e.getKind() != ElementKind.METHOD) {
 184             throw new AssertionError("incompatible member type or visible member map" + e);
 185         }
 186         // start with the current class
 187         for (Element m : getMembers(typeElement)) {
 188             ExecutableElement mthd = (ExecutableElement)m;
 189             if (utils.executableMembersEqual(mthd, e)) {
 190                 return mthd;
 191             }
 192         }
 193 
 194         for (TypeElement te : visibleClasses) {
 195             if (te == typeElement)
 196                 continue;
 197             for (Element m : getMembers(te)) {
 198                 ExecutableElement mthd = (ExecutableElement)m;
 199                 if (utils.executableMembersEqual(mthd, e)) {
 200                     return mthd;
 201                 }
 202             }
 203         }
 204         return null;
 205     }
 206 
 207     /**
 208      * Returns the property field documentation belonging to the given member.
 209      * @param element the member for which the property documentation is needed.
 210      * @return the property field documentation, null if there is none.
 211      */
 212     public Element getPropertyElement(Element element) {
 213         return classPropertiesMap.get(element);
 214     }
 215 
 216     /**
 217      * Returns the getter documentation belonging to the given property method.
 218      * @param propertyMethod the method for which the getter is needed.
 219      * @return the getter documentation, null if there is none.
 220      */
 221     public Element getGetterForProperty(Element propertyMethod) {
 222         return getterSetterMap.get(propertyMethod).getGetter();
 223     }
 224 
 225     /**
 226      * Returns the setter documentation belonging to the given property method.
 227      * @param propertyMethod the method for which the setter is needed.
 228      * @return the setter documentation, null if there is none.
 229      */
 230     public Element getSetterForProperty(Element propertyMethod) {
 231         return getterSetterMap.get(propertyMethod).getSetter();
 232     }
 233 
 234     /**
 235      * Return the package private members inherited by the class.  Only return
 236      * if parent is package private and not documented.
 237      *
 238      * @return the package private members inherited by the class.
 239      */
 240     private List<Element> getInheritedPackagePrivateMethods() {
 241         List<Element> results = new ArrayList<>();
 242         for (TypeElement currentClass : visibleClasses) {
 243             if (currentClass != typeElement &&
 244                 utils.isPackagePrivate(currentClass) &&
 245                 !utils.isLinkable(currentClass)) {
 246                 // Document these members in the child class because
 247                 // the parent is inaccessible.
 248                 results.addAll(classMap.get(currentClass).members);
 249             }
 250         }
 251         return results;
 252     }
 253 
 254     /**
 255      * Returns a list of visible enclosed members of the mapped type element.
 256      *
 257      * In the case of methods, the list may contain those methods that are
 258      * extended with no specification changes as indicated by the existence
 259      * of a sole @inheritDoc or devoid of any API commments.
 260      *
 261      * This list may also contain appended members, inherited by inaccessible
 262      * super types. These members are documented in the subtype when the
 263      * super type is not documented.
 264      *
 265      * @return a list of visible enclosed members
 266      */
 267 
 268     public List<Element> getLeafMembers() {
 269         List<Element> result = new ArrayList<>();
 270         result.addAll(getMembers(typeElement));
 271         result.addAll(getInheritedPackagePrivateMethods());
 272         return result;
 273     }
 274 
 275     private boolean hasOverridden(ExecutableElement method) {
 276         for (TypeElement t : visibleClasses) {
 277             for (ExecutableElement inheritedMethod : ElementFilter.methodsIn(classMap.get(t).members)) {
 278                 if (utils.elementUtils.overrides(method, inheritedMethod, t)) {
 279                     return true;
 280                 }
 281             }
 282         }
 283         return false;
 284     }
 285 
 286     /**
 287      * Returns a list of enclosed members for the given type.
 288      *
 289      * @param typeElement the given type
 290      *
 291      * @return a list of enclosed members
 292      */
 293     public List<Element> getMembers(TypeElement typeElement) {
 294         List<Element> result = new ArrayList<>();
 295         if (this.kind == Kind.METHODS) {
 296             for (Element member : classMap.get(typeElement).members) {
 297                 ExecutableElement method = (ExecutableElement)member;
 298                 if (hasOverridden(method)) {
 299                     if (!utils.isSimpleOverride(method)) {
 300                         result.add(method);
 301                     }
 302                 } else {
 303                     result.add(method);
 304                 }
 305             }
 306         } else {
 307             result.addAll(classMap.get(typeElement).members);
 308         }
 309         return result;
 310     }
 311 
 312     public boolean hasMembers(TypeElement typeElement) {
 313         return !classMap.get(typeElement).members.isEmpty();
 314     }
 315 
 316     private void fillMemberLevelMap(List<? extends Element> list, String level) {
 317         for (Element element : list) {
 318             Object key = getMemberKey(element);
 319             Map<Element, String> memberLevelMap = memberNameMap.get(key);
 320             if (memberLevelMap == null) {
 321                 memberLevelMap = new HashMap<>();
 322                 memberNameMap.put(key, memberLevelMap);
 323             }
 324             memberLevelMap.put(element, level);
 325         }
 326     }
 327 
 328     private void purgeMemberLevelMap(Iterable<? extends Element> list, String level) {
 329         for (Element element : list) {
 330             Object key = getMemberKey(element);
 331             Map<Element, String> memberLevelMap = memberNameMap.get(key);
 332             if (memberLevelMap != null && level.equals(memberLevelMap.get(element)))
 333                 memberLevelMap.remove(element);
 334         }
 335     }
 336 
 337     /**
 338      * Represents a class member.
 339      */
 340     private class ClassMember {
 341         private Set<Element> members;
 342 
 343         public ClassMember(Element element) {
 344             members = new HashSet<>();
 345             members.add(element);
 346         }
 347 
 348         public boolean isEqual(ExecutableElement method) {
 349             for (Element member : members) {
 350                 if (member.getKind() != ElementKind.METHOD)
 351                     continue;
 352                 ExecutableElement thatMethod = (ExecutableElement) member;
 353                 if (utils.executableMembersEqual(method, thatMethod) &&
 354                         !utils.isSimpleOverride(thatMethod)) {
 355                     members.add(method);
 356                     return true;
 357                 }
 358             }
 359             return false;
 360         }
 361     }
 362 
 363     /**
 364      * A data structure that represents the class members for
 365      * a visible class.
 366      */
 367     private class ClassMembers {
 368 
 369         /**
 370          * The mapping class, whose inherited members are put in the
 371          * {@link #members} list.
 372          */
 373         private final TypeElement typeElement;
 374 
 375         /**
 376          * List of members from the mapping class.
 377          */
 378         private List<Element> members = null;
 379 
 380         /**
 381          * Level/Depth of inheritance.
 382          */
 383         private final String level;
 384 
 385         private ClassMembers(TypeElement mappingClass, String level) {
 386             this.typeElement = mappingClass;
 387             this.level = level;
 388             if (classMap.containsKey(mappingClass) &&
 389                         level.startsWith(classMap.get(mappingClass).level)) {
 390                 //Remove lower level class so that it can be replaced with
 391                 //same class found at higher level.
 392                 purgeMemberLevelMap(getClassMembers(mappingClass, false),
 393                     classMap.get(mappingClass).level);
 394                 classMap.remove(mappingClass);
 395                 visibleClasses.remove(mappingClass);
 396             }
 397             if (!classMap.containsKey(mappingClass)) {
 398                 classMap.put(mappingClass, this);
 399                 visibleClasses.add(mappingClass);
 400             }
 401         }
 402 
 403         private void build() {
 404             if (kind == Kind.CONSTRUCTORS) {
 405                 addMembers(typeElement);
 406             } else {
 407                 mapClass();
 408             }
 409         }
 410 
 411         private void mapClass() {
 412             addMembers(typeElement);
 413             List<? extends TypeMirror> interfaces = typeElement.getInterfaces();
 414             for (TypeMirror anInterface : interfaces) {
 415                 String locallevel = level + 1;
 416                 ClassMembers cm = new ClassMembers(utils.asTypeElement(anInterface), locallevel);
 417                 cm.mapClass();
 418             }
 419             if (utils.isClass(typeElement)) {
 420                 TypeElement superclass = utils.getSuperClass(typeElement);
 421                 if (!(superclass == null || typeElement.equals(superclass))) {
 422                     ClassMembers cm = new ClassMembers(superclass, level + "c");
 423                     cm.mapClass();
 424                 }
 425             }
 426         }
 427 
 428         /**
 429          * Get all the valid members from the mapping class. Get the list of
 430          * members for the class to be included into(ctii), also get the level
 431          * string for ctii. If mapping class member is not already in the
 432          * inherited member list and if it is visible in the ctii and not
 433          * overridden, put such a member in the inherited member list.
 434          * Adjust member-level-map, class-map.
 435          */
 436         private void addMembers(TypeElement fromClass) {
 437             List<Element> result = new ArrayList<>();
 438             for (Element element : getClassMembers(fromClass, true)) {
 439                 if (memberIsVisible(element)) {
 440                     if (!isOverridden(element, level)) {
 441                         if (!utils.isHidden(element)) {
 442                             result.add(element);
 443                         }
 444                     }
 445                 }
 446             }
 447             if (members != null) {
 448                 throw new AssertionError("members should not be null");
 449             }
 450             members = Collections.unmodifiableList(result);
 451             if (!members.isEmpty()) {
 452                 noVisibleMembers = false;
 453             }
 454             fillMemberLevelMap(getClassMembers(fromClass, false), level);
 455         }
 456 
 457         /**
 458          * Is given element visible in given typeElement in terms of inheritance? The given element
 459          * is visible in the given typeElement if it is public or protected and if it is
 460          * package-private if it's containing class is in the same package as the given typeElement.
 461          */
 462         private boolean memberIsVisible(Element element) {
 463             if (utils.getEnclosingTypeElement(element).equals(VisibleMemberMap.this.typeElement)) {
 464                 //Member is in class that we are finding visible members for.
 465                 //Of course it is visible.
 466                 return true;
 467             } else if (utils.isPrivate(element)) {
 468                 //Member is in super class or implemented interface.
 469                 //Private, so not inherited.
 470                 return false;
 471             } else if (utils.isPackagePrivate(element)) {
 472                 //Member is package private.  Only return true if its class is in
 473                 //same package.
 474                 return utils.containingPackage(element).equals(utils.containingPackage(VisibleMemberMap.this.typeElement));
 475             } else {
 476                 //Public members are always inherited.
 477                 return true;
 478             }
 479         }
 480 
 481         /**
 482          * Return all available class members.
 483          */
 484         private List<? extends Element> getClassMembers(TypeElement te, boolean filter) {
 485             if (utils.isEnum(te) && kind == Kind.CONSTRUCTORS) {
 486                 //If any of these rules are hit, return empty array because
 487                 //we don't document these members ever.
 488                 return Collections.emptyList();
 489             }
 490             List<? extends Element> list;
 491             switch (kind) {
 492                 case ANNOTATION_TYPE_FIELDS:
 493                     list = (filter)
 494                             ? utils.getAnnotationFields(te)
 495                             : utils.getAnnotationFieldsUnfiltered(te);
 496                     break;
 497                 case ANNOTATION_TYPE_MEMBER_OPTIONAL:
 498                     list = utils.isAnnotationType(te)
 499                             ? filterAnnotations(te, false)
 500                             : Collections.emptyList();
 501                     break;
 502                 case ANNOTATION_TYPE_MEMBER_REQUIRED:
 503                     list = utils.isAnnotationType(te)
 504                             ? filterAnnotations(te, true)
 505                             : Collections.emptyList();
 506                     break;
 507                 case INNER_CLASSES:
 508                     List<TypeElement> xlist = filter
 509                             ? utils.getInnerClasses(te)
 510                             : utils.getInnerClassesUnfiltered(te);
 511                     list = new ArrayList<>(xlist);
 512                     break;
 513                 case ENUM_CONSTANTS:
 514                     list = utils.getEnumConstants(te);
 515                     break;
 516                 case FIELDS:
 517                     if (filter) {
 518                         list = utils.isAnnotationType(te)
 519                                 ? utils.getAnnotationFields(te)
 520                                 : utils.getFields(te);
 521                     } else {
 522                         list = utils.isAnnotationType(te)
 523                                 ? utils.getAnnotationFieldsUnfiltered(te)
 524                                 : utils.getFieldsUnfiltered(te);
 525                     }
 526                     break;
 527                 case CONSTRUCTORS:
 528                     list = utils.getConstructors(te);
 529                     break;
 530                 case METHODS:
 531                     list = filter ? utils.getMethods(te) : utils.getMethodsUnfiltered(te);
 532                     checkOnPropertiesTags(list);
 533                     break;
 534                 case PROPERTIES:
 535                     list = properties(te, filter);
 536                     break;
 537                 default:
 538                     list = Collections.emptyList();
 539             }
 540             // Deprected members should be excluded or not?
 541             if (configuration.nodeprecated) {
 542                 return utils.excludeDeprecatedMembers(list);
 543             }
 544             return list;
 545         }
 546 
 547         /**
 548          * Filter the annotation type members and return either the required
 549          * members or the optional members, depending on the value of the
 550          * required parameter.
 551          *
 552          * @param typeElement The annotation type to process.
 553          * @param required
 554          * @return the annotation type members and return either the required
 555          * members or the optional members, depending on the value of the
 556          * required parameter.
 557          */
 558         private List<Element> filterAnnotations(TypeElement typeElement, boolean required) {
 559             List<Element> members = utils.getAnnotationMethods(typeElement);
 560             List<Element> targetMembers = new ArrayList<>();
 561             for (Element member : members) {
 562                 ExecutableElement ee = (ExecutableElement)member;
 563                 if ((required && ee.getDefaultValue() == null)
 564                         || ((!required) && ee.getDefaultValue() != null)) {
 565                     targetMembers.add(member);
 566                 }
 567             }
 568             return targetMembers;
 569         }
 570 
 571         /**
 572          * Is member overridden? The member is overridden if it is found in the
 573          * same level hierarchy e.g. member at level "11" overrides member at
 574          * level "111".
 575          */
 576         private boolean isOverridden(Element element, String level) {
 577             Object key = getMemberKey(element);
 578             Map<?, String> memberLevelMap = (Map<?, String>) memberNameMap.get(key);
 579             if (memberLevelMap == null)
 580                 return false;
 581             for (String mappedlevel : memberLevelMap.values()) {
 582                 if (mappedlevel.equals(STARTLEVEL)
 583                         || (level.startsWith(mappedlevel)
 584                         && !level.equals(mappedlevel))) {
 585                     return true;
 586                 }
 587             }
 588             return false;
 589         }
 590 
 591         private List<Element> properties(final TypeElement typeElement, final boolean filter) {
 592             final List<ExecutableElement> allMethods = filter
 593                     ? utils.getMethods(typeElement)
 594                     : utils.getMethodsUnfiltered(typeElement);
 595             final List<VariableElement> allFields = utils.getFieldsUnfiltered(typeElement);
 596 
 597             if (propertiesCache.containsKey(typeElement)) {
 598                 return propertiesCache.get(typeElement);
 599             }
 600 
 601             final List<Element> result = new ArrayList<>();
 602 
 603             for (final Element propertyMethod : allMethods) {
 604                 ExecutableElement ee = (ExecutableElement)propertyMethod;
 605                 if (!isPropertyMethod(ee)) {
 606                     continue;
 607                 }
 608 
 609                 final ExecutableElement getter = getterForField(allMethods, ee);
 610                 final ExecutableElement setter = setterForField(allMethods, ee);
 611                 final VariableElement field = fieldForProperty(allFields, ee);
 612 
 613                 addToPropertiesMap(setter, getter, ee, field);
 614                 getterSetterMap.put(propertyMethod, new GetterSetter(getter, setter));
 615                 result.add(ee);
 616             }
 617             propertiesCache.put(typeElement, result);
 618             return result;
 619         }
 620 
 621         private void addToPropertiesMap(ExecutableElement setter,
 622                                         ExecutableElement getter,
 623                                         ExecutableElement propertyMethod,
 624                                         VariableElement field) {
 625             if (field == null || utils.getDocCommentTree(field) == null) {
 626                 addToPropertiesMap(setter, propertyMethod);
 627                 addToPropertiesMap(getter, propertyMethod);
 628                 addToPropertiesMap(propertyMethod, propertyMethod);
 629             } else {
 630                 addToPropertiesMap(getter, field);
 631                 addToPropertiesMap(setter, field);
 632                 addToPropertiesMap(propertyMethod, field);
 633             }
 634         }
 635 
 636         private void addToPropertiesMap(Element propertyMethod,
 637                                         Element commentSource) {
 638             if (null == propertyMethod || null == commentSource) {
 639                 return;
 640             }
 641             DocCommentTree docTree = utils.getDocCommentTree(propertyMethod);
 642 
 643             /* The second condition is required for the property buckets. In
 644              * this case the comment is at the property method (not at the field)
 645              * and it needs to be listed in the map.
 646              */
 647             if ((docTree == null) || propertyMethod.equals(commentSource)) {
 648                 classPropertiesMap.put(propertyMethod, commentSource);
 649             }
 650         }
 651 
 652         private ExecutableElement getterForField(List<ExecutableElement> methods,
 653                                          ExecutableElement propertyMethod) {
 654             final String propertyMethodName = utils.getSimpleName(propertyMethod);
 655             final String fieldName = propertyMethodName.substring(0,
 656                             propertyMethodName.lastIndexOf("Property"));
 657             final String fieldNameUppercased =
 658                     "" + Character.toUpperCase(fieldName.charAt(0))
 659                                             + fieldName.substring(1);
 660             final String getterNamePattern;
 661             final String fieldTypeName = propertyMethod.getReturnType().toString();
 662             if ("boolean".equals(fieldTypeName)
 663                     || fieldTypeName.endsWith("BooleanProperty")) {
 664                 getterNamePattern = "(is|get)" + fieldNameUppercased;
 665             } else {
 666                 getterNamePattern = "get" + fieldNameUppercased;
 667             }
 668 
 669             for (ExecutableElement method : methods) {
 670                 if (Pattern.matches(getterNamePattern, utils.getSimpleName(method))) {
 671                     if (method.getParameters().isEmpty() &&
 672                             utils.isPublic(method) || utils.isProtected(method)) {
 673                         return method;
 674                     }
 675                 }
 676             }
 677             return null;
 678         }
 679 
 680         private ExecutableElement setterForField(List<ExecutableElement> methods,
 681                                          ExecutableElement propertyMethod) {
 682             final String propertyMethodName = utils.getSimpleName(propertyMethod);
 683             final String fieldName =
 684                     propertyMethodName.substring(0,
 685                             propertyMethodName.lastIndexOf("Property"));
 686             final String fieldNameUppercased =
 687                     "" + Character.toUpperCase(fieldName.charAt(0))
 688                                              + fieldName.substring(1);
 689             final String setter = "set" + fieldNameUppercased;
 690 
 691             for (ExecutableElement method : methods) {
 692                 if (setter.equals(utils.getSimpleName(method))) {
 693                     if (method.getParameters().size() == 1
 694                             && method.getReturnType().getKind() == TypeKind.VOID
 695                             && (utils.isPublic(method) || utils.isProtected(method))) {
 696                         return method;
 697                     }
 698                 }
 699             }
 700             return null;
 701         }
 702 
 703         private VariableElement fieldForProperty(List<VariableElement> fields, ExecutableElement property) {
 704 
 705             for (VariableElement field : fields) {
 706                 final String fieldName = utils.getSimpleName(field);
 707                 final String propertyName = fieldName + "Property";
 708                 if (propertyName.equals(utils.getSimpleName(property))) {
 709                     return field;
 710                 }
 711             }
 712             return null;
 713         }
 714 
 715         private boolean isPropertyMethod(ExecutableElement method) {
 716             if (!configuration.javafx) {
 717                return false;
 718             }
 719             if (!utils.getSimpleName(method).endsWith("Property")) {
 720                 return false;
 721             }
 722 
 723             if (!memberIsVisible(method)) {
 724                 return false;
 725             }
 726 
 727             if (GETTERSETTERPATTERN.matcher(utils.getSimpleName(method)).matches()) {
 728                 return false;
 729             }
 730             if (!method.getTypeParameters().isEmpty()) {
 731                 return false;
 732             }
 733             return method.getParameters().isEmpty()
 734                     && method.getReturnType().getKind() != TypeKind.VOID;
 735         }
 736 
 737         private void checkOnPropertiesTags(List<? extends Element> members) {
 738             for (Element e: members) {
 739                 ExecutableElement ee = (ExecutableElement)e;
 740                 if (utils.isIncluded(ee)) {
 741                     CommentHelper ch = utils.getCommentHelper(ee);
 742                     for (DocTree tree: utils.getBlockTags(ee)) {
 743                         String tagName = ch.getTagName(tree);
 744                         if (tagName.equals("@propertySetter")
 745                                 || tagName.equals("@propertyGetter")
 746                                 || tagName.equals("@propertyDescription")) {
 747                             if (!isPropertyGetterOrSetter(members, ee)) {
 748                                 messages.warning(ch.getDocTreePath(tree),
 749                                         "doclet.javafx_tag_misuse");
 750                             }
 751                             break;
 752                         }
 753                     }
 754                 }
 755             }
 756         }
 757 
 758         private boolean isPropertyGetterOrSetter(List<? extends Element> members,
 759                                                  ExecutableElement method) {
 760             String propertyName = utils.propertyName(method);
 761             if (!propertyName.isEmpty()) {
 762                 String propertyMethodName = propertyName + "Property";
 763                 for (Element member: members) {
 764                     if (utils.getSimpleName(member).equals(propertyMethodName)) {
 765                         return true;
 766                     }
 767                 }
 768             }
 769             return false;
 770         }
 771     }
 772 
 773     public class GetterSetter {
 774         private final Element getter;
 775         private final Element setter;
 776 
 777         public GetterSetter(Element getter, Element setter) {
 778             this.getter = getter;
 779             this.setter = setter;
 780         }
 781 
 782         public Element getGetter() {
 783             return getter;
 784         }
 785 
 786         public Element getSetter() {
 787             return setter;
 788         }
 789     }
 790 
 791     /**
 792      * Return true if this map has no visible members.
 793      *
 794      * @return true if this map has no visible members.
 795      */
 796     public boolean noVisibleMembers() {
 797         return noVisibleMembers;
 798     }
 799 
 800     private ClassMember getClassMember(ExecutableElement member) {
 801         for (Object key : memberNameMap.keySet()) {
 802             if (key instanceof String) {
 803                 continue;
 804             }
 805             if (((ClassMember) key).isEqual(member)) {
 806                 return (ClassMember) key;
 807             }
 808         }
 809         return new ClassMember(member);
 810     }
 811 
 812     /**
 813      * Return the key to the member map for the given member.
 814      */
 815     private Object getMemberKey(Element element) {
 816         if (utils.isConstructor(element)) {
 817             return utils.getSimpleName(element) + utils.flatSignature((ExecutableElement)element);
 818         } else if (utils.isMethod(element)) {
 819             return getClassMember((ExecutableElement) element);
 820         } else if (utils.isField(element) || utils.isEnumConstant(element) || utils.isAnnotationType(element)) {
 821             return utils.getSimpleName(element);
 822         } else { // it's a class or interface
 823             String classOrIntName = utils.getSimpleName(element);
 824             //Strip off the containing class name because we only want the member name.
 825             classOrIntName = classOrIntName.indexOf('.') != 0
 826                     ? classOrIntName.substring(classOrIntName.lastIndexOf('.'))
 827                     : classOrIntName;
 828             return "clint" + classOrIntName;
 829         }
 830     }
 831 }