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 19 import java.lang.reflect.Constructor; 20 import java.util.Collection; 21 import java.util.Collections; 22 import java.util.LinkedList; 23 import java.util.List; 24 25 import org.apache.commons.configuration2.convert.ConversionHandler; 26 import org.apache.commons.configuration2.convert.DefaultConversionHandler; 27 import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 28 29 /** 30 * <p> 31 * The default implementation of the {@code BeanFactory} interface. 32 * </p> 33 * <p> 34 * This class creates beans of arbitrary types using reflection. Each time the {@code createBean()} method is invoked, a 35 * new bean instance is created. A default bean class is not supported. 36 * </p> 37 * <p> 38 * For data type conversions (which may be needed before invoking methods through reflection to ensure that the current 39 * parameters match their declared types) a {@link ConversionHandler} object is used. An instance of this class can be 40 * passed to the constructor. Alternatively, a default {@code ConversionHandler} instance is used. 41 * </p> 42 * <p> 43 * An instance of this factory class will be set as the default bean factory for the {@link BeanHelper} class. This 44 * means that if not bean factory is specified in a {@link BeanDeclaration}, this default instance will be used. 45 * </p> 46 * 47 * @since 1.3 48 */ 49 public class DefaultBeanFactory implements BeanFactory { 50 51 /** Stores the default instance of this class. */ 52 public static final DefaultBeanFactory INSTANCE = new DefaultBeanFactory(); 53 54 /** A format string for generating error messages for constructor matching. */ 55 private static final String FMT_CTOR_ERROR = "%s! Bean class = %s, constructor arguments = %s"; 56 57 /** The conversion handler used by this instance. */ 58 private final ConversionHandler conversionHandler; 59 60 /** 61 * Constructs a new instance of {@code DefaultBeanFactory} using a default {@code ConversionHandler}. 62 */ 63 public DefaultBeanFactory() { 64 this(null); 65 } 66 67 /** 68 * Constructs a new instance of {@code DefaultBeanFactory} using the specified {@code ConversionHandler} for data type 69 * conversions. 70 * 71 * @param convHandler the {@code ConversionHandler}; can be <b>null</b>, then a default handler is used 72 * @since 2.0 73 */ 74 public DefaultBeanFactory(final ConversionHandler convHandler) { 75 conversionHandler = convHandler != null ? convHandler : DefaultConversionHandler.INSTANCE; 76 } 77 78 /** 79 * Gets the {@code ConversionHandler} used by this object. 80 * 81 * @return the {@code ConversionHandler} 82 * @since 2.0 83 */ 84 public ConversionHandler getConversionHandler() { 85 return conversionHandler; 86 } 87 88 /** 89 * Creates a new bean instance. This implementation delegates to the protected methods {@code createBeanInstance()} and 90 * {@code initBeanInstance()} for creating and initializing the bean. This makes it easier for derived classes that need 91 * to change specific functionality of the base class. 92 * 93 * @param bcc the context object defining the bean to be created 94 * @return the new bean instance 95 * @throws Exception if an error occurs 96 */ 97 @Override 98 public Object createBean(final BeanCreationContext bcc) throws Exception { 99 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 }