001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.configuration2.beanutils;
018
019import java.lang.reflect.Constructor;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.LinkedList;
023import java.util.List;
024
025import org.apache.commons.configuration2.convert.ConversionHandler;
026import org.apache.commons.configuration2.convert.DefaultConversionHandler;
027import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
028
029/**
030 * <p>
031 * The default implementation of the {@code BeanFactory} interface.
032 * </p>
033 * <p>
034 * This class creates beans of arbitrary types using reflection. Each time the
035 * {@code createBean()} method is invoked, a new bean instance is created. A
036 * default bean class is not supported.
037 * </p>
038 * <p>
039 * For data type conversions (which may be needed before invoking methods
040 * through reflection to ensure that the current parameters match their declared
041 * types) a {@link ConversionHandler} object is used. An instance of this class
042 * can be passed to the constructor. Alternatively, a default
043 * {@code ConversionHandler} instance is used.
044 * </p>
045 * <p>
046 * An instance of this factory class will be set as the default bean factory for
047 * the {@link BeanHelper} class. This means that if not bean factory is
048 * specified in a {@link BeanDeclaration}, this default instance will be used.
049 * </p>
050 *
051 * @since 1.3
052 */
053public class DefaultBeanFactory implements BeanFactory
054{
055    /** Stores the default instance of this class. */
056    public static final DefaultBeanFactory INSTANCE = new DefaultBeanFactory();
057
058    /** A format string for generating error messages for constructor matching. */
059    private static final String FMT_CTOR_ERROR =
060            "%s! Bean class = %s, constructor arguments = %s";
061
062    /** The conversion handler used by this instance. */
063    private final ConversionHandler conversionHandler;
064
065    /**
066     * Creates a new instance of {@code DefaultBeanFactory} using a default
067     * {@code ConversionHandler}.
068     */
069    public DefaultBeanFactory()
070    {
071        this(null);
072    }
073
074    /**
075     * Creates a new instance of {@code DefaultBeanFactory} using the specified
076     * {@code ConversionHandler} for data type conversions.
077     *
078     * @param convHandler the {@code ConversionHandler}; can be <b>null</b>,
079     *        then a default handler is used
080     * @since 2.0
081     */
082    public DefaultBeanFactory(final ConversionHandler convHandler)
083    {
084        conversionHandler =
085                convHandler != null ? convHandler
086                        : DefaultConversionHandler.INSTANCE;
087    }
088
089    /**
090     * Returns the {@code ConversionHandler} used by this object.
091     *
092     * @return the {@code ConversionHandler}
093     * @since 2.0
094     */
095    public ConversionHandler getConversionHandler()
096    {
097        return conversionHandler;
098    }
099
100    /**
101     * Creates a new bean instance. This implementation delegates to the
102     * protected methods {@code createBeanInstance()} and
103     * {@code initBeanInstance()} for creating and initializing the bean.
104     * This makes it easier for derived classes that need to change specific
105     * functionality of the base class.
106     *
107     * @param bcc the context object defining the bean to be created
108     * @return the new bean instance
109     * @throws Exception if an error occurs
110     */
111    @Override
112    public Object createBean(final BeanCreationContext bcc) throws Exception
113    {
114        final Object result = createBeanInstance(bcc);
115        initBeanInstance(result, bcc);
116        return result;
117    }
118
119    /**
120     * Returns the default bean class used by this factory. This is always
121     * <b>null</b> for this implementation.
122     *
123     * @return the default bean class
124     */
125    @Override
126    public Class<?> getDefaultBeanClass()
127    {
128        return null;
129    }
130
131    /**
132     * Creates the bean instance. This method is called by
133     * {@code createBean()}. It uses reflection to create a new instance
134     * of the specified class.
135     *
136     * @param bcc the context object defining the bean to be created
137     * @return the new bean instance
138     * @throws Exception if an error occurs
139     */
140    protected Object createBeanInstance(final BeanCreationContext bcc)
141            throws Exception
142    {
143        final Constructor<?> ctor =
144                findMatchingConstructor(bcc.getBeanClass(),
145                        bcc.getBeanDeclaration());
146        final Object[] args = fetchConstructorArgs(ctor, bcc);
147        return ctor.newInstance(args);
148    }
149
150    /**
151     * Initializes the newly created bean instance. This method is called by
152     * {@code createBean()}. It calls the {@code initBean()} method of the
153     * context object for performing the initialization.
154     *
155     * @param bean the newly created bean instance
156     * @param bcc the context object defining the bean to be created
157     * @throws Exception if an error occurs
158     */
159    protected void initBeanInstance(final Object bean, final BeanCreationContext bcc) throws Exception
160    {
161        bcc.initBean(bean, bcc.getBeanDeclaration());
162    }
163
164    /**
165     * Evaluates constructor arguments in the specified {@code BeanDeclaration}
166     * and tries to find a unique matching constructor. If this is not possible,
167     * an exception is thrown. Note: This method is intended to be used by
168     * concrete {@link BeanFactory} implementations and not by client code.
169     *
170     * @param beanClass the class of the bean to be created
171     * @param data the current {@code BeanDeclaration}
172     * @param <T> the type of the bean to be created
173     * @return the single matching constructor
174     * @throws ConfigurationRuntimeException if no single matching constructor
175     *         can be found
176     * @throws NullPointerException if the bean class or bean declaration are
177     *         <b>null</b>
178     */
179    protected static <T> Constructor<T> findMatchingConstructor(
180            final Class<T> beanClass, final BeanDeclaration data)
181    {
182        final List<Constructor<T>> matchingConstructors =
183                findMatchingConstructors(beanClass, data);
184        checkSingleMatchingConstructor(beanClass, data, matchingConstructors);
185        return matchingConstructors.get(0);
186    }
187
188    /**
189     * Obtains the arguments for a constructor call to create a bean. This method
190     * resolves nested bean declarations and performs necessary type
191     * conversions.
192     *
193     * @param ctor the constructor to be invoked
194     * @param bcc the context object defining the bean to be created
195     * @return an array with constructor arguments
196     */
197    private Object[] fetchConstructorArgs(final Constructor<?> ctor,
198            final BeanCreationContext bcc)
199    {
200        final Class<?>[] types = ctor.getParameterTypes();
201        assert types.length == nullSafeConstructorArgs(bcc.getBeanDeclaration()).size()
202                : "Wrong number of constructor arguments!";
203        final Object[] args = new Object[types.length];
204        int idx = 0;
205
206        for (final ConstructorArg arg : nullSafeConstructorArgs(bcc.getBeanDeclaration()))
207        {
208            final Object val =
209                    arg.isNestedBeanDeclaration() ? bcc.createBean(arg
210                            .getBeanDeclaration()) : arg.getValue();
211            args[idx] = getConversionHandler().to(val, types[idx], null);
212            idx++;
213        }
214
215        return args;
216    }
217
218    /**
219     * Fetches constructor arguments from the given bean declaration. Handles
220     * <b>null</b> values safely.
221     *
222     * @param data the bean declaration
223     * @return the collection with constructor arguments (never <b>null</b>)
224     */
225    private static Collection<ConstructorArg> nullSafeConstructorArgs(
226            final BeanDeclaration data)
227    {
228        Collection<ConstructorArg> args = data.getConstructorArgs();
229        if (args == null)
230        {
231            args = Collections.emptySet();
232        }
233        return args;
234    }
235
236    /**
237     * Returns a list with all constructors which are compatible with the
238     * constructor arguments specified by the given {@code BeanDeclaration}.
239     *
240     * @param beanClass the bean class to be instantiated
241     * @param data the current {@code BeanDeclaration}
242     * @return a list with all matching constructors
243     */
244    private static <T> List<Constructor<T>> findMatchingConstructors(
245            final Class<T> beanClass, final BeanDeclaration data)
246    {
247        final List<Constructor<T>> result = new LinkedList<>();
248        final Collection<ConstructorArg> args = getConstructorArgs(data);
249        for (final Constructor<?> ctor : beanClass.getConstructors())
250        {
251            if (matchesConstructor(ctor, args))
252            {
253                // cast should be okay according to the Javadocs of
254                // getConstructors()
255                @SuppressWarnings("unchecked")
256                final
257                Constructor<T> match = (Constructor<T>) ctor;
258                result.add(match);
259            }
260        }
261        return result;
262    }
263
264    /**
265     * Checks whether the given constructor is compatible with the given list of
266     * arguments.
267     *
268     * @param ctor the constructor to be checked
269     * @param args the collection of constructor arguments
270     * @return a flag whether this constructor is compatible with the given
271     *         arguments
272     */
273    private static boolean matchesConstructor(final Constructor<?> ctor,
274            final Collection<ConstructorArg> args)
275    {
276        final Class<?>[] types = ctor.getParameterTypes();
277        if (types.length != args.size())
278        {
279            return false;
280        }
281
282        int idx = 0;
283        for (final ConstructorArg arg : args)
284        {
285            if (!arg.matches(types[idx++]))
286            {
287                return false;
288            }
289        }
290
291        return true;
292    }
293
294    /**
295     * Helper method for extracting constructor arguments from a bean
296     * declaration. Deals with <b>null</b> values.
297     *
298     * @param data the bean declaration
299     * @return the collection with constructor arguments (never <b>null</b>)
300     */
301    private static Collection<ConstructorArg> getConstructorArgs(
302            final BeanDeclaration data)
303    {
304        Collection<ConstructorArg> args = data.getConstructorArgs();
305        if (args == null)
306        {
307            args = Collections.emptySet();
308        }
309        return args;
310    }
311
312    /**
313     * Helper method for testing whether exactly one matching constructor was
314     * found. Throws a meaningful exception if there is not a single matching
315     * constructor.
316     *
317     * @param beanClass the bean class
318     * @param data the bean declaration
319     * @param matchingConstructors the list with matching constructors
320     * @throws ConfigurationRuntimeException if there is not exactly one match
321     */
322    private static <T> void checkSingleMatchingConstructor(final Class<T> beanClass,
323            final BeanDeclaration data, final List<Constructor<T>> matchingConstructors)
324    {
325        if (matchingConstructors.isEmpty())
326        {
327            throw constructorMatchingException(beanClass, data,
328                    "No matching constructor found");
329        }
330        if (matchingConstructors.size() > 1)
331        {
332            throw constructorMatchingException(beanClass, data,
333                    "Multiple matching constructors found");
334        }
335    }
336
337    /**
338     * Creates an exception if no single matching constructor was found with a
339     * meaningful error message.
340     *
341     * @param beanClass the affected bean class
342     * @param data the bean declaration
343     * @param msg an error message
344     * @return the exception with the error message
345     */
346    private static ConfigurationRuntimeException constructorMatchingException(
347            final Class<?> beanClass, final BeanDeclaration data, final String msg)
348    {
349        return new ConfigurationRuntimeException(FMT_CTOR_ERROR,
350                msg, beanClass.getName(), getConstructorArgs(data).toString());
351    }
352}