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 * @version $Id: DefaultBeanFactory.java 1842194 2018-09-27 22:24:23Z ggregory $
053 */
054public class DefaultBeanFactory implements BeanFactory
055{
056    /** Stores the default instance of this class. */
057    public static final DefaultBeanFactory INSTANCE = new DefaultBeanFactory();
058
059    /** A format string for generating error messages for constructor matching. */
060    private static final String FMT_CTOR_ERROR =
061            "%s! Bean class = %s, constructor arguments = %s";
062
063    /** The conversion handler used by this instance. */
064    private final ConversionHandler conversionHandler;
065
066    /**
067     * Creates a new instance of {@code DefaultBeanFactory} using a default
068     * {@code ConversionHandler}.
069     */
070    public DefaultBeanFactory()
071    {
072        this(null);
073    }
074
075    /**
076     * Creates a new instance of {@code DefaultBeanFactory} using the specified
077     * {@code ConversionHandler} for data type conversions.
078     *
079     * @param convHandler the {@code ConversionHandler}; can be <b>null</b>,
080     *        then a default handler is used
081     * @since 2.0
082     */
083    public DefaultBeanFactory(final ConversionHandler convHandler)
084    {
085        conversionHandler =
086                (convHandler != null) ? convHandler
087                        : DefaultConversionHandler.INSTANCE;
088    }
089
090    /**
091     * Returns the {@code ConversionHandler} used by this object.
092     *
093     * @return the {@code ConversionHandler}
094     * @since 2.0
095     */
096    public ConversionHandler getConversionHandler()
097    {
098        return conversionHandler;
099    }
100
101    /**
102     * Creates a new bean instance. This implementation delegates to the
103     * protected methods {@code createBeanInstance()} and
104     * {@code initBeanInstance()} for creating and initializing the bean.
105     * This makes it easier for derived classes that need to change specific
106     * functionality of the base class.
107     *
108     * @param bcc the context object defining the bean to be created
109     * @return the new bean instance
110     * @throws Exception if an error occurs
111     */
112    @Override
113    public Object createBean(final BeanCreationContext bcc) throws Exception
114    {
115        final Object result = createBeanInstance(bcc);
116        initBeanInstance(result, bcc);
117        return result;
118    }
119
120    /**
121     * Returns the default bean class used by this factory. This is always
122     * <b>null</b> for this implementation.
123     *
124     * @return the default bean class
125     */
126    @Override
127    public Class<?> getDefaultBeanClass()
128    {
129        return null;
130    }
131
132    /**
133     * Creates the bean instance. This method is called by
134     * {@code createBean()}. It uses reflection to create a new instance
135     * of the specified class.
136     *
137     * @param bcc the context object defining the bean to be created
138     * @return the new bean instance
139     * @throws Exception if an error occurs
140     */
141    protected Object createBeanInstance(final BeanCreationContext bcc)
142            throws Exception
143    {
144        final Constructor<?> ctor =
145                findMatchingConstructor(bcc.getBeanClass(),
146                        bcc.getBeanDeclaration());
147        final Object[] args = fetchConstructorArgs(ctor, bcc);
148        return ctor.newInstance(args);
149    }
150
151    /**
152     * Initializes the newly created bean instance. This method is called by
153     * {@code createBean()}. It calls the {@code initBean()} method of the
154     * context object for performing the initialization.
155     *
156     * @param bean the newly created bean instance
157     * @param bcc the context object defining the bean to be created
158     * @throws Exception if an error occurs
159     */
160    protected void initBeanInstance(final Object bean, final BeanCreationContext bcc) throws Exception
161    {
162        bcc.initBean(bean, bcc.getBeanDeclaration());
163    }
164
165    /**
166     * Evaluates constructor arguments in the specified {@code BeanDeclaration}
167     * and tries to find a unique matching constructor. If this is not possible,
168     * an exception is thrown. Note: This method is intended to be used by
169     * concrete {@link BeanFactory} implementations and not by client code.
170     *
171     * @param beanClass the class of the bean to be created
172     * @param data the current {@code BeanDeclaration}
173     * @param <T> the type of the bean to be created
174     * @return the single matching constructor
175     * @throws ConfigurationRuntimeException if no single matching constructor
176     *         can be found
177     * @throws NullPointerException if the bean class or bean declaration are
178     *         <b>null</b>
179     */
180    protected static <T> Constructor<T> findMatchingConstructor(
181            final Class<T> beanClass, final BeanDeclaration data)
182    {
183        final List<Constructor<T>> matchingConstructors =
184                findMatchingConstructors(beanClass, data);
185        checkSingleMatchingConstructor(beanClass, data, matchingConstructors);
186        return matchingConstructors.get(0);
187    }
188
189    /**
190     * Obtains the arguments for a constructor call to create a bean. This method
191     * resolves nested bean declarations and performs necessary type
192     * conversions.
193     *
194     * @param ctor the constructor to be invoked
195     * @param bcc the context object defining the bean to be created
196     * @return an array with constructor arguments
197     */
198    private Object[] fetchConstructorArgs(final Constructor<?> ctor,
199            final BeanCreationContext bcc)
200    {
201        final Class<?>[] types = ctor.getParameterTypes();
202        assert types.length == nullSafeConstructorArgs(bcc.getBeanDeclaration()).size()
203                : "Wrong number of constructor arguments!";
204        final Object[] args = new Object[types.length];
205        int idx = 0;
206
207        for (final ConstructorArg arg : nullSafeConstructorArgs(bcc.getBeanDeclaration()))
208        {
209            final Object val =
210                    arg.isNestedBeanDeclaration() ? bcc.createBean(arg
211                            .getBeanDeclaration()) : arg.getValue();
212            args[idx] = getConversionHandler().to(val, types[idx], null);
213            idx++;
214        }
215
216        return args;
217    }
218
219    /**
220     * Fetches constructor arguments from the given bean declaration. Handles
221     * <b>null</b> values safely.
222     *
223     * @param data the bean declaration
224     * @return the collection with constructor arguments (never <b>null</b>)
225     */
226    private static Collection<ConstructorArg> nullSafeConstructorArgs(
227            final BeanDeclaration data)
228    {
229        Collection<ConstructorArg> args = data.getConstructorArgs();
230        if (args == null)
231        {
232            args = Collections.emptySet();
233        }
234        return args;
235    }
236
237    /**
238     * Returns a list with all constructors which are compatible with the
239     * constructor arguments specified by the given {@code BeanDeclaration}.
240     *
241     * @param beanClass the bean class to be instantiated
242     * @param data the current {@code BeanDeclaration}
243     * @return a list with all matching constructors
244     */
245    private static <T> List<Constructor<T>> findMatchingConstructors(
246            final Class<T> beanClass, final BeanDeclaration data)
247    {
248        final List<Constructor<T>> result = new LinkedList<>();
249        final Collection<ConstructorArg> args = getConstructorArgs(data);
250        for (final Constructor<?> ctor : beanClass.getConstructors())
251        {
252            if (matchesConstructor(ctor, args))
253            {
254                // cast should be okay according to the Javadocs of
255                // getConstructors()
256                @SuppressWarnings("unchecked")
257                final
258                Constructor<T> match = (Constructor<T>) ctor;
259                result.add(match);
260            }
261        }
262        return result;
263    }
264
265    /**
266     * Checks whether the given constructor is compatible with the given list of
267     * arguments.
268     *
269     * @param ctor the constructor to be checked
270     * @param args the collection of constructor arguments
271     * @return a flag whether this constructor is compatible with the given
272     *         arguments
273     */
274    private static boolean matchesConstructor(final Constructor<?> ctor,
275            final Collection<ConstructorArg> args)
276    {
277        final Class<?>[] types = ctor.getParameterTypes();
278        if (types.length != args.size())
279        {
280            return false;
281        }
282
283        int idx = 0;
284        for (final ConstructorArg arg : args)
285        {
286            if (!arg.matches(types[idx++]))
287            {
288                return false;
289            }
290        }
291
292        return true;
293    }
294
295    /**
296     * Helper method for extracting constructor arguments from a bean
297     * declaration. Deals with <b>null</b> values.
298     *
299     * @param data the bean declaration
300     * @return the collection with constructor arguments (never <b>null</b>)
301     */
302    private static Collection<ConstructorArg> getConstructorArgs(
303            final BeanDeclaration data)
304    {
305        Collection<ConstructorArg> args = data.getConstructorArgs();
306        if (args == null)
307        {
308            args = Collections.emptySet();
309        }
310        return args;
311    }
312
313    /**
314     * Helper method for testing whether exactly one matching constructor was
315     * found. Throws a meaningful exception if there is not a single matching
316     * constructor.
317     *
318     * @param beanClass the bean class
319     * @param data the bean declaration
320     * @param matchingConstructors the list with matching constructors
321     * @throws ConfigurationRuntimeException if there is not exactly one match
322     */
323    private static <T> void checkSingleMatchingConstructor(final Class<T> beanClass,
324            final BeanDeclaration data, final List<Constructor<T>> matchingConstructors)
325    {
326        if (matchingConstructors.isEmpty())
327        {
328            throw constructorMatchingException(beanClass, data,
329                    "No matching constructor found");
330        }
331        if (matchingConstructors.size() > 1)
332        {
333            throw constructorMatchingException(beanClass, data,
334                    "Multiple matching constructors found");
335        }
336    }
337
338    /**
339     * Creates an exception if no single matching constructor was found with a
340     * meaningful error message.
341     *
342     * @param beanClass the affected bean class
343     * @param data the bean declaration
344     * @param msg an error message
345     * @return the exception with the error message
346     */
347    private static ConfigurationRuntimeException constructorMatchingException(
348            final Class<?> beanClass, final BeanDeclaration data, final String msg)
349    {
350        return new ConfigurationRuntimeException(FMT_CTOR_ERROR,
351                msg, beanClass.getName(), getConstructorArgs(data).toString());
352    }
353}