View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.beanutils2;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
23  import static org.junit.jupiter.api.Assertions.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertNull;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  
28  import java.beans.IntrospectionException;
29  import java.beans.PropertyDescriptor;
30  import java.lang.reflect.InvocationTargetException;
31  import java.lang.reflect.Method;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.HashMap;
35  import java.util.Iterator;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  import java.util.TreeSet;
40  
41  import org.apache.commons.beanutils2.priv.PrivateBeanFactory;
42  import org.apache.commons.beanutils2.priv.PrivateDirect;
43  import org.apache.commons.beanutils2.priv.PublicSubBean;
44  import org.apache.commons.lang3.StringUtils;
45  import org.junit.jupiter.api.AfterEach;
46  import org.junit.jupiter.api.BeforeEach;
47  import org.junit.jupiter.api.Test;
48  
49  /**
50   * <p>
51   * Test Case for the PropertyUtils class. The majority of these tests use instances of the TestBean class, so be sure to update the tests if you change the
52   * characteristics of that class.
53   * </p>
54   *
55   * <p>
56   * So far, this test case has tests for the following methods of the {@code PropertyUtils} class:
57   * </p>
58   * <ul>
59   * <li>getIndexedProperty(Object,String)</li>
60   * <li>getIndexedProperty(Object,String,int)</li>
61   * <li>getMappedProperty(Object,String)</li>
62   * <li>getMappedProperty(Object,String,String</li>
63   * <li>getNestedProperty(Object,String)</li>
64   * <li>getPropertyDescriptor(Object,String)</li>
65   * <li>getPropertyDescriptors(Object)</li>
66   * <li>getPropertyType(Object,String)</li>
67   * <li>getSimpleProperty(Object,String)</li>
68   * <li>setIndexedProperty(Object,String,Object)</li>
69   * <li>setIndexedProperty(Object,String,String,Object)</li>
70   * <li>setMappedProperty(Object,String,Object)</li>
71   * <li>setMappedProperty(Object,String,String,Object)</li>
72   * <li>setNestedProperty(Object,String,Object)</li>
73   * <li>setSimpleProperty(Object,String,Object)</li>
74   * </ul>
75   */
76  public class PropertyUtilsTest {
77  
78      /**
79       * The fully qualified class name of our private directly implemented interface.
80       */
81      private static final String PRIVATE_DIRECT_CLASS = "org.apache.commons.beanutils2.priv.PrivateDirect";
82  
83      /**
84       * The fully qualified class name of our private indirectly implemented interface.
85       */
86      private static final String PRIVATE_INDIRECT_CLASS = "org.apache.commons.beanutils2.priv.PrivateIndirect";
87  
88      /**
89       * The fully qualified class name of our test bean class.
90       */
91      private static final String TEST_BEAN_CLASS = "org.apache.commons.beanutils2.TestBean";
92  
93      /**
94       * The set of property names we expect to have returned when calling {@code getPropertyDescriptors()}. You should update this list when new properties are
95       * added to TestBean.
96       */
97      protected static final String[] properties = { "booleanProperty", "booleanSecond", "doubleProperty", "dupProperty", "floatProperty", "intArray",
98              "intIndexed", "intProperty", "listIndexed", "longProperty", "nested", "nullProperty", "readOnlyProperty", "shortProperty", "stringArray",
99              "stringIndexed", "stringProperty", "writeOnlyProperty", };
100 
101     /**
102      * Finds the descriptor of the name property.
103      *
104      * @param desc the array with descriptors
105      * @return the found descriptor or null
106      */
107     private static PropertyDescriptor findNameDescriptor(final PropertyDescriptor[] desc) {
108         for (final PropertyDescriptor element : desc) {
109             if (element.getName().equals("name")) {
110                 return element;
111             }
112         }
113         return null;
114     }
115 
116     /**
117      * The basic test bean for each test.
118      */
119     protected TestBean bean;
120 
121     /**
122      * The "package private subclass" test bean for each test.
123      */
124     protected TestBeanPackageSubclass beanPackageSubclass;
125 
126     /**
127      * The test bean for private access tests.
128      */
129     protected PrivateDirect beanPrivate;
130 
131     /**
132      * The test bean for private access tests of subclasses.
133      */
134     protected PrivateDirect beanPrivateSubclass;
135 
136     /**
137      * The "public subclass" test bean for each test.
138      */
139     protected TestBeanPublicSubclass beanPublicSubclass;
140 
141     /**
142      * The set of properties that should be described.
143      */
144     protected String[] describes = { "booleanProperty", "booleanSecond", "doubleProperty", "floatProperty", "intArray",
145             // "intIndexed",
146             "intProperty", "listIndexed", "longProperty",
147             // "mappedObjects",
148             // "mappedProperty",
149             // "mappedIntProperty",
150             "nested", "nullProperty",
151             // "readOnlyProperty",
152             "shortProperty", "stringArray",
153             // "stringIndexed",
154             "stringProperty" };
155 
156     /**
157      * Returns a single string containing all the keys in the map, sorted in alphabetical order and separated by ", ".
158      * <p>
159      * If there are no keys, an empty string is returned.
160      */
161     private String keysToString(final Map<String, ?> map) {
162         return StringUtils.join(new TreeSet<>(map.keySet()), ", ");
163     }
164 
165     /**
166      * Sets up instance variables required by this test case.
167      */
168     @BeforeEach
169     public void setUp() {
170 
171         bean = new TestBean();
172         beanPackageSubclass = new TestBeanPackageSubclass();
173         beanPrivate = PrivateBeanFactory.create();
174         beanPrivateSubclass = PrivateBeanFactory.createSubclass();
175         beanPublicSubclass = new TestBeanPublicSubclass();
176 
177         final DynaProperty[] properties = { new DynaProperty("stringProperty", String.class), new DynaProperty("nestedBean", TestBean.class),
178                 new DynaProperty("nullDynaBean", DynaBean.class) };
179         final BasicDynaClass dynaClass = new BasicDynaClass("nestedDynaBean", BasicDynaBean.class, properties);
180         final BasicDynaBean nestedDynaBean = new BasicDynaBean(dynaClass);
181         nestedDynaBean.set("nestedBean", bean);
182         bean.setNestedDynaBean(nestedDynaBean);
183         PropertyUtils.clearDescriptors();
184     }
185 
186     /**
187      * Tear down instance variables required by this test case.
188      */
189     @AfterEach
190     public void tearDown() {
191 
192         bean = null;
193         beanPackageSubclass = null;
194         beanPrivate = null;
195         beanPrivateSubclass = null;
196         beanPublicSubclass = null;
197 
198         PropertyUtils.resetBeanIntrospectors();
199     }
200 
201     /**
202      * Tries to add a null BeanIntrospector.
203      */
204     @Test
205     public void testAddBeanIntrospectorNull() {
206         assertThrows(NullPointerException.class, () -> PropertyUtils.addBeanIntrospector(null));
207     }
208 
209     /**
210      * Test copyProperties() when the origin is a {@code Map}.
211      */
212     @Test
213     public void testCopyPropertiesMap() throws Exception {
214 
215         final Map<String, Object> map = new HashMap<>();
216         map.put("booleanProperty", Boolean.FALSE);
217         map.put("doubleProperty", Double.valueOf(333.0));
218         map.put("dupProperty", new String[] { "New 0", "New 1", "New 2" });
219         map.put("floatProperty", Float.valueOf((float) 222.0));
220         map.put("intArray", new int[] { 0, 100, 200 });
221         map.put("intProperty", Integer.valueOf(111));
222         map.put("longProperty", Long.valueOf(444));
223         map.put("shortProperty", Short.valueOf((short) 555));
224         map.put("stringProperty", "New String Property");
225 
226         PropertyUtils.copyProperties(bean, map);
227 
228         // Scalar properties
229         assertEquals(false, bean.getBooleanProperty(), "booleanProperty");
230         assertEquals(333.0, bean.getDoubleProperty(), 0.005, "doubleProperty");
231         assertEquals((float) 222.0, bean.getFloatProperty(), (float) 0.005, "floatProperty");
232         assertEquals(111, bean.getIntProperty(), "intProperty");
233         assertEquals(444, bean.getLongProperty(), "longProperty");
234         assertEquals((short) 555, bean.getShortProperty(), "shortProperty");
235         assertEquals("New String Property", bean.getStringProperty(), "stringProperty");
236 
237         // Indexed Properties
238         final String[] dupProperty = bean.getDupProperty();
239         assertNotNull(dupProperty, "dupProperty present");
240         assertEquals(3, dupProperty.length, "dupProperty length");
241         assertEquals("New 0", dupProperty[0], "dupProperty[0]");
242         assertEquals("New 1", dupProperty[1], "dupProperty[1]");
243         assertEquals("New 2", dupProperty[2], "dupProperty[2]");
244         final int[] intArray = bean.getIntArray();
245         assertNotNull(intArray, "intArray present");
246         assertEquals(3, intArray.length, "intArray length");
247         assertEquals(0, intArray[0], "intArray[0]");
248         assertEquals(100, intArray[1], "intArray[1]");
249         assertEquals(200, intArray[2], "intArray[2]");
250 
251     }
252 
253     /**
254      * Tests whether the default introspection mechanism can be replaced by a custom BeanIntrospector.
255      */
256     @Test
257     public void testCustomIntrospection() {
258         final PropertyDescriptor[] desc1 = PropertyUtils.getPropertyDescriptors(AlphaBean.class);
259         PropertyDescriptor nameDescriptor = findNameDescriptor(desc1);
260         assertNotNull(nameDescriptor.getWriteMethod(), "No write method");
261 
262         final BeanIntrospector bi = icontext -> {
263             final Set<String> names = icontext.propertyNames();
264             final PropertyDescriptor[] newDescs = new PropertyDescriptor[names.size()];
265             int idx = 0;
266             for (final Iterator<String> it = names.iterator(); it.hasNext(); idx++) {
267                 final String propName = it.next();
268                 final PropertyDescriptor pd = icontext.getPropertyDescriptor(propName);
269                 newDescs[idx] = new PropertyDescriptor(pd.getName(), pd.getReadMethod(), null);
270             }
271             icontext.addPropertyDescriptors(newDescs);
272         };
273         PropertyUtils.clearDescriptors();
274         PropertyUtils.addBeanIntrospector(bi);
275         final PropertyDescriptor[] desc2 = PropertyUtils.getPropertyDescriptors(AlphaBean.class);
276         assertEquals(desc1.length, desc2.length, "Different number of properties");
277         nameDescriptor = findNameDescriptor(desc2);
278         assertNull(nameDescriptor.getWriteMethod(), "Got a write method");
279         PropertyUtils.removeBeanIntrospector(bi);
280     }
281 
282     /**
283      * Tests whether exceptions during custom introspection are handled.
284      */
285     @Test
286     public void testCustomIntrospectionEx() {
287         final BeanIntrospector bi = icontext -> {
288             throw new IntrospectionException("TestException");
289         };
290         PropertyUtils.clearDescriptors();
291         PropertyUtils.addBeanIntrospector(bi);
292         final PropertyDescriptor[] desc = PropertyUtils.getPropertyDescriptors(AlphaBean.class);
293         assertNotNull(findNameDescriptor(desc), "Introspection did not work");
294         PropertyUtils.removeBeanIntrospector(bi);
295     }
296 
297     /**
298      * Test the describe() method.
299      */
300     @Test
301     public void testDescribe() throws Exception {
302 
303         final Map<String, Object> map = PropertyUtils.describe(bean);
304 
305         // Verify existence of all the properties that should be present
306         for (final String describe : describes) {
307             assertTrue(map.containsKey(describe), "Property '" + describe + "' is present");
308         }
309         assertFalse(map.containsKey("writeOnlyProperty"), "Property 'writeOnlyProperty' is not present");
310 
311         // Verify the values of scalar properties
312         assertEquals(Boolean.TRUE, map.get("booleanProperty"), "Value of 'booleanProperty'");
313         assertEquals(Double.valueOf(321.0), map.get("doubleProperty"), "Value of 'doubleProperty'");
314         assertEquals(Float.valueOf((float) 123.0), map.get("floatProperty"), "Value of 'floatProperty'");
315         assertEquals(Integer.valueOf(123), map.get("intProperty"), "Value of 'intProperty'");
316         assertEquals(Long.valueOf(321), map.get("longProperty"), "Value of 'longProperty'");
317         assertEquals(Short.valueOf((short) 987), map.get("shortProperty"), "Value of 'shortProperty'");
318         assertEquals("This is a string", (String) map.get("stringProperty"), "Value of 'stringProperty'");
319 
320     }
321 
322     /**
323      * Test {@link PropertyUtilsBean}'s invoke method throwing an IllegalArgumentException and check that the "cause" has been properly initialized for JDK 1.4+
324      * See BEANUTILS-266 for changes and reason for test
325      */
326     @Test
327     public void testExceptionFromInvoke() throws Exception {
328         try {
329             PropertyUtils.setSimpleProperty(bean, "intProperty", "XXX");
330         } catch (final IllegalArgumentException t) {
331             final Throwable cause = (Throwable) PropertyUtils.getProperty(t, "cause");
332             assertNotNull(cause, "Cause not found");
333             assertInstanceOf(IllegalArgumentException.class, cause, "Expected cause to be IllegalArgumentException, but was: " + cause.getClass());
334             // JDK 1.6 doesn't have "argument type mismatch" message
335             // assertEquals("Check error message", "argument type mismatch", cause.getMessage());
336         }
337     }
338 
339     /**
340      * Corner cases on getPropertyDescriptor invalid arguments.
341      */
342     @Test
343     public void testGetDescriptorArguments() {
344         assertThrows(NullPointerException.class, () -> PropertyUtils.getPropertyDescriptor(null, "stringProperty"));
345         assertThrows(NullPointerException.class, () -> PropertyUtils.getPropertyDescriptor(bean, null));
346     }
347 
348     /**
349      * Base for testGetDescriptorXxxxx() series of tests.
350      *
351      * @param name  Name of the property to be retrieved
352      * @param read  Expected name of the read method (or null)
353      * @param write Expected name of the write method (or null)
354      * @throws NoSuchMethodException
355      * @throws InvocationTargetException
356      * @throws IllegalAccessException
357      */
358     private void testGetDescriptorBase(final String name, final String read, final String write)
359             throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
360         final PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(bean, name);
361         if (read == null && write == null) {
362             assertNull(pd, "Got descriptor");
363             return;
364         }
365         assertNotNull(pd, "Got descriptor");
366         final Method rm = pd.getReadMethod();
367         if (read != null) {
368             assertNotNull(rm, "Got read method");
369             assertEquals(rm.getName(), read, "Got correct read method");
370         } else {
371             assertNull(rm, "Got read method");
372         }
373         final Method wm = pd.getWriteMethod();
374         if (write != null) {
375             assertNotNull(wm, "Got write method");
376             assertEquals(wm.getName(), write, "Got correct write method");
377         } else {
378             assertNull(wm, "Got write method");
379         }
380     }
381 
382     /**
383      * Positive getPropertyDescriptor on property {@code booleanProperty}.
384      */
385     @Test
386     public void testGetDescriptorBoolean() throws Exception {
387         testGetDescriptorBase("booleanProperty", "getBooleanProperty", "setBooleanProperty");
388     }
389 
390     /**
391      * Positive getPropertyDescriptor on property {@code doubleProperty}.
392      */
393     @Test
394     public void testGetDescriptorDouble() throws Exception {
395         testGetDescriptorBase("doubleProperty", "getDoubleProperty", "setDoubleProperty");
396     }
397 
398     /**
399      * Positive getPropertyDescriptor on property {@code floatProperty}.
400      */
401     @Test
402     public void testGetDescriptorFloat() throws Exception {
403         testGetDescriptorBase("floatProperty", "getFloatProperty", "setFloatProperty");
404     }
405 
406     /**
407      * Positive getPropertyDescriptor on property {@code intProperty}.
408      */
409     @Test
410     public void testGetDescriptorInt() throws Exception {
411         testGetDescriptorBase("intProperty", "getIntProperty", "setIntProperty");
412     }
413 
414     /**
415      * <p>
416      * Negative tests on an invalid property with two different boolean getters (which is fine, according to the JavaBeans spec) but a String setter instead of
417      * a boolean setter.
418      * </p>
419      *
420      * <p>
421      * Although one could logically argue that this combination of method signatures should not identify a property at all, there is a sentence in Section 8.3.1
422      * making it clear that the behavior tested for here is correct: "If we find only one of these methods, then we regard it as defining either a read-only or
423      * write-only property called <em>&lt;property-name&gt;</em>.
424      * </p>
425      */
426     @Test
427     public void testGetDescriptorInvalidBoolean() throws Exception {
428 
429         final PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(bean, "invalidBoolean");
430         assertNotNull(pd, "invalidBoolean is a property");
431         assertNotNull(pd.getReadMethod(), "invalidBoolean has a getter method");
432         assertNull(pd.getWriteMethod(), "invalidBoolean has no write method");
433         assertTrue(Arrays.asList("isInvalidBoolean", "getInvalidBoolean").contains(pd.getReadMethod().getName()),
434                 "invalidBoolean getter method is isInvalidBoolean or getInvalidBoolean");
435     }
436 
437     /**
438      * Positive getPropertyDescriptor on property {@code longProperty}.
439      */
440     @Test
441     public void testGetDescriptorLong() throws Exception {
442         testGetDescriptorBase("longProperty", "getLongProperty", "setLongProperty");
443     }
444 
445     /**
446      * Test getting mapped descriptor with periods in the key.
447      */
448     @Test
449     public void testGetDescriptorMappedPeriods() throws Exception {
450 
451         bean.getMappedIntProperty("xyz"); // initializes mappedIntProperty
452 
453         PropertyDescriptor desc;
454         final Integer testIntegerValue = Integer.valueOf(1234);
455 
456         bean.setMappedIntProperty("key.with.a.dot", testIntegerValue.intValue());
457         assertEquals(testIntegerValue, Integer.valueOf(bean.getMappedIntProperty("key.with.a.dot")), "Can retrieve directly");
458         desc = PropertyUtils.getPropertyDescriptor(bean, "mappedIntProperty(key.with.a.dot)");
459         assertEquals(Integer.TYPE, ((MappedPropertyDescriptor) desc).getMappedPropertyType(), "Check descriptor type (A)");
460 
461         bean.setMappedObjects("nested.property", new TestBean(testIntegerValue.intValue()));
462         assertEquals(testIntegerValue, Integer.valueOf(((TestBean) bean.getMappedObjects("nested.property")).getIntProperty()), "Can retrieve directly");
463         desc = PropertyUtils.getPropertyDescriptor(bean, "mappedObjects(nested.property).intProperty");
464         assertEquals(Integer.TYPE, desc.getPropertyType(), "Check descriptor type (B)");
465     }
466 
467     /**
468      * Positive getPropertyDescriptor on property {@code readOnlyProperty}.
469      */
470     @Test
471     public void testGetDescriptorReadOnly() throws Exception {
472         testGetDescriptorBase("readOnlyProperty", "getReadOnlyProperty", null);
473     }
474 
475     /**
476      * Positive test for getPropertyDescriptors(). Each property name listed in {@code properties} should be returned exactly once.
477      */
478     @Test
479     public void testGetDescriptors() {
480 
481         final PropertyDescriptor[] pd = PropertyUtils.getPropertyDescriptors(bean);
482         assertNotNull(pd, "Got descriptors");
483         final int[] count = new int[properties.length];
484         for (final PropertyDescriptor element : pd) {
485             final String name = element.getName();
486             for (int j = 0; j < properties.length; j++) {
487                 if (name.equals(properties[j])) {
488                     count[j]++;
489                 }
490             }
491         }
492         for (int j = 0; j < properties.length; j++) {
493             assertFalse(count[j] < 0, "Missing property " + properties[j]);
494             assertFalse(count[j] > 1, "Missing property " + properties[j]);
495         }
496 
497     }
498 
499     /**
500      * Corner cases on getPropertyDescriptors invalid arguments.
501      */
502     @Test
503     public void testGetDescriptorsArguments() {
504         assertThrows(NullPointerException.class, () -> PropertyUtils.getPropertyDescriptors(null));
505     }
506 
507     /**
508      * Positive getPropertyDescriptor on property {@code booleanSecond} that uses an "is" method as the getter.
509      */
510     @Test
511     public void testGetDescriptorSecond() throws Exception {
512         testGetDescriptorBase("booleanSecond", "isBooleanSecond", "setBooleanSecond");
513     }
514 
515     /**
516      * Positive getPropertyDescriptor on property {@code shortProperty}.
517      */
518     @Test
519     public void testGetDescriptorShort() throws Exception {
520         testGetDescriptorBase("shortProperty", "getShortProperty", "setShortProperty");
521     }
522 
523     /**
524      * Positive getPropertyDescriptor on property {@code stringProperty}.
525      */
526     @Test
527     public void testGetDescriptorString() throws Exception {
528         testGetDescriptorBase("stringProperty", "getStringProperty", "setStringProperty");
529     }
530 
531     /**
532      * Negative getPropertyDescriptor on property {@code unknown}.
533      */
534     @Test
535     public void testGetDescriptorUnknown() throws Exception {
536         testGetDescriptorBase("unknown", null, null);
537     }
538 
539     /**
540      * Positive getPropertyDescriptor on property {@code writeOnlyProperty}.
541      */
542     @Test
543     public void testGetDescriptorWriteOnly() throws Exception {
544         testGetDescriptorBase("writeOnlyProperty", null, "setWriteOnlyProperty");
545     }
546 
547     /**
548      * Corner cases on getIndexedProperty invalid arguments.
549      */
550     @Test
551     public void testGetIndexedArguments() {
552         // Use explicit index argument
553         assertThrows(NullPointerException.class, () -> PropertyUtils.getIndexedProperty(null, "intArray", 0));
554         assertThrows(NullPointerException.class, () -> PropertyUtils.getIndexedProperty(bean, null, 0));
555         // Use index expression
556         assertThrows(NullPointerException.class, () -> PropertyUtils.getIndexedProperty(null, "intArray[0]"));
557         assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getIndexedProperty(bean, "[0]"));
558         assertThrows(IllegalArgumentException.class, () -> PropertyUtils.getIndexedProperty(bean, "intArray"));
559         // Use explicit index argument
560         assertThrows(NullPointerException.class, () -> PropertyUtils.getIndexedProperty(null, "intIndexed", 0));
561         assertThrows(NullPointerException.class, () -> PropertyUtils.getIndexedProperty(bean, null, 0));
562         // Use index expression
563         assertThrows(NullPointerException.class, () -> PropertyUtils.getIndexedProperty(null, "intIndexed[0]"));
564         assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getIndexedProperty(bean, "[0]"));
565         assertThrows(IllegalArgumentException.class, () -> PropertyUtils.getIndexedProperty(bean, "intIndexed"));
566     }
567 
568     /**
569      * Test getting an indexed value out of a multi-dimensional array
570      */
571     @Test
572     public void testGetIndexedArray() throws Exception {
573         final String[] firstArray = { "FIRST-1", "FIRST-2", "FIRST-3" };
574         final String[] secondArray = { "SECOND-1", "SECOND-2", "SECOND-3", "SECOND-4" };
575         final String[][] mainArray = { firstArray, secondArray };
576         final TestBean bean = new TestBean(mainArray);
577         assertEquals(firstArray[0], PropertyUtils.getProperty(bean, "string2dArray[0][0]"), "firstArray[0]");
578         assertEquals(firstArray[1], PropertyUtils.getProperty(bean, "string2dArray[0][1]"), "firstArray[1]");
579         assertEquals(firstArray[2], PropertyUtils.getProperty(bean, "string2dArray[0][2]"), "firstArray[2]");
580         assertEquals(secondArray[0], PropertyUtils.getProperty(bean, "string2dArray[1][0]"), "secondArray[0]");
581         assertEquals(secondArray[1], PropertyUtils.getProperty(bean, "string2dArray[1][1]"), "secondArray[1]");
582         assertEquals(secondArray[2], PropertyUtils.getProperty(bean, "string2dArray[1][2]"), "secondArray[2]");
583         assertEquals(secondArray[3], PropertyUtils.getProperty(bean, "string2dArray[1][3]"), "secondArray[3]");
584     }
585 
586     /**
587      * Test getting an indexed value out of List of Lists
588      */
589     @Test
590     public void testGetIndexedList() throws Exception {
591         final String[] firstArray = { "FIRST-1", "FIRST-2", "FIRST-3" };
592         final String[] secondArray = { "SECOND-1", "SECOND-2", "SECOND-3", "SECOND-4" };
593         final List<Object> mainList = new ArrayList<>();
594         mainList.add(Arrays.asList(firstArray));
595         mainList.add(Arrays.asList(secondArray));
596         final TestBean bean = new TestBean(mainList);
597         assertEquals(firstArray[0], PropertyUtils.getProperty(bean, "listIndexed[0][0]"), "firstArray[0]");
598         assertEquals(firstArray[1], PropertyUtils.getProperty(bean, "listIndexed[0][1]"), "firstArray[1]");
599         assertEquals(firstArray[2], PropertyUtils.getProperty(bean, "listIndexed[0][2]"), "firstArray[2]");
600         assertEquals(secondArray[0], PropertyUtils.getProperty(bean, "listIndexed[1][0]"), "secondArray[0]");
601         assertEquals(secondArray[1], PropertyUtils.getProperty(bean, "listIndexed[1][1]"), "secondArray[1]");
602         assertEquals(secondArray[2], PropertyUtils.getProperty(bean, "listIndexed[1][2]"), "secondArray[2]");
603         assertEquals(secondArray[3], PropertyUtils.getProperty(bean, "listIndexed[1][3]"), "secondArray[3]");
604     }
605 
606     /**
607      * Test getting a value out of a mapped Map
608      */
609     @Test
610     public void testGetIndexedMap() throws Exception {
611         final Map<String, Object> firstMap = new HashMap<>();
612         firstMap.put("FIRST-KEY-1", "FIRST-VALUE-1");
613         firstMap.put("FIRST-KEY-2", "FIRST-VALUE-2");
614         final Map<String, Object> secondMap = new HashMap<>();
615         secondMap.put("SECOND-KEY-1", "SECOND-VALUE-1");
616         secondMap.put("SECOND-KEY-2", "SECOND-VALUE-2");
617 
618         final List<Object> mainList = new ArrayList<>();
619         mainList.add(firstMap);
620         mainList.add(secondMap);
621         final TestBean bean = new TestBean(mainList);
622         assertEquals("FIRST-VALUE-1", PropertyUtils.getProperty(bean, "listIndexed[0](FIRST-KEY-1)"), "listIndexed[0](FIRST-KEY-1)");
623         assertEquals("FIRST-VALUE-2", PropertyUtils.getProperty(bean, "listIndexed[0](FIRST-KEY-2)"), "listIndexed[0](FIRST-KEY-2)");
624         assertEquals("SECOND-VALUE-1", PropertyUtils.getProperty(bean, "listIndexed[1](SECOND-KEY-1)"), "listIndexed[1](SECOND-KEY-1)");
625         assertEquals("SECOND-VALUE-2", PropertyUtils.getProperty(bean, "listIndexed[1](SECOND-KEY-2)"), "listIndexed[1](SECOND-KEY-2)");
626     }
627 
628     /**
629      * Positive and negative tests on getIndexedProperty valid arguments.
630      */
631     @Test
632     public void testGetIndexedValues() throws Exception {
633         Object value = null;
634         // Use explicit key argument
635         for (int i = 0; i < 5; i++) {
636             value = PropertyUtils.getIndexedProperty(bean, "dupProperty", i);
637             assertNotNull(value, "dupProperty returned value " + i);
638             assertInstanceOf(String.class, value, "dupProperty returned String " + i);
639             assertEquals("Dup " + i, (String) value, "dupProperty returned correct " + i);
640 
641             value = PropertyUtils.getIndexedProperty(bean, "intArray", i);
642             assertNotNull(value, "intArray returned value " + i);
643             assertInstanceOf(Integer.class, value, "intArray returned Integer " + i);
644             assertEquals(i * 10, ((Integer) value).intValue(), "intArray returned correct " + i);
645 
646             value = PropertyUtils.getIndexedProperty(bean, "intIndexed", i);
647             assertNotNull(value, "intIndexed returned value " + i);
648             assertInstanceOf(Integer.class, value, "intIndexed returned Integer " + i);
649             assertEquals(i * 10, ((Integer) value).intValue(), "intIndexed returned correct " + i);
650 
651             value = PropertyUtils.getIndexedProperty(bean, "listIndexed", i);
652             assertNotNull(value, "listIndexed returned value " + i);
653             assertInstanceOf(String.class, value, "list returned String " + i);
654             assertEquals("String " + i, (String) value, "listIndexed returned correct " + i);
655 
656             value = PropertyUtils.getIndexedProperty(bean, "stringArray", i);
657             assertNotNull(value, "stringArray returned value " + i);
658             assertInstanceOf(String.class, value, "stringArray returned String " + i);
659             assertEquals("String " + i, (String) value, "stringArray returned correct " + i);
660 
661             value = PropertyUtils.getIndexedProperty(bean, "stringIndexed", i);
662             assertNotNull(value, "stringIndexed returned value " + i);
663             assertInstanceOf(String.class, value, "stringIndexed returned String " + i);
664             assertEquals("String " + i, (String) value, "stringIndexed returned correct " + i);
665 
666         }
667 
668         // Use key expression
669 
670         for (int i = 0; i < 5; i++) {
671 
672             value = PropertyUtils.getIndexedProperty(bean, "dupProperty[" + i + "]");
673             assertNotNull(value, "dupProperty returned value " + i);
674             assertInstanceOf(String.class, value, "dupProperty returned String " + i);
675             assertEquals("Dup " + i, (String) value, "dupProperty returned correct " + i);
676 
677             value = PropertyUtils.getIndexedProperty(bean, "intArray[" + i + "]");
678             assertNotNull(value, "intArray returned value " + i);
679             assertInstanceOf(Integer.class, value, "intArray returned Integer " + i);
680             assertEquals(i * 10, ((Integer) value).intValue(), "intArray returned correct " + i);
681 
682             value = PropertyUtils.getIndexedProperty(bean, "intIndexed[" + i + "]");
683             assertNotNull(value, "intIndexed returned value " + i);
684             assertInstanceOf(Integer.class, value, "intIndexed returned Integer " + i);
685             assertEquals(i * 10, ((Integer) value).intValue(), "intIndexed returned correct " + i);
686 
687             value = PropertyUtils.getIndexedProperty(bean, "listIndexed[" + i + "]");
688             assertNotNull(value, "listIndexed returned value " + i);
689             assertInstanceOf(String.class, value, "listIndexed returned String " + i);
690             assertEquals("String " + i, (String) value, "listIndexed returned correct " + i);
691 
692             value = PropertyUtils.getIndexedProperty(bean, "stringArray[" + i + "]");
693             assertNotNull(value, "stringArray returned value " + i);
694             assertInstanceOf(String.class, value, "stringArray returned String " + i);
695             assertEquals("String " + i, (String) value, "stringArray returned correct " + i);
696 
697             value = PropertyUtils.getIndexedProperty(bean, "stringIndexed[" + i + "]");
698             assertNotNull(value, "stringIndexed returned value " + i);
699             assertInstanceOf(String.class, value, "stringIndexed returned String " + i);
700             assertEquals("String " + i, (String) value, "stringIndexed returned correct " + i);
701 
702         }
703 
704         // Index out of bounds tests
705 
706         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "dupProperty", -1));
707         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "dupProperty", 5));
708         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "intArray", -1));
709         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "intArray", 5));
710         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "intIndexed", -1));
711         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "intIndexed", 5));
712         assertThrows(IndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "listIndexed", -1));
713         assertThrows(IndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "listIndexed", 5));
714         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "stringArray", -1));
715         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "stringArray", 5));
716         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "stringIndexed", -1));
717         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "stringIndexed", 5));
718     }
719 
720     /**
721      * Corner cases on getMappedProperty invalid arguments.
722      */
723     @Test
724     public void testGetMappedArguments() {
725         // Use explicit key argument
726         assertThrows(NullPointerException.class, () -> PropertyUtils.getMappedProperty(null, "mappedProperty", "First Key"));
727         assertThrows(NullPointerException.class, () -> PropertyUtils.getMappedProperty(bean, null, "First Key"));
728         assertThrows(NullPointerException.class, () -> PropertyUtils.getMappedProperty(bean, "mappedProperty", null));
729         // Use key expression
730         assertThrows(NullPointerException.class, () -> PropertyUtils.getMappedProperty(null, "mappedProperty(First Key)"));
731         assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getMappedProperty(bean, "(Second Key)"));
732         assertThrows(IllegalArgumentException.class, () -> PropertyUtils.getMappedProperty(bean, "mappedProperty"));
733     }
734 
735     /**
736      * Test getting an indexed value out of a mapped array
737      */
738     @Test
739     public void testGetMappedArray() throws Exception {
740         final TestBean bean = new TestBean();
741         final String[] array = { "abc", "def", "ghi" };
742         bean.getMapProperty().put("mappedArray", array);
743         assertEquals("abc", PropertyUtils.getProperty(bean, "mapProperty(mappedArray)[0]"));
744         assertEquals("def", PropertyUtils.getProperty(bean, "mapProperty(mappedArray)[1]"));
745         assertEquals("ghi", PropertyUtils.getProperty(bean, "mapProperty(mappedArray)[2]"));
746     }
747 
748     /**
749      * Test getting an indexed value out of a mapped List
750      */
751     @Test
752     public void testGetMappedList() throws Exception {
753         final TestBean bean = new TestBean();
754         final List<Object> list = new ArrayList<>();
755         list.add("klm");
756         list.add("nop");
757         list.add("qrs");
758         bean.getMapProperty().put("mappedList", list);
759         assertEquals("klm", PropertyUtils.getProperty(bean, "mapProperty(mappedList)[0]"));
760         assertEquals("nop", PropertyUtils.getProperty(bean, "mapProperty(mappedList)[1]"));
761         assertEquals("qrs", PropertyUtils.getProperty(bean, "mapProperty(mappedList)[2]"));
762     }
763 
764     /**
765      * Test getting a value out of a mapped Map
766      */
767     @Test
768     public void testGetMappedMap() throws Exception {
769         final TestBean bean = new TestBean();
770         final Map<String, Object> map = new HashMap<>();
771         map.put("sub-key-1", "sub-value-1");
772         map.put("sub-key-2", "sub-value-2");
773         map.put("sub-key-3", "sub-value-3");
774         bean.getMapProperty().put("mappedMap", map);
775         assertEquals("sub-value-1", PropertyUtils.getProperty(bean, "mapProperty(mappedMap)(sub-key-1)"));
776         assertEquals("sub-value-2", PropertyUtils.getProperty(bean, "mapProperty(mappedMap)(sub-key-2)"));
777         assertEquals("sub-value-3", PropertyUtils.getProperty(bean, "mapProperty(mappedMap)(sub-key-3)"));
778     }
779 
780     /**
781      * Test getting mapped values with periods in the key.
782      */
783     @Test
784     public void testGetMappedPeriods() throws Exception {
785 
786         bean.setMappedProperty("key.with.a.dot", "Special Value");
787         assertEquals("Special Value", bean.getMappedProperty("key.with.a.dot"), "Can retrieve directly");
788         assertEquals("Special Value", PropertyUtils.getMappedProperty(bean, "mappedProperty", "key.with.a.dot"), "Can retrieve via getMappedProperty");
789         assertEquals("Special Value", PropertyUtils.getNestedProperty(bean, "mappedProperty(key.with.a.dot)"), "Can retrieve via getNestedProperty");
790 
791         bean.setMappedObjects("nested.property", new TestBean());
792         assertNotNull(bean.getMappedObjects("nested.property"), "Can retrieve directly");
793         assertEquals("This is a string", PropertyUtils.getNestedProperty(bean, "mappedObjects(nested.property).stringProperty"), "Can retrieve nested");
794 
795         assertEquals("Mapped Value", PropertyUtils.getNestedProperty(bean, "mappedNested.value(Mapped Key)"), "Can't retrieved nested with mapped property");
796     }
797 
798     /**
799      * Test getting mapped values with slashes in the key. This is different from periods because slashes are not syntactically significant.
800      */
801     @Test
802     public void testGetMappedSlashes() throws Exception {
803 
804         bean.setMappedProperty("key/with/a/slash", "Special Value");
805         assertEquals("Special Value", bean.getMappedProperty("key/with/a/slash"), "Can retrieve directly");
806         assertEquals("Special Value", PropertyUtils.getMappedProperty(bean, "mappedProperty", "key/with/a/slash"), "Can retrieve via getMappedProperty");
807         assertEquals("Special Value", PropertyUtils.getNestedProperty(bean, "mappedProperty(key/with/a/slash)"), "Can retrieve via getNestedProperty");
808 
809         bean.setMappedObjects("nested/property", new TestBean());
810         assertNotNull(bean.getMappedObjects("nested/property"), "Can retrieve directly");
811         assertEquals("This is a string", PropertyUtils.getNestedProperty(bean, "mappedObjects(nested/property).stringProperty"), "Can retrieve nested");
812     }
813 
814     /**
815      * Positive and negative tests on getMappedProperty valid arguments.
816      */
817     @Test
818     public void testGetMappedValues() throws Exception {
819         Object value = null;
820         // Use explicit key argument
821         value = PropertyUtils.getMappedProperty(bean, "mappedProperty", "First Key");
822         assertEquals("First Value", value, "Can find first value");
823 
824         value = PropertyUtils.getMappedProperty(bean, "mappedProperty", "Second Key");
825         assertEquals("Second Value", value, "Can find second value");
826 
827         value = PropertyUtils.getMappedProperty(bean, "mappedProperty", "Third Key");
828         assertNull(value, "Can not find third value");
829 
830         // Use key expression with parentheses
831         value = PropertyUtils.getMappedProperty(bean, "mappedProperty(First Key)");
832         assertEquals("First Value", value, "Can find first value");
833 
834         value = PropertyUtils.getMappedProperty(bean, "mappedProperty(Second Key)");
835         assertEquals("Second Value", value, "Can find second value");
836 
837         value = PropertyUtils.getMappedProperty(bean, "mappedProperty(Third Key)");
838         assertNull(value, "Can not find third value");
839 
840         // Use key expression with dotted syntax
841         value = PropertyUtils.getNestedProperty(bean, "mapProperty.First Key");
842         assertEquals("First Value", value, "Can find first value");
843 
844         value = PropertyUtils.getNestedProperty(bean, "mapProperty.Second Key");
845         assertEquals("Second Value", value, "Can find second value");
846 
847         value = PropertyUtils.getNestedProperty(bean, "mapProperty.Third Key");
848         assertNull(value, "Can not find third value");
849     }
850 
851     /**
852      * Corner cases on getNestedProperty invalid arguments.
853      */
854     @Test
855     public void testGetNestedArguments() {
856         assertThrows(NullPointerException.class, () -> PropertyUtils.getNestedProperty(null, "stringProperty"));
857         assertThrows(NullPointerException.class, () -> PropertyUtils.getNestedProperty(bean, null));
858     }
859 
860     /**
861      * Test getNestedProperty on a boolean property.
862      */
863     @Test
864     public void testGetNestedBoolean() throws Exception {
865         final Object value = PropertyUtils.getNestedProperty(bean, "nested.booleanProperty");
866         assertNotNull(value, "Got a value");
867         assertInstanceOf(Boolean.class, value, "Got correct type");
868         assertEquals(((Boolean) value).booleanValue(), bean.getNested().getBooleanProperty(), "Got correct value");
869     }
870 
871     /**
872      * Test getNestedProperty on a double property.
873      */
874     @Test
875     public void testGetNestedDouble() throws Exception {
876         final Object value = PropertyUtils.getNestedProperty(bean, "nested.doubleProperty");
877         assertNotNull(value, "Got a value");
878         assertInstanceOf(Double.class, value, "Got correct type");
879         assertEquals(((Double) value).doubleValue(), bean.getNested().getDoubleProperty(), 0.005, "Got correct value");
880     }
881 
882     /**
883      * Test getNestedProperty on a float property.
884      */
885     @Test
886     public void testGetNestedFloat() throws Exception {
887         final Object value = PropertyUtils.getNestedProperty(bean, "nested.floatProperty");
888         assertNotNull(value, "Got a value");
889         assertInstanceOf(Float.class, value, "Got correct type");
890         assertEquals(((Float) value).floatValue(), bean.getNested().getFloatProperty(), (float) 0.005, "Got correct value");
891     }
892 
893     /**
894      * Test getNestedProperty on an int property.
895      */
896     @Test
897     public void testGetNestedInt() throws Exception {
898         final Object value = PropertyUtils.getNestedProperty(bean, "nested.intProperty");
899         assertNotNull(value, "Got a value");
900         assertInstanceOf(Integer.class, value, "Got correct type");
901         assertEquals(((Integer) value).intValue(), bean.getNested().getIntProperty(), "Got correct value");
902     }
903 
904     /**
905      * Test getNestedProperty on a long property.
906      */
907     @Test
908     public void testGetNestedLong() throws Exception {
909         final Object value = PropertyUtils.getNestedProperty(bean, "nested.longProperty");
910         assertNotNull(value, "Got a value");
911         assertInstanceOf(Long.class, value, "Got correct type");
912         assertEquals(((Long) value).longValue(), bean.getNested().getLongProperty(), "Got correct value");
913     }
914 
915     /**
916      * Test getNestedProperty on a read-only String property.
917      */
918     @Test
919     public void testGetNestedReadOnly() throws Exception {
920         final Object value = PropertyUtils.getNestedProperty(bean, "nested.readOnlyProperty");
921         assertNotNull(value, "Got a value");
922         assertInstanceOf(String.class, value, "Got correct type");
923         assertEquals((String) value, bean.getReadOnlyProperty(), "Got correct value");
924     }
925 
926     /**
927      * Test getNestedProperty on a short property.
928      */
929     @Test
930     public void testGetNestedShort() throws Exception {
931         final Object value = PropertyUtils.getNestedProperty(bean, "nested.shortProperty");
932         assertNotNull(value, "Got a value");
933         assertInstanceOf(Short.class, value, "Got correct type");
934         assertEquals(((Short) value).shortValue(), bean.getNested().getShortProperty(), "Got correct value");
935     }
936 
937     /**
938      * Test getNestedProperty on a String property.
939      */
940     @Test
941     public void testGetNestedString() throws Exception {
942         final Object value = PropertyUtils.getNestedProperty(bean, "nested.stringProperty");
943         assertNotNull(value, "Got a value");
944         assertInstanceOf(String.class, value, "Got correct type");
945         assertEquals((String) value, bean.getNested().getStringProperty(), "Got correct value");
946     }
947 
948     /**
949      * Negative test getNestedProperty on an unknown property.
950      */
951     @Test
952     public void testGetNestedUnknown() throws Exception {
953         assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getNestedProperty(bean, "nested.unknown"));
954     }
955 
956     /**
957      * Test getNestedProperty on a write-only String property.
958      */
959     @Test
960     public void testGetNestedWriteOnly() throws Exception {
961         assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getNestedProperty(bean, "writeOnlyProperty"));
962     }
963 
964     /**
965      * Test getPropertyType() on all kinds of properties.
966      */
967     @Test
968     public void testGetPropertyType() throws Exception {
969 
970         Class<?> clazz = null;
971         final int[] intArray = {};
972         final String[] stringArray = {};
973 
974         // Scalar and Indexed Properties
975         clazz = PropertyUtils.getPropertyType(bean, "booleanProperty");
976         assertEquals(Boolean.TYPE, clazz, "booleanProperty type");
977         clazz = PropertyUtils.getPropertyType(bean, "booleanSecond");
978         assertEquals(Boolean.TYPE, clazz, "booleanSecond type");
979         clazz = PropertyUtils.getPropertyType(bean, "doubleProperty");
980         assertEquals(Double.TYPE, clazz, "doubleProperty type");
981         clazz = PropertyUtils.getPropertyType(bean, "dupProperty");
982         assertEquals(String.class, clazz, "dupProperty type");
983         clazz = PropertyUtils.getPropertyType(bean, "floatProperty");
984         assertEquals(Float.TYPE, clazz, "floatProperty type");
985         clazz = PropertyUtils.getPropertyType(bean, "intArray");
986         assertEquals(intArray.getClass(), clazz, "intArray type");
987         clazz = PropertyUtils.getPropertyType(bean, "intIndexed");
988         assertEquals(Integer.TYPE, clazz, "intIndexed type");
989         clazz = PropertyUtils.getPropertyType(bean, "intProperty");
990         assertEquals(Integer.TYPE, clazz, "intProperty type");
991         clazz = PropertyUtils.getPropertyType(bean, "listIndexed");
992         assertEquals(List.class, clazz, "listIndexed type");
993         clazz = PropertyUtils.getPropertyType(bean, "longProperty");
994         assertEquals(Long.TYPE, clazz, "longProperty type");
995         clazz = PropertyUtils.getPropertyType(bean, "mappedProperty");
996         assertEquals(String.class, clazz, "mappedProperty type");
997         clazz = PropertyUtils.getPropertyType(bean, "mappedIntProperty");
998         assertEquals(Integer.TYPE, clazz, "mappedIntProperty type");
999         clazz = PropertyUtils.getPropertyType(bean, "readOnlyProperty");
1000         assertEquals(String.class, clazz, "readOnlyProperty type");
1001         clazz = PropertyUtils.getPropertyType(bean, "shortProperty");
1002         assertEquals(Short.TYPE, clazz, "shortProperty type");
1003         clazz = PropertyUtils.getPropertyType(bean, "stringArray");
1004         assertEquals(stringArray.getClass(), clazz, "stringArray type");
1005         clazz = PropertyUtils.getPropertyType(bean, "stringIndexed");
1006         assertEquals(String.class, clazz, "stringIndexed type");
1007         clazz = PropertyUtils.getPropertyType(bean, "stringProperty");
1008         assertEquals(String.class, clazz, "stringProperty type");
1009         clazz = PropertyUtils.getPropertyType(bean, "writeOnlyProperty");
1010         assertEquals(String.class, clazz, "writeOnlyProperty type");
1011 
1012         // Nested Properties
1013         clazz = PropertyUtils.getPropertyType(bean, "nested.booleanProperty");
1014         assertEquals(Boolean.TYPE, clazz, "booleanProperty type");
1015         clazz = PropertyUtils.getPropertyType(bean, "nested.booleanSecond");
1016         assertEquals(Boolean.TYPE, clazz, "booleanSecond type");
1017         clazz = PropertyUtils.getPropertyType(bean, "nested.doubleProperty");
1018         assertEquals(Double.TYPE, clazz, "doubleProperty type");
1019         clazz = PropertyUtils.getPropertyType(bean, "nested.dupProperty");
1020         assertEquals(String.class, clazz, "dupProperty type");
1021         clazz = PropertyUtils.getPropertyType(bean, "nested.floatProperty");
1022         assertEquals(Float.TYPE, clazz, "floatProperty type");
1023         clazz = PropertyUtils.getPropertyType(bean, "nested.intArray");
1024         assertEquals(intArray.getClass(), clazz, "intArray type");
1025         clazz = PropertyUtils.getPropertyType(bean, "nested.intIndexed");
1026         assertEquals(Integer.TYPE, clazz, "intIndexed type");
1027         clazz = PropertyUtils.getPropertyType(bean, "nested.intProperty");
1028         assertEquals(Integer.TYPE, clazz, "intProperty type");
1029         clazz = PropertyUtils.getPropertyType(bean, "nested.listIndexed");
1030         assertEquals(List.class, clazz, "listIndexed type");
1031         clazz = PropertyUtils.getPropertyType(bean, "nested.longProperty");
1032         assertEquals(Long.TYPE, clazz, "longProperty type");
1033         clazz = PropertyUtils.getPropertyType(bean, "nested.mappedProperty");
1034         assertEquals(String.class, clazz, "mappedProperty type");
1035         clazz = PropertyUtils.getPropertyType(bean, "nested.mappedIntProperty");
1036         assertEquals(Integer.TYPE, clazz, "mappedIntProperty type");
1037         clazz = PropertyUtils.getPropertyType(bean, "nested.readOnlyProperty");
1038         assertEquals(String.class, clazz, "readOnlyProperty type");
1039         clazz = PropertyUtils.getPropertyType(bean, "nested.shortProperty");
1040         assertEquals(Short.TYPE, clazz, "shortProperty type");
1041         clazz = PropertyUtils.getPropertyType(bean, "nested.stringArray");
1042         assertEquals(stringArray.getClass(), clazz, "stringArray type");
1043         clazz = PropertyUtils.getPropertyType(bean, "nested.stringIndexed");
1044         assertEquals(String.class, clazz, "stringIndexed type");
1045         clazz = PropertyUtils.getPropertyType(bean, "nested.stringProperty");
1046         assertEquals(String.class, clazz, "stringProperty type");
1047         clazz = PropertyUtils.getPropertyType(bean, "nested.writeOnlyProperty");
1048         assertEquals(String.class, clazz, "writeOnlyProperty type");
1049 
1050         // Nested DynaBean
1051         clazz = PropertyUtils.getPropertyType(bean, "nestedDynaBean");
1052         assertEquals(DynaBean.class, clazz, "nestedDynaBean type");
1053         clazz = PropertyUtils.getPropertyType(bean, "nestedDynaBean.stringProperty");
1054         assertEquals(String.class, clazz, "nestedDynaBean.stringProperty type");
1055         clazz = PropertyUtils.getPropertyType(bean, "nestedDynaBean.nestedBean");
1056         assertEquals(TestBean.class, clazz, "nestedDynaBean.nestedBean type");
1057         clazz = PropertyUtils.getPropertyType(bean, "nestedDynaBean.nestedBean.nestedDynaBean");
1058         assertEquals(DynaBean.class, clazz, "nestedDynaBean.nestedBean.nestedDynaBean type");
1059         clazz = PropertyUtils.getPropertyType(bean, "nestedDynaBean.nestedBean.nestedDynaBean.stringProperty");
1060         assertEquals(String.class, clazz, "nestedDynaBean.nestedBean.nestedDynaBean.stringPropert type");
1061 
1062         // test Null
1063         clazz = PropertyUtils.getPropertyType(bean, "nestedDynaBean.nullDynaBean");
1064         assertEquals(DynaBean.class, clazz, "nestedDynaBean.nullDynaBean type");
1065         assertThrows(NestedNullException.class, () -> PropertyUtils.getPropertyType(bean, "nestedDynaBean.nullDynaBean.foo"));
1066     }
1067 
1068     /**
1069      * Test accessing a public sub-bean of a package scope bean
1070      */
1071     @Test
1072     public void testGetPublicSubBean_of_PackageBean() throws Exception {
1073 
1074         final PublicSubBean bean = new PublicSubBean();
1075         bean.setFoo("foo-start");
1076         bean.setBar("bar-start");
1077         Object result = null;
1078 
1079         // Get Foo
1080         result = PropertyUtils.getProperty(bean, "foo");
1081         assertEquals("foo-start", result, "foo property");
1082 
1083         // Get Bar
1084         result = PropertyUtils.getProperty(bean, "bar");
1085         assertEquals("bar-start", result, "bar property");
1086     }
1087 
1088     /**
1089      * Base for testGetReadMethod() series of tests.
1090      *
1091      * @param bean       Bean for which to retrieve read methods.
1092      * @param properties Property names to search for
1093      * @param className  Class name where this method should be defined
1094      * @throws InvocationTargetException
1095      * @throws IllegalArgumentException
1096      * @throws IllegalAccessException
1097      */
1098     private void testGetReadMethod(final Object bean, final String[] properties, final String className)
1099             throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
1100 
1101         final PropertyDescriptor[] pd = PropertyUtils.getPropertyDescriptors(bean);
1102         for (final String propertie : properties) {
1103 
1104             // Identify the property descriptor for this property
1105             switch (propertie) {
1106             case "intIndexed":
1107                 continue;
1108             case "stringIndexed":
1109                 continue;
1110             case "writeOnlyProperty":
1111                 continue;
1112             default:
1113                 break;
1114             }
1115             int n = -1;
1116             for (int j = 0; j < pd.length; j++) {
1117                 if (propertie.equals(pd[j].getName())) {
1118                     n = j;
1119                     break;
1120                 }
1121             }
1122             assertTrue(n >= 0, "PropertyDescriptor for " + propertie);
1123 
1124             // Locate an accessible property reader method for it
1125             final Method reader = PropertyUtils.getReadMethod(pd[n]);
1126             assertNotNull(reader, "Reader for " + propertie);
1127             final Class<?> clazz = reader.getDeclaringClass();
1128             assertNotNull(clazz, "Declaring class for " + propertie);
1129             assertEquals(clazz.getName(), className, "Correct declaring class for " + propertie);
1130 
1131             // Actually call the reader method we received
1132             reader.invoke(bean, (Object[]) new Class<?>[0]);
1133         }
1134 
1135     }
1136 
1137     /**
1138      * Test getting accessible property reader methods for a specified list of properties of our standard test bean.
1139      */
1140     @Test
1141     public void testGetReadMethodBasic() throws Exception {
1142         testGetReadMethod(bean, properties, TEST_BEAN_CLASS);
1143 
1144     }
1145 
1146     /**
1147      * Test getting accessible property reader methods for a specified list of properties of a package private subclass of our standard test bean.
1148      */
1149     @Test
1150     public void testGetReadMethodPackageSubclass() throws Exception {
1151         testGetReadMethod(beanPackageSubclass, properties, TEST_BEAN_CLASS);
1152 
1153     }
1154 
1155     /**
1156      * Test getting accessible property reader methods for a specified list of properties that are declared either directly or via implemented interfaces.
1157      */
1158     @Test
1159     public void testGetReadMethodPublicInterface() throws Exception {
1160 
1161         // Properties "bar" and "baz" are visible via implemented interfaces
1162         // (one direct and one indirect)
1163         testGetReadMethod(beanPrivate, new String[] { "bar" }, PRIVATE_DIRECT_CLASS);
1164         testGetReadMethod(beanPrivate, new String[] { "baz" }, PRIVATE_INDIRECT_CLASS);
1165 
1166         // Properties "bar" and "baz" are visible via implemented interfaces
1167         // (one direct and one indirect). The interface is implemented in
1168         // a superclass
1169         testGetReadMethod(beanPrivateSubclass, new String[] { "bar" }, PRIVATE_DIRECT_CLASS);
1170         testGetReadMethod(beanPrivateSubclass, new String[] { "baz" }, PRIVATE_INDIRECT_CLASS);
1171 
1172         // Property "foo" is not accessible because the underlying
1173         // class has package scope
1174         final PropertyDescriptor[] pd = PropertyUtils.getPropertyDescriptors(beanPrivate);
1175         int n = -1;
1176         for (int i = 0; i < pd.length; i++) {
1177             if ("foo".equals(pd[i].getName())) {
1178                 n = i;
1179                 break;
1180             }
1181         }
1182         assertTrue(n >= 0, "Found foo descriptor");
1183         final Method reader = pd[n].getReadMethod();
1184         assertNotNull(reader, "Found foo read method");
1185         assertThrows(IllegalAccessException.class, () -> reader.invoke(beanPrivate, (Object[]) new Class<?>[0]));
1186     }
1187 
1188     /**
1189      * Test getting accessible property reader methods for a specified list of properties of a public subclass of our standard test bean.
1190      */
1191     @Test
1192     public void testGetReadMethodPublicSubclass() throws Exception {
1193         testGetReadMethod(beanPublicSubclass, properties, TEST_BEAN_CLASS);
1194     }
1195 
1196     /** Text case for setting properties on inner classes */
1197     @Test
1198     public void testGetSetInnerBean() throws Exception {
1199         final BeanWithInnerBean bean = new BeanWithInnerBean();
1200 
1201         PropertyUtils.setProperty(bean, "innerBean.fish(loiterTimer)", "5");
1202         String out = (String) PropertyUtils.getProperty(bean.getInnerBean(), "fish(loiterTimer)");
1203         assertEquals("5", out, "(1) Inner class property set/get property failed.");
1204 
1205         out = (String) PropertyUtils.getProperty(bean, "innerBean.fish(loiterTimer)");
1206 
1207         assertEquals("5", out, "(2) Inner class property set/get property failed.");
1208     }
1209 
1210     /** Text case for setting properties on parent */
1211     @Test
1212     public void testGetSetParentBean() throws Exception {
1213 
1214         final SonOfAlphaBean bean = new SonOfAlphaBean("Roger");
1215 
1216         final String out = (String) PropertyUtils.getProperty(bean, "name");
1217         assertEquals("Roger", out, "(1) Get/Set On Parent.");
1218 
1219         PropertyUtils.setProperty(bean, "name", "abcd");
1220         assertEquals("abcd", bean.getName(), "(2) Get/Set On Parent.");
1221     }
1222 
1223     /**
1224      * Corner cases on getSimpleProperty invalid arguments.
1225      */
1226     @Test
1227     public void testGetSimpleArguments() {
1228         assertThrows(NullPointerException.class, () -> PropertyUtils.getSimpleProperty(null, "stringProperty"));
1229         assertThrows(NullPointerException.class, () -> PropertyUtils.getSimpleProperty(bean, null));
1230     }
1231 
1232     /**
1233      * Test getSimpleProperty on a boolean property.
1234      */
1235     @Test
1236     public void testGetSimpleBoolean() throws Exception {
1237         final Object value = PropertyUtils.getSimpleProperty(bean, "booleanProperty");
1238         assertNotNull(value, "Got a value");
1239         assertInstanceOf(Boolean.class, value, "Got correct type");
1240         assertEquals(((Boolean) value).booleanValue(), bean.getBooleanProperty(), "Got correct value");
1241     }
1242 
1243     /**
1244      * Test getSimpleProperty on a double property.
1245      */
1246     @Test
1247     public void testGetSimpleDouble() throws Exception {
1248         final Object value = PropertyUtils.getSimpleProperty(bean, "doubleProperty");
1249         assertNotNull(value, "Got a value");
1250         assertInstanceOf(Double.class, value, "Got correct type");
1251         assertEquals(((Double) value).doubleValue(), bean.getDoubleProperty(), 0.005, "Got correct value");
1252     }
1253 
1254     /**
1255      * Test getSimpleProperty on a float property.
1256      */
1257     @Test
1258     public void testGetSimpleFloat() throws Exception {
1259         final Object value = PropertyUtils.getSimpleProperty(bean, "floatProperty");
1260         assertNotNull(value, "Got a value");
1261         assertInstanceOf(Float.class, value, "Got correct type");
1262         assertEquals(((Float) value).floatValue(), bean.getFloatProperty(), (float) 0.005, "Got correct value");
1263     }
1264 
1265     /**
1266      * Negative test getSimpleProperty on an indexed property.
1267      */
1268     @Test
1269     public void testGetSimpleIndexed() throws Exception {
1270         assertThrows(IllegalArgumentException.class, () -> PropertyUtils.getSimpleProperty(bean, "intIndexed[0]"));
1271     }
1272 
1273     /**
1274      * Test getSimpleProperty on an int property.
1275      */
1276     @Test
1277     public void testGetSimpleInt() throws Exception {
1278         final Object value = PropertyUtils.getSimpleProperty(bean, "intProperty");
1279         assertNotNull(value, "Got a value");
1280         assertInstanceOf(Integer.class, value, "Got correct type");
1281         assertEquals(((Integer) value).intValue(), bean.getIntProperty(), "Got correct value");
1282     }
1283 
1284     /**
1285      * Test getSimpleProperty on a long property.
1286      */
1287     @Test
1288     public void testGetSimpleLong() throws Exception {
1289         final Object value = PropertyUtils.getSimpleProperty(bean, "longProperty");
1290         assertNotNull(value, "Got a value");
1291         assertInstanceOf(Long.class, value, "Got correct type");
1292         assertEquals(((Long) value).longValue(), bean.getLongProperty(), "Got correct value");
1293     }
1294 
1295     /**
1296      * Negative test getSimpleProperty on a nested property.
1297      */
1298     @Test
1299     public void testGetSimpleNested() throws Exception {
1300         assertThrows(IllegalArgumentException.class, () -> PropertyUtils.getSimpleProperty(bean, "nested.stringProperty"));
1301     }
1302 
1303     /**
1304      * Test getSimpleProperty on a read-only String property.
1305      */
1306     @Test
1307     public void testGetSimpleReadOnly() throws Exception {
1308         final Object value = PropertyUtils.getSimpleProperty(bean, "readOnlyProperty");
1309         assertNotNull(value, "Got a value");
1310         assertInstanceOf(String.class, value, "Got correct type");
1311         assertEquals((String) value, bean.getReadOnlyProperty(), "Got correct value");
1312     }
1313 
1314     /**
1315      * Test getSimpleProperty on a short property.
1316      */
1317     @Test
1318     public void testGetSimpleShort() throws Exception {
1319         final Object value = PropertyUtils.getSimpleProperty(bean, "shortProperty");
1320         assertNotNull(value, "Got a value");
1321         assertInstanceOf(Short.class, value, "Got correct type");
1322         assertEquals(((Short) value).shortValue(), bean.getShortProperty(), "Got correct value");
1323     }
1324 
1325     /**
1326      * Test getSimpleProperty on a String property.
1327      */
1328     @Test
1329     public void testGetSimpleString() throws Exception {
1330         final Object value = PropertyUtils.getSimpleProperty(bean, "stringProperty");
1331         assertNotNull(value, "Got a value");
1332         assertInstanceOf(String.class, value, "Got correct type");
1333         assertEquals((String) value, bean.getStringProperty(), "Got correct value");
1334     }
1335 
1336     /**
1337      * Negative test getSimpleProperty on an unknown property.
1338      */
1339     @Test
1340     public void testGetSimpleUnknown() throws Exception {
1341         assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getSimpleProperty(bean, "unknown"));
1342     }
1343 
1344     /**
1345      * Test getSimpleProperty on a write-only String property.
1346      */
1347     @Test
1348     public void testGetSimpleWriteOnly() throws Exception {
1349         assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getSimpleProperty(bean, "writeOnlyProperty"));
1350 
1351     }
1352 
1353     /**
1354      * Base for testGetWriteMethod() series of tests.
1355      *
1356      * @param bean       Bean for which to retrieve write methods.
1357      * @param properties Property names to search for
1358      * @param className  Class name where this method should be defined
1359      */
1360     private void testGetWriteMethod(final Object bean, final String[] properties, final String className) {
1361 
1362         final PropertyDescriptor[] pd = PropertyUtils.getPropertyDescriptors(bean);
1363         for (final String property : properties) {
1364 
1365             // Identify the property descriptor for this property
1366             switch (property) {
1367             case "intIndexed":
1368                 continue;
1369             case "listIndexed":
1370                 continue;
1371             case "nested":
1372                 continue; // This property is read only
1373             case "readOnlyProperty":
1374                 continue;
1375             case "stringIndexed":
1376                 continue;
1377             default:
1378                 break;
1379             }
1380             int n = -1;
1381             for (int j = 0; j < pd.length; j++) {
1382                 if (property.equals(pd[j].getName())) {
1383                     n = j;
1384                     break;
1385                 }
1386             }
1387             assertTrue(n >= 0, "PropertyDescriptor for " + property);
1388 
1389             // Locate an accessible property reader method for it
1390             final Method writer = PropertyUtils.getWriteMethod(pd[n]);
1391             assertNotNull(writer, "Writer for " + property);
1392             final Class<?> clazz = writer.getDeclaringClass();
1393             assertNotNull(clazz, "Declaring class for " + property);
1394             assertEquals(clazz.getName(), className, "Correct declaring class for " + property);
1395 
1396         }
1397 
1398     }
1399 
1400     /**
1401      * Test getting accessible property writer methods for a specified list of properties of our standard test bean.
1402      */
1403     @Test
1404     public void testGetWriteMethodBasic() {
1405         testGetWriteMethod(bean, properties, TEST_BEAN_CLASS);
1406     }
1407 
1408     /**
1409      * Test getting accessible property writer methods for a specified list of properties of a package private subclass of our standard test bean.
1410      */
1411     @Test
1412     public void testGetWriteMethodPackageSubclass() {
1413         testGetWriteMethod(beanPackageSubclass, properties, TEST_BEAN_CLASS);
1414     }
1415 
1416     /**
1417      * Test getting accessible property writer methods for a specified list of properties of a public subclass of our standard test bean.
1418      */
1419     @Test
1420     public void testGetWriteMethodPublicSubclass() {
1421         testGetWriteMethod(beanPublicSubclass, properties, TEST_BEAN_CLASS);
1422     }
1423 
1424     /**
1425      * Test isReadable() method.
1426      */
1427     @Test
1428     public void testIsReadable() throws Exception {
1429         String property = null;
1430         property = "stringProperty";
1431         assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
1432         property = "stringIndexed";
1433         assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
1434         property = "mappedProperty";
1435         assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
1436         property = "nestedDynaBean";
1437         assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
1438         property = "nestedDynaBean.stringProperty";
1439         assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
1440         property = "nestedDynaBean.nestedBean";
1441         assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
1442         property = "nestedDynaBean.nestedBean.nestedDynaBean";
1443         assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
1444         property = "nestedDynaBean.nestedBean.nestedDynaBean.stringProperty";
1445         assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
1446         property = "nestedDynaBean.nullDynaBean";
1447         assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
1448 
1449         assertThrows(NestedNullException.class, () -> PropertyUtils.isReadable(bean, "nestedDynaBean.nullDynaBean.foo"));
1450     }
1451 
1452     /**
1453      * Test isWriteable() method.
1454      */
1455     @Test
1456     public void testIsWriteable() throws Exception {
1457         String property = null;
1458         property = "stringProperty";
1459         assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
1460         property = "stringIndexed";
1461         assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
1462         property = "mappedProperty";
1463         assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
1464         property = "nestedDynaBean";
1465         assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
1466         property = "nestedDynaBean.stringProperty";
1467         assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
1468         property = "nestedDynaBean.nestedBean";
1469         assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
1470         property = "nestedDynaBean.nestedBean.nestedDynaBean";
1471         assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
1472         property = "nestedDynaBean.nestedBean.nestedDynaBean.stringProperty";
1473         assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
1474         property = "nestedDynaBean.nullDynaBean";
1475         assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
1476         assertThrows(NestedNullException.class, () -> PropertyUtils.isWriteable(bean, "nestedDynaBean.nullDynaBean.foo"));
1477     }
1478 
1479     /**
1480      * This tests to see that it is possible to subclass PropertyUtilsBean and change the behavior of setNestedProperty/getNestedProperty when dealing with
1481      * objects that implement Map.
1482      */
1483     @Test
1484     public void testMapExtensionCustom() throws Exception {
1485         final PropsFirstPropertyUtilsBean utilsBean = new PropsFirstPropertyUtilsBean();
1486         final ExtendMapBean bean = new ExtendMapBean();
1487 
1488         // hardly worth testing this, really :-)
1489         bean.setUnusuallyNamedProperty("bean value");
1490         assertEquals("bean value", bean.getUnusuallyNamedProperty(), "Set property direct failed");
1491 
1492         // setSimpleProperty should affect the simple property
1493         utilsBean.setSimpleProperty(bean, "unusuallyNamedProperty", "new value");
1494         assertEquals("new value", bean.getUnusuallyNamedProperty(), "Set property on map failed (1)");
1495 
1496         // setNestedProperty with setter should affect the simple property
1497         // getNestedProperty with getter should obtain the simple property
1498         utilsBean.setProperty(bean, "unusuallyNamedProperty", "next value");
1499         assertEquals("next value", bean.getUnusuallyNamedProperty(), "Set property on map failed (2)");
1500         assertEquals("next value", utilsBean.getNestedProperty(bean, "unusuallyNamedProperty"), "setNestedProperty on non-simple property failed");
1501 
1502         // setting property without setter should update the map
1503         // getting property without setter should fetch from the map
1504         utilsBean.setProperty(bean, "mapProperty", "value1");
1505         assertEquals("value1", utilsBean.getNestedProperty(bean, "mapProperty"), "setNestedProperty on non-simple property failed");
1506 
1507         final HashMap<String, Object> myMap = new HashMap<>();
1508         myMap.put("thebean", bean);
1509         utilsBean.getNestedProperty(myMap, "thebean.mapitem");
1510         utilsBean.getNestedProperty(myMap, "thebean(mapitem)");
1511     }
1512 
1513     /**
1514      * This tests to see that classes that implement Map always have their custom properties ignored.
1515      * <p>
1516      * Note that this behavior has changed several times over past releases of beanutils, breaking backwards compatibility each time. Here's hoping that the
1517      * current 1.7.1 release is the last time this behavior changes!
1518      */
1519     @Test
1520     public void testMapExtensionDefault() throws Exception {
1521         final ExtendMapBean bean = new ExtendMapBean();
1522 
1523         // setting property direct should work, and not affect map
1524         bean.setUnusuallyNamedProperty("bean value");
1525         assertEquals("bean value", bean.getUnusuallyNamedProperty(), "Set property direct failed");
1526         assertNull(PropertyUtils.getNestedProperty(bean, "unusuallyNamedProperty"), "Get on unset map property failed");
1527 
1528         // setting simple property should call the setter method only, and not
1529         // affect the map.
1530         PropertyUtils.setSimpleProperty(bean, "unusuallyNamedProperty", "new value");
1531         assertEquals("new value", bean.getUnusuallyNamedProperty(), "Set property on map failed (1)");
1532         assertNull(PropertyUtils.getNestedProperty(bean, "unusuallyNamedProperty"), "Get on unset map property failed");
1533 
1534         // setting via setNestedProperty should affect the map only, and not
1535         // call the setter method.
1536         PropertyUtils.setProperty(bean, "unusuallyNamedProperty", "next value");
1537         assertEquals("next value", PropertyUtils.getNestedProperty(bean, "unusuallyNamedProperty"),
1538                 "setNestedProperty on map not visible to getNestedProperty");
1539         assertEquals("new value", bean.getUnusuallyNamedProperty(), "Set nested property on map unexpected affected simple property");
1540     }
1541 
1542     /**
1543      * Test the mappedPropertyType of MappedPropertyDescriptor.
1544      */
1545     @Test
1546     public void testMappedPropertyType() throws Exception {
1547 
1548         MappedPropertyDescriptor desc;
1549 
1550         // Check a String property
1551         desc = (MappedPropertyDescriptor) PropertyUtils.getPropertyDescriptor(bean, "mappedProperty");
1552         assertEquals(String.class, desc.getMappedPropertyType());
1553 
1554         // Check an int property
1555         desc = (MappedPropertyDescriptor) PropertyUtils.getPropertyDescriptor(bean, "mappedIntProperty");
1556         assertEquals(Integer.TYPE, desc.getMappedPropertyType());
1557 
1558     }
1559 
1560     /**
1561      * There is an issue in setNestedProperty/getNestedProperty when the target bean is a map and the name string requests mapped or indexed operations on a
1562      * field. These are not supported for fields of a Map, but it's an easy mistake to make and this test case ensures that an appropriate exception is thrown
1563      * when a user does this.
1564      * <p>
1565      * The problem is with passing strings of form "a(b)" or "a[3]" to setNestedProperty or getNestedProperty when the target bean they are applied to
1566      * implements Map. These strings are actually requesting "the result of calling mapped method a on the target object with a parameter of b" or "the result
1567      * of calling indexed method a on the target object with a parameter of 3". And these requests are not valid when the target is a Map as a Map only supports
1568      * calling get(fieldName) or put(fieldName), neither of which can be further indexed with a string or an integer.
1569      * <p>
1570      * However it is likely that some users will assume that "a[3]" when applied to a map will be equivalent to (map.get("a"))[3] with the appropriate
1571      * typecasting, or for "a(b)" to be equivalent to map.get("a").get("b").
1572      * <p>
1573      * Here we verify that an exception is thrown if the user makes this mistake.
1574      */
1575     @Test
1576     public void testNestedPropertyKeyOrIndexOnBeanImplementingMap() throws Exception {
1577         final HashMap<String, Object> map = new HashMap<>();
1578         final HashMap<String, Object> submap = new HashMap<>();
1579         final BetaBean betaBean1 = new BetaBean("test1");
1580         final BetaBean betaBean2 = new BetaBean("test2");
1581 
1582         // map.put("submap", submap)
1583         PropertyUtils.setNestedProperty(map, "submap", submap);
1584 
1585         // map.get("submap").put("beta1", betaBean1)
1586         PropertyUtils.setNestedProperty(map, "submap.beta1", betaBean1);
1587         assertEquals("submap", keysToString(map), "Unexpected keys in map");
1588         assertEquals("beta1", keysToString(submap), "Unexpected keys in submap");
1589 
1590         // One would expect that the command below would be equivalent to
1591         // Map m = (Map) map.get("submap");
1592         // m.put("beta2", betaBean2)
1593         // However this isn't how javabeans property methods work. A map
1594         // only effectively has "simple" properties, even when the
1595         // returned object is a Map or Array.
1596         final IllegalArgumentException e1 = assertThrows(IllegalArgumentException.class,
1597                 () -> PropertyUtils.setNestedProperty(map, "submap(beta2)", betaBean2));
1598         // What, no exception? In that case, setNestedProperties has
1599         // probably just tried to do
1600         // map.set("submap(beta2)", betaBean2)
1601         // which is almost certainly not what the used expected. This is
1602         // what BeanUtils 1.5.0 to 1.7.1 did....
1603         // ok, getting an exception was expected. As it is of a generic
1604         // type, let's check the message string to make sure it really
1605         // was caused by the issue we expected.
1606         assertTrue(e1.getMessage().contains("Indexed or mapped properties are not supported"), "Unexpected exception message");
1607 
1608         // One would expect that "submap[3]" would be equivalent to
1609         // Object[] objects = (Object[]) map.get("submap");
1610         // return objects[3];
1611         // However this isn't how javabeans property methods work. A map
1612         // only effectively has "simple" properties, even when the
1613         // returned object is a Map or Array.
1614         final IllegalArgumentException e2 = assertThrows(IllegalArgumentException.class, () -> PropertyUtils.getNestedProperty(map, "submap[3]"));
1615         // What, no exception? In that case, getNestedProperties has
1616         // probably just tried to do
1617         // map.get("submap[3]")
1618         // which is almost certainly not what the used expected. This is
1619         // what BeanUtils 1.5.0 to 1.7.1 did....
1620         // ok, getting an exception was expected. As it is of a generic
1621         // type, let's check the message string to make sure it really
1622         // was caused by the issue we expected.
1623         final int index = e2.getMessage().indexOf("Indexed or mapped properties are not supported");
1624         assertTrue(index >= 0, "Unexpected exception message");
1625     }
1626 
1627     @Test
1628     public void testNestedWithIndex() throws Exception {
1629         final NestedTestBean nestedBean = new NestedTestBean("base");
1630         nestedBean.init();
1631         nestedBean.getSimpleBeanProperty().init();
1632 
1633         NestedTestBean
1634 
1635         // test first calling properties on indexed beans
1636 
1637         value = (NestedTestBean) PropertyUtils.getProperty(nestedBean, "indexedProperty[0]");
1638         assertEquals("Bean@0", value.getName(), "Cannot get simple index(1)");
1639         assertEquals("NOT SET", value.getTestString(), "Bug in NestedTestBean");
1640 
1641         value = (NestedTestBean) PropertyUtils.getProperty(nestedBean, "indexedProperty[1]");
1642         assertEquals("Bean@1", value.getName(), "Cannot get simple index(1)");
1643         assertEquals("NOT SET", value.getTestString(), "Bug in NestedTestBean");
1644 
1645         String prop = (String) PropertyUtils.getProperty(nestedBean, "indexedProperty[0].testString");
1646         assertEquals("NOT SET", prop, "Get property on indexes failed (1)");
1647 
1648         prop = (String) PropertyUtils.getProperty(nestedBean, "indexedProperty[1].testString");
1649         assertEquals("NOT SET", prop, "Get property on indexes failed (2)");
1650 
1651         PropertyUtils.setProperty(nestedBean, "indexedProperty[0].testString", "Test#1");
1652         assertEquals("Test#1", nestedBean.getIndexedProperty(0).getTestString(), "Cannot set property on indexed bean (1)");
1653 
1654         PropertyUtils.setProperty(nestedBean, "indexedProperty[1].testString", "Test#2");
1655         assertEquals("Test#2", nestedBean.getIndexedProperty(1).getTestString(), "Cannot set property on indexed bean (2)");
1656 
1657         // test first calling indexed properties on a simple property
1658 
1659         value = (NestedTestBean) PropertyUtils.getProperty(nestedBean, "simpleBeanProperty");
1660         assertEquals("Simple Property Bean", value.getName(), "Cannot get simple bean");
1661         assertEquals("NOT SET", value.getTestString(), "Bug in NestedTestBean");
1662 
1663         value = (NestedTestBean) PropertyUtils.getProperty(nestedBean, "simpleBeanProperty.indexedProperty[3]");
1664         assertEquals("Bean@3", value.getName(), "Cannot get index property on property");
1665         assertEquals("NOT SET", value.getTestString(), "Bug in NestedTestBean");
1666 
1667         PropertyUtils.setProperty(nestedBean, "simpleBeanProperty.indexedProperty[3].testString", "Test#3");
1668         assertEquals("Test#3", nestedBean.getSimpleBeanProperty().getIndexedProperty(3).getTestString(), "Cannot set property on indexed property on property");
1669     }
1670 
1671     /**
1672      * Tests whether a BeanIntrospector can be removed.
1673      */
1674     @Test
1675     public void testRemoveBeanIntrospector() {
1676         assertTrue(PropertyUtils.removeBeanIntrospector(DefaultBeanIntrospector.INSTANCE), "Wrong result");
1677         final PropertyDescriptor[] desc = PropertyUtils.getPropertyDescriptors(AlphaBean.class);
1678         assertEquals(0, desc.length, "Got descriptors");
1679         PropertyUtils.addBeanIntrospector(DefaultBeanIntrospector.INSTANCE);
1680     }
1681 
1682     /**
1683      * Tests whether a reset of the registered BeanIntrospectors can be performed.
1684      */
1685     @Test
1686     public void testResetBeanIntrospectors() {
1687         assertTrue(PropertyUtils.removeBeanIntrospector(DefaultBeanIntrospector.INSTANCE), "Wrong result");
1688         PropertyUtils.resetBeanIntrospectors();
1689         final PropertyDescriptor[] desc = PropertyUtils.getPropertyDescriptors(AlphaBean.class);
1690         assertTrue(desc.length > 0, "Got no descriptors");
1691     }
1692 
1693     /**
1694      * Corner cases on setIndexedProperty invalid arguments.
1695      */
1696     @Test
1697     public void testSetIndexedArguments() {
1698         // Use explicit index argument
1699         assertThrows(NullPointerException.class, () -> PropertyUtils.setIndexedProperty(null, "intArray", 0, Integer.valueOf(1)));
1700         assertThrows(NullPointerException.class, () -> PropertyUtils.setIndexedProperty(bean, null, 0, Integer.valueOf(1)));
1701         // Use index expression
1702         assertThrows(NullPointerException.class, () -> PropertyUtils.setIndexedProperty(null, "intArray[0]", Integer.valueOf(1)));
1703         assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setIndexedProperty(bean, "[0]", Integer.valueOf(1)));
1704         assertThrows(IllegalArgumentException.class, () -> PropertyUtils.setIndexedProperty(bean, "intArray", Integer.valueOf(1)));
1705         // Use explicit index argument
1706         assertThrows(NullPointerException.class, () -> PropertyUtils.setIndexedProperty(null, "intIndexed", 0, Integer.valueOf(1)));
1707         assertThrows(NullPointerException.class, () -> PropertyUtils.setIndexedProperty(bean, null, 0, Integer.valueOf(1)));
1708         // Use index expression
1709         assertThrows(NullPointerException.class, () -> PropertyUtils.setIndexedProperty(null, "intIndexed[0]", Integer.valueOf(1)));
1710         assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setIndexedProperty(bean, "[0]", Integer.valueOf(1)));
1711         assertThrows(IllegalArgumentException.class, () -> PropertyUtils.setIndexedProperty(bean, "intIndexed", Integer.valueOf(1)));
1712     }
1713 
1714     /**
1715      * Test setting an indexed value out of a multi-dimensional array
1716      */
1717     @Test
1718     public void testSetIndexedArray() throws Exception {
1719         final String[] firstArray = { "FIRST-1", "FIRST-2", "FIRST-3" };
1720         final String[] secondArray = { "SECOND-1", "SECOND-2", "SECOND-3", "SECOND-4" };
1721         final String[][] mainArray = { firstArray, secondArray };
1722         final TestBean bean = new TestBean(mainArray);
1723         assertEquals("SECOND-3", bean.getString2dArray(1)[2], "BEFORE");
1724         PropertyUtils.setProperty(bean, "string2dArray[1][2]", "SECOND-3-UPDATED");
1725         assertEquals("SECOND-3-UPDATED", bean.getString2dArray(1)[2], "AFTER");
1726     }
1727 
1728     /**
1729      * Test setting an indexed value out of List of Lists
1730      */
1731     @Test
1732     public void testSetIndexedList() throws Exception {
1733         final String[] firstArray = { "FIRST-1", "FIRST-2", "FIRST-3" };
1734         final String[] secondArray = { "SECOND-1", "SECOND-2", "SECOND-3", "SECOND-4" };
1735         final List<Object> mainList = new ArrayList<>();
1736         mainList.add(Arrays.asList(firstArray));
1737         mainList.add(Arrays.asList(secondArray));
1738         final TestBean bean = new TestBean(mainList);
1739         assertEquals("SECOND-4", ((List<?>) bean.getListIndexed().get(1)).get(3), "BEFORE");
1740         PropertyUtils.setProperty(bean, "listIndexed[1][3]", "SECOND-4-UPDATED");
1741         assertEquals("SECOND-4-UPDATED", ((List<?>) bean.getListIndexed().get(1)).get(3), "AFTER");
1742     }
1743 
1744     /**
1745      * Test setting a value out of a mapped Map
1746      */
1747     @Test
1748     public void testSetIndexedMap() throws Exception {
1749         final Map<String, Object> firstMap = new HashMap<>();
1750         firstMap.put("FIRST-KEY-1", "FIRST-VALUE-1");
1751         firstMap.put("FIRST-KEY-2", "FIRST-VALUE-2");
1752         final Map<String, Object> secondMap = new HashMap<>();
1753         secondMap.put("SECOND-KEY-1", "SECOND-VALUE-1");
1754         secondMap.put("SECOND-KEY-2", "SECOND-VALUE-2");
1755 
1756         final List<Object> mainList = new ArrayList<>();
1757         mainList.add(firstMap);
1758         mainList.add(secondMap);
1759         final TestBean bean = new TestBean(mainList);
1760 
1761         assertEquals(null, ((Map<?, ?>) bean.getListIndexed().get(0)).get("FIRST-NEW-KEY"), "BEFORE");
1762         assertEquals("SECOND-VALUE-1", ((Map<?, ?>) bean.getListIndexed().get(1)).get("SECOND-KEY-1"), "BEFORE");
1763         PropertyUtils.setProperty(bean, "listIndexed[0](FIRST-NEW-KEY)", "FIRST-NEW-VALUE");
1764         PropertyUtils.setProperty(bean, "listIndexed[1](SECOND-KEY-1)", "SECOND-VALUE-1-UPDATED");
1765         assertEquals("FIRST-NEW-VALUE", ((Map<?, ?>) bean.getListIndexed().get(0)).get("FIRST-NEW-KEY"), "BEFORE");
1766         assertEquals("SECOND-VALUE-1-UPDATED", ((Map<?, ?>) bean.getListIndexed().get(1)).get("SECOND-KEY-1"), "AFTER");
1767     }
1768 
1769     /**
1770      * Positive and negative tests on setIndexedProperty valid arguments.
1771      */
1772     @Test
1773     public void testSetIndexedValues() throws Exception {
1774         Object value = null;
1775         // Use explicit index argument
1776         PropertyUtils.setIndexedProperty(bean, "dupProperty", 0, "New 0");
1777         value = PropertyUtils.getIndexedProperty(bean, "dupProperty", 0);
1778         assertNotNull(value, "Returned new value 0");
1779         assertInstanceOf(String.class, value, "Returned String new value 0");
1780         assertEquals("New 0", (String) value, "Returned correct new value 0");
1781 
1782         PropertyUtils.setIndexedProperty(bean, "intArray", 0, Integer.valueOf(1));
1783         value = PropertyUtils.getIndexedProperty(bean, "intArray", 0);
1784         assertNotNull(value, "Returned new value 0");
1785         assertInstanceOf(Integer.class, value, "Returned Integer new value 0");
1786         assertEquals(1, ((Integer) value).intValue(), "Returned correct new value 0");
1787 
1788         PropertyUtils.setIndexedProperty(bean, "intIndexed", 1, Integer.valueOf(11));
1789         value = PropertyUtils.getIndexedProperty(bean, "intIndexed", 1);
1790         assertNotNull(value, "Returned new value 1");
1791         assertInstanceOf(Integer.class, value, "Returned Integer new value 1");
1792         assertEquals(11, ((Integer) value).intValue(), "Returned correct new value 1");
1793 
1794         PropertyUtils.setIndexedProperty(bean, "listIndexed", 2, "New Value 2");
1795         value = PropertyUtils.getIndexedProperty(bean, "listIndexed", 2);
1796         assertNotNull(value, "Returned new value 2");
1797         assertInstanceOf(String.class, value, "Returned String new value 2");
1798         assertEquals("New Value 2", (String) value, "Returned correct new value 2");
1799 
1800         PropertyUtils.setIndexedProperty(bean, "stringArray", 2, "New Value 2");
1801         value = PropertyUtils.getIndexedProperty(bean, "stringArray", 2);
1802         assertNotNull(value, "Returned new value 2");
1803         assertInstanceOf(String.class, value, "Returned String new value 2");
1804         assertEquals("New Value 2", (String) value, "Returned correct new value 2");
1805 
1806         PropertyUtils.setIndexedProperty(bean, "stringArray", 3, "New Value 3");
1807         value = PropertyUtils.getIndexedProperty(bean, "stringArray", 3);
1808         assertNotNull(value, "Returned new value 3");
1809         assertInstanceOf(String.class, value, "Returned String new value 3");
1810         assertEquals("New Value 3", (String) value, "Returned correct new value 3");
1811 
1812         // Use index expression
1813 
1814         PropertyUtils.setIndexedProperty(bean, "dupProperty[4]", "New 4");
1815         value = PropertyUtils.getIndexedProperty(bean, "dupProperty[4]");
1816         assertNotNull(value, "Returned new value 4");
1817         assertInstanceOf(String.class, value, "Returned String new value 4");
1818         assertEquals("New 4", (String) value, "Returned correct new value 4");
1819 
1820         PropertyUtils.setIndexedProperty(bean, "intArray[4]", Integer.valueOf(1));
1821         value = PropertyUtils.getIndexedProperty(bean, "intArray[4]");
1822         assertNotNull(value, "Returned new value 4");
1823         assertInstanceOf(Integer.class, value, "Returned Integer new value 4");
1824         assertEquals(1, ((Integer) value).intValue(), "Returned correct new value 4");
1825 
1826         PropertyUtils.setIndexedProperty(bean, "intIndexed[3]", Integer.valueOf(11));
1827         value = PropertyUtils.getIndexedProperty(bean, "intIndexed[3]");
1828         assertNotNull(value, "Returned new value 5");
1829         assertInstanceOf(Integer.class, value, "Returned Integer new value 5");
1830         assertEquals(11, ((Integer) value).intValue(), "Returned correct new value 5");
1831 
1832         PropertyUtils.setIndexedProperty(bean, "listIndexed[1]", "New Value 2");
1833         value = PropertyUtils.getIndexedProperty(bean, "listIndexed[1]");
1834         assertNotNull(value, "Returned new value 6");
1835         assertInstanceOf(String.class, value, "Returned String new value 6");
1836         assertEquals("New Value 2", (String) value, "Returned correct new value 6");
1837 
1838         PropertyUtils.setIndexedProperty(bean, "stringArray[1]", "New Value 2");
1839         value = PropertyUtils.getIndexedProperty(bean, "stringArray[2]");
1840         assertNotNull(value, "Returned new value 6");
1841         assertInstanceOf(String.class, value, "Returned String new value 6");
1842         assertEquals("New Value 2", (String) value, "Returned correct new value 6");
1843 
1844         PropertyUtils.setIndexedProperty(bean, "stringArray[0]", "New Value 3");
1845         value = PropertyUtils.getIndexedProperty(bean, "stringArray[0]");
1846         assertNotNull(value, "Returned new value 7");
1847         assertInstanceOf(String.class, value, "Returned String new value 7");
1848         assertEquals("New Value 3", (String) value, "Returned correct new value 7");
1849 
1850         // Index out of bounds tests
1851         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "dupProperty", -1, "New -1"));
1852         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "dupProperty", 5, "New 5"));
1853         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "intArray", -1, Integer.valueOf(0)));
1854         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "intArray", 5, Integer.valueOf(0)));
1855         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "intIndexed", -1, Integer.valueOf(0)));
1856         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "intIndexed", 5, Integer.valueOf(0)));
1857         assertThrows(IndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "listIndexed", 5, "New String"));
1858         assertThrows(IndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "listIndexed", -1, "New String"));
1859         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "stringArray", -1, "New String"));
1860         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "stringArray", 5, "New String"));
1861         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "stringIndexed", -1, "New String"));
1862         assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "stringIndexed", 5, "New String"));
1863     }
1864 
1865     /**
1866      * Corner cases on getMappedProperty invalid arguments.
1867      */
1868     @Test
1869     public void testSetMappedArguments() {
1870         // Use explicit key argument
1871         assertThrows(NullPointerException.class, () -> PropertyUtils.setMappedProperty(null, "mappedProperty", "First Key", "First Value"));
1872         assertThrows(NullPointerException.class, () -> PropertyUtils.setMappedProperty(bean, null, "First Key", "First Value"));
1873         assertThrows(NullPointerException.class, () -> PropertyUtils.setMappedProperty(bean, "mappedProperty", null, "First Value"));
1874         // Use key expression
1875         assertThrows(NullPointerException.class, () -> PropertyUtils.setMappedProperty(null, "mappedProperty(First Key)", "First Value"));
1876         assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setMappedProperty(bean, "(Second Key)", "Second Value"));
1877         assertThrows(IllegalArgumentException.class, () -> PropertyUtils.setMappedProperty(bean, "mappedProperty", "Third Value"));
1878     }
1879 
1880     /**
1881      * Test setting an indexed value out of a mapped array
1882      */
1883     @Test
1884     public void testSetMappedArray() throws Exception {
1885         final TestBean bean = new TestBean();
1886         final String[] array = { "abc", "def", "ghi" };
1887         bean.getMapProperty().put("mappedArray", array);
1888 
1889         assertEquals("def", ((String[]) bean.getMapProperty().get("mappedArray"))[1], "BEFORE");
1890         PropertyUtils.setProperty(bean, "mapProperty(mappedArray)[1]", "DEF-UPDATED");
1891         assertEquals("DEF-UPDATED", ((String[]) bean.getMapProperty().get("mappedArray"))[1], "AFTER");
1892     }
1893 
1894     /**
1895      * Test setting an indexed value out of a mapped List
1896      */
1897     @Test
1898     public void testSetMappedList() throws Exception {
1899         final TestBean bean = new TestBean();
1900         final List<Object> list = new ArrayList<>();
1901         list.add("klm");
1902         list.add("nop");
1903         list.add("qrs");
1904         bean.getMapProperty().put("mappedList", list);
1905 
1906         assertEquals("klm", ((List<?>) bean.getMapProperty().get("mappedList")).get(0), "BEFORE");
1907         PropertyUtils.setProperty(bean, "mapProperty(mappedList)[0]", "KLM-UPDATED");
1908         assertEquals("KLM-UPDATED", ((List<?>) bean.getMapProperty().get("mappedList")).get(0), "AFTER");
1909     }
1910 
1911     /**
1912      * Test setting a value out of a mapped Map
1913      */
1914     @Test
1915     public void testSetMappedMap() throws Exception {
1916         final TestBean bean = new TestBean();
1917         final Map<String, Object> map = new HashMap<>();
1918         map.put("sub-key-1", "sub-value-1");
1919         map.put("sub-key-2", "sub-value-2");
1920         map.put("sub-key-3", "sub-value-3");
1921         bean.getMapProperty().put("mappedMap", map);
1922 
1923         assertEquals("sub-value-3", ((Map<?, ?>) bean.getMapProperty().get("mappedMap")).get("sub-key-3"), "BEFORE");
1924         PropertyUtils.setProperty(bean, "mapProperty(mappedMap)(sub-key-3)", "SUB-KEY-3-UPDATED");
1925         assertEquals("SUB-KEY-3-UPDATED", ((Map<?, ?>) bean.getMapProperty().get("mappedMap")).get("sub-key-3"), "AFTER");
1926     }
1927 
1928     /**
1929      * Test setting mapped values with periods in the key.
1930      */
1931     @Test
1932     public void testSetMappedPeriods() throws Exception {
1933 
1934         // PropertyUtils.setMappedProperty()--------
1935         bean.setMappedProperty("key.with.a.dot", "Special Value");
1936         assertEquals("Special Value", bean.getMappedProperty("key.with.a.dot"), "Can retrieve directly (A)");
1937 
1938         PropertyUtils.setMappedProperty(bean, "mappedProperty", "key.with.a.dot", "Updated Special Value");
1939         assertEquals("Updated Special Value", bean.getMappedProperty("key.with.a.dot"), "Check set via setMappedProperty");
1940 
1941         // PropertyUtils.setNestedProperty()
1942         bean.setMappedProperty("key.with.a.dot", "Special Value");
1943         assertEquals("Special Value", bean.getMappedProperty("key.with.a.dot"), "Can retrieve directly (B)");
1944         PropertyUtils.setNestedProperty(bean, "mappedProperty(key.with.a.dot)", "Updated Special Value");
1945         assertEquals("Updated Special Value", bean.getMappedProperty("key.with.a.dot"), "Check set via setNestedProperty (B)");
1946 
1947         // PropertyUtils.setNestedProperty()
1948         final TestBean testBean = new TestBean();
1949         bean.setMappedObjects("nested.property", testBean);
1950         assertEquals("This is a string", testBean.getStringProperty(), "Can retrieve directly (C)");
1951         PropertyUtils.setNestedProperty(bean, "mappedObjects(nested.property).stringProperty", "Updated String Value");
1952         assertEquals("Updated String Value", testBean.getStringProperty(), "Check set via setNestedProperty (C)");
1953 
1954         // PropertyUtils.setNestedProperty()
1955         bean.getNested().setMappedProperty("Mapped Key", "Nested Mapped Value");
1956         assertEquals("Nested Mapped Value", PropertyUtils.getNestedProperty(bean, "nested.mappedProperty(Mapped Key)"),
1957                 "Can retrieve via getNestedProperty (D)");
1958         PropertyUtils.setNestedProperty(bean, "nested.mappedProperty(Mapped Key)", "Updated Nested Mapped Value");
1959         assertEquals("Updated Nested Mapped Value", PropertyUtils.getNestedProperty(bean, "nested.mappedProperty(Mapped Key)"),
1960                 "Check set via setNestedProperty (D)");
1961     }
1962 
1963     /**
1964      * Positive and negative tests on setMappedProperty valid arguments.
1965      */
1966     @Test
1967     public void testSetMappedValues() throws Exception {
1968         Object value = null;
1969         // Use explicit key argument
1970         value = PropertyUtils.getMappedProperty(bean, "mappedProperty", "Fourth Key");
1971         assertNull(value, "Can not find fourth value");
1972 
1973         PropertyUtils.setMappedProperty(bean, "mappedProperty", "Fourth Key", "Fourth Value");
1974 
1975         value = PropertyUtils.getMappedProperty(bean, "mappedProperty", "Fourth Key");
1976         assertEquals("Fourth Value", value, "Can find fourth value");
1977 
1978         // Use key expression with parentheses
1979         value = PropertyUtils.getMappedProperty(bean, "mappedProperty(Fifth Key)");
1980         assertNull(value, "Can not find fifth value");
1981 
1982         PropertyUtils.setMappedProperty(bean, "mappedProperty(Fifth Key)", "Fifth Value");
1983 
1984         value = PropertyUtils.getMappedProperty(bean, "mappedProperty(Fifth Key)");
1985         assertEquals("Fifth Value", value, "Can find fifth value");
1986 
1987         // Use key expression with dotted expression
1988 
1989         value = PropertyUtils.getNestedProperty(bean, "mapProperty.Sixth Key");
1990         assertNull(value, "Can not find sixth value");
1991 
1992         PropertyUtils.setNestedProperty(bean, "mapProperty.Sixth Key", "Sixth Value");
1993 
1994         value = PropertyUtils.getNestedProperty(bean, "mapProperty.Sixth Key");
1995         assertEquals("Sixth Value", value, "Can find sixth value");
1996     }
1997 
1998     /**
1999      * Corner cases on setNestedProperty invalid arguments.
2000      */
2001     @Test
2002     public void testSetNestedArguments() {
2003         assertThrows(NullPointerException.class, () -> PropertyUtils.setNestedProperty(null, "stringProperty", ""));
2004         assertThrows(NullPointerException.class, () -> PropertyUtils.setNestedProperty(bean, null, ""));
2005     }
2006 
2007     /**
2008      * Test setNextedProperty on a boolean property.
2009      */
2010     @Test
2011     public void testSetNestedBoolean() throws Exception {
2012         final boolean oldValue = bean.getNested().getBooleanProperty();
2013         final boolean newValue = !oldValue;
2014         PropertyUtils.setNestedProperty(bean, "nested.booleanProperty", Boolean.valueOf(newValue));
2015         assertEquals(newValue, bean.getNested().getBooleanProperty(), "Matched new value");
2016     }
2017 
2018     /**
2019      * Test setNestedProperty on a double property.
2020      */
2021     @Test
2022     public void testSetNestedDouble() throws Exception {
2023         final double oldValue = bean.getNested().getDoubleProperty();
2024         final double newValue = oldValue + 1.0;
2025         PropertyUtils.setNestedProperty(bean, "nested.doubleProperty", Double.valueOf(newValue));
2026         assertEquals(newValue, bean.getNested().getDoubleProperty(), 0.005, "Matched new value");
2027     }
2028 
2029     /**
2030      * Test setNestedProperty on a float property.
2031      */
2032     @Test
2033     public void testSetNestedFloat() throws Exception {
2034         final float oldValue = bean.getNested().getFloatProperty();
2035         final float newValue = oldValue + (float) 1.0;
2036         PropertyUtils.setNestedProperty(bean, "nested.floatProperty", Float.valueOf(newValue));
2037         assertEquals(newValue, bean.getNested().getFloatProperty(), (float) 0.005, "Matched new value");
2038     }
2039 
2040     /**
2041      * Test setNestedProperty on a int property.
2042      */
2043     @Test
2044     public void testSetNestedInt() throws Exception {
2045         final int oldValue = bean.getNested().getIntProperty();
2046         final int newValue = oldValue + 1;
2047         PropertyUtils.setNestedProperty(bean, "nested.intProperty", Integer.valueOf(newValue));
2048         assertEquals(newValue, bean.getNested().getIntProperty(), "Matched new value");
2049     }
2050 
2051     /**
2052      * Test setNestedProperty on a long property.
2053      */
2054     @Test
2055     public void testSetNestedLong() throws Exception {
2056         final long oldValue = bean.getNested().getLongProperty();
2057         final long newValue = oldValue + 1;
2058         PropertyUtils.setNestedProperty(bean, "nested.longProperty", Long.valueOf(newValue));
2059         assertEquals(newValue, bean.getNested().getLongProperty(), "Matched new value");
2060     }
2061 
2062     /**
2063      * Test setNestedProperty on a read-only String property.
2064      */
2065     @Test
2066     public void testSetNestedReadOnly() throws Exception {
2067         final String oldValue = bean.getNested().getWriteOnlyPropertyValue();
2068         final String newValue = oldValue + " Extra Value";
2069         assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setNestedProperty(bean, "nested.readOnlyProperty", newValue));
2070     }
2071 
2072     /**
2073      * Test setNestedProperty on a short property.
2074      */
2075     @Test
2076     public void testSetNestedShort() throws Exception {
2077         final short oldValue = bean.getNested().getShortProperty();
2078         short newValue = oldValue;
2079         newValue++;
2080         PropertyUtils.setNestedProperty(bean, "nested.shortProperty", Short.valueOf(newValue));
2081         assertEquals(newValue, bean.getNested().getShortProperty(), "Matched new value");
2082     }
2083 
2084     /**
2085      * Test setNestedProperty on a String property.
2086      */
2087     @Test
2088     public void testSetNestedString() throws Exception {
2089         final String oldValue = bean.getNested().getStringProperty();
2090         final String newValue = oldValue + " Extra Value";
2091         PropertyUtils.setNestedProperty(bean, "nested.stringProperty", newValue);
2092         assertEquals(newValue, bean.getNested().getStringProperty(), "Matched new value");
2093     }
2094 
2095     /**
2096      * Test setNestedProperty on an unknown property name.
2097      */
2098     @Test
2099     public void testSetNestedUnknown() throws Exception {
2100         final String newValue = "New String Value";
2101         assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setNestedProperty(bean, "nested.unknown", newValue));
2102     }
2103 
2104     /**
2105      * Test setNestedProperty on a write-only String property.
2106      */
2107     @Test
2108     public void testSetNestedWriteOnly() throws Exception {
2109         final String oldValue = bean.getNested().getWriteOnlyPropertyValue();
2110         final String newValue = oldValue + " Extra Value";
2111         PropertyUtils.setNestedProperty(bean, "nested.writeOnlyProperty", newValue);
2112         assertEquals(newValue, bean.getNested().getWriteOnlyPropertyValue(), "Matched new value");
2113     }
2114 
2115     @Test
2116     public void testSetNoGetter() throws Exception {
2117         final BetaBean bean = new BetaBean("Cedric");
2118 
2119         // test standard no getter
2120         bean.setNoGetterProperty("Sigma");
2121         assertEquals("Sigma", bean.getSecret(), "BetaBean test failed");
2122 
2123         assertNotNull(PropertyUtils.getPropertyDescriptor(bean, "noGetterProperty"), "Descriptor is null");
2124 
2125         BeanUtils.setProperty(bean, "noGetterProperty", "Omega");
2126         assertEquals("Omega", bean.getSecret(), "Cannot set no-getter property");
2127 
2128         // test mapped no getter descriptor
2129         assertNotNull(PropertyUtils.getPropertyDescriptor(bean, "noGetterMappedProperty"), "Map Descriptor is null");
2130 
2131         PropertyUtils.setMappedProperty(bean, "noGetterMappedProperty", "Epsilon", "Epsilon");
2132         assertEquals("MAP:Epsilon", bean.getSecret(), "Cannot set mapped no-getter property");
2133     }
2134 
2135     /**
2136      * Test accessing a public sub-bean of a package scope bean
2137      */
2138     @Test
2139     public void testSetPublicSubBean_of_PackageBean() throws Exception {
2140 
2141         final PublicSubBean bean = new PublicSubBean();
2142         bean.setFoo("foo-start");
2143         bean.setBar("bar-start");
2144         // Set Foo
2145         PropertyUtils.setProperty(bean, "foo", "foo-updated");
2146         assertEquals("foo-updated", bean.getFoo(), "foo property");
2147         // Set Bar
2148         PropertyUtils.setProperty(bean, "bar", "bar-updated");
2149         assertEquals("bar-updated", bean.getBar(), "bar property");
2150     }
2151 
2152     /**
2153      * Corner cases on setSimpleProperty invalid arguments.
2154      */
2155     @Test
2156     public void testSetSimpleArguments() {
2157         assertThrows(NullPointerException.class, () -> PropertyUtils.setSimpleProperty(null, "stringProperty", ""));
2158         assertThrows(NullPointerException.class, () -> PropertyUtils.setSimpleProperty(bean, null, ""));
2159     }
2160 
2161     /**
2162      * Test setSimpleProperty on a boolean property.
2163      */
2164     @Test
2165     public void testSetSimpleBoolean() throws Exception {
2166         final boolean oldValue = bean.getBooleanProperty();
2167         final boolean newValue = !oldValue;
2168         PropertyUtils.setSimpleProperty(bean, "booleanProperty", Boolean.valueOf(newValue));
2169         assertEquals(newValue, bean.getBooleanProperty(), "Matched new value");
2170     }
2171 
2172     /**
2173      * Test setSimpleProperty on a double property.
2174      */
2175     @Test
2176     public void testSetSimpleDouble() throws Exception {
2177         final double oldValue = bean.getDoubleProperty();
2178         final double newValue = oldValue + 1.0;
2179         PropertyUtils.setSimpleProperty(bean, "doubleProperty", Double.valueOf(newValue));
2180         assertEquals(newValue, bean.getDoubleProperty(), 0.005, "Matched new value");
2181     }
2182 
2183     /**
2184      * Test setSimpleProperty on a float property.
2185      */
2186     @Test
2187     public void testSetSimpleFloat() throws Exception {
2188         final float oldValue = bean.getFloatProperty();
2189         final float newValue = oldValue + (float) 1.0;
2190         PropertyUtils.setSimpleProperty(bean, "floatProperty", Float.valueOf(newValue));
2191         assertEquals(newValue, bean.getFloatProperty(), (float) 0.005, "Matched new value");
2192     }
2193 
2194     /**
2195      * Negative test setSimpleProperty on an indexed property.
2196      */
2197     @Test
2198     public void testSetSimpleIndexed() throws Exception {
2199         assertThrows(IllegalArgumentException.class, () -> PropertyUtils.setSimpleProperty(bean, "stringIndexed[0]", "New String Value"));
2200     }
2201 
2202     /**
2203      * Test setSimpleProperty on a int property.
2204      */
2205     @Test
2206     public void testSetSimpleInt() throws Exception {
2207         final int oldValue = bean.getIntProperty();
2208         final int newValue = oldValue + 1;
2209         PropertyUtils.setSimpleProperty(bean, "intProperty", Integer.valueOf(newValue));
2210         assertEquals(newValue, bean.getIntProperty(), "Matched new value");
2211     }
2212 
2213     /**
2214      * Test setSimpleProperty on a long property.
2215      */
2216     @Test
2217     public void testSetSimpleLong() throws Exception {
2218         final long oldValue = bean.getLongProperty();
2219         final long newValue = oldValue + 1;
2220         PropertyUtils.setSimpleProperty(bean, "longProperty", Long.valueOf(newValue));
2221         assertEquals(newValue, bean.getLongProperty(), "Matched new value");
2222     }
2223 
2224     /**
2225      * Negative test setSimpleProperty on a nested property.
2226      */
2227     @Test
2228     public void testSetSimpleNested() throws Exception {
2229         assertThrows(IllegalArgumentException.class, () -> PropertyUtils.setSimpleProperty(bean, "nested.stringProperty", "New String Value"));
2230     }
2231 
2232     /**
2233      * Test setSimpleProperty on a read-only String property.
2234      */
2235     @Test
2236     public void testSetSimpleReadOnly() throws Exception {
2237         final String oldValue = bean.getWriteOnlyPropertyValue();
2238         final String newValue = oldValue + " Extra Value";
2239         final NoSuchMethodException e = assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setSimpleProperty(bean, "readOnlyProperty", newValue));
2240         assertEquals("Property 'readOnlyProperty' has no setter method in class '" + bean.getClass() + "'", e.getMessage());
2241     }
2242 
2243     /**
2244      * Test setSimpleProperty on a short property.
2245      */
2246     @Test
2247     public void testSetSimpleShort() throws Exception {
2248         final short oldValue = bean.getShortProperty();
2249         short newValue = oldValue;
2250         newValue++;
2251         PropertyUtils.setSimpleProperty(bean, "shortProperty", Short.valueOf(newValue));
2252         assertEquals(newValue, bean.getShortProperty(), "Matched new value");
2253     }
2254 
2255     /**
2256      * Test setSimpleProperty on a String property.
2257      */
2258     @Test
2259     public void testSetSimpleString() throws Exception {
2260         final String oldValue = bean.getStringProperty();
2261         final String newValue = oldValue + " Extra Value";
2262         PropertyUtils.setSimpleProperty(bean, "stringProperty", newValue);
2263         assertEquals(newValue, bean.getStringProperty(), "Matched new value");
2264     }
2265 
2266     /**
2267      * Test setSimpleProperty on an unknown property name.
2268      */
2269     @Test
2270     public void testSetSimpleUnknown() throws Exception {
2271         final String newValue = "New String Value";
2272         final NoSuchMethodException e = assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setSimpleProperty(bean, "unknown", newValue));
2273         assertEquals("Unknown property 'unknown' on class '" + bean.getClass() + "'", e.getMessage());
2274     }
2275 
2276     /**
2277      * Test setSimpleProperty on a write-only String property.
2278      */
2279     @Test
2280     public void testSetSimpleWriteOnly() throws Exception {
2281         final String oldValue = bean.getWriteOnlyPropertyValue();
2282         final String newValue = oldValue + " Extra Value";
2283         PropertyUtils.setSimpleProperty(bean, "writeOnlyProperty", newValue);
2284         assertEquals(newValue, bean.getWriteOnlyPropertyValue(), "Matched new value");
2285     }
2286 
2287     /**
2288      * When a bean has a null property which is reference by the standard access language, this should throw a NestedNullException.
2289      */
2290     @Test
2291     public void testThrowNestedNull() throws Exception {
2292         final NestedTestBean nestedBean = new NestedTestBean("base");
2293         // don't init!
2294         assertThrows(NestedNullException.class, () -> PropertyUtils.getProperty(nestedBean, "simpleBeanProperty.indexedProperty[0]"));
2295     }
2296 }