View Javadoc

    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
    18  package org.apache.commons.beanutils;
    19
    20
    21  import java.beans.PropertyDescriptor;
    22  import java.util.Collection;
    23  import java.util.HashMap;
    24  import java.util.Iterator;
    25  import java.util.Map;
    26  import java.util.Set;
    27  import java.util.WeakHashMap;
    28
    29
    30  /***
    31   * <p>Implementation of <code>DynaClass</code> for DynaBeans that wrap
    32   * standard JavaBean instances.</p>
    33   *
    34   * <p>
    35   * It is suggested that this class should not usually need to be used directly
    36   * to create new <code>WrapDynaBean</code> instances.
    37   * It's usually better to call the <code>WrapDynaBean</code> constructor directly.
    38   * For example:</p>
    39   * <code><pre>
    40   *   Object javaBean = ...;
    41   *   DynaBean wrapper = new WrapDynaBean(javaBean);
    42   * </pre></code>
    43   * <p>
    44   *
    45   * @author Craig McClanahan
    46   * @version $Revision: 556229 $ $Date: 2007-07-14 07:11:19 +0100 (Sat, 14 Jul 2007) $
    47   */
    48
    49  public class WrapDynaClass implements DynaClass {
    50
    51
    52      // ----------------------------------------------------------- Constructors
    53
    54
    55      /***
    56       * Construct a new WrapDynaClass for the specified JavaBean class.  This
    57       * constructor is private; WrapDynaClass instances will be created as
    58       * needed via calls to the <code>createDynaClass(Class)</code> method.
    59       *
    60       * @param beanClass JavaBean class to be introspected around
    61       */
    62      private WrapDynaClass(Class beanClass) {
    63
    64          this.beanClass = beanClass;
    65          introspect();
    66
    67      }
    68
    69
    70      // ----------------------------------------------------- Instance Variables
    71
    72
    73      /***
    74       * The JavaBean <code>Class</code> which is represented by this
    75       * <code>WrapDynaClass</code>.
    76       */
    77      protected Class beanClass = null;
    78
    79
    80      /***
    81       * The set of PropertyDescriptors for this bean class.
    82       */
    83      protected PropertyDescriptor[] descriptors = null;
    84
    85
    86      /***
    87       * The set of PropertyDescriptors for this bean class, keyed by the
    88       * property name.  Individual descriptor instances will be the same
    89       * instances as those in the <code>descriptors</code> list.
    90       */
    91      protected HashMap descriptorsMap = new HashMap();
    92
    93
    94      /***
    95       * The set of dynamic properties that are part of this DynaClass.
    96       */
    97      protected DynaProperty[] properties = null;
    98
    99
    100     /***
    101      * The set of dynamic properties that are part of this DynaClass,
    102      * keyed by the property name.  Individual descriptor instances will
    103      * be the same instances as those in the <code>properties</code> list.
    104      */
    105     protected HashMap propertiesMap = new HashMap();
    106
    107
    108     // ------------------------------------------------------- Static Variables
    109
    110
    111     private static final ContextClassLoaderLocal CLASSLOADER_CACHE =
    112         new ContextClassLoaderLocal() {
    113             protected Object initialValue() {
    114                 return new WeakHashMap();
    115         }
    116     };
    117
    118     /***
    119      * Get the wrap dyna classes cache
    120      */
    121     private static Map getDynaClassesMap() {
    122         return (Map)CLASSLOADER_CACHE.get();
    123     }
    124
    125     /***
    126      * The set of <code>WrapDynaClass</code> instances that have ever been
    127      * created, keyed by the underlying bean Class. The keys to this map
    128      * are Class objects, and the values are corresponding WrapDynaClass
    129      * objects.
    130      * <p>
    131      * This static variable is safe even when this code is deployed via a
    132      * shared classloader because it is keyed via a Class object. The same
    133      * class loaded via two different classloaders will result in different
    134      * entries in this map.
    135      * <p>
    136      * Note, however, that this HashMap can result in a memory leak. When
    137      * this class is in a shared classloader it will retain references to
    138      * classes loaded via a webapp classloader even after the webapp has been
    139      * undeployed. That will prevent the entire classloader and all the classes
    140      * it refers to and all their static members from being freed.
    141      *
    142      ************* !!!!!!!!!!!! PLEASE NOTE !!!!!!!!!!!! *************
    143      *
    144      * THE FOLLOWING IS A NASTY HACK TO SO THAT BEANUTILS REMAINS BINARY
    145      *              COMPATIBLE WITH PREVIOUS RELEASES.
    146      *
    147      * There are two issues here:
    148      *
    149      * 1) Memory Issues: The static HashMap caused memory problems (See BEANUTILS-59)
    150      *    to resolve this it has been moved into a ContextClassLoaderLocal instance
    151      *    (named CLASSLOADER_CACHE above) which holds one copy per
    152      *    ClassLoader in a WeakHashMap.
    153      *
    154      * 2) Binary Compatibility: As the "dynaClasses" static HashMap is "protected"
    155      *    removing it breaks BeanUtils binary compatibility with previous versions.
    156      *    To resolve this all the methods have been overriden to delegate to the
    157      *    Map for the ClassLoader in the ContextClassLoaderLocal.
    158      *
    159      * @deprecated The dynaClasses Map will be removed in a subsequent release
    160      */
    161     protected static HashMap dynaClasses = new HashMap() {
    162         public void clear() {
    163             getDynaClassesMap().clear();
    164         }
    165         public boolean containsKey(Object key) {
    166             return getDynaClassesMap().containsKey(key);
    167         }
    168         public boolean containsValue(Object value) {
    169             return getDynaClassesMap().containsValue(value);
    170         }
    171         public Set entrySet() {
    172             return getDynaClassesMap().entrySet();
    173         }
    174         public boolean equals(Object o) {
    175             return getDynaClassesMap().equals(o);
    176         }
    177         public Object get(Object key) {
    178             return getDynaClassesMap().get(key);
    179         }
    180         public int hashCode() {
    181             return getDynaClassesMap().hashCode();
    182         }
    183         public boolean isEmpty() {
    184             return getDynaClassesMap().isEmpty();
    185         }
    186         public Set keySet() {
    187             return getDynaClassesMap().keySet();
    188         }
    189         public Object put(Object key, Object value) {
    190             return getDynaClassesMap().put(key, value);
    191         }
    192         public void putAll(Map m) {
    193             getDynaClassesMap().putAll(m);
    194         }
    195         public Object remove(Object key) {
    196             return getDynaClassesMap().remove(key);
    197         }
    198         public int size() {
    199             return getDynaClassesMap().size();
    200         }
    201         public Collection values() {
    202             return getDynaClassesMap().values();
    203         }
    204     };
    205
    206
    207     // ------------------------------------------------------ DynaClass Methods
    208
    209
    210     /***
    211      * Return the name of this DynaClass (analogous to the
    212      * <code>getName()</code> method of <code>java.lang.Class</code), which
    213      * allows the same <code>DynaClass</code> implementation class to support
    214      * different dynamic classes, with different sets of properties.
    215      *
    216      * @return the name of the DynaClass
    217      */
    218     public String getName() {
    219
    220         return (this.beanClass.getName());
    221
    222     }
    223
    224
    225     /***
    226      * Return a property descriptor for the specified property, if it exists;
    227      * otherwise, return <code>null</code>.
    228      *
    229      * @param name Name of the dynamic property for which a descriptor
    230      *  is requested
    231      * @return The descriptor for the specified property
    232      *
    233      * @exception IllegalArgumentException if no property name is specified
    234      */
    235     public DynaProperty getDynaProperty(String name) {
    236
    237         if (name == null) {
    238             throw new IllegalArgumentException
    239                     ("No property name specified");
    240         }
    241         return ((DynaProperty) propertiesMap.get(name));
    242
    243     }
    244
    245
    246     /***
    247      * <p>Return an array of <code>ProperyDescriptors</code> for the properties
    248      * currently defined in this DynaClass.  If no properties are defined, a
    249      * zero-length array will be returned.</p>
    250      *
    251      * <p><strong>FIXME</strong> - Should we really be implementing
    252      * <code>getBeanInfo()</code> instead, which returns property descriptors
    253      * and a bunch of other stuff?</p>
    254      *
    255      * @return the set of properties for this DynaClass
    256      */
    257     public DynaProperty[] getDynaProperties() {
    258
    259         return (properties);
    260
    261     }
    262
    263
    264     /***
    265      * <p>Instantiates a new standard JavaBean instance associated with
    266      * this DynaClass and return it wrapped in a new WrapDynaBean
    267      * instance. <strong>NOTE</strong> the JavaBean should have a
    268      * no argument constructor.</p>
    269      *
    270      * <strong>NOTE</strong> - Most common use cases should not need to use
    271      * this method. It is usually better to create new
    272      * <code>WrapDynaBean</code> instances by calling its constructor.
    273      * For example:</p>
    274      * <code><pre>
    275      *   Object javaBean = ...;
    276      *   DynaBean wrapper = new WrapDynaBean(javaBean);
    277      * </pre></code>
    278      * <p>
    279      * (This method is needed for some kinds of <code>DynaBean</code> framework.)
    280      * </p>
    281      *
    282      * @return A new <code>DynaBean</code> instance
    283      * @exception IllegalAccessException if the Class or the appropriate
    284      *  constructor is not accessible
    285      * @exception InstantiationException if this Class represents an abstract
    286      *  class, an array class, a primitive type, or void; or if instantiation
    287      *  fails for some other reason
    288      */
    289     public DynaBean newInstance()
    290             throws IllegalAccessException, InstantiationException {
    291
    292         return new WrapDynaBean(beanClass.newInstance());
    293
    294     }
    295
    296
    297     // --------------------------------------------------------- Public Methods
    298
    299
    300     /***
    301      * Return the PropertyDescriptor for the specified property name, if any;
    302      * otherwise return <code>null</code>.
    303      *
    304      * @param name Name of the property to be retrieved
    305      * @return The descriptor for the specified property
    306      */
    307     public PropertyDescriptor getPropertyDescriptor(String name) {
    308
    309         return ((PropertyDescriptor) descriptorsMap.get(name));
    310
    311     }
    312
    313
    314     // --------------------------------------------------------- Static Methods
    315
    316
    317     /***
    318      * Clear our cache of WrapDynaClass instances.
    319      */
    320     public static void clear() {
    321
    322         getDynaClassesMap().clear();
    323
    324     }
    325
    326
    327     /***
    328      * Create (if necessary) and return a new <code>WrapDynaClass</code>
    329      * instance for the specified bean class.
    330      *
    331      * @param beanClass Bean class for which a WrapDynaClass is requested
    332      * @return A new <i>Wrap</i> {@link DynaClass}
    333      */
    334     public static WrapDynaClass createDynaClass(Class beanClass) {
    335
    336             WrapDynaClass dynaClass =
    337                     (WrapDynaClass) getDynaClassesMap().get(beanClass);
    338             if (dynaClass == null) {
    339                 dynaClass = new WrapDynaClass(beanClass);
    340                 getDynaClassesMap().put(beanClass, dynaClass);
    341             }
    342             return (dynaClass);
    343
    344     }
    345
    346
    347     // ------------------------------------------------------ Protected Methods
    348
    349
    350     /***
    351      * Introspect our bean class to identify the supported properties.
    352      */
    353     protected void introspect() {
    354
    355         // Look up the property descriptors for this bean class
    356         PropertyDescriptor[] regulars =
    357                 PropertyUtils.getPropertyDescriptors(beanClass);
    358         if (regulars == null) {
    359             regulars = new PropertyDescriptor[0];
    360         }
    361         Map mappeds =
    362                 PropertyUtils.getMappedPropertyDescriptors(beanClass);
    363         if (mappeds == null) {
    364             mappeds = new HashMap();
    365         }
    366
    367         // Construct corresponding DynaProperty information
    368         properties = new DynaProperty[regulars.length + mappeds.size()];
    369         for (int i = 0; i < regulars.length; i++) {
    370             descriptorsMap.put(regulars[i].getName(),
    371                     regulars[i]);
    372             properties[i] =
    373                     new DynaProperty(regulars[i].getName(),
    374                             regulars[i].getPropertyType());
    375             propertiesMap.put(properties[i].getName(),
    376                     properties[i]);
    377         }
    378         int j = regulars.length;
    379         Iterator names = mappeds.keySet().iterator();
    380         while (names.hasNext()) {
    381             String name = (String) names.next();
    382             PropertyDescriptor descriptor =
    383                     (PropertyDescriptor) mappeds.get(name);
    384             properties[j] =
    385                     new DynaProperty(descriptor.getName(),
    386                             Map.class);
    387             propertiesMap.put(properties[j].getName(),
    388                     properties[j]);
    389             j++;
    390         }
    391
    392     }
    393
    394
    395 }