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.configuration2.beanutils;
18  
19  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertSame;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  import static org.mockito.Mockito.mock;
27  
28  import java.lang.reflect.Constructor;
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.Collections;
32  import java.util.HashMap;
33  import java.util.Map;
34  
35  import org.apache.commons.configuration2.PropertiesConfiguration;
36  import org.apache.commons.configuration2.convert.ConversionHandler;
37  import org.apache.commons.configuration2.convert.DefaultConversionHandler;
38  import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
39  import org.junit.jupiter.api.BeforeEach;
40  import org.junit.jupiter.api.Test;
41  
42  /**
43   * Test class for DefaultBeanFactory.
44   */
45  public class TestDefaultBeanFactory {
46  
47      /** Constant for the test value of the string property. */
48      private static final String TEST_STRING = "testString";
49  
50      /** Constant for the test value of the numeric property. */
51      private static final int TEST_INT = 42;
52  
53      /**
54       * Creates a bean creation context for a create operation.
55       *
56       * @param cls the bean class
57       * @param decl the bean declaration
58       * @return the new creation context
59       */
60      private static BeanCreationContext createBcc(final Class<?> cls, final BeanDeclaration decl) {
61          return new BeanCreationContext() {
62              private final BeanHelper beanHelper = new BeanHelper();
63  
64              @Override
65              public Object createBean(final BeanDeclaration data) {
66                  return beanHelper.createBean(data);
67              }
68  
69              @Override
70              public Class<?> getBeanClass() {
71                  return cls;
72              }
73  
74              @Override
75              public BeanDeclaration getBeanDeclaration() {
76                  return decl;
77              }
78  
79              @Override
80              public Object getParameter() {
81                  return null;
82              }
83  
84              @Override
85              public void initBean(final Object bean, final BeanDeclaration data) {
86                  beanHelper.initBean(bean, data);
87              }
88          };
89      }
90  
91      /**
92       * Returns an initialized bean declaration.
93       *
94       * @return the bean declaration
95       */
96      private static BeanDeclarationTestImpl setUpBeanDeclaration() {
97          final BeanDeclarationTestImpl data = new BeanDeclarationTestImpl();
98          final Map<String, Object> properties = new HashMap<>();
99          properties.put("stringValue", TEST_STRING);
100         properties.put("intValue", String.valueOf(TEST_INT));
101         data.setBeanProperties(properties);
102         final BeanDeclarationTestImpl buddyData = new BeanDeclarationTestImpl();
103         final Map<String, Object> properties2 = new HashMap<>();
104         properties2.put("stringValue", "Another test string");
105         properties2.put("intValue", 100);
106         buddyData.setBeanProperties(properties2);
107         buddyData.setBeanClassName(BeanCreationTestBean.class.getName());
108 
109         final Map<String, Object> nested = new HashMap<>();
110         nested.put("buddy", buddyData);
111         data.setNestedBeanDeclarations(nested);
112         return data;
113     }
114 
115     /** The object to be tested. */
116     private DefaultBeanFactory factory;
117 
118     @BeforeEach
119     public void setUp() {
120         factory = new DefaultBeanFactory();
121     }
122 
123     /**
124      * Tests creating a bean.
125      */
126     @Test
127     void testCreateBean() throws Exception {
128         final BeanDeclarationTestImpl decl = new BeanDeclarationTestImpl();
129         final Map<String, Object> props = new HashMap<>();
130         props.put("throwExceptionOnMissing", Boolean.TRUE);
131         decl.setBeanProperties(props);
132         final Object bean = factory.createBean(createBcc(PropertiesConfiguration.class, decl));
133         assertNotNull(bean);
134         assertEquals(PropertiesConfiguration.class, bean.getClass());
135         final PropertiesConfiguration config = (PropertiesConfiguration) bean;
136         assertTrue(config.isThrowExceptionOnMissing());
137     }
138 
139     /**
140      * Tests whether a bean can be created by calling its constructor.
141      */
142     @Test
143     void testCreateBeanConstructor() throws Exception {
144         final BeanDeclarationTestImpl decl = new BeanDeclarationTestImpl();
145         final Collection<ConstructorArg> args = new ArrayList<>();
146         args.add(ConstructorArg.forValue("test"));
147         args.add(ConstructorArg.forValue("42"));
148         decl.setConstructorArgs(args);
149         final BeanCreationTestCtorBean bean = (BeanCreationTestCtorBean) factory.createBean(createBcc(BeanCreationTestCtorBean.class, decl));
150         assertEquals("test", bean.getStringValue());
151         assertEquals(42, bean.getIntValue());
152     }
153 
154     /**
155      * Tests whether nested bean declarations in constructor arguments are taken into account.
156      */
157     @Test
158     void testCreateBeanConstructorNestedBean() throws Exception {
159         final BeanDeclarationTestImpl declNested = new BeanDeclarationTestImpl();
160         final Collection<ConstructorArg> args = new ArrayList<>();
161         args.add(ConstructorArg.forValue("test", String.class.getName()));
162         declNested.setConstructorArgs(args);
163         declNested.setBeanClassName(BeanCreationTestCtorBean.class.getName());
164         final BeanDeclarationTestImpl decl = new BeanDeclarationTestImpl();
165         decl.setConstructorArgs(Collections.singleton(ConstructorArg.forBeanDeclaration(declNested, BeanCreationTestBean.class.getName())));
166         final BeanCreationTestCtorBean bean = (BeanCreationTestCtorBean) factory.createBean(createBcc(BeanCreationTestCtorBean.class, decl));
167         assertNotNull(bean.getBuddy());
168         assertEquals("test", bean.getBuddy().getStringValue());
169     }
170 
171     /**
172      * Tests whether a correct default conversion handler is set.
173      */
174     @Test
175     void testDefaultConversionHandler() {
176         assertSame(DefaultConversionHandler.INSTANCE, factory.getConversionHandler());
177     }
178 
179     /**
180      * Tests whether ambiguous constructor arguments are detected.
181      */
182     @Test
183     void testFindMatchingConstructorAmbiguous() {
184         final BeanDeclarationTestImpl decl = new BeanDeclarationTestImpl();
185         final Collection<ConstructorArg> args = new ArrayList<>();
186         args.add(ConstructorArg.forValue(TEST_STRING));
187         decl.setConstructorArgs(args);
188         assertThrows(ConfigurationRuntimeException.class, () -> DefaultBeanFactory.findMatchingConstructor(BeanCreationTestCtorBean.class, decl));
189     }
190 
191     /**
192      * Tests whether a matching constructor is found if the number of arguments is unique.
193      */
194     @Test
195     void testFindMatchingConstructorArgCount() {
196         final BeanDeclarationTestImpl decl = new BeanDeclarationTestImpl();
197         final Collection<ConstructorArg> args = new ArrayList<>();
198         args.add(ConstructorArg.forValue(TEST_STRING));
199         args.add(ConstructorArg.forValue(String.valueOf(TEST_INT)));
200         decl.setConstructorArgs(args);
201         final Constructor<BeanCreationTestCtorBean> ctor = DefaultBeanFactory.findMatchingConstructor(BeanCreationTestCtorBean.class, decl);
202         final Class<?>[] paramTypes = ctor.getParameterTypes();
203         assertArrayEquals(new Class<?>[] {String.class, Integer.TYPE}, paramTypes);
204     }
205 
206     /**
207      * Tests whether explicit type declarations are used to resolve ambiguous parameter types.
208      */
209     @Test
210     void testFindMatchingConstructorExplicitType() {
211         final BeanDeclarationTestImpl decl = new BeanDeclarationTestImpl();
212         final Collection<ConstructorArg> args = new ArrayList<>();
213         args.add(ConstructorArg.forBeanDeclaration(setUpBeanDeclaration(), BeanCreationTestBean.class.getName()));
214         decl.setConstructorArgs(args);
215         final Constructor<BeanCreationTestCtorBean> ctor = DefaultBeanFactory.findMatchingConstructor(BeanCreationTestCtorBean.class, decl);
216         final Class<?>[] paramTypes = ctor.getParameterTypes();
217         assertArrayEquals(new Class<?>[] {BeanCreationTestBean.class}, paramTypes);
218     }
219 
220     /**
221      * Tests whether the standard constructor can be found.
222      */
223     @Test
224     void testFindMatchingConstructorNoArgs() {
225         final BeanDeclarationTestImpl decl = new BeanDeclarationTestImpl();
226         final Constructor<BeanCreationTestBean> ctor = DefaultBeanFactory.findMatchingConstructor(BeanCreationTestBean.class, decl);
227         assertEquals(0, ctor.getParameterTypes().length);
228     }
229 
230     /**
231      * Tests the case that no matching constructor is found.
232      */
233     @Test
234     void testFindMatchingConstructorNoMatch() {
235         final BeanDeclarationTestImpl decl = new BeanDeclarationTestImpl();
236         final Collection<ConstructorArg> args = new ArrayList<>();
237         args.add(ConstructorArg.forValue(TEST_STRING, getClass().getName()));
238         decl.setConstructorArgs(args);
239         final ConfigurationRuntimeException crex = assertThrows(ConfigurationRuntimeException.class,
240                 () -> DefaultBeanFactory.findMatchingConstructor(BeanCreationTestCtorBean.class, decl));
241         final String msg = crex.getMessage();
242         assertTrue(msg.contains(BeanCreationTestCtorBean.class.getName()));
243         assertTrue(msg.contains(TEST_STRING));
244         assertTrue(msg.contains("(" + getClass().getName() + ')'));
245     }
246 
247     /**
248      * Tests obtaining the default class. This should be null.
249      */
250     @Test
251     void testGetDefaultBeanClass() {
252         assertNull(factory.getDefaultBeanClass());
253     }
254 
255     /**
256      * Tests whether a custom conversion handler can be passed to the constructor.
257      */
258     @Test
259     void testInitWithConversionHandler() {
260         final ConversionHandler handler = mock(ConversionHandler.class);
261         factory = new DefaultBeanFactory(handler);
262         assertSame(handler, factory.getConversionHandler());
263     }
264 }