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: import java.beans.PropertyDescriptor;
021: import java.util.Collection;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.Map;
025: import java.util.Set;
026: import java.util.WeakHashMap;
027:
028: /**
029: * <p>Implementation of <code>DynaClass</code> for DynaBeans that wrap
030: * standard JavaBean instances.</p>
031: *
032: * <p>
033: * It is suggested that this class should not usually need to be used directly
034: * to create new <code>WrapDynaBean</code> instances.
035: * It's usually better to call the <code>WrapDynaBean</code> constructor directly.
036: * For example:</p>
037: * <code><pre>
038: * Object javaBean = ...;
039: * DynaBean wrapper = new WrapDynaBean(javaBean);
040: * </pre></code>
041: * <p>
042: *
043: * @author Craig McClanahan
044: * @version $Revision: 556229 $ $Date: 2007-07-14 07:11:19 +0100 (Sat, 14 Jul 2007) $
045: */
046:
047: public class WrapDynaClass implements DynaClass {
048:
049: // ----------------------------------------------------------- Constructors
050:
051: /**
052: * Construct a new WrapDynaClass for the specified JavaBean class. This
053: * constructor is private; WrapDynaClass instances will be created as
054: * needed via calls to the <code>createDynaClass(Class)</code> method.
055: *
056: * @param beanClass JavaBean class to be introspected around
057: */
058: private WrapDynaClass(Class beanClass) {
059:
060: this .beanClass = beanClass;
061: introspect();
062:
063: }
064:
065: // ----------------------------------------------------- Instance Variables
066:
067: /**
068: * The JavaBean <code>Class</code> which is represented by this
069: * <code>WrapDynaClass</code>.
070: */
071: protected Class beanClass = null;
072:
073: /**
074: * The set of PropertyDescriptors for this bean class.
075: */
076: protected PropertyDescriptor[] descriptors = null;
077:
078: /**
079: * The set of PropertyDescriptors for this bean class, keyed by the
080: * property name. Individual descriptor instances will be the same
081: * instances as those in the <code>descriptors</code> list.
082: */
083: protected HashMap descriptorsMap = new HashMap();
084:
085: /**
086: * The set of dynamic properties that are part of this DynaClass.
087: */
088: protected DynaProperty[] properties = null;
089:
090: /**
091: * The set of dynamic properties that are part of this DynaClass,
092: * keyed by the property name. Individual descriptor instances will
093: * be the same instances as those in the <code>properties</code> list.
094: */
095: protected HashMap propertiesMap = new HashMap();
096:
097: // ------------------------------------------------------- Static Variables
098:
099: private static final ContextClassLoaderLocal CLASSLOADER_CACHE = new ContextClassLoaderLocal() {
100: protected Object initialValue() {
101: return new WeakHashMap();
102: }
103: };
104:
105: /**
106: * Get the wrap dyna classes cache
107: */
108: private static Map getDynaClassesMap() {
109: return (Map) CLASSLOADER_CACHE.get();
110: }
111:
112: /**
113: * The set of <code>WrapDynaClass</code> instances that have ever been
114: * created, keyed by the underlying bean Class. The keys to this map
115: * are Class objects, and the values are corresponding WrapDynaClass
116: * objects.
117: * <p>
118: * This static variable is safe even when this code is deployed via a
119: * shared classloader because it is keyed via a Class object. The same
120: * class loaded via two different classloaders will result in different
121: * entries in this map.
122: * <p>
123: * Note, however, that this HashMap can result in a memory leak. When
124: * this class is in a shared classloader it will retain references to
125: * classes loaded via a webapp classloader even after the webapp has been
126: * undeployed. That will prevent the entire classloader and all the classes
127: * it refers to and all their static members from being freed.
128: *
129: ************* !!!!!!!!!!!! PLEASE NOTE !!!!!!!!!!!! *************
130: *
131: * THE FOLLOWING IS A NASTY HACK TO SO THAT BEANUTILS REMAINS BINARY
132: * COMPATIBLE WITH PREVIOUS RELEASES.
133: *
134: * There are two issues here:
135: *
136: * 1) Memory Issues: The static HashMap caused memory problems (See BEANUTILS-59)
137: * to resolve this it has been moved into a ContextClassLoaderLocal instance
138: * (named CLASSLOADER_CACHE above) which holds one copy per
139: * ClassLoader in a WeakHashMap.
140: *
141: * 2) Binary Compatibility: As the "dynaClasses" static HashMap is "protected"
142: * removing it breaks BeanUtils binary compatibility with previous versions.
143: * To resolve this all the methods have been overriden to delegate to the
144: * Map for the ClassLoader in the ContextClassLoaderLocal.
145: *
146: * @deprecated The dynaClasses Map will be removed in a subsequent release
147: */
148: protected static HashMap dynaClasses = new HashMap() {
149: public void clear() {
150: getDynaClassesMap().clear();
151: }
152:
153: public boolean containsKey(Object key) {
154: return getDynaClassesMap().containsKey(key);
155: }
156:
157: public boolean containsValue(Object value) {
158: return getDynaClassesMap().containsValue(value);
159: }
160:
161: public Set entrySet() {
162: return getDynaClassesMap().entrySet();
163: }
164:
165: public boolean equals(Object o) {
166: return getDynaClassesMap().equals(o);
167: }
168:
169: public Object get(Object key) {
170: return getDynaClassesMap().get(key);
171: }
172:
173: public int hashCode() {
174: return getDynaClassesMap().hashCode();
175: }
176:
177: public boolean isEmpty() {
178: return getDynaClassesMap().isEmpty();
179: }
180:
181: public Set keySet() {
182: return getDynaClassesMap().keySet();
183: }
184:
185: public Object put(Object key, Object value) {
186: return getDynaClassesMap().put(key, value);
187: }
188:
189: public void putAll(Map m) {
190: getDynaClassesMap().putAll(m);
191: }
192:
193: public Object remove(Object key) {
194: return getDynaClassesMap().remove(key);
195: }
196:
197: public int size() {
198: return getDynaClassesMap().size();
199: }
200:
201: public Collection values() {
202: return getDynaClassesMap().values();
203: }
204: };
205:
206: // ------------------------------------------------------ DynaClass Methods
207:
208: /**
209: * Return the name of this DynaClass (analogous to the
210: * <code>getName()</code> method of <code>java.lang.Class</code), which
211: * allows the same <code>DynaClass</code> implementation class to support
212: * different dynamic classes, with different sets of properties.
213: *
214: * @return the name of the DynaClass
215: */
216: public String getName() {
217:
218: return (this .beanClass.getName());
219:
220: }
221:
222: /**
223: * Return a property descriptor for the specified property, if it exists;
224: * otherwise, return <code>null</code>.
225: *
226: * @param name Name of the dynamic property for which a descriptor
227: * is requested
228: * @return The descriptor for the specified property
229: *
230: * @exception IllegalArgumentException if no property name is specified
231: */
232: public DynaProperty getDynaProperty(String name) {
233:
234: if (name == null) {
235: throw new IllegalArgumentException(
236: "No property name specified");
237: }
238: return ((DynaProperty) propertiesMap.get(name));
239:
240: }
241:
242: /**
243: * <p>Return an array of <code>ProperyDescriptors</code> for the properties
244: * currently defined in this DynaClass. If no properties are defined, a
245: * zero-length array will be returned.</p>
246: *
247: * <p><strong>FIXME</strong> - Should we really be implementing
248: * <code>getBeanInfo()</code> instead, which returns property descriptors
249: * and a bunch of other stuff?</p>
250: *
251: * @return the set of properties for this DynaClass
252: */
253: public DynaProperty[] getDynaProperties() {
254:
255: return (properties);
256:
257: }
258:
259: /**
260: * <p>Instantiates a new standard JavaBean instance associated with
261: * this DynaClass and return it wrapped in a new WrapDynaBean
262: * instance. <strong>NOTE</strong> the JavaBean should have a
263: * no argument constructor.</p>
264: *
265: * <strong>NOTE</strong> - Most common use cases should not need to use
266: * this method. It is usually better to create new
267: * <code>WrapDynaBean</code> instances by calling its constructor.
268: * For example:</p>
269: * <code><pre>
270: * Object javaBean = ...;
271: * DynaBean wrapper = new WrapDynaBean(javaBean);
272: * </pre></code>
273: * <p>
274: * (This method is needed for some kinds of <code>DynaBean</code> framework.)
275: * </p>
276: *
277: * @return A new <code>DynaBean</code> instance
278: * @exception IllegalAccessException if the Class or the appropriate
279: * constructor is not accessible
280: * @exception InstantiationException if this Class represents an abstract
281: * class, an array class, a primitive type, or void; or if instantiation
282: * fails for some other reason
283: */
284: public DynaBean newInstance() throws IllegalAccessException,
285: InstantiationException {
286:
287: return new WrapDynaBean(beanClass.newInstance());
288:
289: }
290:
291: // --------------------------------------------------------- Public Methods
292:
293: /**
294: * Return the PropertyDescriptor for the specified property name, if any;
295: * otherwise return <code>null</code>.
296: *
297: * @param name Name of the property to be retrieved
298: * @return The descriptor for the specified property
299: */
300: public PropertyDescriptor getPropertyDescriptor(String name) {
301:
302: return ((PropertyDescriptor) descriptorsMap.get(name));
303:
304: }
305:
306: // --------------------------------------------------------- Static Methods
307:
308: /**
309: * Clear our cache of WrapDynaClass instances.
310: */
311: public static void clear() {
312:
313: getDynaClassesMap().clear();
314:
315: }
316:
317: /**
318: * Create (if necessary) and return a new <code>WrapDynaClass</code>
319: * instance for the specified bean class.
320: *
321: * @param beanClass Bean class for which a WrapDynaClass is requested
322: * @return A new <i>Wrap</i> {@link DynaClass}
323: */
324: public static WrapDynaClass createDynaClass(Class beanClass) {
325:
326: WrapDynaClass dynaClass = (WrapDynaClass) getDynaClassesMap()
327: .get(beanClass);
328: if (dynaClass == null) {
329: dynaClass = new WrapDynaClass(beanClass);
330: getDynaClassesMap().put(beanClass, dynaClass);
331: }
332: return (dynaClass);
333:
334: }
335:
336: // ------------------------------------------------------ Protected Methods
337:
338: /**
339: * Introspect our bean class to identify the supported properties.
340: */
341: protected void introspect() {
342:
343: // Look up the property descriptors for this bean class
344: PropertyDescriptor[] regulars = PropertyUtils
345: .getPropertyDescriptors(beanClass);
346: if (regulars == null) {
347: regulars = new PropertyDescriptor[0];
348: }
349: Map mappeds = PropertyUtils
350: .getMappedPropertyDescriptors(beanClass);
351: if (mappeds == null) {
352: mappeds = new HashMap();
353: }
354:
355: // Construct corresponding DynaProperty information
356: properties = new DynaProperty[regulars.length + mappeds.size()];
357: for (int i = 0; i < regulars.length; i++) {
358: descriptorsMap.put(regulars[i].getName(), regulars[i]);
359: properties[i] = new DynaProperty(regulars[i].getName(),
360: regulars[i].getPropertyType());
361: propertiesMap.put(properties[i].getName(), properties[i]);
362: }
363: int j = regulars.length;
364: Iterator names = mappeds.keySet().iterator();
365: while (names.hasNext()) {
366: String name = (String) names.next();
367: PropertyDescriptor descriptor = (PropertyDescriptor) mappeds
368: .get(name);
369: properties[j] = new DynaProperty(descriptor.getName(),
370: Map.class);
371: propertiesMap.put(properties[j].getName(), properties[j]);
372: j++;
373: }
374:
375: }
376:
377: }
|