]> source.dussan.org Git - vaadin-framework.git/commitdiff
Support for null intermediate beans in NestedMethodProperty (#11435)
authorMatti Hosio <mhosio@vaadin.com>
Mon, 15 Jul 2013 12:56:07 +0000 (15:56 +0300)
committerVaadin Code Review <review@vaadin.com>
Fri, 9 Aug 2013 08:13:00 +0000 (08:13 +0000)
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

server/src/com/vaadin/data/util/AbstractBeanContainer.java
server/src/com/vaadin/data/util/BeanItem.java
server/src/com/vaadin/data/util/NestedMethodProperty.java
server/src/com/vaadin/data/util/NestedPropertyDescriptor.java
server/tests/src/com/vaadin/data/util/BeanContainerTest.java
server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java
server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java
server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java

index 35403d641950f5402e91cde55fcf4c768044490a..cd5c0c809d57bf4e4df5cff18199d9ea84dd4513 100644 (file)
@@ -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()) {
index fc51be8f36413c871b0e437c01917573c800a4e3..4834fe4f8971735fbd2cbb95bcd052c14e782654 100644 (file)
@@ -267,6 +267,27 @@ public class BeanItem<BT> extends PropertysetItem {
                 getBean(), nestedPropertyId));
     }
 
+    /**
+     * 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.
      * 
index b62ecfbfc35579cfb0ba83fb7b6ae00fb086b21d..7a3963c17e874e0945a1c6e63db8704cf2fcea92 100644 (file)
@@ -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);
     }
 
@@ -103,6 +138,25 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
         initialize(instanceClass, propertyName);
     }
 
+    /**
+     * 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) {
index b2055fe776b98e408a0b53e911ae26eadd2d9aec..67eb30fae5272d70ea1fc551729c80aa4be29eb9 100644 (file)
@@ -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);
     }
 
 }
index 9037e303a875937c81bae7d236ac742004c243c5..2dcbb4aed8370cd04f226f0a0f48119150caa355 100644 (file)
@@ -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
+        }
+    }
+
 }
index 6b88eb336d5385afae8af6d4bdb2cacd9f5b1bdd..3a2cb268b973957b36f121509afaef2afa04c171 100644 (file)
@@ -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
+        }
+    }
 }
index 640ede87432fb30015087090b26cc3a3d1e40042..d51732201089822366d14e8704ef6fe287d86788 100644 (file)
@@ -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");
index 14e70d76d45b2d2cdc723755ee0a0736f46f4a00..0ae76430f68b84fbeca7991923269e18d6d20c35 100644 (file)
@@ -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());
+    }
+
 }