001    /*
002     * Copyright 2002-2007 the original author or authors.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.binding.support;
017    
018    import java.beans.PropertyChangeEvent;
019    import java.util.ArrayList;
020    import java.util.HashMap;
021    import java.util.List;
022    import java.util.Map;
023    
024    import org.springframework.beans.InvalidPropertyException;
025    import org.springframework.beans.NotReadablePropertyException;
026    import org.springframework.beans.NotWritablePropertyException;
027    import org.springframework.beans.NullValueInNestedPathException;
028    import org.springframework.binding.MutablePropertyAccessStrategy;
029    import org.springframework.binding.PropertyMetadataAccessStrategy;
030    import org.springframework.binding.value.ValueModel;
031    import org.springframework.rules.closure.Closure;
032    import org.springframework.rules.closure.support.Block;
033    import org.springframework.richclient.test.SpringRichTestCase;
034    
035    /**
036     * Tests class {@link AbstractPropertyAccessStrategy}.
037     *
038     * @author Oliver Hutchison
039    * @author Arne Limburg
040     */
041    public abstract class AbstractPropertyAccessStrategyTests extends SpringRichTestCase {
042    
043        protected AbstractPropertyAccessStrategy pas;
044    
045        protected TestBean testBean;
046    
047        protected ValueModel vm;
048    
049        protected TestPropertyChangeListener pcl;
050    
051        protected void doSetUp() throws Exception {
052            testBean = new TestBean();
053            pas = createPropertyAccessStrategy(testBean);
054            pcl = new TestPropertyChangeListener(ValueModel.VALUE_PROPERTY);
055        }
056    
057        protected abstract AbstractPropertyAccessStrategy createPropertyAccessStrategy(Object target);
058    
059        protected boolean isStrictNullHandlingEnabled() {
060            return true;
061        }
062    
063        public void testSimpleProperty() {
064            vm = pas.getPropertyValueModel("simpleProperty");
065            Block setValueDirectly = new Block() {
066               public void handle(Object newValue) {
067                    testBean.setSimpleProperty((String)newValue);
068                }
069            };
070            Closure getValueDirectly = new Closure() {
071                public Object call(Object ignore) {
072                    return testBean.getSimpleProperty();
073                }
074            };
075            Object[] valuesToTest = new Object[] {"1", "2", null, "3"};
076    
077            testSettingAndGetting(valuesToTest, getValueDirectly, setValueDirectly);
078        }
079    
080        public void testNestedProperty() {
081            final TestBean nestedProperty = new TestBean();
082            testBean.setNestedProperty(nestedProperty);
083            vm = pas.getPropertyValueModel("nestedProperty.simpleProperty");
084            Block setValueDirectly = new Block() {
085                public void handle(Object newValue) {
086                    nestedProperty.setSimpleProperty((String)newValue);
087                }
088            };
089            Closure getValueDirectly = new Closure() {
090                public Object call(Object ignored) {
091                    return nestedProperty.getSimpleProperty();
092                }
093            };
094            Object[] valuesToTest = new Object[] {"1", "2", null, "3"};
095    
096            testSettingAndGetting(valuesToTest, getValueDirectly, setValueDirectly);
097        }
098    
099        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    }