DynaPropertyUtilsTest.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.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
 * Test accessing DynaBeans transparently via PropertyUtils.
 */
class DynaPropertyUtilsTest {

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

    /**
     * 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" };

    /**
     * The nested bean pointed at by the "nested" property.
     */
    protected TestBean nested;

    /**
     * Create and return a {@code DynaClass} instance for our test {@code DynaBean}.
     */
    protected DynaClass createDynaClass() {
        final int[] intArray = {};
        final String[] stringArray = {};
        return new BasicDynaClass("TestDynaClass", null, new DynaProperty[] { new DynaProperty("booleanProperty", Boolean.TYPE),
                new DynaProperty("booleanSecond", Boolean.TYPE), new DynaProperty("doubleProperty", Double.TYPE),
                new DynaProperty("dupProperty", stringArray.getClass()), new DynaProperty("floatProperty", Float.TYPE),
                new DynaProperty("intArray", intArray.getClass()), new DynaProperty("intIndexed", intArray.getClass()),
                new DynaProperty("intProperty", Integer.TYPE), new DynaProperty("listIndexed", List.class), new DynaProperty("longProperty", Long.TYPE),
                new DynaProperty("mapProperty", Map.class), new DynaProperty("mappedObjects", Map.class), new DynaProperty("mappedProperty", Map.class),
                new DynaProperty("mappedIntProperty", Map.class), new DynaProperty("nested", TestBean.class), new DynaProperty("nullProperty", String.class),
                new DynaProperty("shortProperty", Short.TYPE), new DynaProperty("stringArray", stringArray.getClass()),
                new DynaProperty("stringIndexed", stringArray.getClass()), new DynaProperty("stringProperty", String.class), });
    }

    /**
     * Sets up instance variables required by this test case.
     */
    @BeforeEach
    public void setUp() throws Exception {
        // Instantiate a new DynaBean instance
        final DynaClass dynaClass = createDynaClass();
        bean = dynaClass.newInstance();
        // Initialize the DynaBean's property values (like TestBean)
        bean.set("booleanProperty", Boolean.valueOf(true));
        bean.set("booleanSecond", Boolean.valueOf(true));
        bean.set("doubleProperty", Double.valueOf(321.0));
        bean.set("floatProperty", Float.valueOf((float) 123.0));
        final int[] intArray = { 0, 10, 20, 30, 40 };
        bean.set("intArray", intArray);
        final int[] intIndexed = { 0, 10, 20, 30, 40 };
        bean.set("intIndexed", intIndexed);
        bean.set("intProperty", Integer.valueOf(123));
        final List<String> listIndexed = new ArrayList<>();
        listIndexed.add("String 0");
        listIndexed.add("String 1");
        listIndexed.add("String 2");
        listIndexed.add("String 3");
        listIndexed.add("String 4");
        bean.set("listIndexed", listIndexed);
        bean.set("longProperty", Long.valueOf(321));
        final HashMap<String, Object> mapProperty = new HashMap<>();
        mapProperty.put("First Key", "First Value");
        mapProperty.put("Second Key", "Second Value");
        bean.set("mapProperty", mapProperty);
        final HashMap<String, Object> mappedObjects = new HashMap<>();
        mappedObjects.put("First Key", "First Value");
        mappedObjects.put("Second Key", "Second Value");
        bean.set("mappedObjects", mappedObjects);
        final HashMap<String, Object> mappedProperty = new HashMap<>();
        mappedProperty.put("First Key", "First Value");
        mappedProperty.put("Second Key", "Second Value");
        bean.set("mappedProperty", mappedProperty);
        final HashMap<String, Integer> mappedIntProperty = new HashMap<>();
        mappedIntProperty.put("One", Integer.valueOf(1));
        mappedIntProperty.put("Two", Integer.valueOf(2));
        bean.set("mappedIntProperty", mappedIntProperty);
        nested = new TestBean();
        bean.set("nested", nested);
        // Property "nullProperty" is not initialized, so it should return null
        bean.set("shortProperty", Short.valueOf((short) 987));
        final String[] stringArray = { "String 0", "String 1", "String 2", "String 3", "String 4" };
        bean.set("stringArray", stringArray);
        final String[] stringIndexed = { "String 0", "String 1", "String 2", "String 3", "String 4" };
        bean.set("stringIndexed", stringIndexed);
        bean.set("stringProperty", "This is a string");

    }

    /**
     * Tear down instance variables required by this test case.
     */
    @AfterEach
    public void tearDown() {
        bean = null;
        nested = 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, ((Boolean) bean.get("booleanProperty")).booleanValue(), "booleanProperty");
        assertEquals(333.0, ((Double) bean.get("doubleProperty")).doubleValue(), 0.005, "doubleProperty");
        assertEquals((float) 222.0, ((Float) bean.get("floatProperty")).floatValue(), (float) 0.005, "floatProperty");
        assertEquals(111, ((Integer) bean.get("intProperty")).intValue(), "intProperty");
        assertEquals(444, ((Long) bean.get("longProperty")).longValue(), "longProperty");
        assertEquals((short) 555, ((Short) bean.get("shortProperty")).shortValue(), "shortProperty");
        assertEquals("New String Property", (String) bean.get("stringProperty"), "stringProperty");

