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 }