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