1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.beanutils2;
19
20 import java.beans.IntrospectionException;
21 import java.beans.PropertyDescriptor;
22 import java.lang.ref.Reference;
23 import java.lang.ref.SoftReference;
24 import java.lang.ref.WeakReference;
25 import java.lang.reflect.Method;
26 import java.lang.reflect.Modifier;
27 import java.util.Objects;
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 public class MappedPropertyDescriptor extends PropertyDescriptor {
43
44
45
46
47
48
49
50 private static final class MappedMethodReference {
51 private String className;
52 private String methodName;
53 private Reference<Method> methodRef;
54 private Reference<Class<?>> classRef;
55 private Reference<Class<?>> writeParamTypeRef0;
56 private Reference<Class<?>> writeParamTypeRef1;
57 private String[] writeParamClassNames;
58
59 MappedMethodReference(final Method m) {
60 if (m != null) {
61 className = m.getDeclaringClass().getName();
62 methodName = m.getName();
63 methodRef = new SoftReference<>(m);
64 classRef = new WeakReference<>(m.getDeclaringClass());
65 final Class<?>[] types = m.getParameterTypes();
66 if (types.length == 2) {
67 writeParamTypeRef0 = new WeakReference<>(types[0]);
68 writeParamTypeRef1 = new WeakReference<>(types[1]);
69 writeParamClassNames = new String[2];
70 writeParamClassNames[0] = types[0].getName();
71 writeParamClassNames[1] = types[1].getName();
72 }
73 }
74 }
75
76 private Method get() {
77 if (methodRef == null) {
78 return null;
79 }
80 Method m = methodRef.get();
81 if (m == null) {
82 Class<?> clazz = classRef.get();
83 if (clazz == null) {
84 clazz = reLoadClass();
85 if (clazz != null) {
86 classRef = new WeakReference<>(clazz);
87 }
88 }
89 Objects.requireNonNull(clazz, () -> "Method " + methodName + " for " + className + " could not be reconstructed - class reference has gone");
90 Class<?>[] paramTypes = null;
91 if (writeParamClassNames != null) {
92 paramTypes = new Class[2];
93 paramTypes[0] = writeParamTypeRef0.get();
94 if (paramTypes[0] == null) {
95 paramTypes[0] = reLoadClass(writeParamClassNames[0]);
96 if (paramTypes[0] != null) {
97 writeParamTypeRef0 = new WeakReference<>(paramTypes[0]);
98 }
99 }
100 paramTypes[1] = writeParamTypeRef1.get();
101 if (paramTypes[1] == null) {
102 paramTypes[1] = reLoadClass(writeParamClassNames[1]);
103 if (paramTypes[1] != null) {
104 writeParamTypeRef1 = new WeakReference<>(paramTypes[1]);
105 }
106 }
107 } else {
108 paramTypes = STRING_CLASS_PARAMETER;
109 }
110 try {
111 m = clazz.getMethod(methodName, paramTypes);
112
113
114 } catch (final NoSuchMethodException e) {
115 throw new IllegalStateException("Method " + methodName + " for " + className + " could not be reconstructed - method not found");
116 }
117 methodRef = new SoftReference<>(m);
118 }
119 return m;
120 }
121
122
123
124
125 private Class<?> reLoadClass() {
126 return reLoadClass(className);
127 }
128
129
130
131
132 private Class<?> reLoadClass(final String name) {
133
134 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
135
136
137 if (classLoader != null) {
138 try {
139 return classLoader.loadClass(name);
140 } catch (final ClassNotFoundException e) {
141
142 }
143 }
144
145
146 classLoader = MappedPropertyDescriptor.class.getClassLoader();
147 try {
148 return classLoader.loadClass(name);
149 } catch (final ClassNotFoundException e) {
150 return null;
151 }
152 }
153 }
154
155
156
157
158 private static final Class<?>[] STRING_CLASS_PARAMETER = new Class[] { String.class };
159
160
161
162
163
164
165 private static String capitalizePropertyName(final String s) {
166 if (s.isEmpty()) {
167 return s;
168 }
169
170 final char[] chars = s.toCharArray();
171 chars[0] = Character.toUpperCase(chars[0]);
172 return new String(chars);
173 }
174
175
176
177
178 private static Method getMethod(final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes) throws IntrospectionException {
179 if (methodName == null) {
180 return null;
181 }
182
183 final Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, parameterTypes);
184 if (method != null) {
185 return method;
186 }
187
188 final int parameterCount = parameterTypes == null ? 0 : parameterTypes.length;
189
190
191 throw new IntrospectionException("No method \"" + methodName + "\" with " + parameterCount + " parameter(s) of matching types.");
192 }
193
194
195
196
197 private static Method getMethod(final Class<?> clazz, final String methodName, final int parameterCount) throws IntrospectionException {
198 if (methodName == null) {
199 return null;
200 }
201
202 final Method method = internalGetMethod(clazz, methodName, parameterCount);
203 if (method != null) {
204 return method;
205 }
206
207
208 throw new IntrospectionException("No method \"" + methodName + "\" with " + parameterCount + " parameter(s)");
209 }
210
211
212
213
214 private static Method internalGetMethod(final Class<?> initial, final String methodName, final int parameterCount) {
215
216
217 for (Class<?> clazz = initial; clazz != null; clazz = clazz.getSuperclass()) {
218 final Method[] methods = clazz.getDeclaredMethods();
219 for (final Method method : methods) {
220 if (method == null) {
221 continue;
222 }
223
224 final int mods = method.getModifiers();
225 if (!Modifier.isPublic(mods) || Modifier.isStatic(mods)) {
226 continue;
227 }
228 if (method.getName().equals(methodName) && method.getParameterTypes().length == parameterCount) {
229 return method;
230 }
231 }
232 }
233
234
235
236
237 final Class<?>[] interfaces = initial.getInterfaces();
238 for (final Class<?> interface1 : interfaces) {
239 final Method method = internalGetMethod(interface1, methodName, parameterCount);
240 if (method != null) {
241 return method;
242 }
243 }
244
245 return null;
246 }
247
248
249
250
251 private Reference<Class<?>> mappedPropertyTypeRef;
252
253
254
255
256 private MappedMethodReference mappedReadMethodRef;
257
258
259
260
261 private MappedMethodReference mappedWriteMethodRef;
262
263
264
265
266
267
268
269
270
271
272 public MappedPropertyDescriptor(final String propertyName, final Class<?> beanClass) throws IntrospectionException {
273 super(propertyName, null, null);
274
275 if (propertyName == null || propertyName.isEmpty()) {
276 throw new IntrospectionException("bad property name: " + propertyName + " on class: " + beanClass.getClass().getName());
277 }
278
279 setName(propertyName);
280 final String base = capitalizePropertyName(propertyName);
281
282
283 Method mappedReadMethod = null;
284 Method mappedWriteMethod = null;
285 try {
286 try {
287 mappedReadMethod = getMethod(beanClass, "get" + base, STRING_CLASS_PARAMETER);
288 } catch (final IntrospectionException e) {
289 mappedReadMethod = getMethod(beanClass, "is" + base, STRING_CLASS_PARAMETER);
290 }
291 final Class<?>[] params = { String.class, mappedReadMethod.getReturnType() };
292 mappedWriteMethod = getMethod(beanClass, "set" + base, params);
293 } catch (final IntrospectionException e) {
294
295
296
297 }
298
299
300 if (mappedReadMethod == null) {
301 mappedWriteMethod = getMethod(beanClass, "set" + base, 2);
302 }
303
304 if (mappedReadMethod == null && mappedWriteMethod == null) {
305 throw new IntrospectionException("Property '" + propertyName + "' not found on " + beanClass.getName());
306 }
307 mappedReadMethodRef = new MappedMethodReference(mappedReadMethod);
308 mappedWriteMethodRef = new MappedMethodReference(mappedWriteMethod);
309
310 findMappedPropertyType();
311 }
312
313
314
315
316
317
318
319
320
321
322 public MappedPropertyDescriptor(final String propertyName, final Class<?> beanClass, final String mappedGetterName, final String mappedSetterName)
323 throws IntrospectionException {
324 super(propertyName, null, null);
325
326 if (propertyName == null || propertyName.isEmpty()) {
327 throw new IntrospectionException("bad property name: " + propertyName);
328 }
329 setName(propertyName);
330
331
332 Method mappedReadMethod;
333 Method mappedWriteMethod = null;
334 mappedReadMethod = getMethod(beanClass, mappedGetterName, STRING_CLASS_PARAMETER);
335
336 if (mappedReadMethod != null) {
337 final Class<?>[] params = { String.class, mappedReadMethod.getReturnType() };
338 mappedWriteMethod = getMethod(beanClass, mappedSetterName, params);
339 } else {
340 mappedWriteMethod = getMethod(beanClass, mappedSetterName, 2);
341 }
342 mappedReadMethodRef = new MappedMethodReference(mappedReadMethod);
343 mappedWriteMethodRef = new MappedMethodReference(mappedWriteMethod);
344
345 findMappedPropertyType();
346 }
347
348
349
350
351
352
353
354
355
356 public MappedPropertyDescriptor(final String propertyName, final Method mappedGetter, final Method mappedSetter) throws IntrospectionException {
357 super(propertyName, mappedGetter, mappedSetter);
358
359 if (propertyName == null || propertyName.isEmpty()) {
360 throw new IntrospectionException("bad property name: " + propertyName);
361 }
362
363 setName(propertyName);
364 mappedReadMethodRef = new MappedMethodReference(mappedGetter);
365 mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
366 findMappedPropertyType();
367 }
368
369
370
371
372 private void findMappedPropertyType() throws IntrospectionException {
373 final Method mappedReadMethod = getMappedReadMethod();
374 final Method mappedWriteMethod = getMappedWriteMethod();
375 Class<?> mappedPropertyType = null;
376 if (mappedReadMethod != null) {
377 if (mappedReadMethod.getParameterTypes().length != 1) {
378 throw new IntrospectionException("bad mapped read method arg count");
379 }
380 mappedPropertyType = mappedReadMethod.getReturnType();
381 if (mappedPropertyType == Void.TYPE) {
382 throw new IntrospectionException("mapped read method " + mappedReadMethod.getName() + " returns void");
383 }
384 }
385
386 if (mappedWriteMethod != null) {
387 final Class<?>[] params = mappedWriteMethod.getParameterTypes();
388 if (params.length != 2) {
389 throw new IntrospectionException("bad mapped write method arg count");
390 }
391 if (mappedPropertyType != null && mappedPropertyType != params[1]) {
392 throw new IntrospectionException("type mismatch between mapped read and write methods");
393 }
394 mappedPropertyType = params[1];
395 }
396 mappedPropertyTypeRef = new SoftReference<>(mappedPropertyType);
397 }
398
399
400
401
402
403
404
405
406
407 public Class<?> getMappedPropertyType() {
408 return mappedPropertyTypeRef.get();
409 }
410
411
412
413
414
415
416 public Method getMappedReadMethod() {
417 return mappedReadMethodRef.get();
418 }
419
420
421
422
423
424
425 public Method getMappedWriteMethod() {
426 return mappedWriteMethodRef.get();
427 }
428
429
430
431
432
433
434
435 public void setMappedReadMethod(final Method mappedGetter) throws IntrospectionException {
436 mappedReadMethodRef = new MappedMethodReference(mappedGetter);
437 findMappedPropertyType();
438 }
439
440
441
442
443
444
445
446 public void setMappedWriteMethod(final Method mappedSetter) throws IntrospectionException {
447 mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
448 findMappedPropertyType();
449 }
450 }