1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
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
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
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
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
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
348
349
350 /***
351 * Introspect our bean class to identify the supported properties.
352 */
353 protected void introspect() {
354
355
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
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 }