Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is JRE 1.8 still JavaBean specs compliant about IndexedPropertyDescriptor?

This question seems awkward but we are facing a strange behaviour while retrieving the PropertyDescriptors of a javabean. Here are the execution results on 1.6, 1.7 and 1.8 of a simple piece of code, compiled with 1.6 compliance.

Java 1.6 execution:

java.beans.PropertyDescriptor@4ddc1428 <- Not important java.beans.IndexedPropertyDescriptor@7174807e <- Yes I have an indexed property

Java 1.7 execution:

java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()] <- Not important java.beans.IndexedPropertyDescriptor[name=values; indexedPropertyType=class java.lang.String; indexedReadMethod=public java.lang.String JavaBean.getValues(int)] <- Yes I have an indexed property

Java 1.8 execution:

java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()] <- Not important java.beans.PropertyDescriptor[name=values; propertyType=interface java.util.List; readMethod=public java.util.List JavaBean.getValues()] <- Ouch! This is not an indexed property anymore!

Why has it changed?

The javabean specs states about accessing a property with an index. It is not said as mandatory to use an array as the container of the indexed property. Am I wrong?

I read the specs and chapter 8.3.3 talks about Design Patterns for Indexed properties, not the strict rule.

How to make the previous behaviour coming back again without refactoring all the app ? < Old application, lot of code to modify, etc...

Thanks for the answers,

JavaBean class

import java.util.ArrayList;  
import java.util.List;  


public class JavaBean {  


  private List<String> values = new ArrayList<String>();  


  public String getValues(int index) {  
  return this.values.get(index);  
  }  


  public List<String> getValues() {  
  return this.values;  
  }  
}  

Main class

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;

public class Test {
    public static void main(String[] args) throws IntrospectionException {
         PropertyDescriptor[] descs =
         Introspector.getBeanInfo(JavaBean.class).getPropertyDescriptors();
         for (PropertyDescriptor pd : descs) {
         System.out.println(pd);
         }
    }
}
like image 594
Fabien BUSSINGER Avatar asked Sep 25 '22 17:09

Fabien BUSSINGER


2 Answers

From JavaBeans 1.01 specification, section 7.2 “Indexed properties”:

A component may also expose an indexed property as a single array value.

Section 8.3 is describing the design patterns which introspection recognizes, in the absence of explicit BeanInfo. Section 8.3.3 is saying that only array properties will trigger automatic recognition of indexed properties.

You're technically correct; it is not mandatory to use an array. But if you don't, the spec says you have to provide your own BeanInfo to expose the property as an indexed property.

So the answer to your question's title is: Yes, Java 1.8 is JavaBean specs compliant.

I'm not sure why List properties were ever supported. Maybe a future JavaBeans specification was going to support them which has since been withdrawn.

As to your final question: I think you'll have to create a BeanInfo class for each class with List properties. I expect you can create a general superclass to make it easier, something like:

