Coverage Report - org.apache.commons.configuration.beanutils.BeanHelper
 
Classes in this File Line Coverage Branch Coverage Complexity
BeanHelper
87%
94/108
87%
42/48
3,611
 
 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.configuration.beanutils;
 18  
 
 19  
 import java.beans.PropertyDescriptor;
 20  
 import java.lang.reflect.InvocationTargetException;
 21  
 import java.util.ArrayList;
 22  
 import java.util.Collection;
 23  
 import java.util.Collections;
 24  
 import java.util.HashMap;
 25  
 import java.util.List;
 26  
 import java.util.Map;
 27  
 import java.util.Set;
 28  
 import java.util.TreeSet;
 29  
 
 30  
 import org.apache.commons.beanutils.BeanUtils;
 31  
 import org.apache.commons.beanutils.PropertyUtils;
 32  
 import org.apache.commons.configuration.ConfigurationRuntimeException;
 33  
 import org.apache.commons.lang.ClassUtils;
 34  
 
 35  
 /**
 36  
  * <p>
 37  
  * A helper class for creating bean instances that are defined in configuration
 38  
  * files.
 39  
  * </p>
 40  
  * <p>
 41  
  * This class provides static utility methods related to bean creation
 42  
  * operations. These methods simplify such operations because a client need not
 43  
  * deal with all involved interfaces. Usually, if a bean declaration has already
 44  
  * been obtained, a single method call is necessary to create a new bean
 45  
  * instance.
 46  
  * </p>
 47  
  * <p>
 48  
  * This class also supports the registration of custom bean factories.
 49  
  * Implementations of the {@link BeanFactory} interface can be
 50  
  * registered under a symbolic name using the {@code registerBeanFactory()}
 51  
  * method. In the configuration file the name of the bean factory can be
 52  
  * specified in the bean declaration. Then this factory will be used to create
 53  
  * the bean.
 54  
  * </p>
 55  
  *
 56  
  * @since 1.3
 57  
  * @author <a
 58  
  * href="http://commons.apache.org/configuration/team-list.html">Commons
 59  
  * Configuration team</a>
 60  
  * @version $Id: BeanHelper.java 1534393 2013-10-21 22:02:27Z henning $
 61  
  */
 62  
 public final class BeanHelper
 63  
 {
 64  
     /** Stores a map with the registered bean factories. */
 65  1
     private static final Map<String, BeanFactory> BEAN_FACTORIES = Collections
 66  
             .synchronizedMap(new HashMap<String, BeanFactory>());
 67  
 
 68  
     /**
 69  
      * Stores the default bean factory, which will be used if no other factory
 70  
      * is provided.
 71  
      */
 72  1
     private static BeanFactory defaultBeanFactory = DefaultBeanFactory.INSTANCE;
 73  
 
 74  
     /**
 75  
      * Private constructor, so no instances can be created.
 76  
      */
 77  
     private BeanHelper()
 78  0
     {
 79  0
     }
 80  
 
 81  
     /**
 82  
      * Register a bean factory under a symbolic name. This factory object can
 83  
      * then be specified in bean declarations with the effect that this factory
 84  
      * will be used to obtain an instance for the corresponding bean
 85  
      * declaration.
 86  
      *
 87  
      * @param name the name of the factory
 88  
      * @param factory the factory to be registered
 89  
      */
 90  
     public static void registerBeanFactory(String name, BeanFactory factory)
 91  
     {
 92  13
         if (name == null)
 93  
         {
 94  1
             throw new IllegalArgumentException(
 95  
                     "Name for bean factory must not be null!");
 96  
         }
 97  12
         if (factory == null)
 98  
         {
 99  1
             throw new IllegalArgumentException("Bean factory must not be null!");
 100  
         }
 101  
 
 102  11
         BEAN_FACTORIES.put(name, factory);
 103  11
     }
 104  
 
 105  
     /**
 106  
      * Deregisters the bean factory with the given name. After that this factory
 107  
      * cannot be used any longer.
 108  
      *
 109  
      * @param name the name of the factory to be deregistered
 110  
      * @return the factory that was registered under this name; <b>null</b> if
 111  
      * there was no such factory
 112  
      */
 113  
     public static BeanFactory deregisterBeanFactory(String name)
 114  
     {
 115  12
         return BEAN_FACTORIES.remove(name);
 116  
     }
 117  
 
 118  
     /**
 119  
      * Returns a set with the names of all currently registered bean factories.
 120  
      *
 121  
      * @return a set with the names of the registered bean factories
 122  
      */
 123  
     public static Set<String> registeredFactoryNames()
 124  
     {
 125  219
         return BEAN_FACTORIES.keySet();
 126  
     }
 127  
 
 128  
     /**
 129  
      * Returns the default bean factory.
 130  
      *
 131  
      * @return the default bean factory
 132  
      */
 133  
     public static BeanFactory getDefaultBeanFactory()
 134  
     {
 135  425
         return defaultBeanFactory;
 136  
     }
 137  
 
 138  
     /**
 139  
      * Sets the default bean factory. This factory will be used for all create
 140  
      * operations, for which no special factory is provided in the bean
 141  
      * declaration.
 142  
      *
 143  
      * @param factory the default bean factory (must not be <b>null</b>)
 144  
      */
 145  
     public static void setDefaultBeanFactory(BeanFactory factory)
 146  
     {
 147  23
         if (factory == null)
 148  
         {
 149  1
             throw new IllegalArgumentException(
 150  
                     "Default bean factory must not be null!");
 151  
         }
 152  22
         defaultBeanFactory = factory;
 153  22
     }
 154  
 
 155  
     /**
 156  
      * Initializes the passed in bean. This method will obtain all the bean's
 157  
      * properties that are defined in the passed in bean declaration. These
 158  
      * properties will be set on the bean. If necessary, further beans will be
 159  
      * created recursively.
 160  
      *
 161  
      * @param bean the bean to be initialized
 162  
      * @param data the bean declaration
 163  
      * @throws ConfigurationRuntimeException if a property cannot be set
 164  
      */
 165  
     public static void initBean(Object bean, BeanDeclaration data)
 166  
             throws ConfigurationRuntimeException
 167  
     {
 168  673
         initBeanProperties(bean, data);
 169  
 
 170  672
         Map<String, Object> nestedBeans = data.getNestedBeanDeclarations();
 171  672
         if (nestedBeans != null)
 172  
         {
 173  662
             if (bean instanceof Collection)
 174  
             {
 175  
                 // This is safe because the collection stores the values of the
 176  
                 // nested beans.
 177  
                 @SuppressWarnings("unchecked")
 178  1
                 Collection<Object> coll = (Collection<Object>) bean;
 179  1
                 if (nestedBeans.size() == 1)
 180  
                 {
 181  1
                     Map.Entry<String, Object> e = nestedBeans.entrySet().iterator().next();
 182  1
                     String propName = e.getKey();
 183  1
                     Class<?> defaultClass = getDefaultClass(bean, propName);
 184  1
                     if (e.getValue() instanceof List)
 185  
                     {
 186  
                         // This is safe, provided that the bean declaration is implemented
 187  
                         // correctly.
 188  
                         @SuppressWarnings("unchecked")
 189  1
                         List<BeanDeclaration> decls = (List<BeanDeclaration>) e.getValue();
 190  1
                         for (BeanDeclaration decl : decls)
 191  
                         {
 192  2
                             coll.add(createBean(decl, defaultClass));
 193  2
                         }
 194  1
                     }
 195  
                     else
 196  
                     {
 197  0
                         BeanDeclaration decl = (BeanDeclaration) e.getValue();
 198  0
                         coll.add(createBean(decl, defaultClass));
 199  
                     }
 200  
                 }
 201  1
             }
 202  
             else
 203  
             {
 204  661
                 for (Map.Entry<String, Object> e : nestedBeans.entrySet())
 205  
                 {
 206  233
                     String propName = e.getKey();
 207  233
                     Class<?> defaultClass = getDefaultClass(bean, propName);
 208  
 
 209  233
                     Object prop = e.getValue();
 210  
 
 211  233
                     if (prop instanceof Collection)
 212  
                     {
 213  1
                         Collection<Object> beanCollection =
 214  
                                 createPropertyCollection(propName, defaultClass);
 215  
 
 216  1
                         for (Object elemDef : (Collection<?>) prop)
 217  
                         {
 218  2
                             beanCollection
 219  
                                     .add(createBean((BeanDeclaration) elemDef));
 220  2
                         }
 221  
 
 222  1
                         initProperty(bean, propName, beanCollection);
 223  1
                     }
 224  
                     else
 225  
                     {
 226  232
                         initProperty(bean, propName, createBean(
 227  
                             (BeanDeclaration) e.getValue(), defaultClass));
 228  
                     }
 229  233
                 }
 230  
             }
 231  
         }
 232  672
     }
 233  
 
 234  
     /**
 235  
      * Initializes the beans properties.
 236  
      *
 237  
      * @param bean the bean to be initialized
 238  
      * @param data the bean declaration
 239  
      * @throws ConfigurationRuntimeException if a property cannot be set
 240  
      */
 241  
     public static void initBeanProperties(Object bean, BeanDeclaration data)
 242  
             throws ConfigurationRuntimeException
 243  
     {
 244  673
         Map<String, Object> properties = data.getBeanProperties();
 245  673
         if (properties != null)
 246  
         {
 247  672
             for (Map.Entry<String, Object> e : properties.entrySet())
 248  
             {
 249  705
                 String propName = e.getKey();
 250  705
                 initProperty(bean, propName, e.getValue());
 251  704
             }
 252  
         }
 253  672
     }
 254  
 
 255  
     /**
 256  
      * Return the Class of the property if it can be determined.
 257  
      * @param bean The bean containing the property.
 258  
      * @param propName The name of the property.
 259  
      * @return The class associated with the property or null.
 260  
      */
 261  
     private static Class<?> getDefaultClass(Object bean, String propName)
 262  
     {
 263  
         try
 264  
         {
 265  234
             PropertyDescriptor desc = PropertyUtils.getPropertyDescriptor(bean, propName);
 266  234
             if (desc == null)
 267  
             {
 268  0
                 return null;
 269  
             }
 270  234
             return desc.getPropertyType();
 271  
         }
 272  0
         catch (Exception ex)
 273  
         {
 274  0
             return null;
 275  
         }
 276  
     }
 277  
 
 278  
     /**
 279  
      * Sets a property on the given bean using Common Beanutils.
 280  
      *
 281  
      * @param bean the bean
 282  
      * @param propName the name of the property
 283  
      * @param value the property's value
 284  
      * @throws ConfigurationRuntimeException if the property is not writeable or
 285  
      * an error occurred
 286  
      */
 287  
     private static void initProperty(Object bean, String propName, Object value)
 288  
             throws ConfigurationRuntimeException
 289  
     {
 290  993
         if (!PropertyUtils.isWriteable(bean, propName))
 291  
         {
 292  1
             throw new ConfigurationRuntimeException("Property " + propName
 293  
                     + " cannot be set on " + bean.getClass().getName());
 294  
         }
 295  
 
 296  
         try
 297  
         {
 298  992
             BeanUtils.setProperty(bean, propName, value);
 299  
         }
 300  0
         catch (IllegalAccessException iaex)
 301  
         {
 302  0
             throw new ConfigurationRuntimeException(iaex);
 303  
         }
 304  0
         catch (InvocationTargetException itex)
 305  
         {
 306  0
             throw new ConfigurationRuntimeException(itex);
 307  992
         }
 308  992
     }
 309  
 
 310  
     /**
 311  
      * Creates a concrete collection instance to populate a property of type
 312  
      * collection. This method tries to guess an appropriate collection type.
 313  
      * Mostly the type of the property will be one of the collection interfaces
 314  
      * rather than a concrete class; so we have to create a concrete equivalent.
 315  
      *
 316  
      * @param propName the name of the collection property
 317  
      * @param propertyClass the type of the property
 318  
      * @return the newly created collection
 319  
      */
 320  
     private static Collection<Object> createPropertyCollection(String propName,
 321  
             Class<?> propertyClass)
 322  
     {
 323  1
         Collection<Object> beanCollection = null;
 324  
 
 325  1
         if (List.class.isAssignableFrom(propertyClass))
 326  
         {
 327  1
             beanCollection = new ArrayList<Object>();
 328  
         }
 329  0
         else if (Set.class.isAssignableFrom(propertyClass))
 330  
         {
 331  0
             beanCollection = new TreeSet<Object>();
 332  
         }
 333  
         else
 334  
         {
 335  0
             throw new UnsupportedOperationException(
 336  
                     "Unable to handle collection of type : "
 337  
                             + propertyClass.getName() + " for property "
 338  
                             + propName);
 339  
         }
 340  1
         return beanCollection;
 341  
     }
 342  
 
 343  
     /**
 344  
      * Set a property on the bean only if the property exists
 345  
      *
 346  
      * @param bean the bean
 347  
      * @param propName the name of the property
 348  
      * @param value the property's value
 349  
      * @throws ConfigurationRuntimeException if the property is not writeable or
 350  
      *         an error occurred
 351  
      */
 352  
     public static void setProperty(Object bean, String propName, Object value)
 353  
     {
 354  61
         if (PropertyUtils.isWriteable(bean, propName))
 355  
         {
 356  55
             initProperty(bean, propName, value);
 357  
         }
 358  61
     }
 359  
 
 360  
     /**
 361  
      * The main method for creating and initializing beans from a configuration.
 362  
      * This method will return an initialized instance of the bean class
 363  
      * specified in the passed in bean declaration. If this declaration does not
 364  
      * contain the class of the bean, the passed in default class will be used.
 365  
      * From the bean declaration the factory to be used for creating the bean is
 366  
      * queried. The declaration may here return <b>null</b>, then a default
 367  
      * factory is used. This factory is then invoked to perform the create
 368  
      * operation.
 369  
      *
 370  
      * @param data the bean declaration
 371  
      * @param defaultClass the default class to use
 372  
      * @param param an additional parameter that will be passed to the bean
 373  
      * factory; some factories may support parameters and behave different
 374  
      * depending on the value passed in here
 375  
      * @return the new bean
 376  
      * @throws ConfigurationRuntimeException if an error occurs
 377  
      */
 378  
     public static Object createBean(BeanDeclaration data, Class<?> defaultClass,
 379  
             Object param) throws ConfigurationRuntimeException
 380  
     {
 381  672
         if (data == null)
 382  
         {
 383  1
             throw new IllegalArgumentException(
 384  
                     "Bean declaration must not be null!");
 385  
         }
 386  
 
 387  671
         BeanFactory factory = fetchBeanFactory(data);
 388  
         try
 389  
         {
 390  670
             return factory.createBean(fetchBeanClass(data, defaultClass,
 391  
                     factory), data, param);
 392  
         }
 393  7
         catch (Exception ex)
 394  
         {
 395  7
             throw new ConfigurationRuntimeException(ex);
 396  
         }
 397  
     }
 398  
 
 399  
     /**
 400  
      * Returns a bean instance for the specified declaration. This method is a
 401  
      * short cut for {@code createBean(data, null, null);}.
 402  
      *
 403  
      * @param data the bean declaration
 404  
      * @param defaultClass the class to be used when in the declaration no class
 405  
      * is specified
 406  
      * @return the new bean
 407  
      * @throws ConfigurationRuntimeException if an error occurs
 408  
      */
 409  
     public static Object createBean(BeanDeclaration data, Class<?> defaultClass)
 410  
             throws ConfigurationRuntimeException
 411  
     {
 412  671
         return createBean(data, defaultClass, null);
 413  
     }
 414  
 
 415  
     /**
 416  
      * Returns a bean instance for the specified declaration. This method is a
 417  
      * short cut for {@code createBean(data, null);}.
 418  
      *
 419  
      * @param data the bean declaration
 420  
      * @return the new bean
 421  
      * @throws ConfigurationRuntimeException if an error occurs
 422  
      */
 423  
     public static Object createBean(BeanDeclaration data)
 424  
             throws ConfigurationRuntimeException
 425  
     {
 426  320
         return createBean(data, null);
 427  
     }
 428  
 
 429  
     /**
 430  
      * Returns a {@code java.lang.Class} object for the specified name.
 431  
      * Because class loading can be tricky in some environments the code for
 432  
      * retrieving a class by its name was extracted into this helper method. So
 433  
      * if changes are necessary, they can be made at a single place.
 434  
      *
 435  
      * @param name the name of the class to be loaded
 436  
      * @param callingClass the calling class
 437  
      * @return the class object for the specified name
 438  
      * @throws ClassNotFoundException if the class cannot be loaded
 439  
      */
 440  
     static Class<?> loadClass(String name, Class<?> callingClass)
 441  
             throws ClassNotFoundException
 442  
     {
 443  316
         return ClassUtils.getClass(name);
 444  
     }
 445  
 
 446  
     /**
 447  
      * Determines the class of the bean to be created. If the bean declaration
 448  
      * contains a class name, this class is used. Otherwise it is checked
 449  
      * whether a default class is provided. If this is not the case, the
 450  
      * factory's default class is used. If this class is undefined, too, an
 451  
      * exception is thrown.
 452  
      *
 453  
      * @param data the bean declaration
 454  
      * @param defaultClass the default class
 455  
      * @param factory the bean factory to use
 456  
      * @return the class of the bean to be created
 457  
      * @throws ConfigurationRuntimeException if the class cannot be determined
 458  
      */
 459  
     private static Class<?> fetchBeanClass(BeanDeclaration data,
 460  
             Class<?> defaultClass, BeanFactory factory)
 461  
             throws ConfigurationRuntimeException
 462  
     {
 463  670
         String clsName = data.getBeanClassName();
 464  670
         if (clsName != null)
 465  
         {
 466  
             try
 467  
             {
 468  316
                 return loadClass(clsName, factory.getClass());
 469  
             }
 470  1
             catch (ClassNotFoundException cex)
 471  
             {
 472  1
                 throw new ConfigurationRuntimeException(cex);
 473  
             }
 474  
         }
 475  
 
 476  354
         if (defaultClass != null)
 477  
         {
 478  82
             return defaultClass;
 479  
         }
 480  
 
 481  272
         Class<?> clazz = factory.getDefaultBeanClass();
 482  272
         if (clazz == null)
 483  
         {
 484  1
             throw new ConfigurationRuntimeException(
 485  
                     "Bean class is not specified!");
 486  
         }
 487  271
         return clazz;
 488  
     }
 489  
 
 490  
     /**
 491  
      * Obtains the bean factory to use for creating the specified bean. This
 492  
      * method will check whether a factory is specified in the bean declaration.
 493  
      * If this is not the case, the default bean factory will be used.
 494  
      *
 495  
      * @param data the bean declaration
 496  
      * @return the bean factory to use
 497  
      * @throws ConfigurationRuntimeException if the factory cannot be determined
 498  
      */
 499  
     private static BeanFactory fetchBeanFactory(BeanDeclaration data)
 500  
             throws ConfigurationRuntimeException
 501  
     {
 502  671
         String factoryName = data.getBeanFactoryName();
 503  671
         if (factoryName != null)
 504  
         {
 505  279
             BeanFactory factory = BEAN_FACTORIES.get(factoryName);
 506  279
             if (factory == null)
 507  
             {
 508  1
                 throw new ConfigurationRuntimeException(
 509  
                         "Unknown bean factory: " + factoryName);
 510  
             }
 511  
             else
 512  
             {
 513  278
                 return factory;
 514  
             }
 515  
         }
 516  
         else
 517  
         {
 518  392
             return getDefaultBeanFactory();
 519  
         }
 520  
     }
 521  
 }