PropertyUtilsTest.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.beanutils2;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.beanutils2.priv.PrivateBeanFactory;
import org.apache.commons.beanutils2.priv.PrivateDirect;
import org.apache.commons.beanutils2.priv.PublicSubBean;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
 * <p>
 * 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
 * characteristics of that class.
 * </p>
 *
 * <p>
 * So far, this test case has tests for the following methods of the {@code PropertyUtils} class:
 * </p>
 * <ul>
 * <li>getIndexedProperty(Object,String)</li>
 * <li>getIndexedProperty(Object,String,int)</li>
 * <li>getMappedProperty(Object,String)</li>
 * <li>getMappedProperty(Object,String,String</li>
 * <li>getNestedProperty(Object,String)</li>
 * <li>getPropertyDescriptor(Object,String)</li>
 * <li>getPropertyDescriptors(Object)</li>
 * <li>getPropertyType(Object,String)</li>
 * <li>getSimpleProperty(Object,String)</li>
 * <li>setIndexedProperty(Object,String,Object)</li>
 * <li>setIndexedProperty(Object,String,String,Object)</li>
 * <li>setMappedProperty(Object,String,Object)</li>
 * <li>setMappedProperty(Object,String,String,Object)</li>
 * <li>setNestedProperty(Object,String,Object)</li>
 * <li>setSimpleProperty(Object,String,Object)</li>
 * </ul>
 */
class PropertyUtilsTest {

    /**
     * The fully qualified class name of our private directly implemented interface.
     */
    private static final String PRIVATE_DIRECT_CLASS = "org.apache.commons.beanutils2.priv.PrivateDirect";

    /**
     * The fully qualified class name of our private indirectly implemented interface.
     */
    private static final String PRIVATE_INDIRECT_CLASS = "org.apache.commons.beanutils2.priv.PrivateIndirect";

    /**
     * The fully qualified class name of our test bean class.
     */
    private static final String TEST_BEAN_CLASS = "org.apache.commons.beanutils2.TestBean";

    /**
     * The set of property names we expect to have returned when calling {@code getPropertyDescriptors()}. You should update this list when new properties are
     * added to TestBean.
     */
    protected static final String[] properties = { "booleanProperty", "booleanSecond", "doubleProperty", "dupProperty", "floatProperty", "intArray",
            "intIndexed", "intProperty", "listIndexed", "longProperty", "nested", "nullProperty", "readOnlyProperty", "shortProperty", "stringArray",
            "stringIndexed", "stringProperty", "writeOnlyProperty", };

    /**
     * Finds the descriptor of the name property.
     *
     * @param desc the array with descriptors
     * @return the found descriptor or null
     */
    private static PropertyDescriptor findNameDescriptor(final PropertyDescriptor[] desc) {
        for (final PropertyDescriptor element : desc) {
            if (element.getName().equals("name")) {
                return element;
            }
        }
        return null;
    }

    /**
     * The basic test bean for each test.
     */
    protected TestBean bean;

    /**
     * The "package private subclass" test bean for each test.
     */
    protected TestBeanPackageSubclass beanPackageSubclass;

    /**
     * The test bean for private access tests.
     */
    protected PrivateDirect beanPrivate;

    /**
     * The test bean for private access tests of subclasses.
     */
    protected PrivateDirect beanPrivateSubclass;

    /**
     * The "public subclass" test bean for each test.
     */
    protected TestBeanPublicSubclass beanPublicSubclass;

    /**
     * The set of properties that should be described.
     */
    protected String[] describes = { "booleanProperty", "booleanSecond", "doubleProperty", "floatProperty", "intArray",
            // "intIndexed",
            "intProperty", "listIndexed", "longProperty",
            // "mappedObjects",
            // "mappedProperty",
            // "mappedIntProperty",
            "nested", "nullProperty",
            // "readOnlyProperty",
            "shortProperty", "stringArray",
            // "stringIndexed",
            "stringProperty" };

    /**
     * Returns a single string containing all the keys in the map, sorted in alphabetical order and separated by ", ".
     * <p>
     * If there are no keys, an empty string is returned.
     */
    private String keysToString(final Map<String, ?> map) {
        return StringUtils.join(new TreeSet<>(map.keySet()), ", ");
    }

    /**
     * Sets up instance variables required by this test case.
     */
    @BeforeEach
    public void setUp() {

        bean = new TestBean();
        beanPackageSubclass = new TestBeanPackageSubclass();
        beanPrivate = PrivateBeanFactory.create();
        beanPrivateSubclass = PrivateBeanFactory.createSubclass();
        beanPublicSubclass = new TestBeanPublicSubclass();

        final DynaProperty[] properties = { new DynaProperty("stringProperty", String.class), new DynaProperty("nestedBean", TestBean.class),
                new DynaProperty("nullDynaBean", DynaBean.class) };
        final BasicDynaClass dynaClass = new BasicDynaClass("nestedDynaBean", BasicDynaBean.class, properties);
        final BasicDynaBean nestedDynaBean = new BasicDynaBean(dynaClass);
        nestedDynaBean.set("nestedBean", bean);
        bean.setNestedDynaBean(nestedDynaBean);
        PropertyUtils.clearDescriptors();
    }

    /**
     * Tear down instance variables required by this test case.
     */
    @AfterEach
    public void tearDown() {

        bean = null;
        beanPackageSubclass = null;
        beanPrivate = null;
        beanPrivateSubclass = null;
        beanPublicSubclass = null;

        PropertyUtils.resetBeanIntrospectors();
    }

    /**
     * Tries to add a null BeanIntrospector.
     */
    @Test
    void testAddBeanIntrospectorNull() {
        assertThrows(NullPointerException.class, () -> PropertyUtils.addBeanIntrospector(null));
    }

    /**
     * Test copyProperties() when the origin is a {@code Map}.
     */
    @Test
    void testCopyPropertiesMap() throws Exception {

        final Map<String, Object> map = new HashMap<>();
        map.put("booleanProperty", Boolean.FALSE);
        map.put("doubleProperty", Double.valueOf(333.0));
        map.put("dupProperty", new String[] { "New 0", "New 1", "New 2" });
        map.put("floatProperty", Float.valueOf((float) 222.0));
        map.put("intArray", new int[] { 0, 100, 200 });
        map.put("intProperty", Integer.valueOf(111));
        map.put("longProperty", Long.valueOf(444));
        map.put("shortProperty", Short.valueOf((short) 555));
        map.put("stringProperty", "New String Property");

        PropertyUtils.copyProperties(bean, map);

        // Scalar properties
        assertEquals(false, bean.getBooleanProperty(), "booleanProperty");
        assertEquals(333.0, bean.getDoubleProperty(), 0.005, "doubleProperty");
        assertEquals((float) 222.0, bean.getFloatProperty(), (float) 0.005, "floatProperty");
        assertEquals(111, bean.getIntProperty(), "intProperty");
        assertEquals(444, bean.getLongProperty(), "longProperty");
        assertEquals((short) 555, bean.getShortProperty(), "shortProperty");
        assertEquals("New String Property", bean.getStringProperty(), "stringProperty");

        // Indexed Properties
        final String[] dupProperty = bean.getDupProperty();
        assertNotNull(dupProperty, "dupProperty present");
        assertEquals(3, dupProperty.length, "dupProperty length");
        assertEquals("New 0", dupProperty[0], "dupProperty[0]");
        assertEquals("New 1", dupProperty[1], "dupProperty[1]");
        assertEquals("New 2", dupProperty[2], "dupProperty[2]");
        final int[] intArray = bean.getIntArray();
        assertNotNull(intArray, "intArray present");
        assertEquals(3, intArray.length, "intArray length");
        assertEquals(0, intArray[0], "intArray[0]");
        assertEquals(100, intArray[1], "intArray[1]");
        assertEquals(200, intArray[2], "intArray[2]");

    }

    /**
     * Tests whether the default introspection mechanism can be replaced by a custom BeanIntrospector.
     */
    @Test
    void testCustomIntrospection() {
        final PropertyDescriptor[] desc1 = PropertyUtils.getPropertyDescriptors(AlphaBean.class);
        PropertyDescriptor nameDescriptor = findNameDescriptor(desc1);
        assertNotNull(nameDescriptor.getWriteMethod(), "No write method");

        final BeanIntrospector bi = icontext -> {
            final Set<String> names = icontext.propertyNames();
            final PropertyDescriptor[] newDescs = new PropertyDescriptor[names.size()];
            int idx = 0;
            for (final Iterator<String> it = names.iterator(); it.hasNext(); idx++) {
                final String propName = it.next();
                final PropertyDescriptor pd = icontext.getPropertyDescriptor(propName);
                newDescs[idx] = new PropertyDescriptor(pd.getName(), pd.getReadMethod(), null);
            }
            icontext.addPropertyDescriptors(newDescs);
        };
        PropertyUtils.clearDescriptors();
        PropertyUtils.addBeanIntrospector(bi);
        final PropertyDescriptor[] desc2 = PropertyUtils.getPropertyDescriptors(AlphaBean.class);
        assertEquals(desc1.length, desc2.length, "Different number of properties");
        nameDescriptor = findNameDescriptor(desc2);
        assertNull(nameDescriptor.getWriteMethod(), "Got a write method");
        PropertyUtils.removeBeanIntrospector(bi);
    }

    /**
     * Tests whether exceptions during custom introspection are handled.
     */
    @Test
    void testCustomIntrospectionEx() {
        final BeanIntrospector bi = icontext -> {
            throw new IntrospectionException("TestException");
        };
        PropertyUtils.clearDescriptors();
        PropertyUtils.addBeanIntrospector(bi);
        final PropertyDescriptor[] desc = PropertyUtils.getPropertyDescriptors(AlphaBean.class);
        assertNotNull(findNameDescriptor(desc), "Introspection did not work");
        PropertyUtils.removeBeanIntrospector(bi);
    }

    /**
     * Test the describe() method.
     */
    @Test
    void testDescribe() throws Exception {

        final Map<String, Object> map = PropertyUtils.describe(bean);

        // Verify existence of all the properties that should be present
        for (final String describe : describes) {
            assertTrue(map.containsKey(describe), "Property '" + describe + "' is present");
        }
        assertFalse(map.containsKey("writeOnlyProperty"), "Property 'writeOnlyProperty' is not present");

        // Verify the values of scalar properties
        assertEquals(Boolean.TRUE, map.get("booleanProperty"), "Value of 'booleanProperty'");
        assertEquals(Double.valueOf(321.0), map.get("doubleProperty"), "Value of 'doubleProperty'");
        assertEquals(Float.valueOf((float) 123.0), map.get("floatProperty"), "Value of 'floatProperty'");
        assertEquals(Integer.valueOf(123), map.get("intProperty"), "Value of 'intProperty'");
        assertEquals(Long.valueOf(321), map.get("longProperty"), "Value of 'longProperty'");
        assertEquals(Short.valueOf((short) 987), map.get("shortProperty"), "Value of 'shortProperty'");
        assertEquals("This is a string", (String) map.get("stringProperty"), "Value of 'stringProperty'");

    }

    /**
     * Test {@link PropertyUtilsBean}'s invoke method throwing an IllegalArgumentException and check that the "cause" has been properly initialized for JDK 1.4+
     * See BEANUTILS-266 for changes and reason for test
     */
    @Test
    void testExceptionFromInvoke() throws Exception {
        try {
            PropertyUtils.setSimpleProperty(bean, "intProperty", "XXX");
        } catch (final IllegalArgumentException t) {
            final Throwable cause = (Throwable) PropertyUtils.getProperty(t, "cause");
            assertNotNull(cause, "Cause not found");
            assertInstanceOf(IllegalArgumentException.class, cause, "Expected cause to be IllegalArgumentException, but was: " + cause.getClass());
            // JDK 1.6 doesn't have "argument type mismatch" message
            // assertEquals("Check error message", "argument type mismatch", cause.getMessage());
        }
    }

    /**
     * Corner cases on getPropertyDescriptor invalid arguments.
     */
    @Test
    void testGetDescriptorArguments() {
        assertThrows(NullPointerException.class, () -> PropertyUtils.getPropertyDescriptor(null, "stringProperty"));
        assertThrows(NullPointerException.class, () -> PropertyUtils.getPropertyDescriptor(bean, null));
    }

    /**
     * Base for testGetDescriptorXxxxx() series of tests.
     *
     * @param name  Name of the property to be retrieved
     * @param read  Expected name of the read method (or null)
     * @param write Expected name of the write method (or null)
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private void testGetDescriptorBase(final String name, final String read, final String write)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        final PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(bean, name);
        if (read == null && write == null) {
            assertNull(pd, "Got descriptor");
            return;
        }
        assertNotNull(pd, "Got descriptor");
        final Method rm = pd.getReadMethod();
        if (read != null) {
            assertNotNull(rm, "Got read method");
            assertEquals(rm.getName(), read, "Got correct read method");
        } else {
            assertNull(rm, "Got read method");
        }
        final Method wm = pd.getWriteMethod();
        if (write != null) {
            assertNotNull(wm, "Got write method");
            assertEquals(wm.getName(), write, "Got correct write method");
        } else {
            assertNull(wm, "Got write method");
        }
    }

    /**
     * Positive getPropertyDescriptor on property {@code booleanProperty}.
     */
    @Test
    void testGetDescriptorBoolean() throws Exception {
        testGetDescriptorBase("booleanProperty", "getBooleanProperty", "setBooleanProperty");
    }

    /**
     * Positive getPropertyDescriptor on property {@code doubleProperty}.
     */
    @Test
    void testGetDescriptorDouble() throws Exception {
        testGetDescriptorBase("doubleProperty", "getDoubleProperty", "setDoubleProperty");
    }

    /**
     * Positive getPropertyDescriptor on property {@code floatProperty}.
     */
    @Test
    void testGetDescriptorFloat() throws Exception {
        testGetDescriptorBase("floatProperty", "getFloatProperty", "setFloatProperty");
    }

    /**
     * Positive getPropertyDescriptor on property {@code intProperty}.
     */
    @Test
    void testGetDescriptorInt() throws Exception {
        testGetDescriptorBase("intProperty", "getIntProperty", "setIntProperty");
    }

    /**
     * <p>
     * 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
     * a boolean setter.
     * </p>
     *
     * <p>
     * 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
     * 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
     * write-only property called <em>&lt;property-name&gt;</em>.
     * </p>
     */
    @Test
    void testGetDescriptorInvalidBoolean() throws Exception {

        final PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(bean, "invalidBoolean");
        assertNotNull(pd, "invalidBoolean is a property");
        assertNotNull(pd.getReadMethod(), "invalidBoolean has a getter method");
        assertNull(pd.getWriteMethod(), "invalidBoolean has no write method");
        assertTrue(Arrays.asList("isInvalidBoolean", "getInvalidBoolean").contains(pd.getReadMethod().getName()),
                "invalidBoolean getter method is isInvalidBoolean or getInvalidBoolean");
    }

    /**
     * Positive getPropertyDescriptor on property {@code longProperty}.
     */
    @Test
    void testGetDescriptorLong() throws Exception {
        testGetDescriptorBase("longProperty", "getLongProperty", "setLongProperty");
    }

    /**
     * Test getting mapped descriptor with periods in the key.
     */
    @Test
    void testGetDescriptorMappedPeriods() throws Exception {

        bean.getMappedIntProperty("xyz"); // initializes mappedIntProperty

        PropertyDescriptor desc;
        final Integer testIntegerValue = Integer.valueOf(1234);

        bean.setMappedIntProperty("key.with.a.dot", testIntegerValue.intValue());
        assertEquals(testIntegerValue, Integer.valueOf(bean.getMappedIntProperty("key.with.a.dot")), "Can retrieve directly");
        desc = PropertyUtils.getPropertyDescriptor(bean, "mappedIntProperty(key.with.a.dot)");
        assertEquals(Integer.TYPE, ((MappedPropertyDescriptor) desc).getMappedPropertyType(), "Check descriptor type (A)");

        bean.setMappedObjects("nested.property", new TestBean(testIntegerValue.intValue()));
        assertEquals(testIntegerValue, Integer.valueOf(((TestBean) bean.getMappedObjects("nested.property")).getIntProperty()), "Can retrieve directly");
        desc = PropertyUtils.getPropertyDescriptor(bean, "mappedObjects(nested.property).intProperty");
        assertEquals(Integer.TYPE, desc.getPropertyType(), "Check descriptor type (B)");
    }

    /**
     * Positive getPropertyDescriptor on property {@code readOnlyProperty}.
     */
    @Test
    void testGetDescriptorReadOnly() throws Exception {
        testGetDescriptorBase("readOnlyProperty", "getReadOnlyProperty", null);
    }

    /**
     * Positive test for getPropertyDescriptors(). Each property name listed in {@code properties} should be returned exactly once.
     */
    @Test
    void testGetDescriptors() {

        final PropertyDescriptor[] pd = PropertyUtils.getPropertyDescriptors(bean);
        assertNotNull(pd, "Got descriptors");
        final int[] count = new int[properties.length];
        for (final PropertyDescriptor element : pd) {
            final String name = element.getName();
            for (int j = 0; j < properties.length; j++) {
                if (name.equals(properties[j])) {
                    count[j]++;
                }
            }
        }
        for (int j = 0; j < properties.length; j++) {
            assertFalse(count[j] < 0, "Missing property " + properties[j]);
            assertFalse(count[j] > 1, "Missing property " + properties[j]);
        }

    }

    /**
     * Corner cases on getPropertyDescriptors invalid arguments.
     */
    @Test
    void testGetDescriptorsArguments() {
        assertThrows(NullPointerException.class, () -> PropertyUtils.getPropertyDescriptors(null));
    }

    /**
     * Positive getPropertyDescriptor on property {@code booleanSecond} that uses an "is" method as the getter.
     */
    @Test
    void testGetDescriptorSecond() throws Exception {
        testGetDescriptorBase("booleanSecond", "isBooleanSecond", "setBooleanSecond");
    }

    /**
     * Positive getPropertyDescriptor on property {@code shortProperty}.
     */
    @Test
    void testGetDescriptorShort() throws Exception {
        testGetDescriptorBase("shortProperty", "getShortProperty", "setShortProperty");
    }

    /**
     * Positive getPropertyDescriptor on property {@code stringProperty}.
     */
    @Test
    void testGetDescriptorString() throws Exception {
        testGetDescriptorBase("stringProperty", "getStringProperty", "setStringProperty");
    }

    /**
     * Negative getPropertyDescriptor on property {@code unknown}.
     */
    @Test
    void testGetDescriptorUnknown() throws Exception {
        testGetDescriptorBase("unknown", null, null);
    }

    /**
     * Positive getPropertyDescriptor on property {@code writeOnlyProperty}.
     */
    @Test
    void testGetDescriptorWriteOnly() throws Exception {
        testGetDescriptorBase("writeOnlyProperty", null, "setWriteOnlyProperty");
    }

    /**
     * Corner cases on getIndexedProperty invalid arguments.
     */
    @Test
    void testGetIndexedArguments() {
        // Use explicit index argument
        assertThrows(NullPointerException.class, () -> PropertyUtils.getIndexedProperty(null, "intArray", 0));
        assertThrows(NullPointerException.class, () -> PropertyUtils.getIndexedProperty(bean, null, 0));
        // Use index expression
        assertThrows(NullPointerException.class, () -> PropertyUtils.getIndexedProperty(null, "intArray[0]"));
        assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getIndexedProperty(bean, "[0]"));
        assertThrows(IllegalArgumentException.class, () -> PropertyUtils.getIndexedProperty(bean, "intArray"));
        // Use explicit index argument
        assertThrows(NullPointerException.class, () -> PropertyUtils.getIndexedProperty(null, "intIndexed", 0));
        assertThrows(NullPointerException.class, () -> PropertyUtils.getIndexedProperty(bean, null, 0));
        // Use index expression
        assertThrows(NullPointerException.class, () -> PropertyUtils.getIndexedProperty(null, "intIndexed[0]"));
        assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getIndexedProperty(bean, "[0]"));
        assertThrows(IllegalArgumentException.class, () -> PropertyUtils.getIndexedProperty(bean, "intIndexed"));
    }

    /**
     * Test getting an indexed value out of a multi-dimensional array
     */
    @Test
    void testGetIndexedArray() throws Exception {
        final String[] firstArray = { "FIRST-1", "FIRST-2", "FIRST-3" };
        final String[] secondArray = { "SECOND-1", "SECOND-2", "SECOND-3", "SECOND-4" };
        final String[][] mainArray = { firstArray, secondArray };
        final TestBean bean = new TestBean(mainArray);
        assertEquals(firstArray[0], PropertyUtils.getProperty(bean, "string2dArray[0][0]"), "firstArray[0]");
        assertEquals(firstArray[1], PropertyUtils.getProperty(bean, "string2dArray[0][1]"), "firstArray[1]");
        assertEquals(firstArray[2], PropertyUtils.getProperty(bean, "string2dArray[0][2]"), "firstArray[2]");
        assertEquals(secondArray[0], PropertyUtils.getProperty(bean, "string2dArray[1][0]"), "secondArray[0]");
        assertEquals(secondArray[1], PropertyUtils.getProperty(bean, "string2dArray[1][1]"), "secondArray[1]");
        assertEquals(secondArray[2], PropertyUtils.getProperty(bean, "string2dArray[1][2]"), "secondArray[2]");
        assertEquals(secondArray[3], PropertyUtils.getProperty(bean, "string2dArray[1][3]"), "secondArray[3]");
    }

    /**
     * Test getting an indexed value out of List of Lists
     */
    @Test
    void testGetIndexedList() throws Exception {
        final String[] firstArray = { "FIRST-1", "FIRST-2", "FIRST-3" };
        final String[] secondArray = { "SECOND-1", "SECOND-2", "SECOND-3", "SECOND-4" };
        final List<Object> mainList = new ArrayList<>();
        mainList.add(Arrays.asList(firstArray));
        mainList.add(Arrays.asList(secondArray));
        final TestBean bean = new TestBean(mainList);
        assertEquals(firstArray[0], PropertyUtils.getProperty(bean, "listIndexed[0][0]"), "firstArray[0]");
        assertEquals(firstArray[1], PropertyUtils.getProperty(bean, "listIndexed[0][1]"), "firstArray[1]");
        assertEquals(firstArray[2], PropertyUtils.getProperty(bean, "listIndexed[0][2]"), "firstArray[2]");
        assertEquals(secondArray[0], PropertyUtils.getProperty(bean, "listIndexed[1][0]"), "secondArray[0]");
        assertEquals(secondArray[1], PropertyUtils.getProperty(bean, "listIndexed[1][1]"), "secondArray[1]");
        assertEquals(secondArray[2], PropertyUtils.getProperty(bean, "listIndexed[1][2]"), "secondArray[2]");
        assertEquals(secondArray[3], PropertyUtils.getProperty(bean, "listIndexed[1][3]"), "secondArray[3]");
    }

    /**
     * Test getting a value out of a mapped Map
     */
    @Test
    void testGetIndexedMap() throws Exception {
        final Map<String, Object> firstMap = new HashMap<>();
        firstMap.put("FIRST-KEY-1", "FIRST-VALUE-1");
        firstMap.put("FIRST-KEY-2", "FIRST-VALUE-2");
        final Map<String, Object> secondMap = new HashMap<>();
        secondMap.put("SECOND-KEY-1", "SECOND-VALUE-1");
        secondMap.put("SECOND-KEY-2", "SECOND-VALUE-2");

        final List<Object> mainList = new ArrayList<>();
        mainList.add(firstMap);
        mainList.add(secondMap);
        final TestBean bean = new TestBean(mainList);
        assertEquals("FIRST-VALUE-1", PropertyUtils.getProperty(bean, "listIndexed[0](FIRST-KEY-1)"), "listIndexed[0](FIRST-KEY-1)");
        assertEquals("FIRST-VALUE-2", PropertyUtils.getProperty(bean, "listIndexed[0](FIRST-KEY-2)"), "listIndexed[0](FIRST-KEY-2)");
        assertEquals("SECOND-VALUE-1", PropertyUtils.getProperty(bean, "listIndexed[1](SECOND-KEY-1)"), "listIndexed[1](SECOND-KEY-1)");
        assertEquals("SECOND-VALUE-2", PropertyUtils.getProperty(bean, "listIndexed[1](SECOND-KEY-2)"), "listIndexed[1](SECOND-KEY-2)");
    }

    /**
     * Positive and negative tests on getIndexedProperty valid arguments.
     */
    @Test
    void testGetIndexedValues() throws Exception {
        Object value = null;
        // Use explicit key argument
        for (int i = 0; i < 5; i++) {
            value = PropertyUtils.getIndexedProperty(bean, "dupProperty", i);
            assertNotNull(value, "dupProperty returned value " + i);
            assertInstanceOf(String.class, value, "dupProperty returned String " + i);
            assertEquals("Dup " + i, (String) value, "dupProperty returned correct " + i);

            value = PropertyUtils.getIndexedProperty(bean, "intArray", i);
            assertNotNull(value, "intArray returned value " + i);
            assertInstanceOf(Integer.class, value, "intArray returned Integer " + i);
            assertEquals(i * 10, ((Integer) value).intValue(), "intArray returned correct " + i);

            value = PropertyUtils.getIndexedProperty(bean, "intIndexed", i);
            assertNotNull(value, "intIndexed returned value " + i);
            assertInstanceOf(Integer.class, value, "intIndexed returned Integer " + i);
            assertEquals(i * 10, ((Integer) value).intValue(), "intIndexed returned correct " + i);

            value = PropertyUtils.getIndexedProperty(bean, "listIndexed", i);
            assertNotNull(value, "listIndexed returned value " + i);
            assertInstanceOf(String.class, value, "list returned String " + i);
            assertEquals("String " + i, (String) value, "listIndexed returned correct " + i);

            value = PropertyUtils.getIndexedProperty(bean, "stringArray", i);
            assertNotNull(value, "stringArray returned value " + i);
            assertInstanceOf(String.class, value, "stringArray returned String " + i);
            assertEquals("String " + i, (String) value, "stringArray returned correct " + i);

            value = PropertyUtils.getIndexedProperty(bean, "stringIndexed", i);
            assertNotNull(value, "stringIndexed returned value " + i);
            assertInstanceOf(String.class, value, "stringIndexed returned String " + i);
            assertEquals("String " + i, (String) value, "stringIndexed returned correct " + i);

        }

        // Use key expression

        for (int i = 0; i < 5; i++) {

            value = PropertyUtils.getIndexedProperty(bean, "dupProperty[" + i + "]");
            assertNotNull(value, "dupProperty returned value " + i);
            assertInstanceOf(String.class, value, "dupProperty returned String " + i);
            assertEquals("Dup " + i, (String) value, "dupProperty returned correct " + i);

            value = PropertyUtils.getIndexedProperty(bean, "intArray[" + i + "]");
            assertNotNull(value, "intArray returned value " + i);
            assertInstanceOf(Integer.class, value, "intArray returned Integer " + i);
            assertEquals(i * 10, ((Integer) value).intValue(), "intArray returned correct " + i);

            value = PropertyUtils.getIndexedProperty(bean, "intIndexed[" + i + "]");
            assertNotNull(value, "intIndexed returned value " + i);
            assertInstanceOf(Integer.class, value, "intIndexed returned Integer " + i);
            assertEquals(i * 10, ((Integer) value).intValue(), "intIndexed returned correct " + i);

            value = PropertyUtils.getIndexedProperty(bean, "listIndexed[" + i + "]");
            assertNotNull(value, "listIndexed returned value " + i);
            assertInstanceOf(String.class, value, "listIndexed returned String " + i);
            assertEquals("String " + i, (String) value, "listIndexed returned correct " + i);

            value = PropertyUtils.getIndexedProperty(bean, "stringArray[" + i + "]");
            assertNotNull(value, "stringArray returned value " + i);
            assertInstanceOf(String.class, value, "stringArray returned String " + i);
            assertEquals("String " + i, (String) value, "stringArray returned correct " + i);

            value = PropertyUtils.getIndexedProperty(bean, "stringIndexed[" + i + "]");
            assertNotNull(value, "stringIndexed returned value " + i);
            assertInstanceOf(String.class, value, "stringIndexed returned String " + i);
            assertEquals("String " + i, (String) value, "stringIndexed returned correct " + i);

        }

        // Index out of bounds tests

        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "dupProperty", -1));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "dupProperty", 5));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "intArray", -1));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "intArray", 5));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "intIndexed", -1));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "intIndexed", 5));
        assertThrows(IndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "listIndexed", -1));
        assertThrows(IndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "listIndexed", 5));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "stringArray", -1));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "stringArray", 5));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "stringIndexed", -1));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.getIndexedProperty(bean, "stringIndexed", 5));
    }

    /**
     * Corner cases on getMappedProperty invalid arguments.
     */
    @Test
    void testGetMappedArguments() {
        // Use explicit key argument
        assertThrows(NullPointerException.class, () -> PropertyUtils.getMappedProperty(null, "mappedProperty", "First Key"));
        assertThrows(NullPointerException.class, () -> PropertyUtils.getMappedProperty(bean, null, "First Key"));
        assertThrows(NullPointerException.class, () -> PropertyUtils.getMappedProperty(bean, "mappedProperty", null));
        // Use key expression
        assertThrows(NullPointerException.class, () -> PropertyUtils.getMappedProperty(null, "mappedProperty(First Key)"));
        assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getMappedProperty(bean, "(Second Key)"));
        assertThrows(IllegalArgumentException.class, () -> PropertyUtils.getMappedProperty(bean, "mappedProperty"));
    }

    /**
     * Test getting an indexed value out of a mapped array
     */
    @Test
    void testGetMappedArray() throws Exception {
        final TestBean bean = new TestBean();
        final String[] array = { "abc", "def", "ghi" };
        bean.getMapProperty().put("mappedArray", array);
        assertEquals("abc", PropertyUtils.getProperty(bean, "mapProperty(mappedArray)[0]"));
        assertEquals("def", PropertyUtils.getProperty(bean, "mapProperty(mappedArray)[1]"));
        assertEquals("ghi", PropertyUtils.getProperty(bean, "mapProperty(mappedArray)[2]"));
    }

    /**
     * Test getting an indexed value out of a mapped List
     */
    @Test
    void testGetMappedList() throws Exception {
        final TestBean bean = new TestBean();
        final List<Object> list = new ArrayList<>();
        list.add("klm");
        list.add("nop");
        list.add("qrs");
        bean.getMapProperty().put("mappedList", list);
        assertEquals("klm", PropertyUtils.getProperty(bean, "mapProperty(mappedList)[0]"));
        assertEquals("nop", PropertyUtils.getProperty(bean, "mapProperty(mappedList)[1]"));
        assertEquals("qrs", PropertyUtils.getProperty(bean, "mapProperty(mappedList)[2]"));
    }

    /**
     * Test getting a value out of a mapped Map
     */
    @Test
    void testGetMappedMap() throws Exception {
        final TestBean bean = new TestBean();
        final Map<String, Object> map = new HashMap<>();
        map.put("sub-key-1", "sub-value-1");
        map.put("sub-key-2", "sub-value-2");
        map.put("sub-key-3", "sub-value-3");
        bean.getMapProperty().put("mappedMap", map);
        assertEquals("sub-value-1", PropertyUtils.getProperty(bean, "mapProperty(mappedMap)(sub-key-1)"));
        assertEquals("sub-value-2", PropertyUtils.getProperty(bean, "mapProperty(mappedMap)(sub-key-2)"));
        assertEquals("sub-value-3", PropertyUtils.getProperty(bean, "mapProperty(mappedMap)(sub-key-3)"));
    }

    /**
     * Test getting mapped values with periods in the key.
     */
    @Test
    void testGetMappedPeriods() throws Exception {

        bean.setMappedProperty("key.with.a.dot", "Special Value");
        assertEquals("Special Value", bean.getMappedProperty("key.with.a.dot"), "Can retrieve directly");
        assertEquals("Special Value", PropertyUtils.getMappedProperty(bean, "mappedProperty", "key.with.a.dot"), "Can retrieve via getMappedProperty");
        assertEquals("Special Value", PropertyUtils.getNestedProperty(bean, "mappedProperty(key.with.a.dot)"), "Can retrieve via getNestedProperty");

        bean.setMappedObjects("nested.property", new TestBean());
        assertNotNull(bean.getMappedObjects("nested.property"), "Can retrieve directly");
        assertEquals("This is a string", PropertyUtils.getNestedProperty(bean, "mappedObjects(nested.property).stringProperty"), "Can retrieve nested");

        assertEquals("Mapped Value", PropertyUtils.getNestedProperty(bean, "mappedNested.value(Mapped Key)"), "Can't retrieved nested with mapped property");
    }

    /**
     * Test getting mapped values with slashes in the key. This is different from periods because slashes are not syntactically significant.
     */
    @Test
    void testGetMappedSlashes() throws Exception {

        bean.setMappedProperty("key/with/a/slash", "Special Value");
        assertEquals("Special Value", bean.getMappedProperty("key/with/a/slash"), "Can retrieve directly");
        assertEquals("Special Value", PropertyUtils.getMappedProperty(bean, "mappedProperty", "key/with/a/slash"), "Can retrieve via getMappedProperty");
        assertEquals("Special Value", PropertyUtils.getNestedProperty(bean, "mappedProperty(key/with/a/slash)"), "Can retrieve via getNestedProperty");

        bean.setMappedObjects("nested/property", new TestBean());
        assertNotNull(bean.getMappedObjects("nested/property"), "Can retrieve directly");
        assertEquals("This is a string", PropertyUtils.getNestedProperty(bean, "mappedObjects(nested/property).stringProperty"), "Can retrieve nested");
    }

    /**
     * Positive and negative tests on getMappedProperty valid arguments.
     */
    @Test
    void testGetMappedValues() throws Exception {
        Object value = null;
        // Use explicit key argument
        value = PropertyUtils.getMappedProperty(bean, "mappedProperty", "First Key");
        assertEquals("First Value", value, "Can find first value");

        value = PropertyUtils.getMappedProperty(bean, "mappedProperty", "Second Key");
        assertEquals("Second Value", value, "Can find second value");

        value = PropertyUtils.getMappedProperty(bean, "mappedProperty", "Third Key");
        assertNull(value, "Can not find third value");

        // Use key expression with parentheses
        value = PropertyUtils.getMappedProperty(bean, "mappedProperty(First Key)");
        assertEquals("First Value", value, "Can find first value");

        value = PropertyUtils.getMappedProperty(bean, "mappedProperty(Second Key)");
        assertEquals("Second Value", value, "Can find second value");

        value = PropertyUtils.getMappedProperty(bean, "mappedProperty(Third Key)");
        assertNull(value, "Can not find third value");

        // Use key expression with dotted syntax
        value = PropertyUtils.getNestedProperty(bean, "mapProperty.First Key");
        assertEquals("First Value", value, "Can find first value");

        value = PropertyUtils.getNestedProperty(bean, "mapProperty.Second Key");
        assertEquals("Second Value", value, "Can find second value");

        value = PropertyUtils.getNestedProperty(bean, "mapProperty.Third Key");
        assertNull(value, "Can not find third value");
    }

    /**
     * Corner cases on getNestedProperty invalid arguments.
     */
    @Test
    void testGetNestedArguments() {
        assertThrows(NullPointerException.class, () -> PropertyUtils.getNestedProperty(null, "stringProperty"));
        assertThrows(NullPointerException.class, () -> PropertyUtils.getNestedProperty(bean, null));
    }

    /**
     * Test getNestedProperty on a boolean property.
     */
    @Test
    void testGetNestedBoolean() throws Exception {
        final Object value = PropertyUtils.getNestedProperty(bean, "nested.booleanProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(Boolean.class, value, "Got correct type");
        assertEquals(((Boolean) value).booleanValue(), bean.getNested().getBooleanProperty(), "Got correct value");
    }

    /**
     * Test getNestedProperty on a double property.
     */
    @Test
    void testGetNestedDouble() throws Exception {
        final Object value = PropertyUtils.getNestedProperty(bean, "nested.doubleProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(Double.class, value, "Got correct type");
        assertEquals(((Double) value).doubleValue(), bean.getNested().getDoubleProperty(), 0.005, "Got correct value");
    }

    /**
     * Test getNestedProperty on a float property.
     */
    @Test
    void testGetNestedFloat() throws Exception {
        final Object value = PropertyUtils.getNestedProperty(bean, "nested.floatProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(Float.class, value, "Got correct type");
        assertEquals(((Float) value).floatValue(), bean.getNested().getFloatProperty(), (float) 0.005, "Got correct value");
    }

    /**
     * Test getNestedProperty on an int property.
     */
    @Test
    void testGetNestedInt() throws Exception {
        final Object value = PropertyUtils.getNestedProperty(bean, "nested.intProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(Integer.class, value, "Got correct type");
        assertEquals(((Integer) value).intValue(), bean.getNested().getIntProperty(), "Got correct value");
    }

    /**
     * Test getNestedProperty on a long property.
     */
    @Test
    void testGetNestedLong() throws Exception {
        final Object value = PropertyUtils.getNestedProperty(bean, "nested.longProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(Long.class, value, "Got correct type");
        assertEquals(((Long) value).longValue(), bean.getNested().getLongProperty(), "Got correct value");
    }

    /**
     * Test getNestedProperty on a read-only String property.
     */
    @Test
    void testGetNestedReadOnly() throws Exception {
        final Object value = PropertyUtils.getNestedProperty(bean, "nested.readOnlyProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(String.class, value, "Got correct type");
        assertEquals((String) value, bean.getReadOnlyProperty(), "Got correct value");
    }

    /**
     * Test getNestedProperty on a short property.
     */
    @Test
    void testGetNestedShort() throws Exception {
        final Object value = PropertyUtils.getNestedProperty(bean, "nested.shortProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(Short.class, value, "Got correct type");
        assertEquals(((Short) value).shortValue(), bean.getNested().getShortProperty(), "Got correct value");
    }

    /**
     * Test getNestedProperty on a String property.
     */
    @Test
    void testGetNestedString() throws Exception {
        final Object value = PropertyUtils.getNestedProperty(bean, "nested.stringProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(String.class, value, "Got correct type");
        assertEquals((String) value, bean.getNested().getStringProperty(), "Got correct value");
    }

    /**
     * Negative test getNestedProperty on an unknown property.
     */
    @Test
    void testGetNestedUnknown() throws Exception {
        assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getNestedProperty(bean, "nested.unknown"));
    }

    /**
     * Test getNestedProperty on a write-only String property.
     */
    @Test
    void testGetNestedWriteOnly() throws Exception {
        assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getNestedProperty(bean, "writeOnlyProperty"));
    }

    /**
     * Test getPropertyType() on all kinds of properties.
     */
    @Test
    void testGetPropertyType() throws Exception {

        Class<?> clazz = null;
        final int[] intArray = {};
        final String[] stringArray = {};

        // Scalar and Indexed Properties
        clazz = PropertyUtils.getPropertyType(bean, "booleanProperty");
        assertEquals(Boolean.TYPE, clazz, "booleanProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "booleanSecond");
        assertEquals(Boolean.TYPE, clazz, "booleanSecond type");
        clazz = PropertyUtils.getPropertyType(bean, "doubleProperty");
        assertEquals(Double.TYPE, clazz, "doubleProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "dupProperty");
        assertEquals(String.class, clazz, "dupProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "floatProperty");
        assertEquals(Float.TYPE, clazz, "floatProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "intArray");
        assertEquals(intArray.getClass(), clazz, "intArray type");
        clazz = PropertyUtils.getPropertyType(bean, "intIndexed");
        assertEquals(Integer.TYPE, clazz, "intIndexed type");
        clazz = PropertyUtils.getPropertyType(bean, "intProperty");
        assertEquals(Integer.TYPE, clazz, "intProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "listIndexed");
        assertEquals(List.class, clazz, "listIndexed type");
        clazz = PropertyUtils.getPropertyType(bean, "longProperty");
        assertEquals(Long.TYPE, clazz, "longProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "mappedProperty");
        assertEquals(String.class, clazz, "mappedProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "mappedIntProperty");
        assertEquals(Integer.TYPE, clazz, "mappedIntProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "readOnlyProperty");
        assertEquals(String.class, clazz, "readOnlyProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "shortProperty");
        assertEquals(Short.TYPE, clazz, "shortProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "stringArray");
        assertEquals(stringArray.getClass(), clazz, "stringArray type");
        clazz = PropertyUtils.getPropertyType(bean, "stringIndexed");
        assertEquals(String.class, clazz, "stringIndexed type");
        clazz = PropertyUtils.getPropertyType(bean, "stringProperty");
        assertEquals(String.class, clazz, "stringProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "writeOnlyProperty");
        assertEquals(String.class, clazz, "writeOnlyProperty type");

        // Nested Properties
        clazz = PropertyUtils.getPropertyType(bean, "nested.booleanProperty");
        assertEquals(Boolean.TYPE, clazz, "booleanProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.booleanSecond");
        assertEquals(Boolean.TYPE, clazz, "booleanSecond type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.doubleProperty");
        assertEquals(Double.TYPE, clazz, "doubleProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.dupProperty");
        assertEquals(String.class, clazz, "dupProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.floatProperty");
        assertEquals(Float.TYPE, clazz, "floatProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.intArray");
        assertEquals(intArray.getClass(), clazz, "intArray type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.intIndexed");
        assertEquals(Integer.TYPE, clazz, "intIndexed type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.intProperty");
        assertEquals(Integer.TYPE, clazz, "intProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.listIndexed");
        assertEquals(List.class, clazz, "listIndexed type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.longProperty");
        assertEquals(Long.TYPE, clazz, "longProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.mappedProperty");
        assertEquals(String.class, clazz, "mappedProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.mappedIntProperty");
        assertEquals(Integer.TYPE, clazz, "mappedIntProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.readOnlyProperty");
        assertEquals(String.class, clazz, "readOnlyProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.shortProperty");
        assertEquals(Short.TYPE, clazz, "shortProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.stringArray");
        assertEquals(stringArray.getClass(), clazz, "stringArray type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.stringIndexed");
        assertEquals(String.class, clazz, "stringIndexed type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.stringProperty");
        assertEquals(String.class, clazz, "stringProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "nested.writeOnlyProperty");
        assertEquals(String.class, clazz, "writeOnlyProperty type");

        // Nested DynaBean
        clazz = PropertyUtils.getPropertyType(bean, "nestedDynaBean");
        assertEquals(DynaBean.class, clazz, "nestedDynaBean type");
        clazz = PropertyUtils.getPropertyType(bean, "nestedDynaBean.stringProperty");
        assertEquals(String.class, clazz, "nestedDynaBean.stringProperty type");
        clazz = PropertyUtils.getPropertyType(bean, "nestedDynaBean.nestedBean");
        assertEquals(TestBean.class, clazz, "nestedDynaBean.nestedBean type");
        clazz = PropertyUtils.getPropertyType(bean, "nestedDynaBean.nestedBean.nestedDynaBean");
        assertEquals(DynaBean.class, clazz, "nestedDynaBean.nestedBean.nestedDynaBean type");
        clazz = PropertyUtils.getPropertyType(bean, "nestedDynaBean.nestedBean.nestedDynaBean.stringProperty");
        assertEquals(String.class, clazz, "nestedDynaBean.nestedBean.nestedDynaBean.stringPropert type");

        // test Null
        clazz = PropertyUtils.getPropertyType(bean, "nestedDynaBean.nullDynaBean");
        assertEquals(DynaBean.class, clazz, "nestedDynaBean.nullDynaBean type");
        assertThrows(NestedNullException.class, () -> PropertyUtils.getPropertyType(bean, "nestedDynaBean.nullDynaBean.foo"));
    }

    /**
     * Test accessing a public sub-bean of a package scope bean
     */
    @Test
    void testGetPublicSubBean_of_PackageBean() throws Exception {

        final PublicSubBean bean = new PublicSubBean();
        bean.setFoo("foo-start");
        bean.setBar("bar-start");
        Object result = null;

        // Get Foo
        result = PropertyUtils.getProperty(bean, "foo");
        assertEquals("foo-start", result, "foo property");

        // Get Bar
        result = PropertyUtils.getProperty(bean, "bar");
        assertEquals("bar-start", result, "bar property");
    }

    /**
     * Base for testGetReadMethod() series of tests.
     *
     * @param bean       Bean for which to retrieve read methods.
     * @param properties Property names to search for
     * @param className  Class name where this method should be defined
     * @throws InvocationTargetException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     */
    private void testGetReadMethod(final Object bean, final String[] properties, final String className)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

        final PropertyDescriptor[] pd = PropertyUtils.getPropertyDescriptors(bean);
        for (final String propertie : properties) {

            // Identify the property descriptor for this property
            switch (propertie) {
            case "intIndexed":
                continue;
            case "stringIndexed":
                continue;
            case "writeOnlyProperty":
                continue;
            default:
                break;
            }
            int n = -1;
            for (int j = 0; j < pd.length; j++) {
                if (propertie.equals(pd[j].getName())) {
                    n = j;
                    break;
                }
            }
            assertTrue(n >= 0, "PropertyDescriptor for " + propertie);

            // Locate an accessible property reader method for it
            final Method reader = PropertyUtils.getReadMethod(pd[n]);
            assertNotNull(reader, "Reader for " + propertie);
            final Class<?> clazz = reader.getDeclaringClass();
            assertNotNull(clazz, "Declaring class for " + propertie);
            assertEquals(clazz.getName(), className, "Correct declaring class for " + propertie);

            // Actually call the reader method we received
            reader.invoke(bean, (Object[]) new Class<?>[0]);
        }

    }

    /**
     * Test getting accessible property reader methods for a specified list of properties of our standard test bean.
     */
    @Test
    void testGetReadMethodBasic() throws Exception {
        testGetReadMethod(bean, properties, TEST_BEAN_CLASS);

    }

    /**
     * Test getting accessible property reader methods for a specified list of properties of a package private subclass of our standard test bean.
     */
    @Test
    void testGetReadMethodPackageSubclass() throws Exception {
        testGetReadMethod(beanPackageSubclass, properties, TEST_BEAN_CLASS);

    }

    /**
     * Test getting accessible property reader methods for a specified list of properties that are declared either directly or via implemented interfaces.
     */
    @Test
    void testGetReadMethodPublicInterface() throws Exception {

        // Properties "bar" and "baz" are visible via implemented interfaces
        // (one direct and one indirect)
        testGetReadMethod(beanPrivate, new String[] { "bar" }, PRIVATE_DIRECT_CLASS);
        testGetReadMethod(beanPrivate, new String[] { "baz" }, PRIVATE_INDIRECT_CLASS);

        // Properties "bar" and "baz" are visible via implemented interfaces
        // (one direct and one indirect). The interface is implemented in
        // a superclass
        testGetReadMethod(beanPrivateSubclass, new String[] { "bar" }, PRIVATE_DIRECT_CLASS);
        testGetReadMethod(beanPrivateSubclass, new String[] { "baz" }, PRIVATE_INDIRECT_CLASS);

        // Property "foo" is not accessible because the underlying
        // class has package scope
        final PropertyDescriptor[] pd = PropertyUtils.getPropertyDescriptors(beanPrivate);
        int n = -1;
        for (int i = 0; i < pd.length; i++) {
            if ("foo".equals(pd[i].getName())) {
                n = i;
                break;
            }
        }
        assertTrue(n >= 0, "Found foo descriptor");
        final Method reader = pd[n].getReadMethod();
        assertNotNull(reader, "Found foo read method");
        assertThrows(IllegalAccessException.class, () -> reader.invoke(beanPrivate, (Object[]) new Class<?>[0]));
    }

    /**
     * Test getting accessible property reader methods for a specified list of properties of a public subclass of our standard test bean.
     */
    @Test
    void testGetReadMethodPublicSubclass() throws Exception {
        testGetReadMethod(beanPublicSubclass, properties, TEST_BEAN_CLASS);
    }

    /** Text case for setting properties on inner classes */
    @Test
    void testGetSetInnerBean() throws Exception {
        final BeanWithInnerBean bean = new BeanWithInnerBean();

        PropertyUtils.setProperty(bean, "innerBean.fish(loiterTimer)", "5");
        String out = (String) PropertyUtils.getProperty(bean.getInnerBean(), "fish(loiterTimer)");
        assertEquals("5", out, "(1) Inner class property set/get property failed.");

        out = (String) PropertyUtils.getProperty(bean, "innerBean.fish(loiterTimer)");

        assertEquals("5", out, "(2) Inner class property set/get property failed.");
    }

    /** Text case for setting properties on parent */
    @Test
    void testGetSetParentBean() throws Exception {

        final SonOfAlphaBean bean = new SonOfAlphaBean("Roger");

        final String out = (String) PropertyUtils.getProperty(bean, "name");
        assertEquals("Roger", out, "(1) Get/Set On Parent.");

        PropertyUtils.setProperty(bean, "name", "abcd");
        assertEquals("abcd", bean.getName(), "(2) Get/Set On Parent.");
    }

    /**
     * Corner cases on getSimpleProperty invalid arguments.
     */
    @Test
    void testGetSimpleArguments() {
        assertThrows(NullPointerException.class, () -> PropertyUtils.getSimpleProperty(null, "stringProperty"));
        assertThrows(NullPointerException.class, () -> PropertyUtils.getSimpleProperty(bean, null));
    }

    /**
     * Test getSimpleProperty on a boolean property.
     */
    @Test
    void testGetSimpleBoolean() throws Exception {
        final Object value = PropertyUtils.getSimpleProperty(bean, "booleanProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(Boolean.class, value, "Got correct type");
        assertEquals(((Boolean) value).booleanValue(), bean.getBooleanProperty(), "Got correct value");
    }

    /**
     * Test getSimpleProperty on a double property.
     */
    @Test
    void testGetSimpleDouble() throws Exception {
        final Object value = PropertyUtils.getSimpleProperty(bean, "doubleProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(Double.class, value, "Got correct type");
        assertEquals(((Double) value).doubleValue(), bean.getDoubleProperty(), 0.005, "Got correct value");
    }

    /**
     * Test getSimpleProperty on a float property.
     */
    @Test
    void testGetSimpleFloat() throws Exception {
        final Object value = PropertyUtils.getSimpleProperty(bean, "floatProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(Float.class, value, "Got correct type");
        assertEquals(((Float) value).floatValue(), bean.getFloatProperty(), (float) 0.005, "Got correct value");
    }

    /**
     * Negative test getSimpleProperty on an indexed property.
     */
    @Test
    void testGetSimpleIndexed() throws Exception {
        assertThrows(IllegalArgumentException.class, () -> PropertyUtils.getSimpleProperty(bean, "intIndexed[0]"));
    }

    /**
     * Test getSimpleProperty on an int property.
     */
    @Test
    void testGetSimpleInt() throws Exception {
        final Object value = PropertyUtils.getSimpleProperty(bean, "intProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(Integer.class, value, "Got correct type");
        assertEquals(((Integer) value).intValue(), bean.getIntProperty(), "Got correct value");
    }

    /**
     * Test getSimpleProperty on a long property.
     */
    @Test
    void testGetSimpleLong() throws Exception {
        final Object value = PropertyUtils.getSimpleProperty(bean, "longProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(Long.class, value, "Got correct type");
        assertEquals(((Long) value).longValue(), bean.getLongProperty(), "Got correct value");
    }

    /**
     * Negative test getSimpleProperty on a nested property.
     */
    @Test
    void testGetSimpleNested() throws Exception {
        assertThrows(IllegalArgumentException.class, () -> PropertyUtils.getSimpleProperty(bean, "nested.stringProperty"));
    }

    /**
     * Test getSimpleProperty on a read-only String property.
     */
    @Test
    void testGetSimpleReadOnly() throws Exception {
        final Object value = PropertyUtils.getSimpleProperty(bean, "readOnlyProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(String.class, value, "Got correct type");
        assertEquals((String) value, bean.getReadOnlyProperty(), "Got correct value");
    }

    /**
     * Test getSimpleProperty on a short property.
     */
    @Test
    void testGetSimpleShort() throws Exception {
        final Object value = PropertyUtils.getSimpleProperty(bean, "shortProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(Short.class, value, "Got correct type");
        assertEquals(((Short) value).shortValue(), bean.getShortProperty(), "Got correct value");
    }

    /**
     * Test getSimpleProperty on a String property.
     */
    @Test
    void testGetSimpleString() throws Exception {
        final Object value = PropertyUtils.getSimpleProperty(bean, "stringProperty");
        assertNotNull(value, "Got a value");
        assertInstanceOf(String.class, value, "Got correct type");
        assertEquals((String) value, bean.getStringProperty(), "Got correct value");
    }

    /**
     * Negative test getSimpleProperty on an unknown property.
     */
    @Test
    void testGetSimpleUnknown() throws Exception {
        assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getSimpleProperty(bean, "unknown"));
    }

    /**
     * Test getSimpleProperty on a write-only String property.
     */
    @Test
    void testGetSimpleWriteOnly() throws Exception {
        assertThrows(NoSuchMethodException.class, () -> PropertyUtils.getSimpleProperty(bean, "writeOnlyProperty"));

    }

    /**
     * Base for testGetWriteMethod() series of tests.
     *
     * @param bean       Bean for which to retrieve write methods.
     * @param properties Property names to search for
     * @param className  Class name where this method should be defined
     */
    private void testGetWriteMethod(final Object bean, final String[] properties, final String className) {

        final PropertyDescriptor[] pd = PropertyUtils.getPropertyDescriptors(bean);
        for (final String property : properties) {

            // Identify the property descriptor for this property
            switch (property) {
            case "intIndexed":
                continue;
            case "listIndexed":
                continue;
            case "nested":
                continue; // This property is read only
            case "readOnlyProperty":
                continue;
            case "stringIndexed":
                continue;
            default:
                break;
            }
            int n = -1;
            for (int j = 0; j < pd.length; j++) {
                if (property.equals(pd[j].getName())) {
                    n = j;
                    break;
                }
            }
            assertTrue(n >= 0, "PropertyDescriptor for " + property);

            // Locate an accessible property reader method for it
            final Method writer = PropertyUtils.getWriteMethod(pd[n]);
            assertNotNull(writer, "Writer for " + property);
            final Class<?> clazz = writer.getDeclaringClass();
            assertNotNull(clazz, "Declaring class for " + property);
            assertEquals(clazz.getName(), className, "Correct declaring class for " + property);

        }

    }

    /**
     * Test getting accessible property writer methods for a specified list of properties of our standard test bean.
     */
    @Test
    void testGetWriteMethodBasic() {
        testGetWriteMethod(bean, properties, TEST_BEAN_CLASS);
    }

    /**
     * Test getting accessible property writer methods for a specified list of properties of a package private subclass of our standard test bean.
     */
    @Test
    void testGetWriteMethodPackageSubclass() {
        testGetWriteMethod(beanPackageSubclass, properties, TEST_BEAN_CLASS);
    }

    /**
     * Test getting accessible property writer methods for a specified list of properties of a public subclass of our standard test bean.
     */
    @Test
    void testGetWriteMethodPublicSubclass() {
        testGetWriteMethod(beanPublicSubclass, properties, TEST_BEAN_CLASS);
    }

    /**
     * Test isReadable() method.
     */
    @Test
    void testIsReadable() throws Exception {
        String property = null;
        property = "stringProperty";
        assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
        property = "stringIndexed";
        assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
        property = "mappedProperty";
        assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
        property = "nestedDynaBean";
        assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
        property = "nestedDynaBean.stringProperty";
        assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
        property = "nestedDynaBean.nestedBean";
        assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
        property = "nestedDynaBean.nestedBean.nestedDynaBean";
        assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
        property = "nestedDynaBean.nestedBean.nestedDynaBean.stringProperty";
        assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");
        property = "nestedDynaBean.nullDynaBean";
        assertTrue(PropertyUtils.isReadable(bean, property), "Property " + property + " isReadable expected TRUE");

        assertThrows(NestedNullException.class, () -> PropertyUtils.isReadable(bean, "nestedDynaBean.nullDynaBean.foo"));
    }

    /**
     * Test isWriteable() method.
     */
    @Test
    void testIsWriteable() throws Exception {
        String property = null;
        property = "stringProperty";
        assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
        property = "stringIndexed";
        assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
        property = "mappedProperty";
        assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
        property = "nestedDynaBean";
        assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
        property = "nestedDynaBean.stringProperty";
        assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
        property = "nestedDynaBean.nestedBean";
        assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
        property = "nestedDynaBean.nestedBean.nestedDynaBean";
        assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
        property = "nestedDynaBean.nestedBean.nestedDynaBean.stringProperty";
        assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
        property = "nestedDynaBean.nullDynaBean";
        assertTrue(PropertyUtils.isWriteable(bean, property), "Property " + property + " isWriteable expected TRUE");
        assertThrows(NestedNullException.class, () -> PropertyUtils.isWriteable(bean, "nestedDynaBean.nullDynaBean.foo"));
    }

    /**
     * This tests to see that it is possible to subclass PropertyUtilsBean and change the behavior of setNestedProperty/getNestedProperty when dealing with
     * objects that implement Map.
     */
    @Test
    void testMapExtensionCustom() throws Exception {
        final PropsFirstPropertyUtilsBean utilsBean = new PropsFirstPropertyUtilsBean();
        final ExtendMapBean bean = new ExtendMapBean();

        // hardly worth testing this, really :-)
        bean.setUnusuallyNamedProperty("bean value");
        assertEquals("bean value", bean.getUnusuallyNamedProperty(), "Set property direct failed");

        // setSimpleProperty should affect the simple property
        utilsBean.setSimpleProperty(bean, "unusuallyNamedProperty", "new value");
        assertEquals("new value", bean.getUnusuallyNamedProperty(), "Set property on map failed (1)");

        // setNestedProperty with setter should affect the simple property
        // getNestedProperty with getter should obtain the simple property
        utilsBean.setProperty(bean, "unusuallyNamedProperty", "next value");
        assertEquals("next value", bean.getUnusuallyNamedProperty(), "Set property on map failed (2)");
        assertEquals("next value", utilsBean.getNestedProperty(bean, "unusuallyNamedProperty"), "setNestedProperty on non-simple property failed");

        // setting property without setter should update the map
        // getting property without setter should fetch from the map
        utilsBean.setProperty(bean, "mapProperty", "value1");
        assertEquals("value1", utilsBean.getNestedProperty(bean, "mapProperty"), "setNestedProperty on non-simple property failed");

        final HashMap<String, Object> myMap = new HashMap<>();
        myMap.put("thebean", bean);
        utilsBean.getNestedProperty(myMap, "thebean.mapitem");
        utilsBean.getNestedProperty(myMap, "thebean(mapitem)");
    }

    /**
     * This tests to see that classes that implement Map always have their custom properties ignored.
     * <p>
     * Note that this behavior has changed several times over past releases of beanutils, breaking backwards compatibility each time. Here's hoping that the
     * current 1.7.1 release is the last time this behavior changes!
     */
    @Test
    void testMapExtensionDefault() throws Exception {
        final ExtendMapBean bean = new ExtendMapBean();

        // setting property direct should work, and not affect map
        bean.setUnusuallyNamedProperty("bean value");
        assertEquals("bean value", bean.getUnusuallyNamedProperty(), "Set property direct failed");
        assertNull(PropertyUtils.getNestedProperty(bean, "unusuallyNamedProperty"), "Get on unset map property failed");

        // setting simple property should call the setter method only, and not
        // affect the map.
        PropertyUtils.setSimpleProperty(bean, "unusuallyNamedProperty", "new value");
        assertEquals("new value", bean.getUnusuallyNamedProperty(), "Set property on map failed (1)");
        assertNull(PropertyUtils.getNestedProperty(bean, "unusuallyNamedProperty"), "Get on unset map property failed");

        // setting via setNestedProperty should affect the map only, and not
        // call the setter method.
        PropertyUtils.setProperty(bean, "unusuallyNamedProperty", "next value");
        assertEquals("next value", PropertyUtils.getNestedProperty(bean, "unusuallyNamedProperty"),
                "setNestedProperty on map not visible to getNestedProperty");
        assertEquals("new value", bean.getUnusuallyNamedProperty(), "Set nested property on map unexpected affected simple property");
    }

    /**
     * Test the mappedPropertyType of MappedPropertyDescriptor.
     */
    @Test
    void testMappedPropertyType() throws Exception {

        MappedPropertyDescriptor desc;

        // Check a String property
        desc = (MappedPropertyDescriptor) PropertyUtils.getPropertyDescriptor(bean, "mappedProperty");
        assertEquals(String.class, desc.getMappedPropertyType());

        // Check an int property
        desc = (MappedPropertyDescriptor) PropertyUtils.getPropertyDescriptor(bean, "mappedIntProperty");
        assertEquals(Integer.TYPE, desc.getMappedPropertyType());

    }

    /**
     * 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
     * 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
     * when a user does this.
     * <p>
     * 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
     * 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
     * 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
     * calling get(fieldName) or put(fieldName), neither of which can be further indexed with a string or an integer.
     * <p>
     * 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
     * typecasting, or for "a(b)" to be equivalent to map.get("a").get("b").
     * <p>
     * Here we verify that an exception is thrown if the user makes this mistake.
     */
    @Test
    void testNestedPropertyKeyOrIndexOnBeanImplementingMap() throws Exception {
        final HashMap<String, Object> map = new HashMap<>();
        final HashMap<String, Object> submap = new HashMap<>();
        final BetaBean betaBean1 = new BetaBean("test1");
        final BetaBean betaBean2 = new BetaBean("test2");

        // map.put("submap", submap)
        PropertyUtils.setNestedProperty(map, "submap", submap);

        // map.get("submap").put("beta1", betaBean1)
        PropertyUtils.setNestedProperty(map, "submap.beta1", betaBean1);
        assertEquals("submap", keysToString(map), "Unexpected keys in map");
        assertEquals("beta1", keysToString(submap), "Unexpected keys in submap");

        // One would expect that the command below would be equivalent to
        // Map m = (Map) map.get("submap");
        // m.put("beta2", betaBean2)
        // However this isn't how javabeans property methods work. A map
        // only effectively has "simple" properties, even when the
        // returned object is a Map or Array.
        final IllegalArgumentException e1 = assertThrows(IllegalArgumentException.class,
                () -> PropertyUtils.setNestedProperty(map, "submap(beta2)", betaBean2));
        // What, no exception? In that case, setNestedProperties has
        // probably just tried to do
        // map.set("submap(beta2)", betaBean2)
        // which is almost certainly not what the used expected. This is
        // what BeanUtils 1.5.0 to 1.7.1 did....
        // ok, getting an exception was expected. As it is of a generic
        // type, let's check the message string to make sure it really
        // was caused by the issue we expected.
        assertTrue(e1.getMessage().contains("Indexed or mapped properties are not supported"), "Unexpected exception message");

        // One would expect that "submap[3]" would be equivalent to
        // Object[] objects = (Object[]) map.get("submap");
        // return objects[3];
        // However this isn't how javabeans property methods work. A map
        // only effectively has "simple" properties, even when the
        // returned object is a Map or Array.
        final IllegalArgumentException e2 = assertThrows(IllegalArgumentException.class, () -> PropertyUtils.getNestedProperty(map, "submap[3]"));
        // What, no exception? In that case, getNestedProperties has
        // probably just tried to do
        // map.get("submap[3]")
        // which is almost certainly not what the used expected. This is
        // what BeanUtils 1.5.0 to 1.7.1 did....
        // ok, getting an exception was expected. As it is of a generic
        // type, let's check the message string to make sure it really
        // was caused by the issue we expected.
        final int index = e2.getMessage().indexOf("Indexed or mapped properties are not supported");
        assertTrue(index >= 0, "Unexpected exception message");
    }

    @Test
    void testNestedWithIndex() throws Exception {
        final NestedTestBean nestedBean = new NestedTestBean("base");
        nestedBean.init();
        nestedBean.getSimpleBeanProperty().init();

        NestedTestBean

        // test first calling properties on indexed beans

        value = (NestedTestBean) PropertyUtils.getProperty(nestedBean, "indexedProperty[0]");
        assertEquals("Bean@0", value.getName(), "Cannot get simple index(1)");
        assertEquals("NOT SET", value.getTestString(), "Bug in NestedTestBean");

        value = (NestedTestBean) PropertyUtils.getProperty(nestedBean, "indexedProperty[1]");
        assertEquals("Bean@1", value.getName(), "Cannot get simple index(1)");
        assertEquals("NOT SET", value.getTestString(), "Bug in NestedTestBean");

        String prop = (String) PropertyUtils.getProperty(nestedBean, "indexedProperty[0].testString");
        assertEquals("NOT SET", prop, "Get property on indexes failed (1)");

        prop = (String) PropertyUtils.getProperty(nestedBean, "indexedProperty[1].testString");
        assertEquals("NOT SET", prop, "Get property on indexes failed (2)");

        PropertyUtils.setProperty(nestedBean, "indexedProperty[0].testString", "Test#1");
        assertEquals("Test#1", nestedBean.getIndexedProperty(0).getTestString(), "Cannot set property on indexed bean (1)");

        PropertyUtils.setProperty(nestedBean, "indexedProperty[1].testString", "Test#2");
        assertEquals("Test#2", nestedBean.getIndexedProperty(1).getTestString(), "Cannot set property on indexed bean (2)");

        // test first calling indexed properties on a simple property

        value = (NestedTestBean) PropertyUtils.getProperty(nestedBean, "simpleBeanProperty");
        assertEquals("Simple Property Bean", value.getName(), "Cannot get simple bean");
        assertEquals("NOT SET", value.getTestString(), "Bug in NestedTestBean");

        value = (NestedTestBean) PropertyUtils.getProperty(nestedBean, "simpleBeanProperty.indexedProperty[3]");
        assertEquals("Bean@3", value.getName(), "Cannot get index property on property");
        assertEquals("NOT SET", value.getTestString(), "Bug in NestedTestBean");

        PropertyUtils.setProperty(nestedBean, "simpleBeanProperty.indexedProperty[3].testString", "Test#3");
        assertEquals("Test#3", nestedBean.getSimpleBeanProperty().getIndexedProperty(3).getTestString(), "Cannot set property on indexed property on property");
    }

    /**
     * Tests whether a BeanIntrospector can be removed.
     */
    @Test
    void testRemoveBeanIntrospector() {
        assertTrue(PropertyUtils.removeBeanIntrospector(DefaultBeanIntrospector.INSTANCE), "Wrong result");
        final PropertyDescriptor[] desc = PropertyUtils.getPropertyDescriptors(AlphaBean.class);
        assertEquals(0, desc.length, "Got descriptors");
        PropertyUtils.addBeanIntrospector(DefaultBeanIntrospector.INSTANCE);
    }

    /**
     * Tests whether a reset of the registered BeanIntrospectors can be performed.
     */
    @Test
    void testResetBeanIntrospectors() {
        assertTrue(PropertyUtils.removeBeanIntrospector(DefaultBeanIntrospector.INSTANCE), "Wrong result");
        PropertyUtils.resetBeanIntrospectors();
        final PropertyDescriptor[] desc = PropertyUtils.getPropertyDescriptors(AlphaBean.class);
        assertTrue(desc.length > 0, "Got no descriptors");
    }

    /**
     * Corner cases on setIndexedProperty invalid arguments.
     */
    @Test
    void testSetIndexedArguments() {
        // Use explicit index argument
        assertThrows(NullPointerException.class, () -> PropertyUtils.setIndexedProperty(null, "intArray", 0, Integer.valueOf(1)));
        assertThrows(NullPointerException.class, () -> PropertyUtils.setIndexedProperty(bean, null, 0, Integer.valueOf(1)));
        // Use index expression
        assertThrows(NullPointerException.class, () -> PropertyUtils.setIndexedProperty(null, "intArray[0]", Integer.valueOf(1)));
        assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setIndexedProperty(bean, "[0]", Integer.valueOf(1)));
        assertThrows(IllegalArgumentException.class, () -> PropertyUtils.setIndexedProperty(bean, "intArray", Integer.valueOf(1)));
        // Use explicit index argument
        assertThrows(NullPointerException.class, () -> PropertyUtils.setIndexedProperty(null, "intIndexed", 0, Integer.valueOf(1)));
        assertThrows(NullPointerException.class, () -> PropertyUtils.setIndexedProperty(bean, null, 0, Integer.valueOf(1)));
        // Use index expression
        assertThrows(NullPointerException.class, () -> PropertyUtils.setIndexedProperty(null, "intIndexed[0]", Integer.valueOf(1)));
        assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setIndexedProperty(bean, "[0]", Integer.valueOf(1)));
        assertThrows(IllegalArgumentException.class, () -> PropertyUtils.setIndexedProperty(bean, "intIndexed", Integer.valueOf(1)));
    }

    /**
     * Test setting an indexed value out of a multi-dimensional array
     */
    @Test
    void testSetIndexedArray() throws Exception {
        final String[] firstArray = { "FIRST-1", "FIRST-2", "FIRST-3" };
        final String[] secondArray = { "SECOND-1", "SECOND-2", "SECOND-3", "SECOND-4" };
        final String[][] mainArray = { firstArray, secondArray };
        final TestBean bean = new TestBean(mainArray);
        assertEquals("SECOND-3", bean.getString2dArray(1)[2], "BEFORE");
        PropertyUtils.setProperty(bean, "string2dArray[1][2]", "SECOND-3-UPDATED");
        assertEquals("SECOND-3-UPDATED", bean.getString2dArray(1)[2], "AFTER");
    }

    /**
     * Test setting an indexed value out of List of Lists
     */
    @Test
    void testSetIndexedList() throws Exception {
        final String[] firstArray = { "FIRST-1", "FIRST-2", "FIRST-3" };
        final String[] secondArray = { "SECOND-1", "SECOND-2", "SECOND-3", "SECOND-4" };
        final List<Object> mainList = new ArrayList<>();
        mainList.add(Arrays.asList(firstArray));
        mainList.add(Arrays.asList(secondArray));
        final TestBean bean = new TestBean(mainList);
        assertEquals("SECOND-4", ((List<?>) bean.getListIndexed().get(1)).get(3), "BEFORE");
        PropertyUtils.setProperty(bean, "listIndexed[1][3]", "SECOND-4-UPDATED");
        assertEquals("SECOND-4-UPDATED", ((List<?>) bean.getListIndexed().get(1)).get(3), "AFTER");
    }

    /**
     * Test setting a value out of a mapped Map
     */
    @Test
    void testSetIndexedMap() throws Exception {
        final Map<String, Object> firstMap = new HashMap<>();
        firstMap.put("FIRST-KEY-1", "FIRST-VALUE-1");
        firstMap.put("FIRST-KEY-2", "FIRST-VALUE-2");
        final Map<String, Object> secondMap = new HashMap<>();
        secondMap.put("SECOND-KEY-1", "SECOND-VALUE-1");
        secondMap.put("SECOND-KEY-2", "SECOND-VALUE-2");

        final List<Object> mainList = new ArrayList<>();
        mainList.add(firstMap);
        mainList.add(secondMap);
        final TestBean bean = new TestBean(mainList);

        assertEquals(null, ((Map<?, ?>) bean.getListIndexed().get(0)).get("FIRST-NEW-KEY"), "BEFORE");
        assertEquals("SECOND-VALUE-1", ((Map<?, ?>) bean.getListIndexed().get(1)).get("SECOND-KEY-1"), "BEFORE");
        PropertyUtils.setProperty(bean, "listIndexed[0](FIRST-NEW-KEY)", "FIRST-NEW-VALUE");
        PropertyUtils.setProperty(bean, "listIndexed[1](SECOND-KEY-1)", "SECOND-VALUE-1-UPDATED");
        assertEquals("FIRST-NEW-VALUE", ((Map<?, ?>) bean.getListIndexed().get(0)).get("FIRST-NEW-KEY"), "BEFORE");
        assertEquals("SECOND-VALUE-1-UPDATED", ((Map<?, ?>) bean.getListIndexed().get(1)).get("SECOND-KEY-1"), "AFTER");
    }

    /**
     * Positive and negative tests on setIndexedProperty valid arguments.
     */
    @Test
    void testSetIndexedValues() throws Exception {
        Object value = null;
        // Use explicit index argument
        PropertyUtils.setIndexedProperty(bean, "dupProperty", 0, "New 0");
        value = PropertyUtils.getIndexedProperty(bean, "dupProperty", 0);
        assertNotNull(value, "Returned new value 0");
        assertInstanceOf(String.class, value, "Returned String new value 0");
        assertEquals("New 0", (String) value, "Returned correct new value 0");

        PropertyUtils.setIndexedProperty(bean, "intArray", 0, Integer.valueOf(1));
        value = PropertyUtils.getIndexedProperty(bean, "intArray", 0);
        assertNotNull(value, "Returned new value 0");
        assertInstanceOf(Integer.class, value, "Returned Integer new value 0");
        assertEquals(1, ((Integer) value).intValue(), "Returned correct new value 0");

        PropertyUtils.setIndexedProperty(bean, "intIndexed", 1, Integer.valueOf(11));
        value = PropertyUtils.getIndexedProperty(bean, "intIndexed", 1);
        assertNotNull(value, "Returned new value 1");
        assertInstanceOf(Integer.class, value, "Returned Integer new value 1");
        assertEquals(11, ((Integer) value).intValue(), "Returned correct new value 1");

        PropertyUtils.setIndexedProperty(bean, "listIndexed", 2, "New Value 2");
        value = PropertyUtils.getIndexedProperty(bean, "listIndexed", 2);
        assertNotNull(value, "Returned new value 2");
        assertInstanceOf(String.class, value, "Returned String new value 2");
        assertEquals("New Value 2", (String) value, "Returned correct new value 2");

        PropertyUtils.setIndexedProperty(bean, "stringArray", 2, "New Value 2");
        value = PropertyUtils.getIndexedProperty(bean, "stringArray", 2);
        assertNotNull(value, "Returned new value 2");
        assertInstanceOf(String.class, value, "Returned String new value 2");
        assertEquals("New Value 2", (String) value, "Returned correct new value 2");

        PropertyUtils.setIndexedProperty(bean, "stringArray", 3, "New Value 3");
        value = PropertyUtils.getIndexedProperty(bean, "stringArray", 3);
        assertNotNull(value, "Returned new value 3");
        assertInstanceOf(String.class, value, "Returned String new value 3");
        assertEquals("New Value 3", (String) value, "Returned correct new value 3");

        // Use index expression

        PropertyUtils.setIndexedProperty(bean, "dupProperty[4]", "New 4");
        value = PropertyUtils.getIndexedProperty(bean, "dupProperty[4]");
        assertNotNull(value, "Returned new value 4");
        assertInstanceOf(String.class, value, "Returned String new value 4");
        assertEquals("New 4", (String) value, "Returned correct new value 4");

        PropertyUtils.setIndexedProperty(bean, "intArray[4]", Integer.valueOf(1));
        value = PropertyUtils.getIndexedProperty(bean, "intArray[4]");
        assertNotNull(value, "Returned new value 4");
        assertInstanceOf(Integer.class, value, "Returned Integer new value 4");
        assertEquals(1, ((Integer) value).intValue(), "Returned correct new value 4");

        PropertyUtils.setIndexedProperty(bean, "intIndexed[3]", Integer.valueOf(11));
        value = PropertyUtils.getIndexedProperty(bean, "intIndexed[3]");
        assertNotNull(value, "Returned new value 5");
        assertInstanceOf(Integer.class, value, "Returned Integer new value 5");
        assertEquals(11, ((Integer) value).intValue(), "Returned correct new value 5");

        PropertyUtils.setIndexedProperty(bean, "listIndexed[1]", "New Value 2");
        value = PropertyUtils.getIndexedProperty(bean, "listIndexed[1]");
        assertNotNull(value, "Returned new value 6");
        assertInstanceOf(String.class, value, "Returned String new value 6");
        assertEquals("New Value 2", (String) value, "Returned correct new value 6");

        PropertyUtils.setIndexedProperty(bean, "stringArray[1]", "New Value 2");
        value = PropertyUtils.getIndexedProperty(bean, "stringArray[2]");
        assertNotNull(value, "Returned new value 6");
        assertInstanceOf(String.class, value, "Returned String new value 6");
        assertEquals("New Value 2", (String) value, "Returned correct new value 6");

        PropertyUtils.setIndexedProperty(bean, "stringArray[0]", "New Value 3");
        value = PropertyUtils.getIndexedProperty(bean, "stringArray[0]");
        assertNotNull(value, "Returned new value 7");
        assertInstanceOf(String.class, value, "Returned String new value 7");
        assertEquals("New Value 3", (String) value, "Returned correct new value 7");

        // Index out of bounds tests
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "dupProperty", -1, "New -1"));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "dupProperty", 5, "New 5"));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "intArray", -1, Integer.valueOf(0)));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "intArray", 5, Integer.valueOf(0)));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "intIndexed", -1, Integer.valueOf(0)));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "intIndexed", 5, Integer.valueOf(0)));
        assertThrows(IndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "listIndexed", 5, "New String"));
        assertThrows(IndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "listIndexed", -1, "New String"));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "stringArray", -1, "New String"));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "stringArray", 5, "New String"));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "stringIndexed", -1, "New String"));
        assertThrows(ArrayIndexOutOfBoundsException.class, () -> PropertyUtils.setIndexedProperty(bean, "stringIndexed", 5, "New String"));
    }

    /**
     * Corner cases on getMappedProperty invalid arguments.
     */
    @Test
    void testSetMappedArguments() {
        // Use explicit key argument
        assertThrows(NullPointerException.class, () -> PropertyUtils.setMappedProperty(null, "mappedProperty", "First Key", "First Value"));
        assertThrows(NullPointerException.class, () -> PropertyUtils.setMappedProperty(bean, null, "First Key", "First Value"));
        assertThrows(NullPointerException.class, () -> PropertyUtils.setMappedProperty(bean, "mappedProperty", null, "First Value"));
        // Use key expression
        assertThrows(NullPointerException.class, () -> PropertyUtils.setMappedProperty(null, "mappedProperty(First Key)", "First Value"));
        assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setMappedProperty(bean, "(Second Key)", "Second Value"));
        assertThrows(IllegalArgumentException.class, () -> PropertyUtils.setMappedProperty(bean, "mappedProperty", "Third Value"));
    }

    /**
     * Test setting an indexed value out of a mapped array
     */
    @Test
    void testSetMappedArray() throws Exception {
        final TestBean bean = new TestBean();
        final String[] array = { "abc", "def", "ghi" };
        bean.getMapProperty().put("mappedArray", array);

        assertEquals("def", ((String[]) bean.getMapProperty().get("mappedArray"))[1], "BEFORE");
        PropertyUtils.setProperty(bean, "mapProperty(mappedArray)[1]", "DEF-UPDATED");
        assertEquals("DEF-UPDATED", ((String[]) bean.getMapProperty().get("mappedArray"))[1], "AFTER");
    }

    /**
     * Test setting an indexed value out of a mapped List
     */
    @Test
    void testSetMappedList() throws Exception {
        final TestBean bean = new TestBean();
        final List<Object> list = new ArrayList<>();
        list.add("klm");
        list.add("nop");
        list.add("qrs");
        bean.getMapProperty().put("mappedList", list);

        assertEquals("klm", ((List<?>) bean.getMapProperty().get("mappedList")).get(0), "BEFORE");
        PropertyUtils.setProperty(bean, "mapProperty(mappedList)[0]", "KLM-UPDATED");
        assertEquals("KLM-UPDATED", ((List<?>) bean.getMapProperty().get("mappedList")).get(0), "AFTER");
    }

    /**
     * Test setting a value out of a mapped Map
     */
    @Test
    void testSetMappedMap() throws Exception {
        final TestBean bean = new TestBean();
        final Map<String, Object> map = new HashMap<>();
        map.put("sub-key-1", "sub-value-1");
        map.put("sub-key-2", "sub-value-2");
        map.put("sub-key-3", "sub-value-3");
        bean.getMapProperty().put("mappedMap", map);

        assertEquals("sub-value-3", ((Map<?, ?>) bean.getMapProperty().get("mappedMap")).get("sub-key-3"), "BEFORE");
        PropertyUtils.setProperty(bean, "mapProperty(mappedMap)(sub-key-3)", "SUB-KEY-3-UPDATED");
        assertEquals("SUB-KEY-3-UPDATED", ((Map<?, ?>) bean.getMapProperty().get("mappedMap")).get("sub-key-3"), "AFTER");
    }

    /**
     * Test setting mapped values with periods in the key.
     */
    @Test
    void testSetMappedPeriods() throws Exception {

        // PropertyUtils.setMappedProperty()--------
        bean.setMappedProperty("key.with.a.dot", "Special Value");
        assertEquals("Special Value", bean.getMappedProperty("key.with.a.dot"), "Can retrieve directly (A)");

        PropertyUtils.setMappedProperty(bean, "mappedProperty", "key.with.a.dot", "Updated Special Value");
        assertEquals("Updated Special Value", bean.getMappedProperty("key.with.a.dot"), "Check set via setMappedProperty");

        // PropertyUtils.setNestedProperty()
        bean.setMappedProperty("key.with.a.dot", "Special Value");
        assertEquals("Special Value", bean.getMappedProperty("key.with.a.dot"), "Can retrieve directly (B)");
        PropertyUtils.setNestedProperty(bean, "mappedProperty(key.with.a.dot)", "Updated Special Value");
        assertEquals("Updated Special Value", bean.getMappedProperty("key.with.a.dot"), "Check set via setNestedProperty (B)");

        // PropertyUtils.setNestedProperty()
        final TestBean testBean = new TestBean();
        bean.setMappedObjects("nested.property", testBean);
        assertEquals("This is a string", testBean.getStringProperty(), "Can retrieve directly (C)");
        PropertyUtils.setNestedProperty(bean, "mappedObjects(nested.property).stringProperty", "Updated String Value");
        assertEquals("Updated String Value", testBean.getStringProperty(), "Check set via setNestedProperty (C)");

        // PropertyUtils.setNestedProperty()
        bean.getNested().setMappedProperty("Mapped Key", "Nested Mapped Value");
        assertEquals("Nested Mapped Value", PropertyUtils.getNestedProperty(bean, "nested.mappedProperty(Mapped Key)"),
                "Can retrieve via getNestedProperty (D)");
        PropertyUtils.setNestedProperty(bean, "nested.mappedProperty(Mapped Key)", "Updated Nested Mapped Value");
        assertEquals("Updated Nested Mapped Value", PropertyUtils.getNestedProperty(bean, "nested.mappedProperty(Mapped Key)"),
                "Check set via setNestedProperty (D)");
    }

    /**
     * Positive and negative tests on setMappedProperty valid arguments.
     */
    @Test
    void testSetMappedValues() throws Exception {
        Object value = null;
        // Use explicit key argument
        value = PropertyUtils.getMappedProperty(bean, "mappedProperty", "Fourth Key");
        assertNull(value, "Can not find fourth value");

        PropertyUtils.setMappedProperty(bean, "mappedProperty", "Fourth Key", "Fourth Value");

        value = PropertyUtils.getMappedProperty(bean, "mappedProperty", "Fourth Key");
        assertEquals("Fourth Value", value, "Can find fourth value");

        // Use key expression with parentheses
        value = PropertyUtils.getMappedProperty(bean, "mappedProperty(Fifth Key)");
        assertNull(value, "Can not find fifth value");

        PropertyUtils.setMappedProperty(bean, "mappedProperty(Fifth Key)", "Fifth Value");

        value = PropertyUtils.getMappedProperty(bean, "mappedProperty(Fifth Key)");
        assertEquals("Fifth Value", value, "Can find fifth value");

        // Use key expression with dotted expression

        value = PropertyUtils.getNestedProperty(bean, "mapProperty.Sixth Key");
        assertNull(value, "Can not find sixth value");

        PropertyUtils.setNestedProperty(bean, "mapProperty.Sixth Key", "Sixth Value");

        value = PropertyUtils.getNestedProperty(bean, "mapProperty.Sixth Key");
        assertEquals("Sixth Value", value, "Can find sixth value");
    }

    /**
     * Corner cases on setNestedProperty invalid arguments.
     */
    @Test
    void testSetNestedArguments() {
        assertThrows(NullPointerException.class, () -> PropertyUtils.setNestedProperty(null, "stringProperty", ""));
        assertThrows(NullPointerException.class, () -> PropertyUtils.setNestedProperty(bean, null, ""));
    }

    /**
     * Test setNextedProperty on a boolean property.
     */
    @Test
    void testSetNestedBoolean() throws Exception {
        final boolean oldValue = bean.getNested().getBooleanProperty();
        final boolean newValue = !oldValue;
        PropertyUtils.setNestedProperty(bean, "nested.booleanProperty", Boolean.valueOf(newValue));
        assertEquals(newValue, bean.getNested().getBooleanProperty(), "Matched new value");
    }

    /**
     * Test setNestedProperty on a double property.
     */
    @Test
    void testSetNestedDouble() throws Exception {
        final double oldValue = bean.getNested().getDoubleProperty();
        final double newValue = oldValue + 1.0;
        PropertyUtils.setNestedProperty(bean, "nested.doubleProperty", Double.valueOf(newValue));
        assertEquals(newValue, bean.getNested().getDoubleProperty(), 0.005, "Matched new value");
    }

    /**
     * Test setNestedProperty on a float property.
     */
    @Test
    void testSetNestedFloat() throws Exception {
        final float oldValue = bean.getNested().getFloatProperty();
        final float newValue = oldValue + (float) 1.0;
        PropertyUtils.setNestedProperty(bean, "nested.floatProperty", Float.valueOf(newValue));
        assertEquals(newValue, bean.getNested().getFloatProperty(), (float) 0.005, "Matched new value");
    }

    /**
     * Test setNestedProperty on a int property.
     */
    @Test
    void testSetNestedInt() throws Exception {
        final int oldValue = bean.getNested().getIntProperty();
        final int newValue = oldValue + 1;
        PropertyUtils.setNestedProperty(bean, "nested.intProperty", Integer.valueOf(newValue));
        assertEquals(newValue, bean.getNested().getIntProperty(), "Matched new value");
    }

    /**
     * Test setNestedProperty on a long property.
     */
    @Test
    void testSetNestedLong() throws Exception {
        final long oldValue = bean.getNested().getLongProperty();
        final long newValue = oldValue + 1;
        PropertyUtils.setNestedProperty(bean, "nested.longProperty", Long.valueOf(newValue));
        assertEquals(newValue, bean.getNested().getLongProperty(), "Matched new value");
    }

    /**
     * Test setNestedProperty on a read-only String property.
     */
    @Test
    void testSetNestedReadOnly() throws Exception {
        final String oldValue = bean.getNested().getWriteOnlyPropertyValue();
        final String newValue = oldValue + " Extra Value";
        assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setNestedProperty(bean, "nested.readOnlyProperty", newValue));
    }

    /**
     * Test setNestedProperty on a short property.
     */
    @Test
    void testSetNestedShort() throws Exception {
        final short oldValue = bean.getNested().getShortProperty();
        short newValue = oldValue;
        newValue++;
        PropertyUtils.setNestedProperty(bean, "nested.shortProperty", Short.valueOf(newValue));
        assertEquals(newValue, bean.getNested().getShortProperty(), "Matched new value");
    }

    /**
     * Test setNestedProperty on a String property.
     */
    @Test
    void testSetNestedString() throws Exception {
        final String oldValue = bean.getNested().getStringProperty();
        final String newValue = oldValue + " Extra Value";
        PropertyUtils.setNestedProperty(bean, "nested.stringProperty", newValue);
        assertEquals(newValue, bean.getNested().getStringProperty(), "Matched new value");
    }

    /**
     * Test setNestedProperty on an unknown property name.
     */
    @Test
    void testSetNestedUnknown() throws Exception {
        final String newValue = "New String Value";
        assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setNestedProperty(bean, "nested.unknown", newValue));
    }

    /**
     * Test setNestedProperty on a write-only String property.
     */
    @Test
    void testSetNestedWriteOnly() throws Exception {
        final String oldValue = bean.getNested().getWriteOnlyPropertyValue();
        final String newValue = oldValue + " Extra Value";
        PropertyUtils.setNestedProperty(bean, "nested.writeOnlyProperty", newValue);
        assertEquals(newValue, bean.getNested().getWriteOnlyPropertyValue(), "Matched new value");
    }

    @Test
    void testSetNoGetter() throws Exception {
        final BetaBean bean = new BetaBean("Cedric");

        // test standard no getter
        bean.setNoGetterProperty("Sigma");
        assertEquals("Sigma", bean.getSecret(), "BetaBean test failed");

        assertNotNull(PropertyUtils.getPropertyDescriptor(bean, "noGetterProperty"), "Descriptor is null");

        BeanUtils.setProperty(bean, "noGetterProperty", "Omega");
        assertEquals("Omega", bean.getSecret(), "Cannot set no-getter property");

        // test mapped no getter descriptor
        assertNotNull(PropertyUtils.getPropertyDescriptor(bean, "noGetterMappedProperty"), "Map Descriptor is null");

        PropertyUtils.setMappedProperty(bean, "noGetterMappedProperty", "Epsilon", "Epsilon");
        assertEquals("MAP:Epsilon", bean.getSecret(), "Cannot set mapped no-getter property");
    }

    /**
     * Test accessing a public sub-bean of a package scope bean
     */
    @Test
    void testSetPublicSubBean_of_PackageBean() throws Exception {

        final PublicSubBean bean = new PublicSubBean();
        bean.setFoo("foo-start");
        bean.setBar("bar-start");
        // Set Foo
        PropertyUtils.setProperty(bean, "foo", "foo-updated");
        assertEquals("foo-updated", bean.getFoo(), "foo property");
        // Set Bar
        PropertyUtils.setProperty(bean, "bar", "bar-updated");
        assertEquals("bar-updated", bean.getBar(), "bar property");
    }

    /**
     * Corner cases on setSimpleProperty invalid arguments.
     */
    @Test
    void testSetSimpleArguments() {
        assertThrows(NullPointerException.class, () -> PropertyUtils.setSimpleProperty(null, "stringProperty", ""));
        assertThrows(NullPointerException.class, () -> PropertyUtils.setSimpleProperty(bean, null, ""));
    }

    /**
     * Test setSimpleProperty on a boolean property.
     */
    @Test
    void testSetSimpleBoolean() throws Exception {
        final boolean oldValue = bean.getBooleanProperty();
        final boolean newValue = !oldValue;
        PropertyUtils.setSimpleProperty(bean, "booleanProperty", Boolean.valueOf(newValue));
        assertEquals(newValue, bean.getBooleanProperty(), "Matched new value");
    }

    /**
     * Test setSimpleProperty on a double property.
     */
    @Test
    void testSetSimpleDouble() throws Exception {
        final double oldValue = bean.getDoubleProperty();
        final double newValue = oldValue + 1.0;
        PropertyUtils.setSimpleProperty(bean, "doubleProperty", Double.valueOf(newValue));
        assertEquals(newValue, bean.getDoubleProperty(), 0.005, "Matched new value");
    }

    /**
     * Test setSimpleProperty on a float property.
     */
    @Test
    void testSetSimpleFloat() throws Exception {
        final float oldValue = bean.getFloatProperty();
        final float newValue = oldValue + (float) 1.0;
        PropertyUtils.setSimpleProperty(bean, "floatProperty", Float.valueOf(newValue));
        assertEquals(newValue, bean.getFloatProperty(), (float) 0.005, "Matched new value");
    }

    /**
     * Negative test setSimpleProperty on an indexed property.
     */
    @Test
    void testSetSimpleIndexed() throws Exception {
        assertThrows(IllegalArgumentException.class, () -> PropertyUtils.setSimpleProperty(bean, "stringIndexed[0]", "New String Value"));
    }

    /**
     * Test setSimpleProperty on a int property.
     */
    @Test
    void testSetSimpleInt() throws Exception {
        final int oldValue = bean.getIntProperty();
        final int newValue = oldValue + 1;
        PropertyUtils.setSimpleProperty(bean, "intProperty", Integer.valueOf(newValue));
        assertEquals(newValue, bean.getIntProperty(), "Matched new value");
    }

    /**
     * Test setSimpleProperty on a long property.
     */
    @Test
    void testSetSimpleLong() throws Exception {
        final long oldValue = bean.getLongProperty();
        final long newValue = oldValue + 1;
        PropertyUtils.setSimpleProperty(bean, "longProperty", Long.valueOf(newValue));
        assertEquals(newValue, bean.getLongProperty(), "Matched new value");
    }

    /**
     * Negative test setSimpleProperty on a nested property.
     */
    @Test
    void testSetSimpleNested() throws Exception {
        assertThrows(IllegalArgumentException.class, () -> PropertyUtils.setSimpleProperty(bean, "nested.stringProperty", "New String Value"));
    }

    /**
     * Test setSimpleProperty on a read-only String property.
     */
    @Test
    void testSetSimpleReadOnly() throws Exception {
        final String oldValue = bean.getWriteOnlyPropertyValue();
        final String newValue = oldValue + " Extra Value";
        final NoSuchMethodException e = assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setSimpleProperty(bean, "readOnlyProperty", newValue));
        assertEquals("Property 'readOnlyProperty' has no setter method in class '" + bean.getClass() + "'", e.getMessage());
    }

    /**
     * Test setSimpleProperty on a short property.
     */
    @Test
    void testSetSimpleShort() throws Exception {
        final short oldValue = bean.getShortProperty();
        short newValue = oldValue;
        newValue++;
        PropertyUtils.setSimpleProperty(bean, "shortProperty", Short.valueOf(newValue));
        assertEquals(newValue, bean.getShortProperty(), "Matched new value");
    }

    /**
     * Test setSimpleProperty on a String property.
     */
    @Test
    void testSetSimpleString() throws Exception {
        final String oldValue = bean.getStringProperty();
        final String newValue = oldValue + " Extra Value";
        PropertyUtils.setSimpleProperty(bean, "stringProperty", newValue);
        assertEquals(newValue, bean.getStringProperty(), "Matched new value");
    }

    /**
     * Test setSimpleProperty on an unknown property name.
     */
    @Test
    void testSetSimpleUnknown() throws Exception {
        final String newValue = "New String Value";
        final NoSuchMethodException e = assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setSimpleProperty(bean, "unknown", newValue));
        assertEquals("Unknown property 'unknown' on class '" + bean.getClass() + "'", e.getMessage());
    }

    /**
     * Test setSimpleProperty on a write-only String property.
     */
    @Test
    void testSetSimpleWriteOnly() throws Exception {
        final String oldValue = bean.getWriteOnlyPropertyValue();
        final String newValue = oldValue + " Extra Value";
        PropertyUtils.setSimpleProperty(bean, "writeOnlyProperty", newValue);
        assertEquals(newValue, bean.getWriteOnlyPropertyValue(), "Matched new value");
    }

    /**
     * When a bean has a null property which is reference by the standard access language, this should throw a NestedNullException.
     */
    @Test
    void testThrowNestedNull() throws Exception {
        final NestedTestBean nestedBean = new NestedTestBean("base");
        // don't init!
        assertThrows(NestedNullException.class, () -> PropertyUtils.getProperty(nestedBean, "simpleBeanProperty.indexedProperty[0]"));
    }
}