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 }