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  
18  package org.apache.commons.beanutils2;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
23  import static org.junit.jupiter.api.Assertions.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertNull;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  import static org.junit.jupiter.api.Assertions.fail;
28  
29  import java.io.ByteArrayInputStream;
30  import java.io.ByteArrayOutputStream;
31  import java.io.NotSerializableException;
32  import java.io.ObjectInputStream;
33  import java.io.ObjectOutputStream;
34  import java.io.WriteAbortedException;
35  import java.util.ArrayList;
36  import java.util.HashMap;
37  import java.util.List;
38  import java.util.Map;
39  
40  import org.junit.jupiter.api.AfterEach;
41  import org.junit.jupiter.api.BeforeEach;
42  import org.junit.jupiter.api.Test;
43  
44  /**
45   * <p>
46   * Test Case for the {@code BasicDynaBean} implementation class. These tests were based on the ones in {@code PropertyUtilsTestCase} because the two classes
47   * provide similar levels of functionality.
48   * </p>
49   */
50  public class BasicDynaBeanTest {
51  
52      /**
53       * The set of property names we expect to have returned when calling {@code getDynaProperties()}. You should update this list when new properties are added
54       * to TestBean.
55       */
56      protected static final String[] properties = { "booleanProperty", "booleanSecond", "doubleProperty", "floatProperty", "intArray", "intIndexed",
57              "intProperty", "listIndexed", "longProperty", "mappedProperty", "mappedIntProperty", "nullProperty", "shortProperty", "stringArray",
58              "stringIndexed", "stringProperty", };
59  
60      /**
61       * The basic test bean for each test.
62       */
63      protected DynaBean bean;
64  
65      /**
66       * Create and return a {@code DynaClass} instance for our test {@code DynaBean}.
67       */
68      protected DynaClass createDynaClass() {
69          final int[] intArray = {};
70          final String[] stringArray = {};
71          final DynaClass dynaClass = new BasicDynaClass("TestDynaClass", null, new DynaProperty[] { new DynaProperty("booleanProperty", Boolean.TYPE),
72                  new DynaProperty("booleanSecond", Boolean.TYPE), new DynaProperty("doubleProperty", Double.TYPE), new DynaProperty("floatProperty", Float.TYPE),
73                  new DynaProperty("intArray", intArray.getClass()), new DynaProperty("intIndexed", intArray.getClass()),
74                  new DynaProperty("intProperty", Integer.TYPE), new DynaProperty("listIndexed", List.class), new DynaProperty("longProperty", Long.TYPE),
75                  new DynaProperty("mappedProperty", Map.class), new DynaProperty("mappedIntProperty", Map.class), new DynaProperty("nullProperty", String.class),
76                  new DynaProperty("shortProperty", Short.TYPE), new DynaProperty("stringArray", stringArray.getClass()),
77                  new DynaProperty("stringIndexed", stringArray.getClass()), new DynaProperty("stringProperty", String.class), });
78          return dynaClass;
79      }
80  
81      /**
82       * Sets up instance variables required by this test case.
83       */
84      @BeforeEach
85      public void setUp() throws Exception {
86          // Instantiate a new DynaBean instance
87          final DynaClass dynaClass = createDynaClass();
88          bean = dynaClass.newInstance();
89          // Initialize the DynaBean's property values (like TestBean)
90          bean.set("booleanProperty", Boolean.valueOf(true));
91          bean.set("booleanSecond", Boolean.valueOf(true));
92          bean.set("doubleProperty", Double.valueOf(321.0));
93          bean.set("floatProperty", Float.valueOf((float) 123.0));
94          final int[] intArray = { 0, 10, 20, 30, 40 };
95          bean.set("intArray", intArray);
96          final int[] intIndexed = { 0, 10, 20, 30, 40 };
97          bean.set("intIndexed", intIndexed);
98          bean.set("intProperty", Integer.valueOf(123));
99          final List<String> listIndexed = new ArrayList<>();
100         listIndexed.add("String 0");
101         listIndexed.add("String 1");
102         listIndexed.add("String 2");
103         listIndexed.add("String 3");
104         listIndexed.add("String 4");
105         bean.set("listIndexed", listIndexed);
106         bean.set("longProperty", Long.valueOf(321));
107         final HashMap<String, String> mappedProperty = new HashMap<>();
108         mappedProperty.put("First Key", "First Value");
109         mappedProperty.put("Second Key", "Second Value");
110         bean.set("mappedProperty", mappedProperty);
111         final HashMap<String, Integer> mappedIntProperty = new HashMap<>();
112         mappedIntProperty.put("One", Integer.valueOf(1));
113         mappedIntProperty.put("Two", Integer.valueOf(2));
114         bean.set("mappedIntProperty", mappedIntProperty);
115         // Property "nullProperty" is not initialized, so it should return null
116         bean.set("shortProperty", Short.valueOf((short) 987));
117         final String[] stringArray = { "String 0", "String 1", "String 2", "String 3", "String 4" };
118         bean.set("stringArray", stringArray);
119         final String[] stringIndexed = { "String 0", "String 1", "String 2", "String 3", "String 4" };
120         bean.set("stringIndexed", stringIndexed);
121         bean.set("stringProperty", "This is a string");
122     }
123 
124     /**
125      * Tear down instance variables required by this test case.
126      */
127     @AfterEach
128     public void tearDown() {
129 
130         bean = null;
131 
132     }
133 
134     /**
135      * Corner cases on getDynaProperty invalid arguments.
136      */
137     @Test
138     public void testGetDescriptorArguments() {
139         assertNull(bean.getDynaClass().getDynaProperty("unknown"));
140         assertThrows(NullPointerException.class, () -> bean.getDynaClass().getDynaProperty(null));
141     }
142 
143     /**
144      * Base for testGetDescriptorXxxxx() series of tests.
145      *
146      * @param name Name of the property to be retrieved
147      * @param type Expected class type of this property
148      */
149     protected void testGetDescriptorBase(final String name, final Class<?> type) {
150         final DynaProperty descriptor = bean.getDynaClass().getDynaProperty(name);
151         assertNotNull(descriptor, "Got descriptor");
152         assertEquals(type, descriptor.getType(), "Got correct type");
153     }
154 
155     /**
156      * Positive getDynaProperty on property {@code booleanProperty}.
157      */
158     @Test
159     public void testGetDescriptorBoolean() {
160         testGetDescriptorBase("booleanProperty", Boolean.TYPE);
161     }
162 
163     /**
164      * Positive getDynaProperty on property {@code doubleProperty}.
165      */
166     @Test
167     public void testGetDescriptorDouble() {
168         testGetDescriptorBase("doubleProperty", Double.TYPE);
169     }
170 
171     /**
172      * Positive getDynaProperty on property {@code floatProperty}.
173      */
174     @Test
175     public void testGetDescriptorFloat() {
176         testGetDescriptorBase("floatProperty", Float.TYPE);
177     }
178 
179     /**
180      * Positive getDynaProperty on property {@code intProperty}.
181      */
182     @Test
183     public void testGetDescriptorInt() {
184         testGetDescriptorBase("intProperty", Integer.TYPE);
185     }
186 
187     /**
188      * Positive getDynaProperty on property {@code longProperty}.
189      */
190     @Test
191     public void testGetDescriptorLong() {
192         testGetDescriptorBase("longProperty", Long.TYPE);
193     }
194 
195     /**
196      * Positive test for getDynaProperties(). Each property name listed in {@code properties} should be returned exactly once.
197      */
198     @Test
199     public void testGetDescriptors() {
200         final DynaProperty[] pd = bean.getDynaClass().getDynaProperties();
201         assertNotNull(pd, "Got descriptors");
202         final int[] count = new int[properties.length];
203         for (final DynaProperty element : pd) {
204             final String name = element.getName();
205             for (int j = 0; j < properties.length; j++) {
206                 if (name.equals(properties[j])) {
207                     count[j]++;
208                 }
209             }
210         }
211         for (int j = 0; j < properties.length; j++) {
212             if (count[j] < 0) {
213                 fail("Missing property " + properties[j]);
214             } else if (count[j] > 1) {
215                 fail("Duplicate property " + properties[j]);
216             }
217         }
218     }
219 
220     /**
221      * Positive getDynaProperty on property {@code booleanSecond} that uses an "is" method as the getter.
222      */
223     @Test
224     public void testGetDescriptorSecond() {
225         testGetDescriptorBase("booleanSecond", Boolean.TYPE);
226     }
227 
228     /**
229      * Positive getDynaProperty on property {@code shortProperty}.
230      */
231     @Test
232     public void testGetDescriptorShort() {
233         testGetDescriptorBase("shortProperty", Short.TYPE);
234     }
235 
236     /**
237      * Positive getDynaProperty on property {@code stringProperty}.
238      */
239     @Test
240     public void testGetDescriptorString() {
241         testGetDescriptorBase("stringProperty", String.class);
242     }
243 
244     /**
245      * Corner cases on getIndexedProperty invalid arguments.
246      */
247     @Test
248     public void testGetIndexedArguments() {
249         assertThrows(IndexOutOfBoundsException.class, () -> bean.get("intArray", -1));
250     }
251 
252     /**
253      * Positive and negative tests on getIndexedProperty valid arguments.
254      */
255     @Test
256     public void testGetIndexedValues() {
257         Object value = null;
258         for (int i = 0; i < 5; i++) {
259             value = bean.get("intArray", i);
260             assertNotNull(value, "intArray returned value " + i);
261             assertInstanceOf(Integer.class, value, "intArray returned Integer " + i);
262             assertEquals(i * 10, ((Integer) value).intValue(), "intArray returned correct " + i);
263             value = bean.get("intIndexed", i);
264             assertNotNull(value, "intIndexed returned value " + i);
265             assertInstanceOf(Integer.class, value, "intIndexed returned Integer " + i);
266             assertEquals(i * 10, ((Integer) value).intValue(), "intIndexed returned correct " + i);
267             value = bean.get("listIndexed", i);
268             assertNotNull(value, "listIndexed returned value " + i);
269             assertInstanceOf(String.class, value, "list returned String " + i);
270             assertEquals("String " + i, (String) value, "listIndexed returned correct " + i);
271             value = bean.get("stringArray", i);
272             assertNotNull(value, "stringArray returned value " + i);
273             assertInstanceOf(String.class, value, "stringArray returned String " + i);
274             assertEquals("String " + i, (String) value, "stringArray returned correct " + i);
275             value = bean.get("stringIndexed", i);
276             assertNotNull(value, "stringIndexed returned value " + i);
277             assertInstanceOf(String.class, value, "stringIndexed returned String " + i);
278             assertEquals("String " + i, (String) value, "stringIndexed returned correct " + i);
279         }
280     }
281 
282     /**
283      * Corner cases on getMappedProperty invalid arguments.
284      */
285     @Test
286     public void testGetMappedArguments() {
287         final Object value = bean.get("mappedProperty", "unknown");
288         assertNull(value, "Should not return a value");
289     }
290 
291     /**
292      * Positive and negative tests on getMappedProperty valid arguments.
293      */
294     @Test
295     public void testGetMappedValues() {
296         Object value = null;
297         value = bean.get("mappedProperty", "First Key");
298         assertEquals("First Value", value, "Can find first value");
299         value = bean.get("mappedProperty", "Second Key");
300         assertEquals("Second Value", value, "Can find second value");
301         value = bean.get("mappedProperty", "Third Key");
302         assertNull(value, "Can not find third value");
303     }
304 
305     /**
306      * Corner cases on getSimpleProperty invalid arguments.
307      */
308     @Test
309     public void testGetSimpleArguments() {
310         assertThrows(NullPointerException.class, () -> bean.get(null));
311     }
312 
313     /**
314      * Test getSimpleProperty on a boolean property.
315      */
316     @Test
317     public void testGetSimpleBoolean() {
318         final Object value = bean.get("booleanProperty");
319         assertNotNull(value, "Got a value");
320         assertInstanceOf(Boolean.class, value, "Got correct type");
321         assertTrue((Boolean) value, "Got correct value");
322     }
323 
324     /**
325      * Test getSimpleProperty on a double property.
326      */
327     @Test
328     public void testGetSimpleDouble() {
329         final Object value = bean.get("doubleProperty");
330         assertNotNull(value, "Got a value");
331         assertInstanceOf(Double.class, value, "Got correct type");
332         assertEquals(((Double) value).doubleValue(), 321.0, 0.005, "Got correct value");
333     }
334 
335     /**
336      * Test getSimpleProperty on a float property.
337      */
338     @Test
339     public void testGetSimpleFloat() {
340         final Object value = bean.get("floatProperty");
341         assertNotNull(value, "Got a value");
342         assertInstanceOf(Float.class, value, "Got correct type");
343         assertEquals(((Float) value).floatValue(), (float) 123.0, (float) 0.005, "Got correct value");
344     }
345 
346     /**
347      * Test getSimpleProperty on a int property.
348      */
349     @Test
350     public void testGetSimpleInt() {
351         final Object value = bean.get("intProperty");
352         assertNotNull(value, "Got a value");
353         assertInstanceOf(Integer.class, value, "Got correct type");
354         assertEquals(((Integer) value).intValue(), 123, "Got correct value");
355     }
356 
357     /**
358      * Test getSimpleProperty on a long property.
359      */
360     @Test
361     public void testGetSimpleLong() {
362         final Object value = bean.get("longProperty");
363         assertNotNull(value, "Got a value");
364         assertInstanceOf(Long.class, value, "Got correct type");
365         assertEquals(((Long) value).longValue(), 321, "Got correct value");
366     }
367 
368     /**
369      * Test getSimpleProperty on a short property.
370      */
371     @Test
372     public void testGetSimpleShort() {
373         final Object value = bean.get("shortProperty");
374         assertNotNull(value, "Got a value");
375         assertInstanceOf(Short.class, value, "Got correct type");
376         assertEquals(((Short) value).shortValue(), (short) 987, "Got correct value");
377     }
378 
379     /**
380      * Test getSimpleProperty on a String property.
381      */
382     @Test
383     public void testGetSimpleString() {
384         final Object value = bean.get("stringProperty");
385         assertNotNull(value, "Got a value");
386         assertInstanceOf(String.class, value, "Got correct type");
387         assertEquals((String) value, "This is a string", "Got correct value");
388     }
389 
390     /**
391      * Test {@code contains()} method for mapped properties.
392      */
393     @Test
394     public void testMappedContains() {
395         assertTrue(bean.contains("mappedProperty", "First Key"), "Can see first key");
396         assertFalse(bean.contains("mappedProperty", "Unknown Key"), "Can not see unknown key");
397     }
398 
399     /**
400      * Test {@code remove()} method for mapped properties.
401      */
402     @Test
403     public void testMappedRemove() {
404         assertTrue(bean.contains("mappedProperty", "First Key"), "Can see first key");
405         bean.remove("mappedProperty", "First Key");
406         assertFalse(bean.contains("mappedProperty", "First Key"), "Can not see first key");
407         assertFalse(bean.contains("mappedProperty", "Unknown Key"), "Can not see unknown key");
408         bean.remove("mappedProperty", "Unknown Key");
409         assertFalse(bean.contains("mappedProperty", "Unknown Key"), "Can not see unknown key");
410     }
411 
412     /**
413      * Test serialization and deserialization.
414      */
415     @Test
416     public void testNotSerializableException() throws Exception {
417         // Serialize the test bean
418         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
419         try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
420             assertThrows(NotSerializableException.class, () -> oos.writeObject(bean));
421             oos.flush();
422         }
423         // Deserialize the test bean
424         bean = null;
425         try (ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray())) {
426             final ObjectInputStream ois = new ObjectInputStream(bais);
427             assertThrows(WriteAbortedException.class, () -> bean = (DynaBean) ois.readObject());
428         }
429         assertNull(bean);
430     }
431 
432     /**
433      * Corner cases on setIndexedProperty invalid arguments.
434      */
435     @Test
436     public void testSetIndexedArguments() {
437         assertThrows(IndexOutOfBoundsException.class, () -> bean.set("intArray", -1, Integer.valueOf(0)));
438     }
439 
440     /**
441      * Positive and negative tests on setIndexedProperty valid arguments.
442      */
443     @Test
444     public void testSetIndexedValues() {
445         Object value = null;
446         bean.set("intArray", 0, Integer.valueOf(1));
447         value = bean.get("intArray", 0);
448         assertNotNull(value, "Returned new value 0");
449         assertInstanceOf(Integer.class, value, "Returned Integer new value 0");
450         assertEquals(1, ((Integer) value).intValue(), "Returned correct new value 0");
451         bean.set("intIndexed", 1, Integer.valueOf(11));
452         value = bean.get("intIndexed", 1);
453         assertNotNull(value, "Returned new value 1");
454         assertInstanceOf(Integer.class, value, "Returned Integer new value 1");
455         assertEquals(11, ((Integer) value).intValue(), "Returned correct new value 1");
456         bean.set("listIndexed", 2, "New Value 2");
457         value = bean.get("listIndexed", 2);
458         assertNotNull(value, "Returned new value 2");
459         assertInstanceOf(String.class, value, "Returned String new value 2");
460         assertEquals("New Value 2", (String) value, "Returned correct new value 2");
461         bean.set("stringArray", 3, "New Value 3");
462         value = bean.get("stringArray", 3);
463         assertNotNull(value, "Returned new value 3");
464         assertInstanceOf(String.class, value, "Returned String new value 3");
465         assertEquals("New Value 3", (String) value, "Returned correct new value 3");
466         bean.set("stringIndexed", 4, "New Value 4");
467         value = bean.get("stringIndexed", 4);
468         assertNotNull(value, "Returned new value 4");
469         assertInstanceOf(String.class, value, "Returned String new value 4");
470         assertEquals("New Value 4", (String) value, "Returned correct new value 4");
471     }
472 
473     /**
474      * Positive and negative tests on setMappedProperty valid arguments.
475      */
476     @Test
477     public void testSetMappedValues() {
478         bean.set("mappedProperty", "First Key", "New First Value");
479         assertEquals("New First Value", (String) bean.get("mappedProperty", "First Key"), "Can replace old value");
480         bean.set("mappedProperty", "Fourth Key", "Fourth Value");
481         assertEquals("Fourth Value", (String) bean.get("mappedProperty", "Fourth Key"), "Can set new value");
482     }
483 
484     /**
485      * Test setSimpleProperty on a boolean property.
486      */
487     @Test
488     public void testSetSimpleBoolean() {
489         final boolean oldValue = ((Boolean) bean.get("booleanProperty")).booleanValue();
490         final boolean newValue = !oldValue;
491         bean.set("booleanProperty", Boolean.valueOf(newValue));
492         assertEquals(newValue, ((Boolean) bean.get("booleanProperty")).booleanValue(), "Matched new value");
493     }
494 
495     /**
496      * Test setSimpleProperty on a double property.
497      */
498     @Test
499     public void testSetSimpleDouble() {
500         final double oldValue = ((Double) bean.get("doubleProperty")).doubleValue();
501         final double newValue = oldValue + 1.0;
502         bean.set("doubleProperty", Double.valueOf(newValue));
503         assertEquals(newValue, ((Double) bean.get("doubleProperty")).doubleValue(), 0.005, "Matched new value");
504     }
505 
506     /**
507      * Test setSimpleProperty on a float property.
508      */
509     @Test
510     public void testSetSimpleFloat() {
511         final float oldValue = ((Float) bean.get("floatProperty")).floatValue();
512         final float newValue = oldValue + (float) 1.0;
513         bean.set("floatProperty", Float.valueOf(newValue));
514         assertEquals(newValue, ((Float) bean.get("floatProperty")).floatValue(), (float) 0.005, "Matched new value");
515     }
516 
517     /**
518      * Test setSimpleProperty on a int property.
519      */
520     @Test
521     public void testSetSimpleInt() {
522         final int oldValue = ((Integer) bean.get("intProperty")).intValue();
523         final int newValue = oldValue + 1;
524         bean.set("intProperty", Integer.valueOf(newValue));
525         assertEquals(newValue, ((Integer) bean.get("intProperty")).intValue(), "Matched new value");
526     }
527 
528     /**
529      * Test setSimpleProperty on a long property.
530      */
531     @Test
532     public void testSetSimpleLong() {
533         final long oldValue = ((Long) bean.get("longProperty")).longValue();
534         final long newValue = oldValue + 1;
535         bean.set("longProperty", Long.valueOf(newValue));
536         assertEquals(newValue, ((Long) bean.get("longProperty")).longValue(), "Matched new value");
537     }
538 
539     /**
540      * Test setSimpleProperty on a short property.
541      */
542     @Test
543     public void testSetSimpleShort() {
544         final short oldValue = ((Short) bean.get("shortProperty")).shortValue();
545         final short newValue = (short) (oldValue + 1);
546         bean.set("shortProperty", Short.valueOf(newValue));
547         assertEquals(newValue, ((Short) bean.get("shortProperty")).shortValue(), "Matched new value");
548     }
549 
550     /**
551      * Test setSimpleProperty on a String property.
552      */
553     @Test
554     public void testSetSimpleString() {
555         final String oldValue = (String) bean.get("stringProperty");
556         final String newValue = oldValue + " Extra Value";
557         bean.set("stringProperty", newValue);
558         assertEquals(newValue, (String) bean.get("stringProperty"), "Matched new value");
559     }
560 
561 }