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    }