From 4b13cfab7c0adaefdf8b4cfcb3c68bddf9111dc2 Mon Sep 17 00:00:00 2001 From: Henri Sara Date: Mon, 18 Apr 2011 12:13:36 +0000 Subject: [PATCH] #4995 Nested bean property support svn changeset:18356/svn branch:6.6 --- .../data/util/AbstractBeanContainer.java | 145 ++++++-- src/com/vaadin/data/util/BeanItem.java | 41 +-- src/com/vaadin/data/util/MethodProperty.java | 139 ++++---- .../data/util/MethodPropertyDescriptor.java | 128 +++++++ .../data/util/NestedMethodProperty.java | 200 +++++++++++ .../data/util/NestedPropertyDescriptor.java | 51 +++ .../data/util/VaadinPropertyDescriptor.java | 40 +++ .../server/container/BeanContainerTest.java | 38 +- .../container/BeanItemContainerTest.java | 39 +- .../tests/server/container/BeanItemTest.java | 56 ++- .../container/NestedMethodPropertyTest.java | 334 ++++++++++++++++++ .../container/PropertyDescriptorTest.java | 56 +++ 12 files changed, 1123 insertions(+), 144 deletions(-) create mode 100644 src/com/vaadin/data/util/MethodPropertyDescriptor.java create mode 100644 src/com/vaadin/data/util/NestedMethodProperty.java create mode 100644 src/com/vaadin/data/util/NestedPropertyDescriptor.java create mode 100644 src/com/vaadin/data/util/VaadinPropertyDescriptor.java create mode 100644 tests/src/com/vaadin/tests/server/container/NestedMethodPropertyTest.java create mode 100644 tests/src/com/vaadin/tests/server/container/PropertyDescriptorTest.java 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)}. *

* - *

- * It is not possible to add additional properties to the container and nested - * bean properties are not supported. - *

