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