| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.configuration2.beanutils; |
| |
| import java.lang.reflect.Constructor; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| import org.apache.commons.configuration2.convert.ConversionHandler; |
| import org.apache.commons.configuration2.convert.DefaultConversionHandler; |
| import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; |
| |
| /** |
| * <p> |
| * The default implementation of the {@code BeanFactory} interface. |
| * </p> |
| * <p> |
| * This class creates beans of arbitrary types using reflection. Each time the |
| * {@code createBean()} method is invoked, a new bean instance is created. A |
| * default bean class is not supported. |
| * </p> |
| * <p> |
| * For data type conversions (which may be needed before invoking methods |
| * through reflection to ensure that the current parameters match their declared |
| * types) a {@link ConversionHandler} object is used. An instance of this class |
| * can be passed to the constructor. Alternatively, a default |
| * {@code ConversionHandler} instance is used. |
| * </p> |
| * <p> |
| * An instance of this factory class will be set as the default bean factory for |
| * the {@link BeanHelper} class. This means that if not bean factory is |
| * specified in a {@link BeanDeclaration}, this default instance will be used. |
| * </p> |
| * |
| * @since 1.3 |
| * @version $Id$ |
| */ |
| public class DefaultBeanFactory implements BeanFactory |
| { |
| /** Stores the default instance of this class. */ |
| public static final DefaultBeanFactory INSTANCE = new DefaultBeanFactory(); |
| |
| /** A format string for generating error messages for constructor matching. */ |
| private static final String FMT_CTOR_ERROR = |
| "%s! Bean class = %s, constructor arguments = %s"; |
| |
| /** The conversion handler used by this instance. */ |
| private final ConversionHandler conversionHandler; |
| |
| /** |
| * Creates a new instance of {@code DefaultBeanFactory} using a default |
| * {@code ConversionHandler}. |
| */ |
| public DefaultBeanFactory() |
| { |
| this(null); |
| } |
| |
| /** |
| * Creates a new instance of {@code DefaultBeanFactory} using the specified |
| * {@code ConversionHandler} for data type conversions. |
| * |
| * @param convHandler the {@code ConversionHandler}; can be <b>null</b>, |
| * then a default handler is used |
| * @since 2.0 |
| */ |
| public DefaultBeanFactory(ConversionHandler convHandler) |
| { |
| conversionHandler = |
| (convHandler != null) ? convHandler |
| : DefaultConversionHandler.INSTANCE; |
| } |
| |
| /** |
| * Returns the {@code ConversionHandler} used by this object. |
| * |
| * @return the {@code ConversionHandler} |
| * @since 2.0 |
| */ |
| public ConversionHandler getConversionHandler() |
| { |
| return conversionHandler; |
| } |
| |
| /** |
| * Creates a new bean instance. This implementation delegates to the |
| * protected methods {@code createBeanInstance()} and |
| * {@code initBeanInstance()} for creating and initializing the bean. |
| * This makes it easier for derived classes that need to change specific |
| * functionality of the base class. |
| * |
| * @param bcc the context object defining the bean to be created |
| * @return the new bean instance |
| * @throws Exception if an error occurs |
| */ |
| @Override |
| public Object createBean(BeanCreationContext bcc) throws Exception |
| { |
| Object result = createBeanInstance(bcc); |
| initBeanInstance(result, bcc); |
| return result; |
| } |
| |
| /** |
| * Returns the default bean class used by this factory. This is always |
| * <b>null</b> for this implementation. |
| * |
| * @return the default bean class |
| */ |
| @Override |
| public Class<?> getDefaultBeanClass() |
| { |
| return null; |
| } |
| |
| /** |
| * Creates the bean instance. This method is called by |
| * {@code createBean()}. It uses reflection to create a new instance |
| * of the specified class. |
| * |
| * @param bcc the context object defining the bean to be created |
| * @return the new bean instance |
| * @throws Exception if an error occurs |
| */ |
| protected Object createBeanInstance(BeanCreationContext bcc) |
| throws Exception |
| { |
| Constructor<?> ctor = |
| findMatchingConstructor(bcc.getBeanClass(), |
| bcc.getBeanDeclaration()); |
| Object[] args = fetchConstructorArgs(ctor, bcc); |
| return ctor.newInstance(args); |
| } |
| |
| /** |
| * Initializes the newly created bean instance. This method is called by |
| * {@code createBean()}. It calls the {@code initBean()} method of the |
| * context object for performing the initialization. |
| * |
| * @param bean the newly created bean instance |
| * @param bcc the context object defining the bean to be created |
| * @throws Exception if an error occurs |
| */ |
| protected void initBeanInstance(Object bean, BeanCreationContext bcc) throws Exception |
| { |
| bcc.initBean(bean, bcc.getBeanDeclaration()); |
| } |
| |
| /** |
| * Evaluates constructor arguments in the specified {@code BeanDeclaration} |
| * and tries to find a unique matching constructor. If this is not possible, |
| * an exception is thrown. Note: This method is intended to be used by |
| * concrete {@link BeanFactory} implementations and not by client code. |
| * |
| * @param beanClass the class of the bean to be created |
| * @param data the current {@code BeanDeclaration} |
| * @param <T> the type of the bean to be created |
| * @return the single matching constructor |
| * @throws ConfigurationRuntimeException if no single matching constructor |
| * can be found |
| * @throws NullPointerException if the bean class or bean declaration are |
| * <b>null</b> |
| */ |
| protected static <T> Constructor<T> findMatchingConstructor( |
| Class<T> beanClass, BeanDeclaration data) |
| { |
| List<Constructor<T>> matchingConstructors = |
| findMatchingConstructors(beanClass, data); |
| checkSingleMatchingConstructor(beanClass, data, matchingConstructors); |
| return matchingConstructors.get(0); |
| } |
| |
| /** |
| * Obtains the arguments for a constructor call to create a bean. This method |
| * resolves nested bean declarations and performs necessary type |
| * conversions. |
| * |
| * @param ctor the constructor to be invoked |
| * @param bcc the context object defining the bean to be created |
| * @return an array with constructor arguments |
| */ |
| private Object[] fetchConstructorArgs(Constructor<?> ctor, |
| BeanCreationContext bcc) |
| { |
| Class<?>[] types = ctor.getParameterTypes(); |
| assert types.length == nullSafeConstructorArgs(bcc.getBeanDeclaration()).size() |
| : "Wrong number of constructor arguments!"; |
| Object[] args = new Object[types.length]; |
| int idx = 0; |
| |
| for (ConstructorArg arg : nullSafeConstructorArgs(bcc.getBeanDeclaration())) |
| { |
| Object val = |
| arg.isNestedBeanDeclaration() ? bcc.createBean(arg |
| .getBeanDeclaration()) : arg.getValue(); |
| args[idx] = getConversionHandler().to(val, types[idx], null); |
| idx++; |
| } |
| |
| return args; |
| } |
| |
| /** |
| * Fetches constructor arguments from the given bean declaration. Handles |
| * <b>null</b> values safely. |
| * |
| * @param data the bean declaration |
| * @return the collection with constructor arguments (never <b>null</b>) |
| */ |
| private static Collection<ConstructorArg> nullSafeConstructorArgs( |
| BeanDeclaration data) |
| { |
| Collection<ConstructorArg> args = data.getConstructorArgs(); |
| if (args == null) |
| { |
| args = Collections.emptySet(); |
| } |
| return args; |
| } |
| |
| /** |
| * Returns a list with all constructors which are compatible with the |
| * constructor arguments specified by the given {@code BeanDeclaration}. |
| * |
| * @param beanClass the bean class to be instantiated |
| * @param data the current {@code BeanDeclaration} |
| * @return a list with all matching constructors |
| */ |
| private static <T> List<Constructor<T>> findMatchingConstructors( |
| Class<T> beanClass, BeanDeclaration data) |
| { |
| List<Constructor<T>> result = new LinkedList<Constructor<T>>(); |
| Collection<ConstructorArg> args = getConstructorArgs(data); |
| for (Constructor<?> ctor : beanClass.getConstructors()) |
| { |
| if (matchesConstructor(ctor, args)) |
| { |
| // cast should be okay according to the JavaDocs of |
| // getConstructors() |
| @SuppressWarnings("unchecked") |
| Constructor<T> match = (Constructor<T>) ctor; |
| result.add(match); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Checks whether the given constructor is compatible with the given list of |
| * arguments. |
| * |
| * @param ctor the constructor to be checked |
| * @param args the collection of constructor arguments |
| * @return a flag whether this constructor is compatible with the given |
| * arguments |
| */ |
| private static boolean matchesConstructor(Constructor<?> ctor, |
| Collection<ConstructorArg> args) |
| { |
| Class<?>[] types = ctor.getParameterTypes(); |
| if (types.length != args.size()) |
| { |
| return false; |
| } |
| |
| int idx = 0; |
| for (ConstructorArg arg : args) |
| { |
| if (!arg.matches(types[idx++])) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Helper method for extracting constructor arguments from a bean |
| * declaration. Deals with <b>null</b> values. |
| * |
| * @param data the bean declaration |
| * @return the collection with constructor arguments (never <b>null</b>) |
| */ |
| private static Collection<ConstructorArg> getConstructorArgs( |
| BeanDeclaration data) |
| { |
| Collection<ConstructorArg> args = data.getConstructorArgs(); |
| if (args == null) |
| { |
| args = Collections.emptySet(); |
| } |
| return args; |
| } |
| |
| /** |
| * Helper method for testing whether exactly one matching constructor was |
| * found. Throws a meaningful exception if there is not a single matching |
| * constructor. |
| * |
| * @param beanClass the bean class |
| * @param data the bean declaration |
| * @param matchingConstructors the list with matching constructors |
| * @throws ConfigurationRuntimeException if there is not exactly one match |
| */ |
| private static <T> void checkSingleMatchingConstructor(Class<T> beanClass, |
| BeanDeclaration data, List<Constructor<T>> matchingConstructors) |
| { |
| if (matchingConstructors.isEmpty()) |
| { |
| throw constructorMatchingException(beanClass, data, |
| "No matching constructor found"); |
| } |
| if (matchingConstructors.size() > 1) |
| { |
| throw constructorMatchingException(beanClass, data, |
| "Multiple matching constructors found"); |
| } |
| } |
| |
| /** |
| * Creates an exception if no single matching constructor was found with a |
| * meaningful error message. |
| * |
| * @param beanClass the affected bean class |
| * @param data the bean declaration |
| * @param msg an error message |
| * @return the exception with the error message |
| */ |
| private static ConfigurationRuntimeException constructorMatchingException( |
| Class<?> beanClass, BeanDeclaration data, String msg) |
| { |
| return new ConfigurationRuntimeException(String.format(FMT_CTOR_ERROR, |
| msg, beanClass.getName(), getConstructorArgs(data).toString())); |
| } |
| } |