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 018package org.apache.commons.beanutils; 019 020 021import java.beans.PropertyDescriptor; 022import java.lang.ref.Reference; 023import java.lang.ref.SoftReference; 024import java.util.Collection; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.Iterator; 028import java.util.Map; 029import java.util.Set; 030import java.util.WeakHashMap; 031 032 033/** 034 * Implementation of {@link DynaClass} that wrap 035 * standard JavaBean instances. 036 * <p> 037 * This class should not usually need to be used directly 038 * to create new {@link WrapDynaBean} instances - 039 * it's usually better to call the {@link WrapDynaBean} constructor. 040 * For example: 041 * </p> 042 * <pre> 043 * Object javaBean = ...; 044 * DynaBean wrapper = new WrapDynaBean(javaBean); 045 * </pre> 046 * 047 * @version $Id$ 048 */ 049 050public class WrapDynaClass implements DynaClass { 051 052 053 // ----------------------------------------------------------- Constructors 054 055 056 /** 057 * Construct a new WrapDynaClass for the specified JavaBean class. This 058 * constructor is private; WrapDynaClass instances will be created as 059 * needed via calls to the <code>createDynaClass(Class)</code> method. 060 * 061 * @param beanClass JavaBean class to be introspected around 062 * @param propUtils the {@code PropertyUtilsBean} associated with this class 063 */ 064 private WrapDynaClass(final Class<?> beanClass, final PropertyUtilsBean propUtils) { 065 066 this.beanClassRef = new SoftReference<Class<?>>(beanClass); 067 this.beanClassName = beanClass.getName(); 068 propertyUtilsBean = propUtils; 069 introspect(); 070 071 } 072 073 074 // ----------------------------------------------------- Instance Variables 075 076 /** 077 * Name of the JavaBean class represented by this WrapDynaClass. 078 */ 079 private String beanClassName = null; 080 081 /** 082 * Reference to the JavaBean class represented by this WrapDynaClass. 083 */ 084 private Reference<Class<?>> beanClassRef = null; 085 086 /** Stores the associated {@code PropertyUtilsBean} instance. */ 087 private final PropertyUtilsBean propertyUtilsBean; 088 089 /** 090 * The JavaBean <code>Class</code> which is represented by this 091 * <code>WrapDynaClass</code>. 092 * 093 * @deprecated No longer initialized, use getBeanClass() method instead 094 */ 095 @Deprecated 096 protected Class<?> beanClass = null; 097 098 099 /** 100 * The set of PropertyDescriptors for this bean class. 101 */ 102 protected PropertyDescriptor[] descriptors = null; 103 104 105 /** 106 * The set of PropertyDescriptors for this bean class, keyed by the 107 * property name. Individual descriptor instances will be the same 108 * instances as those in the <code>descriptors</code> list. 109 */ 110 protected HashMap<String, PropertyDescriptor> descriptorsMap = new HashMap<String, PropertyDescriptor>(); 111 112 113 /** 114 * The set of dynamic properties that are part of this DynaClass. 115 */ 116 protected DynaProperty[] properties = null; 117 118 119 /** 120 * The set of dynamic properties that are part of this DynaClass, 121 * keyed by the property name. Individual descriptor instances will 122 * be the same instances as those in the <code>properties</code> list. 123 */ 124 protected HashMap<String, DynaProperty> propertiesMap = new HashMap<String, DynaProperty>(); 125 126 127 // ------------------------------------------------------- Static Variables 128 129 130 private static final ContextClassLoaderLocal<Map<CacheKey, WrapDynaClass>> CLASSLOADER_CACHE = 131 new ContextClassLoaderLocal<Map<CacheKey, WrapDynaClass>>() { 132 @Override 133 protected Map<CacheKey, WrapDynaClass> initialValue() { 134 return new WeakHashMap<CacheKey, WrapDynaClass>(); 135 } 136 }; 137 138 /** 139 * Get the wrap dyna classes cache. Note: This method only exists to 140 * satisfy the deprecated {@code dynaClasses} hash map. 141 */ 142 @SuppressWarnings("unchecked") 143 private static Map<Object, Object> getDynaClassesMap() { 144 @SuppressWarnings("rawtypes") 145 final 146 Map cache = CLASSLOADER_CACHE.get(); 147 return cache; 148 } 149 150 /** 151 * Returns the cache for the already created class instances. For each 152 * combination of bean class and {@code PropertyUtilsBean} instance an 153 * entry is created in the cache. 154 * @return the cache for already created {@code WrapDynaClass} instances 155 */ 156 private static Map<CacheKey, WrapDynaClass> getClassesCache() { 157 return CLASSLOADER_CACHE.get(); 158 } 159 160 /** 161 * The set of <code>WrapDynaClass</code> instances that have ever been 162 * created, keyed by the underlying bean Class. The keys to this map 163 * are Class objects, and the values are corresponding WrapDynaClass 164 * objects. 165 * <p> 166 * This static variable is safe even when this code is deployed via a 167 * shared classloader because it is keyed via a Class object. The same 168 * class loaded via two different classloaders will result in different 169 * entries in this map. 170 * <p> 171 * Note, however, that this HashMap can result in a memory leak. When 172 * this class is in a shared classloader it will retain references to 173 * classes loaded via a webapp classloader even after the webapp has been 174 * undeployed. That will prevent the entire classloader and all the classes 175 * it refers to and all their static members from being freed. 176 * 177 ************* !!!!!!!!!!!! PLEASE NOTE !!!!!!!!!!!! ************* 178 * 179 * THE FOLLOWING IS A NASTY HACK TO SO THAT BEANUTILS REMAINS BINARY 180 * COMPATIBLE WITH PREVIOUS RELEASES. 181 * 182 * There are two issues here: 183 * 184 * 1) Memory Issues: The static HashMap caused memory problems (See BEANUTILS-59) 185 * to resolve this it has been moved into a ContextClassLoaderLocal instance 186 * (named CLASSLOADER_CACHE above) which holds one copy per 187 * ClassLoader in a WeakHashMap. 188 * 189 * 2) Binary Compatibility: As the "dynaClasses" static HashMap is "protected" 190 * removing it breaks BeanUtils binary compatibility with previous versions. 191 * To resolve this all the methods have been overriden to delegate to the 192 * Map for the ClassLoader in the ContextClassLoaderLocal. 193 * 194 * @deprecated The dynaClasses Map will be removed in a subsequent release 195 */ 196 @Deprecated 197 protected static HashMap<Object, Object> dynaClasses = new HashMap<Object, Object>() { 198 @Override 199 public void clear() { 200 getDynaClassesMap().clear(); 201 } 202 @Override 203 public boolean containsKey(final Object key) { 204 return getDynaClassesMap().containsKey(key); 205 } 206 @Override 207 public boolean containsValue(final Object value) { 208 return getDynaClassesMap().containsValue(value); 209 } 210 @Override 211 public Set<Map.Entry<Object, Object>> entrySet() { 212 return getDynaClassesMap().entrySet(); 213 } 214 @Override 215 public boolean equals(final Object o) { 216 return getDynaClassesMap().equals(o); 217 } 218 @Override 219 public Object get(final Object key) { 220 return getDynaClassesMap().get(key); 221 } 222 @Override 223 public int hashCode() { 224 return getDynaClassesMap().hashCode(); 225 } 226 @Override 227 public boolean isEmpty() { 228 return getDynaClassesMap().isEmpty(); 229 } 230 @Override 231 public Set<Object> keySet() { 232 // extract the classes from the key to stay backwards compatible 233 final Set<Object> result = new HashSet<Object>(); 234 for (final CacheKey k : getClassesCache().keySet()) { 235 result.add(k.beanClass); 236 } 237 return result; 238 } 239 @Override 240 public Object put(final Object key, final Object value) { 241 return getClassesCache().put( 242 new CacheKey((Class<?>) key, PropertyUtilsBean.getInstance()), 243 (WrapDynaClass) value); 244 } 245 @Override 246 public void putAll(final Map<? extends Object, ? extends Object> m) { 247 for (final Map.Entry<? extends Object, ? extends Object> e : m.entrySet()) { 248 put(e.getKey(), e.getValue()); 249 } 250 } 251 @Override 252 public Object remove(final Object key) { 253 return getDynaClassesMap().remove(key); 254 } 255 @Override 256 public int size() { 257 return getDynaClassesMap().size(); 258 } 259 @Override 260 public Collection<Object> values() { 261 return getDynaClassesMap().values(); 262 } 263 }; 264 265 266 // ------------------------------------------------------ DynaClass Methods 267 268 /** 269 * Return the class of the underlying wrapped bean. 270 * 271 * @return the class of the underlying wrapped bean 272 * @since 1.8.0 273 */ 274 protected Class<?> getBeanClass() { 275 return beanClassRef.get(); 276 } 277 278 /** 279 * Return the name of this DynaClass (analogous to the 280 * {@code getName()} method of {@code java.lang.Class}, which 281 * allows the same {@code DynaClass} implementation class to support 282 * different dynamic classes, with different sets of properties. 283 * 284 * @return the name of the DynaClass 285 */ 286 public String getName() { 287 288 return beanClassName; 289 290 } 291 292 293 /** 294 * Return a property descriptor for the specified property, if it exists; 295 * otherwise, return <code>null</code>. 296 * 297 * @param name Name of the dynamic property for which a descriptor 298 * is requested 299 * @return The descriptor for the specified property 300 * 301 * @throws IllegalArgumentException if no property name is specified 302 */ 303 public DynaProperty getDynaProperty(final String name) { 304 305 if (name == null) { 306 throw new IllegalArgumentException 307 ("No property name specified"); 308 } 309 return (propertiesMap.get(name)); 310 311 } 312 313 314 /** 315 * <p>Return an array of <code>ProperyDescriptors</code> for the properties 316 * currently defined in this DynaClass. If no properties are defined, a 317 * zero-length array will be returned.</p> 318 * 319 * <p><strong>FIXME</strong> - Should we really be implementing 320 * <code>getBeanInfo()</code> instead, which returns property descriptors 321 * and a bunch of other stuff?</p> 322 * 323 * @return the set of properties for this DynaClass 324 */ 325 public DynaProperty[] getDynaProperties() { 326 327 return (properties); 328 329 } 330 331 332 /** 333 * <p>Instantiates a new standard JavaBean instance associated with 334 * this DynaClass and return it wrapped in a new WrapDynaBean 335 * instance. <strong>NOTE</strong> the JavaBean should have a 336 * no argument constructor.</p> 337 * 338 * <p><strong>NOTE</strong> - Most common use cases should not need to use 339 * this method. It is usually better to create new 340 * <code>WrapDynaBean</code> instances by calling its constructor. 341 * For example:</p> 342 * <pre><code> 343 * Object javaBean = ...; 344 * DynaBean wrapper = new WrapDynaBean(javaBean); 345 * </code></pre> 346 * <p> 347 * (This method is needed for some kinds of <code>DynaBean</code> framework.) 348 * </p> 349 * 350 * @return A new <code>DynaBean</code> instance 351 * @throws IllegalAccessException if the Class or the appropriate 352 * constructor is not accessible 353 * @throws InstantiationException if this Class represents an abstract 354 * class, an array class, a primitive type, or void; or if instantiation 355 * fails for some other reason 356 */ 357 public DynaBean newInstance() 358 throws IllegalAccessException, InstantiationException { 359 360 return new WrapDynaBean(getBeanClass().newInstance()); 361 362 } 363 364 365 // --------------------------------------------------------- Public Methods 366 367 368 /** 369 * Return the PropertyDescriptor for the specified property name, if any; 370 * otherwise return <code>null</code>. 371 * 372 * @param name Name of the property to be retrieved 373 * @return The descriptor for the specified property 374 */ 375 public PropertyDescriptor getPropertyDescriptor(final String name) { 376 377 return (descriptorsMap.get(name)); 378 379 } 380 381 382 // --------------------------------------------------------- Static Methods 383 384 385 /** 386 * Clear our cache of WrapDynaClass instances. 387 */ 388 public static void clear() { 389 390 getClassesCache().clear(); 391 392 } 393 394 395 /** 396 * Create (if necessary) and return a new <code>WrapDynaClass</code> 397 * instance for the specified bean class. 398 * 399 * @param beanClass Bean class for which a WrapDynaClass is requested 400 * @return A new <i>Wrap</i> {@link DynaClass} 401 */ 402 public static WrapDynaClass createDynaClass(final Class<?> beanClass) { 403 404 return createDynaClass(beanClass, null); 405 406 } 407 408 409 /** 410 * Create (if necessary) and return a new {@code WrapDynaClass} instance 411 * for the specified bean class using the given {@code PropertyUtilsBean} 412 * instance for introspection. Using this method a specially configured 413 * {@code PropertyUtilsBean} instance can be hooked into the introspection 414 * mechanism of the managed bean. The argument is optional; if no 415 * {@code PropertyUtilsBean} object is provided, the default instance is used. 416 * @param beanClass Bean class for which a WrapDynaClass is requested 417 * @param pu the optional {@code PropertyUtilsBean} to be used for introspection 418 * @return A new <i>Wrap</i> {@link DynaClass} 419 * @since 1.9 420 */ 421 public static WrapDynaClass createDynaClass(final Class<?> beanClass, final PropertyUtilsBean pu) { 422 423 final PropertyUtilsBean propUtils = (pu != null) ? pu : PropertyUtilsBean.getInstance(); 424 final CacheKey key = new CacheKey(beanClass, propUtils); 425 WrapDynaClass dynaClass = getClassesCache().get(key); 426 if (dynaClass == null) { 427 dynaClass = new WrapDynaClass(beanClass, propUtils); 428 getClassesCache().put(key, dynaClass); 429 } 430 return (dynaClass); 431 432 } 433 434 435 // ------------------------------------------------------ Protected Methods 436 437 /** 438 * Returns the {@code PropertyUtilsBean} instance associated with this class. This 439 * bean is used for introspection. 440 * 441 * @return the associated {@code PropertyUtilsBean} instance 442 * @since 1.9 443 */ 444 protected PropertyUtilsBean getPropertyUtilsBean() { 445 return propertyUtilsBean; 446 } 447 448 /** 449 * Introspect our bean class to identify the supported properties. 450 */ 451 protected void introspect() { 452 453 // Look up the property descriptors for this bean class 454 final Class<?> beanClass = getBeanClass(); 455 PropertyDescriptor[] regulars = 456 getPropertyUtilsBean().getPropertyDescriptors(beanClass); 457 if (regulars == null) { 458 regulars = new PropertyDescriptor[0]; 459 } 460 @SuppressWarnings("deprecation") 461 Map<?, ?> mappeds = 462 PropertyUtils.getMappedPropertyDescriptors(beanClass); 463 if (mappeds == null) { 464 mappeds = new HashMap<Object, Object>(); 465 } 466 467 // Construct corresponding DynaProperty information 468 properties = new DynaProperty[regulars.length + mappeds.size()]; 469 for (int i = 0; i < regulars.length; i++) { 470 descriptorsMap.put(regulars[i].getName(), 471 regulars[i]); 472 properties[i] = 473 new DynaProperty(regulars[i].getName(), 474 regulars[i].getPropertyType()); 475 propertiesMap.put(properties[i].getName(), 476 properties[i]); 477 } 478 int j = regulars.length; 479 final Iterator<?> names = mappeds.keySet().iterator(); 480 while (names.hasNext()) { 481 final String name = (String) names.next(); 482 final PropertyDescriptor descriptor = 483 (PropertyDescriptor) mappeds.get(name); 484 properties[j] = 485 new DynaProperty(descriptor.getName(), 486 Map.class); 487 propertiesMap.put(properties[j].getName(), 488 properties[j]); 489 j++; 490 } 491 492 } 493 494 /** 495 * A class representing the combined key for the cache of {@code WrapDynaClass} 496 * instances. A single key consists of a bean class and an instance of 497 * {@code PropertyUtilsBean}. Instances are immutable. 498 */ 499 private static class CacheKey { 500 /** The bean class. */ 501 private final Class<?> beanClass; 502 503 /** The instance of PropertyUtilsBean. */ 504 private final PropertyUtilsBean propUtils; 505 506 /** 507 * Creates a new instance of {@code CacheKey}. 508 * 509 * @param beanCls the bean class 510 * @param pu the instance of {@code PropertyUtilsBean} 511 */ 512 public CacheKey(final Class<?> beanCls, final PropertyUtilsBean pu) { 513 beanClass = beanCls; 514 propUtils = pu; 515 } 516 517 @Override 518 public int hashCode() { 519 final int factor = 31; 520 int result = 17; 521 result = factor * beanClass.hashCode() + result; 522 result = factor * propUtils.hashCode() + result; 523 return result; 524 } 525 526 @Override 527 public boolean equals(final Object obj) { 528 if (this == obj) { 529 return true; 530 } 531 if (!(obj instanceof CacheKey)) { 532 return false; 533 } 534 535 final CacheKey c = (CacheKey) obj; 536 return beanClass.equals(c.beanClass) && propUtils.equals(c.propUtils); 537 } 538 } 539}