001: /*
002: * Copyright 2002-2005 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005: * in compliance with the License. You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software distributed under the License
010: * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011: * or implied. See the License for the specific language governing permissions and limitations under
012: * the License.
013: */
014:
015: package org.strecks.util;
016:
017: import java.beans.BeanInfo;
018: import java.beans.IntrospectionException;
019: import java.beans.Introspector;
020: import java.beans.PropertyDescriptor;
021: import java.lang.ref.Reference;
022: import java.lang.ref.WeakReference;
023: import java.lang.reflect.Method;
024: import java.lang.reflect.Modifier;
025: import java.util.Collections;
026: import java.util.HashMap;
027: import java.util.Map;
028: import java.util.WeakHashMap;
029:
030: import org.apache.commons.logging.Log;
031: import org.apache.commons.logging.LogFactory;
032: import org.strecks.exceptions.ApplicationRuntimeException;
033:
034: /**
035: * Class taken originally from Spring Framework. Caches PropertyDescriptor information for a Java
036: * class. Package-visible; not for use by application code.
037: *
038: * <p>
039: * Necessary as Introspector.getBeanInfo() in JDK 1.3 will return a new deep copy of the BeanInfo
040: * every time we ask for it. We take the opportunity to hash property descriptors by method name for
041: * fast lookup. Furthermore, we do our own caching of descriptors here, rather than rely on the
042: * JDK's system-wide BeanInfo cache (to avoid leaks on class loader shutdown).
043: *
044: * <p>
045: * Information is cached statically, so we don't need to create new objects of this class for every
046: * JavaBean we manipulate. Thus this class implements the factory design pattern, using a private
047: * constructor and a static <code>forClass</code> method to obtain instances.
048: *
049: * <p>
050: * Phil Zoio: lifted from Spring source and package renamed to remove Spring as a runtime mandatory
051: * distribution file. Other changes:
052: * <ul>
053: * <li>Java 5 type safety compliance.</li>
054: * <li>No longer throws BeansException.</li>
055: * </ul>
056: * </p>
057: *
058: * @author Rod Johnson
059: * @author Juergen Hoeller
060: * @author Phil Zoio
061: * @since 05 May 2001
062: */
063: final class CachedIntrospectionResults {
064:
065: private static final Log logger = LogFactory
066: .getLog(CachedIntrospectionResults.class);
067:
068: /**
069: * Map keyed by class containing CachedIntrospectionResults. Needs to be a WeakHashMap with
070: * WeakReferences as values to allow for proper garbage collection in case of multiple
071: * classloaders.
072: */
073: private static final Map<Class, Object> classCache = Collections
074: .synchronizedMap(new WeakHashMap<Class, Object>());
075:
076: /**
077: * We might use this from the EJB tier, so we don't want to use synchronization. Object
078: * references are atomic, so we can live with doing the occasional unnecessary lookup at startup
079: * only.
080: */
081: static CachedIntrospectionResults forClass(Class clazz)
082: throws ApplicationRuntimeException {
083: CachedIntrospectionResults results = null;
084: Object value = classCache.get(clazz);
085: if (value instanceof Reference) {
086: Reference ref = (Reference) value;
087: results = (CachedIntrospectionResults) ref.get();
088: } else {
089: results = (CachedIntrospectionResults) value;
090: }
091: if (results == null) {
092: // can throw BeansException
093: results = new CachedIntrospectionResults(clazz);
094: boolean cacheSafe = isCacheSafe(clazz);
095: if (logger.isDebugEnabled()) {
096: logger.debug("Class [" + clazz.getName() + "] is "
097: + (!cacheSafe ? "not " : "") + "cache-safe");
098: }
099: if (cacheSafe) {
100: classCache.put(clazz, results);
101: } else {
102: classCache.put(clazz,
103: new WeakReference<CachedIntrospectionResults>(
104: results));
105: }
106: } else {
107: if (logger.isDebugEnabled()) {
108: logger
109: .debug("Using cached introspection results for class ["
110: + clazz.getName() + "]");
111: }
112: }
113: return results;
114: }
115:
116: /**
117: * Check whether the given class is cache-safe, i.e. whether it is loaded by the same class
118: * loader as the CachedIntrospectionResults class or a parent of it.
119: * <p>
120: * Many thanks to Guillaume Poirier for pointing out the garbage collection issues and for
121: * suggesting this solution.
122: * @param clazz
123: * the class to analyze
124: * @return whether the given class is thread-safe
125: */
126: private static boolean isCacheSafe(Class clazz) {
127: ClassLoader cur = CachedIntrospectionResults.class
128: .getClassLoader();
129: ClassLoader target = clazz.getClassLoader();
130: if (target == null || cur == target) {
131: return true;
132: }
133: while (cur != null) {
134: cur = cur.getParent();
135: if (cur == target) {
136: return true;
137: }
138: }
139: return false;
140: }
141:
142: private final BeanInfo beanInfo;
143:
144: /** Property descriptors keyed by property name */
145: private final Map<String, PropertyDescriptor> propertyDescriptorCache;
146:
147: /**
148: * Create new CachedIntrospectionResults instance fot the given class.
149: */
150: private CachedIntrospectionResults(Class clazz)
151: throws ApplicationRuntimeException {
152: try {
153: if (logger.isDebugEnabled()) {
154: logger.debug("Getting BeanInfo for class ["
155: + clazz.getName() + "]");
156: }
157: this .beanInfo = Introspector.getBeanInfo(clazz);
158:
159: // Immediately remove class from Introspector cache, to allow for proper
160: // garbage collection on class loader shutdown - we cache it here anyway,
161: // in a GC-friendly manner. In contrast to CachedIntrospectionResults,
162: // Introspector does not use WeakReferences as values of its WeakHashMap!
163: Class classToFlush = clazz;
164: do {
165: Introspector.flushFromCaches(classToFlush);
166: classToFlush = classToFlush.getSuperclass();
167: } while (classToFlush != null);
168:
169: if (logger.isDebugEnabled()) {
170: logger.debug("Caching PropertyDescriptors for class ["
171: + clazz.getName() + "]");
172: }
173: this .propertyDescriptorCache = new HashMap<String, PropertyDescriptor>();
174:
175: // This call is slow so we do it once.
176: PropertyDescriptor[] pds = this .beanInfo
177: .getPropertyDescriptors();
178: for (int i = 0; i < pds.length; i++) {
179: if (logger.isDebugEnabled()) {
180: logger
181: .debug("Found property '"
182: + pds[i].getName()
183: + "'"
184: + (pds[i].getPropertyType() != null ? " of type ["
185: + pds[i].getPropertyType()
186: .getName() + "]"
187: : "")
188: + (pds[i].getPropertyEditorClass() != null ? "; editor ["
189: + pds[i]
190: .getPropertyEditorClass()
191: .getName() + "]"
192: : ""));
193: }
194:
195: // Set methods accessible if declaring class is not public, for example
196: // in case of package-protected base classes that define bean properties.
197: Method readMethod = pds[i].getReadMethod();
198: if (readMethod != null
199: && !Modifier.isPublic(readMethod
200: .getDeclaringClass().getModifiers())) {
201: readMethod.setAccessible(true);
202: }
203: Method writeMethod = pds[i].getWriteMethod();
204: if (writeMethod != null
205: && !Modifier.isPublic(writeMethod
206: .getDeclaringClass().getModifiers())) {
207: writeMethod.setAccessible(true);
208: }
209:
210: this .propertyDescriptorCache.put(pds[i].getName(),
211: pds[i]);
212: }
213: } catch (IntrospectionException ex) {
214: throw new ApplicationRuntimeException(
215: "Cannot get BeanInfo for object of class ["
216: + clazz.getName() + "]", ex);
217: }
218: }
219:
220: BeanInfo getBeanInfo() {
221: return this .beanInfo;
222: }
223:
224: Class getBeanClass() {
225: return this .beanInfo.getBeanDescriptor().getBeanClass();
226: }
227:
228: PropertyDescriptor getPropertyDescriptor(String propertyName) {
229: return (PropertyDescriptor) this.propertyDescriptorCache
230: .get(propertyName);
231: }
232:
233: }
|