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 * https://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 package org.apache.commons.beanutils2;
18
19 import java.beans.IntrospectionException;
20 import java.beans.PropertyDescriptor;
21 import java.lang.reflect.Method;
22 import java.util.HashMap;
23 import java.util.Map;
24
25 /**
26 * <p>
27 * An internally used helper class for storing introspection information about a bean class.
28 * </p>
29 * <p>
30 * This class is used by {@link PropertyUtilsBean}. When accessing bean properties via reflection information about the properties available and their types and
31 * access methods must be present. {@code PropertyUtilsBean} stores this information in a cache so that it can be accessed quickly. The cache stores instances
32 * of this class.
33 * </p>
34 * <p>
35 * This class mainly stores information about the properties of a bean class. Per default, this is contained in {@code PropertyDescriptor} objects. Some
36 * additional information required by the {@code BeanUtils} library is also stored here.
37 * </p>
38 *
39 * @since 1.9.1
40 */
41 final class BeanIntrospectionData {
42 /**
43 * Initializes the map with the names of the write methods for the supported properties. The method names - if defined - need to be stored separately
44 * because they may get lost when the GC claims soft references used by the {@code PropertyDescriptor} objects.
45 *
46 * @param descs the array with the descriptors of the available properties
47 * @return the map with the names of write methods for properties
48 */
49 private static Map<String, String> setUpWriteMethodNames(final PropertyDescriptor[] descs) {
50 final Map<String, String> methods = new HashMap<>();
51 for (final PropertyDescriptor pd : descs) {
52 final Method method = pd.getWriteMethod();
53 if (method != null) {
54 methods.put(pd.getName(), method.getName());
55 }
56 }
57 return methods;
58 }
59
60 /** An array with property descriptors for the managed bean class. */
61 private final PropertyDescriptor[] descriptors;
62
63 /** A map for remembering the write method names for properties. */
64 private final Map<String, String> writeMethodNames;
65
66 /**
67 * Creates a new instance of {@code BeanIntrospectionData} and initializes its completely.
68 *
69 * @param descs the array with the descriptors of the available properties
70 */
71 public BeanIntrospectionData(final PropertyDescriptor[] descs) {
72 this(descs, setUpWriteMethodNames(descs));
73 }
74
75 /**
76 * Creates a new instance of {@code BeanIntrospectionData} and allows setting the map with write method names. This constructor is mainly used for testing
77 * purposes.
78 *
79 * @param descs the array with the descriptors of the available properties
80 * @param writeMethNames the map with the names of write methods
81 */
82 BeanIntrospectionData(final PropertyDescriptor[] descs, final Map<String, String> writeMethNames) {
83 descriptors = descs;
84 writeMethodNames = writeMethNames;
85 }
86
87 /**
88 * Returns the {@code PropertyDescriptor} for the property with the specified name. If this property is unknown, result is <strong>null</strong>.
89 *
90 * @param name the name of the property in question
91 * @return the {@code PropertyDescriptor} for this property or <strong>null</strong>
92 */
93 public PropertyDescriptor getDescriptor(final String name) {
94 for (final PropertyDescriptor pd : getDescriptors()) {
95 if (name.equals(pd.getName())) {
96 return pd;
97 }
98 }
99 return null;
100 }
101
102 /**
103 * Returns the array with property descriptors.
104 *
105 * @return the property descriptors for the associated bean class
106 */
107 public PropertyDescriptor[] getDescriptors() {
108 return descriptors;
109 }
110
111 /**
112 * Returns the write method for the property determined by the given {@code PropertyDescriptor}. This information is normally available in the descriptor
113 * object itself. However, at least by the ORACLE implementation, the method is stored as a {@code SoftReference}. If this reference has been freed by the
114 * GC, it may be the case that the method cannot be obtained again. Then, additional information stored in this object is necessary to obtain the method
115 * again.
116 *
117 * @param beanCls the class of the affected bean
118 * @param desc the {@code PropertyDescriptor} of the desired property
119 * @return the write method for this property or <strong>null</strong> if there is none
120 */
121 public Method getWriteMethod(final Class<?> beanCls, final PropertyDescriptor desc) {
122 Method method = desc.getWriteMethod();
123 if (method == null) {
124 final String methodName = writeMethodNames.get(desc.getName());
125 if (methodName != null) {
126 method = MethodUtils.getAccessibleMethod(beanCls, methodName, desc.getPropertyType());
127 if (method != null) {
128 try {
129 desc.setWriteMethod(method);
130 } catch (final IntrospectionException e) {
131 // ignore, in this case the method is not cached
132 }
133 }
134 }
135 }
136
137 return method;
138 }
139 }