    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}