View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.beanutils2;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertNotNull;
21  import static org.junit.jupiter.api.Assertions.assertTrue;
22  import static org.junit.jupiter.api.Assertions.fail;
23  import static org.junit.jupiter.api.Assumptions.assumeFalse;
24  
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.util.Map;
28  import java.util.function.Function;
29  
30  import org.apache.commons.beanutils2.bugs.other.Jira87BeanFactory;
31  import org.apache.commons.collections4.map.AbstractMapTest;
32  
33  /**
34   * Tests {@link BeanMap}.
35   */
36  public class BeanMapTest extends AbstractMapTest<BeanMap, String, Object> {
37  
38      public static class BeanThrowingExceptions extends BeanWithProperties {
39  
40          public String getValueThrowingException() {
41              throw new TestException();
42          }
43  
44          public void setValueThrowingException(final String value) {
45              throw new TestException();
46          }
47      }
48  
49      public static class BeanWithProperties {
50          private int someInt;
51          private long someLong;
52          private double someDouble;
53          private float someFloat;
54          private short someShort;
55          private byte someByte;
56          private char someChar;
57          private Integer someInteger;
58          private String someString;
59          private Object someObject;
60  
61          public byte getSomeByteValue() {
62              return someByte;
63          }
64  
65          public char getSomeCharValue() {
66              return someChar;
67          }
68  
69          public double getSomeDoubleValue() {
70              return someDouble;
71          }
72  
73          public float getSomeFloatValue() {
74              return someFloat;
75          }
76  
77          public Integer getSomeIntegerValue() {
78              return someInteger;
79          }
80  
81          public int getSomeIntValue() {
82              return someInt;
83          }
84  
85          public long getSomeLongValue() {
86              return someLong;
87          }
88  
89          public Object getSomeObjectValue() {
90              return someObject;
91          }
92  
93          public short getSomeShortValue() {
94              return someShort;
95          }
96  
97          public String getSomeStringValue() {
98              return someString;
99          }
100 
101         public void setSomeByteValue(final byte value) {
102             someByte = value;
103         }
104 
105         public void setSomeCharValue(final char value) {
106             someChar = value;
107         }
108 
109         public void setSomeDoubleValue(final double value) {
110             someDouble = value;
111         }
112 
113         public void setSomeFloatValue(final float value) {
114             someFloat = value;
115         }
116 
117         public void setSomeIntegerValue(final Integer value) {
118             someInteger = value;
119         }
120 
121         public void setSomeIntValue(final int value) {
122             someInt = value;
123         }
124 
125         public void setSomeLongValue(final long value) {
126             someLong = value;
127         }
128 
129         public void setSomeObjectValue(final Object value) {
130             someObject = value;
131         }
132 
133         public void setSomeShortValue(final short value) {
134             someShort = value;
135         }
136 
137         public void setSomeStringValue(final String value) {
138             someString = value;
139         }
140     }
141 
142     /**
143      * Exception for testing exception handling.
144      */
145     public static class TestException extends RuntimeException {
146         private static final long serialVersionUID = 1L;
147     }
148 
149     /**
150      * 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
151      * since getSampleValues() would otherwise construct a new and different Object each time.
152      **/
153     private final Object objectInFullMap = new Object();
154 
155     /**
156      * note to self. The getter and setter methods were generated by copying the field declarations and using the following regular expression search and
157      * replace:
158      *
159      * From: private \(.*\) some\(.*\); To: public \1 getSome\2Value() { return some\2; } public void setSome\2Value(\1 value) { some\2 = value; }
160      *
161      * Also note: The sample keys and mappings were generated manually.
162      */
163     public BeanMapTest() {
164     }
165 
166     @Override
167     protected Object computeIfAbsent(final String key, final Function<? super String, ? extends Object> mappingFunction) {
168         if (getMap().getBean() == null) {
169             // pretend this is a problem to make the test framework happy
170             throw new IllegalArgumentException();
171         }
172         return super.computeIfAbsent(key, mappingFunction);
173     }
174 
175     @Override
176     public Object[] getNewSampleValues() {
177         final Object[] values = { Integer.valueOf(223), Long.valueOf(23341928234L), Double.valueOf(23423.34), Float.valueOf(213332.12f),
178                 Short.valueOf((short) 234), Byte.valueOf((byte) 20), Character.valueOf('b'), Integer.valueOf(232), "SomeNewStringValue", new Object(), null, };
179         return values;
180     }
181 
182     // note to self. The Sample keys were generated by copying the field
183     // declarations and using the following regular expression search and replace:
184     //
185     // From:
186     // private \(.*\) some\(.*\);
187     // To:
188     // "some\2Value",
189     //
190     // Then, I manually added the "class" key, which is a property that exists for
191     // all beans (and all objects for that matter.
192     @Override
193     public String[] getSampleKeys() {
194         final String[] keys = { "someIntValue", "someLongValue", "someDoubleValue", "someFloatValue", "someShortValue", "someByteValue", "someCharValue",
195                 "someIntegerValue", "someStringValue", "someObjectValue", "class", };
196         return keys;
197     }
198 
199     // note to self: the sample values were created manually
200     @Override
201     public Object[] getSampleValues() {
202         final Object[] values = { Integer.valueOf(1234), Long.valueOf(1298341928234L), Double.valueOf(123423.34), Float.valueOf(1213332.12f),
203                 Short.valueOf((short) 134), Byte.valueOf((byte) 10), Character.valueOf('a'), Integer.valueOf(1432), "SomeStringValue", objectInFullMap,
204                 BeanWithProperties.class, };
205         return values;
206     }
207 
208     public String[] ignoredTests() {
209         // Ignore the serialization tests on collection views.
210         return new String[] { "TestBeanMap.bulkTestMapEntrySet.testCanonicalEmptyCollectionExists",
211                 "TestBeanMap.bulkTestMapEntrySet.testCanonicalFullCollectionExists", "TestBeanMap.bulkTestMapKeySet.testCanonicalEmptyCollectionExists",
212                 "TestBeanMap.bulkTestMapKeySet.testCanonicalFullCollectionExists", "TestBeanMap.bulkTestMapValues.testCanonicalEmptyCollectionExists",
213                 "TestBeanMap.bulkTestMapValues.testCanonicalFullCollectionExists", "TestBeanMap.bulkTestMapEntrySet.testSimpleSerialization",
214                 "TestBeanMap.bulkTestMapKeySet.testSimpleSerialization", "TestBeanMap.bulkTestMapEntrySet.testSerializeDeserializeThenCompare",
215                 "TestBeanMap.bulkTestMapKeySet.testSerializeDeserializeThenCompare" };
216     }
217 
218     /**
219      * 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
220      * overridden to return false.
221      */
222     @Override
223     public boolean isPutAddSupported() {
224         return false;
225     }
226 
227     /**
228      * 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
229      * overridden to return false.
230      */
231     @Override
232     public boolean isRemoveSupported() {
233         return false;
234     }
235 
236     @Override
237     public BeanMap makeFullMap() {
238         // note: These values must match (i.e. .equals() must return true)
239         // those returned from getSampleValues().
240         final BeanWithProperties bean = new BeanWithProperties();
241         bean.setSomeIntValue(1234);
242         bean.setSomeLongValue(1298341928234L);
243         bean.setSomeDoubleValue(123423.34);
244         bean.setSomeFloatValue(1213332.12f);
245         bean.setSomeShortValue((short) 134);
246         bean.setSomeByteValue((byte) 10);
247         bean.setSomeCharValue('a');
248         bean.setSomeIntegerValue(Integer.valueOf(1432));
249         bean.setSomeStringValue("SomeStringValue");
250         bean.setSomeObjectValue(objectInFullMap);
251         return new BeanMap(bean);
252     }
253 
254     @Override
255     public BeanMap makeObject() {
256         return new BeanMap();
257     }
258 
259     @Override
260     protected Object putIfAbsent(final String key, final Object value) {
261         if (getMap().getBean() == null) {
262             // pretend this is a problem to make the test framework happy
263             throw new IllegalArgumentException();
264         }
265         return super.putIfAbsent(key, value);
266     }
267 
268     public void testBeanMapClone() {
269         final BeanMap map = (BeanMap) makeFullMap();
270         try {
271             final BeanMap map2 = (BeanMap) map.clone();
272 
273             // make sure containsKey is working to verify the bean was cloned
274             // ok, and the read methods were properly initialized
275             final Object[] keys = getSampleKeys();
276             for (final Object key : keys) {
277                 assertTrue(map2.containsKey(key), "Cloned BeanMap should contain the same keys");
278             }
279         } catch (final CloneNotSupportedException exception) {
280             fail("BeanMap.clone() should not throw a " + "CloneNotSupportedException when clone should succeed.");
281         }
282     }
283 
284     public void testBeanMapPutAllWriteable() {
285         final BeanMap map1 = (BeanMap) makeFullMap();
286         final BeanMap map2 = (BeanMap) makeFullMap();
287         map2.put("someIntValue", Integer.valueOf(0));
288         map1.putAllWriteable(map2);
289         assertEquals(map1.get("someIntValue"), Integer.valueOf(0));
290     }
291 
292     /**
293      * Test that the cause of exception thrown by clear() is initialized.
294      */
295     public void testExceptionThrowFromClear() {
296         try {
297             final Object bean = Jira87BeanFactory.createMappedPropertyBean();
298             final BeanMap map = new BeanMap(bean);
299             map.clear();
300             fail("clear() - expected UnsupportedOperationException");
301         } catch (final UnsupportedOperationException e) {
302             Throwable cause = null;
303             try {
304                 cause = (Throwable) PropertyUtils.getProperty(e, "cause");
305             } catch (final Exception e2) {
306                 fail("Retrieving the cause threw " + e2);
307             }
308             assertNotNull(cause, "Cause null");
309             assertEquals(IllegalAccessException.class, cause.getClass(), "Cause");
310         }
311     }
312 
313     /**
314      * Test that the cause of exception thrown by a clone() is initialized.
315      */
316     public void testExceptionThrowFromClone() {
317         // Test cloning a non-public bean (instantiation exception)
318         try {
319             final Object bean = Jira87BeanFactory.createMappedPropertyBean();
320             final BeanMap map = new BeanMap(bean);
321             map.clone();
322             fail("Non-public bean clone() - expected CloneNotSupportedException");
323         } catch (final CloneNotSupportedException e) {
324             Throwable cause = null;
325             try {
326                 cause = (Throwable) PropertyUtils.getProperty(e, "cause");
327             } catch (final Exception e2) {
328                 fail("Non-public bean - retrieving the cause threw " + e2);
329             }
330             assertNotNull(cause, "Non-public bean cause null");
331             assertEquals(IllegalAccessException.class, cause.getClass(), "Non-public bean cause");
332         }
333 
334         // Test cloning a bean that throws exception
335         try {
336             final BeanMap map = new BeanMap(new BeanThrowingExceptions());
337             map.clone();
338             fail("Setter Exception clone() - expected CloneNotSupportedException");
339         } catch (final CloneNotSupportedException e) {
340             Throwable cause = null;
341             try {
342                 cause = (Throwable) PropertyUtils.getProperty(e, "cause");
343             } catch (final Exception e2) {
344                 fail("Setter Exception - retrieving the cause threw " + e2);
345             }
346             assertNotNull(cause, "Setter Exception cause null");
347             assertEquals(IllegalArgumentException.class, cause.getClass(), "Setter Exception cause");
348         }
349     }
350 
351     /**
352      * Test that the cause of exception thrown by put() is initialized.
353      */
354     public void testExceptionThrowFromPut() {
355         try {
356             final Map<String, Object> map = new BeanMap(new BeanThrowingExceptions());
357             map.put("valueThrowingException", "value");
358             fail("Setter exception - expected IllegalArgumentException");
359         } catch (final IllegalArgumentException e) {
360             Throwable cause1 = null;
361             Throwable cause2 = null;
362             try {
363                 cause1 = (Throwable) PropertyUtils.getProperty(e, "cause");
364                 cause2 = (Throwable) PropertyUtils.getProperty(e, "cause.cause");
365             } catch (final Exception e2) {
366                 fail("Setter exception - retrieving the cause threw " + e2);
367             }
368             assertNotNull(cause1, "Setter exception cause 1 null");
369             assertEquals(InvocationTargetException.class, cause1.getClass(), "Setter exception cause 1");
370             assertNotNull(cause2, "Setter exception cause 2 null");
371             assertEquals(TestException.class, cause2.getClass(), "Setter exception cause 2");
372         }
373     }
374 
375     /**
376      * Test the default transformers using the getTypeTransformer() method
377      */
378     public void testGetTypeTransformerMethod() {
379         final BeanMap beanMap = new BeanMap();
380         assertEquals(Boolean.TRUE, beanMap.getTypeTransformer(Boolean.TYPE).apply("true"), "Boolean.TYPE");
381         assertEquals(Character.valueOf('B'), beanMap.getTypeTransformer(Character.TYPE).apply("BCD"), "Character.TYPE");
382         assertEquals(Byte.valueOf((byte) 1), beanMap.getTypeTransformer(Byte.TYPE).apply("1"), "Byte.TYPE");
383         assertEquals(Short.valueOf((short) 2), beanMap.getTypeTransformer(Short.TYPE).apply("2"), "Short.TYPE");
384         assertEquals(Integer.valueOf(3), beanMap.getTypeTransformer(Integer.TYPE).apply("3"), "Integer.TYPE");
385         assertEquals(Long.valueOf(4), beanMap.getTypeTransformer(Long.TYPE).apply("4"), "Long.TYPE");
386         assertEquals(Float.valueOf("5"), beanMap.getTypeTransformer(Float.TYPE).apply("5"), "Float.TYPE");
387         assertEquals(Double.valueOf("6"), beanMap.getTypeTransformer(Double.TYPE).apply("6"), "Double.TYPE");
388     }
389 
390     /**
391      * 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
392      * remove the mappings as per the map contract. The default testClear() methods checks that the clear method throws an UnsupportedOperationException since
393      * this class is not add/remove modifiable. In our case though, we do not always throw that exception.
394      */
395     @Override
396     public void testMapClear() {
397         // TODO: make sure a call to BeanMap.clear returns the bean to its
398         // default initialization values.
399     }
400 
401     /**
402      * Need to override this method because the "put()" method on the bean doesn't work for this type of Map.
403      */
404     @Override
405     public void testMapPut() {
406         // see testBeanMapPutAllWriteable
407     }
408 
409     public void testMethodAccessor() throws Exception {
410         final BeanMap map = (BeanMap) makeFullMap();
411         final Method method = BeanWithProperties.class.getDeclaredMethod("getSomeIntegerValue");
412         assertEquals(method, map.getReadMethod("someIntegerValue"));
413     }
414 
415     public void testMethodMutator() throws Exception {
416         final BeanMap map = (BeanMap) makeFullMap();
417         final Method method = BeanWithProperties.class.getDeclaredMethod("setSomeIntegerValue", Integer.class);
418         assertEquals(method, map.getWriteMethod("someIntegerValue"));
419     }
420 
421     @Override
422     public void testReplaceAll() {
423         assumeFalse(getMap().keySet().stream().anyMatch(k -> getMap().getWriteMethod(k) == null));
424         super.testReplaceAll();
425     }
426 
427     /**
428      * Values is a dead copy in BeanMap, so refresh each time.
429      */
430     @Override
431     public void verifyValues() {
432         values = map.values();
433         super.verifyValues();
434     }
435 }