diff options
author | Henri Sara <henri.sara@itmill.com> | 2011-04-18 12:13:36 +0000 |
---|---|---|
committer | Henri Sara <henri.sara@itmill.com> | 2011-04-18 12:13:36 +0000 |
commit | 4b13cfab7c0adaefdf8b4cfcb3c68bddf9111dc2 (patch) | |
tree | b3bcc744f31be480720cb5aa7edd5da6925c2e94 /src/com | |
parent | 5e90e458843207793cfa25a3f9d1611bf1b09629 (diff) | |
download | vaadin-framework-4b13cfab7c0adaefdf8b4cfcb3c68bddf9111dc2.tar.gz vaadin-framework-4b13cfab7c0adaefdf8b4cfcb3c68bddf9111dc2.zip |
#4995 Nested bean property support
svn changeset:18356/svn branch:6.6
Diffstat (limited to 'src/com')
-rw-r--r-- | src/com/vaadin/data/util/AbstractBeanContainer.java | 145 | ||||
-rw-r--r-- | src/com/vaadin/data/util/BeanItem.java | 41 | ||||
-rw-r--r-- | src/com/vaadin/data/util/MethodProperty.java | 139 | ||||
-rw-r--r-- | src/com/vaadin/data/util/MethodPropertyDescriptor.java | 128 | ||||
-rw-r--r-- | src/com/vaadin/data/util/NestedMethodProperty.java | 200 | ||||
-rw-r--r-- | src/com/vaadin/data/util/NestedPropertyDescriptor.java | 51 | ||||
-rw-r--r-- | src/com/vaadin/data/util/VaadinPropertyDescriptor.java | 40 |
7 files changed, 620 insertions, 124 deletions
diff --git a/src/com/vaadin/data/util/AbstractBeanContainer.java b/src/com/vaadin/data/util/AbstractBeanContainer.java index fab61786d1..1daa4050c5 100644 --- a/src/com/vaadin/data/util/AbstractBeanContainer.java +++ b/src/com/vaadin/data/util/AbstractBeanContainer.java @@ -3,17 +3,16 @@ */ package com.vaadin.data.util; -import java.beans.PropertyDescriptor; -import java.io.IOException; import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; +import com.vaadin.data.Container; import com.vaadin.data.Container.Filterable; +import com.vaadin.data.Container.PropertySetChangeNotifier; import com.vaadin.data.Container.SimpleFilterable; import com.vaadin.data.Container.Sortable; import com.vaadin.data.Item; @@ -21,6 +20,7 @@ import com.vaadin.data.Property; import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.Property.ValueChangeNotifier; +import com.vaadin.data.util.MethodProperty.MethodException; import com.vaadin.data.util.filter.SimpleStringFilter; import com.vaadin.data.util.filter.UnsupportedFilterException; @@ -40,11 +40,6 @@ import com.vaadin.data.util.filter.UnsupportedFilterException; * {@link #addItemAt(int, Object, Object)}. * </p> * - * <p> - * It is not possible to add additional properties to the container and nested - * bean properties are not supported. - * </p> - * * @param <IDTYPE> * The type of the item identifier * @param <BEANTYPE> @@ -54,7 +49,8 @@ import com.vaadin.data.util.filter.UnsupportedFilterException; */ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends AbstractInMemoryContainer<IDTYPE, String, BeanItem<BEANTYPE>> implements - Filterable, SimpleFilterable, Sortable, ValueChangeListener { + Filterable, SimpleFilterable, Sortable, ValueChangeListener, + PropertySetChangeNotifier { /** * Resolver that maps beans to their (item) identifiers, removing the need @@ -90,7 +86,6 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends BeanIdResolver<IDTYPE, BEANTYPE> { private final Object propertyId; - private transient Method getMethod; public PropertyBasedBeanIdResolver(Object propertyId) { if (propertyId == null) { @@ -100,25 +95,18 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends this.propertyId = propertyId; } - private Method getGetter() throws IllegalStateException { - if (getMethod == null) { - if (!model.containsKey(propertyId)) { - throw new IllegalStateException("Property " + propertyId - + " not found"); - } - getMethod = model.get(propertyId).getReadMethod(); - } - return getMethod; - } - @SuppressWarnings("unchecked") public IDTYPE getIdForBean(BEANTYPE bean) throws IllegalArgumentException { + VaadinPropertyDescriptor<BEANTYPE> pd = model.get(propertyId); + if (null == pd) { + throw new IllegalStateException("Property " + propertyId + + " not found"); + } try { - return (IDTYPE) getGetter().invoke(bean); - } catch (IllegalAccessException e) { - throw new IllegalArgumentException(e); - } catch (InvocationTargetException e) { + Property property = pd.createProperty(bean); + return (IDTYPE) property.getValue(); + } catch (MethodException e) { throw new IllegalArgumentException(e); } } @@ -149,7 +137,7 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends * A description of the properties found in beans of type {@link #type}. * Determines the property ids that are present in the container. */ - private transient LinkedHashMap<String, PropertyDescriptor> model; + private LinkedHashMap<String, VaadinPropertyDescriptor<BEANTYPE>> model; /** * Constructs a {@code AbstractBeanContainer} for beans of the given type. @@ -165,17 +153,7 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends "The bean type passed to AbstractBeanContainer must not be null"); } this.type = type; - model = BeanItem.getPropertyDescriptors(type); - } - - /** - * A special deserialization method that resolves {@link #model} is needed - * as PropertyDescriptor is not {@link Serializable}. - */ - private void readObject(java.io.ObjectInputStream in) throws IOException, - ClassNotFoundException { - in.defaultReadObject(); - model = BeanItem.getPropertyDescriptors(type); + model = BeanItem.getPropertyDescriptors((Class<BEANTYPE>) type); } /* @@ -725,4 +703,95 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends return new PropertyBasedBeanIdResolver(propertyId); } + @Override + public void addListener(Container.PropertySetChangeListener listener) { + super.addListener(listener); + } + + @Override + public void removeListener(Container.PropertySetChangeListener listener) { + super.removeListener(listener); + } + + @Override + public boolean addContainerProperty(Object propertyId, Class<?> type, + Object defaultValue) throws UnsupportedOperationException { + throw new UnsupportedOperationException( + "Use addNestedContainerProperty(String) to add container properties to a " + + getClass().getSimpleName()); + } + + /** + * Adds a property for the container and all its items. + * + * Primarily for internal use, may change in future versions. + * + * @param propertyId + * @param propertyDescriptor + * @return true if the property was added + */ + protected final boolean addContainerProperty(String propertyId, + VaadinPropertyDescriptor<BEANTYPE> propertyDescriptor) { + if (null == propertyId || null == propertyDescriptor) { + return false; + } + + // Fails if the Property is already present + if (model.containsKey(propertyId)) { + return false; + } + + model.put(propertyId, propertyDescriptor); + for (BeanItem item : itemIdToItem.values()) { + item.addItemProperty(propertyId, propertyDescriptor + .createProperty((BEANTYPE) item.getBean())); + } + + // Sends a change event + fireContainerPropertySetChange(); + + return true; + } + + /** + * Adds a nested container property for the container, e.g. + * "manager.address.street". + * + * All intermediate getters must exist and must return non-null values when + * the property value is accessed. + * + * @see NestedMethodProperty + * + * @param propertyId + * @param propertyType + * @return true if the property was added + */ + public boolean addNestedContainerProperty(String propertyId, + Class<?> propertyType) { + return addContainerProperty(propertyId, new NestedPropertyDescriptor( + propertyId, propertyType)); + } + + @Override + public boolean removeContainerProperty(Object propertyId) + throws UnsupportedOperationException { + // Fails if the Property is not present + if (!model.containsKey(propertyId)) { + return false; + } + + // Removes the Property to Property list and types + model.remove(propertyId); + + // If remove the Property from all Items + for (final Iterator<IDTYPE> i = getAllItemIds().iterator(); i.hasNext();) { + getUnfilteredItem(i.next()).removeItemProperty(propertyId); + } + + // Sends a change event + fireContainerPropertySetChange(); + + return true; + } + } diff --git a/src/com/vaadin/data/util/BeanItem.java b/src/com/vaadin/data/util/BeanItem.java index eb0d2f44c3..bb8dd6cdc2 100644 --- a/src/com/vaadin/data/util/BeanItem.java +++ b/src/com/vaadin/data/util/BeanItem.java @@ -14,8 +14,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; - -import com.vaadin.data.Property; +import java.util.Map; /** * A wrapper class for adding the Item interface to any Java Bean. @@ -51,7 +50,7 @@ public class BeanItem<BT> extends PropertysetItem { * */ public BeanItem(BT bean) { - this(bean, getPropertyDescriptors(bean.getClass())); + this(bean, getPropertyDescriptors((Class<BT>) bean.getClass())); } /** @@ -67,19 +66,12 @@ public class BeanItem<BT> extends PropertysetItem { * pre-computed property descriptors */ BeanItem(BT bean, - LinkedHashMap<String, PropertyDescriptor> propertyDescriptors) { + Map<String, VaadinPropertyDescriptor<BT>> propertyDescriptors) { this.bean = bean; - for (PropertyDescriptor pd : propertyDescriptors.values()) { - final Method getMethod = pd.getReadMethod(); - final Method setMethod = pd.getWriteMethod(); - final Class<?> type = pd.getPropertyType(); - final String name = pd.getName(); - final Property p = new MethodProperty<Object>(type, bean, - getMethod, setMethod); - addItemProperty(name, p); - + for (VaadinPropertyDescriptor<BT> pd : propertyDescriptors.values()) { + addItemProperty(pd.getName(), pd.createProperty(bean)); } } @@ -106,20 +98,14 @@ public class BeanItem<BT> extends PropertysetItem { this.bean = bean; // Create bean information - LinkedHashMap<String, PropertyDescriptor> pds = getPropertyDescriptors(bean + LinkedHashMap<String, VaadinPropertyDescriptor<BT>> pds = getPropertyDescriptors((Class<BT>) bean .getClass()); // Add all the bean properties as MethodProperties to this Item for (Object id : propertyIds) { - PropertyDescriptor pd = pds.get(id); + VaadinPropertyDescriptor<BT> pd = pds.get(id); if (pd != null) { - final String name = pd.getName(); - final Method getMethod = pd.getReadMethod(); - final Method setMethod = pd.getWriteMethod(); - final Class<?> type = pd.getPropertyType(); - final Property p = new MethodProperty<Object>(type, bean, - getMethod, setMethod); - addItemProperty(name, p); + addItemProperty(pd.getName(), pd.createProperty(bean)); } } @@ -162,9 +148,9 @@ public class BeanItem<BT> extends PropertysetItem { * the Java Bean class to get properties for. * @return an ordered map from property names to property descriptors */ - static LinkedHashMap<String, PropertyDescriptor> getPropertyDescriptors( - final Class<?> beanClass) { - final LinkedHashMap<String, PropertyDescriptor> pdMap = new LinkedHashMap<String, PropertyDescriptor>(); + static <BT> LinkedHashMap<String, VaadinPropertyDescriptor<BT>> getPropertyDescriptors( + final Class<BT> beanClass) { + final LinkedHashMap<String, VaadinPropertyDescriptor<BT>> pdMap = new LinkedHashMap<String, VaadinPropertyDescriptor<BT>>(); // Try to introspect, if it fails, we just have an empty Item try { @@ -176,7 +162,10 @@ public class BeanItem<BT> extends PropertysetItem { final Method getMethod = pd.getReadMethod(); if ((getMethod != null) && getMethod.getDeclaringClass() != Object.class) { - pdMap.put(pd.getName(), pd); + VaadinPropertyDescriptor<BT> vaadinPropertyDescriptor = new MethodPropertyDescriptor( + pd.getName(), pd.getPropertyType(), + pd.getReadMethod(), pd.getWriteMethod()); + pdMap.put(pd.getName(), vaadinPropertyDescriptor); } } } catch (final java.beans.IntrospectionException ignored) { diff --git a/src/com/vaadin/data/util/MethodProperty.java b/src/com/vaadin/data/util/MethodProperty.java index 6cfa5e87b7..12b69858da 100644 --- a/src/com/vaadin/data/util/MethodProperty.java +++ b/src/com/vaadin/data/util/MethodProperty.java @@ -106,19 +106,23 @@ public class MethodProperty<T> implements Property, out.writeObject(getArgs); if (setMethod != null) { out.writeObject(setMethod.getName()); + SerializerHelper.writeClass(out, setMethod.getDeclaringClass()); SerializerHelper .writeClassArray(out, setMethod.getParameterTypes()); } else { out.writeObject(null); out.writeObject(null); + out.writeObject(null); } if (getMethod != null) { out.writeObject(getMethod.getName()); + SerializerHelper.writeClass(out, getMethod.getDeclaringClass()); SerializerHelper .writeClassArray(out, getMethod.getParameterTypes()); } else { out.writeObject(null); out.writeObject(null); + out.writeObject(null); } }; @@ -135,17 +139,19 @@ public class MethodProperty<T> implements Property, setArgs = (Object[]) in.readObject(); getArgs = (Object[]) in.readObject(); String name = (String) in.readObject(); + Class<T> setMethodClass = (Class<T>) SerializerHelper.readClass(in); Class<?>[] paramTypes = SerializerHelper.readClassArray(in); if (name != null) { - setMethod = instance.getClass().getMethod(name, paramTypes); + setMethod = setMethodClass.getMethod(name, paramTypes); } else { setMethod = null; } name = (String) in.readObject(); + Class<T> getMethodClass = (Class<T>) SerializerHelper.readClass(in); paramTypes = SerializerHelper.readClassArray(in); if (name != null) { - getMethod = instance.getClass().getMethod(name, paramTypes); + getMethod = getMethodClass.getMethod(name, paramTypes); } else { getMethod = null; } @@ -202,21 +208,10 @@ public class MethodProperty<T> implements Property, // Find the get method getMethod = null; try { - getMethod = beanClass.getMethod("get" + beanPropertyName, - new Class[] {}); + getMethod = initGetterMethod(beanPropertyName, beanClass); } catch (final java.lang.NoSuchMethodException ignored) { - try { - getMethod = beanClass.getMethod("is" + beanPropertyName, - new Class[] {}); - } catch (final java.lang.NoSuchMethodException ignoredAsWell) { - try { - getMethod = beanClass.getMethod("are" + beanPropertyName, - new Class[] {}); - } catch (final java.lang.NoSuchMethodException e) { - throw new MethodException(this, "Bean property " - + beanPropertyName + " can not be found"); - } - } + throw new MethodException(this, "Bean property " + beanPropertyName + + " can not be found"); } // In case the get method is found, resolve the type @@ -232,23 +227,8 @@ public class MethodProperty<T> implements Property, // Gets the return type from get method if (returnType.isPrimitive()) { - if (returnType.equals(Boolean.TYPE)) { - type = (Class<T>) Boolean.class; - } else if (returnType.equals(Integer.TYPE)) { - type = (Class<T>) Integer.class; - } else if (returnType.equals(Float.TYPE)) { - type = (Class<T>) Float.class; - } else if (returnType.equals(Double.TYPE)) { - type = (Class<T>) Double.class; - } else if (returnType.equals(Byte.TYPE)) { - type = (Class<T>) Byte.class; - } else if (returnType.equals(Character.TYPE)) { - type = (Class<T>) Character.class; - } else if (returnType.equals(Short.TYPE)) { - type = (Class<T>) Short.class; - } else if (returnType.equals(Long.TYPE)) { - type = (Class<T>) Long.class; - } else { + type = (Class<T>) convertPrimitiveType(returnType); + if (type.isPrimitive()) { throw new MethodException(this, "Bean property " + beanPropertyName + " getter return type must not be void"); @@ -484,25 +464,7 @@ public class MethodProperty<T> implements Property, } // Gets the return type from get method - if (type.isPrimitive()) { - if (type.equals(Boolean.TYPE)) { - this.type = (Class<T>) Boolean.class; - } else if (type.equals(Integer.TYPE)) { - this.type = (Class<T>) Integer.class; - } else if (type.equals(Float.TYPE)) { - this.type = (Class<T>) Float.class; - } else if (type.equals(Double.TYPE)) { - this.type = (Class<T>) Double.class; - } else if (type.equals(Byte.TYPE)) { - this.type = (Class<T>) Byte.class; - } else if (type.equals(Character.TYPE)) { - this.type = (Class<T>) Character.class; - } else if (type.equals(Short.TYPE)) { - this.type = (Class<T>) Short.class; - } else if (type.equals(Long.TYPE)) { - this.type = (Class<T>) Long.class; - } - } + this.type = (Class<T>) convertPrimitiveType(type); setArguments(getArgs, setArgs, setArgumentIndex); readOnly = (setMethod == null); @@ -560,6 +522,50 @@ public class MethodProperty<T> implements Property, } // Gets the return type from get method + type = convertPrimitiveType(type); + + this.getMethod = getMethod; + this.setMethod = setMethod; + setArguments(getArgs, setArgs, setArgumentIndex); + readOnly = (setMethod == null); + this.instance = instance; + this.type = type; + } + + /** + * Find a getter method for a property (getXyz(), isXyz() or areXyz()). + * + * @param propertyName + * name of the property + * @param beanClass + * class in which to look for the getter methods + * @return Method + * @throws NoSuchMethodException + * if no getter found + */ + protected static Method initGetterMethod(String propertyName, + final Class<?> beanClass) throws NoSuchMethodException { + propertyName = propertyName.substring(0, 1).toUpperCase() + + propertyName.substring(1); + + Method getMethod = null; + try { + getMethod = beanClass.getMethod("get" + propertyName, + new Class[] {}); + } catch (final java.lang.NoSuchMethodException ignored) { + try { + getMethod = beanClass.getMethod("is" + propertyName, + new Class[] {}); + } catch (final java.lang.NoSuchMethodException ignoredAsWell) { + getMethod = beanClass.getMethod("are" + propertyName, + new Class[] {}); + } + } + return getMethod; + } + + protected static Class<?> convertPrimitiveType(Class<?> type) { + // Gets the return type from get method if (type.isPrimitive()) { if (type.equals(Boolean.TYPE)) { type = Boolean.class; @@ -579,13 +585,7 @@ public class MethodProperty<T> implements Property, type = Long.class; } } - - this.getMethod = getMethod; - this.setMethod = setMethod; - setArguments(getArgs, setArgs, setArgumentIndex); - readOnly = (setMethod == null); - this.instance = instance; - this.type = type; + return type; } /** @@ -725,7 +725,7 @@ public class MethodProperty<T> implements Property, * * @param value */ - private void invokeSetMethod(Object value) { + protected void invokeSetMethod(Object value) { try { // Construct a temporary argument array only if needed @@ -749,6 +749,25 @@ public class MethodProperty<T> implements Property, } /** + * Returns the bean instance which to which the property applies. For + * internal use. + * + * @return bean instance + */ + protected Object getInstance() { + return instance; + } + + /** + * Returns the setter method to use. For internal use. + * + * @return setter {@link Method} + */ + protected Method getSetMethod() { + return setMethod; + } + + /** * Sets the Property's read-only mode to the specified status. * * @param newStatus diff --git a/src/com/vaadin/data/util/MethodPropertyDescriptor.java b/src/com/vaadin/data/util/MethodPropertyDescriptor.java new file mode 100644 index 0000000000..4f366d1e1d --- /dev/null +++ b/src/com/vaadin/data/util/MethodPropertyDescriptor.java @@ -0,0 +1,128 @@ +package com.vaadin.data.util; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.vaadin.data.Property; +import com.vaadin.util.SerializerHelper; + +/** + * Property descriptor that is able to create simple {@link MethodProperty} + * instances for a bean, using given accessors. + * + * @param <BT> + * bean type + * + * @since 6.6 + */ +public class MethodPropertyDescriptor<BT> implements + VaadinPropertyDescriptor<BT> { + + private static final Logger logger = Logger + .getLogger(MethodPropertyDescriptor.class.getName()); + + private final String name; + private Class<?> propertyType; + private transient Method readMethod; + private transient Method writeMethod; + + /** + * Creates a property descriptor that can create MethodProperty instances to + * access the underlying bean property. + * + * @param name + * of the property + * @param propertyType + * type (class) of the property + * @param readMethod + * getter {@link Method} for the property + * @param writeMethod + * setter {@link Method} for the property or null if read-only + * property + */ + public MethodPropertyDescriptor(String name, Class<?> propertyType, + Method readMethod, Method writeMethod) { + this.name = name; + this.propertyType = propertyType; + this.readMethod = readMethod; + this.writeMethod = writeMethod; + } + + /* Special serialization to handle method references */ + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + SerializerHelper.writeClass(out, propertyType); + + if (writeMethod != null) { + out.writeObject(writeMethod.getName()); + SerializerHelper.writeClass(out, writeMethod.getDeclaringClass()); + SerializerHelper.writeClassArray(out, + writeMethod.getParameterTypes()); + } else { + out.writeObject(null); + out.writeObject(null); + out.writeObject(null); + } + + if (readMethod != null) { + out.writeObject(readMethod.getName()); + SerializerHelper.writeClass(out, readMethod.getDeclaringClass()); + SerializerHelper.writeClassArray(out, + readMethod.getParameterTypes()); + } else { + out.writeObject(null); + out.writeObject(null); + out.writeObject(null); + } + } + + /* Special serialization to handle method references */ + private void readObject(java.io.ObjectInputStream in) throws IOException, + ClassNotFoundException { + in.defaultReadObject(); + try { + @SuppressWarnings("unchecked") + // business assumption; type parameters not checked at runtime + Class<BT> class1 = (Class<BT>) SerializerHelper.readClass(in); + propertyType = class1; + + String name = (String) in.readObject(); + Class<?> writeMethodClass = SerializerHelper.readClass(in); + Class<?>[] paramTypes = SerializerHelper.readClassArray(in); + if (name != null) { + writeMethod = writeMethodClass.getMethod(name, paramTypes); + } else { + writeMethod = null; + } + + name = (String) in.readObject(); + Class<?> readMethodClass = SerializerHelper.readClass(in); + paramTypes = SerializerHelper.readClassArray(in); + if (name != null) { + readMethod = readMethodClass.getMethod(name, paramTypes); + } else { + readMethod = null; + } + } catch (SecurityException e) { + logger.log(Level.SEVERE, "Internal deserialization error", e); + } catch (NoSuchMethodException e) { + logger.log(Level.SEVERE, "Internal deserialization error", e); + } + }; + + public String getName() { + return name; + } + + public Class<?> getPropertyType() { + return propertyType; + } + + public Property createProperty(Object bean) { + return new MethodProperty<Object>(propertyType, bean, readMethod, + writeMethod); + } + +}
\ No newline at end of file diff --git a/src/com/vaadin/data/util/NestedMethodProperty.java b/src/com/vaadin/data/util/NestedMethodProperty.java new file mode 100644 index 0000000000..12250211aa --- /dev/null +++ b/src/com/vaadin/data/util/NestedMethodProperty.java @@ -0,0 +1,200 @@ +package com.vaadin.data.util; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Nested accessor based property for a bean. + * + * The property is specified in the dotted notation, e.g. "address.street", and + * can contain multiple levels of nesting. + * + * When accessing the property value, all intermediate getters must return + * non-null values. + * + * @see MethodProperty + * + * @param <T> + * property type + * + * @since 6.6 + */ +public class NestedMethodProperty<T> extends MethodProperty<T> { + + // needed for de-serialization + private String propertyName; + + // chain of getter methods up to but not including the last method handled + // by the superclass + private transient List<Method> getMethods; + + /* Special serialization to handle method references */ + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + // getMethods is reconstructed on read based on propertyName + } + + /* Special serialization to handle method references */ + private void readObject(java.io.ObjectInputStream in) throws IOException, + ClassNotFoundException { + in.defaultReadObject(); + + // re-build getMethods: some duplicated code with builder method + getMethods = new ArrayList<Method>(); + Class<?> propertyClass = getInstance().getClass(); + String[] simplePropertyNames = propertyName.split("\\."); + for (int i = 0; i < simplePropertyNames.length; i++) { + String simplePropertyName = simplePropertyNames[i].trim(); + try { + Method getter = initGetterMethod(simplePropertyName, + propertyClass); + propertyClass = getter.getReturnType(); + getMethods.add(getter); + } catch (final java.lang.NoSuchMethodException e) { + throw new InvalidObjectException("Bean property '" + + simplePropertyName + "' not found"); + } + } + } + + /** + * Constructs a nested method property for a given object instance. The + * property name is a dot separated string pointing to a nested property, + * e.g. "manager.address.street". + * + * @param <T> + * property type (deepest nested property) + * @param instance + * top-level bean to which the property applies + * @param propertyName + * dot separated nested property name + * @return new NestedMethodProperty instance + */ + public static <T> NestedMethodProperty<T> buildNestedMethodProperty( + Object instance, String propertyName) { + List<Method> getMethods = new ArrayList<Method>(); + + String lastSimplePropertyName = propertyName; + Class<?> lastClass = instance.getClass(); + + // first top-level property, then go deeper in a loop + Class<?> propertyClass = instance.getClass(); + String[] simplePropertyNames = propertyName.split("\\."); + if (propertyName.endsWith(".") || 0 == simplePropertyNames.length) { + throw new MethodException(null, "Invalid property name '" + + propertyName + "'"); + } + for (int i = 0; i < simplePropertyNames.length; i++) { + String simplePropertyName = simplePropertyNames[i].trim(); + if (simplePropertyName.length() > 0) { + lastSimplePropertyName = simplePropertyName; + lastClass = propertyClass; + try { + Method getter = initGetterMethod(simplePropertyName, + propertyClass); + propertyClass = getter.getReturnType(); + getMethods.add(getter); + } catch (final java.lang.NoSuchMethodException e) { + throw new MethodException(null, "Bean property '" + + simplePropertyName + "' not found"); + } + } else { + throw new MethodException(null, + "Empty or invalid bean property identifier in '" + + propertyName + "'"); + } + } + + // In case the get method is found, resolve the type + Method lastGetMethod = getMethods.get(getMethods.size() - 1); + Class<?> type = lastGetMethod.getReturnType(); + + // Finds the set method + Method setMethod = null; + try { + // Assure that the first letter is upper cased (it is a common + // mistake to write firstName, not FirstName). + if (Character.isLowerCase(lastSimplePropertyName.charAt(0))) { + final char[] buf = lastSimplePropertyName.toCharArray(); + buf[0] = Character.toUpperCase(buf[0]); + lastSimplePropertyName = new String(buf); + } + + setMethod = lastClass.getMethod("set" + lastSimplePropertyName, + new Class[] { type }); + } catch (final NoSuchMethodException skipped) { + } + + NestedMethodProperty<T> property = new NestedMethodProperty<T>( + (Class<T>) convertPrimitiveType(type), instance, propertyName, + lastGetMethod, setMethod); + property.getMethods = getMethods; + + return property; + } + + protected NestedMethodProperty(Class<T> type, Object instance, + String propertyName, Method lastGetMethod, Method setMethod) { + super(type, instance, lastGetMethod, setMethod); + this.propertyName = propertyName; + } + + /** + * Gets the value stored in the Property. The value is resolved by calling + * the specified getter method with the argument specified at instantiation. + * + * @return the value of the Property + */ + @Override + public Object getValue() { + try { + Object instance = getInstance(); + for (Method m : getMethods) { + instance = m.invoke(instance); + } + return instance; + } catch (final Throwable e) { + throw new MethodException(this, e); + } + } + + /** + * Internal method to actually call the setter method of the wrapped + * property. + * + * @param value + */ + @Override + protected void invokeSetMethod(Object value) { + try { + Object instance = getInstance(); + for (int i = 0; i < getMethods.size() - 1; i++) { + instance = getMethods.get(i).invoke(instance); + } + getSetMethod().invoke(instance, new Object[] { value }); + } catch (final InvocationTargetException e) { + throw new MethodException(this, e.getTargetException()); + } catch (final Exception e) { + throw new MethodException(this, e); + } + } + + /** + * Returns an unmodifiable list of getter methods to call in sequence to get + * the property value. + * + * This API may change in future versions. + * + * @return unmodifiable list of getter methods corresponding to each segment + * of the property name + */ + protected List<Method> getGetMethods() { + return Collections.unmodifiableList(getMethods); + } + +} diff --git a/src/com/vaadin/data/util/NestedPropertyDescriptor.java b/src/com/vaadin/data/util/NestedPropertyDescriptor.java new file mode 100644 index 0000000000..e916592196 --- /dev/null +++ b/src/com/vaadin/data/util/NestedPropertyDescriptor.java @@ -0,0 +1,51 @@ +package com.vaadin.data.util; + +import com.vaadin.data.Property; + +/** + * Property descriptor that is able to create nested property instances for a + * bean. + * + * The property is specified in the dotted notation, e.g. "address.street", and + * can contain multiple levels of nesting. + * + * @param <BT> + * bean type + * + * @since 6.6 + */ +public class NestedPropertyDescriptor<BT> implements + VaadinPropertyDescriptor<BT> { + + private final String name; + private final Class<?> propertyType; + + /** + * Creates a property descriptor that can create MethodProperty instances to + * access the underlying bean property. + * + * The property is only validated when trying to access its value. + * + * @param name + * of the property in a dotted path format, e.g. "address.street" + * @param propertyType + * type (class) of the property + */ + public NestedPropertyDescriptor(String name, Class<?> propertyType) { + this.name = name; + this.propertyType = propertyType; + } + + public String getName() { + return name; + } + + public Class<?> getPropertyType() { + return propertyType; + } + + public Property createProperty(BT bean) { + return NestedMethodProperty.buildNestedMethodProperty(bean, name); + } + +} diff --git a/src/com/vaadin/data/util/VaadinPropertyDescriptor.java b/src/com/vaadin/data/util/VaadinPropertyDescriptor.java new file mode 100644 index 0000000000..b6ae89f41e --- /dev/null +++ b/src/com/vaadin/data/util/VaadinPropertyDescriptor.java @@ -0,0 +1,40 @@ +package com.vaadin.data.util; + +import java.io.Serializable; + +import com.vaadin.data.Property; + +/** + * Property descriptor that can create a property instance for a bean. + * + * Used by {@link BeanItem} and {@link AbstractBeanContainer} to keep track of + * the set of properties of items. + * + * @param <BT> + * bean type + * + * @since 6.6 + */ +public interface VaadinPropertyDescriptor<BT> extends Serializable { + /** + * Returns the name of the property. + * + * @return + */ + public String getName(); + + /** + * Returns the type of the property. + * + * @return Class<?> + */ + public Class<?> getPropertyType(); + + /** + * Creates a new {@link Property} instance for this property for a bean. + * + * @param bean + * @return + */ + public Property createProperty(BT bean); +}
\ No newline at end of file |