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  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 }