View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.beanutils2;
19  
20  import java.beans.PropertyDescriptor;
21  import java.lang.ref.Reference;
22  import java.lang.ref.SoftReference;
23  import java.util.HashMap;
24  import java.util.Map;
25  import java.util.Objects;
26  import java.util.WeakHashMap;
27  
28  /**
29   * Implements {@link DynaClass} to wrap standard JavaBean instances.
30   * <p>
31   * This class should not usually need to be used directly to create new {@link WrapDynaBean} instances - it's usually better to call the {@link WrapDynaBean}
32   * constructor. For example:
33   * </p>
34   *
35   * <pre>
36   *   Object javaBean = ...;
37   *   DynaBean wrapper = new WrapDynaBean(javaBean);
38   * </pre>
39   */
40  public class WrapDynaClass implements DynaClass {
41  
42      /**
43       * A class representing the combined key for the cache of {@code WrapDynaClass} instances. A single key consists of a bean class and an instance of
44       * {@code PropertyUtilsBean}. Instances are immutable.
45       */
46      private static final class CacheKey {
47  
48          /** The bean class. */
49          private final Class<?> beanClass;
50  
51          /** The instance of PropertyUtilsBean. */
52          private final PropertyUtilsBean propUtils;
53  
54          /**
55           * Creates a new instance of {@code CacheKey}.
56           *
57           * @param beanCls the bean class
58           * @param pu      the instance of {@code PropertyUtilsBean}
59           */
60          public CacheKey(final Class<?> beanCls, final PropertyUtilsBean pu) {
61              beanClass = beanCls;
62              propUtils = pu;
63          }
64  
65          @Override
66          public boolean equals(final Object obj) {
67              if (this == obj) {
68                  return true;
69              }
70              if (!(obj instanceof CacheKey)) {
71                  return false;
72              }
73  
74              final CacheKey c = (CacheKey) obj;
75              return beanClass.equals(c.beanClass) && propUtils.equals(c.propUtils);
76          }
77  
78          @Override
79          public int hashCode() {
80              final int factor = 31;
81              int result = 17;
82              result = factor * beanClass.hashCode() + result;
83              result = factor * propUtils.hashCode() + result;
84              return result;
85          }
86      }
87  
88      private static final ContextClassLoaderLocal<Map<CacheKey, WrapDynaClass>> CLASSLOADER_CACHE = new ContextClassLoaderLocal<Map<CacheKey, WrapDynaClass>>() {
89          @Override
90          protected Map<CacheKey, WrapDynaClass> initialValue() {
91              return new WeakHashMap<>();
92          }
93      };
94  
95      /**
96       * Clear our cache of WrapDynaClass instances.
97       */
98      public static void clear() {
99          getClassesCache().clear();
100     }
101 
102     /**
103      * Create (if necessary) and return a new {@code WrapDynaClass} instance for the specified bean class.
104      *
105      * @param beanClass Bean class for which a WrapDynaClass is requested
106      * @return A new <em>Wrap</em> {@link DynaClass}
107      */
108     public static WrapDynaClass createDynaClass(final Class<?> beanClass) {
109         return createDynaClass(beanClass, null);
110     }
111 
112     /**
113      * Create (if necessary) and return a new {@code WrapDynaClass} instance for the specified bean class using the given {@code PropertyUtilsBean} instance for
114      * introspection. Using this method a specially configured {@code PropertyUtilsBean} instance can be hooked into the introspection mechanism of the managed
115      * bean. The argument is optional; if no {@code PropertyUtilsBean} object is provided, the default instance is used.
116      *
117      * @param beanClass Bean class for which a WrapDynaClass is requested
118      * @param pu        the optional {@code PropertyUtilsBean} to be used for introspection
119      * @return A new <em>Wrap</em> {@link DynaClass}
120      * @since 1.9
121      */
122     public static WrapDynaClass createDynaClass(final Class<?> beanClass, final PropertyUtilsBean pu) {
123         final PropertyUtilsBean propUtils = pu != null ? pu : PropertyUtilsBean.getInstance();
124         final CacheKey key = new CacheKey(beanClass, propUtils);
125         return getClassesCache().computeIfAbsent(key, k -> new WrapDynaClass(beanClass, propUtils));
126     }
127 
128     /**
129      * Returns the cache for the already created class instances. For each combination of bean class and {@code PropertyUtilsBean} instance an entry is created
130      * in the cache.
131      *
132      * @return the cache for already created {@code WrapDynaClass} instances
133      */
134     private static Map<CacheKey, WrapDynaClass> getClassesCache() {
135         return CLASSLOADER_CACHE.get();
136     }
137 
138     /**
139      * Name of the JavaBean class represented by this WrapDynaClass.
140      */
141     private final String beanClassName;
142 
143     /**
144      * Reference to the JavaBean class represented by this WrapDynaClass.
145      */
146     private final Reference<Class<?>> beanClassRef;
147 
148     /** Stores the associated {@code PropertyUtilsBean} instance. */
149     private final PropertyUtilsBean propertyUtilsBean;
150 
151     /**
152      * The set of PropertyDescriptors for this bean class, keyed by the property name. Individual descriptor instances will be the same instances as those in
153      * the {@code descriptors} list.
154      */
155     protected HashMap<String, PropertyDescriptor> descriptorsMap = new HashMap<>();
156 
157     /**
158      * The set of dynamic properties that are part of this DynaClass.
159      */
160     protected DynaProperty[] properties;
161 
162     /**
163      * The set of dynamic properties that are part of this DynaClass, keyed by the property name. Individual descriptor instances will be the same instances as
164      * those in the {@code properties} list.
165      */
166     protected HashMap<String, DynaProperty> propertiesMap = new HashMap<>();
167 
168     /**
169      * Constructs a new WrapDynaClass for the specified JavaBean class. This constructor is private; WrapDynaClass instances will be created as needed via calls
170      * to the {@code createDynaClass(Class)} method.
171      *
172      * @param beanClass JavaBean class to be introspected around
173      * @param propUtils the {@code PropertyUtilsBean} associated with this class
174      */
175     private WrapDynaClass(final Class<?> beanClass, final PropertyUtilsBean propUtils) {
176         this.beanClassRef = new SoftReference<>(beanClass);
177         this.beanClassName = beanClass.getName();
178         this.propertyUtilsBean = propUtils;
179         introspect();
180     }
181 
182     /**
183      * Gets the class of the underlying wrapped bean.
184      *
185      * @return the class of the underlying wrapped bean
186      * @since 1.8.0
187      */
188     protected Class<?> getBeanClass() {
189         return beanClassRef.get();
190     }
191 
192     /**
193      * <p>
194      * Return an array of {@code PropertyDescriptor} for the properties currently defined in this DynaClass. If no properties are defined, a zero-length array
195      * will be returned.
196      * </p>
197      *
198      * <p>
199      * <strong>FIXME</strong> - Should we really be implementing {@code getBeanInfo()} instead, which returns property descriptors and a bunch of other stuff?
200      * </p>
201      *
202      * @return the set of properties for this DynaClass
203      */
204     @Override
205     public DynaProperty[] getDynaProperties() {
206         return properties.clone();
207     }
208 
209     /**
210      * Gets a property descriptor for the specified property, if it exists; otherwise, return {@code null}.
211      *
212      * @param name Name of the dynamic property for which a descriptor is requested
213      * @return The descriptor for the specified property
214      * @throws IllegalArgumentException if no property name is specified
215      */
216     @Override
217     public DynaProperty getDynaProperty(final String name) {
218         return propertiesMap.get(Objects.requireNonNull(name, "name"));
219     }
220 
221     /**
222      * Gets the name of this DynaClass (analogous to the {@code getName()} method of {@link Class}, which allows the same {@code DynaClass} implementation class
223      * to support different dynamic classes, with different sets of properties.
224      *
225      * @return the name of the DynaClass
226      */
227     @Override
228     public String getName() {
229         return beanClassName;
230     }
231 
232     /**
233      * Gets the PropertyDescriptor for the specified property name, if any; otherwise return {@code null}.
234      *
235      * @param name Name of the property to be retrieved
236      * @return The descriptor for the specified property
237      */
238     public PropertyDescriptor getPropertyDescriptor(final String name) {
239         return descriptorsMap.get(name);
240     }
241 
242     /**
243      * Returns the {@code PropertyUtilsBean} instance associated with this class. This bean is used for introspection.
244      *
245      * @return the associated {@code PropertyUtilsBean} instance
246      * @since 1.9
247      */
248     protected PropertyUtilsBean getPropertyUtilsBean() {
249         return propertyUtilsBean;
250     }
251 
252     /**
253      * Introspect our bean class to identify the supported properties.
254      */
255     protected void introspect() {
256         // Look up the property descriptors for this bean class
257         final Class<?> beanClass = getBeanClass();
258         PropertyDescriptor[] regulars = getPropertyUtilsBean().getPropertyDescriptors(beanClass);
259         if (regulars == null) {
260             regulars = PropertyDescriptors.EMPTY_ARRAY;
261         }
262         Map<?, ?> mappeds = PropertyUtils.getMappedPropertyDescriptors(beanClass);
263         if (mappeds == null) {
264             mappeds = new HashMap<>();
265         }
266 
267         // Construct corresponding DynaProperty information
268         properties = new DynaProperty[regulars.length + mappeds.size()];
269         for (int i = 0; i < regulars.length; i++) {
270             descriptorsMap.put(regulars[i].getName(), regulars[i]);
271             properties[i] = new DynaProperty(regulars[i].getName(), regulars[i].getPropertyType());
272             propertiesMap.put(properties[i].getName(), properties[i]);
273         }
274 
275         int j = regulars.length;
276 
277         for (final Object value : mappeds.values()) {
278             final PropertyDescriptor descriptor = (PropertyDescriptor) value;
279             properties[j] = new DynaProperty(descriptor.getName(), Map.class);
280             propertiesMap.put(properties[j].getName(), properties[j]);
281             j++;
282         }
283     }
284 
285     /**
286      * <p>
287      * Instantiates a new standard JavaBean instance associated with this DynaClass and return it wrapped in a new WrapDynaBean instance. <strong>NOTE</strong>
288      * the JavaBean should have a no argument constructor.
289      * </p>
290      *
291      * <p>
292      * <strong>NOTE</strong> - Most common use cases should not need to use this method. It is usually better to create new {@code WrapDynaBean} instances by
293      * calling its constructor. For example:
294      * </p>
295      *
296      * <pre>{@code
297      *   Object javaBean = ...;
298      *   DynaBean wrapper = new WrapDynaBean(javaBean);
299      * }</pre>
300      * <p>
301      * (This method is needed for some kinds of {@code DynaBean} framework.)
302      * </p>
303      *
304      * @return A new {@code DynaBean} instance
305      * @throws IllegalAccessException if the Class or the appropriate constructor is not accessible
306      * @throws InstantiationException if this Class represents an abstract class, an array class, a primitive type, or void; or if instantiation fails for some
307      *                                other reason
308      */
309     @Override
310     public DynaBean newInstance() throws IllegalAccessException, InstantiationException {
311         return new WrapDynaBean(getBeanClass().newInstance());
312     }
313 }