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 {@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 <b>null</b>
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 <b>null</b> values.
131     *
132     * @param data the bean declaration
133     * @return the collection with constructor arguments (never <b>null</b>)
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 <b>null</b> values safely.
168     *
169     * @param data the bean declaration
170     * @return the collection with constructor arguments (never <b>null</b>)
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 <b>null</b>, 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 <b>null</b> 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}