in freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospector.java [469:568]
private List<PropertyDescriptor> getPropertyDescriptors(BeanInfo beanInfo, Class<?> clazz) {
PropertyDescriptor[] introspectorPDsArray = beanInfo.getPropertyDescriptors();
List<PropertyDescriptor> introspectorPDs = introspectorPDsArray != null ? Arrays.asList(introspectorPDsArray)
: Collections.<PropertyDescriptor>emptyList();
if (!treatDefaultMethodsAsBeanMembers) {
// java.beans.Introspector was good enough then.
return introspectorPDs;
}
// introspectorPDs contains each property exactly once. But as now we will search them manually too, it can
// happen that we find the same property for multiple times. Worse, because of indexed properties, it's possible
// that we have to merge entries (like one has the normal reader method, the other has the indexed reader
// method), instead of just replacing them in a Map. That's why we have introduced PropertyReaderMethodPair,
// which holds the methods belonging to the same property name. IndexedPropertyDescriptor is not good for that,
// as it can't store two methods whose types are incompatible, and we have to wait until all the merging was
// done to see if the incompatibility goes away.
// This could be Map<String, PropertyReaderMethodPair>, but since we rarely need to do merging, we try to avoid
// creating those and use the source objects as much as possible. Also note that we initialize this lazily.
LinkedHashMap<String, Object /*PropertyReaderMethodPair|Method|PropertyDescriptor*/> mergedPRMPs = null;
// Collect Java 8 default methods that look like property readers into mergedPRMPs:
// (Note that java.beans.Introspector discovers non-accessible public methods, and to emulate that behavior
// here, we don't utilize the accessibleMethods Map, which we might already have at this point.)
for (Method method : clazz.getMethods()) {
if (method.isDefault() && method.getReturnType() != void.class
&& !method.isBridge()) {
Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length == 0
|| paramTypes.length == 1 && paramTypes[0] == int.class /* indexed property reader */) {
String propName = _MethodUtil.getBeanPropertyNameFromReaderMethodName(
method.getName(), method.getReturnType());
if (propName != null) {
if (mergedPRMPs == null) {
// Lazy initialization
mergedPRMPs = new LinkedHashMap<>();
}
if (paramTypes.length == 0) {
mergeInPropertyReaderMethod(mergedPRMPs, propName, method);
} else { // It's an indexed property reader method
mergeInPropertyReaderMethodPair(mergedPRMPs, propName,
new PropertyReaderMethodPair(null, method));
}
}
}
}
} // for clazz.getMethods()
if (mergedPRMPs == null) {
// We had no interfering Java 8 default methods, so we can chose the fast route.
return introspectorPDs;
}
for (PropertyDescriptor introspectorPD : introspectorPDs) {
mergeInPropertyDescriptor(mergedPRMPs, introspectorPD);
}
// Now we convert the PRMPs to PDs, handling case where the normal and the indexed read methods contradict.
List<PropertyDescriptor> mergedPDs = new ArrayList<>(mergedPRMPs.size());
for (Entry<String, Object> entry : mergedPRMPs.entrySet()) {
String propName = entry.getKey();
Object propDescObj = entry.getValue();
if (propDescObj instanceof PropertyDescriptor) {
mergedPDs.add((PropertyDescriptor) propDescObj);
} else {
Method readMethod;
Method indexedReadMethod;
if (propDescObj instanceof Method) {
readMethod = (Method) propDescObj;
indexedReadMethod = null;
} else if (propDescObj instanceof PropertyReaderMethodPair) {
PropertyReaderMethodPair prmp = (PropertyReaderMethodPair) propDescObj;
readMethod = prmp.readMethod;
indexedReadMethod = prmp.indexedReadMethod;
if (readMethod != null && indexedReadMethod != null
&& indexedReadMethod.getReturnType() != readMethod.getReturnType().getComponentType()) {
// Here we copy the java.beans.Introspector behavior: If the array item class is not exactly the
// the same as the indexed read method return type, we say that the property is not indexed.
indexedReadMethod = null;
}
} else {
throw new BugException();
}
try {
mergedPDs.add(
indexedReadMethod != null
? new IndexedPropertyDescriptor(propName,
readMethod, null, indexedReadMethod, null)
: new PropertyDescriptor(propName, readMethod, null));
} catch (IntrospectionException e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Failed creating property descriptor for " + clazz.getName() + " property " + propName,
e);
}
}
}
}
return mergedPDs;
}