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