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 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 }