1
+ /*
2
+ * Copyright 2002-2011 the original author or authors.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package org .springframework .beans ;
18
+
19
+ import java .awt .Image ;
20
+ import java .beans .BeanDescriptor ;
21
+ import java .beans .BeanInfo ;
22
+ import java .beans .EventSetDescriptor ;
23
+ import java .beans .IndexedPropertyDescriptor ;
24
+ import java .beans .IntrospectionException ;
25
+ import java .beans .Introspector ;
26
+ import java .beans .MethodDescriptor ;
27
+ import java .beans .PropertyDescriptor ;
28
+ import java .lang .reflect .Method ;
29
+ import java .util .Comparator ;
30
+ import java .util .SortedSet ;
31
+ import java .util .TreeSet ;
32
+
33
+ import org .springframework .util .ReflectionUtils ;
34
+ import org .springframework .util .StringUtils ;
35
+
36
+ /**
37
+ * Decorates a standard {@link BeanInfo} object (likely created created by
38
+ * {@link Introspector#getBeanInfo(Class)}) by including non-void returning setter
39
+ * methods in the collection of {@link #getPropertyDescriptors() property descriptors}.
40
+ * Both regular and
41
+ * <a href="http://download.oracle.com/javase/tutorial/javabeans/properties/indexed.html">
42
+ * indexed properties</a> are fully supported.
43
+ *
44
+ * <p>The wrapped {@code BeanInfo} object is not modified in any way.
45
+ *
46
+ * @author Chris Beams
47
+ * @since 3.1
48
+ * @see CachedIntrospectionResults
49
+ */
50
+ public class ExtendedBeanInfo implements BeanInfo {
51
+ private final BeanInfo delegate ;
52
+ private final SortedSet <PropertyDescriptor > propertyDescriptors =
53
+ new TreeSet <PropertyDescriptor >(new PropertyDescriptorComparator ());
54
+
55
+ /**
56
+ * Wrap the given delegate {@link BeanInfo} instance and find any non-void returning
57
+ * setter methods, creating and adding a {@link PropertyDescriptor} for each.
58
+ *
59
+ * <p>The wrapped {@code BeanInfo} is not modified in any way by this process.
60
+ *
61
+ * @see #getPropertyDescriptors()
62
+ * @throws IntrospectionException if any problems occur creating and adding new {@code PropertyDescriptors}
63
+ */
64
+ public ExtendedBeanInfo (BeanInfo delegate ) throws IntrospectionException {
65
+ this .delegate = delegate ;
66
+
67
+ // PropertyDescriptor instances from the delegate object are never added directly, but always
68
+ // copied to the local collection of #propertyDescriptors and returned by calls to
69
+ // #getPropertyDescriptors(). this algorithm iterates through all methods (method descriptors)
70
+ // in the wrapped BeanInfo object, copying any existing PropertyDescriptor or creating a new
71
+ // one for any non-standard setter methods found.
72
+
73
+ ALL_METHODS :
74
+ for (MethodDescriptor md : delegate .getMethodDescriptors ()) {
75
+ Method method = md .getMethod ();
76
+
77
+ // bypass non-getter java.lang.Class methods for efficiency
78
+ if (ReflectionUtils .isObjectMethod (method ) && !method .getName ().startsWith ("get" )) {
79
+ continue ALL_METHODS ;
80
+ }
81
+
82
+ // is the method a NON-INDEXED setter? ignore return type in order to capture non-void signatures
83
+ if (method .getName ().startsWith ("set" ) && method .getParameterTypes ().length == 1 ) {
84
+ String propertyName = propertyNameFor (method );
85
+ for (PropertyDescriptor pd : delegate .getPropertyDescriptors ()) {
86
+ Method readMethod = pd .getReadMethod ();
87
+ Method writeMethod = pd .getWriteMethod ();
88
+ // has the setter already been found by the wrapped BeanInfo?
89
+ if (writeMethod != null
90
+ && writeMethod .getName ().equals (method .getName ())) {
91
+ // yes -> copy it, including corresponding getter method (if any -- may be null)
92
+ this .addOrUpdatePropertyDescriptor (propertyName , readMethod , writeMethod );
93
+ continue ALL_METHODS ;
94
+ }
95
+ // has a getter corresponding to this setter already been found by the wrapped BeanInfo?
96
+ if (readMethod != null
97
+ && readMethod .getName ().equals (getterMethodNameFor (propertyName ))
98
+ && readMethod .getReturnType ().equals (method .getParameterTypes ()[0 ])) {
99
+ this .addOrUpdatePropertyDescriptor (propertyName , readMethod , method );
100
+ continue ALL_METHODS ;
101
+ }
102
+ }
103
+ // the setter method was not found by the wrapped BeanInfo -> add a new PropertyDescriptor for it
104
+ // no corresponding getter was detected, so the 'read method' parameter is null.
105
+ this .addOrUpdatePropertyDescriptor (propertyName , null , method );
106
+ continue ALL_METHODS ;
107
+ }
108
+
109
+ // is the method an INDEXED setter? ignore return type in order to capture non-void signatures
110
+ if (method .getName ().startsWith ("set" ) && method .getParameterTypes ().length == 2 && method .getParameterTypes ()[0 ].equals (int .class )) {
111
+ String propertyName = propertyNameFor (method );
112
+ DELEGATE_PD :
113
+ for (PropertyDescriptor pd : delegate .getPropertyDescriptors ()) {
114
+ if (!(pd instanceof IndexedPropertyDescriptor )) {
115
+ continue DELEGATE_PD ;
116
+ }
117
+ IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor ) pd ;
118
+ Method readMethod = ipd .getReadMethod ();
119
+ Method writeMethod = ipd .getWriteMethod ();
120
+ Method indexedReadMethod = ipd .getIndexedReadMethod ();
121
+ Method indexedWriteMethod = ipd .getIndexedWriteMethod ();
122
+ // has the setter already been found by the wrapped BeanInfo?
123
+ if (indexedWriteMethod != null
124
+ && indexedWriteMethod .getName ().equals (method .getName ())) {
125
+ // yes -> copy it, including corresponding getter method (if any -- may be null)
126
+ this .addOrUpdatePropertyDescriptor (propertyName , readMethod , writeMethod , indexedReadMethod , indexedWriteMethod );
127
+ continue ALL_METHODS ;
128
+ }
129
+ // has a getter corresponding to this setter already been found by the wrapped BeanInfo?
130
+ if (indexedReadMethod != null
131
+ && indexedReadMethod .getName ().equals (getterMethodNameFor (propertyName ))
132
+ && indexedReadMethod .getReturnType ().equals (method .getParameterTypes ()[1 ])) {
133
+ this .addOrUpdatePropertyDescriptor (propertyName , readMethod , writeMethod , indexedReadMethod , method );
134
+ continue ALL_METHODS ;
135
+ }
136
+ }
137
+ // the INDEXED setter method was not found by the wrapped BeanInfo -> add a new PropertyDescriptor
138
+ // for it. no corresponding INDEXED getter was detected, so the 'indexed read method' parameter is null.
139
+ this .addOrUpdatePropertyDescriptor (propertyName , null , null , null , method );
140
+ continue ALL_METHODS ;
141
+ }
142
+
143
+ // the method is not a setter, but is it a getter?
144
+ for (PropertyDescriptor pd : delegate .getPropertyDescriptors ()) {
145
+ // have we already copied this read method to a property descriptor locally?
146
+ for (PropertyDescriptor existingPD : this .propertyDescriptors ) {
147
+ if (method .equals (pd .getReadMethod ())
148
+ && existingPD .getName ().equals (pd .getName ())) {
149
+ if (existingPD .getReadMethod () == null ) {
150
+ // no -> add it now
151
+ this .addOrUpdatePropertyDescriptor (pd .getName (), method , pd .getWriteMethod ());
152
+ }
153
+ // yes -> do not add a duplicate
154
+ continue ALL_METHODS ;
155
+ }
156
+ }
157
+ if (method == pd .getReadMethod ()
158
+ || (pd instanceof IndexedPropertyDescriptor && method == ((IndexedPropertyDescriptor ) pd ).getIndexedReadMethod ())) {
159
+ // yes -> copy it, including corresponding setter method (if any -- may be null)
160
+ if (pd instanceof IndexedPropertyDescriptor ) {
161
+ this .addOrUpdatePropertyDescriptor (pd .getName (), pd .getReadMethod (), pd .getWriteMethod (), ((IndexedPropertyDescriptor )pd ).getIndexedReadMethod (), ((IndexedPropertyDescriptor )pd ).getIndexedWriteMethod ());
162
+ } else {
163
+ this .addOrUpdatePropertyDescriptor (pd .getName (), pd .getReadMethod (), pd .getWriteMethod ());
164
+ }
165
+ continue ALL_METHODS ;
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ private void addOrUpdatePropertyDescriptor (String propertyName , Method readMethod , Method writeMethod ) throws IntrospectionException {
172
+ addOrUpdatePropertyDescriptor (propertyName , readMethod , writeMethod , null , null );
173
+ }
174
+
175
+ private void addOrUpdatePropertyDescriptor (String propertyName , Method readMethod , Method writeMethod , Method indexedReadMethod , Method indexedWriteMethod ) throws IntrospectionException {
176
+ for (PropertyDescriptor existingPD : this .propertyDescriptors ) {
177
+ if (existingPD .getName ().equals (propertyName )) {
178
+ // is there already a descriptor that captures this read method or its corresponding write method?
179
+ if (existingPD .getReadMethod () != null ) {
180
+ if (readMethod != null && existingPD .getReadMethod ().getReturnType () != readMethod .getReturnType ()
181
+ || writeMethod != null && existingPD .getReadMethod ().getReturnType () != writeMethod .getParameterTypes ()[0 ]) {
182
+ // no -> add a new descriptor for it below
183
+ break ;
184
+ }
185
+ }
186
+ // update the existing descriptor's read method
187
+ if (readMethod != null ) {
188
+ try {
189
+ existingPD .setReadMethod (readMethod );
190
+ } catch (IntrospectionException ex ) {
191
+ // there is a conflicting setter method present -> null it out and try again
192
+ existingPD .setWriteMethod (null );
193
+ existingPD .setReadMethod (readMethod );
194
+ }
195
+ }
196
+
197
+ // is there already a descriptor that captures this write method or its corresponding read method?
198
+ if (existingPD .getWriteMethod () != null ) {
199
+ if (readMethod != null && existingPD .getWriteMethod ().getParameterTypes ()[0 ] != readMethod .getReturnType ()
200
+ || writeMethod != null && existingPD .getWriteMethod ().getParameterTypes ()[0 ] != writeMethod .getParameterTypes ()[0 ]) {
201
+ // no -> add a new descriptor for it below
202
+ break ;
203
+ }
204
+ }
205
+ // update the existing descriptor's write method
206
+ if (writeMethod != null ) {
207
+ existingPD .setWriteMethod (writeMethod );
208
+ }
209
+
210
+ // is this descriptor indexed?
211
+ if (existingPD instanceof IndexedPropertyDescriptor ) {
212
+ IndexedPropertyDescriptor existingIPD = (IndexedPropertyDescriptor ) existingPD ;
213
+
214
+ // is there already a descriptor that captures this indexed read method or its corresponding indexed write method?
215
+ if (existingIPD .getIndexedReadMethod () != null ) {
216
+ if (indexedReadMethod != null && existingIPD .getIndexedReadMethod ().getReturnType () != indexedReadMethod .getReturnType ()
217
+ || indexedWriteMethod != null && existingIPD .getIndexedReadMethod ().getReturnType () != indexedWriteMethod .getParameterTypes ()[1 ]) {
218
+ // no -> add a new descriptor for it below
219
+ break ;
220
+ }
221
+ }
222
+ // update the existing descriptor's indexed read method
223
+ try {
224
+ existingIPD .setIndexedReadMethod (indexedReadMethod );
225
+ } catch (IntrospectionException ex ) {
226
+ // there is a conflicting indexed setter method present -> null it out and try again
227
+ existingIPD .setIndexedWriteMethod (null );
228
+ existingIPD .setIndexedReadMethod (indexedReadMethod );
229
+ }
230
+
231
+ // is there already a descriptor that captures this indexed write method or its corresponding indexed read method?
232
+ if (existingIPD .getIndexedWriteMethod () != null ) {
233
+ if (indexedReadMethod != null && existingIPD .getIndexedWriteMethod ().getParameterTypes ()[1 ] != indexedReadMethod .getReturnType ()
234
+ || indexedWriteMethod != null && existingIPD .getIndexedWriteMethod ().getParameterTypes ()[1 ] != indexedWriteMethod .getParameterTypes ()[1 ]) {
235
+ // no -> add a new descriptor for it below
236
+ break ;
237
+ }
238
+ }
239
+ // update the existing descriptor's indexed write method
240
+ if (indexedWriteMethod != null ) {
241
+ existingIPD .setIndexedWriteMethod (indexedWriteMethod );
242
+ }
243
+ }
244
+
245
+ // the descriptor has been updated -> return immediately
246
+ return ;
247
+ }
248
+ }
249
+
250
+ // we haven't yet seen read or write methods for this property -> add a new descriptor
251
+ if (indexedReadMethod == null && indexedWriteMethod == null ) {
252
+ this .propertyDescriptors .add (new PropertyDescriptor (propertyName , readMethod , writeMethod ));
253
+ } else {
254
+ this .propertyDescriptors .add (new IndexedPropertyDescriptor (propertyName , readMethod , writeMethod , indexedReadMethod , indexedWriteMethod ));
255
+ }
256
+ }
257
+
258
+ private String propertyNameFor (Method method ) {
259
+ return Introspector .decapitalize (method .getName ().substring (3 ,method .getName ().length ()));
260
+ }
261
+
262
+ private Object getterMethodNameFor (String name ) {
263
+ return "get" + StringUtils .capitalize (name );
264
+ }
265
+
266
+ public BeanInfo [] getAdditionalBeanInfo () {
267
+ return delegate .getAdditionalBeanInfo ();
268
+ }
269
+
270
+ public BeanDescriptor getBeanDescriptor () {
271
+ return delegate .getBeanDescriptor ();
272
+ }
273
+
274
+ public int getDefaultEventIndex () {
275
+ return delegate .getDefaultEventIndex ();
276
+ }
277
+
278
+ public int getDefaultPropertyIndex () {
279
+ return delegate .getDefaultPropertyIndex ();
280
+ }
281
+
282
+ public EventSetDescriptor [] getEventSetDescriptors () {
283
+ return delegate .getEventSetDescriptors ();
284
+ }
285
+
286
+ public Image getIcon (int arg0 ) {
287
+ return delegate .getIcon (arg0 );
288
+ }
289
+
290
+ public MethodDescriptor [] getMethodDescriptors () {
291
+ return delegate .getMethodDescriptors ();
292
+ }
293
+
294
+ /**
295
+ * Return the set of {@link PropertyDescriptor}s from the wrapped {@link BeanInfo}
296
+ * object as well as {@code PropertyDescriptor}s for each non-void returning setter
297
+ * method found during construction.
298
+ * @see #ExtendedBeanInfo(BeanInfo)
299
+ */
300
+ public PropertyDescriptor [] getPropertyDescriptors () {
301
+ return this .propertyDescriptors .toArray (new PropertyDescriptor [this .propertyDescriptors .size ()]);
302
+ }
303
+
304
+
305
+ /**
306
+ * Sorts PropertyDescriptor instances alphanumerically to emulate the behavior of {@link java.beans.BeanInfo#getPropertyDescriptors()}.
307
+ *
308
+ * @see ExtendedBeanInfo#propertyDescriptors
309
+ */
310
+ static class PropertyDescriptorComparator implements Comparator <PropertyDescriptor > {
311
+ public int compare (PropertyDescriptor desc1 , PropertyDescriptor desc2 ) {
312
+ String left = desc1 .getName ();
313
+ String right = desc2 .getName ();
314
+ for (int i = 0 ; i < left .length (); i ++) {
315
+ if (right .length () == i ) {
316
+ return 1 ;
317
+ }
318
+ int result = left .getBytes ()[i ] - right .getBytes ()[i ];
319
+ if (result != 0 ) {
320
+ return result ;
321
+ }
322
+ }
323
+ return left .length () - right .length ();
324
+ }
325
+ }
326
+ }
0 commit comments