View Javadoc
1   /*
2    * Copyright 2002-2013 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of 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,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.beans;
18  
19  import java.beans.Introspector;
20  import java.beans.PropertyDescriptor;
21  import java.lang.reflect.Method;
22  import java.util.ArrayList;
23  import java.util.List;
24  
25  import org.junit.Test;
26  
27  import org.springframework.beans.factory.BeanFactory;
28  import org.springframework.beans.propertyeditors.CustomDateEditor;
29  import org.springframework.core.io.Resource;
30  import org.springframework.core.io.ResourceEditor;
31  import org.springframework.tests.sample.beans.DerivedTestBean;
32  import org.springframework.tests.sample.beans.ITestBean;
33  import org.springframework.tests.sample.beans.TestBean;
34  
35  import static org.junit.Assert.*;
36  
37  /**
38   * Unit tests for {@link BeanUtils}.
39   *
40   * @author Juergen Hoeller
41   * @author Rob Harrop
42   * @author Chris Beams
43   * @since 19.05.2003
44   */
45  public final class BeanUtilsTests {
46  
47  	@Test
48  	public void testInstantiateClass() {
49  		// give proper class
50  		BeanUtils.instantiateClass(ArrayList.class);
51  
52  		try {
53  			// give interface
54  			BeanUtils.instantiateClass(List.class);
55  			fail("Should have thrown FatalBeanException");
56  		}
57  		catch (FatalBeanException ex) {
58  			// expected
59  		}
60  
61  		try {
62  			// give class without default constructor
63  			BeanUtils.instantiateClass(CustomDateEditor.class);
64  			fail("Should have thrown FatalBeanException");
65  		}
66  		catch (FatalBeanException ex) {
67  			// expected
68  		}
69  	}
70  
71  	@Test
72  	public void testGetPropertyDescriptors() throws Exception {
73  		PropertyDescriptor[] actual = Introspector.getBeanInfo(TestBean.class).getPropertyDescriptors();
74  		PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(TestBean.class);
75  		assertNotNull("Descriptors should not be null", descriptors);
76  		assertEquals("Invalid number of descriptors returned", actual.length, descriptors.length);
77  	}
78  
79  	@Test
80  	public void testBeanPropertyIsArray() {
81  		PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(ContainerBean.class);
82  		for (PropertyDescriptor descriptor : descriptors) {
83  			if ("containedBeans".equals(descriptor.getName())) {
84  				assertTrue("Property should be an array", descriptor.getPropertyType().isArray());
85  				assertEquals(descriptor.getPropertyType().getComponentType(), ContainedBean.class);
86  			}
87  		}
88  	}
89  
90  	@Test
91  	public void testFindEditorByConvention() {
92  		assertEquals(ResourceEditor.class, BeanUtils.findEditorByConvention(Resource.class).getClass());
93  	}
94  
95  	@Test
96  	public void testCopyProperties() throws Exception {
97  		TestBean tb = new TestBean();
98  		tb.setName("rod");
99  		tb.setAge(32);
100 		tb.setTouchy("touchy");
101 		TestBean tb2 = new TestBean();
102 		assertTrue("Name empty", tb2.getName() == null);
103 		assertTrue("Age empty", tb2.getAge() == 0);
104 		assertTrue("Touchy empty", tb2.getTouchy() == null);
105 		BeanUtils.copyProperties(tb, tb2);
106 		assertTrue("Name copied", tb2.getName().equals(tb.getName()));
107 		assertTrue("Age copied", tb2.getAge() == tb.getAge());
108 		assertTrue("Touchy copied", tb2.getTouchy().equals(tb.getTouchy()));
109 	}
110 
111 	@Test
112 	public void testCopyPropertiesWithDifferentTypes1() throws Exception {
113 		DerivedTestBean tb = new DerivedTestBean();
114 		tb.setName("rod");
115 		tb.setAge(32);
116 		tb.setTouchy("touchy");
117 		TestBean tb2 = new TestBean();
118 		assertTrue("Name empty", tb2.getName() == null);
119 		assertTrue("Age empty", tb2.getAge() == 0);
120 		assertTrue("Touchy empty", tb2.getTouchy() == null);
121 		BeanUtils.copyProperties(tb, tb2);
122 		assertTrue("Name copied", tb2.getName().equals(tb.getName()));
123 		assertTrue("Age copied", tb2.getAge() == tb.getAge());
124 		assertTrue("Touchy copied", tb2.getTouchy().equals(tb.getTouchy()));
125 	}
126 
127 	@Test
128 	public void testCopyPropertiesWithDifferentTypes2() throws Exception {
129 		TestBean tb = new TestBean();
130 		tb.setName("rod");
131 		tb.setAge(32);
132 		tb.setTouchy("touchy");
133 		DerivedTestBean tb2 = new DerivedTestBean();
134 		assertTrue("Name empty", tb2.getName() == null);
135 		assertTrue("Age empty", tb2.getAge() == 0);
136 		assertTrue("Touchy empty", tb2.getTouchy() == null);
137 		BeanUtils.copyProperties(tb, tb2);
138 		assertTrue("Name copied", tb2.getName().equals(tb.getName()));
139 		assertTrue("Age copied", tb2.getAge() == tb.getAge());
140 		assertTrue("Touchy copied", tb2.getTouchy().equals(tb.getTouchy()));
141 	}
142 
143 	@Test
144 	public void testCopyPropertiesWithEditable() throws Exception {
145 		TestBean tb = new TestBean();
146 		assertTrue("Name empty", tb.getName() == null);
147 		tb.setAge(32);
148 		tb.setTouchy("bla");
149 		TestBean tb2 = new TestBean();
150 		tb2.setName("rod");
151 		assertTrue("Age empty", tb2.getAge() == 0);
152 		assertTrue("Touchy empty", tb2.getTouchy() == null);
153 
154 		// "touchy" should not be copied: it's not defined in ITestBean
155 		BeanUtils.copyProperties(tb, tb2, ITestBean.class);
156 		assertTrue("Name copied", tb2.getName() == null);
157 		assertTrue("Age copied", tb2.getAge() == 32);
158 		assertTrue("Touchy still empty", tb2.getTouchy() == null);
159 	}
160 
161 	@Test
162 	public void testCopyPropertiesWithIgnore() throws Exception {
163 		TestBean tb = new TestBean();
164 		assertTrue("Name empty", tb.getName() == null);
165 		tb.setAge(32);
166 		tb.setTouchy("bla");
167 		TestBean tb2 = new TestBean();
168 		tb2.setName("rod");
169 		assertTrue("Age empty", tb2.getAge() == 0);
170 		assertTrue("Touchy empty", tb2.getTouchy() == null);
171 
172 		// "spouse", "touchy", "age" should not be copied
173 		BeanUtils.copyProperties(tb, tb2, "spouse", "touchy", "age");
174 		assertTrue("Name copied", tb2.getName() == null);
175 		assertTrue("Age still empty", tb2.getAge() == 0);
176 		assertTrue("Touchy still empty", tb2.getTouchy() == null);
177 	}
178 
179 	@Test
180 	public void testCopyPropertiesWithIgnoredNonExistingProperty() {
181 		NameAndSpecialProperty source = new NameAndSpecialProperty();
182 		source.setName("name");
183 		TestBean target = new TestBean();
184 		BeanUtils.copyProperties(source, target, "specialProperty");
185 		assertEquals(target.getName(), "name");
186 	}
187 
188 	@Test
189 	public void testCopyPropertiesWithInvalidProperty() {
190 		InvalidProperty source = new InvalidProperty();
191 		source.setName("name");
192 		source.setFlag1(true);
193 		source.setFlag2(true);
194 		InvalidProperty target = new InvalidProperty();
195 		BeanUtils.copyProperties(source, target);
196 		assertEquals(target.getName(), "name");
197 		assertTrue(target.getFlag1());
198 		assertTrue(target.getFlag2());
199 	}
200 
201 	@Test
202 	public void testResolveSimpleSignature() throws Exception {
203 		Method desiredMethod = MethodSignatureBean.class.getMethod("doSomething");
204 		assertSignatureEquals(desiredMethod, "doSomething");
205 		assertSignatureEquals(desiredMethod, "doSomething()");
206 	}
207 
208 	@Test
209 	public void testResolveInvalidSignature() throws Exception {
210 		try {
211 			BeanUtils.resolveSignature("doSomething(", MethodSignatureBean.class);
212 			fail("Should not be able to parse with opening but no closing paren.");
213 		}
214 		catch (IllegalArgumentException ex) {
215 			// success
216 		}
217 
218 		try {
219 			BeanUtils.resolveSignature("doSomething)", MethodSignatureBean.class);
220 			fail("Should not be able to parse with closing but no opening paren.");
221 		}
222 		catch (IllegalArgumentException ex) {
223 			// success
224 		}
225 	}
226 
227 	@Test
228 	public void testResolveWithAndWithoutArgList() throws Exception {
229 		Method desiredMethod = MethodSignatureBean.class.getMethod("doSomethingElse", new Class[]{String.class, int.class});
230 		assertSignatureEquals(desiredMethod, "doSomethingElse");
231 		assertNull(BeanUtils.resolveSignature("doSomethingElse()", MethodSignatureBean.class));
232 	}
233 
234 	@Test
235 	public void testResolveTypedSignature() throws Exception {
236 		Method desiredMethod = MethodSignatureBean.class.getMethod("doSomethingElse", new Class[]{String.class, int.class});
237 		assertSignatureEquals(desiredMethod, "doSomethingElse(java.lang.String, int)");
238 	}
239 
240 	@Test
241 	public void testResolveOverloadedSignature() throws Exception {
242 		// test resolve with no args
243 		Method desiredMethod = MethodSignatureBean.class.getMethod("overloaded");
244 		assertSignatureEquals(desiredMethod, "overloaded()");
245 
246 		// resolve with single arg
247 		desiredMethod = MethodSignatureBean.class.getMethod("overloaded", new Class[]{String.class});
248 		assertSignatureEquals(desiredMethod, "overloaded(java.lang.String)");
249 
250 		// resolve with two args
251 		desiredMethod = MethodSignatureBean.class.getMethod("overloaded", new Class[]{String.class, BeanFactory.class});
252 		assertSignatureEquals(desiredMethod, "overloaded(java.lang.String, org.springframework.beans.factory.BeanFactory)");
253 	}
254 
255 	@Test
256 	public void testResolveSignatureWithArray() throws Exception {
257 		Method desiredMethod = MethodSignatureBean.class.getMethod("doSomethingWithAnArray", new Class[]{String[].class});
258 		assertSignatureEquals(desiredMethod, "doSomethingWithAnArray(java.lang.String[])");
259 
260 		desiredMethod = MethodSignatureBean.class.getMethod("doSomethingWithAMultiDimensionalArray", new Class[]{String[][].class});
261 		assertSignatureEquals(desiredMethod, "doSomethingWithAMultiDimensionalArray(java.lang.String[][])");
262 	}
263 
264 	@Test
265 	public void testSPR6063() {
266 		PropertyDescriptor[] descrs = BeanUtils.getPropertyDescriptors(Bean.class);
267 
268 		PropertyDescriptor keyDescr = BeanUtils.getPropertyDescriptor(Bean.class, "value");
269 		assertEquals(String.class, keyDescr.getPropertyType());
270 		for (PropertyDescriptor propertyDescriptor : descrs) {
271 			if (propertyDescriptor.getName().equals(keyDescr.getName())) {
272 				assertEquals(propertyDescriptor.getName() + " has unexpected type",
273 						keyDescr.getPropertyType(), propertyDescriptor.getPropertyType());
274 			}
275 		}
276 	}
277 
278 	private void assertSignatureEquals(Method desiredMethod, String signature) {
279 		assertEquals(desiredMethod, BeanUtils.resolveSignature(signature, MethodSignatureBean.class));
280 	}
281 
282 
283 	@SuppressWarnings("unused")
284 	private static class NameAndSpecialProperty {
285 
286 		private String name;
287 
288 		private int specialProperty;
289 
290 		public void setName(String name) {
291 			this.name = name;
292 		}
293 
294 		public String getName() {
295 			return this.name;
296 		}
297 
298 		public void setSpecialProperty(int specialProperty) {
299 			this.specialProperty = specialProperty;
300 		}
301 
302 		public int getSpecialProperty() {
303 			return specialProperty;
304 		}
305 	}
306 
307 
308 	@SuppressWarnings("unused")
309 	private static class InvalidProperty {
310 
311 		private String name;
312 
313 		private String value;
314 
315 		private boolean flag1;
316 
317 		private boolean flag2;
318 
319 		public void setName(String name) {
320 			this.name = name;
321 		}
322 
323 		public String getName() {
324 			return this.name;
325 		}
326 
327 		public void setValue(int value) {
328 			this.value = Integer.toString(value);
329 		}
330 
331 		public String getValue() {
332 			return this.value;
333 		}
334 
335 		public void setFlag1(boolean flag1) {
336 			this.flag1 = flag1;
337 		}
338 
339 		public Boolean getFlag1() {
340 			return this.flag1;
341 		}
342 
343 		public void setFlag2(Boolean flag2) {
344 			this.flag2 = flag2;
345 		}
346 
347 		public boolean getFlag2() {
348 			return this.flag2;
349 		}
350 	}
351 
352 
353 	@SuppressWarnings("unused")
354 	private static class ContainerBean {
355 
356 		private ContainedBean[] containedBeans;
357 
358 		public ContainedBean[] getContainedBeans() {
359 			return containedBeans;
360 		}
361 
362 		public void setContainedBeans(ContainedBean[] containedBeans) {
363 			this.containedBeans = containedBeans;
364 		}
365 	}
366 
367 
368 	@SuppressWarnings("unused")
369 	private static class ContainedBean {
370 
371 		private String name;
372 
373 		public String getName() {
374 			return name;
375 		}
376 
377 		public void setName(String name) {
378 			this.name = name;
379 		}
380 	}
381 
382 
383 	@SuppressWarnings("unused")
384 	private static class MethodSignatureBean {
385 
386 		public void doSomething() {
387 		}
388 
389 		public void doSomethingElse(String s, int x) {
390 		}
391 
392 		public void overloaded() {
393 		}
394 
395 		public void overloaded(String s) {
396 		}
397 
398 		public void overloaded(String s, BeanFactory beanFactory) {
399 		}
400 
401 		public void doSomethingWithAnArray(String[] strings) {
402 		}
403 
404 		public void doSomethingWithAMultiDimensionalArray(String[][] strings) {
405 		}
406 	}
407 
408 
409 	private interface MapEntry<K, V> {
410 
411 		K getKey();
412 
413 		void setKey(V value);
414 
415 		V getValue();
416 
417 		void setValue(V value);
418 	}
419 
420 
421 	private static class Bean implements MapEntry<String, String> {
422 
423 		private String key;
424 
425 		private String value;
426 
427 		@Override
428 		public String getKey() {
429 			return key;
430 		}
431 
432 		@Override
433 		public void setKey(String aKey) {
434 			key = aKey;
435 		}
436 
437 		@Override
438 		public String getValue() {
439 			return value;
440 		}
441 
442 		@Override
443 		public void setValue(String aValue) {
444 			value = aValue;
445 		}
446 	}
447 
448 }