/*
* 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;
The default implementation of the BeanFactory
interface.
This class creates beans of arbitrary types using reflection. Each time the createBean()
method is invoked, a new bean instance is created. A default bean class is not supported.
For data type conversions (which may be needed before invoking methods through reflection to ensure that the current parameters match their declared types) a ConversionHandler
object is used. An instance of this class can be passed to the constructor. Alternatively, a default ConversionHandler
instance is used.
An instance of this factory class will be set as the default bean factory for the BeanHelper
class. This means that if not bean factory is specified in a BeanDeclaration
, this default instance will be used.
Since: 1.3
/**
* <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
*/
public class DefaultBeanFactory implements BeanFactory
{
Stores the default instance of this class. /** Stores the default instance of this class. */
public static final DefaultBeanFactory INSTANCE = new DefaultBeanFactory();
A format string for generating error messages for constructor matching. /** 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. /** The conversion handler used by this instance. */
private final ConversionHandler conversionHandler;
Creates a new instance of DefaultBeanFactory
using a default ConversionHandler
. /**
* Creates a new instance of {@code DefaultBeanFactory} using a default
* {@code ConversionHandler}.
*/
public DefaultBeanFactory()
{
this(null);
}
Creates a new instance of DefaultBeanFactory
using the specified ConversionHandler
for data type conversions. Params: - convHandler – the
ConversionHandler
; can be null,
then a default handler is used
Since: 2.0
/**
* 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(final ConversionHandler convHandler)
{
conversionHandler =
convHandler != null ? convHandler
: DefaultConversionHandler.INSTANCE;
}
Returns the ConversionHandler
used by this object. Returns: the ConversionHandler
Since: 2.0
/**
* 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 createBeanInstance()
and initBeanInstance()
for creating and initializing the bean. This makes it easier for derived classes that need to change specific functionality of the base class. Params: - bcc – the context object defining the bean to be created
Throws: - Exception – if an error occurs
Returns: the new bean instance
/**
* 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(final BeanCreationContext bcc) throws Exception
{
final Object result = createBeanInstance(bcc);
initBeanInstance(result, bcc);
return result;
}
Returns the default bean class used by this factory. This is always
null for this implementation.
Returns: the default bean class
/**
* 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 createBean()
. It uses reflection to create a new instance of the specified class. Params: - bcc – the context object defining the bean to be created
Throws: - Exception – if an error occurs
Returns: the new bean instance
/**
* 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(final BeanCreationContext bcc)
throws Exception
{
final Constructor<?> ctor =
findMatchingConstructor(bcc.getBeanClass(),
bcc.getBeanDeclaration());
final Object[] args = fetchConstructorArgs(ctor, bcc);
return ctor.newInstance(args);
}
Initializes the newly created bean instance. This method is called by createBean()
. It calls the initBean()
method of the context object for performing the initialization. Params: - bean – the newly created bean instance
- bcc – the context object defining the bean to be created
Throws: - Exception – if an error occurs
/**
* 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(final Object bean, final BeanCreationContext bcc) throws Exception
{
bcc.initBean(bean, bcc.getBeanDeclaration());
}
Evaluates constructor arguments in the specified 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 BeanFactory
implementations and not by client code. Params: - beanClass – the class of the bean to be created
- data – the current
BeanDeclaration
Type parameters: - <T> – the type of the bean to be created
Throws: - ConfigurationRuntimeException – if no single matching constructor
can be found
- NullPointerException – if the bean class or bean declaration are
null
Returns: the single matching constructor
/**
* 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(
final Class<T> beanClass, final BeanDeclaration data)
{
final 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.
Params: - ctor – the constructor to be invoked
- bcc – the context object defining the bean to be created
Returns: an array with constructor arguments
/**
* 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(final Constructor<?> ctor,
final BeanCreationContext bcc)
{
final Class<?>[] types = ctor.getParameterTypes();
assert types.length == nullSafeConstructorArgs(bcc.getBeanDeclaration()).size()
: "Wrong number of constructor arguments!";
final Object[] args = new Object[types.length];
int idx = 0;
for (final ConstructorArg arg : nullSafeConstructorArgs(bcc.getBeanDeclaration()))
{
final 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
null values safely.
Params: - data – the bean declaration
Returns: the collection with constructor arguments (never null)
/**
* 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(
final 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 BeanDeclaration
. Params: - beanClass – the bean class to be instantiated
- data – the current
BeanDeclaration
Returns: a list with all matching constructors
/**
* 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(
final Class<T> beanClass, final BeanDeclaration data)
{
final List<Constructor<T>> result = new LinkedList<>();
final Collection<ConstructorArg> args = getConstructorArgs(data);
for (final Constructor<?> ctor : beanClass.getConstructors())
{
if (matchesConstructor(ctor, args))
{
// cast should be okay according to the Javadocs of
// getConstructors()
@SuppressWarnings("unchecked")
final
Constructor<T> match = (Constructor<T>) ctor;
result.add(match);
}
}
return result;
}
Checks whether the given constructor is compatible with the given list of
arguments.
Params: - ctor – the constructor to be checked
- args – the collection of constructor arguments
Returns: a flag whether this constructor is compatible with the given
arguments
/**
* 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(final Constructor<?> ctor,
final Collection<ConstructorArg> args)
{
final Class<?>[] types = ctor.getParameterTypes();
if (types.length != args.size())
{
return false;
}
int idx = 0;
for (final ConstructorArg arg : args)
{
if (!arg.matches(types[idx++]))
{
return false;
}
}
return true;
}
Helper method for extracting constructor arguments from a bean
declaration. Deals with null values.
Params: - data – the bean declaration
Returns: the collection with constructor arguments (never null)
/**
* 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(
final 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.
Params: - beanClass – the bean class
- data – the bean declaration
- matchingConstructors – the list with matching constructors
Throws: - ConfigurationRuntimeException – if there is not exactly one match
/**
* 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(final Class<T> beanClass,
final BeanDeclaration data, final 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.
Params: - beanClass – the affected bean class
- data – the bean declaration
- msg – an error message
Returns: the exception with the error message
/**
* 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(
final Class<?> beanClass, final BeanDeclaration data, final String msg)
{
return new ConfigurationRuntimeException(FMT_CTOR_ERROR,
msg, beanClass.getName(), getConstructorArgs(data).toString());
}
}