1 | |
|
2 | |
|
3 | |
|
4 | |
|
5 | |
|
6 | |
|
7 | |
|
8 | |
|
9 | |
|
10 | |
package org.openpermis.editor.policy.beans; |
11 | |
|
12 | |
import java.beans.PropertyChangeListener; |
13 | |
import java.lang.reflect.InvocationTargetException; |
14 | |
import java.lang.reflect.Method; |
15 | |
import java.lang.reflect.Modifier; |
16 | |
import java.util.Arrays; |
17 | |
import java.util.HashMap; |
18 | |
import java.util.Map; |
19 | |
|
20 | |
|
21 | |
|
22 | |
|
23 | |
|
24 | |
final class BeanUtilities { |
25 | |
|
26 | |
|
27 | |
|
28 | |
|
29 | |
|
30 | |
|
31 | |
|
32 | |
private static final String ADD = "addPropertyChangeListener"; |
33 | |
|
34 | |
|
35 | |
|
36 | |
|
37 | |
|
38 | |
private static final String REMOVE = "removePropertyChangeListener"; |
39 | |
|
40 | |
|
41 | |
|
42 | |
|
43 | |
|
44 | |
private static final String SET_PREFIX = "set"; |
45 | |
|
46 | |
|
47 | |
|
48 | |
|
49 | |
|
50 | |
private static final String GET_PREFIX = "get"; |
51 | |
|
52 | |
|
53 | |
|
54 | |
|
55 | |
|
56 | |
private static final String IS_PREFIX = "is"; |
57 | |
|
58 | |
|
59 | |
|
60 | |
|
61 | |
|
62 | 1 | private static final int[] ARG_COUNT = { 0, 1, 2, 4 }; |
63 | |
|
64 | |
|
65 | |
|
66 | |
|
67 | |
|
68 | |
private static final int ARG_SOURCE = 0; |
69 | |
|
70 | |
|
71 | |
|
72 | |
|
73 | |
|
74 | |
private static final int ARG_PROPERTY = 1; |
75 | |
|
76 | |
|
77 | |
|
78 | |
|
79 | |
|
80 | |
private static final int ARG_OLD_VALUE = 2; |
81 | |
|
82 | |
|
83 | |
|
84 | |
|
85 | |
|
86 | |
private static final int ARG_NEW_VALUE = 3; |
87 | |
|
88 | |
|
89 | |
|
90 | |
|
91 | |
|
92 | |
|
93 | |
|
94 | |
|
95 | |
private static final String methodName (String prefix, String property) { |
96 | 88 | if (property == null) { |
97 | 4 | throw new IllegalArgumentException("Invalid property name [null]."); |
98 | 84 | } else if (property.length() == 0) { |
99 | 4 | throw new IllegalArgumentException("Invalid empty property name."); |
100 | |
} |
101 | 80 | final StringBuilder sb = new StringBuilder(prefix); |
102 | 80 | sb.append(Character.toUpperCase(property.charAt(0))); |
103 | 80 | sb.append(property.substring(1)); |
104 | 80 | return sb.toString(); |
105 | |
} |
106 | |
|
107 | |
|
108 | |
|
109 | |
|
110 | |
|
111 | |
|
112 | |
|
113 | |
|
114 | |
static final String getterName (String property, boolean booleanValue) { |
115 | 65 | return methodName(booleanValue ? IS_PREFIX : GET_PREFIX, property); |
116 | |
} |
117 | |
|
118 | |
|
119 | |
|
120 | |
|
121 | |
|
122 | |
|
123 | |
|
124 | |
static final String setterName (String property) { |
125 | 23 | return methodName(SET_PREFIX, property); |
126 | |
} |
127 | |
|
128 | |
|
129 | |
|
130 | |
|
131 | |
|
132 | |
|
133 | |
|
134 | |
|
135 | |
|
136 | |
|
137 | |
static String signature ( |
138 | |
Object beanOrClass, String methodName, Class<?> parameterClass |
139 | |
) { |
140 | 8 | if (methodName == null) { |
141 | 2 | throw new IllegalArgumentException("Invalid method name [null]."); |
142 | 6 | } else if (methodName.length() == 0) { |
143 | 1 | throw new IllegalArgumentException("Invalid empty method name."); |
144 | |
} |
145 | 5 | final StringBuilder sb = new StringBuilder(); |
146 | 5 | if (beanOrClass != null) { |
147 | 3 | final Class<?> cls = beanOrClass instanceof Class<?> ? |
148 | |
(Class<?>) beanOrClass : beanOrClass.getClass(); |
149 | 3 | sb.append(cls.getName()).append('.'); |
150 | |
} |
151 | 5 | sb.append(methodName).append('('); |
152 | 5 | if (parameterClass != null) { |
153 | 3 | sb.append(parameterClass.getName()); |
154 | |
} |
155 | 5 | return sb.append(')').toString(); |
156 | |
} |
157 | |
|
158 | |
|
159 | |
|
160 | |
|
161 | |
|
162 | |
|
163 | |
|
164 | |
|
165 | |
|
166 | |
|
167 | |
public static Method method (Class<?> cls, String methodName, Class<?> parameterClass) { |
168 | 263 | if (cls == null) { |
169 | 2 | throw new IllegalArgumentException("Invalid class [null]."); |
170 | |
} |
171 | 261 | if (methodName == null) { |
172 | 1 | throw new IllegalArgumentException("Invalid method name [null]."); |
173 | 260 | } else if (methodName.length() == 0) { |
174 | 1 | throw new IllegalArgumentException("Invalid empty method name."); |
175 | |
} |
176 | |
try { |
177 | 259 | final Class<?>[] parameters = parameterClass == null ? |
178 | |
new Class<?>[0] : new Class<?>[] { parameterClass }; |
179 | 259 | return cls.getMethod(methodName, parameters); |
180 | 0 | } catch (SecurityException e) { |
181 | 0 | throw new IllegalArgumentException( |
182 | |
"Cannot access method [" + signature(cls, methodName, parameterClass) + "].", e |
183 | |
); |
184 | 17 | } catch (NoSuchMethodException e) { |
185 | 17 | return null; |
186 | |
} |
187 | |
} |
188 | |
|
189 | |
|
190 | |
|
191 | |
|
192 | |
|
193 | |
|
194 | |
|
195 | |
|
196 | |
|
197 | |
public static Method getter (Class<?> beanClass, String property) { |
198 | 47 | Method method = method(beanClass, getterName(property, false), null); |
199 | 44 | if (method == null) { |
200 | |
|
201 | 8 | method = method(beanClass, getterName(property, true), null); |
202 | |
} |
203 | 44 | if (method == null) { |
204 | 3 | return null; |
205 | |
} |
206 | 41 | if (Void.TYPE.equals(method.getReturnType())) { |
207 | 1 | return null; |
208 | |
} |
209 | 40 | return method; |
210 | |
} |
211 | |
|
212 | |
|
213 | |
|
214 | |
|
215 | |
|
216 | |
|
217 | |
|
218 | |
|
219 | |
|
220 | |
public static Method setter (Class<?> beanClass, String property) { |
221 | 18 | if (beanClass == null) { |
222 | 1 | throw new IllegalArgumentException("Invalid class [null]."); |
223 | |
} |
224 | 17 | final String setterName = setterName(property); |
225 | |
try { |
226 | 192 | for (Method method : beanClass.getMethods()) { |
227 | 189 | if ( |
228 | |
setterName.equals(method.getName()) && |
229 | |
method.getParameterTypes().length == 1 |
230 | |
) { |
231 | 12 | return method; |
232 | |
} |
233 | |
} |
234 | 0 | } catch (SecurityException e) { |
235 | 0 | throw new IllegalArgumentException( |
236 | |
"Cannot access methods of class [" + beanClass.getName() + "].", e |
237 | |
); |
238 | 3 | } |
239 | 3 | return null; |
240 | |
} |
241 | |
|
242 | |
|
243 | |
|
244 | |
|
245 | |
|
246 | |
|
247 | |
|
248 | |
public static boolean isJavaBean (Class<?> cls) { |
249 | 88 | return |
250 | |
cls != null && |
251 | |
method(cls, ADD, PropertyChangeListener.class) != null && |
252 | |
method(cls, REMOVE, PropertyChangeListener.class) != null; |
253 | |
} |
254 | |
|
255 | |
|
256 | |
|
257 | |
|
258 | |
|
259 | |
|
260 | |
|
261 | |
public static boolean isJavaBean (Object object) { |
262 | 22 | return object != null && isJavaBean(object.getClass()); |
263 | |
} |
264 | |
|
265 | |
|
266 | |
|
267 | |
|
268 | |
|
269 | |
|
270 | |
|
271 | |
|
272 | |
static void validate (Method method, PropertyChange pc) { |
273 | 63 | if (pc.bean() == null) { |
274 | 0 | throw new IllegalStateException( |
275 | |
"@PropertyChange bean is [null] for method [" + method + "]." |
276 | |
); |
277 | |
} |
278 | 63 | if (pc.property() == null) { |
279 | 0 | throw new IllegalStateException( |
280 | |
"@PropertyChange property is [null] for method [" + method + "]." |
281 | |
); |
282 | |
} |
283 | 63 | if (!isJavaBean(pc.bean())) { |
284 | 1 | throw new IllegalStateException( |
285 | |
"@PropertyChange bean [" + pc.bean().getName() + |
286 | |
"] is not a Java Bean for method [" + method + "]." |
287 | |
); |
288 | |
} |
289 | 62 | Class<?>[] parameterTypes = method.getParameterTypes(); |
290 | 62 | boolean validLength = false; |
291 | 204 | for (int length : ARG_COUNT) { |
292 | 199 | if (length == parameterTypes.length) { |
293 | 57 | validLength = true; |
294 | 57 | break; |
295 | |
} |
296 | |
} |
297 | 62 | if (!validLength) { |
298 | 5 | throw new IllegalStateException( |
299 | |
"Invalid number of parameters [" + parameterTypes.length + |
300 | |
"] for property change method [" + method + |
301 | |
"] expecting one of " + Arrays.toString(ARG_COUNT) + " parameters." |
302 | |
); |
303 | |
} |
304 | 57 | final boolean hasSource = parameterTypes.length > ARG_SOURCE; |
305 | 57 | final boolean hasProperty = parameterTypes.length > ARG_PROPERTY; |
306 | 57 | final boolean hasOldValue = parameterTypes.length > ARG_OLD_VALUE; |
307 | 57 | final boolean hasNewValue = parameterTypes.length > ARG_NEW_VALUE; |
308 | 57 | final boolean hasParameters = hasOldValue && hasNewValue; |
309 | 57 | if (hasSource && !pc.bean().equals(parameterTypes[ARG_SOURCE])) { |
310 | 1 | throw new IllegalStateException( |
311 | |
"Type mismatch of first method parameter of [" + method + |
312 | |
"], expecting same class as bean class [" + pc.bean().getName() + |
313 | |
"] to receive the source of the property change." |
314 | |
); |
315 | |
} |
316 | 56 | if (PropertyChange.WILDCARD_PROPERTY.equals(pc.property())) { |
317 | 23 | if (hasProperty && !String.class.equals(parameterTypes[ARG_PROPERTY])) { |
318 | 0 | throw new IllegalStateException( |
319 | |
"Type mismatch of second method parameter of [" + method + |
320 | |
"] has invalid type, expecting [String] to receive the property name." |
321 | |
); |
322 | |
} |
323 | 23 | if (hasOldValue && !Object.class.equals(parameterTypes[ARG_OLD_VALUE])) { |
324 | 2 | throw new IllegalStateException( |
325 | |
"Type mismatch of third method parameter of [" + method + |
326 | |
"] has invalid type, expecting [Object] to receive the old value." |
327 | |
); |
328 | |
} |
329 | 21 | if (hasNewValue && !Object.class.equals(parameterTypes[ARG_NEW_VALUE])) { |
330 | 1 | throw new IllegalStateException( |
331 | |
"Type mismatch of fourth method parameter of [" + method + |
332 | |
"] has invalid type, expecting [Object] to receive the new value." |
333 | |
); |
334 | |
} |
335 | |
|
336 | 20 | return; |
337 | |
} |
338 | 33 | final Method getter = getter(pc.bean(), pc.property()); |
339 | 33 | if (getter == null) { |
340 | 1 | throw new IllegalStateException( |
341 | |
"@PropertyChange property [" + pc.property() + |
342 | |
"] is not a bean property of [" + pc.bean().getName() + "]." |
343 | |
); |
344 | |
} |
345 | 32 | if (hasParameters && !getter.getReturnType().equals(pc.parameter())) { |
346 | 1 | throw new IllegalStateException( |
347 | |
"@PropertyChange type mismatch of 'parameter' type," + |
348 | |
" expecting [parameter=" + getter.getReturnType().getName() + "]." |
349 | |
); |
350 | 31 | } else if (!hasParameters && !Void.class.equals(pc.parameter())) { |
351 | 3 | throw new IllegalStateException( |
352 | |
"@PropertyChange parameter specified but method [" + method + |
353 | |
"] does not declare old and new value parameters." |
354 | |
); |
355 | |
} |
356 | 28 | if (hasProperty && !String.class.equals(parameterTypes[ARG_PROPERTY])) { |
357 | 0 | throw new IllegalStateException( |
358 | |
"Type mismatch of second method parameter of [" + method + |
359 | |
"] has invalid type, expecting [String] to receive the property name." |
360 | |
); |
361 | |
} |
362 | 28 | if (hasOldValue && !getter.getReturnType().equals(parameterTypes[ARG_OLD_VALUE])) { |
363 | 1 | throw new IllegalStateException( |
364 | |
"Type mismatch of third method parameter of [" + method + |
365 | |
"], expecting [" + getter.getReturnType().getName() + |
366 | |
"] to receive the old value of the property change." |
367 | |
); |
368 | |
} |
369 | 27 | if (hasNewValue && !getter.getReturnType().equals(parameterTypes[ARG_NEW_VALUE])) { |
370 | 1 | throw new IllegalStateException( |
371 | |
"Type mismatch of fourth method parameter of [" + method + |
372 | |
"], expecting [" + getter.getReturnType().getName() + |
373 | |
"] to receive the new value of the property change." |
374 | |
); |
375 | |
} |
376 | 26 | } |
377 | |
|
378 | |
|
379 | |
|
380 | |
|
381 | |
|
382 | |
|
383 | |
|
384 | |
|
385 | |
public static Map<String, Method> getPropertyChangeMethods ( |
386 | |
Class<?> beanClass, Class<?> targetClass |
387 | |
) { |
388 | 59 | if (targetClass == null) { |
389 | 1 | throw new IllegalArgumentException("Invalid class [null]."); |
390 | |
} |
391 | 58 | final Map<String, Method> map = new HashMap<String, Method>(); |
392 | |
|
393 | 384 | for (Method method : targetClass.getDeclaredMethods()) { |
394 | 329 | if (method.getAnnotation(PropertyChange.class) == null) { |
395 | 257 | continue; |
396 | |
} |
397 | 72 | if (!Modifier.isPublic(method.getModifiers())) { |
398 | 3 | throw new IllegalStateException( |
399 | |
"Bad modifiers [" + Modifier.toString(method.getModifiers()) + |
400 | |
"] for property change support method [" + method + "]." |
401 | |
); |
402 | |
} |
403 | |
} |
404 | |
|
405 | 708 | for (Method method : targetClass.getMethods()) { |
406 | 672 | final PropertyChange pc = method.getAnnotation(PropertyChange.class); |
407 | 672 | if (pc != null && pc.bean().isAssignableFrom(beanClass)) { |
408 | 63 | validate(method, pc); |
409 | 46 | final Method old = map.put(pc.property(), method); |
410 | 46 | if (old != null) { |
411 | 2 | if (PropertyChange.WILDCARD_PROPERTY.equals(pc.property())) { |
412 | 1 | throw new IllegalStateException( |
413 | |
"Doubly defined wildcard property change annotation in method [" + |
414 | |
method + "] with first occurence in method [ " + old + "]." |
415 | |
); |
416 | |
} |
417 | 1 | throw new IllegalStateException( |
418 | |
"Doubly defined property change annotation [" + pc.property() + |
419 | |
"] in method [" + method + |
420 | |
"] with first occurence in method [ " + old + "]." |
421 | |
); |
422 | |
} |
423 | |
} |
424 | |
} |
425 | 36 | return map; |
426 | |
} |
427 | |
|
428 | |
|
429 | |
|
430 | |
|
431 | |
|
432 | |
|
433 | |
|
434 | |
|
435 | |
|
436 | |
|
437 | |
public static <T> T addPropertyChangeListener (T bean, PropertyChangeListener listener) { |
438 | 15 | if (bean == null) { |
439 | 0 | throw new IllegalArgumentException("Cannot add a listener to a [null] bean."); |
440 | |
} |
441 | 15 | if (listener == null) { |
442 | 0 | throw new IllegalArgumentException("Cannot add [null] listener to a bean."); |
443 | |
} |
444 | 15 | final Method add = method(bean.getClass(), ADD, PropertyChangeListener.class); |
445 | 15 | if (add == null || method(bean.getClass(), REMOVE, PropertyChangeListener.class) == null) { |
446 | 0 | throw new IllegalArgumentException( |
447 | |
"Object [" + bean.toString() + |
448 | |
"] of class [" + bean.getClass().getName() + |
449 | |
"] is not a Java Bean." |
450 | |
); |
451 | |
} |
452 | |
try { |
453 | 15 | add.invoke(bean, listener); |
454 | 0 | } catch (IllegalArgumentException e) { |
455 | 0 | throw new IllegalStateException( |
456 | |
"Internal error while invoking [" + ADD + "()] of [" + bean.toString() + |
457 | |
"] of class [" + bean.getClass().getName() + "].", e |
458 | |
); |
459 | 0 | } catch (IllegalAccessException e) { |
460 | 0 | throw new IllegalStateException( |
461 | |
"Cannot access method [" + ADD + "()] of [" + bean.toString() + |
462 | |
"] of class [" + bean.getClass().getName() + "].", e |
463 | |
); |
464 | 0 | } catch (InvocationTargetException e) { |
465 | 0 | throw new IllegalStateException( |
466 | |
"Invocation of [" + ADD + "()] of [" + bean.toString() + |
467 | |
"] of class [" + bean.getClass().getName() + "] failed.", e.getCause() |
468 | |
); |
469 | 15 | } |
470 | 15 | return bean; |
471 | |
} |
472 | |
|
473 | |
|
474 | |
|
475 | |
|
476 | |
|
477 | |
|
478 | |
|
479 | |
|
480 | |
|
481 | |
|
482 | |
public static <T> T removePropertyChangeListener (T bean, PropertyChangeListener listener) { |
483 | 1 | if (bean == null) { |
484 | 0 | throw new IllegalArgumentException("Cannot remove listener from a [null] bean."); |
485 | |
} |
486 | 1 | if (listener == null) { |
487 | 0 | throw new IllegalArgumentException("Cannot remove [null] listener from a bean."); |
488 | |
} |
489 | 1 | final Method remove = method(bean.getClass(), REMOVE, PropertyChangeListener.class); |
490 | 1 | if (remove == null) { |
491 | 0 | throw new IllegalStateException( |
492 | |
"Method [" + REMOVE + "()] of [" + bean.toString() + |
493 | |
"] of class [" + bean.getClass().getName() + "] not found." |
494 | |
); |
495 | |
} |
496 | |
try { |
497 | 1 | remove.invoke(bean, listener); |
498 | 0 | } catch (IllegalArgumentException e) { |
499 | 0 | throw new IllegalStateException( |
500 | |
"Internal error while invoking [" + REMOVE + "()] of [" + bean.toString() + |
501 | |
"] of class [" + bean.getClass().getName() + "].", e |
502 | |
); |
503 | 0 | } catch (IllegalAccessException e) { |
504 | 0 | throw new IllegalStateException( |
505 | |
"Cannot access method [" + REMOVE + "()] of [" + bean.toString() + |
506 | |
"] of class [" + bean.getClass().getName() + "].", e |
507 | |
); |
508 | 0 | } catch (InvocationTargetException e) { |
509 | 0 | throw new IllegalStateException( |
510 | |
"Invocation of [" + REMOVE + "()] of [" + bean.toString() + |
511 | |
"] of class [" + bean.getClass().getName() + "] failed.", e.getCause() |
512 | |
); |
513 | 1 | } |
514 | 1 | return bean; |
515 | |
} |
516 | |
|
517 | |
|
518 | |
|
519 | |
|
520 | |
|
521 | |
|
522 | |
|
523 | |
private BeanUtilities () { |
524 | 0 | super(); |
525 | 0 | } |
526 | |
|
527 | |
} |