    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     */
    017
    018    package org.apache.commons.beanutils;
    019
    020
    021    import java.beans.PropertyDescriptor;
    022    import java.lang.ref.Reference;
    023    import java.lang.ref.SoftReference;
    024    import java.util.Collection;
    025    import java.util.HashMap;
    026    import java.util.Iterator;
    027    import java.util.Map;
    028    import java.util.Set;
    029    import java.util.WeakHashMap;
    030
    031
    032    /**
    033     * <p>Implementation of <code>DynaClass</code> for DynaBeans that wrap
    034     * standard JavaBean instances.</p>
    035     *
    036     * <p>
    037     * It is suggested that this class should not usually need to be used directly
    038     * to create new <code>WrapDynaBean</code> instances.
    039     * It's usually better to call the <code>WrapDynaBean</code> constructor directly.
    040     * For example:</p>
    041     * <code><pre>
    042     *   Object javaBean = ...;
    043     *   DynaBean wrapper = new WrapDynaBean(javaBean);
    044     * </pre></code>
    045     * <p>
    046     *
    047     * @author Craig McClanahan
    048     * @version $Revision: 690380 $ $Date: 2008-08-29 21:04:38 +0100 (Fri, 29 Aug 2008) $
    049     */
    050
    051    public class WrapDynaClass implements DynaClass {
    052
    053
    054        // ----------------------------------------------------------- Constructors
    055
    056
    057        /**
    058         * Construct a new WrapDynaClass for the specified JavaBean class.  This
    059         * constructor is private; WrapDynaClass instances will be created as
    060         * needed via calls to the <code>createDynaClass(Class)</code> method.
    061         *
    062         * @param beanClass JavaBean class to be introspected around
    063         */
    064        private WrapDynaClass(Class beanClass) {
    065
    066            this.beanClassRef = new SoftReference(beanClass);
    067            this.beanClassName = beanClass.getName();
    068            introspect();
    069
    070        }
    071
    072
    073        // ----------------------------------------------------- Instance Variables
    074
    075        /**
    076         * Name of the JavaBean class represented by this WrapDynaClass.
    077         */
    078        private String beanClassName = null;
    079
    080        /**
    081         * Reference to the JavaBean class represented by this WrapDynaClass.
    082         */
    083        private Reference beanClassRef = null;
    084
    085        /**
    086         * The JavaBean <code>Class</code> which is represented by this
    087         * <code>WrapDynaClass</code>.
    088         *
    089         * @deprecated No longer initialized, use getBeanClass() method instead
    090         */
    091        protected Class beanClass = null;
    092
    093
    094        /**
    095         * The set of PropertyDescriptors for this bean class.
    096         */
    097        protected PropertyDescriptor[] descriptors = null;
    098
    099
    100        /**
    101         * The set of PropertyDescriptors for this bean class, keyed by the
    102         * property name.  Individual descriptor instances will be the same
    103         * instances as those in the <code>descriptors</code> list.
    104         */
    105        protected HashMap descriptorsMap = new HashMap();
    106
    107
    108        /**
    109         * The set of dynamic properties that are part of this DynaClass.
    110         */
    111        protected DynaProperty[] properties = null;
    112
    113
    114        /**
    115         * The set of dynamic properties that are part of this DynaClass,
    116         * keyed by the property name.  Individual descriptor instances will
    117         * be the same instances as those in the <code>properties</code> list.
    118         */
    119        protected HashMap propertiesMap = new HashMap();
    120
    121
    122        // ------------------------------------------------------- Static Variables
    123
    124
    125        private static final ContextClassLoaderLocal CLASSLOADER_CACHE =
    126            new ContextClassLoaderLocal() {
    127                protected Object initialValue() {
    128                    return new WeakHashMap();
    129            }
    130        };
    131
    132        /**
    133         * Get the wrap dyna classes cache
    134         */
    135        private static Map getDynaClassesMap() {
    136            return (Map)CLASSLOADER_CACHE.get();
    137        }
    138
    139        /**
    140         * The set of <code>WrapDynaClass</code> instances that have ever been
    141         * created, keyed by the underlying bean Class. The keys to this map
    142         * are Class objects, and the values are corresponding WrapDynaClass
    143         * objects.
    144         * <p>
    145         * This static variable is safe even when this code is deployed via a
    146         * shared classloader because it is keyed via a Class object. The same
    147         * class loaded via two different classloaders will result in different
    148         * entries in this map.
    149         * <p>
    150         * Note, however, that this HashMap can result in a memory leak. When
    151         * this class is in a shared classloader it will retain references to
    152         * classes loaded via a webapp classloader even after the webapp has been
    153         * undeployed. That will prevent the entire classloader and all the classes
    154         * it refers to and all their static members from being freed.
    155         *
    156         ************* !!!!!!!!!!!! PLEASE NOTE !!!!!!!!!!!! *************
    157         *
    158         * THE FOLLOWING IS A NASTY HACK TO SO THAT BEANUTILS REMAINS BINARY
    159         *              COMPATIBLE WITH PREVIOUS RELEASES.
    160         *
    161         * There are two issues here:
    162         *
    163         * 1) Memory Issues: The static HashMap caused memory problems (See BEANUTILS-59)
    164         *    to resolve this it has been moved into a ContextClassLoaderLocal instance
    165         *    (named CLASSLOADER_CACHE above) which holds one copy per
    166         *    ClassLoader in a WeakHashMap.
    167         *
    168         * 2) Binary Compatibility: As the "dynaClasses" static HashMap is "protected"
    169         *    removing it breaks BeanUtils binary compatibility with previous versions.
    170         *    To resolve this all the methods have been overriden to delegate to the
    171         *    Map for the ClassLoader in the ContextClassLoaderLocal.
    172         *
    173         * @deprecated The dynaClasses Map will be removed in a subsequent release
    174         */
    175        protected static HashMap dynaClasses = new HashMap() {
    176            public void clear() {
    177                getDynaClassesMap().clear();
    178            }
    179            public boolean containsKey(Object key) {
    180                return getDynaClassesMap().containsKey(key);
    181            }
    182            public boolean containsValue(Object value) {
    183                return getDynaClassesMap().containsValue(value);
    184            }
    185            public Set entrySet() {
    186                return getDynaClassesMap().entrySet();
    187            }
    188            public boolean equals(Object o) {
    189                return getDynaClassesMap().equals(o);
    190            }
    191            public Object get(Object key) {
    192                return getDynaClassesMap().get(key);
    193            }
    194            public int hashCode() {
    195                return getDynaClassesMap().hashCode();
    196            }
    197            public boolean isEmpty() {
    198                return getDynaClassesMap().isEmpty();
    199            }
    200            public Set keySet() {
    201                return getDynaClassesMap().keySet();
    202            }
    203            public Object put(Object key, Object value) {
    204                return getDynaClassesMap().put(key, value);
    205            }
    206            public void putAll(Map m) {
    207                getDynaClassesMap().putAll(m);
    208            }
    209            public Object remove(Object key) {
    210                return getDynaClassesMap().remove(key);
    211            }
    212            public int size() {
    213                return getDynaClassesMap().size();
    214            }
    215            public Collection values() {
    216                return getDynaClassesMap().values();
    217            }
    218        };
    219
    220
    221        // ------------------------------------------------------ DynaClass Methods
    222
    223        /**
    224         * Return the class of the underlying wrapped bean.
    225         *
    226         * @return the class of the underlying wrapped bean
    227         * @since 1.8.0
    228         */
    229        protected Class getBeanClass() {
    230            return (Class)beanClassRef.get();
    231        }
    232
    233        /**
    234         * Return the name of this DynaClass (analogous to the
    235         * <code>getName()</code> method of <code>java.lang.Class</code), which
    236         * allows the same <code>DynaClass</code> implementation class to support
    237         * different dynamic classes, with different sets of properties.
    238         *
    239         * @return the name of the DynaClass
    240         */
    241        public String getName() {
    242
    243            return beanClassName;
    244
    245        }
    246
    247
    248        /**
    249         * Return a property descriptor for the specified property, if it exists;
    250         * otherwise, return <code>null</code>.
    251         *
    252         * @param name Name of the dynamic property for which a descriptor
    253         *  is requested
    254         * @return The descriptor for the specified property
    255         *
    256         * @exception IllegalArgumentException if no property name is specified
    257         */
    258        public DynaProperty getDynaProperty(String name) {
    259
    260            if (name == null) {
    261                throw new IllegalArgumentException
    262                        ("No property name specified");
    263            }
    264            return ((DynaProperty) propertiesMap.get(name));
    265
    266        }
    267
    268
    269        /**
    270         * <p>Return an array of <code>ProperyDescriptors</code> for the properties
    271         * currently defined in this DynaClass.  If no properties are defined, a
    272         * zero-length array will be returned.</p>
    273         *
    274         * <p><strong>FIXME</strong> - Should we really be implementing
    275         * <code>getBeanInfo()</code> instead, which returns property descriptors
    276         * and a bunch of other stuff?</p>
    277         *
    278         * @return the set of properties for this DynaClass
    279         */
    280        public DynaProperty[] getDynaProperties() {
    281
    282            return (properties);
    283
    284        }
    285
    286
    287        /**
    288         * <p>Instantiates a new standard JavaBean instance associated with
    289         * this DynaClass and return it wrapped in a new WrapDynaBean
    290         * instance. <strong>NOTE</strong> the JavaBean should have a
    291         * no argument constructor.</p>
    292         *
    293         * <strong>NOTE</strong> - Most common use cases should not need to use
    294         * this method. It is usually better to create new
    295         * <code>WrapDynaBean</code> instances by calling its constructor.
    296         * For example:</p>
    297         * <code><pre>
    298         *   Object javaBean = ...;
    299         *   DynaBean wrapper = new WrapDynaBean(javaBean);
    300         * </pre></code>
    301         * <p>
    302         * (This method is needed for some kinds of <code>DynaBean</code> framework.)
    303         * </p>
    304         *
    305         * @return A new <code>DynaBean</code> instance
    306         * @exception IllegalAccessException if the Class or the appropriate
    307         *  constructor is not accessible
    308         * @exception InstantiationException if this Class represents an abstract
    309         *  class, an array class, a primitive type, or void; or if instantiation
    310         *  fails for some other reason
    311         */
    312        public DynaBean newInstance()
    313                throws IllegalAccessException, InstantiationException {
    314
    315            return new WrapDynaBean(getBeanClass().newInstance());
    316
    317        }
    318
    319
    320        // --------------------------------------------------------- Public Methods
    321
    322
    323        /**
    324         * Return the PropertyDescriptor for the specified property name, if any;
    325         * otherwise return <code>null</code>.
    326         *
    327         * @param name Name of the property to be retrieved
    328         * @return The descriptor for the specified property
    329         */
    330        public PropertyDescriptor getPropertyDescriptor(String name) {
    331
    332            return ((PropertyDescriptor) descriptorsMap.get(name));
    333
    334        }
    335
    336
    337        // --------------------------------------------------------- Static Methods
    338
    339
    340        /**
    341         * Clear our cache of WrapDynaClass instances.
    342         */
    343        public static void clear() {
    344
    345            getDynaClassesMap().clear();
    346
    347        }
    348
    349
    350        /**
    351         * Create (if necessary) and return a new <code>WrapDynaClass</code>
    352         * instance for the specified bean class.
    353         *
    354         * @param beanClass Bean class for which a WrapDynaClass is requested
    355         * @return A new <i>Wrap</i> {@link DynaClass}
    356         */
    357        public static WrapDynaClass createDynaClass(Class beanClass) {
    358
    359                WrapDynaClass dynaClass =
    360                        (WrapDynaClass) getDynaClassesMap().get(beanClass);
    361                if (dynaClass == null) {
    362                    dynaClass = new WrapDynaClass(beanClass);
    363                    getDynaClassesMap().put(beanClass, dynaClass);
    364                }
    365                return (dynaClass);
    366
    367        }
    368
    369
    370        // ------------------------------------------------------ Protected Methods
    371
    372
    373        /**
    374         * Introspect our bean class to identify the supported properties.
    375         */
    376        protected void introspect() {
    377
    378            // Look up the property descriptors for this bean class
    379            Class beanClass = getBeanClass();
    380            PropertyDescriptor[] regulars =
    381                    PropertyUtils.getPropertyDescriptors(beanClass);
    382            if (regulars == null) {
    383                regulars = new PropertyDescriptor[0];
    384            }
    385            Map mappeds =
    386                    PropertyUtils.getMappedPropertyDescriptors(beanClass);
    387            if (mappeds == null) {
    388                mappeds = new HashMap();
    389            }
    390
    391            // Construct corresponding DynaProperty information
    392            properties = new DynaProperty[regulars.length + mappeds.size()];
    393            for (int i = 0; i < regulars.length; i++) {
    394                descriptorsMap.put(regulars[i].getName(),
    395                        regulars[i]);
    396                properties[i] =
    397                        new DynaProperty(regulars[i].getName(),
    398                                regulars[i].getPropertyType());
    399                propertiesMap.put(properties[i].getName(),
    400                        properties[i]);
    401            }
    402            int j = regulars.length;
    403            Iterator names = mappeds.keySet().iterator();
    404            while (names.hasNext()) {
    405                String name = (String) names.next();
    406                PropertyDescriptor descriptor =
    407                        (PropertyDescriptor) mappeds.get(name);
    408                properties[j] =
    409                        new DynaProperty(descriptor.getName(),
    410                                Map.class);
    411                propertiesMap.put(properties[j].getName(),
    412                        properties[j]);
    413                j++;
    414            }
    415
    416        }
    417
    418
    419    }