View Javadoc
1   /*
2    * Copyright 2002-2014 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.BeanInfo;
20  import java.beans.IndexedPropertyDescriptor;
21  import java.beans.IntrospectionException;
22  import java.beans.Introspector;
23  import java.beans.PropertyDescriptor;
24  import java.math.BigDecimal;
25  
26  import org.junit.Test;
27  
28  import org.springframework.core.JdkVersion;
29  import org.springframework.tests.sample.beans.TestBean;
30  
31  import static org.hamcrest.CoreMatchers.equalTo;
32  import static org.hamcrest.CoreMatchers.is;
33  import static org.hamcrest.Matchers.*;
34  import static org.junit.Assert.*;
35  
36  /**
37   * @author Chris Beams
38   * @since 3.1
39   */
40  public class ExtendedBeanInfoTests {
41  
42  	@Test
43  	public void standardReadMethodOnly() throws IntrospectionException {
44  		@SuppressWarnings("unused") class C {
45  			public String getFoo() { return null; }
46  		}
47  
48  		BeanInfo bi = Introspector.getBeanInfo(C.class);
49  		ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
50  
51  		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
52  		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
53  
54  		assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
55  		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(false));
56  	}
57  
58  	@Test
59  	public void standardWriteMethodOnly() throws IntrospectionException {
60  		@SuppressWarnings("unused") class C {
61  			public void setFoo(String f) { }
62  		}
63  
64  		BeanInfo bi = Introspector.getBeanInfo(C.class);
65  		ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
66  
67  		assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
68  		assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
69  
70  		assertThat(hasReadMethodForProperty(ebi, "foo"), is(false));
71  		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
72  	}
73  
74  	@Test
75  	public void standardReadAndWriteMethods() throws IntrospectionException {
76  		@SuppressWarnings("unused") class C {
77  			public void setFoo(String f) { }
78  			public String getFoo() { return null; }
79  		}
80  
81  		BeanInfo bi = Introspector.getBeanInfo(C.class);
82  		ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
83  
84  		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
85  		assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
86  
87  		assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
88  		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
89  	}
90  
91  	@Test
92  	public void nonStandardWriteMethodOnly() throws IntrospectionException {
93  		@SuppressWarnings("unused") class C {
94  			public C setFoo(String foo) { return this; }
95  		}
96  
97  		BeanInfo bi = Introspector.getBeanInfo(C.class);
98  		ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
99  
100 		assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
101 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
102 
103 		assertThat(hasReadMethodForProperty(ebi, "foo"), is(false));
104 		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
105 	}
106 
107 	@Test
108 	public void standardReadAndNonStandardWriteMethods() throws IntrospectionException {
109 		@SuppressWarnings("unused") class C {
110 			public String getFoo() { return null; }
111 			public C setFoo(String foo) { return this; }
112 		}
113 
114 		BeanInfo bi = Introspector.getBeanInfo(C.class);
115 
116 		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
117 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
118 
119 		ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
120 
121 		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
122 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
123 
124 		assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
125 		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
126 	}
127 
128 	@Test
129 	public void standardReadAndNonStandardIndexedWriteMethod() throws IntrospectionException {
130 		@SuppressWarnings("unused") class C {
131 			public String[] getFoo() { return null; }
132 			public C setFoo(int i, String foo) { return this; }
133 		}
134 
135 		BeanInfo bi = Introspector.getBeanInfo(C.class);
136 
137 		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
138 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
139 		assertThat(hasIndexedWriteMethodForProperty(bi, "foo"), is(trueUntilJdk17()));
140 
141 		BeanInfo ebi = new ExtendedBeanInfo(bi);
142 
143 		assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
144 		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(false));
145 		assertThat(hasIndexedWriteMethodForProperty(ebi, "foo"), is(true));
146 	}
147 
148 	@Test
149 	public void standardReadMethodsAndOverloadedNonStandardWriteMethods() throws Exception {
150 		@SuppressWarnings("unused") class C {
151 			public String getFoo() { return null; }
152 			public C setFoo(String foo) { return this; }
153 			public C setFoo(Number foo) { return this; }
154 		}
155 
156 		BeanInfo bi = Introspector.getBeanInfo(C.class);
157 
158 		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
159 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
160 
161 		ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
162 
163 		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
164 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
165 
166 		assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
167 		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
168 
169 		for (PropertyDescriptor pd : ebi.getPropertyDescriptors()) {
170 			if (pd.getName().equals("foo")) {
171 				assertThat(pd.getWriteMethod(), is(C.class.getMethod("setFoo", String.class)));
172 				return;
173 			}
174 		}
175 		fail("never matched write method");
176 	}
177 
178 	@Test
179 	public void cornerSpr9414() throws IntrospectionException {
180 		@SuppressWarnings("unused") class Parent {
181 			public Number getProperty1() {
182 				return 1;
183 			}
184 		}
185 		class Child extends Parent {
186 			@Override
187 			public Integer getProperty1() {
188 				return 2;
189 			}
190 		}
191 		{ // always passes
192 			ExtendedBeanInfo bi = new ExtendedBeanInfo(Introspector.getBeanInfo(Parent.class));
193 			assertThat(hasReadMethodForProperty(bi, "property1"), is(true));
194 		}
195 		{ // failed prior to fix for SPR-9414
196 			ExtendedBeanInfo bi = new ExtendedBeanInfo(Introspector.getBeanInfo(Child.class));
197 			assertThat(hasReadMethodForProperty(bi, "property1"), is(true));
198 		}
199 	}
200 
201 	@Test
202 	public void cornerSpr9453() throws IntrospectionException {
203 		final class Bean implements Spr9453<Class<?>> {
204 			@Override
205 			public Class<?> getProp() {
206 				return null;
207 			}
208 		}
209 		{ // always passes
210 			BeanInfo info = Introspector.getBeanInfo(Bean.class);
211 			assertThat(info.getPropertyDescriptors().length, equalTo(2));
212 		}
213 		{ // failed prior to fix for SPR-9453
214 			BeanInfo info = new ExtendedBeanInfo(Introspector.getBeanInfo(Bean.class));
215 			assertThat(info.getPropertyDescriptors().length, equalTo(2));
216 		}
217 	}
218 
219 	@Test
220 	public void standardReadMethodInSuperclassAndNonStandardWriteMethodInSubclass() throws Exception {
221 		@SuppressWarnings("unused") class B {
222 			public String getFoo() { return null; }
223 		}
224 		@SuppressWarnings("unused") class C extends B {
225 			public C setFoo(String foo) { return this; }
226 		}
227 
228 		BeanInfo bi = Introspector.getBeanInfo(C.class);
229 
230 		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
231 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
232 
233 		ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
234 
235 		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
236 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
237 
238 		assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
239 		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
240 	}
241 
242 	@Test
243 	public void standardReadMethodInSuperAndSubclassesAndGenericBuilderStyleNonStandardWriteMethodInSuperAndSubclasses() throws Exception {
244 		abstract class B<This extends B<This>> {
245 			@SuppressWarnings("unchecked")
246 			protected final This instance = (This) this;
247 			private String foo;
248 			public String getFoo() { return foo; }
249 			public This setFoo(String foo) {
250 				this.foo = foo;
251 				return this.instance;
252 			}
253 		}
254 
255 		class C extends B<C> {
256 			private int bar = -1;
257 			public int getBar() { return bar; }
258 			public C setBar(int bar) {
259 				this.bar = bar;
260 				return this.instance;
261 			}
262 		}
263 
264 		C c = new C()
265 			.setFoo("blue")
266 			.setBar(42);
267 
268 		assertThat(c.getFoo(), is("blue"));
269 		assertThat(c.getBar(), is(42));
270 
271 		BeanInfo bi = Introspector.getBeanInfo(C.class);
272 
273 		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
274 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
275 
276 		assertThat(hasReadMethodForProperty(bi, "bar"), is(true));
277 		assertThat(hasWriteMethodForProperty(bi, "bar"), is(false));
278 
279 		BeanInfo ebi = new ExtendedBeanInfo(bi);
280 
281 		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
282 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
283 
284 		assertThat(hasReadMethodForProperty(bi, "bar"), is(true));
285 		assertThat(hasWriteMethodForProperty(bi, "bar"), is(false));
286 
287 		assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
288 		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
289 
290 		assertThat(hasReadMethodForProperty(ebi, "bar"), is(true));
291 		assertThat(hasWriteMethodForProperty(ebi, "bar"), is(true));
292 	}
293 
294 	@Test
295 	public void nonPublicStandardReadAndWriteMethods() throws Exception {
296 		@SuppressWarnings("unused") class C {
297 			String getFoo() { return null; }
298 			C setFoo(String foo) { return this; }
299 		}
300 
301 		BeanInfo bi = Introspector.getBeanInfo(C.class);
302 		BeanInfo ebi = new ExtendedBeanInfo(bi);
303 
304 		assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
305 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
306 
307 		assertThat(hasReadMethodForProperty(ebi, "foo"), is(false));
308 		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(false));
309 	}
310 
311 	/**
312 	 * {@link ExtendedBeanInfo} should behave exactly like {@link BeanInfo}
313 	 * in strange edge cases.
314 	 */
315 	@Test
316 	public void readMethodReturnsSupertypeOfWriteMethodParameter() throws IntrospectionException {
317 		@SuppressWarnings("unused") class C {
318 			public Number getFoo() { return null; }
319 			public void setFoo(Integer foo) { }
320 		}
321 
322 		BeanInfo bi = Introspector.getBeanInfo(C.class);
323 		BeanInfo ebi = new ExtendedBeanInfo(bi);
324 
325 		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
326 		assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
327 		assertEquals(hasWriteMethodForProperty(bi, "foo"), hasWriteMethodForProperty(ebi, "foo"));
328 	}
329 
330 	@Test
331 	public void indexedReadMethodReturnsSupertypeOfIndexedWriteMethodParameter() throws IntrospectionException {
332 		@SuppressWarnings("unused") class C {
333 			public Number getFoos(int index) { return null; }
334 			public void setFoos(int index, Integer foo) { }
335 		}
336 
337 		BeanInfo bi = Introspector.getBeanInfo(C.class);
338 		BeanInfo ebi = new ExtendedBeanInfo(bi);
339 
340 		assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
341 		assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true));
342 		assertEquals(hasIndexedWriteMethodForProperty(bi, "foos"), hasIndexedWriteMethodForProperty(ebi, "foos"));
343 	}
344 
345 	/**
346 	 * {@link ExtendedBeanInfo} should behave exactly like {@link BeanInfo}
347 	 * in strange edge cases.
348 	 */
349 	@Test
350 	public void readMethodReturnsSubtypeOfWriteMethodParameter() throws IntrospectionException {
351 		@SuppressWarnings("unused") class C {
352 			public Integer getFoo() { return null; }
353 			public void setFoo(Number foo) { }
354 		}
355 
356 		BeanInfo bi = Introspector.getBeanInfo(C.class);
357 		BeanInfo ebi = new ExtendedBeanInfo(bi);
358 
359 		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
360 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
361 
362 		assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
363 		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(false));
364 	}
365 
366 	@Test
367 	public void indexedReadMethodReturnsSubtypeOfIndexedWriteMethodParameter() throws IntrospectionException {
368 		@SuppressWarnings("unused") class C {
369 			public Integer getFoos(int index) { return null; }
370 			public void setFoo(int index, Number foo) { }
371 		}
372 
373 		BeanInfo bi = Introspector.getBeanInfo(C.class);
374 		BeanInfo ebi = new ExtendedBeanInfo(bi);
375 
376 		assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
377 		assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(false));
378 
379 		assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true));
380 		assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(false));
381 	}
382 
383 	@Test
384 	public void indexedReadMethodOnly() throws IntrospectionException {
385 		@SuppressWarnings("unused")
386 		class C {
387 			// indexed read method
388 			public String getFoos(int i) { return null; }
389 		}
390 
391 		BeanInfo bi = Introspector.getBeanInfo(C.class);
392 		BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class));
393 
394 		assertThat(hasReadMethodForProperty(bi, "foos"), is(false));
395 		assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
396 
397 		assertThat(hasReadMethodForProperty(ebi, "foos"), is(false));
398 		assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true));
399 	}
400 
401 	@Test
402 	public void indexedWriteMethodOnly() throws IntrospectionException {
403 		@SuppressWarnings("unused")
404 		class C {
405 			// indexed write method
406 			public void setFoos(int i, String foo) { }
407 		}
408 
409 		BeanInfo bi = Introspector.getBeanInfo(C.class);
410 		BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class));
411 
412 		assertThat(hasWriteMethodForProperty(bi, "foos"), is(false));
413 		assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true));
414 
415 		assertThat(hasWriteMethodForProperty(ebi, "foos"), is(false));
416 		assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(true));
417 	}
418 
419 	@Test
420 	public void indexedReadAndIndexedWriteMethods() throws IntrospectionException {
421 		@SuppressWarnings("unused")
422 		class C {
423 			// indexed read method
424 			public String getFoos(int i) { return null; }
425 			// indexed write method
426 			public void setFoos(int i, String foo) { }
427 		}
428 
429 		BeanInfo bi = Introspector.getBeanInfo(C.class);
430 		BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class));
431 
432 		assertThat(hasReadMethodForProperty(bi, "foos"), is(false));
433 		assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
434 		assertThat(hasWriteMethodForProperty(bi, "foos"), is(false));
435 		assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true));
436 
437 		assertThat(hasReadMethodForProperty(ebi, "foos"), is(false));
438 		assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true));
439 		assertThat(hasWriteMethodForProperty(ebi, "foos"), is(false));
440 		assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(true));
441 	}
442 
443 	@Test
444 	public void readAndWriteAndIndexedReadAndIndexedWriteMethods() throws IntrospectionException {
445 		@SuppressWarnings("unused")
446 		class C {
447 			// read method
448 			public String[] getFoos() { return null; }
449 			// indexed read method
450 			public String getFoos(int i) { return null; }
451 			// write method
452 			public void setFoos(String[] foos) { }
453 			// indexed write method
454 			public void setFoos(int i, String foo) { }
455 		}
456 
457 		BeanInfo bi = Introspector.getBeanInfo(C.class);
458 		BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class));
459 
460 		assertThat(hasReadMethodForProperty(bi, "foos"), is(true));
461 		assertThat(hasWriteMethodForProperty(bi, "foos"), is(true));
462 		assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
463 		assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true));
464 
465 		assertThat(hasReadMethodForProperty(ebi, "foos"), is(true));
466 		assertThat(hasWriteMethodForProperty(ebi, "foos"), is(true));
467 		assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true));
468 		assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(true));
469 	}
470 
471 	@Test
472 	public void indexedReadAndNonStandardIndexedWrite() throws IntrospectionException {
473 		@SuppressWarnings("unused")
474 		class C {
475 			// indexed read method
476 			public String getFoos(int i) { return null; }
477 			// non-standard indexed write method
478 			public C setFoos(int i, String foo) { return this; }
479 		}
480 
481 		BeanInfo bi = Introspector.getBeanInfo(C.class);
482 
483 		assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
484 		// interesting! standard Inspector picks up non-void return types on indexed write methods by default
485 		assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(trueUntilJdk17()));
486 
487 		BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class));
488 
489 		assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true));
490 		assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(true));
491 	}
492 
493 	@Test
494 	public void indexedReadAndNonStandardWriteAndNonStandardIndexedWrite() throws IntrospectionException {
495 		@SuppressWarnings("unused")
496 		class C {
497 			// non-standard write method
498 			public C setFoos(String[] foos) { return this; }
499 			// indexed read method
500 			public String getFoos(int i) { return null; }
501 			// non-standard indexed write method
502 			public C setFoos(int i, String foo) { return this; }
503 		}
504 
505 		BeanInfo bi = Introspector.getBeanInfo(C.class);
506 
507 		assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
508 		assertThat(hasWriteMethodForProperty(bi, "foos"), is(false));
509 		// again as above, standard Inspector picks up non-void return types on indexed write methods by default
510 		assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(trueUntilJdk17()));
511 
512 		BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class));
513 
514 		assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
515 		assertThat(hasWriteMethodForProperty(bi, "foos"), is(false));
516 		assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(trueUntilJdk17()));
517 
518 		assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true));
519 		assertThat(hasWriteMethodForProperty(ebi, "foos"), is(true));
520 		assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(true));
521 	}
522 
523 	@Test
524 	public void cornerSpr9702() throws IntrospectionException {
525 		{ // baseline with standard write method
526 			@SuppressWarnings("unused")
527 			class C {
528 				// VOID-RETURNING, NON-INDEXED write method
529 				public void setFoos(String[] foos) { }
530 				// indexed read method
531 				public String getFoos(int i) { return null; }
532 			}
533 
534 			BeanInfo bi = Introspector.getBeanInfo(C.class);
535 			assertThat(hasReadMethodForProperty(bi, "foos"), is(false));
536 			assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
537 			assertThat(hasWriteMethodForProperty(bi, "foos"), is(true));
538 			assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(false));
539 
540 			BeanInfo ebi = Introspector.getBeanInfo(C.class);
541 			assertThat(hasReadMethodForProperty(ebi, "foos"), is(false));
542 			assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true));
543 			assertThat(hasWriteMethodForProperty(ebi, "foos"), is(true));
544 			assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(false));
545 		}
546 		{ // variant with non-standard write method
547 			@SuppressWarnings("unused")
548 			class C {
549 				// NON-VOID-RETURNING, NON-INDEXED write method
550 				public C setFoos(String[] foos) { return this; }
551 				// indexed read method
552 				public String getFoos(int i) { return null; }
553 			}
554 
555 			BeanInfo bi = Introspector.getBeanInfo(C.class);
556 			assertThat(hasReadMethodForProperty(bi, "foos"), is(false));
557 			assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
558 			assertThat(hasWriteMethodForProperty(bi, "foos"), is(false));
559 			assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(false));
560 
561 			BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class));
562 			assertThat(hasReadMethodForProperty(ebi, "foos"), is(false));
563 			assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true));
564 			assertThat(hasWriteMethodForProperty(ebi, "foos"), is(true));
565 			assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(false));
566 		}
567 	}
568 
569 	/**
570 	 * Prior to SPR-10111 (a follow-up fix for SPR-9702), this method would throw an
571 	 * IntrospectionException regarding a "type mismatch between indexed and non-indexed
572 	 * methods" intermittently (approximately one out of every four times) under JDK 7
573 	 * due to non-deterministic results from {@link Class#getDeclaredMethods()}.
574 	 * See http://bugs.sun.com/view_bug.do?bug_id=7023180
575 	 * @see #cornerSpr9702()
576 	 */
577 	@Test
578 	public void cornerSpr10111() throws Exception {
579 		new ExtendedBeanInfo(Introspector.getBeanInfo(BigDecimal.class));
580 	}
581 
582 	@Test
583 	public void subclassWriteMethodWithCovariantReturnType() throws IntrospectionException {
584 		@SuppressWarnings("unused") class B {
585 			public String getFoo() { return null; }
586 			public Number setFoo(String foo) { return null; }
587 		}
588 		class C extends B {
589 			@Override
590 			public String getFoo() { return null; }
591 			@Override
592 			public Integer setFoo(String foo) { return null; }
593 		}
594 
595 		BeanInfo bi = Introspector.getBeanInfo(C.class);
596 
597 		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
598 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
599 
600 		BeanInfo ebi = new ExtendedBeanInfo(bi);
601 
602 		assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
603 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
604 
605 		assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
606 		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
607 
608 		assertThat(ebi.getPropertyDescriptors().length, equalTo(bi.getPropertyDescriptors().length));
609 	}
610 
611 	@Test
612 	public void nonStandardReadMethodAndStandardWriteMethod() throws IntrospectionException {
613 		@SuppressWarnings("unused") class C {
614 			public void getFoo() { }
615 			public void setFoo(String foo) { }
616 		}
617 
618 		BeanInfo bi = Introspector.getBeanInfo(C.class);
619 		BeanInfo ebi = new ExtendedBeanInfo(bi);
620 
621 		assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
622 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
623 
624 		assertThat(hasReadMethodForProperty(ebi, "foo"), is(false));
625 		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
626 	}
627 
628 	/**
629 	 * Ensures that an empty string is not passed into a PropertyDescriptor constructor. This
630 	 * could occur when handling ArrayList.set(int,Object)
631 	 */
632 	@Test
633 	public void emptyPropertiesIgnored() throws IntrospectionException {
634 		@SuppressWarnings("unused") class C {
635 			public Object set(Object o) { return null; }
636 			public Object set(int i, Object o) { return null; }
637 		}
638 
639 		BeanInfo bi = Introspector.getBeanInfo(C.class);
640 		BeanInfo ebi = new ExtendedBeanInfo(bi);
641 
642 		assertThat(ebi.getPropertyDescriptors(), equalTo(bi.getPropertyDescriptors()));
643 	}
644 
645 	@Test
646 	public void overloadedNonStandardWriteMethodsOnly_orderA() throws IntrospectionException, SecurityException, NoSuchMethodException {
647 		@SuppressWarnings("unused") class C {
648 			public Object setFoo(String p) { return new Object(); }
649 			public Object setFoo(int p) { return new Object(); }
650 		}
651 		BeanInfo bi = Introspector.getBeanInfo(C.class);
652 
653 		assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
654 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
655 
656 		BeanInfo ebi = new ExtendedBeanInfo(bi);
657 
658 		assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
659 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
660 
661 		assertThat(hasReadMethodForProperty(ebi, "foo"), is(false));
662 		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
663 
664 		for (PropertyDescriptor pd : ebi.getPropertyDescriptors()) {
665 			if (pd.getName().equals("foo")) {
666 				assertThat(pd.getWriteMethod(), is(C.class.getMethod("setFoo", String.class)));
667 				return;
668 			}
669 		}
670 		fail("never matched write method");
671 	}
672 
673 	@Test
674 	public void overloadedNonStandardWriteMethodsOnly_orderB() throws IntrospectionException, SecurityException, NoSuchMethodException {
675 		@SuppressWarnings("unused") class C {
676 			public Object setFoo(int p) { return new Object(); }
677 			public Object setFoo(String p) { return new Object(); }
678 		}
679 		BeanInfo bi = Introspector.getBeanInfo(C.class);
680 
681 		assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
682 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
683 
684 		BeanInfo ebi = new ExtendedBeanInfo(bi);
685 
686 		assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
687 		assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
688 
689 		assertThat(hasReadMethodForProperty(ebi, "foo"), is(false));
690 		assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
691 
692 		for (PropertyDescriptor pd : ebi.getPropertyDescriptors()) {
693 			if (pd.getName().equals("foo")) {
694 				assertThat(pd.getWriteMethod(), is(C.class.getMethod("setFoo", String.class)));
695 				return;
696 			}
697 		}
698 		fail("never matched write method");
699 	}
700 
701 	/**
702 	 * Corners the bug revealed by SPR-8522, in which an (apparently) indexed write method
703 	 * without a corresponding indexed read method would fail to be processed correctly by
704 	 * ExtendedBeanInfo. The local class C below represents the relevant methods from
705 	 * Google's GsonBuilder class. Interestingly, the setDateFormat(int, int) method was
706 	 * not actually intended to serve as an indexed write method; it just appears that way.
707 	 */
708 	@Test
709 	public void reproSpr8522() throws IntrospectionException {
710 		@SuppressWarnings("unused") class C {
711 			public Object setDateFormat(String pattern) { return new Object(); }
712 			public Object setDateFormat(int style) { return new Object(); }
713 			public Object setDateFormat(int dateStyle, int timeStyle) { return new Object(); }
714 		}
715 		BeanInfo bi = Introspector.getBeanInfo(C.class);
716 
717 		assertThat(hasReadMethodForProperty(bi, "dateFormat"), is(false));
718 		assertThat(hasWriteMethodForProperty(bi, "dateFormat"), is(false));
719 		assertThat(hasIndexedReadMethodForProperty(bi, "dateFormat"), is(false));
720 		assertThat(hasIndexedWriteMethodForProperty(bi, "dateFormat"), is(trueUntilJdk17()));
721 
722 		BeanInfo ebi = new ExtendedBeanInfo(bi);
723 
724 		assertThat(hasReadMethodForProperty(bi, "dateFormat"), is(false));
725 		assertThat(hasWriteMethodForProperty(bi, "dateFormat"), is(false));
726 		assertThat(hasIndexedReadMethodForProperty(bi, "dateFormat"), is(false));
727 		assertThat(hasIndexedWriteMethodForProperty(bi, "dateFormat"), is(trueUntilJdk17()));
728 
729 		assertThat(hasReadMethodForProperty(ebi, "dateFormat"), is(false));
730 		assertThat(hasWriteMethodForProperty(ebi, "dateFormat"), is(true));
731 		assertThat(hasIndexedReadMethodForProperty(ebi, "dateFormat"), is(false));
732 		assertThat(hasIndexedWriteMethodForProperty(ebi, "dateFormat"), is(trueUntilJdk17()));
733 	}
734 
735 	@Test
736 	public void propertyCountsMatch() throws IntrospectionException {
737 		BeanInfo bi = Introspector.getBeanInfo(TestBean.class);
738 		BeanInfo ebi = new ExtendedBeanInfo(bi);
739 
740 		assertThat(ebi.getPropertyDescriptors().length, equalTo(bi.getPropertyDescriptors().length));
741 	}
742 
743 	@Test
744 	public void propertyCountsWithNonStandardWriteMethod() throws IntrospectionException {
745 		class ExtendedTestBean extends TestBean {
746 			@SuppressWarnings("unused")
747 			public ExtendedTestBean setFoo(String s) { return this; }
748 		}
749 		BeanInfo bi = Introspector.getBeanInfo(ExtendedTestBean.class);
750 		BeanInfo ebi = new ExtendedBeanInfo(bi);
751 
752 		boolean found = false;
753 		for (PropertyDescriptor pd : ebi.getPropertyDescriptors()) {
754 			if (pd.getName().equals("foo")) {
755 				found = true;
756 			}
757 		}
758 		assertThat(found, is(true));
759 		assertThat(ebi.getPropertyDescriptors().length, equalTo(bi.getPropertyDescriptors().length+1));
760 	}
761 
762 	/**
763 	 * {@link BeanInfo#getPropertyDescriptors()} returns alphanumerically sorted.
764 	 * Test that {@link ExtendedBeanInfo#getPropertyDescriptors()} does the same.
765 	 */
766 	@Test
767 	public void propertyDescriptorOrderIsEqual() throws IntrospectionException {
768 		BeanInfo bi = Introspector.getBeanInfo(TestBean.class);
769 		BeanInfo ebi = new ExtendedBeanInfo(bi);
770 
771 		for (int i = 0; i < bi.getPropertyDescriptors().length; i++) {
772 			assertThat("element " + i + " in BeanInfo and ExtendedBeanInfo propertyDescriptor arrays do not match",
773 					ebi.getPropertyDescriptors()[i].getName(), equalTo(bi.getPropertyDescriptors()[i].getName()));
774 		}
775 	}
776 
777 	@Test
778 	public void propertyDescriptorComparator() throws IntrospectionException {
779 		ExtendedBeanInfo.PropertyDescriptorComparator c = new ExtendedBeanInfo.PropertyDescriptorComparator();
780 
781 		assertThat(c.compare(new PropertyDescriptor("a", null, null), new PropertyDescriptor("a", null, null)), equalTo(0));
782 		assertThat(c.compare(new PropertyDescriptor("abc", null, null), new PropertyDescriptor("abc", null, null)), equalTo(0));
783 		assertThat(c.compare(new PropertyDescriptor("a", null, null), new PropertyDescriptor("b", null, null)), lessThan(0));
784 		assertThat(c.compare(new PropertyDescriptor("b", null, null), new PropertyDescriptor("a", null, null)), greaterThan(0));
785 		assertThat(c.compare(new PropertyDescriptor("abc", null, null), new PropertyDescriptor("abd", null, null)), lessThan(0));
786 		assertThat(c.compare(new PropertyDescriptor("xyz", null, null), new PropertyDescriptor("123", null, null)), greaterThan(0));
787 		assertThat(c.compare(new PropertyDescriptor("a", null, null), new PropertyDescriptor("abc", null, null)), lessThan(0));
788 		assertThat(c.compare(new PropertyDescriptor("abc", null, null), new PropertyDescriptor("a", null, null)), greaterThan(0));
789 		assertThat(c.compare(new PropertyDescriptor("abc", null, null), new PropertyDescriptor("b", null, null)), lessThan(0));
790 
791 		assertThat(c.compare(new PropertyDescriptor(" ", null, null), new PropertyDescriptor("a", null, null)), lessThan(0));
792 		assertThat(c.compare(new PropertyDescriptor("1", null, null), new PropertyDescriptor("a", null, null)), lessThan(0));
793 		assertThat(c.compare(new PropertyDescriptor("a", null, null), new PropertyDescriptor("A", null, null)), greaterThan(0));
794 	}
795 
796 	@Test
797 	public void reproSpr8806() throws IntrospectionException {
798 		// does not throw
799 		Introspector.getBeanInfo(LawLibrary.class);
800 
801 		// does not throw after the changes introduced in SPR-8806
802 		new ExtendedBeanInfo(Introspector.getBeanInfo(LawLibrary.class));
803 	}
804 
805 	@Test
806 	public void cornerSpr8949() throws IntrospectionException {
807 		class A {
808 			@SuppressWarnings("unused")
809 			public boolean isTargetMethod() {
810 				return false;
811 			}
812 		}
813 
814 		class B extends A {
815 			@Override
816 			public boolean isTargetMethod() {
817 				return false;
818 			}
819 		}
820 
821 		BeanInfo bi = Introspector.getBeanInfo(B.class);
822 
823 		// java.beans.Introspector returns the "wrong" declaring class for overridden read
824 		// methods, which in turn violates expectations in {@link ExtendedBeanInfo} regarding
825 		// method equality. Spring's {@link ClassUtils#getMostSpecificMethod(Method, Class)}
826 		// helps out here, and is now put into use in ExtendedBeanInfo as well.
827 		BeanInfo ebi = new ExtendedBeanInfo(bi);
828 
829 		assertThat(hasReadMethodForProperty(bi, "targetMethod"), is(true));
830 		assertThat(hasWriteMethodForProperty(bi, "targetMethod"), is(false));
831 
832 		assertThat(hasReadMethodForProperty(ebi, "targetMethod"), is(true));
833 		assertThat(hasWriteMethodForProperty(ebi, "targetMethod"), is(false));
834 	}
835 
836 	@Test
837 	public void cornerSpr8937() throws IntrospectionException {
838 		@SuppressWarnings("unused") class A {
839 			public void setAddress(String addr){ }
840 			public void setAddress(int index, String addr) { }
841 			public String getAddress(int index){ return null; }
842 		}
843 
844 		// Baseline: ExtendedBeanInfo needs to behave exactly like the following...
845 		boolean hasReadMethod;
846 		boolean hasWriteMethod;
847 		boolean hasIndexedReadMethod;
848 		boolean hasIndexedWriteMethod;
849 		{
850 			BeanInfo bi = Introspector.getBeanInfo(A.class);
851 			hasReadMethod = hasReadMethodForProperty(bi, "address");
852 			hasWriteMethod = hasWriteMethodForProperty(bi, "address");
853 			hasIndexedReadMethod = hasIndexedReadMethodForProperty(bi, "address");
854 			hasIndexedWriteMethod = hasIndexedWriteMethodForProperty(bi, "address");
855 		}
856 		{
857 			BeanInfo bi = new ExtendedBeanInfo(Introspector.getBeanInfo(A.class));
858 			assertEquals(hasReadMethodForProperty(bi, "address"), hasReadMethod);
859 			assertEquals(hasWriteMethodForProperty(bi, "address"), hasWriteMethod);
860 			assertEquals(hasIndexedReadMethodForProperty(bi, "address"), hasIndexedReadMethod);
861 			assertEquals(hasIndexedWriteMethodForProperty(bi, "address"), hasIndexedWriteMethod);
862 		}
863 	}
864 
865 	@Test
866 	public void shouldSupportStaticWriteMethod() throws IntrospectionException {
867 		{
868 			BeanInfo bi = Introspector.getBeanInfo(WithStaticWriteMethod.class);
869 			assertThat(hasReadMethodForProperty(bi, "prop1"), is(false));
870 			assertThat(hasWriteMethodForProperty(bi, "prop1"), is(false));
871 			assertThat(hasIndexedReadMethodForProperty(bi, "prop1"), is(false));
872 			assertThat(hasIndexedWriteMethodForProperty(bi, "prop1"), is(false));
873 		}
874 		{
875 			BeanInfo bi = new ExtendedBeanInfo(Introspector.getBeanInfo(WithStaticWriteMethod.class));
876 			assertThat(hasReadMethodForProperty(bi, "prop1"), is(false));
877 			assertThat(hasWriteMethodForProperty(bi, "prop1"), is(true));
878 			assertThat(hasIndexedReadMethodForProperty(bi, "prop1"), is(false));
879 			assertThat(hasIndexedWriteMethodForProperty(bi, "prop1"), is(false));
880 		}
881 	}
882 
883 	@Test  // SPR-12434
884 	public void shouldDetectValidPropertiesAndIgnoreInvalidProperties() throws IntrospectionException {
885 		BeanInfo bi = new ExtendedBeanInfo(Introspector.getBeanInfo(java.awt.Window.class));
886 		assertThat(hasReadMethodForProperty(bi, "locationByPlatform"), is(true));
887 		assertThat(hasWriteMethodForProperty(bi, "locationByPlatform"), is(true));
888 		assertThat(hasIndexedReadMethodForProperty(bi, "locationByPlatform"), is(false));
889 		assertThat(hasIndexedWriteMethodForProperty(bi, "locationByPlatform"), is(false));
890 	}
891 
892 
893 	private boolean hasWriteMethodForProperty(BeanInfo beanInfo, String propertyName) {
894 		for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
895 			if (pd.getName().equals(propertyName)) {
896 				return pd.getWriteMethod() != null;
897 			}
898 		}
899 		return false;
900 	}
901 
902 	private boolean hasReadMethodForProperty(BeanInfo beanInfo, String propertyName) {
903 		for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
904 			if (pd.getName().equals(propertyName)) {
905 				return pd.getReadMethod() != null;
906 			}
907 		}
908 		return false;
909 	}
910 
911 	private boolean hasIndexedWriteMethodForProperty(BeanInfo beanInfo, String propertyName) {
912 		for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
913 			if (pd.getName().equals(propertyName)) {
914 				if (!(pd instanceof IndexedPropertyDescriptor)) {
915 					return false;
916 				}
917 				return ((IndexedPropertyDescriptor)pd).getIndexedWriteMethod() != null;
918 			}
919 		}
920 		return false;
921 	}
922 
923 	private boolean hasIndexedReadMethodForProperty(BeanInfo beanInfo, String propertyName) {
924 		for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
925 			if (pd.getName().equals(propertyName)) {
926 				if (!(pd instanceof IndexedPropertyDescriptor)) {
927 					return false;
928 				}
929 				return ((IndexedPropertyDescriptor)pd).getIndexedReadMethod() != null;
930 			}
931 		}
932 		return false;
933 	}
934 
935 	private boolean trueUntilJdk17() {
936 		return JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_17;
937 	}
938 
939 
940 	interface Spr9453<T> {
941 
942 		T getProp();
943 	}
944 
945 
946 	interface Book {
947 	}
948 
949 
950 	interface TextBook extends Book {
951 	}
952 
953 
954 	interface LawBook extends TextBook {
955 	}
956 
957 
958 	interface BookOperations {
959 
960 		Book getBook();
961 
962 		void setBook(Book book);
963 	}
964 
965 
966 	interface TextBookOperations extends BookOperations {
967 
968 		@Override
969 		TextBook getBook();
970 	}
971 
972 
973 	abstract class Library {
974 
975 		public Book getBook() {
976 			return null;
977 		}
978 
979 		public void setBook(Book book) {
980 		}
981 	}
982 
983 
984 	class LawLibrary extends Library implements TextBookOperations {
985 
986 		@Override
987 		public LawBook getBook() {
988 			return null;
989 		}
990 	}
991 
992 
993 	static class WithStaticWriteMethod {
994 
995 		public static void setProp1(String prop1) {
996 		}
997 	}
998 
999 }