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 }