    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}