]> source.dussan.org Git - vaadin-framework.git/commitdiff
Enable changing the backing bean for BeanItem (#4302) (#77)
authorArtur <artur@vaadin.com>
Tue, 13 Dec 2016 11:56:54 +0000 (13:56 +0200)
committerPekka Hyvönen <pekka@vaadin.com>
Tue, 13 Dec 2016 11:56:54 +0000 (13:56 +0200)
When storing a bean to the database, you typically get a new and updated
bean instance back. By allowing to change the bean instance, we make it
possible to just update the single BeanItem instance which can be used
in many places.

server/src/main/java/com/vaadin/data/util/BeanItem.java
server/src/main/java/com/vaadin/data/util/MethodProperty.java
server/src/main/java/com/vaadin/data/util/NestedMethodProperty.java
server/src/test/java/com/vaadin/data/util/BeanItemTest.java
server/src/test/java/com/vaadin/data/util/MethodPropertyTest.java [new file with mode: 0644]
server/src/test/java/com/vaadin/data/util/NestedMethodPropertyTest.java

index 78010a14c0859029a7b254c6e582d534d340b7d6..99853d458cc7993a204e189c45bf1a571f47597c 100644 (file)
@@ -26,6 +26,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import com.vaadin.data.Property;
+
 /**
  * A wrapper class for adding the Item interface to any Java Bean.
  *
@@ -38,7 +40,7 @@ public class BeanItem<BT> extends PropertysetItem {
     /**
      * The bean which this Item is based on.
      */
-    private final BT bean;
+    private BT bean;
 
     /**
      * <p>
@@ -261,4 +263,46 @@ public class BeanItem<BT> extends PropertysetItem {
         return bean;
     }
 
+    /**
+     * Changes the Java Bean this item is based on.
+     * <p>
+     * This will cause any existing properties to be re-mapped to the new bean.
+     * Any added custom properties which are not of type {@link MethodProperty}
+     * or {@link NestedMethodProperty} will not be updated to reflect the change
+     * of bean.
+     * <p>
+     * Changing the bean will fire value change events for all properties of
+     * type {@link MethodProperty} or {@link NestedMethodProperty}.
+     * 
+     * @param bean
+     *            The new bean to use for this item, not <code>null</code>
+     */
+    public void setBean(BT bean) {
+        if (bean == null) {
+            throw new IllegalArgumentException("Bean cannot be null");
+        }
+
+        if (getBean().getClass() != bean.getClass()) {
+            throw new IllegalArgumentException(
+                    "The new bean class " + bean.getClass().getName()
+                            + " does not match the old bean class "
+                            + getBean().getClass());
+        }
+
+        // Remap properties
+        for (Object propertyId : getItemPropertyIds()) {
+            Property p = getItemProperty(propertyId);
+            if (p instanceof MethodProperty) {
+                MethodProperty mp = (MethodProperty) p;
+                assert (mp.getInstance() == getBean());
+                mp.setInstance(bean);
+            } else if (p instanceof NestedMethodProperty) {
+                NestedMethodProperty nmp = (NestedMethodProperty) p;
+                assert (nmp.getInstance() == getBean());
+                nmp.setInstance(bean);
+            }
+        }
+
+        this.bean = bean;
+    }
 }
index 70ee735d9609a56c24aba81cb99010a6647cfd23..829e37e321748a4438423720a6ea1feb1b4b74d2 100644 (file)
@@ -767,6 +767,37 @@ public class MethodProperty<T> extends AbstractProperty<T> {
         super.fireValueChange();
     }
 
+    /**
+     * The instance used by this property
+     *
+     * @return the instance used for fetching the property value
+     */
+    public Object getInstance() {
+        return instance;
+    }
+
+    /**
+     * Sets the instance used by this property.
+     * <p>
+     * The new instance must be of the same type as the old instance
+     * <p>
+     * To be consistent with {@link #setValue(Object)}, this method will fire a
+     * value change event even if the value stays the same
+     *
+     * @param instance
+     *            the instance to use
+     */
+    public void setInstance(Object instance) {
+        if (this.instance.getClass() != instance.getClass()) {
+            throw new IllegalArgumentException("The new instance is of type "
+                    + instance.getClass().getName()
+                    + " which does not match the old instance type "
+                    + this.instance.getClass().getName());
+        }
+        this.instance = instance;
+        fireValueChange();
+    }
+
     private static final Logger getLogger() {
         return Logger.getLogger(MethodProperty.class.getName());
     }
index 0b39efef5c301fb15c5d546d5a368506cd072102..0ed4f085f6605ee48e7e9b0ae1db877712728705 100644 (file)
@@ -192,9 +192,10 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
 
     /**
      * Gets the value stored in the Property. The value is resolved by calling
-     * the specified getter method with the argument specified at instantiation.
+     * the specified getter methods on the current instance:
      *
      * @return the value of the Property
+     * @see #getInstance()
      */
     @Override
     public T getValue() {
@@ -266,4 +267,35 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
         return Collections.unmodifiableList(getMethods);
     }
 