        // Indexed Properties
        final String[] dupProperty = (String[]) bean.get("dupProperty");
        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 = (int[]) bean.get("intArray");
        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]");

    }

    /**
     * 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'");

    }

    /**
     * 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"));
    }

    /**
     * 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, "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, "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, "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 mapped values with periods in the key.
     */
    @Test
    void testGetMappedPeriods() throws Exception {
        bean.set("mappedProperty", "key.with.a.dot", "Special Value");
        assertEquals("Special Value", (String) bean.get("mappedProperty", "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.set("mappedObjects", "nested.property", new TestBean());
        assertNotNull(bean.get("mappedObjects", "nested.property"), "Can retrieve directly");
        assertEquals("This is a string", PropertyUtils.getNestedProperty(bean, "mappedObjects(nested.property).stringProperty"), "Can retrieve nested");
    }

    /**
     * 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.set("mappedProperty", "key/with/a/slash", "Special Value");
        assertEquals("Special Value", bean.get("mappedProperty", "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.set("mappedObjects", "nested/property", new TestBean());
        assertNotNull(bean.get("mappedObjects", "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");
        final TestBean nested = (TestBean) bean.get("nested");
        assertEquals(((Boolean) value).booleanValue(), nested.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");
        final TestBean nested = (TestBean) bean.get("nested");
        assertEquals(((Double) value).doubleValue(), nested.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");
        final TestBean nested = (TestBean) bean.get("nested");
        assertEquals(((Float) value).floatValue(), nested.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");
        final TestBean nested = (TestBean) bean.get("nested");
        assertEquals(((Integer) value).intValue(), nested.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");
        final TestBean nested = (TestBean) bean.get("nested");
        assertEquals(((Long) value).longValue(), nested.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");
        final TestBean nested = (TestBean) bean.get("nested");
        assertEquals((String) value, nested.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");
        final TestBean nested = (TestBean) bean.get("nested");
        assertEquals(((Short) value).shortValue(), nested.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");
        final TestBean nested = (TestBean) bean.get("nested");
        assertEquals((String) value, nested.getStringProperty(), "Got correct value");
    }

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

    /**
     * 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");
        assertTrue(((Boolean) value).booleanValue(), "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(), 321.0, 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(), (float) 123.0, (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(), 123, "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(), 321, "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 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(), (short) 987, "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, "This is a string", "Got correct value");
    }

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

    /**
     * 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)));
    }

    /**
     * Positive and negative tests on setIndexedProperty valid arguments.
     */
    @Test
    void testSetIndexedValues() throws Exception {
        Object value = null;
        // Use explicit index argument
        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, "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, "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"));
    }

    /**
     * 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 = nested.getBooleanProperty();
        final boolean newValue = !oldValue;
        PropertyUtils.setNestedProperty(bean, "nested.booleanProperty", Boolean.valueOf(newValue));
        assertEquals(newValue, nested.getBooleanProperty(), "Matched new value");
    }

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

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

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

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

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

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

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

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

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

    /**
     * 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 = ((Boolean) bean.get("booleanProperty")).booleanValue();
        final boolean newValue = !oldValue;
        PropertyUtils.setSimpleProperty(bean, "booleanProperty", Boolean.valueOf(newValue));
        assertEquals(newValue, ((Boolean) bean.get("booleanProperty")).booleanValue(), "Matched new value");
    }

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

    /**
     * Test setSimpleProperty on a float property.
     */
    @Test
    void testSetSimpleFloat() throws Exception {
        final float oldValue = ((Float) bean.get("floatProperty")).floatValue();
        final float newValue = oldValue + (float) 1.0;
        PropertyUtils.setSimpleProperty(bean, "floatProperty", Float.valueOf(newValue));
        assertEquals(newValue, ((Float) bean.get("floatProperty")).floatValue(), (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 = ((Integer) bean.get("intProperty")).intValue();
        final int newValue = oldValue + 1;
        PropertyUtils.setSimpleProperty(bean, "intProperty", Integer.valueOf(newValue));
        assertEquals(newValue, ((Integer) bean.get("intProperty")).intValue(), "Matched new value");
    }

    /**
     * Test setSimpleProperty on a long property.
     */
    @Test
    void testSetSimpleLong() throws Exception {
        final long oldValue = ((Long) bean.get("longProperty")).longValue();
        final long newValue = oldValue + 1;
        PropertyUtils.setSimpleProperty(bean, "longProperty", Long.valueOf(newValue));
        assertEquals(newValue, ((Long) bean.get("longProperty")).longValue(), "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 short property.
     */
    @Test
    void testSetSimpleShort() throws Exception {
        final short oldValue = ((Short) bean.get("shortProperty")).shortValue();
        short newValue = oldValue;
        newValue++;
        PropertyUtils.setSimpleProperty(bean, "shortProperty", Short.valueOf(newValue));
        assertEquals(newValue, ((Short) bean.get("shortProperty")).shortValue(), "Matched new value");
    }

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

    /**
     * Test setSimpleProperty on an unknown property name.
     */
    @Test
    void testSetSimpleUnknown() throws Exception {
        assertThrows(NoSuchMethodException.class, () -> PropertyUtils.setSimpleProperty(bean, "unknown", "New String Value"));
    }

}