| /* |
| * 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.assertNotNull; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| import static org.junit.jupiter.api.Assertions.fail; |
| import static org.junit.jupiter.api.Assumptions.assumeFalse; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.Map; |
| import java.util.function.Function; |
| |
| import org.apache.commons.beanutils2.bugs.other.Jira87BeanFactory; |
| import org.apache.commons.collections4.map.AbstractMapTest; |
| import org.junit.jupiter.api.Test; |
| |
| /** |
| * Tests {@link BeanMap}. |
| */ |
| class BeanMapTest extends AbstractMapTest<BeanMap, String, Object> { |
| |
| public static class BeanThrowingExceptions extends BeanWithProperties { |
| |
| public String getValueThrowingException() { |
| throw new TestException(); |
| } |
| |
| public void setValueThrowingException(final String value) { |
| throw new TestException(); |
| } |
| } |
| |
| public static class BeanWithProperties { |
| private int someInt; |
| private long someLong; |
| private double someDouble; |
| private float someFloat; |
| private short someShort; |
| private byte someByte; |
| private char someChar; |
| private Integer someInteger; |
| private String someString; |
| private Object someObject; |
| |
| public byte getSomeByteValue() { |
| return someByte; |
| } |
| |
| public char getSomeCharValue() { |
| return someChar; |
| } |
| |
| public double getSomeDoubleValue() { |
| return someDouble; |
| } |
| |
| public float getSomeFloatValue() { |
| return someFloat; |
| } |
| |
| public Integer getSomeIntegerValue() { |
| return someInteger; |
| } |
| |
| public int getSomeIntValue() { |
| return someInt; |
| } |
| |
| public long getSomeLongValue() { |
| return someLong; |
| } |
| |
| public Object getSomeObjectValue() { |
| return someObject; |
| } |
| |
| public short getSomeShortValue() { |
| return someShort; |
| } |
| |
| public String getSomeStringValue() { |
| return someString; |
| } |
| |
| public void setSomeByteValue(final byte value) { |
| someByte = value; |
| } |
| |
| public void setSomeCharValue(final char value) { |
| someChar = value; |
| } |
| |
| public void setSomeDoubleValue(final double value) { |
| someDouble = value; |
| } |
| |
| public void setSomeFloatValue(final float value) { |
| someFloat = value; |
| } |
| |
| public void setSomeIntegerValue(final Integer value) { |
| someInteger = value; |
| } |
| |
| public void setSomeIntValue(final int value) { |
| someInt = value; |
| } |
| |
| public void setSomeLongValue(final long value) { |
| someLong = value; |
| } |
| |
| public void setSomeObjectValue(final Object value) { |
| someObject = value; |
| } |
| |
| public void setSomeShortValue(final short value) { |
| someShort = value; |
| } |
| |
| public void setSomeStringValue(final String value) { |
| someString = value; |
| } |
| } |
| |
| /** |
| * Exception for testing exception handling. |
| */ |
| public static class TestException extends RuntimeException { |
| private static final long serialVersionUID = 1L; |
| } |
| |
| /** |
| * An object value that will be stored in the bean map as a value. Need to save this externally so that we can make sure the object instances are equivalent |
| * since getSampleValues() would otherwise construct a new and different Object each time. |
| **/ |
| private final Object objectInFullMap = new Object(); |
| |
| /** |
| * note to self. The getter and setter methods were generated by copying the field declarations and using the following regular expression search and |
| * replace: |
| * |
| * From: private \(.*\) some\(.*\); To: public \1 getSome\2Value() { return some\2; } public void setSome\2Value(\1 value) { some\2 = value; } |
| * |
| * Also note: The sample keys and mappings were generated manually. |
| */ |
| public BeanMapTest() { |
| } |
| |
| @Override |
| protected Object computeIfAbsent(final String key, final Function<? super String, ? extends Object> mappingFunction) { |
| if (getMap().getBean() == null) { |
| // pretend this is a problem to make the test framework happy |
| throw new IllegalArgumentException(); |
| } |
| return super.computeIfAbsent(key, mappingFunction); |
| } |
| |
| @Override |
| public Object[] getNewSampleValues() { |
| final Object[] values = { Integer.valueOf(223), Long.valueOf(23341928234L), Double.valueOf(23423.34), Float.valueOf(213332.12f), |
| Short.valueOf((short) 234), Byte.valueOf((byte) 20), Character.valueOf('b'), Integer.valueOf(232), "SomeNewStringValue", new Object(), null, }; |
| return values; |
| } |
| |
| // note to self. The Sample keys were generated by copying the field |
| // declarations and using the following regular expression search and replace: |
| // |
| // From: |
| // private \(.*\) some\(.*\); |
| // To: |
| // "some\2Value", |
| // |
| // Then, I manually added the "class" key, which is a property that exists for |
| // all beans (and all objects for that matter. |
| @Override |
| public String[] getSampleKeys() { |
| final String[] keys = { "someIntValue", "someLongValue", "someDoubleValue", "someFloatValue", "someShortValue", "someByteValue", "someCharValue", |
| "someIntegerValue", "someStringValue", "someObjectValue", "class", }; |
| return keys; |
| } |
| |
| // note to self: the sample values were created manually |
| @Override |
| public Object[] getSampleValues() { |
| final Object[] values = { Integer.valueOf(1234), Long.valueOf(1298341928234L), Double.valueOf(123423.34), Float.valueOf(1213332.12f), |
| Short.valueOf((short) 134), Byte.valueOf((byte) 10), Character.valueOf('a'), Integer.valueOf(1432), "SomeStringValue", objectInFullMap, |
| BeanWithProperties.class, }; |
| return values; |
| } |
| |
| public String[] ignoredTests() { |
| // Ignore the serialization tests on collection views. |
| return new String[] { "TestBeanMap.bulkTestMapEntrySet.testCanonicalEmptyCollectionExists", |
| "TestBeanMap.bulkTestMapEntrySet.testCanonicalFullCollectionExists", "TestBeanMap.bulkTestMapKeySet.testCanonicalEmptyCollectionExists", |
| "TestBeanMap.bulkTestMapKeySet.testCanonicalFullCollectionExists", "TestBeanMap.bulkTestMapValues.testCanonicalEmptyCollectionExists", |
| "TestBeanMap.bulkTestMapValues.testCanonicalFullCollectionExists", "TestBeanMap.bulkTestMapEntrySet.testSimpleSerialization", |
| "TestBeanMap.bulkTestMapKeySet.testSimpleSerialization", "TestBeanMap.bulkTestMapEntrySet.testSerializeDeserializeThenCompare", |
| "TestBeanMap.bulkTestMapKeySet.testSerializeDeserializeThenCompare" }; |
| } |
| |
| /** |
| * The mappings in a BeanMap are fixed on the properties the underlying bean has. Adding and removing mappings is not possible, thus this method is |
| * overridden to return false. |
| */ |
| @Override |
| public boolean isPutAddSupported() { |
| return false; |
| } |
| |
| /** |
| * The mappings in a BeanMap are fixed on the properties the underlying bean has. Adding and removing mappings is not possible, thus this method is |
| * overridden to return false. |
| */ |
| @Override |
| public boolean isRemoveSupported() { |
| return false; |
| } |
| |
| @Override |
| public BeanMap makeFullMap() { |
| // note: These values must match (i.e. .equals() must return true) |
| // those returned from getSampleValues(). |
| final BeanWithProperties bean = new BeanWithProperties(); |
| bean.setSomeIntValue(1234); |
| bean.setSomeLongValue(1298341928234L); |
| bean.setSomeDoubleValue(123423.34); |
| bean.setSomeFloatValue(1213332.12f); |
| bean.setSomeShortValue((short) 134); |
| bean.setSomeByteValue((byte) 10); |
| bean.setSomeCharValue('a'); |
| bean.setSomeIntegerValue(Integer.valueOf(1432)); |
| bean.setSomeStringValue("SomeStringValue"); |
| bean.setSomeObjectValue(objectInFullMap); |
| return new BeanMap(bean); |
| } |
| |
| @Override |
| public BeanMap makeObject() { |
| return new BeanMap(); |
| } |
| |
| @Override |
| protected Object putIfAbsent(final String key, final Object value) { |
| if (getMap().getBean() == null) { |
| // pretend this is a problem to make the test framework happy |
| throw new IllegalArgumentException(); |
| } |
| return super.putIfAbsent(key, value); |
| } |
| |
| @Test |
| void testBeanMapClone() { |
| final BeanMap map = (BeanMap) makeFullMap(); |
| try { |
| final BeanMap map2 = (BeanMap) map.clone(); |
| |
| // make sure containsKey is working to verify the bean was cloned |
| // ok, and the read methods were properly initialized |
| final Object[] keys = getSampleKeys(); |
| for (final Object key : keys) { |
| assertTrue(map2.containsKey(key), "Cloned BeanMap should contain the same keys"); |
| } |
| } catch (final CloneNotSupportedException exception) { |
| fail("BeanMap.clone() should not throw a CloneNotSupportedException when clone should succeed."); |
| } |
| } |
| |
| @Test |
| void testBeanMapPutAllWriteable() { |
| final BeanMap map1 = (BeanMap) makeFullMap(); |
| final BeanMap map2 = (BeanMap) makeFullMap(); |
| map2.put("someIntValue", Integer.valueOf(0)); |
| map1.putAllWriteable(map2); |
| assertEquals(map1.get("someIntValue"), Integer.valueOf(0)); |
| } |
| |
| /** |
| * Test that the cause of exception thrown by clear() is initialized. |
| */ |
| @Test |
| void testExceptionThrowFromClear() { |
| try { |
| final Object bean = Jira87BeanFactory.createMappedPropertyBean(); |
| final BeanMap map = new BeanMap(bean); |
| map.clear(); |
| fail("clear() - expected UnsupportedOperationException"); |
| } catch (final UnsupportedOperationException e) { |
| Throwable cause = null; |
| try { |
| cause = (Throwable) PropertyUtils.getProperty(e, "cause"); |
| } catch (final Exception e2) { |
| fail("Retrieving the cause threw " + e2); |
| } |
| assertNotNull(cause, "Cause null"); |
| assertEquals(IllegalAccessException.class, cause.getClass(), "Cause"); |
| } |
| } |
| |
| /** |
| * Test that the cause of exception thrown by a clone() is initialized. |
| */ |
| @Test |
| void testExceptionThrowFromClone() { |
| // Test cloning a non-public bean (instantiation exception) |
| try { |
| final Object bean = Jira87BeanFactory.createMappedPropertyBean(); |
| final BeanMap map = new BeanMap(bean); |
| map.clone(); |
| fail("Non-public bean clone() - expected CloneNotSupportedException"); |
| } catch (final CloneNotSupportedException e) { |
| Throwable cause = null; |
| try { |
| cause = (Throwable) PropertyUtils.getProperty(e, "cause"); |
| } catch (final Exception e2) { |
| fail("Non-public bean - retrieving the cause threw " + e2); |
| } |
| assertNotNull(cause, "Non-public bean cause null"); |
| assertEquals(IllegalAccessException.class, cause.getClass(), "Non-public bean cause"); |
| } |
| |
| // Test cloning a bean that throws exception |
| try { |
| final BeanMap map = new BeanMap(new BeanThrowingExceptions()); |
| map.clone(); |
| fail("Setter Exception clone() - expected CloneNotSupportedException"); |
| } catch (final CloneNotSupportedException e) { |
| Throwable cause = null; |
| try { |
| cause = (Throwable) PropertyUtils.getProperty(e, "cause"); |
| } catch (final Exception e2) { |
| fail("Setter Exception - retrieving the cause threw " + e2); |
| } |
| assertNotNull(cause, "Setter Exception cause null"); |
| assertEquals(IllegalArgumentException.class, cause.getClass(), "Setter Exception cause"); |
| } |
| } |
| |
| /** |
| * Test that the cause of exception thrown by put() is initialized. |
| */ |
| @Test |
| void testExceptionThrowFromPut() { |
| try { |
| final Map<String, Object> map = new BeanMap(new BeanThrowingExceptions()); |
| map.put("valueThrowingException", "value"); |
| fail("Setter exception - expected IllegalArgumentException"); |
| } catch (final IllegalArgumentException e) { |
| Throwable cause1 = null; |
| Throwable cause2 = null; |
| try { |
| cause1 = (Throwable) PropertyUtils.getProperty(e, "cause"); |
| cause2 = (Throwable) PropertyUtils.getProperty(e, "cause.cause"); |
| } catch (final Exception e2) { |
| fail("Setter exception - retrieving the cause threw " + e2); |
| } |
| assertNotNull(cause1, "Setter exception cause 1 null"); |
| assertEquals(InvocationTargetException.class, cause1.getClass(), "Setter exception cause 1"); |
| assertNotNull(cause2, "Setter exception cause 2 null"); |
| assertEquals(TestException.class, cause2.getClass(), "Setter exception cause 2"); |
| } |
| } |
| |
| /** |
| * Test the default transformers using the getTypeTransformer() method |
| */ |
| @Test |
| void testGetTypeTransformerMethod() { |
| final BeanMap beanMap = new BeanMap(); |
| assertEquals(Boolean.TRUE, beanMap.getTypeTransformer(Boolean.TYPE).apply("true"), "Boolean.TYPE"); |
| assertEquals(Character.valueOf('B'), beanMap.getTypeTransformer(Character.TYPE).apply("BCD"), "Character.TYPE"); |
| assertEquals(Byte.valueOf((byte) 1), beanMap.getTypeTransformer(Byte.TYPE).apply("1"), "Byte.TYPE"); |
| assertEquals(Short.valueOf((short) 2), beanMap.getTypeTransformer(Short.TYPE).apply("2"), "Short.TYPE"); |
| assertEquals(Integer.valueOf(3), beanMap.getTypeTransformer(Integer.TYPE).apply("3"), "Integer.TYPE"); |
| assertEquals(Long.valueOf(4), beanMap.getTypeTransformer(Long.TYPE).apply("4"), "Long.TYPE"); |
| assertEquals(Float.valueOf("5"), beanMap.getTypeTransformer(Float.TYPE).apply("5"), "Float.TYPE"); |
| assertEquals(Double.valueOf("6"), beanMap.getTypeTransformer(Double.TYPE).apply("6"), "Double.TYPE"); |
| } |
| |
| /** |
| * Need to override this method because the "clear()" method on the bean map just returns the bean properties to their default states. It does not actually |
| * remove the mappings as per the map contract. The default testClear() methods checks that the clear method throws an UnsupportedOperationException since |
| * this class is not add/remove modifiable. In our case though, we do not always throw that exception. |
| */ |
| @Override |
| @Test |
| public void testMapClear() { |
| // TODO: make sure a call to BeanMap.clear returns the bean to its |
| // default initialization values. |
| } |
| |
| /** |
| * Need to override this method because the "put()" method on the bean doesn't work for this type of Map. |
| */ |
| @Override |
| @Test |
| public void testMapPut() { |
| // see testBeanMapPutAllWriteable |
| } |
| |
| @Test |
| void testMethodAccessor() throws Exception { |
| final BeanMap map = (BeanMap) makeFullMap(); |
| final Method method = BeanWithProperties.class.getDeclaredMethod("getSomeIntegerValue"); |
| assertEquals(method, map.getReadMethod("someIntegerValue")); |
| } |
| |
| @Test |
| void testMethodMutator() throws Exception { |
| final BeanMap map = (BeanMap) makeFullMap(); |
| final Method method = BeanWithProperties.class.getDeclaredMethod("setSomeIntegerValue", Integer.class); |
| assertEquals(method, map.getWriteMethod("someIntegerValue")); |
| } |
| |
| @Override |
| // TODO @Test |
| public void testReplaceAll() { |
| assumeFalse(getMap().keySet().stream().anyMatch(k -> getMap().getWriteMethod(k) == null)); |
| super.testReplaceAll(); |
| } |
| |
| /** |
| * Values is a dead copy in BeanMap, so refresh each time. |
| */ |
| @Override |
| public void verifyValues() { |
| values = map.values(); |
| super.verifyValues(); |
| } |
| } |