    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 *     https://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 {@code createBean()} method is invoked, a
    035 * new bean instance is created. A default bean class is not supported.
    036 * </p>
    037 * <p>
    038 * For data type conversions (which may be needed before invoking methods through reflection to ensure that the current
    039 * parameters match their declared types) a {@link ConversionHandler} object is used. An instance of this class can be
    040 * passed to the constructor. Alternatively, a default {@code ConversionHandler} instance is used.
    041 * </p>
    042 * <p>
    043 * An instance of this factory class will be set as the default bean factory for the {@link BeanHelper} class. This
    044 * means that if not bean factory is specified in a {@link BeanDeclaration}, this default instance will be used.
    045 * </p>
    046 *
    047 * @since 1.3
    048 */
    049public class DefaultBeanFactory implements BeanFactory {
    050
    051    /** Stores the default instance of this class. */
    052    public static final DefaultBeanFactory INSTANCE = new DefaultBeanFactory();
    053
    054    /** A format string for generating error messages for constructor matching. */
    055    private static final String FMT_CTOR_ERROR = "%s! Bean class = %s, constructor arguments = %s";
    056
    057    /**
    058     * Checks whether exactly one matching constructor was found. Throws a meaningful exception if there
    059     * is not a single matching constructor.
    060     *
    061     * @param beanClass the bean class
    062     * @param data the bean declaration
    063     * @param matchingConstructors the list with matching constructors
    064     * @throws ConfigurationRuntimeException if there is not exactly one match
    065     */
    066    private static <T> void checkSingleMatchingConstructor(final Class<T> beanClass, final BeanDeclaration data,
    067        final List<Constructor<T>> matchingConstructors) {
    068        if (matchingConstructors.isEmpty()) {
    069            throw constructorMatchingException(beanClass, data, "No matching constructor found");
    070        }
    071        if (matchingConstructors.size() > 1) {
    072            throw constructorMatchingException(beanClass, data, "Multiple matching constructors found");
    073        }
    074    }
    075
    076    /**
    077     * Constructs an exception if no single matching constructor was found with a meaningful error message.
    078     *
    079     * @param beanClass the affected bean class
    080     * @param data the bean declaration
    081     * @param msg an error message
    082     * @return the exception with the error message
    083     */
    084    private static ConfigurationRuntimeException constructorMatchingException(final Class<?> beanClass, final BeanDeclaration data, final String msg) {
    085        return new ConfigurationRuntimeException(FMT_CTOR_ERROR, msg, beanClass.getName(), getConstructorArgs(data).toString());
    086    }
    087
    088    /**
    089     * Evaluates constructor arguments in the specified {@code BeanDeclaration} and tries to find a unique matching
    090     * constructor. If this is not possible, an exception is thrown. Note: This method is intended to be used by concrete
    091     * {@link BeanFactory} implementations and not by client code.
    092     *
    093     * @param beanClass the class of the bean to be created
    094     * @param data the current {@code BeanDeclaration}
    095     * @param <T> the type of the bean to be created
    096     * @return the single matching constructor
    097     * @throws ConfigurationRuntimeException if no single matching constructor can be found
    098     * @throws NullPointerException if the bean class or bean declaration are <strong>null</strong>
    099     */
    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}