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.lang.ref.Reference;
23  import java.lang.ref.SoftReference;
24  import java.util.Collection;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.WeakHashMap;
30  
31  
32  /**
33   * <p>Implementation of <code>DynaClass</code> for DynaBeans that wrap
34   * standard JavaBean instances.</p>
35   *
36   * <p>
37   * It is suggested that this class should not usually need to be used directly
38   * to create new <code>WrapDynaBean</code> instances. 
39   * It's usually better to call the <code>WrapDynaBean</code> constructor directly.
40   * For example:</p>
41   * <code><pre>
42   *   Object javaBean = ...;
43   *   DynaBean wrapper = new WrapDynaBean(javaBean);
44   * </pre></code>
45   * <p>
46   *
47   * @author Craig McClanahan
48   * @version $Revision: 690380 $ $Date: 2008-08-29 16:04:38 -0400 (Fri, 29 Aug 2008) $
49   */
50  
51  public class WrapDynaClass implements DynaClass {
52  
53  
54      // ----------------------------------------------------------- Constructors
55  
56  
57      /**
58       * Construct a new WrapDynaClass for the specified JavaBean class.  This
59       * constructor is private; WrapDynaClass instances will be created as
60       * needed via calls to the <code>createDynaClass(Class)</code> method.
61       *
62       * @param beanClass JavaBean class to be introspected around
63       */
64      private WrapDynaClass(Class beanClass) {
65  
66          this.beanClassRef = new SoftReference(beanClass);
67          this.beanClassName = beanClass.getName();
68          introspect();
69  
70      }
71  
72  
73      // ----------------------------------------------------- Instance Variables
74  
75      /**
76       * Name of the JavaBean class represented by this WrapDynaClass.
77       */
78      private String beanClassName = null;
79  
80      /**
81       * Reference to the JavaBean class represented by this WrapDynaClass.
82       */
83      private Reference beanClassRef = null;
84  
85      /**
86       * The JavaBean <code>Class</code> which is represented by this
87       * <code>WrapDynaClass</code>.
88       *
89       * @deprecated No longer initialized, use getBeanClass() method instead
90       */
91      protected Class beanClass = null;
92  
93  
94      /**
95       * The set of PropertyDescriptors for this bean class.
96       */
97      protected PropertyDescriptor[] descriptors = null;
98  
99  
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 }