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); } } }
Advertisement
Answer
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; } }