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 }