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