blob: 774c68a00dc5837466b795e2556f428e635d4fde [file] [log] [blame]
/*
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.javadoc.internal.doclets.toolkit.util;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleElementVisitor9;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
import jdk.javadoc.internal.doclets.toolkit.PropertyUtils;
/**
* This class computes the main data structure for the doclet's
* operations. Essentially, the implementation encapsulating the
* javax.lang.models view of what can be documented about a
* type element's members.
* <p>
* The general operations are as follows:
* <p>
* Members: these are the members from jx.l.m's view but
* are structured along the kinds of this class.
* <p>
* Extra Members: these are members enclosed in an undocumented
* package-private type element, and may not be linkable (or documented),
* however, the members of such a type element may be documented, as if
* declared in the sub type, only if the enclosing type is not being
* documented by a filter such as -public, -protected, etc.
* <p>
* Visible Members: these are the members that are "visible"
* and available and should be documented, in a type element.
* <p>
* The basic rule for computation: when considering a type element,
* besides its immediate direct types and interfaces, the computation
* should not expand to any other type in the inheritance hierarchy.
* <p>
* This table generates all the data structures it needs for each
* type, as its own view, and will present some form of this to the
* doclet as and when required to.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
*
*/
public class VisibleMemberTable {
public enum Kind {
INNER_CLASSES,
ENUM_CONSTANTS,
FIELDS,
CONSTRUCTORS,
METHODS,
ANNOTATION_TYPE_FIELDS,
ANNOTATION_TYPE_MEMBER_OPTIONAL,
ANNOTATION_TYPE_MEMBER_REQUIRED,
PROPERTIES;
public static final EnumSet<Kind> summarySet = EnumSet.range(INNER_CLASSES, METHODS);
public static final EnumSet<Kind> detailSet = EnumSet.range(ENUM_CONSTANTS, METHODS);
}
final TypeElement te;
final TypeElement parent;
final BaseConfiguration config;
final Utils utils;
final VisibleMemberCache mcache;
private List<VisibleMemberTable> allSuperclasses;
private List<VisibleMemberTable> allSuperinterfaces;
private List<VisibleMemberTable> parents;
private Map<Kind, List<Element>> extraMembers = new EnumMap<>(Kind.class);
private Map<Kind, List<Element>> visibleMembers = null;
private Map<ExecutableElement, PropertyMembers> propertyMap = new HashMap<>();
// Keeps track of method overrides
Map<ExecutableElement, OverridingMethodInfo> overriddenMethodTable
= new LinkedHashMap<>();
protected VisibleMemberTable(TypeElement typeElement, BaseConfiguration configuration,
VisibleMemberCache mcache) {
config = configuration;
utils = configuration.utils;
te = typeElement;
parent = utils.getSuperClass(te);
this.mcache = mcache;
allSuperclasses = new ArrayList<>();
allSuperinterfaces = new ArrayList<>();
parents = new ArrayList<>();
}
private synchronized void ensureInitialized() {
if (visibleMembers != null)
return;
visibleMembers = new EnumMap<>(Kind.class);
for (Kind kind : Kind.values()) {
visibleMembers.put(kind, new ArrayList<>());
}
computeParents();
computeVisibleMembers();
}
List<? extends Element> getExtraMembers(Kind kind) {
ensureInitialized();
return visibleMembers.getOrDefault(kind, Collections.emptyList());
}
List<VisibleMemberTable> getAllSuperclasses() {
ensureInitialized();
return allSuperclasses;
}
List<VisibleMemberTable> getAllSuperinterfaces() {
ensureInitialized();
return allSuperinterfaces;
}
/**
* Returns a list of all visible enclosed members of a type element,
* and inherited members.
* <p>
* Notes:
* a. The list may or may not contain simple overridden methods.
* A simple overridden method is one that overrides a super method
* with no specification changes as indicated by the existence of a
* sole &commat;inheritDoc or devoid of any API commments.
* <p>
* b.The list may contain (extra) members, inherited by inaccessible
* super types, primarily package private types. These members are
* required to be documented in the subtype when the super type is
* not documented.
*
* @param kind the member kind
* @return a list of all visible members
*/
public List<? extends Element> getAllVisibleMembers(Kind kind) {
ensureInitialized();
return visibleMembers.getOrDefault(kind, Collections.emptyList());
}
/**
* Returns a list of visible enclosed members of a specified kind,
* filtered by the specified predicate.
* @param kind the member kind
* @param p the predicate used to filter the output
* @return a list of visible enclosed members
*/
public List<? extends Element> getVisibleMembers(Kind kind, Predicate<Element> p) {
ensureInitialized();
return visibleMembers.getOrDefault(kind, Collections.emptyList()).stream()
.filter(p)
.collect(Collectors.toList());
}
/**
* Returns a list of all enclosed members including any extra members.
* Typically called by various builders.
*
* @param kind the member kind
* @return a list of visible enclosed members
*/
public List<? extends Element> getVisibleMembers(Kind kind) {
Predicate<Element> declaredAndLeafMembers = e -> {
TypeElement encl = utils.getEnclosingTypeElement(e);
return encl == te || isUndocumentedEnclosure(encl);
};
return getVisibleMembers(kind, declaredAndLeafMembers);
}
/**
* Returns a list of visible enclosed members of given kind,
* declared in this type element, and does not include
* any inherited members or extra members.
*
* @return a list of visible enclosed members in this type
*/
public List<? extends Element> getMembers(Kind kind) {
Predicate<Element> onlyLocallyDeclaredMembers = e -> utils.getEnclosingTypeElement(e) == te;
return getVisibleMembers(kind, onlyLocallyDeclaredMembers);
}
/**
* Returns the overridden method, if it is simply overridding or the
* method is a member of a package private type, this method is
* primarily used to determine the location of a possible comment.
*
* @param e the method to check
* @return the method found or null
*/
public ExecutableElement getOverriddenMethod(ExecutableElement e) {
ensureInitialized();
OverridingMethodInfo found = overriddenMethodTable.get(e);
if (found != null && (found.simpleOverride || isUndocumentedEnclosure(utils.getEnclosingTypeElement(e)))) {
return found.overrider;
}
return null;
}
/**
* Returns the simply overridden method.
* @param e the method to check
* @return the overridden method or null
*/
public ExecutableElement getsimplyOverriddenMethod(ExecutableElement e) {
ensureInitialized();
OverridingMethodInfo found = overriddenMethodTable.get(e);
if (found != null && found.simpleOverride) {
return found.overrider;
}
return null;
}
/**
* Returns a set of visible type elements in this type element's lineage.
* <p>
* This method returns the super-types in the inheritance
* order C, B, A, j.l.O. The super-interfaces however are
* alpha sorted and appended to the resulting set.
*
* @return the list of visible classes in this map.
*/
public Set<TypeElement> getVisibleTypeElements() {
ensureInitialized();
Set<TypeElement> result = new LinkedHashSet<>();
// Add this type element first.
result.add(te);
// Add the super classes.
allSuperclasses.stream()
.map(vmt -> vmt.te)
.forEach(result::add);
// ... and finally the sorted super interfaces.
allSuperinterfaces.stream()
.map(vmt -> vmt.te)
.sorted(utils.makeGeneralPurposeComparator())
.forEach(result::add);
return result;
}
/**
* Returns true if this table contains visible members.
*
* @return true if visible members are present.
*/
public boolean hasVisibleMembers() {
for (Kind kind : Kind.values()) {
if (hasVisibleMembers(kind))
return true;
}
return false;
}
/**
* Returns true if this table contains visible members of
* the specified kind, including inhertied members.
*
* @return true if visible members are present.
*/
public boolean hasVisibleMembers(Kind kind) {
ensureInitialized();
List<Element> elements = visibleMembers.get(kind);
return elements != null && !elements.isEmpty();
}
/**
* Returns the property field associated with the property method.
* @param propertyMethod the identifying property method
* @return the field or null if absent
*/
public VariableElement getPropertyField(ExecutableElement propertyMethod) {
ensureInitialized();
PropertyMembers pm = propertyMap.get(propertyMethod);
return pm == null ? null : pm.field;
}
/**
* Returns the getter method associated with the property method.
* @param propertyMethod the identifying property method
* @return the getter or null if absent
*/
public ExecutableElement getPropertyGetter(ExecutableElement propertyMethod) {
ensureInitialized();
PropertyMembers pm = propertyMap.get(propertyMethod);
return pm == null ? null : pm.getter;
}
/**
* Returns the setter method associated with the property method.
* @param propertyMethod the identifying property method
* @return the setter or null if absent
*/
public ExecutableElement getPropertySetter(ExecutableElement propertyMethod) {
ensureInitialized();
PropertyMembers pm = propertyMap.get(propertyMethod);
return pm == null ? null : pm.setter;
}
boolean isUndocumentedEnclosure(TypeElement encl) {
return utils.isPackagePrivate(encl) && !utils.isLinkable(encl);
}
private void computeParents() {
for (TypeMirror intfType : te.getInterfaces()) {
TypeElement intfc = utils.asTypeElement(intfType);
if (intfc != null) {
VisibleMemberTable vmt = mcache.getVisibleMemberTable(intfc);
allSuperinterfaces.add(vmt);
parents.add(vmt);
allSuperinterfaces.addAll(vmt.getAllSuperinterfaces());
}
}
if (parent != null) {
VisibleMemberTable vmt = mcache.getVisibleMemberTable(parent);
allSuperclasses.add(vmt);
allSuperclasses.addAll(vmt.getAllSuperclasses());
// Add direct super interfaces of a super class, if any.
allSuperinterfaces.addAll(vmt.getAllSuperinterfaces());
parents.add(vmt);
}
}
private void computeVisibleMembers() {
// Note: these have some baggage, and are redundant,
// allow this to be GC'ed.
LocalMemberTable lmt = new LocalMemberTable();
for (Kind k : Kind.values()) {
computeLeafMembers(lmt, k);
computeVisibleMembers(lmt, k);
}
// All members have been computed, compute properties.
computeVisibleProperties(lmt);
}
private void computeLeafMembers(LocalMemberTable lmt, Kind kind) {
List<Element> list = new ArrayList<>();
if (isUndocumentedEnclosure(te)) {
list.addAll(lmt.getOrderedMembers(kind));
}
parents.forEach(pvmt -> {
list.addAll(pvmt.getExtraMembers(kind));
});
extraMembers.put(kind, Collections.unmodifiableList(list));
}
void computeVisibleMembers(LocalMemberTable lmt, Kind kind) {
switch (kind) {
case FIELDS: case INNER_CLASSES:
computeVisibleFieldsAndInnerClasses(lmt, kind);
return;
case METHODS:
computeVisibleMethods(lmt);
return;
// Defer properties related computations for later.
case PROPERTIES:
return;
default:
List<Element> list = lmt.getOrderedMembers(kind).stream()
.filter(this::mustDocument)
.collect(Collectors.toList());
visibleMembers.put(kind, Collections.unmodifiableList(list));
break;
}
}
private boolean mustDocument(Element e) {
return !utils.hasHiddenTag(e) && utils.shouldDocument(e);
}
private boolean allowInheritedMembers(Element e, Kind kind, LocalMemberTable lmt) {
return isInherited(e) && !isMemberHidden(e, kind, lmt);
}
private boolean isInherited(Element e) {
if (utils.isPrivate(e))
return false;
if (utils.isPackagePrivate(e))
// Allowed iff this type-element is in the same package as the element
return utils.containingPackage(e).equals(utils.containingPackage(te));
return true;
}
private boolean isMemberHidden(Element inheritedMember, Kind kind, LocalMemberTable lmt) {
Elements elementUtils = config.docEnv.getElementUtils();
switch(kind) {
default:
List<Element> list = lmt.getMembers(inheritedMember, kind);
if (list.isEmpty())
return false;
return elementUtils.hides(list.get(0), inheritedMember);
case METHODS: case CONSTRUCTORS: // Handled elsewhere.
throw new IllegalArgumentException("incorrect kind");
}
}
private void computeVisibleFieldsAndInnerClasses(LocalMemberTable lmt, Kind kind) {
Set<Element> result = new LinkedHashSet<>();
for (VisibleMemberTable pvmt : parents) {
result.addAll(pvmt.getExtraMembers(kind));
result.addAll(pvmt.getAllVisibleMembers(kind));
}
// Filter out members in the inherited list that are hidden
// by this type or should not be inherited at all.
List<Element> list = result.stream()
.filter(e -> allowInheritedMembers(e, kind, lmt)).collect(Collectors.toList());
// Prefix local results first
list.addAll(0, lmt.getOrderedMembers(kind));
// Filter out elements that should not be documented
list = list.stream()
.filter(this::mustDocument)
.collect(Collectors.toList());
visibleMembers.put(kind, Collections.unmodifiableList(list));
}
private void computeVisibleMethods(LocalMemberTable lmt) {
Set<Element> inheritedMethods = new LinkedHashSet<>();
Map<ExecutableElement, List<ExecutableElement>> overriddenByTable = new HashMap<>();
for (VisibleMemberTable pvmt : parents) {
// Merge the lineage overrides into local table
pvmt.overriddenMethodTable.entrySet().forEach(e -> {
OverridingMethodInfo p = e.getValue();
if (!p.simpleOverride) { // consider only real overrides
List<ExecutableElement> list = overriddenByTable.computeIfAbsent(p.overrider,
k -> new ArrayList<>());
list.add(e.getKey());
}
});
inheritedMethods.addAll(pvmt.getAllVisibleMembers(Kind.METHODS));
// Copy the extra members (if any) from the lineage.
if (!utils.shouldDocument(pvmt.te)) {
List<? extends Element> extraMethods = pvmt.getExtraMembers(Kind.METHODS);
if (lmt.getOrderedMembers(Kind.METHODS).isEmpty()) {
inheritedMethods.addAll(extraMethods);
continue;
}
// Check if an extra-method ought to percolate through.
for (Element extraMethod : extraMethods) {
boolean found = false;
List<Element> lmethods = lmt.getMembers(extraMethod, Kind.METHODS);
for (Element lmethod : lmethods) {
ExecutableElement method = (ExecutableElement)lmethod;
found = utils.elementUtils.overrides(method,
(ExecutableElement)extraMethod, te);
if (found)
break;
}
if (!found)
inheritedMethods.add(extraMethod);
}
}
}
// Filter out inherited methods that:
// a. cannot override (private instance members)
// b. are overridden and should not be visible in this type
// c. are hidden in the type being considered
// see allowInheritedMethods, which performs the above actions
List<Element> list = inheritedMethods.stream()
.filter(e -> allowInheritedMethods((ExecutableElement)e, overriddenByTable, lmt))
.collect(Collectors.toList());
// Filter out the local methods, that do not override or simply
// overrides a super method, or those methods that should not
// be visible.
Predicate<ExecutableElement> isVisible = m -> {
OverridingMethodInfo p = overriddenMethodTable.getOrDefault(m, null);
return p == null || !p.simpleOverride;
};
List<Element> mlist = lmt.getOrderedMembers(Kind.METHODS);
List<Element> llist = mlist.stream()
.map(m -> (ExecutableElement)m)
.filter(isVisible)
.collect(Collectors.toList());
// Merge the above lists, making sure the local methods precede
// the others
list.addAll(0, llist);
// Final filtration of elements
list = list.stream()
.filter(this::mustDocument)
.collect(Collectors.toList());
visibleMembers.put(Kind.METHODS, Collections.unmodifiableList(list));
// Copy over overridden tables from the lineage, and finish up.
for (VisibleMemberTable pvmt : parents) {
overriddenMethodTable.putAll(pvmt.overriddenMethodTable);
}
overriddenMethodTable = Collections.unmodifiableMap(overriddenMethodTable);
}
boolean isEnclosureInterface(Element e) {
TypeElement enclosing = utils.getEnclosingTypeElement(e);
return utils.isInterface(enclosing);
}
boolean allowInheritedMethods(ExecutableElement inheritedMethod,
Map<ExecutableElement, List<ExecutableElement>> inheritedOverriddenTable,
LocalMemberTable lmt) {
if (!isInherited(inheritedMethod))
return false;
final boolean haveStatic = utils.isStatic(inheritedMethod);
final boolean inInterface = isEnclosureInterface(inheritedMethod);
// Static methods in interfaces are never documented.
if (haveStatic && inInterface) {
return false;
}
// Multiple-Inheritance: remove the interface method that may have
// been overridden by another interface method in the hierarchy
//
// Note: The following approach is very simplistic and is compatible
// with old VMM. A future enhancement, may include a contention breaker,
// to correctly eliminate those methods that are merely definitions
// in favor of concrete overriding methods, for instance those that have
// API documentation and are not abstract OR default methods.
if (inInterface) {
List<ExecutableElement> list = inheritedOverriddenTable.get(inheritedMethod);
if (list != null) {
boolean found = list.stream()
.anyMatch(this::isEnclosureInterface);
if (found)
return false;
}
}
Elements elementUtils = config.docEnv.getElementUtils();
// Check the local methods in this type.
List<Element> lMethods = lmt.getMembers(inheritedMethod, Kind.METHODS);
for (Element lMethod : lMethods) {
// Ignore private methods or those methods marked with
// a "hidden" tag.
if (utils.isPrivate(lMethod))
continue;
// Remove methods that are "hidden", in JLS terms.
if (haveStatic && utils.isStatic(lMethod) &&
elementUtils.hides(lMethod, inheritedMethod)) {
return false;
}
// Check for overriding methods.
if (elementUtils.overrides((ExecutableElement)lMethod, inheritedMethod,
utils.getEnclosingTypeElement(lMethod))) {
// Disallow package-private super methods to leak in
TypeElement encl = utils.getEnclosingTypeElement(inheritedMethod);
if (isUndocumentedEnclosure(encl)) {
overriddenMethodTable.computeIfAbsent((ExecutableElement)lMethod,
l -> new OverridingMethodInfo(inheritedMethod, false));
return false;
}
boolean simpleOverride = utils.isSimpleOverride((ExecutableElement)lMethod);
overriddenMethodTable.computeIfAbsent((ExecutableElement)lMethod,
l -> new OverridingMethodInfo(inheritedMethod, simpleOverride));
return simpleOverride;
}
}
return true;
}
/*
* This class encapsulates the details of local members, orderedMembers
* contains the members in the declaration order, additionally a
* HashMap is maintained for performance optimization to lookup
* members. As a future enhancement is perhaps to consolidate the ordering
* into a Map, capturing the insertion order, thereby eliminating an
* ordered list.
*/
class LocalMemberTable {
// Maintains declaration order
private final Map<Kind, List<Element>> orderedMembers;
// Performance optimization
private final Map<Kind, Map<String, List<Element>>> memberMap;
LocalMemberTable() {
orderedMembers = new EnumMap<>(Kind.class);
memberMap = new EnumMap<>(Kind.class);
List<? extends Element> elements = te.getEnclosedElements();
for (Element e : elements) {
if (config.nodeprecated && utils.isDeprecated(e)) {
continue;
}
switch (e.getKind()) {
case CLASS:
case INTERFACE:
case ENUM:
case ANNOTATION_TYPE:
addMember(e, Kind.INNER_CLASSES);
break;
case FIELD:
addMember(e, Kind.FIELDS);
addMember(e, Kind.ANNOTATION_TYPE_FIELDS);
break;
case METHOD:
ExecutableElement ee = (ExecutableElement)e;
if (utils.isAnnotationType(te)) {
addMember(e, ee.getDefaultValue() == null
? Kind.ANNOTATION_TYPE_MEMBER_REQUIRED
: Kind.ANNOTATION_TYPE_MEMBER_OPTIONAL);
}
addMember(e, Kind.METHODS);
break;
case CONSTRUCTOR:
addMember(e, Kind.CONSTRUCTORS);
break;
case ENUM_CONSTANT:
addMember(e, Kind.ENUM_CONSTANTS);
break;
}
}
// Freeze the data structures
for (Kind kind : Kind.values()) {
orderedMembers.computeIfPresent(kind, (k, v) -> Collections.unmodifiableList(v));
orderedMembers.computeIfAbsent(kind, t -> Collections.emptyList());
memberMap.computeIfPresent(kind, (k, v) -> Collections.unmodifiableMap(v));
memberMap.computeIfAbsent(kind, t -> Collections.emptyMap());
}
}
String getMemberKey(Element e) {
return new SimpleElementVisitor9<String, Void>() {
@Override
public String visitExecutable(ExecutableElement e, Void aVoid) {
return e.getSimpleName() + ":" + e.getParameters().size();
}
@Override
protected String defaultAction(Element e, Void aVoid) {
return e.getSimpleName().toString();
}
}.visit(e);
}
void addMember(Element e, Kind kind) {
List<Element> list = orderedMembers.computeIfAbsent(kind, k -> new ArrayList<>());
list.add(e);
Map<String, List<Element>> map = memberMap.computeIfAbsent(kind, k -> new HashMap<>());
list = map.computeIfAbsent(getMemberKey(e), l -> new ArrayList<>());
list.add(e);
}
List<Element> getOrderedMembers(Kind kind) {
return orderedMembers.get(kind);
}
List<Element> getMembers(Element e, Kind kind) {
String key = getMemberKey(e);
return getMembers(key, kind);
}
List<Element> getMembers(String key, Kind kind) {
Map <String, List<Element>> map = memberMap.get(kind);
return map.getOrDefault(key, Collections.emptyList());
}
List<Element> getPropertyMethods(String methodName, int argcount) {
return getMembers(methodName + ":" + argcount, Kind.METHODS).stream()
.filter(m -> (utils.isPublic(m) || utils.isProtected(m)))
.collect(Collectors.toList());
}
}
/**
* The properties triad for a property method.
*/
static class PropertyMembers {
final VariableElement field;
final ExecutableElement getter;
final ExecutableElement setter;
PropertyMembers(VariableElement field, ExecutableElement getter, ExecutableElement setter) {
this.field = field;
this.getter = getter;
this.setter = setter;
}
public String toString() {
return ("field: " + field + ", getter: " + getter + ", setter: " + setter);
}
}
/*
* JavaFX convention notes.
* A JavaFX property-method is a method, which ends with "Property" in
* its name, takes no parameters and typically returns a subtype of javafx.beans.
* ReadOnlyProperty, in the strictest sense. However, it may not always
* be possible for the doclet to have access to j.b.ReadOnlyProperty,
* for this reason the strict check is disabled via an undocumented flag.
*
* Note, a method should not be considered as a property-method,
* if it satisfied the previously stated conditions AND if the
* method begins with "set", "get" or "is".
*
* Supposing we have {@code BooleanProperty acmeProperty()}, then the
* property-name is "acme".
*
* Property field, one may or may not exist and could be private, and
* should match the property-method.
*
* A property-setter is a method starting with "set", and the
* first character of the upper-cased starting character of the property name, the
* method must take 1 argument and must return a <code>void</code>.
*
* Using the above example {@code void setAcme(Something s)} can be
* considered as a property-setter of the property "acme".
*
* A property-getter is a method starting with "get" and the first character
* upper-cased property-name, having no parameters. A method that does not take any
* parameters and starting with "is" and an upper-cased property-name,
* returning a primitive type boolean or BooleanProperty can also be
* considered as a getter, however there must be only one getter for every property.
*
* For example {@code Object getAcme()} is a property-getter, and
* {@code boolean isFoo()}
*/
private void computeVisibleProperties(LocalMemberTable lmt) {
if (!config.javafx)
return;
PropertyUtils pUtils = config.propertyUtils;
List<ExecutableElement> list = visibleMembers.getOrDefault(Kind.METHODS, Collections.emptyList())
.stream()
.map(m -> (ExecutableElement)m)
.filter(pUtils::isPropertyMethod)
.collect(Collectors.toList());
visibleMembers.put(Kind.PROPERTIES, Collections.unmodifiableList(list));
List<ExecutableElement> propertyMethods = list.stream()
.filter(e -> utils.getEnclosingTypeElement(e) == te)
.collect(Collectors.toList());
// Compute additional properties related sundries.
for (ExecutableElement propertyMethod : propertyMethods) {
String baseName = pUtils.getBaseName(propertyMethod);
List<Element> flist = lmt.getMembers(baseName, Kind.FIELDS);
Element field = flist.isEmpty() ? null : flist.get(0);
Element getter = null, setter = null;
List<Element> found = lmt.getPropertyMethods(pUtils.getGetName(propertyMethod), 0);
if (!found.isEmpty()) {
// Getters have zero params, no overloads! pick the first.
getter = found.get(0);
}
if (getter == null) {
// Check if isProperty methods are present ?
found = lmt.getPropertyMethods(pUtils.getIsName(propertyMethod), 0);
if (!found.isEmpty()) {
String propertyTypeName = propertyMethod.getReturnType().toString();
// Check if the return type of property method matches an isProperty method.
if (pUtils.hasIsMethod(propertyMethod)) {
// Getters have zero params, no overloads!, pick the first.
getter = found.get(0);
}
}
}
found = lmt.getPropertyMethods(pUtils.getSetName(propertyMethod), 1);
if (found != null) {
for (Element e : found) {
if (pUtils.isValidSetterMethod((ExecutableElement)e)) {
setter = e;
break;
}
}
}
propertyMap.put(propertyMethod, new PropertyMembers((VariableElement)field,
(ExecutableElement)getter, (ExecutableElement)setter));
// Debugging purposes
// System.out.println("te: " + te + ": " + utils.getEnclosingTypeElement(propertyMethod) +
// ":" + propertyMethod.toString() + "->" + propertyMap.get(propertyMethod));
}
}
// Future cleanups
Map<ExecutableElement, SoftReference<ImplementedMethods>> implementMethodsFinders = new HashMap<>();
private ImplementedMethods getImplementedMethodsFinder(ExecutableElement method) {
SoftReference<ImplementedMethods> imf = implementMethodsFinders.get(method);
// IMF does not exist or referent was gc'ed away ?
if (imf == null || imf.get() == null) {
imf = new SoftReference<>(new ImplementedMethods(method));
implementMethodsFinders.put(method, imf);
}
return imf.get();
}
public List<ExecutableElement> getImplementedMethods(ExecutableElement method) {
ImplementedMethods imf = getImplementedMethodsFinder(method);
return imf.getImplementedMethods().stream()
.filter(m -> getsimplyOverriddenMethod(m) == null)
.collect(Collectors.toList());
}
public TypeMirror getImplementedMethodHolder(ExecutableElement method,
ExecutableElement implementedMethod) {
ImplementedMethods imf = getImplementedMethodsFinder(method);
return imf.getMethodHolder(implementedMethod);
}
private class ImplementedMethods {
private final Map<ExecutableElement, TypeMirror> interfaces = new HashMap<>();
private final List<ExecutableElement> methlist = new ArrayList<>();
private final TypeElement typeElement;
private final ExecutableElement method;
public ImplementedMethods(ExecutableElement method) {
this.method = method;
typeElement = utils.getEnclosingTypeElement(method);
Set<TypeMirror> intfacs = utils.getAllInterfaces(typeElement);
/*
* Search for the method in the list of interfaces. If found check if it is
* overridden by any other subinterface method which this class
* implements. If it is not overidden, add it in the method list.
* Do this recursively for all the extended interfaces for each interface
* from the list.
*/
for (TypeMirror interfaceType : intfacs) {
ExecutableElement found = utils.findMethod(utils.asTypeElement(interfaceType), method);
if (found != null) {
removeOverriddenMethod(found);
if (!overridingMethodFound(found)) {
methlist.add(found);
interfaces.put(found, interfaceType);
}
}
}
}
/**
* Return the list of interface methods which the method passed in the
* constructor is implementing. The search/build order is as follows:
* <pre>
* 1. Search in all the immediate interfaces which this method's class is
* implementing. Do it recursively for the superinterfaces as well.
* 2. Traverse all the superclasses and search recursively in the
* interfaces which those superclasses implement.
*</pre>
*
* @return SortedSet<ExecutableElement> of implemented methods.
*/
List<ExecutableElement> getImplementedMethods() {
return methlist;
}
TypeMirror getMethodHolder(ExecutableElement ee) {
return interfaces.get(ee);
}
/**
* Search in the method list and check if it contains a method which
* is overridden by the method as parameter. If found, remove the
* overridden method from the method list.
*
* @param method Is this method overriding a method in the method list.
*/
private void removeOverriddenMethod(ExecutableElement method) {
TypeElement overriddenClass = utils.overriddenClass(method);
if (overriddenClass != null) {
for (int i = 0; i < methlist.size(); i++) {
TypeElement te = utils.getEnclosingTypeElement(methlist.get(i));
if (te == overriddenClass || utils.isSubclassOf(overriddenClass, te)) {
methlist.remove(i); // remove overridden method
return;
}
}
}
}
/**
* Search in the already found methods' list and check if it contains
* a method which is overriding the method parameter or is the method
* parameter itself.
*
* @param method method to be searched
*/
private boolean overridingMethodFound(ExecutableElement method) {
TypeElement containingClass = utils.getEnclosingTypeElement(method);
for (ExecutableElement listmethod : methlist) {
if (containingClass == utils.getEnclosingTypeElement(listmethod)) {
// it's the same method.
return true;
}
TypeElement te = utils.overriddenClass(listmethod);
if (te == null) {
continue;
}
if (te == containingClass || utils.isSubclassOf(te, containingClass)) {
return true;
}
}
return false;
}
}
/**
* A simple container to encapsulate an overriding method
* and the type of override.
*/
static class OverridingMethodInfo {
final ExecutableElement overrider;
final boolean simpleOverride;
public OverridingMethodInfo(ExecutableElement overrider, boolean simpleOverride) {
this.overrider = overrider;
this.simpleOverride = simpleOverride;
}
}
}