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 /** The conversion handler used by this instance. */ 058 private final ConversionHandler conversionHandler; 059 060 /** 061 * Constructs a new instance of {@code DefaultBeanFactory} using a default {@code ConversionHandler}. 062 */ 063 public DefaultBeanFactory() { 064 this(null); 065 } 066 067 /** 068 * Constructs a new instance of {@code DefaultBeanFactory} using the specified {@code ConversionHandler} for data type 069 * conversions. 070 * 071 * @param convHandler the {@code ConversionHandler}; can be <b>null</b>, then a default handler is used 072 * @since 2.0 073 */ 074 public DefaultBeanFactory(final ConversionHandler convHandler) { 075 conversionHandler = convHandler != null ? convHandler : DefaultConversionHandler.INSTANCE; 076 } 077 078 /** 079 * Gets the {@code ConversionHandler} used by this object. 080 * 081 * @return the {@code ConversionHandler} 082 * @since 2.0 083 */ 084 public ConversionHandler getConversionHandler() { 085 return conversionHandler; 086 } 087 088 /** 089 * Creates a new bean instance. This implementation delegates to the protected methods {@code createBeanInstance()} and 090 * {@code initBeanInstance()} for creating and initializing the bean. This makes it easier for derived classes that need 091 * to change specific functionality of the base class. 092 * 093 * @param bcc the context object defining the bean to be created 094 * @return the new bean instance 095 * @throws Exception if an error occurs 096 */ 097 @Override 098 public Object createBean(final BeanCreationContext bcc) throws Exception { 099 final Object result = createBeanInstance(bcc); 100 initBeanInstance(result, bcc); 101 return result; 102 } 103 104 /** 105 * Gets the default bean class used by this factory. This is always <b>null</b> for this implementation. 106 * 107 * @return the default bean class 108 */ 109 @Override 110 public Class<?> getDefaultBeanClass() { 111 return null; 112 } 113 114 /** 115 * Creates the bean instance. This method is called by {@code createBean()}. It uses reflection to create a new instance 116 * of the specified class. 117 * 118 * @param bcc the context object defining the bean to be created 119 * @return the new bean instance 120 * @throws Exception if an error occurs 121 */ 122 protected Object createBeanInstance(final BeanCreationContext bcc) throws Exception { 123 final Constructor<?> ctor = findMatchingConstructor(bcc.getBeanClass(), bcc.getBeanDeclaration()); 124 final Object[] args = fetchConstructorArgs(ctor, bcc); 125 return ctor.newInstance(args); 126 } 127 128 /** 129 * Initializes the newly created bean instance. This method is called by {@code createBean()}. It calls the 130 * {@code initBean()} method of the context object for performing the initialization. 131 * 132 * @param bean the newly created bean instance 133 * @param bcc the context object defining the bean to be created 134 * @throws Exception if an error occurs 135 */ 136 protected void initBeanInstance(final Object bean, final BeanCreationContext bcc) throws Exception { 137 bcc.initBean(bean, bcc.getBeanDeclaration()); 138 } 139 140 /** 141 * Evaluates constructor arguments in the specified {@code BeanDeclaration} and tries to find a unique matching 142 * constructor. If this is not possible, an exception is thrown. Note: This method is intended to be used by concrete 143 * {@link BeanFactory} implementations and not by client code. 144 * 145 * @param beanClass the class of the bean to be created 146 * @param data the current {@code BeanDeclaration} 147 * @param <T> the type of the bean to be created 148 * @return the single matching constructor 149 * @throws ConfigurationRuntimeException if no single matching constructor can be found 150 * @throws NullPointerException if the bean class or bean declaration are <b>null</b> 151 */ 152 protected static <T> Constructor<T> findMatchingConstructor(final Class<T> beanClass, final BeanDeclaration data) { 153 final List<Constructor<T>> matchingConstructors = findMatchingConstructors(beanClass, data); 154 checkSingleMatchingConstructor(beanClass, data, matchingConstructors); 155 return matchingConstructors.get(0); 156 } 157 158 /** 159 * Obtains the arguments for a constructor call to create a bean. This method resolves nested bean declarations and 160 * performs necessary type conversions. 161 * 162 * @param ctor the constructor to be invoked 163 * @param bcc the context object defining the bean to be created 164 * @return an array with constructor arguments 165 */ 166 private Object[] fetchConstructorArgs(final Constructor<?> ctor, final BeanCreationContext bcc) { 167 final Class<?>[] types = ctor.getParameterTypes(); 168 assert types.length == nullSafeConstructorArgs(bcc.getBeanDeclaration()).size() : "Wrong number of constructor arguments!"; 169 final Object[] args = new Object[types.length]; 170 int idx = 0; 171 172 for (final ConstructorArg arg : nullSafeConstructorArgs(bcc.getBeanDeclaration())) { 173 final Object val = arg.isNestedBeanDeclaration() ? bcc.createBean(arg.getBeanDeclaration()) : arg.getValue(); 174 args[idx] = getConversionHandler().to(val, types[idx], null); 175 idx++; 176 } 177 178 return args; 179 } 180 181 /** 182 * Fetches constructor arguments from the given bean declaration. Handles <b>null</b> values safely. 183 * 184 * @param data the bean declaration 185 * @return the collection with constructor arguments (never <b>null</b>) 186 */ 187 private static Collection<ConstructorArg> nullSafeConstructorArgs(final BeanDeclaration data) { 188 Collection<ConstructorArg> args = data.getConstructorArgs(); 189 if (args == null) { 190 args = Collections.emptySet(); 191 } 192 return args; 193 } 194 195 /** 196 * Returns a list with all constructors which are compatible with the constructor arguments specified by the given 197 * {@code BeanDeclaration}. 198 * 199 * @param beanClass the bean class to be instantiated 200 * @param data the current {@code BeanDeclaration} 201 * @return a list with all matching constructors 202 */ 203 private static <T> List<Constructor<T>> findMatchingConstructors(final Class<T> beanClass, final BeanDeclaration data) { 204 final List<Constructor<T>> result = new LinkedList<>(); 205 final Collection<ConstructorArg> args = getConstructorArgs(data); 206 for (final Constructor<?> ctor : beanClass.getConstructors()) { 207 if (matchesConstructor(ctor, args)) { 208 // cast should be okay according to the Javadocs of 209 // getConstructors() 210 @SuppressWarnings("unchecked") 211 final Constructor<T> match = (Constructor<T>) ctor; 212 result.add(match); 213 } 214 } 215 return result; 216 } 217 218 /** 219 * Checks whether the given constructor is compatible with the given list of arguments. 220 * 221 * @param ctor the constructor to be checked 222 * @param args the collection of constructor arguments 223 * @return a flag whether this constructor is compatible with the given arguments 224 */ 225 private static boolean matchesConstructor(final Constructor<?> ctor, final Collection<ConstructorArg> args) { 226 final Class<?>[] types = ctor.getParameterTypes(); 227 if (types.length != args.size()) { 228 return false; 229 } 230 231 int idx = 0; 232 for (final ConstructorArg arg : args) { 233 if (!arg.matches(types[idx++])) { 234 return false; 235 } 236 } 237 238 return true; 239 } 240 241 /** 242 * Gets constructor arguments from a bean declaration. Deals with <b>null</b> values. 243 * 244 * @param data the bean declaration 245 * @return the collection with constructor arguments (never <b>null</b>) 246 */ 247 private static Collection<ConstructorArg> getConstructorArgs(final BeanDeclaration data) { 248 Collection<ConstructorArg> args = data.getConstructorArgs(); 249 if (args == null) { 250 args = Collections.emptySet(); 251 } 252 return args; 253 } 254 255 /** 256 * Checks whether exactly one matching constructor was found. Throws a meaningful exception if there 257 * is not a single matching constructor. 258 * 259 * @param beanClass the bean class 260 * @param data the bean declaration 261 * @param matchingConstructors the list with matching constructors 262 * @throws ConfigurationRuntimeException if there is not exactly one match 263 */ 264 private static <T> void checkSingleMatchingConstructor(final Class<T> beanClass, final BeanDeclaration data, 265 final List<Constructor<T>> matchingConstructors) { 266 if (matchingConstructors.isEmpty()) { 267 throw constructorMatchingException(beanClass, data, "No matching constructor found"); 268 } 269 if (matchingConstructors.size() > 1) { 270 throw constructorMatchingException(beanClass, data, "Multiple matching constructors found"); 271 } 272 } 273 274 /** 275 * Constructs an exception if no single matching constructor was found with a meaningful error message. 276 * 277 * @param beanClass the affected bean class 278 * @param data the bean declaration 279 * @param msg an error message 280 * @return the exception with the error message 281 */ 282 private static ConfigurationRuntimeException constructorMatchingException(final Class<?> beanClass, final BeanDeclaration data, final String msg) { 283 return new ConfigurationRuntimeException(FMT_CTOR_ERROR, msg, beanClass.getName(), getConstructorArgs(data).toString()); 284 } 285}