private List getPropertyDescriptors()

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;
    }