/*
 * 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
 *
 *      http://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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import junit.framework.Test;
import junit.framework.TestSuite;

/**
 * <p>
 * Test Case for the {@code WrapDynaBean} implementation class. These tests were based on the ones in {@code PropertyUtilsTestCase} because the two classes
 * provide similar levels of functionality.
 * </p>
 *
 */

public class WrapDynaBeanTestCase extends BasicDynaBeanTestCase {

    /**
     * Creates the tests included in this test suite.
     */
    public static Test suite() {

        return new TestSuite(WrapDynaBeanTestCase.class);

    }

    /**
     * Constructs a new instance of this test case.
     *
     * @param name Name of the test case
     */
    public WrapDynaBeanTestCase(final String name) {

        super(name);

    }

    /**
     * Helper method for testing whether basic access to properties works as expected.
     */
    private void checkSimplePropertyAccess() {
        // Invalid getter
        try {
            bean.get("invalidProperty");
            fail("Invalid get should have thrown IllegalArgumentException");
        } catch (final IllegalArgumentException t) {
            // Expected result
        }

        // Invalid setter
        try {
            bean.set("invalidProperty", "XYZ");
            fail("Invalid set should have thrown IllegalArgumentException");
        } catch (final IllegalArgumentException t) {
            // Expected result
        }

        // Set up initial Value
        String testValue = "Original Value";
        final String testProperty = "stringProperty";
        final TestBean instance = (TestBean) ((WrapDynaBean) bean).getInstance();
        instance.setStringProperty(testValue);
        assertEquals("Check String property", testValue, instance.getStringProperty());

        // Test Valid Get & Set
        try {
            testValue = "Some new value";
            bean.set(testProperty, testValue);
            assertEquals("Test Set", testValue, instance.getStringProperty());
            assertEquals("Test Get", testValue, bean.get(testProperty));
        } catch (final IllegalArgumentException t) {
            fail("Get threw exception: " + t);
        }
    }

    /**
     * Do serialization and deserialization.
     */
    private Object serializeDeserialize(final Object target, final String text) {

        // Serialize the test object
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            final ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(target);
            oos.flush();
            oos.close();
        } catch (final Exception e) {
            fail(text + ": Exception during serialization: " + e);
        }

