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