1. /*
  2. * Copyright 2002-2004 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.beans;
  17. import java.beans.BeanInfo;
  18. import java.beans.IntrospectionException;
  19. import java.beans.Introspector;
  20. import java.beans.PropertyDescriptor;
  21. import java.lang.ref.Reference;
  22. import java.lang.ref.WeakReference;
  23. import java.util.Collections;
  24. import java.util.HashMap;
  25. import java.util.Map;
  26. import java.util.WeakHashMap;
  27. import org.apache.commons.logging.Log;
  28. import org.apache.commons.logging.LogFactory;
  29. /**
  30. * Class to cache PropertyDescriptor information for a Java class.
  31. * Package-visible; not for use by application code.
  32. *
  33. * <p>Necessary as Introspector.getBeanInfo() in JDK 1.3 will return a new
  34. * deep copy of the BeanInfo every time we ask for it. We take the opportunity
  35. * to hash property descriptors by method name for fast lookup.
  36. *
  37. * <p>Information is cached statically, so we don't need to create new
  38. * objects of this class for every JavaBean we manipulate. Thus this class
  39. * implements the factory design pattern, using a private constructor
  40. * and a static forClass method to obtain instances.
  41. *
  42. * @author Rod Johnson
  43. * @author Juergen Hoeller
  44. * @since 05 May 2001
  45. * @version $Id: CachedIntrospectionResults.java,v 1.12 2004/06/02 00:29:32 jhoeller Exp $
  46. */
  47. final class CachedIntrospectionResults {
  48. private static final Log logger = LogFactory.getLog(CachedIntrospectionResults.class);
  49. /**
  50. * Map keyed by class containing CachedIntrospectionResults.
  51. * Needs to be a WeakHashMap with WeakReferences as values to allow
  52. * for proper garbage collection in case of multiple classloaders.
  53. */
  54. private static final Map classCache = Collections.synchronizedMap(new WeakHashMap());
  55. /**
  56. * We might use this from the EJB tier, so we don't want to use synchronization.
  57. * Object references are atomic, so we can live with doing the occasional
  58. * unnecessary lookup at startup only.
  59. */
  60. static CachedIntrospectionResults forClass(Class clazz) throws BeansException {
  61. CachedIntrospectionResults results = null;
  62. Object value = classCache.get(clazz);
  63. if (value instanceof Reference) {
  64. Reference ref = (Reference) value;
  65. results = (CachedIntrospectionResults) ref.get();
  66. }
  67. else {
  68. results = (CachedIntrospectionResults) value;
  69. }
  70. if (results == null) {
  71. // can throw BeansException
  72. results = new CachedIntrospectionResults(clazz);
  73. boolean cacheSafe = isCacheSafe(clazz);
  74. if (logger.isDebugEnabled()) {
  75. logger.debug("Class [" + clazz.getName() + "] is " + (!cacheSafe ? "not " : "") + "cache-safe");
  76. }
  77. if (cacheSafe) {
  78. classCache.put(clazz, results);
  79. }
  80. else {
  81. classCache.put(clazz, new WeakReference(results));
  82. }
  83. }
  84. else {
  85. if (logger.isDebugEnabled()) {
  86. logger.debug("Using cached introspection results for class [" + clazz.getName() + "]");
  87. }
  88. }
  89. return results;
  90. }
  91. /**
  92. * Check whether the given class is cache-safe,
  93. * i.e. whether it is loaded by the same class loader as the
  94. * CachedIntrospectionResults class or a parent of it.
  95. * <p>Many thanks to Guillaume Poirier for pointing out the
  96. * garbage collection issues and for suggesting this solution.
  97. * @param clazz the class to analyze
  98. * @return whether the given class is thread-safe
  99. */
  100. private static boolean isCacheSafe(Class clazz) {
  101. ClassLoader cur = CachedIntrospectionResults.class.getClassLoader();
  102. ClassLoader target = clazz.getClassLoader();
  103. if (target == null || cur == target) {
  104. return true;
  105. }
  106. while (cur != null) {
  107. cur = cur.getParent();
  108. if (cur == target) {
  109. return true;
  110. }
  111. }
  112. return false;
  113. }
  114. private final BeanInfo beanInfo;
  115. /** Property descriptors keyed by property name */
  116. private final Map propertyDescriptorCache;
  117. /**
  118. * Create new CachedIntrospectionResults instance fot the given class.
  119. */
  120. private CachedIntrospectionResults(Class clazz) throws BeansException {
  121. try {
  122. if (logger.isDebugEnabled()) {
  123. logger.debug("Getting BeanInfo for class [" + clazz.getName() + "]");
  124. }
  125. this.beanInfo = Introspector.getBeanInfo(clazz);
  126. // Immediately remove class from Introspector cache, to allow for proper
  127. // garbage collection on class loader shutdown - we cache it here anyway,
  128. // in a GC-friendly manner. In contrast to CachedIntrospectionResults,
  129. // Introspector does not use WeakReferences as values of its WeakHashMap!
  130. Class classToFlush = clazz;
  131. do {
  132. Introspector.flushFromCaches(classToFlush);
  133. classToFlush = classToFlush.getSuperclass();
  134. }
  135. while (classToFlush != null);
  136. if (logger.isDebugEnabled()) {
  137. logger.debug("Caching PropertyDescriptors for class [" + clazz.getName() + "]");
  138. }
  139. this.propertyDescriptorCache = new HashMap();
  140. // This call is slow so we do it once.
  141. PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
  142. for (int i = 0; i < pds.length; i++) {
  143. if (logger.isDebugEnabled()) {
  144. logger.debug("Found property '" + pds[i].getName() + "' of type [" + pds[i].getPropertyType() +
  145. "]; editor=[" + pds[i].getPropertyEditorClass() + "]");
  146. }
  147. this.propertyDescriptorCache.put(pds[i].getName(), pds[i]);
  148. }
  149. }
  150. catch (IntrospectionException ex) {
  151. throw new FatalBeanException("Cannot get BeanInfo for object of class [" + clazz.getName() + "]", ex);
  152. }
  153. }
  154. BeanInfo getBeanInfo() {
  155. return this.beanInfo;
  156. }
  157. Class getBeanClass() {
  158. return this.beanInfo.getBeanDescriptor().getBeanClass();
  159. }
  160. PropertyDescriptor getPropertyDescriptor(String propertyName) {
  161. return (PropertyDescriptor) this.propertyDescriptorCache.get(propertyName);
  162. }
  163. }