        // Deserialize the test object
        Object result = null;
        try {
            final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            final ObjectInputStream ois = new ObjectInputStream(bais);
            result = ois.readObject();
            bais.close();
        } catch (final Exception e) {
            fail(text + ": Exception during deserialization: " + e);
        }
        return result;

    }

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

        bean = new WrapDynaBean(new TestBean());

    }

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

        bean = null;

    }

    /** Tests getInstance method */
    public void testGetInstance() {
        final AlphaBean alphaBean = new AlphaBean("Now On Air... John Peel");
        final WrapDynaBean dynaBean = new WrapDynaBean(alphaBean);
        final Object wrappedInstance = dynaBean.getInstance();
        assertTrue("Object type is AlphaBean", wrappedInstance instanceof AlphaBean);
        final AlphaBean wrappedAlphaBean = (AlphaBean) wrappedInstance;
        assertTrue("Same Object", wrappedAlphaBean == alphaBean);
    }

    /**
     * Tests whether caching works for WrapDynaClass objects.
     */
    public void testGetWrapDynaClassFromCache() {
        final WrapDynaClass clazz = WrapDynaClass.createDynaClass(TestBean.class);
        assertSame("Instance not cached", clazz, WrapDynaClass.createDynaClass(TestBean.class));
    }

    /**
     * Tests whether the PropertyUtilsBean instance associated with a WrapDynaClass is taken into account when accessing an instance from the cache.
     */
    public void testGetWrapDynaClassFromCacheWithPropUtils() {
        final WrapDynaClass clazz = WrapDynaClass.createDynaClass(TestBean.class);
        final PropertyUtilsBean pu = new PropertyUtilsBean();
        final WrapDynaClass clazz2 = WrapDynaClass.createDynaClass(TestBean.class, pu);
        assertNotSame("Got same instance from cache", clazz, clazz2);
    }

    /**
     * The {@code set()} method.
     */
    public void testIndexedProperties() {

        // Invalid getter
        try {
            bean.get("invalidProperty", 0);
            fail("Invalid get should have thrown IllegalArgumentException");
        } catch (final IllegalArgumentException t) {
            // Expected result
        }

        // Invalid setter
        try {
            bean.set("invalidProperty", 0, "XYZ");
            fail("Invalid set should have thrown IllegalArgumentException");
        } catch (final IllegalArgumentException t) {
            // Expected result
        }

        // Set up initial Value
        String testValue = "Original Value";
        final String testProperty = "stringIndexed";
        final TestBean instance = (TestBean) ((WrapDynaBean) bean).getInstance();
        instance.setStringIndexed(0, testValue);
        assertEquals("Check String property", testValue, instance.getStringIndexed(0));

        // Test Valid Get & Set
        try {
            testValue = "Some new value";
            bean.set(testProperty, 0, testValue);
            assertEquals("Test Set", testValue, instance.getStringIndexed(0));
            assertEquals("Test Get", testValue, bean.get(testProperty, 0));
        } catch (final IllegalArgumentException t) {
            fail("Get threw exception: " + t);
        }

    }

    /**
     * Tests whether a WrapDynaClass can be provided when constructing a bean.
     */
    public void testInitWithDynaClass() {
        final WrapDynaClass clazz = WrapDynaClass.createDynaClass(TestBean.class);
        bean = new WrapDynaBean(new TestBean(), clazz);
        assertSame("Wrong DynaClass", clazz, bean.getDynaClass());
        checkSimplePropertyAccess();
    }

    /**
     * Tests whether a custom PropertyUtilsBean instance can be used for introspection of bean properties.
     */
    public void testIntrospectionWithCustomPropUtils() {
        final PropertyUtilsBean pu = new PropertyUtilsBean();
        pu.addBeanIntrospector(new FluentPropertyBeanIntrospector());
        final WrapDynaClass dynaClass = WrapDynaClass.createDynaClass(FluentIntrospectionTestBean.class, pu);
        final FluentIntrospectionTestBean obj = new FluentIntrospectionTestBean();
        bean = new WrapDynaBean(obj, dynaClass);
        bean.set("fluentProperty", "testvalue");
        assertEquals("Property not set", "testvalue", obj.getStringProperty());
    }

    /**
     * The {@code contains()} method is not supported by the {@code WrapDynaBean} implementation class.
     */
    @Override
    public void testMappedContains() {

        try {
            assertTrue("Can see first key", bean.contains("mappedProperty", "First Key"));
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException t) {
            // Expected result
        } catch (final Throwable t) {
            fail("Exception: " + t);
        }

        try {
            assertTrue("Can not see unknown key", !bean.contains("mappedProperty", "Unknown Key"));
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException t) {
            // Expected result
        } catch (final Throwable t) {
            fail("Exception: " + t);
        }

    }

    /**
     * The {@code remove()} method is not supported by the {@code WrapDynaBean} implementation class.
     */
    @Override
    public void testMappedRemove() {

        try {
            assertTrue("Can see first key", bean.contains("mappedProperty", "First Key"));
            bean.remove("mappedProperty", "First Key");
            fail("Should have thrown UnsupportedOperationException");
            // assertTrue("Can not see first key",
            // !bean.contains("mappedProperty", "First Key"));
        } catch (final UnsupportedOperationException t) {
            // Expected result
        } catch (final Throwable t) {
            fail("Exception: " + t);
        }

        try {
            assertTrue("Can not see unknown key", !bean.contains("mappedProperty", "Unknown Key"));
            bean.remove("mappedProperty", "Unknown Key");
            fail("Should have thrown UnsupportedOperationException");
            // assertTrue("Can not see unknown key",
            // !bean.contains("mappedProperty", "Unknown Key"));
        } catch (final UnsupportedOperationException t) {
            // Expected result
        } catch (final Throwable t) {
            fail("Exception: " + t);
        }

    }

    /** Tests the newInstance implementation for WrapDynaClass */
    public void testNewInstance() throws Exception {
        final WrapDynaClass dynaClass = WrapDynaClass.createDynaClass(AlphaBean.class);
        final Object createdInstance = dynaClass.newInstance();
        assertTrue("Object type is WrapDynaBean", createdInstance instanceof WrapDynaBean);
        final WrapDynaBean dynaBean = (WrapDynaBean) createdInstance;
        assertTrue("Object type is AlphaBean", dynaBean.getInstance() instanceof AlphaBean);
    }

    /**
     * Serialization and deserialization tests. (WrapDynaBean is now serializable, although WrapDynaClass still is not)
     */
    @Override
    public void testSerialization() {

        // Create a bean and set a value
        final WrapDynaBean origBean = new WrapDynaBean(new TestBean());
        final Integer newValue = Integer.valueOf(789);
        assertEquals("origBean default", Integer.valueOf(123), origBean.get("intProperty"));
        origBean.set("intProperty", newValue);
        assertEquals("origBean new value", newValue, origBean.get("intProperty"));

        // Serialize/Deserialize & test value
        final WrapDynaBean bean = (WrapDynaBean) serializeDeserialize(origBean, "First Test");
        assertEquals("bean value", newValue, bean.get("intProperty"));

    }

    /**
     * The {@code set()} method.
     */
    public void testSimpleProperties() {

        checkSimplePropertyAccess();

    }
}
