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 java.lang.reflect.Constructor;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.LinkedList;
23 import java.util.List;
24
25 import org.apache.commons.configuration2.convert.ConversionHandler;
26 import org.apache.commons.configuration2.convert.DefaultConversionHandler;
27 import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
28
29 /**
30 * <p>
31 * The default implementation of the {@code BeanFactory} interface.
32 * </p>
33 * <p>
34 * This class creates beans of arbitrary types using reflection. Each time the {@code createBean()} method is invoked, a
35 * new bean instance is created. A default bean class is not supported.
36 * </p>
37 * <p>
38 * For data type conversions (which may be needed before invoking methods through reflection to ensure that the current
39 * parameters match their declared types) a {@link ConversionHandler} object is used. An instance of this class can be
40 * passed to the constructor. Alternatively, a default {@code ConversionHandler} instance is used.
41 * </p>
42 * <p>
43 * An instance of this factory class will be set as the default bean factory for the {@link BeanHelper} class. This
44 * means that if not bean factory is specified in a {@link BeanDeclaration}, this default instance will be used.
45 * </p>
46 *
47 * @since 1.3
48 */
49 public class DefaultBeanFactory implements BeanFactory {
50
51 /** Stores the default instance of this class. */
52 public static final DefaultBeanFactory INSTANCE = new DefaultBeanFactory();
53
54 /** A format string for generating error messages for constructor matching. */
55 private static final String FMT_CTOR_ERROR = "%s! Bean class = %s, constructor arguments = %s";
56
57 /**
58 * Checks whether exactly one matching constructor was found. Throws a meaningful exception if there
59 * is not a single matching constructor.
60 *
61 * @param beanClass the bean class
62 * @param data the bean declaration
63 * @param matchingConstructors the list with matching constructors
64 * @throws ConfigurationRuntimeException if there is not exactly one match
65 */
66 private static <T> void checkSingleMatchingConstructor(final Class<T> beanClass, final BeanDeclaration data,
67 final List<Constructor<T>> matchingConstructors) {
68 if (matchingConstructors.isEmpty()) {
69 throw constructorMatchingException(beanClass, data, "No matching constructor found");
70 }
71 if (matchingConstructors.size() > 1) {
72 throw constructorMatchingException(beanClass, data, "Multiple matching constructors found");
73 }
74 }
75
76 /**
77 * Constructs an exception if no single matching constructor was found with a meaningful error message.
78 *
79 * @param beanClass the affected bean class
80 * @param data the bean declaration
81 * @param msg an error message
82 * @return the exception with the error message
83 */
84 private static ConfigurationRuntimeException constructorMatchingException(final Class<?> beanClass, final BeanDeclaration data, final String msg) {
85 return new ConfigurationRuntimeException(FMT_CTOR_ERROR, msg, beanClass.getName(), getConstructorArgs(data).toString());
86 }
87
88 /**
89 * Evaluates constructor arguments in the specified {@code BeanDeclaration} and tries to find a unique matching
90 * constructor. If this is not possible, an exception is thrown. Note: This method is intended to be used by concrete
91 * {@link BeanFactory} implementations and not by client code.
92 *
93 * @param beanClass the class of the bean to be created
94 * @param data the current {@code BeanDeclaration}
95 * @param <T> the type of the bean to be created
96 * @return the single matching constructor
97 * @throws ConfigurationRuntimeException if no single matching constructor can be found
98 * @throws NullPointerException if the bean class or bean declaration are <strong>null</strong>
99 */
100 protected static <T> Constructor<T> findMatchingConstructor(final Class<T> beanClass, final BeanDeclaration data) {
101 final List<Constructor<T>> matchingConstructors = findMatchingConstructors(beanClass, data);
102 checkSingleMatchingConstructor(beanClass, data, matchingConstructors);
103 return matchingConstructors.get(0);
104 }
105
106 /**
107 * Returns a list with all constructors which are compatible with the constructor arguments specified by the given
108 * {@code BeanDeclaration}.
109 *
110 * @param beanClass the bean class to be instantiated
111 * @param data the current {@code BeanDeclaration}
112 * @return a list with all matching constructors
113 */
114 private static <T> List<Constructor<T>> findMatchingConstructors(final Class<T> beanClass, final BeanDeclaration data) {
115 final List<Constructor<T>> result = new LinkedList<>();
116 final Collection<ConstructorArg> args = getConstructorArgs(data);
117 for (final Constructor<?> ctor : beanClass.getConstructors()) {
118 if (matchesConstructor(ctor, args)) {
119 // cast should be okay according to the Javadocs of
120 // getConstructors()
121 @SuppressWarnings("unchecked")
122 final Constructor<T> match = (Constructor<T>) ctor;
123 result.add(match);
124 }
125 }
126 return result;
127 }
128
129 /**
130 * Gets constructor arguments from a bean declaration. Deals with <strong>null</strong> values.
131 *
132 * @param data the bean declaration
133 * @return the collection with constructor arguments (never <strong>null</strong>)
134 */
135 private static Collection<ConstructorArg> getConstructorArgs(final BeanDeclaration data) {
136 Collection<ConstructorArg> args = data.getConstructorArgs();
137 if (args == null) {
138 args = Collections.emptySet();
139 }
140 return args;
141 }
142
143 /**
144 * Checks whether the given constructor is compatible with the given list of arguments.
145 *
146 * @param ctor the constructor to be checked
147 * @param args the collection of constructor arguments
148 * @return a flag whether this constructor is compatible with the given arguments
149 */
150 private static boolean matchesConstructor(final Constructor<?> ctor, final Collection<ConstructorArg> args) {
151 final Class<?>[] types = ctor.getParameterTypes();
152 if (types.length != args.size()) {
153 return false;
154 }
155
156 int idx = 0;
157 for (final ConstructorArg arg : args) {
158 if (!arg.matches(types[idx++])) {
159 return false;
160 }
161 }
162
163 return true;
164 }
165
166 /**
167 * Fetches constructor arguments from the given bean declaration. Handles <strong>null</strong> values safely.
168 *
169 * @param data the bean declaration
170 * @return the collection with constructor arguments (never <strong>null</strong>)
171 */
172 private static Collection<ConstructorArg> nullSafeConstructorArgs(final BeanDeclaration data) {
173 Collection<ConstructorArg> args = data.getConstructorArgs();
174 if (args == null) {
175 args = Collections.emptySet();
176 }
177 return args;
178 }
179
180 /** The conversion handler used by this instance. */
181 private final ConversionHandler conversionHandler;
182
183 /**
184 * Constructs a new instance of {@code DefaultBeanFactory} using a default {@code ConversionHandler}.
185 */
186 public DefaultBeanFactory() {
187 this(null);
188 }
189
190 /**
191 * Constructs a new instance of {@code DefaultBeanFactory} using the specified {@code ConversionHandler} for data type
192 * conversions.
193 *
194 * @param convHandler the {@code ConversionHandler}; can be <strong>null</strong>, then a default handler is used
195 * @since 2.0
196 */
197 public DefaultBeanFactory(final ConversionHandler convHandler) {
198 conversionHandler = convHandler != null ? convHandler : DefaultConversionHandler.INSTANCE;
199 }
200
201 /**
202 * Creates a new bean instance. This implementation delegates to the protected methods {@code createBeanInstance()} and
203 * {@code initBeanInstance()} for creating and initializing the bean. This makes it easier for derived classes that need
204 * to change specific functionality of the base class.
205 *
206 * @param bcc the context object defining the bean to be created
207 * @return the new bean instance
208 * @throws Exception if an error occurs
209 */
210 @Override
211 public Object createBean(final BeanCreationContext bcc) throws Exception {
212 final Object result = createBeanInstance(bcc);
213 initBeanInstance(result, bcc);
214 return result;
215 }
216
217 /**
218 * Creates the bean instance. This method is called by {@code createBean()}. It uses reflection to create a new instance
219 * of the specified class.
220 *
221 * @param bcc the context object defining the bean to be created
222 * @return the new bean instance
223 * @throws Exception if an error occurs
224 */
225 protected Object createBeanInstance(final BeanCreationContext bcc) throws Exception {
226 final Constructor<?> ctor = findMatchingConstructor(bcc.getBeanClass(), bcc.getBeanDeclaration());
227 final Object[] args = fetchConstructorArgs(ctor, bcc);
228 return ctor.newInstance(args);
229 }
230
231 /**
232 * Obtains the arguments for a constructor call to create a bean. This method resolves nested bean declarations and
233 * performs necessary type conversions.
234 *
235 * @param ctor the constructor to be invoked
236 * @param bcc the context object defining the bean to be created
237 * @return an array with constructor arguments
238 */
239 private Object[] fetchConstructorArgs(final Constructor<?> ctor, final BeanCreationContext bcc) {
240 final Class<?>[] types = ctor.getParameterTypes();
241 assert types.length == nullSafeConstructorArgs(bcc.getBeanDeclaration()).size() : "Wrong number of constructor arguments!";
242 final Object[] args = new Object[types.length];
243 int idx = 0;
244
245 for (final ConstructorArg arg : nullSafeConstructorArgs(bcc.getBeanDeclaration())) {
246 final Object val = arg.isNestedBeanDeclaration() ? bcc.createBean(arg.getBeanDeclaration()) : arg.getValue();
247 args[idx] = getConversionHandler().to(val, types[idx], null);
248 idx++;
249 }
250
251 return args;
252 }
253
254 /**
255 * Gets the {@code ConversionHandler} used by this object.
256 *
257 * @return the {@code ConversionHandler}
258 * @since 2.0
259 */
260 public ConversionHandler getConversionHandler() {
261 return conversionHandler;
262 }
263
264 /**
265 * Gets the default bean class used by this factory. This is always <strong>null</strong> for this implementation.
266 *
267 * @return the default bean class
268 */
269 @Override
270 public Class<?> getDefaultBeanClass() {
271 return null;
272 }
273
274 /**
275 * Initializes the newly created bean instance. This method is called by {@code createBean()}. It calls the
276 * {@code initBean()} method of the context object for performing the initialization.
277 *
278 * @param bean the newly created bean instance
279 * @param bcc the context object defining the bean to be created
280 * @throws Exception if an error occurs
281 */
282 protected void initBeanInstance(final Object bean, final BeanCreationContext bcc) throws Exception {
283 bcc.initBean(bean, bcc.getBeanDeclaration());
284 }
285 }