summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorHenri Sara <henri.sara@itmill.com>2011-04-18 12:13:36 +0000
committerHenri Sara <henri.sara@itmill.com>2011-04-18 12:13:36 +0000
commit4b13cfab7c0adaefdf8b4cfcb3c68bddf9111dc2 (patch)
treeb3bcc744f31be480720cb5aa7edd5da6925c2e94 /src/com
parent5e90e458843207793cfa25a3f9d1611bf1b09629 (diff)
downloadvaadin-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.java145
-rw-r--r--src/com/vaadin/data/util/BeanItem.java41
-rw-r--r--src/com/vaadin/data/util/MethodProperty.java139
-rw-r--r--src/com/vaadin/data/util/MethodPropertyDescriptor.java128
-rw-r--r--src/com/vaadin/data/util/NestedMethodProperty.java200
-rw-r--r--src/com/vaadin/data/util/NestedPropertyDescriptor.java51
-rw-r--r--src/com/vaadin/data/util/VaadinPropertyDescriptor.java40
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