- * * @param * The type of the item identifier * @param @@ -54,7 +49,8 @@ import com.vaadin.data.util.filter.UnsupportedFilterException; */ public abstract class AbstractBeanContainer extends AbstractInMemoryContainer> 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 extends BeanIdResolver { private final Object propertyId; - private transient Method getMethod; public PropertyBasedBeanIdResolver(Object propertyId) { if (propertyId == null) { @@ -100,25 +95,18 @@ public abstract class AbstractBeanContainer 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 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 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 model; + private LinkedHashMap> model; /** * Constructs a {@code AbstractBeanContainer} for beans of the given type. @@ -165,17 +153,7 @@ public abstract class AbstractBeanContainer 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) type); } /* @@ -725,4 +703,95 @@ public abstract class AbstractBeanContainer 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 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 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 extends PropertysetItem { * */ public BeanItem(BT bean) { - this(bean, getPropertyDescriptors(bean.getClass())); + this(bean, getPropertyDescriptors((Class) bean.getClass())); } /** @@ -67,19 +66,12 @@ public class BeanItem extends PropertysetItem { * pre-computed property descriptors */ BeanItem(BT bean, - LinkedHashMap propertyDescriptors) { + Map> 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(type, bean, - getMethod, setMethod); - addItemProperty(name, p); - + for (VaadinPropertyDescriptor pd : propertyDescriptors.values()) { + addItemProperty(pd.getName(), pd.createProperty(bean)); } } @@ -106,20 +98,14 @@ public class BeanItem extends PropertysetItem { this.bean = bean; // Create bean information - LinkedHashMap pds = getPropertyDescriptors(bean + LinkedHashMap> pds = getPropertyDescriptors((Class) bean .getClass()); // Add all the bean properties as MethodProperties to this Item for (Object id : propertyIds) { - PropertyDescriptor pd = pds.get(id); + VaadinPropertyDescriptor 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(type, bean, - getMethod, setMethod); - addItemProperty(name, p); + addItemProperty(pd.getName(), pd.createProperty(bean)); } } @@ -162,9 +148,9 @@ public class BeanItem extends PropertysetItem { * the Java Bean class to get properties for. * @return an ordered map from property names to property descriptors */ - static LinkedHashMap getPropertyDescriptors( - final Class beanClass) { - final LinkedHashMap pdMap = new LinkedHashMap(); + static LinkedHashMap> getPropertyDescriptors( + final Class beanClass) { + final LinkedHashMap> pdMap = new LinkedHashMap>(); // Try to introspect, if it fails, we just have an empty Item try { @@ -176,7 +162,10 @@ public class BeanItem extends PropertysetItem { final Method getMethod = pd.getReadMethod(); if ((getMethod != null) && getMethod.getDeclaringClass() != Object.class) { - pdMap.put(pd.getName(), pd); + VaadinPropertyDescriptor 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 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 implements Property, setArgs = (Object[]) in.readObject(); getArgs = (Object[]) in.readObject(); String name = (String) in.readObject(); + Class setMethodClass = (Class) 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 getMethodClass = (Class) 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 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 implements Property, // Gets the return type from get method if (returnType.isPrimitive()) { - if (returnType.equals(Boolean.TYPE)) { - type = (Class) Boolean.class; - } else if (returnType.equals(Integer.TYPE)) { - type = (Class) Integer.class; - } else if (returnType.equals(Float.TYPE)) { - type = (Class) Float.class; - } else if (returnType.equals(Double.TYPE)) { - type = (Class) Double.class; - } else if (returnType.equals(Byte.TYPE)) { - type = (Class) Byte.class; - } else if (returnType.equals(Character.TYPE)) { - type = (Class) Character.class; - } else if (returnType.equals(Short.TYPE)) { - type = (Class) Short.class; - } else if (returnType.equals(Long.TYPE)) { - type = (Class) Long.class; - } else { + type = (Class) 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 implements Property, } // Gets the return type from get method - if (type.isPrimitive()) { - if (type.equals(Boolean.TYPE)) { - this.type = (Class) Boolean.class; - } else if (type.equals(Integer.TYPE)) { - this.type = (Class) Integer.class; - } else if (type.equals(Float.TYPE)) { - this.type = (Class) Float.class; - } else if (type.equals(Double.TYPE)) { - this.type = (Class) Double.class; - } else if (type.equals(Byte.TYPE)) { - this.type = (Class) Byte.class; - } else if (type.equals(Character.TYPE)) { - this.type = (Class) Character.class; - } else if (type.equals(Short.TYPE)) { - this.type = (Class) Short.class; - } else if (type.equals(Long.TYPE)) { - this.type = (Class) Long.class; - } - } + this.type = (Class) convertPrimitiveType(type); setArguments(getArgs, setArgs, setArgumentIndex); readOnly = (setMethod == null); @@ -559,6 +521,50 @@ public class MethodProperty 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)) { @@ -579,13 +585,7 @@ public class MethodProperty 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 implements Property, * * @param value */ - private void invokeSetMethod(Object value) { + protected void invokeSetMethod(Object value) { try { // Construct a temporary argument array only if needed @@ -748,6 +748,25 @@ public class MethodProperty 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. * 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 + * bean type + * + * @since 6.6 + */ +public class MethodPropertyDescriptor implements + VaadinPropertyDescriptor { + + 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 class1 = (Class) 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(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 + * property type + * + * @since 6.6 + */ +public class NestedMethodProperty extends MethodProperty { + + // 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 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(); + 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 + * 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 NestedMethodProperty buildNestedMethodProperty( + Object instance, String propertyName) { + List getMethods = new ArrayList(); + + 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 property = new NestedMethodProperty( + (Class) convertPrimitiveType(type), instance, propertyName, + lastGetMethod, setMethod); + property.getMethods = getMethods; + + return property; + } + + protected NestedMethodProperty(Class 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 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 + * bean type + * + * @since 6.6 + */ +public class NestedPropertyDescriptor implements + VaadinPropertyDescriptor { + + 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 + * bean type + * + * @since 6.6 + */ +public interface VaadinPropertyDescriptor 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 diff --git a/tests/src/com/vaadin/tests/server/container/BeanContainerTest.java b/tests/src/com/vaadin/tests/server/container/BeanContainerTest.java index 1641dd996e..921132f858 100644 --- a/tests/src/com/vaadin/tests/server/container/BeanContainerTest.java +++ b/tests/src/com/vaadin/tests/server/container/BeanContainerTest.java @@ -236,16 +236,25 @@ public class BeanContainerTest extends AbstractBeanContainerTest { // should get exception } - try { - container.removeContainerProperty("name"); - Assert.fail(); - } catch (UnsupportedOperationException e) { - // should get exception - } - assertEquals(1, container.size()); } + public void testRemoveContainerProperty() { + BeanContainer container = new BeanContainer( + Person.class); + container.setBeanIdResolver(new PersonNameResolver()); + container.addBean(new Person("John")); + + Assert.assertEquals("John", + container.getContainerProperty("John", "name").getValue()); + Assert.assertTrue(container.removeContainerProperty("name")); + Assert.assertNull(container.getContainerProperty("John", "name")); + + Assert.assertNotNull(container.getItem("John")); + // property removed also from item + Assert.assertNull(container.getItem("John").getItemProperty("name")); + } + public void testAddNullBeans() { BeanContainer container = new BeanContainer( Person.class); @@ -399,4 +408,19 @@ public class BeanContainerTest extends AbstractBeanContainerTest { assertEquals(5, container.size()); } + public void testAddNestedContainerProperty() { + BeanContainer container = new BeanContainer( + NestedMethodPropertyTest.Person.class); + container.setBeanIdProperty("name"); + + container.addBean(new NestedMethodPropertyTest.Person("John", + new NestedMethodPropertyTest.Address("Ruukinkatu 2-4", 20540))); + + assertTrue(container.addNestedContainerProperty("address.street", + String.class)); + assertEquals("Ruukinkatu 2-4", + container.getContainerProperty("John", "address.street") + .getValue()); + } + } diff --git a/tests/src/com/vaadin/tests/server/container/BeanItemContainerTest.java b/tests/src/com/vaadin/tests/server/container/BeanItemContainerTest.java index c9d9d21802..60ea01fdc7 100644 --- a/tests/src/com/vaadin/tests/server/container/BeanItemContainerTest.java +++ b/tests/src/com/vaadin/tests/server/container/BeanItemContainerTest.java @@ -651,16 +651,25 @@ public class BeanItemContainerTest extends AbstractBeanContainerTest { // should get exception } - try { - container.removeContainerProperty("name"); - Assert.fail(); - } catch (UnsupportedOperationException e) { - // should get exception - } - assertEquals(1, container.size()); } + public void testRemoveContainerProperty() { + BeanItemContainer container = new BeanItemContainer( + Person.class); + Person john = new Person("John"); + container.addBean(john); + + Assert.assertEquals("John", container + .getContainerProperty(john, "name").getValue()); + Assert.assertTrue(container.removeContainerProperty("name")); + Assert.assertNull(container.getContainerProperty(john, "name")); + + Assert.assertNotNull(container.getItem(john)); + // property removed also from item + Assert.assertNull(container.getItem(john).getItemProperty("name")); + } + public void testAddNullBean() { BeanItemContainer container = new BeanItemContainer( Person.class); @@ -691,4 +700,20 @@ public class BeanItemContainerTest extends AbstractBeanContainerTest { } } + public void testAddNestedContainerProperty() { + BeanItemContainer container = new BeanItemContainer( + NestedMethodPropertyTest.Person.class); + + NestedMethodPropertyTest.Person john = new NestedMethodPropertyTest.Person( + "John", new NestedMethodPropertyTest.Address("Ruukinkatu 2-4", + 20540)); + container.addBean(john); + + assertTrue(container.addNestedContainerProperty("address.street", + String.class)); + assertEquals("Ruukinkatu 2-4", + container.getContainerProperty(john, "address.street") + .getValue()); + } + } diff --git a/tests/src/com/vaadin/tests/server/container/BeanItemTest.java b/tests/src/com/vaadin/tests/server/container/BeanItemTest.java index dcd7c756d8..70c419fbbd 100644 --- a/tests/src/com/vaadin/tests/server/container/BeanItemTest.java +++ b/tests/src/com/vaadin/tests/server/container/BeanItemTest.java @@ -1,17 +1,21 @@ package com.vaadin.tests.server.container; -import java.beans.PropertyDescriptor; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.Map; import junit.framework.Assert; import junit.framework.TestCase; import com.vaadin.data.util.BeanItem; +import com.vaadin.data.util.MethodProperty; +import com.vaadin.data.util.MethodPropertyDescriptor; +import com.vaadin.data.util.VaadinPropertyDescriptor; /** * Test BeanItem specific features. @@ -175,14 +179,16 @@ public class BeanItemTest extends TestCase { Method method = BeanItem.class.getDeclaredMethod( "getPropertyDescriptors", Class.class); method.setAccessible(true); - LinkedHashMap propertyDescriptors = (LinkedHashMap) method + LinkedHashMap> propertyDescriptors = (LinkedHashMap>) method .invoke(null, MySuperInterface.class); Assert.assertEquals(2, propertyDescriptors.size()); Assert.assertTrue(propertyDescriptors.containsKey("super1")); Assert.assertTrue(propertyDescriptors.containsKey("override")); - Assert.assertNull(propertyDescriptors.get("override").getWriteMethod()); + MethodProperty property = (MethodProperty) propertyDescriptors + .get("override").createProperty(getClass()); + Assert.assertTrue(property.isReadOnly()); } public void testGetSuperInterfaceProperties() throws SecurityException, @@ -191,7 +197,7 @@ public class BeanItemTest extends TestCase { Method method = BeanItem.class.getDeclaredMethod( "getPropertyDescriptors", Class.class); method.setAccessible(true); - LinkedHashMap propertyDescriptors = (LinkedHashMap) method + LinkedHashMap> propertyDescriptors = (LinkedHashMap>) method .invoke(null, MySubInterface.class); Assert.assertEquals(4, propertyDescriptors.size()); @@ -200,8 +206,9 @@ public class BeanItemTest extends TestCase { Assert.assertTrue(propertyDescriptors.containsKey("super2")); Assert.assertTrue(propertyDescriptors.containsKey("override")); - Assert.assertNotNull(propertyDescriptors.get("override") - .getWriteMethod()); + MethodProperty property = (MethodProperty) propertyDescriptors + .get("override").createProperty(getClass()); + Assert.assertFalse(property.isReadOnly()); } public void testPropertyExplicitOrder() { @@ -290,4 +297,41 @@ public class BeanItemTest extends TestCase { Assert.assertTrue(item.getItemProperty("name2").isReadOnly()); } + public void testCustomProperties() throws Exception { + LinkedHashMap> propertyDescriptors = new LinkedHashMap>(); + propertyDescriptors.put( + "myname", + new MethodPropertyDescriptor("myname", + MyClass.class, MyClass.class + .getDeclaredMethod("getName"), MyClass.class + .getDeclaredMethod("setName", String.class))); + MyClass instance = new MyClass("bean1"); + Constructor constructor = BeanItem.class + .getDeclaredConstructor(Object.class, Map.class); + constructor.setAccessible(true); + BeanItem item = constructor.newInstance(instance, + propertyDescriptors); + + Assert.assertEquals(1, item.getItemPropertyIds().size()); + Assert.assertEquals("bean1", item.getItemProperty("myname").getValue()); + } + + public void testAddRemoveProperty() throws Exception { + MethodPropertyDescriptor pd = new MethodPropertyDescriptor( + "myname", MyClass.class, + MyClass.class.getDeclaredMethod("getName"), + MyClass.class.getDeclaredMethod("setName", String.class)); + + BeanItem item = new BeanItem(new MyClass("bean1")); + + Assert.assertEquals(6, item.getItemPropertyIds().size()); + Assert.assertEquals(null, item.getItemProperty("myname")); + + item.addItemProperty("myname", pd.createProperty(item.getBean())); + Assert.assertEquals(7, item.getItemPropertyIds().size()); + Assert.assertEquals("bean1", item.getItemProperty("myname").getValue()); + item.removeItemProperty("myname"); + Assert.assertEquals(6, item.getItemPropertyIds().size()); + Assert.assertEquals(null, item.getItemProperty("myname")); + } } diff --git a/tests/src/com/vaadin/tests/server/container/NestedMethodPropertyTest.java b/tests/src/com/vaadin/tests/server/container/NestedMethodPropertyTest.java new file mode 100644 index 0000000000..e344970bad --- /dev/null +++ b/tests/src/com/vaadin/tests/server/container/NestedMethodPropertyTest.java @@ -0,0 +1,334 @@ +package com.vaadin.tests.server.container; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import com.vaadin.data.util.MethodProperty.MethodException; +import com.vaadin.data.util.NestedMethodProperty; + +public class NestedMethodPropertyTest extends TestCase { + + public static class Address implements Serializable { + private String street; + private int postalCodePrimitive; + private Integer postalCodeObject; + + public Address(String street, int postalCode) { + this.street = street; + postalCodePrimitive = postalCode; + postalCodeObject = postalCode; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getStreet() { + return street; + } + + public void setPostalCodePrimitive(int postalCodePrimitive) { + this.postalCodePrimitive = postalCodePrimitive; + } + + public int getPostalCodePrimitive() { + return postalCodePrimitive; + } + + public void setPostalCodeObject(Integer postalCodeObject) { + this.postalCodeObject = postalCodeObject; + } + + public Integer getPostalCodeObject() { + return postalCodeObject; + } + + // read-only boolean property + public boolean isBoolean() { + return true; + } + } + + public static class Person implements Serializable { + private String name; + private Address address; + + public Person(String name, Address address) { + this.name = name; + this.address = address; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setAddress(Address address) { + this.address = address; + } + + public Address getAddress() { + return address; + } + } + + public static class Team implements Serializable { + private String name; + private Person manager; + + public Team(String name, Person manager) { + this.name = name; + this.manager = manager; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setManager(Person manager) { + this.manager = manager; + } + + public Person getManager() { + return manager; + } + } + + private Address oldMill; + private Person joonas; + private Team vaadin; + + @Override + public void setUp() { + oldMill = new Address("Ruukinkatu 2-4", 20540); + joonas = new Person("Joonas", oldMill); + vaadin = new Team("Vaadin", joonas); + } + + @Override + public void tearDown() { + vaadin = null; + joonas = null; + oldMill = null; + } + + public void testSingleLevelNestedSimpleProperty() { + NestedMethodProperty nameProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "name"); + + Assert.assertEquals(String.class, nameProperty.getType()); + Assert.assertEquals("Vaadin", nameProperty.getValue()); + } + + public void testSingleLevelNestedObjectProperty() { + NestedMethodProperty managerProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "manager"); + + Assert.assertEquals(Person.class, managerProperty.getType()); + Assert.assertEquals(joonas, managerProperty.getValue()); + } + + public void testMultiLevelNestedProperty() { + NestedMethodProperty managerNameProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "manager.name"); + NestedMethodProperty
addressProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "manager.address"); + NestedMethodProperty streetProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "manager.address.street"); + NestedMethodProperty postalCodePrimitiveProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, + "manager.address.postalCodePrimitive"); + NestedMethodProperty postalCodeObjectProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, + "manager.address.postalCodeObject"); + NestedMethodProperty booleanProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "manager.address.boolean"); + + Assert.assertEquals(String.class, managerNameProperty.getType()); + Assert.assertEquals("Joonas", managerNameProperty.getValue()); + + Assert.assertEquals(Address.class, addressProperty.getType()); + Assert.assertEquals(oldMill, addressProperty.getValue()); + + Assert.assertEquals(String.class, streetProperty.getType()); + Assert.assertEquals("Ruukinkatu 2-4", streetProperty.getValue()); + + Assert.assertEquals(Integer.class, + postalCodePrimitiveProperty.getType()); + Assert.assertEquals(20540, postalCodePrimitiveProperty.getValue()); + + Assert.assertEquals(Integer.class, postalCodeObjectProperty.getType()); + Assert.assertEquals(20540, postalCodeObjectProperty.getValue()); + + Assert.assertEquals(Boolean.class, booleanProperty.getType()); + Assert.assertEquals(true, booleanProperty.getValue()); + } + + public void testEmptyPropertyName() { + try { + NestedMethodProperty.buildNestedMethodProperty(vaadin, ""); + fail(); + } catch (MethodException e) { + // should get exception + } + + try { + NestedMethodProperty.buildNestedMethodProperty(vaadin, " "); + fail(); + } catch (MethodException e) { + // should get exception + } + } + + public void testInvalidPropertyName() { + try { + NestedMethodProperty.buildNestedMethodProperty(vaadin, "."); + fail(); + } catch (MethodException e) { + // should get exception + } + try { + NestedMethodProperty.buildNestedMethodProperty(vaadin, ".manager"); + fail(); + } catch (MethodException e) { + // should get exception + } + try { + NestedMethodProperty.buildNestedMethodProperty(vaadin, "manager."); + fail(); + } catch (MethodException e) { + // should get exception + } + try { + NestedMethodProperty.buildNestedMethodProperty(vaadin, + "manager..name"); + fail(); + } catch (MethodException e) { + // should get exception + } + } + + public void testInvalidNestedPropertyName() { + try { + NestedMethodProperty.buildNestedMethodProperty(vaadin, "member"); + fail(); + } catch (MethodException e) { + // should get exception + } + + try { + NestedMethodProperty.buildNestedMethodProperty(vaadin, + "manager.pet"); + fail(); + } catch (MethodException e) { + // should get exception + } + + try { + NestedMethodProperty.buildNestedMethodProperty(vaadin, + "manager.address.city"); + fail(); + } catch (MethodException e) { + // should get exception + } + } + + public void testNullNestedProperty() { + NestedMethodProperty managerNameProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "manager.name"); + NestedMethodProperty streetProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "manager.address.street"); + + joonas.setAddress(null); + try { + streetProperty.getValue(); + fail(); + } catch (MethodException e) { + // should get exception + } + + vaadin.setManager(null); + try { + managerNameProperty.getValue(); + fail(); + } catch (MethodException e) { + // should get exception + } + try { + streetProperty.getValue(); + fail(); + } catch (MethodException e) { + // should get exception + } + + vaadin.setManager(joonas); + Assert.assertEquals("Joonas", managerNameProperty.getValue()); + } + + public void testMultiLevelNestedPropertySetValue() { + NestedMethodProperty managerNameProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "manager.name"); + NestedMethodProperty
addressProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "manager.address"); + NestedMethodProperty streetProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "manager.address.street"); + NestedMethodProperty postalCodePrimitiveProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, + "manager.address.postalCodePrimitive"); + NestedMethodProperty postalCodeObjectProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, + "manager.address.postalCodeObject"); + + managerNameProperty.setValue("Joonas L"); + Assert.assertEquals("Joonas L", joonas.getName()); + streetProperty.setValue("Ruukinkatu"); + Assert.assertEquals("Ruukinkatu", oldMill.getStreet()); + postalCodePrimitiveProperty.setValue(0); + postalCodeObjectProperty.setValue(1); + Assert.assertEquals(0, oldMill.getPostalCodePrimitive()); + Assert.assertEquals(Integer.valueOf(1), oldMill.getPostalCodeObject()); + + postalCodeObjectProperty.setValue(null); + Assert.assertNull(oldMill.getPostalCodeObject()); + + Address address2 = new Address("Other street", 12345); + addressProperty.setValue(address2); + Assert.assertEquals("Other street", streetProperty.getValue()); + } + + public void testSerialization() throws IOException, ClassNotFoundException { + NestedMethodProperty streetProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "manager.address.street"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new ObjectOutputStream(baos).writeObject(streetProperty); + NestedMethodProperty property2 = (NestedMethodProperty) new ObjectInputStream( + new ByteArrayInputStream(baos.toByteArray())).readObject(); + + Assert.assertEquals("Ruukinkatu 2-4", property2.getValue()); + } + + public void testIsReadOnly() { + NestedMethodProperty streetProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "manager.address.street"); + NestedMethodProperty booleanProperty = NestedMethodProperty + .buildNestedMethodProperty(vaadin, "manager.address.boolean"); + + Assert.assertFalse(streetProperty.isReadOnly()); + Assert.assertTrue(booleanProperty.isReadOnly()); + } + +} diff --git a/tests/src/com/vaadin/tests/server/container/PropertyDescriptorTest.java b/tests/src/com/vaadin/tests/server/container/PropertyDescriptorTest.java new file mode 100644 index 0000000000..69efc5f324 --- /dev/null +++ b/tests/src/com/vaadin/tests/server/container/PropertyDescriptorTest.java @@ -0,0 +1,56 @@ +package com.vaadin.tests.server.container; + +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import com.vaadin.data.Property; +import com.vaadin.data.util.MethodPropertyDescriptor; +import com.vaadin.data.util.NestedPropertyDescriptor; +import com.vaadin.data.util.VaadinPropertyDescriptor; +import com.vaadin.tests.server.container.NestedMethodPropertyTest.Person; + +public class PropertyDescriptorTest extends TestCase { + public void testMethodPropertyDescriptorSerialization() throws Exception { + PropertyDescriptor[] pds = Introspector.getBeanInfo(Person.class) + .getPropertyDescriptors(); + + MethodPropertyDescriptor descriptor = null; + + for (PropertyDescriptor pd : pds) { + if ("name".equals(pd.getName())) { + descriptor = new MethodPropertyDescriptor(pd.getName(), + String.class, pd.getReadMethod(), pd.getWriteMethod()); + break; + } + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new ObjectOutputStream(baos).writeObject(descriptor); + VaadinPropertyDescriptor descriptor2 = (VaadinPropertyDescriptor) new ObjectInputStream( + new ByteArrayInputStream(baos.toByteArray())).readObject(); + + Property property = descriptor2 + .createProperty(new Person("John", null)); + Assert.assertEquals("John", property.getValue()); + } + + public void testNestedPropertyDescriptorSerialization() throws Exception { + NestedPropertyDescriptor pd = new NestedPropertyDescriptor( + "name", String.class); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new ObjectOutputStream(baos).writeObject(pd); + VaadinPropertyDescriptor pd2 = (VaadinPropertyDescriptor) new ObjectInputStream( + new ByteArrayInputStream(baos.toByteArray())).readObject(); + + Property property = pd2.createProperty(new Person("John", null)); + Assert.assertEquals("John", property.getValue()); + } +} -- 2.39.5