DefaultBeanFactory.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2.beanutils;

  18. import java.lang.reflect.Constructor;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.LinkedList;
  22. import java.util.List;

  23. import org.apache.commons.configuration2.convert.ConversionHandler;
  24. import org.apache.commons.configuration2.convert.DefaultConversionHandler;
  25. import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;

  26. /**
  27.  * <p>
  28.  * The default implementation of the {@code BeanFactory} interface.
  29.  * </p>
  30.  * <p>
  31.  * This class creates beans of arbitrary types using reflection. Each time the {@code createBean()} method is invoked, a
  32.  * new bean instance is created. A default bean class is not supported.
  33.  * </p>
  34.  * <p>
  35.  * For data type conversions (which may be needed before invoking methods through reflection to ensure that the current
  36.  * parameters match their declared types) a {@link ConversionHandler} object is used. An instance of this class can be
  37.  * passed to the constructor. Alternatively, a default {@code ConversionHandler} instance is used.
  38.  * </p>
  39.  * <p>
  40.  * An instance of this factory class will be set as the default bean factory for the {@link BeanHelper} class. This
  41.  * means that if not bean factory is specified in a {@link BeanDeclaration}, this default instance will be used.
  42.  * </p>
  43.  *
  44.  * @since 1.3
  45.  */
  46. public class DefaultBeanFactory implements BeanFactory {

  47.     /** Stores the default instance of this class. */
  48.     public static final DefaultBeanFactory INSTANCE = new DefaultBeanFactory();

  49.     /** A format string for generating error messages for constructor matching. */
  50.     private static final String FMT_CTOR_ERROR = "%s! Bean class = %s, constructor arguments = %s";

  51.     /**
  52.      * Checks whether exactly one matching constructor was found. Throws a meaningful exception if there
  53.      * is not a single matching constructor.
  54.      *
  55.      * @param beanClass the bean class
  56.      * @param data the bean declaration
  57.      * @param matchingConstructors the list with matching constructors
  58.      * @throws ConfigurationRuntimeException if there is not exactly one match
  59.      */
  60.     private static <T> void checkSingleMatchingConstructor(final Class<T> beanClass, final BeanDeclaration data,
  61.         final List<Constructor<T>> matchingConstructors) {
  62.         if (matchingConstructors.isEmpty()) {
  63.             throw constructorMatchingException(beanClass, data, "No matching constructor found");
  64.         }
  65.         if (matchingConstructors.size() > 1) {
  66.             throw constructorMatchingException(beanClass, data, "Multiple matching constructors found");
  67.         }
  68.     }

  69.     /**
  70.      * Constructs an exception if no single matching constructor was found with a meaningful error message.
  71.      *
  72.      * @param beanClass the affected bean class
  73.      * @param data the bean declaration
  74.      * @param msg an error message
  75.      * @return the exception with the error message
  76.      */
  77.     private static ConfigurationRuntimeException constructorMatchingException(final Class<?> beanClass, final BeanDeclaration data, final String msg) {
  78.         return new ConfigurationRuntimeException(FMT_CTOR_ERROR, msg, beanClass.getName(), getConstructorArgs(data).toString());
  79.     }

  80.     /**
  81.      * Evaluates constructor arguments in the specified {@code BeanDeclaration} and tries to find a unique matching
  82.      * constructor. If this is not possible, an exception is thrown. Note: This method is intended to be used by concrete
  83.      * {@link BeanFactory} implementations and not by client code.
  84.      *
  85.      * @param beanClass the class of the bean to be created
  86.      * @param data the current {@code BeanDeclaration}
  87.      * @param <T> the type of the bean to be created
  88.      * @return the single matching constructor
  89.      * @throws ConfigurationRuntimeException if no single matching constructor can be found
  90.      * @throws NullPointerException if the bean class or bean declaration are <strong>null</strong>
  91.      */
  92.     protected static <T> Constructor<T> findMatchingConstructor(final Class<T> beanClass, final BeanDeclaration data) {
  93.         final List<Constructor<T>> matchingConstructors = findMatchingConstructors(beanClass, data);
  94.         checkSingleMatchingConstructor(beanClass, data, matchingConstructors);
  95.         return matchingConstructors.get(0);
  96.     }

  97.     /**
  98.      * Returns a list with all constructors which are compatible with the constructor arguments specified by the given
  99.      * {@code BeanDeclaration}.
  100.      *
  101.      * @param beanClass the bean class to be instantiated
  102.      * @param data the current {@code BeanDeclaration}
  103.      * @return a list with all matching constructors
  104.      */
  105.     private static <T> List<Constructor<T>> findMatchingConstructors(final Class<T> beanClass, final BeanDeclaration data) {
  106.         final List<Constructor<T>> result = new LinkedList<>();
  107.         final Collection<ConstructorArg> args = getConstructorArgs(data);
  108.         for (final Constructor<?> ctor : beanClass.getConstructors()) {
  109.             if (matchesConstructor(ctor, args)) {
  110.                 // cast should be okay according to the Javadocs of
  111.                 // getConstructors()
  112.                 @SuppressWarnings("unchecked")
  113.                 final Constructor<T> match = (Constructor<T>) ctor;
  114.                 result.add(match);
  115.             }
  116.         }
  117.         return result;
  118.     }

  119.     /**
  120.      * Gets constructor arguments from a bean declaration. Deals with <strong>null</strong> values.
  121.      *
  122.      * @param data the bean declaration
  123.      * @return the collection with constructor arguments (never <strong>null</strong>)
  124.      */
  125.     private static Collection<ConstructorArg> getConstructorArgs(final BeanDeclaration data) {
  126.         Collection<ConstructorArg> args = data.getConstructorArgs();
  127.         if (args == null) {
  128.             args = Collections.emptySet();
  129.         }
  130.         return args;
  131.     }

  132.     /**
  133.      * Checks whether the given constructor is compatible with the given list of arguments.
  134.      *
  135.      * @param ctor the constructor to be checked
  136.      * @param args the collection of constructor arguments
  137.      * @return a flag whether this constructor is compatible with the given arguments
  138.      */
  139.     private static boolean matchesConstructor(final Constructor<?> ctor, final Collection<ConstructorArg> args) {
  140.         final Class<?>[] types = ctor.getParameterTypes();
  141.         if (types.length != args.size()) {
  142.             return false;
  143.         }

  144.         int idx = 0;
  145.         for (final ConstructorArg arg : args) {
  146.             if (!arg.matches(types[idx++])) {
  147.                 return false;
  148.             }
  149.         }

  150.         return true;
  151.     }

  152.     /**
  153.      * Fetches constructor arguments from the given bean declaration. Handles <strong>null</strong> values safely.
  154.      *
  155.      * @param data the bean declaration
  156.      * @return the collection with constructor arguments (never <strong>null</strong>)
  157.      */
  158.     private static Collection<ConstructorArg> nullSafeConstructorArgs(final BeanDeclaration data) {
  159.         Collection<ConstructorArg> args = data.getConstructorArgs();
  160.         if (args == null) {
  161.             args = Collections.emptySet();
  162.         }
  163.         return args;
  164.     }

  165.     /** The conversion handler used by this instance. */
  166.     private final ConversionHandler conversionHandler;

  167.     /**
  168.      * Constructs a new instance of {@code DefaultBeanFactory} using a default {@code ConversionHandler}.
  169.      */
  170.     public DefaultBeanFactory() {
  171.         this(null);
  172.     }

  173.     /**
  174.      * Constructs a new instance of {@code DefaultBeanFactory} using the specified {@code ConversionHandler} for data type
  175.      * conversions.
  176.      *
  177.      * @param convHandler the {@code ConversionHandler}; can be <strong>null</strong>, then a default handler is used
  178.      * @since 2.0
  179.      */
  180.     public DefaultBeanFactory(final ConversionHandler convHandler) {
  181.         conversionHandler = convHandler != null ? convHandler : DefaultConversionHandler.INSTANCE;
  182.     }

  183.     /**
  184.      * Creates a new bean instance. This implementation delegates to the protected methods {@code createBeanInstance()} and
  185.      * {@code initBeanInstance()} for creating and initializing the bean. This makes it easier for derived classes that need
  186.      * to change specific functionality of the base class.
  187.      *
  188.      * @param bcc the context object defining the bean to be created
  189.      * @return the new bean instance
  190.      * @throws Exception if an error occurs
  191.      */
  192.     @Override
  193.     public Object createBean(final BeanCreationContext bcc) throws Exception {
  194.         final Object result = createBeanInstance(bcc);
  195.         initBeanInstance(result, bcc);
  196.         return result;
  197.     }

  198.     /**
  199.      * Creates the bean instance. This method is called by {@code createBean()}. It uses reflection to create a new instance
  200.      * of the specified class.
  201.      *
  202.      * @param bcc the context object defining the bean to be created
  203.      * @return the new bean instance
  204.      * @throws Exception if an error occurs
  205.      */
  206.     protected Object createBeanInstance(final BeanCreationContext bcc) throws Exception {
  207.         final Constructor<?> ctor = findMatchingConstructor(bcc.getBeanClass(), bcc.getBeanDeclaration());
  208.         final Object[] args = fetchConstructorArgs(ctor, bcc);
  209.         return ctor.newInstance(args);
  210.     }

  211.     /**
  212.      * Obtains the arguments for a constructor call to create a bean. This method resolves nested bean declarations and
  213.      * performs necessary type conversions.
  214.      *
  215.      * @param ctor the constructor to be invoked
  216.      * @param bcc the context object defining the bean to be created
  217.      * @return an array with constructor arguments
  218.      */
  219.     private Object[] fetchConstructorArgs(final Constructor<?> ctor, final BeanCreationContext bcc) {
  220.         final Class<?>[] types = ctor.getParameterTypes();
  221.         assert types.length == nullSafeConstructorArgs(bcc.getBeanDeclaration()).size() : "Wrong number of constructor arguments!";
  222.         final Object[] args = new Object[types.length];
  223.         int idx = 0;

  224.         for (final ConstructorArg arg : nullSafeConstructorArgs(bcc.getBeanDeclaration())) {
  225.             final Object val = arg.isNestedBeanDeclaration() ? bcc.createBean(arg.getBeanDeclaration()) : arg.getValue();
  226.             args[idx] = getConversionHandler().to(val, types[idx], null);
  227.             idx++;
  228.         }

  229.         return args;
  230.     }

  231.     /**
  232.      * Gets the {@code ConversionHandler} used by this object.
  233.      *
  234.      * @return the {@code ConversionHandler}
  235.      * @since 2.0
  236.      */
  237.     public ConversionHandler getConversionHandler() {
  238.         return conversionHandler;
  239.     }

  240.     /**
  241.      * Gets the default bean class used by this factory. This is always <strong>null</strong> for this implementation.
  242.      *
  243.      * @return the default bean class
  244.      */
  245.     @Override
  246.     public Class<?> getDefaultBeanClass() {
  247.         return null;
  248.     }

  249.     /**
  250.      * Initializes the newly created bean instance. This method is called by {@code createBean()}. It calls the
  251.      * {@code initBean()} method of the context object for performing the initialization.
  252.      *
  253.      * @param bean the newly created bean instance
  254.      * @param bcc the context object defining the bean to be created
  255.      * @throws Exception if an error occurs
  256.      */
  257.     protected void initBeanInstance(final Object bean, final BeanCreationContext bcc) throws Exception {
  258.         bcc.initBean(bean, bcc.getBeanDeclaration());
  259.     }
  260. }