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 035 * {@code createBean()} method is invoked, a new bean instance is created. A 036 * default bean class is not supported. 037 * </p> 038 * <p> 039 * For data type conversions (which may be needed before invoking methods 040 * through reflection to ensure that the current parameters match their declared 041 * types) a {@link ConversionHandler} object is used. An instance of this class 042 * can be passed to the constructor. Alternatively, a default 043 * {@code ConversionHandler} instance is used. 044 * </p> 045 * <p> 046 * An instance of this factory class will be set as the default bean factory for 047 * the {@link BeanHelper} class. This means that if not bean factory is 048 * specified in a {@link BeanDeclaration}, this default instance will be used. 049 * </p> 050 * 051 * @since 1.3 052 */ 053public class DefaultBeanFactory implements BeanFactory 054{ 055 /** Stores the default instance of this class. */ 056 public static final DefaultBeanFactory INSTANCE = new DefaultBeanFactory(); 057 058 /** A format string for generating error messages for constructor matching. */ 059 private static final String FMT_CTOR_ERROR = 060 "%s! Bean class = %s, constructor arguments = %s"; 061 062 /** The conversion handler used by this instance. */ 063 private final ConversionHandler conversionHandler; 064 065 /** 066 * Creates a new instance of {@code DefaultBeanFactory} using a default 067 * {@code ConversionHandler}. 068 */ 069 public DefaultBeanFactory() 070 { 071 this(null); 072 } 073 074 /** 075 * Creates a new instance of {@code DefaultBeanFactory} using the specified 076 * {@code ConversionHandler} for data type conversions. 077 * 078 * @param convHandler the {@code ConversionHandler}; can be <b>null</b>, 079 * then a default handler is used 080 * @since 2.0 081 */ 082 public DefaultBeanFactory(final ConversionHandler convHandler) 083 { 084 conversionHandler = 085 convHandler != null ? convHandler 086 : DefaultConversionHandler.INSTANCE; 087 } 088 089 /** 090 * Returns the {@code ConversionHandler} used by this object. 091 * 092 * @return the {@code ConversionHandler} 093 * @since 2.0 094 */ 095 public ConversionHandler getConversionHandler() 096 { 097 return conversionHandler; 098 } 099 100 /** 101 * Creates a new bean instance. This implementation delegates to the 102 * protected methods {@code createBeanInstance()} and 103 * {@code initBeanInstance()} for creating and initializing the bean. 104 * This makes it easier for derived classes that need to change specific 105 * functionality of the base class. 106 * 107 * @param bcc the context object defining the bean to be created 108 * @return the new bean instance 109 * @throws Exception if an error occurs 110 */ 111 @Override 112 public Object createBean(final BeanCreationContext bcc) throws Exception 113 { 114 final Object result = createBeanInstance(bcc); 115 initBeanInstance(result, bcc); 116 return result; 117 } 118 119 /** 120 * Returns the default bean class used by this factory. This is always 121 * <b>null</b> for this implementation. 122 * 123 * @return the default bean class 124 */ 125 @Override 126 public Class<?> getDefaultBeanClass() 127 { 128 return null; 129 } 130 131 /** 132 * Creates the bean instance. This method is called by 133 * {@code createBean()}. It uses reflection to create a new instance 134 * of the specified class. 135 * 136 * @param bcc the context object defining the bean to be created 137 * @return the new bean instance 138 * @throws Exception if an error occurs 139 */ 140 protected Object createBeanInstance(final BeanCreationContext bcc) 141 throws Exception 142 { 143 final Constructor<?> ctor = 144 findMatchingConstructor(bcc.getBeanClass(), 145 bcc.getBeanDeclaration()); 146 final Object[] args = fetchConstructorArgs(ctor, bcc); 147 return ctor.newInstance(args); 148 } 149 150 /** 151 * Initializes the newly created bean instance. This method is called by 152 * {@code createBean()}. It calls the {@code initBean()} method of the 153 * context object for performing the initialization. 154 * 155 * @param bean the newly created bean instance 156 * @param bcc the context object defining the bean to be created 157 * @throws Exception if an error occurs 158 */ 159 protected void initBeanInstance(final Object bean, final BeanCreationContext bcc) throws Exception 160 { 161 bcc.initBean(bean, bcc.getBeanDeclaration()); 162 } 163 164 /** 165 * Evaluates constructor arguments in the specified {@code BeanDeclaration} 166 * and tries to find a unique matching constructor. If this is not possible, 167 * an exception is thrown. Note: This method is intended to be used by 168 * concrete {@link BeanFactory} implementations and not by client code. 169 * 170 * @param beanClass the class of the bean to be created 171 * @param data the current {@code BeanDeclaration} 172 * @param <T> the type of the bean to be created 173 * @return the single matching constructor 174 * @throws ConfigurationRuntimeException if no single matching constructor 175 * can be found 176 * @throws NullPointerException if the bean class or bean declaration are 177 * <b>null</b> 178 */ 179 protected static <T> Constructor<T> findMatchingConstructor( 180 final Class<T> beanClass, final BeanDeclaration data) 181 { 182 final List<Constructor<T>> matchingConstructors = 183 findMatchingConstructors(beanClass, data); 184 checkSingleMatchingConstructor(beanClass, data, matchingConstructors); 185 return matchingConstructors.get(0); 186 } 187 188 /** 189 * Obtains the arguments for a constructor call to create a bean. This method 190 * resolves nested bean declarations and performs necessary type 191 * conversions. 192 * 193 * @param ctor the constructor to be invoked 194 * @param bcc the context object defining the bean to be created 195 * @return an array with constructor arguments 196 */ 197 private Object[] fetchConstructorArgs(final Constructor<?> ctor, 198 final BeanCreationContext bcc) 199 { 200 final Class<?>[] types = ctor.getParameterTypes(); 201 assert types.length == nullSafeConstructorArgs(bcc.getBeanDeclaration()).size() 202 : "Wrong number of constructor arguments!"; 203 final Object[] args = new Object[types.length]; 204 int idx = 0; 205 206 for (final ConstructorArg arg : nullSafeConstructorArgs(bcc.getBeanDeclaration())) 207 { 208 final Object val = 209 arg.isNestedBeanDeclaration() ? bcc.createBean(arg 210 .getBeanDeclaration()) : arg.getValue(); 211 args[idx] = getConversionHandler().to(val, types[idx], null); 212 idx++; 213 } 214 215 return args; 216 } 217 218 /** 219 * Fetches constructor arguments from the given bean declaration. Handles 220 * <b>null</b> values safely. 221 * 222 * @param data the bean declaration 223 * @return the collection with constructor arguments (never <b>null</b>) 224 */ 225 private static Collection<ConstructorArg> nullSafeConstructorArgs( 226 final BeanDeclaration data) 227 { 228 Collection<ConstructorArg> args = data.getConstructorArgs(); 229 if (args == null) 230 { 231 args = Collections.emptySet(); 232 } 233 return args; 234 } 235 236 /** 237 * Returns a list with all constructors which are compatible with the 238 * constructor arguments specified by the given {@code BeanDeclaration}. 239 * 240 * @param beanClass the bean class to be instantiated 241 * @param data the current {@code BeanDeclaration} 242 * @return a list with all matching constructors 243 */ 244 private static <T> List<Constructor<T>> findMatchingConstructors( 245 final Class<T> beanClass, final BeanDeclaration data) 246 { 247 final List<Constructor<T>> result = new LinkedList<>(); 248 final Collection<ConstructorArg> args = getConstructorArgs(data); 249 for (final Constructor<?> ctor : beanClass.getConstructors()) 250 { 251 if (matchesConstructor(ctor, args)) 252 { 253 // cast should be okay according to the Javadocs of 254 // getConstructors() 255 @SuppressWarnings("unchecked") 256 final 257 Constructor<T> match = (Constructor<T>) ctor; 258 result.add(match); 259 } 260 } 261 return result; 262 } 263 264 /** 265 * Checks whether the given constructor is compatible with the given list of 266 * arguments. 267 * 268 * @param ctor the constructor to be checked 269 * @param args the collection of constructor arguments 270 * @return a flag whether this constructor is compatible with the given 271 * arguments 272 */ 273 private static boolean matchesConstructor(final Constructor<?> ctor, 274 final Collection<ConstructorArg> args) 275 { 276 final Class<?>[] types = ctor.getParameterTypes(); 277 if (types.length != args.size()) 278 { 279 return false; 280 } 281 282 int idx = 0; 283 for (final ConstructorArg arg : args) 284 { 285 if (!arg.matches(types[idx++])) 286 { 287 return false; 288 } 289 } 290 291 return true; 292 } 293 294 /** 295 * Helper method for extracting constructor arguments from a bean 296 * declaration. Deals with <b>null</b> values. 297 * 298 * @param data the bean declaration 299 * @return the collection with constructor arguments (never <b>null</b>) 300 */ 301 private static Collection<ConstructorArg> getConstructorArgs( 302 final BeanDeclaration data) 303 { 304 Collection<ConstructorArg> args = data.getConstructorArgs(); 305 if (args == null) 306 { 307 args = Collections.emptySet(); 308 } 309 return args; 310 } 311 312 /** 313 * Helper method for testing whether exactly one matching constructor was 314 * found. Throws a meaningful exception if there is not a single matching 315 * constructor. 316 * 317 * @param beanClass the bean class 318 * @param data the bean declaration 319 * @param matchingConstructors the list with matching constructors 320 * @throws ConfigurationRuntimeException if there is not exactly one match 321 */ 322 private static <T> void checkSingleMatchingConstructor(final Class<T> beanClass, 323 final BeanDeclaration data, final List<Constructor<T>> matchingConstructors) 324 { 325 if (matchingConstructors.isEmpty()) 326 { 327 throw constructorMatchingException(beanClass, data, 328 "No matching constructor found"); 329 } 330 if (matchingConstructors.size() > 1) 331 { 332 throw constructorMatchingException(beanClass, data, 333 "Multiple matching constructors found"); 334 } 335 } 336 337 /** 338 * Creates an exception if no single matching constructor was found with a 339 * meaningful error message. 340 * 341 * @param beanClass the affected bean class 342 * @param data the bean declaration 343 * @param msg an error message 344 * @return the exception with the error message 345 */ 346 private static ConfigurationRuntimeException constructorMatchingException( 347 final Class<?> beanClass, final BeanDeclaration data, final String msg) 348 { 349 return new ConfigurationRuntimeException(FMT_CTOR_ERROR, 350 msg, beanClass.getName(), getConstructorArgs(data).toString()); 351 } 352}