diff options
author | Matti Hosio <mhosio@vaadin.com> | 2013-07-15 15:56:07 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2013-08-09 08:13:00 +0000 |
commit | 9f126db566b14afaa8e025056539a38a5d7b9904 (patch) | |
tree | b25d791c4c8a8ddacaf0d85d12cb796ceeb4a4a6 /server | |
parent | 437b6de4eea416317bd79399a5dde9870b306975 (diff) | |
download | vaadin-framework-9f126db566b14afaa8e025056539a38a5d7b9904.tar.gz vaadin-framework-9f126db566b14afaa8e025056539a38a5d7b9904.zip |
Support for null intermediate beans in NestedMethodProperty (#11435)
Allows intermediate beans to return null in the NestedMethodProperty.
The feature is not enabled by default and thus should be fully backwards
compatible.
Change-Id: I438d0f787c5c76f61ab234f3c92dd927a8354a37
Diffstat (limited to 'server')
8 files changed, 250 insertions, 5 deletions
diff --git a/server/src/com/vaadin/data/util/AbstractBeanContainer.java b/server/src/com/vaadin/data/util/AbstractBeanContainer.java index 35403d6419..cd5c0c809d 100644 --- a/server/src/com/vaadin/data/util/AbstractBeanContainer.java +++ b/server/src/com/vaadin/data/util/AbstractBeanContainer.java @@ -845,8 +845,32 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends * @return true if the property was added */ public boolean addNestedContainerProperty(String propertyId) { + return addNestedContainerProperty(propertyId, false); + } + + /** + * 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 or the <code>nullBeansAllowed</code> must + * be set to true. If the <code>nullBeansAllowed</code> flag is set to true, + * calling getValue of the added property will return null if the property + * or any of its intermediate getters returns null. If set to false, null + * values returned by intermediate getters will cause NullPointerException. + * The default value is false to ensure backwards compatibility. + * + * @see NestedMethodProperty + * + * @param propertyId + * @param nullBeansAllowed + * set true to allow null values from intermediate getters + * @return true if the property was added + */ + public boolean addNestedContainerProperty(String propertyId, + boolean nullBeansAllowed) { return addContainerProperty(propertyId, new NestedPropertyDescriptor( - propertyId, type)); + propertyId, type, nullBeansAllowed)); } /** @@ -864,13 +888,42 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends */ @SuppressWarnings("unchecked") public void addNestedContainerBean(String propertyId) { + addNestedContainerBean(propertyId, false); + } + + /** + * Adds a nested container properties for all sub-properties of a named + * property to the container. The named property itself is removed from the + * model as its subproperties are added. + * + * Unless + * <code>nullBeansAllowed<code> is set to true, all intermediate getters must + * exist and must return non-null values when the property values are + * accessed. If the <code>nullBeansAllowed</code> flag is set to true, + * calling getValue of the added subproperties will return null if the + * property or any of their intermediate getters returns null. If set to + * false, null values returned by intermediate getters will cause + * NullPointerException. The default value is false to ensure backwards + * compatibility. + * + * @see NestedMethodProperty + * @see #addNestedContainerProperty(String) + * + * @param propertyId + * @param nullBeansAllowed + * set true to allow null values from intermediate getters + */ + @SuppressWarnings("unchecked") + public void addNestedContainerBean(String propertyId, + boolean nullBeansAllowed) { Class<?> propertyType = getType(propertyId); LinkedHashMap<String, VaadinPropertyDescriptor<Object>> pds = BeanItem .getPropertyDescriptors((Class<Object>) propertyType); for (String subPropertyId : pds.keySet()) { String qualifiedPropertyId = propertyId + "." + subPropertyId; NestedPropertyDescriptor<BEANTYPE> pd = new NestedPropertyDescriptor<BEANTYPE>( - qualifiedPropertyId, (Class<BEANTYPE>) type); + qualifiedPropertyId, (Class<BEANTYPE>) type, + nullBeansAllowed); model.put(qualifiedPropertyId, pd); model.remove(propertyId); for (BeanItem<BEANTYPE> item : itemIdToItem.values()) { diff --git a/server/src/com/vaadin/data/util/BeanItem.java b/server/src/com/vaadin/data/util/BeanItem.java index fc51be8f36..4834fe4f89 100644 --- a/server/src/com/vaadin/data/util/BeanItem.java +++ b/server/src/com/vaadin/data/util/BeanItem.java @@ -268,6 +268,27 @@ public class BeanItem<BT> extends PropertysetItem { } /** + * Adds a nested property to the item. If the <code>nullBeansAllowed</code> + * flag is set to true, calling getValue of the added property will return + * null if the property or any of its intermediate getters returns null. If + * set to false, null values returned by intermediate getters will cause + * NullPointerException. The default value is false to ensure backwards + * compatibility. + * + * @param nestedPropertyId + * property id to add. This property must not exist in the item + * already and must of of form "field1.field2" where field2 is a + * field in the object referenced to by field1 + * @param nullBeansAllowed + * set true to allow null values from intermediate getters + */ + public void addNestedProperty(String nestedPropertyId, + boolean nullBeansAllowed) { + addItemProperty(nestedPropertyId, new NestedMethodProperty<Object>( + getBean(), nestedPropertyId, nullBeansAllowed)); + } + + /** * Gets the underlying JavaBean object. * * @return the bean object. diff --git a/server/src/com/vaadin/data/util/NestedMethodProperty.java b/server/src/com/vaadin/data/util/NestedMethodProperty.java index b62ecfbfc3..7a3963c17e 100644 --- a/server/src/com/vaadin/data/util/NestedMethodProperty.java +++ b/server/src/com/vaadin/data/util/NestedMethodProperty.java @@ -32,7 +32,7 @@ import com.vaadin.data.util.MethodProperty.MethodException; * can contain multiple levels of nesting. * * When accessing the property value, all intermediate getters must return - * non-null values. + * non-null values or the <code>nullBeansAllowed</code> must be set to true. * * @see MethodProperty * @@ -55,6 +55,15 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> { */ private Object instance; + /** + * a boolean flag indicating whether intermediate getters may return null + * values. If the flag is set to true, calling getValue will return null if + * the property or any of its intermediate getters returns null. If set to + * false, intermediate getters returning null value will throw Exception. + * The default value is false to ensure backwards compatibility. + */ + private boolean nullBeansAllowed = false; + private Class<? extends T> type; /* Special serialization to handle method references */ @@ -85,7 +94,33 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> { * if the property name is invalid */ public NestedMethodProperty(Object instance, String propertyName) { + this(instance, propertyName, false); + } + + /** + * 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". The <code>nullBeansAllowed</code> controls + * the behavior in cases where the intermediate getters may return null + * values. If the flag is set to true, calling getValue will return null if + * the property or any of its intermediate getters returns null. If set to + * false, null values returned by intermediate getters will cause + * NullPointerException. The default value is false to ensure backwards + * compatibility. + * + * @param instance + * top-level bean to which the property applies + * @param propertyName + * dot separated nested property name + * @param nullBeansAllowed + * set true to allow null values from intermediate getters + * @throws IllegalArgumentException + * if the property name is invalid + */ + public NestedMethodProperty(Object instance, String propertyName, + boolean nullBeansAllowed) { this.instance = instance; + this.nullBeansAllowed = nullBeansAllowed; initialize(instance.getClass(), propertyName); } @@ -104,6 +139,25 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> { } /** + * For internal use to deduce property type etc. without a bean instance. + * Calling {@link #setValue(Object)} or {@link #getValue()} on properties + * constructed this way is not supported. + * + * @param instanceClass + * class of the top-level bean + * @param propertyName + * dot separated nested property name + * @param nullBeansAllowed + * set true to allow null values from intermediate getters + */ + NestedMethodProperty(Class<?> instanceClass, String propertyName, + boolean nullBeansAllowed) { + instance = null; + this.nullBeansAllowed = nullBeansAllowed; + initialize(instanceClass, propertyName); + } + + /** * Initializes most of the internal fields based on the top-level bean * instance and property name (dot-separated string). * @@ -199,6 +253,9 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> { Object object = instance; for (Method m : getMethods) { object = m.invoke(object); + if (object == null && nullBeansAllowed) { + return null; + } } return (T) object; } catch (final Throwable e) { diff --git a/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java b/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java index b2055fe776..67eb30fae5 100644 --- a/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java +++ b/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java @@ -34,6 +34,7 @@ public class NestedPropertyDescriptor<BT> implements private final String name; private final Class<?> propertyType; + private final boolean nullBeansAllowed; /** * Creates a property descriptor that can create MethodProperty instances to @@ -48,10 +49,29 @@ public class NestedPropertyDescriptor<BT> implements */ public NestedPropertyDescriptor(String name, Class<BT> beanType) throws IllegalArgumentException { + this(name, beanType, false); + } + + /** + * Creates a property descriptor that can create MethodProperty instances to + * access the underlying bean property. + * + * @param name + * of the property in a dotted path format, e.g. "address.street" + * @param beanType + * type (class) of the top-level bean + * @param nullBeansAllowed + * set true to allow null values from intermediate getters + * @throws IllegalArgumentException + * if the property name is invalid + */ + public NestedPropertyDescriptor(String name, Class<BT> beanType, + boolean nullBeansAllowed) throws IllegalArgumentException { this.name = name; NestedMethodProperty<?> property = new NestedMethodProperty<Object>( - beanType, name); + beanType, name, nullBeansAllowed); this.propertyType = property.getType(); + this.nullBeansAllowed = nullBeansAllowed; } @Override @@ -66,7 +86,7 @@ public class NestedPropertyDescriptor<BT> implements @Override public Property<?> createProperty(BT bean) { - return new NestedMethodProperty<Object>(bean, name); + return new NestedMethodProperty<Object>(bean, name, nullBeansAllowed); } } diff --git a/server/tests/src/com/vaadin/data/util/BeanContainerTest.java b/server/tests/src/com/vaadin/data/util/BeanContainerTest.java index 9037e303a8..2dcbb4aed8 100644 --- a/server/tests/src/com/vaadin/data/util/BeanContainerTest.java +++ b/server/tests/src/com/vaadin/data/util/BeanContainerTest.java @@ -457,4 +457,28 @@ public class BeanContainerTest extends AbstractBeanContainerTest { .getValue()); } + public void testNestedContainerPropertyWithNullBean() { + BeanContainer<String, NestedMethodPropertyTest.Person> container = new BeanContainer<String, NestedMethodPropertyTest.Person>( + NestedMethodPropertyTest.Person.class); + container.setBeanIdProperty("name"); + + container.addBean(new NestedMethodPropertyTest.Person("John", null)); + assertTrue(container + .addNestedContainerProperty("address.postalCodeObject")); + assertTrue(container.addNestedContainerProperty("address.street", true)); + // the nested properties added with allowNullBean setting should return + // null + assertNull(container.getContainerProperty("John", "address.street") + .getValue()); + // nested properties added without allowNullBean setting should throw + // exception + try { + container.getContainerProperty("John", "address.postalCodeObject") + .getValue(); + fail(); + } catch (Exception e) { + // should throw exception + } + } + } diff --git a/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java b/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java index 6b88eb336d..3a2cb268b9 100644 --- a/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java +++ b/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java @@ -714,4 +714,27 @@ public class BeanItemContainerTest extends AbstractBeanContainerTest { .getValue()); } + public void testNestedContainerPropertyWithNullBean() { + BeanItemContainer<NestedMethodPropertyTest.Person> container = new BeanItemContainer<NestedMethodPropertyTest.Person>( + NestedMethodPropertyTest.Person.class); + NestedMethodPropertyTest.Person john = new NestedMethodPropertyTest.Person( + "John", null); + assertNotNull(container.addBean(john)); + assertTrue(container + .addNestedContainerProperty("address.postalCodeObject")); + assertTrue(container.addNestedContainerProperty("address.street", true)); + // the nested properties added with allowNullBean setting should return + // null + assertNull(container.getContainerProperty(john, "address.street") + .getValue()); + // nested properties added without allowNullBean setting should throw + // exception + try { + container.getContainerProperty(john, "address.postalCodeObject") + .getValue(); + fail(); + } catch (Exception e) { + // should throw exception + } + } } diff --git a/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java b/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java index 640ede8743..d517322010 100644 --- a/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java +++ b/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java @@ -273,6 +273,23 @@ public class NestedMethodPropertyTest extends TestCase { Assert.assertEquals("Joonas", managerNameProperty.getValue()); } + public void testNullNestedPropertyWithAllowNullBeans() { + NestedMethodProperty<String> managerNameProperty = new NestedMethodProperty<String>( + vaadin, "manager.name", true); + NestedMethodProperty<String> streetProperty = new NestedMethodProperty<String>( + vaadin, "manager.address.street", true); + + joonas.setAddress(null); + // should return null + Assert.assertNull(streetProperty.getValue()); + + vaadin.setManager(null); + Assert.assertNull(managerNameProperty.getValue()); + vaadin.setManager(joonas); + Assert.assertEquals("Joonas", managerNameProperty.getValue()); + Assert.assertNull(streetProperty.getValue()); + } + public void testMultiLevelNestedPropertySetValue() { NestedMethodProperty<String> managerNameProperty = new NestedMethodProperty<String>( vaadin, "manager.name"); @@ -314,6 +331,20 @@ public class NestedMethodPropertyTest extends TestCase { Assert.assertEquals("Ruukinkatu 2-4", property2.getValue()); } + public void testSerializationWithNullBeansAllowed() throws IOException, + ClassNotFoundException { + vaadin.setManager(null); + NestedMethodProperty<String> streetProperty = new NestedMethodProperty<String>( + vaadin, "manager.address.street", true); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new ObjectOutputStream(baos).writeObject(streetProperty); + @SuppressWarnings("unchecked") + NestedMethodProperty<String> property2 = (NestedMethodProperty<String>) new ObjectInputStream( + new ByteArrayInputStream(baos.toByteArray())).readObject(); + + Assert.assertNull(property2.getValue()); + } + public void testIsReadOnly() { NestedMethodProperty<String> streetProperty = new NestedMethodProperty<String>( vaadin, "manager.address.street"); diff --git a/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java b/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java index 14e70d76d4..0ae76430f6 100644 --- a/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java +++ b/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java @@ -52,4 +52,20 @@ public class PropertyDescriptorTest extends TestCase { Property<?> property = pd2.createProperty(new Person("John", null)); Assert.assertEquals("John", property.getValue()); } + + public void testNestedPropertyDescriptorWithNullBeansAllowedSerialization() + throws Exception { + NestedPropertyDescriptor<Person> pd = new NestedPropertyDescriptor<Person>( + "address.street", Person.class, true); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new ObjectOutputStream(baos).writeObject(pd); + @SuppressWarnings("unchecked") + VaadinPropertyDescriptor<Person> pd2 = (VaadinPropertyDescriptor<Person>) new ObjectInputStream( + new ByteArrayInputStream(baos.toByteArray())).readObject(); + + Property<?> property = pd2.createProperty(new Person("John", null)); + Assert.assertNull(property.getValue()); + } + } |