+    /**
+     * The instance used by this property
+     * 
+     * @return the instance used for fetching the property value
+     */
+    public Object getInstance() {
+        return instance;
+    }
+
+    /**
+     * Sets the instance used by this property.
+     * <p>
+     * The new instance must be of the same type as the old instance
+     * <p>
+     * To be consistent with {@link #setValue(Object)}, this method will fire a
+     * value change event even if the value stays the same
+     * 
+     * @param instance
+     *            the instance to use
+     */
+    public void setInstance(Object instance) {
+        if (this.instance.getClass() != instance.getClass()) {
+            throw new IllegalArgumentException("The new instance is of type "
+                    + instance.getClass().getName()
+                    + " which does not match the old instance type "
+                    + this.instance.getClass().getName());
+        }
+        this.instance = instance;
+        fireValueChange();
+    }
+
 }
index 1bbe74818c69520d598c8a8e19923a4a9e81b74c..09e5daca976fa03a58559536103db01ce614bc2b 100644 (file)
@@ -386,4 +386,41 @@ public class BeanItemTest {
         // Should not be exception
         property.setValue(null);
     }
+
+    @Test
+    public void testChangeBean() {
+        BeanItem<MyClass> beanItem = new BeanItem<BeanItemTest.MyClass>(
+                new MyClass("Foo"));
+        beanItem.setBean(new MyClass("Bar"));
+        Assert.assertEquals("Bar", beanItem.getItemProperty("name").getValue());
+    }
+
+    @Test
+    public void testChangeBeanNestedProperty() {
+        BeanItem<MyClass> beanItem = new BeanItem<BeanItemTest.MyClass>(
+                new MyClass("Foo"));
+        beanItem.setBean(new MyClass("Bar"));
+        Assert.assertEquals("Bar", beanItem.getItemProperty("name").getValue());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testChangeBeanToIncompatibleOne() {
+        BeanItem<Object> beanItem = new BeanItem<Object>(new MyClass("Foo"));
+        beanItem.setBean(new Generic<String>());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testChangeBeanToSubclass() {
+        BeanItem<MyClass> beanItem = new BeanItem<BeanItemTest.MyClass>(
+                new MyClass("Foo"));
+        beanItem.setBean(new MyClass("Bar"));
+        beanItem.setBean(new MyClass2("foo"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testChangeBeanToNull() {
+        BeanItem<Object> beanItem = new BeanItem<Object>(new MyClass("Foo"));
+        beanItem.setBean(null);
+    }
+
 }
diff --git a/server/src/test/java/com/vaadin/data/util/MethodPropertyTest.java b/server/src/test/java/com/vaadin/data/util/MethodPropertyTest.java
new file mode 100644 (file)
index 0000000..962b19a
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.data.util.NestedMethodPropertyTest.Address;
+
+public class MethodPropertyTest {
+
+    private Address testObject;
+
+    @Before
+    public void setup() {
+        testObject = new NestedMethodPropertyTest.Address("some street", 123);
+    }
+
+    @Test
+    public void getValue() {
+        MethodProperty<String> mp = new MethodProperty<String>(testObject,
+                "street");
+        Assert.assertEquals("some street", mp.getValue());
+    }
+
+    @Test
+    public void getValueAfterBeanUpdate() {
+        MethodProperty<String> mp = new MethodProperty<String>(testObject,
+                "street");
+        testObject.setStreet("Foo street");
+        Assert.assertEquals("Foo street", mp.getValue());
+    }
+
+    @Test
+    public void setValue() {
+        MethodProperty<String> mp = new MethodProperty<String>(testObject,
+                "street");
+        mp.setValue("Foo street");
+        Assert.assertEquals("Foo street", testObject.getStreet());
+    }
+
+    @Test
+    public void changeInstance() {
+        MethodProperty<String> mp = new MethodProperty<String>(testObject,
+                "street");
+        Address newStreet = new Address("new street", 999);
+        mp.setInstance(newStreet);
+        Assert.assertEquals("new street", mp.getValue());
+        Assert.assertEquals("some street", testObject.getStreet());
+
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void changeInstanceToIncompatible() {
+        MethodProperty<String> mp = new MethodProperty<String>(testObject,
+                "street");
+        mp.setInstance("foobar");
+
+    }
+
+}
index 4a1e2a1784bfbff9173b2dcdc602e13f5ce052ad..e1f59ea8961b764af3a8be5b85892cea2aa5bf6f 100644 (file)
@@ -348,4 +348,27 @@ public class NestedMethodPropertyTest {
         Assert.assertTrue(booleanProperty.isReadOnly());
     }
 
+    @Test
+    public void testChangeInstance() {
+        NestedMethodProperty<String> streetProperty = new NestedMethodProperty<String>(
+                vaadin, "manager.address.street");
+
+        Address somewhere = new Address("The street", 1234);
+        Person someone = new Person("Someone", somewhere);
+        Team someteam = new Team("The team", someone);
+        streetProperty.setInstance(someteam);
+
+        Assert.assertEquals("The street", streetProperty.getValue());
+        Assert.assertEquals("Ruukinkatu 2-4", vaadin.getManager().getAddress()
+                .getStreet());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testChangeInstanceToIncompatible() {
+        NestedMethodProperty<String> streetProperty = new NestedMethodProperty<String>(
+                vaadin, "manager.address.street");
+
+        streetProperty.setInstance("bar");
+    }
+
 }