1   /*
2    * Copyright 2002-2007 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package org.springframework.binding.support;
17  
18  import java.beans.PropertyChangeEvent;
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.springframework.beans.InvalidPropertyException;
25  import org.springframework.beans.NotReadablePropertyException;
26  import org.springframework.beans.NotWritablePropertyException;
27  import org.springframework.beans.NullValueInNestedPathException;
28  import org.springframework.binding.MutablePropertyAccessStrategy;
29  import org.springframework.binding.PropertyMetadataAccessStrategy;
30  import org.springframework.binding.value.ValueModel;
31  import org.springframework.rules.closure.Closure;
32  import org.springframework.rules.closure.support.Block;
33  import org.springframework.richclient.test.SpringRichTestCase;
34  
35  /**
36   * Tests class {@link AbstractPropertyAccessStrategy}.
37   *
38   * @author Oliver Hutchison
39  * @author Arne Limburg
40   */
41  public abstract class AbstractPropertyAccessStrategyTests extends SpringRichTestCase {
42  
43      protected AbstractPropertyAccessStrategy pas;
44  
45      protected TestBean testBean;
46  
47      protected ValueModel vm;
48  
49      protected TestPropertyChangeListener pcl;
50  
51      protected void doSetUp() throws Exception {
52          testBean = new TestBean();
53          pas = createPropertyAccessStrategy(testBean);
54          pcl = new TestPropertyChangeListener(ValueModel.VALUE_PROPERTY);
55      }
56  
57      protected abstract AbstractPropertyAccessStrategy createPropertyAccessStrategy(Object target);
58  
59      protected boolean isStrictNullHandlingEnabled() {
60      	return true;
61      }
62  
63      public void testSimpleProperty() {
64          vm = pas.getPropertyValueModel("simpleProperty");
65          Block setValueDirectly = new Block() {
66             public void handle(Object newValue) {
67                  testBean.setSimpleProperty((String)newValue);
68              }
69          };
70          Closure getValueDirectly = new Closure() {
71              public Object call(Object ignore) {
72                  return testBean.getSimpleProperty();
73              }
74          };
75          Object[] valuesToTest = new Object[] {"1", "2", null, "3"};
76  
77          testSettingAndGetting(valuesToTest, getValueDirectly, setValueDirectly);
78      }
79  
80      public void testNestedProperty() {
81          final TestBean nestedProperty = new TestBean();
82          testBean.setNestedProperty(nestedProperty);
83          vm = pas.getPropertyValueModel("nestedProperty.simpleProperty");
84          Block setValueDirectly = new Block() {
85              public void handle(Object newValue) {
86                  nestedProperty.setSimpleProperty((String)newValue);
87              }
88          };
89          Closure getValueDirectly = new Closure() {
90              public Object call(Object ignored) {
91                  return nestedProperty.getSimpleProperty();
92              }
93          };
94          Object[] valuesToTest = new Object[] {"1", "2", null, "3"};
95  
96          testSettingAndGetting(valuesToTest, getValueDirectly, setValueDirectly);
97      }
98  
99      public void testChildPropertyAccessStrategy() {
100         final TestBean nestedProperty = new TestBean();
101         testBean.setNestedProperty(nestedProperty);
102         MutablePropertyAccessStrategy cpas = pas.getPropertyAccessStrategyForPath("nestedProperty");
103 
104         assertEquals("Child domainObjectHolder should equal equivalent parent ValueModel",
105                 pas.getPropertyValueModel("nestedProperty"), cpas.getDomainObjectHolder());
106 
107         vm = cpas.getPropertyValueModel("simpleProperty");
108         assertEquals("Child should return the same ValueModel as parent",
109                 pas.getPropertyValueModel("nestedProperty.simpleProperty"), vm);
110 
111         Block setValueDirectly = new Block() {
112             public void handle(Object newValue) {
113                 nestedProperty.setSimpleProperty((String)newValue);
114             }
115         };
116         Closure getValueDirectly = new Closure() {
117             public Object call(Object ignore) {
118                 return nestedProperty.getSimpleProperty();
119             }
120         };
121         Object[] valuesToTest = new Object[] {"1", "2", null, "3"};
122 
123         testSettingAndGetting(valuesToTest, getValueDirectly, setValueDirectly);
124 
125         try {
126             pas.getPropertyValueModel("nestedProperty").setValue(null);
127             if (isStrictNullHandlingEnabled())
128             	fail("Should have thrown a NullValueInNestedPathException");
129         }
130         catch (NullValueInNestedPathException e) {
131             if (!isStrictNullHandlingEnabled())
132             	fail("Should not have thrown a NullValueInNestedPathException");
133         }
134     }
135 
136     public void testMapProperty() {
137         final Map map = new HashMap();
138         testBean.setMapProperty(map);
139         vm = pas.getPropertyValueModel("mapProperty[.key]");
140         Block setValueDirectly = new Block() {
141             public void handle(Object newValue) {
142                 map.put(".key", newValue);
143             }
144         };
145         Closure getValueDirectly = new Closure() {
146             public Object call(Object ignore) {
147                 return map.get(".key");
148             }
149         };
150         Object[] valuesToTest = new Object[] {"1", "2", null, "3"};
151         testSettingAndGetting(valuesToTest, getValueDirectly, setValueDirectly);
152 
153         try {
154             pas.getPropertyValueModel("mapProperty").setValue(null);
155             if (isStrictNullHandlingEnabled())
156             	fail("Should have thrown a NullValueInNestedPathException");
157         }
158         catch (NullValueInNestedPathException e) {
159             if (!isStrictNullHandlingEnabled())
160             	fail("Should not have thrown a NullValueInNestedPathException");
161         }
162     }
163 
164     public void testListProperty() {
165         final List list = new ArrayList();
166         list.add(null);
167         testBean.setListProperty(list);
168         vm = pas.getPropertyValueModel("listProperty[0]");
169 
170         Block setValueDirectly = new Block() {
171             public void handle(Object newValue) {
172                 list.set(0, newValue);
173             }
174         };
175         Closure getValueDirectly = new Closure() {
176             public Object call(Object ignore) {
177                 return list.get(0);
178             }
179         };
180         Object[] valuesToTest = new Object[] {"1", "2", null, "3"};
181         testSettingAndGetting(valuesToTest, getValueDirectly, setValueDirectly);
182 
183         list.add("a");
184         ValueModel vm2 = pas.getPropertyValueModel("listProperty[1]");
185         assertEquals("a", vm2.getValue());
186 
187         try {
188             List newList = new ArrayList();
189             pas.getPropertyValueModel("listProperty").setValue(newList);
190             if (isStrictNullHandlingEnabled())
191             	fail("Should have thrown an InvalidPropertyException");
192         }
193         catch (InvalidPropertyException e) {
194             if (!isStrictNullHandlingEnabled())
195             	fail("Should not have thrown an InvalidPropertyException");
196         }
197 
198         try {
199             pas.getPropertyValueModel("listProperty").setValue(null);
200             if (isStrictNullHandlingEnabled())
201             	fail("Should have thrown a NullValueInNestedPathException");
202         }
203         catch (NullValueInNestedPathException e) {
204             if (!isStrictNullHandlingEnabled())
205             	fail("Should not have thrown a NullValueInNestedPathException");
206         }
207     }
208 
209     public void testReadOnlyProperty() {
210         vm = pas.getPropertyValueModel("readOnly");
211 
212         testBean.readOnly = "1";
213         assertEquals(testBean.readOnly, vm.getValue());
214 
215         try {
216             vm.setValue("2");
217             fail("should have thrown NotWritablePropertyException");
218         } catch(NotWritablePropertyException e) {
219             // expected
220         }
221     }
222 
223     public void testWriteOnlyProperty() {
224         vm = pas.getPropertyValueModel("writeOnly");
225 
226         vm.setValue("2");
227         assertEquals("2" , testBean.writeOnly);
228 
229         try {
230             vm.getValue();
231             fail("should have thrown NotReadablePropertyException");
232         } catch(NotReadablePropertyException e) {
233             // expected
234         }
235     }
236 
237     public void testBeanThatImplementsPropertyChangePublisher() {
238         TestBeanWithPCP testBeanPCP = new TestBeanWithPCP();
239         pas.getDomainObjectHolder().setValue(testBeanPCP);
240 
241         vm = pas.getPropertyValueModel("boundProperty");
242         assertEquals("ValueModel should have registered a PropertyChangeListener", 1,
243                 testBeanPCP.getPropertyChangeListeners("boundProperty").length);
244 
245         vm.addValueChangeListener(pcl);
246         testBeanPCP.setBoundProperty("1");
247         assertEquals("Change to bound property should have been detected by ValueModel", 1, pcl.getEventsRecevied()
248                 .size());
249         PropertyChangeEvent e = (PropertyChangeEvent)pcl.getEventsRecevied().get(0);
250         assertEquals(vm, e.getSource());
251         assertEquals(null, e.getOldValue());
252         assertEquals("1", e.getNewValue());
253 
254         pcl.reset();
255         vm.setValue("2");
256         assertEquals("Change to bound property should have been detected by ValueModel", 1, pcl.getEventsRecevied().size());
257         e = (PropertyChangeEvent)pcl.getEventsRecevied().get(0);
258         assertEquals(vm, e.getSource());
259         assertEquals("1", e.getOldValue());
260         assertEquals("2", e.getNewValue());
261 
262         TestBeanWithPCP testBeanPCP2 = new TestBeanWithPCP();
263         pas.getDomainObjectHolder().setValue(testBeanPCP2);
264 
265         assertEquals("ValueModel should have removed the PropertyChangeListener", 0,
266                 testBeanPCP.getPropertyChangeListeners("boundProperty").length);
267         assertEquals("ValueModel should have registered a PropertyChangeListener", 1,
268                 testBeanPCP2.getPropertyChangeListeners("boundProperty").length);
269     }
270 
271     private void testSettingAndGetting(Object[] valuesToTest, Closure getValueDirectly, Block setValueDirectly) {
272         vm.addValueChangeListener(pcl);
273         for (int i = 0; i < valuesToTest.length; i++) {
274             final Object valueToTest = valuesToTest[i];
275             pcl.reset();
276             assertEquals("ValueModel does not have same value as bean property", getValueDirectly.call(null), vm.getValue());
277             setValueDirectly.call(valueToTest);
278             assertEquals("Change to bean not picked up by ValueModel", valueToTest, vm.getValue());
279             setValueDirectly.call(null);
280             assertEquals("Change to bean not picked up by ValueModel", null, vm.getValue());
281             vm.setValue(valueToTest);
282             assertEquals("Change to ValueModel not reflected in bean", valueToTest, getValueDirectly.call(null));
283             assertEquals("Change to ValueModel had no effect", valueToTest, vm.getValue());
284             if (valueToTest != null) {
285                 assertEquals("Incorrect number of property change events fired by value model",
286                     1, pcl.getEventsRecevied().size());
287                 PropertyChangeEvent e = (PropertyChangeEvent)pcl.getEventsRecevied().get(0);
288                 assertEquals(vm, e.getSource());
289                 assertEquals(null, e.getOldValue());
290                 assertEquals(valueToTest, e.getNewValue());
291             }
292         }
293     }
294 
295     protected void assertPropertyMetadata(PropertyMetadataAccessStrategy mas, String property, Class type, boolean isReadable, boolean isWriteable) {
296         assertEquals(type, mas.getPropertyType(property));
297         assertEquals(isReadable, mas.isReadable(property));
298         assertEquals(isWriteable, mas.isWriteable(property));
299     }
300 }