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: 650215 $ $Date: 2008-04-21 19:05:18 +0100 (Mon, 21 Apr 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         */
228        protected Class getBeanClass() {
229            return (Class)beanClassRef.get();
230        }
231    
232        /**
233         * Return the name of this DynaClass (analogous to the
234         * <code>getName()</code> method of <code>java.lang.Class</code), which
235         * allows the same <code>DynaClass</code> implementation class to support
236         * different dynamic classes, with different sets of properties.
237         *
238         * @return the name of the DynaClass
239         */
240        public String getName() {
241    
242            return beanClassName;
243    
244        }
245    
246    
247        /**
248         * Return a property descriptor for the specified property, if it exists;
249         * otherwise, return <code>null</code>.
250         *
251         * @param name Name of the dynamic property for which a descriptor
252         *  is requested
253         * @return The descriptor for the specified property
254         *
255         * @exception IllegalArgumentException if no property name is specified
256         */
257        public DynaProperty getDynaProperty(String name) {
258    
259            if (name == null) {
260                throw new IllegalArgumentException
261                        ("No property name specified");
262            }
263            return ((DynaProperty) propertiesMap.get(name));
264    
265        }
266    
267    
268        /**
269         * <p>Return an array of <code>ProperyDescriptors</code> for the properties
270         * currently defined in this DynaClass.  If no properties are defined, a
271         * zero-length array will be returned.</p>
272         *
273         * <p><strong>FIXME</strong> - Should we really be implementing
274         * <code>getBeanInfo()</code> instead, which returns property descriptors
275         * and a bunch of other stuff?</p>
276         *
277         * @return the set of properties for this DynaClass
278         */
279        public DynaProperty[] getDynaProperties() {
280    
281            return (properties);
282    
283        }
284    
285    
286        /**
287         * <p>Instantiates a new standard JavaBean instance associated with
288         * this DynaClass and return it wrapped in a new WrapDynaBean   
289         * instance. <strong>NOTE</strong> the JavaBean should have a 
290         * no argument constructor.</p>
291         *
292         * <strong>NOTE</strong> - Most common use cases should not need to use
293         * this method. It is usually better to create new
294         * <code>WrapDynaBean</code> instances by calling its constructor.
295         * For example:</p>
296         * <code><pre>
297         *   Object javaBean = ...;
298         *   DynaBean wrapper = new WrapDynaBean(javaBean);
299         * </pre></code>
300         * <p>
301         * (This method is needed for some kinds of <code>DynaBean</code> framework.)
302         * </p>
303         *
304         * @return A new <code>DynaBean</code> instance
305         * @exception IllegalAccessException if the Class or the appropriate
306         *  constructor is not accessible
307         * @exception InstantiationException if this Class represents an abstract
308         *  class, an array class, a primitive type, or void; or if instantiation
309         *  fails for some other reason
310         */
311        public DynaBean newInstance()
312                throws IllegalAccessException, InstantiationException {
313    
314            return new WrapDynaBean(getBeanClass().newInstance());
315    
316        }
317    
318    
319        // --------------------------------------------------------- Public Methods
320    
321    
322        /**
323         * Return the PropertyDescriptor for the specified property name, if any;
324         * otherwise return <code>null</code>.
325         *
326         * @param name Name of the property to be retrieved
327         * @return The descriptor for the specified property
328         */
329        public PropertyDescriptor getPropertyDescriptor(String name) {
330    
331            return ((PropertyDescriptor) descriptorsMap.get(name));
332    
333        }
334    
335    
336        // --------------------------------------------------------- Static Methods
337    
338    
339        /**
340         * Clear our cache of WrapDynaClass instances.
341         */
342        public static void clear() {
343    
344            getDynaClassesMap().clear();
345    
346        }
347    
348    
349        /**
350         * Create (if necessary) and return a new <code>WrapDynaClass</code>
351         * instance for the specified bean class.
352         *
353         * @param beanClass Bean class for which a WrapDynaClass is requested
354         * @return A new <i>Wrap</i> {@link DynaClass}
355         */
356        public static WrapDynaClass createDynaClass(Class beanClass) {
357    
358                WrapDynaClass dynaClass =
359                        (WrapDynaClass) getDynaClassesMap().get(beanClass);
360                if (dynaClass == null) {
361                    dynaClass = new WrapDynaClass(beanClass);
362                    getDynaClassesMap().put(beanClass, dynaClass);
363                }
364                return (dynaClass);
365    
366        }
367    
368    
369        // ------------------------------------------------------ Protected Methods
370    
371    
372        /**
373         * Introspect our bean class to identify the supported properties.
374         */
375        protected void introspect() {
376    
377            // Look up the property descriptors for this bean class
378            Class beanClass = getBeanClass();
379            PropertyDescriptor[] regulars =
380                    PropertyUtils.getPropertyDescriptors(beanClass);
381            if (regulars == null) {
382                regulars = new PropertyDescriptor[0];
383            }
384            Map mappeds =
385                    PropertyUtils.getMappedPropertyDescriptors(beanClass);
386            if (mappeds == null) {
387                mappeds = new HashMap();
388            }
389    
390            // Construct corresponding DynaProperty information
391            properties = new DynaProperty[regulars.length + mappeds.size()];
392            for (int i = 0; i < regulars.length; i++) {
393                descriptorsMap.put(regulars[i].getName(),
394                        regulars[i]);
395                properties[i] =
396                        new DynaProperty(regulars[i].getName(),
397                                regulars[i].getPropertyType());
398                propertiesMap.put(properties[i].getName(),
399                        properties[i]);
400            }
401            int j = regulars.length;
402            Iterator names = mappeds.keySet().iterator();
403            while (names.hasNext()) {
404                String name = (String) names.next();
405                PropertyDescriptor descriptor =
406                        (PropertyDescriptor) mappeds.get(name);
407                properties[j] =
408                        new DynaProperty(descriptor.getName(),
409                                Map.class);
410                propertiesMap.put(properties[j].getName(),
411                        properties[j]);
412                j++;
413            }
414    
415        }
416    
417    
418    }