public abstract class ListRecognizingBeanInfo
extends SimpleBeanInfo {

    private final BeanDescriptor beanDesc;
    private final PropertyDescriptor[] propDesc;

    protected ListRecognizingBeanInfo(Class<?> beanClass)
    throws IntrospectionException {
        beanDesc = new BeanDescriptor(beanClass);

        List<PropertyDescriptor> desc = new ArrayList<>();

        for (Method method : beanClass.getMethods()) {
            int modifiers = method.getModifiers();
            Class<?> type = method.getReturnType();

            if (Modifier.isPublic(modifiers) &&
                !Modifier.isStatic(modifiers) &&
                !type.equals(Void.TYPE) &&
                method.getParameterCount() == 0) {

                String name = method.getName();
                String remainder;
                if (name.startsWith("get")) {
                    remainder = name.substring(3);
                } else if (name.startsWith("is") &&
                           type.equals(Boolean.TYPE)) {
                    remainder = name.substring(2);
                } else {
                    continue;
                }

                if (remainder.isEmpty()) {
                    continue;
                }

                String propName = Introspector.decapitalize(remainder);

                Method writeMethod = null;
                Method possibleWriteMethod =
                    findMethod(beanClass, "set" + remainder, type);
                if (possibleWriteMethod != null &&
                    possibleWriteMethod.getReturnType().equals(Void.TYPE)) {

                    writeMethod = possibleWriteMethod;
                }

                Class<?> componentType = null;
                if (type.isArray()) {
                    componentType = type.getComponentType();
                } else {
                    Type genType = method.getGenericReturnType();
                    if (genType instanceof ParameterizedType) {
                        ParameterizedType p = (ParameterizedType) genType;
                        if (p.getRawType().equals(List.class)) {
                            Type[] argTypes = p.getActualTypeArguments();
                            if (argTypes[0] instanceof Class) {
                                componentType = (Class<?>) argTypes[0];
                            }
                        }
                    }
                }

                Method indexedReadMethod = null;
                Method indexedWriteMethod = null;

                if (componentType != null) {
                    Method possibleReadMethod =
                        findMethod(beanClass, name, Integer.TYPE);
                    Class<?> idxType = possibleReadMethod.getReturnType();
                    if (idxType.equals(componentType)) {
                        indexedReadMethod = possibleReadMethod;
                    }

                    if (writeMethod != null) {
                        possibleWriteMethod =
                            findMethod(beanClass, writeMethod.getName(),
                                Integer.TYPE, componentType);
                        if (possibleWriteMethod != null &&
                            possibleWriteMethod.getReturnType().equals(
                                Void.TYPE)) {

                            indexedWriteMethod = possibleWriteMethod;
                        }
                    }
                }

                if (indexedReadMethod != null) {
                    desc.add(new IndexedPropertyDescriptor(propName,
                        method, writeMethod,
                        indexedReadMethod, indexedWriteMethod));
                } else {
                    desc.add(new PropertyDescriptor(propName,
                        method, writeMethod));
                }
            }
        }

        propDesc = desc.toArray(new PropertyDescriptor[0]);
    }

    private static Method findMethod(Class<?> cls,
                                     String name,
                                     Class<?>... paramTypes) {
        try {
            Method method = cls.getMethod(name, paramTypes);
            int modifiers = method.getModifiers();
            if (Modifier.isPublic(modifiers) &&
                !Modifier.isStatic(modifiers)) {

                return method;
            }
        } catch (NoSuchMethodException e) {
        }

        return null;
    }

    @Override
    public BeanDescriptor getBeanDescriptor() {
        return beanDesc;
    }

    @Override
    public PropertyDescriptor[] getPropertyDescriptors() {
        return propDesc;
    }
}
like image 187
VGR Avatar answered Nov 02 '22 06:11

VGR


I am facing the same issue. I am trying to save StartDate and End date as List from JSP but it is not saved and values are wiped out. In my project, there are start date and end date fields. I debugged BeanUtilsBean then I observed that fields do not have writeMethod. I have added one more setter method for each field in my class and it works.

java.beans.PropertyDescriptor[name=startDateStrings; propertyType=interface java.util.List; readMethod=public java.util.List com.webapp.tradingpartners.TradingPartnerNewForm.getStartDateStrings()]

This is a JavaBeans crapspec problem, which only allows void setters. If you want this-returning setters, then you can't have JavaBeans compatibility and there's nothing Lombok could do about it.

In theory, you could generate two setters, but then you'd have to call them differently and having two setters per field is simply too bad.

<cc:dateInput property='<%= "startDateStrings[" + row + "]" %>' onchange="setPropertyChangedFlag()"/>                     
<cc:dateInput property='<%= "endDateStrings[" + row + "]" %>' onchange="setPropertyChangedFlag()"/>                     
public List<String> getStartDateStrings() {
  return startDateStrings;
}
public String getStartDateStrings(int index) {
    return startDateStrings.get(index);
  }
public void setStartDateStrings(int index, String value) {
    startDateStrings.set(index, value);
  }
public List<String> getEndDateStrings() {
    return endDateStrings;
  }
public String getEndDateStrings(int index) {
    return endDateStrings.get(index);
  }
public void setEndDateStrings(int index, String value) {
    endDateStrings.set(index, value);
like image 22
Sunil Kumar Suman Avatar answered Nov 02 '22 06:11

Sunil Kumar Suman