1 package org.andromda.core.common;
2
3 import java.beans.IntrospectionException;
4 import java.beans.PropertyDescriptor;
5 import java.lang.reflect.Method;
6 import java.util.Collection;
7 import java.util.HashMap;
8 import java.util.Map;
9 import java.util.regex.Pattern;
10 import org.apache.commons.lang.StringUtils;
11 import org.apache.commons.lang.exception.ExceptionUtils;
12 import org.apache.log4j.Logger;
13
14
15
16
17
18
19
20 public final class Introspector
21 {
22
23
24
25 private static Introspector instance = null;
26
27
28
29
30
31
32 public static Introspector instance()
33 {
34 if (instance == null)
35 {
36 instance = new Introspector();
37 }
38 return instance;
39 }
40
41
42
43
44 private static final Logger logger = Logger.getLogger(Introspector.class);
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 public boolean containsValidProperty(
67 final Object object,
68 final String name,
69 final String value)
70 {
71 boolean valid;
72
73 try
74 {
75 final Object propertyValue = this.getProperty(
76 object,
77 name);
78 valid = propertyValue != null;
79
80
81
82 if (valid)
83 {
84
85
86 if (propertyValue instanceof Collection)
87 {
88 valid = !((Collection)propertyValue).isEmpty();
89 }
90 else
91 {
92 final String valueAsString = String.valueOf(propertyValue);
93 if (StringUtils.isNotBlank(value))
94 {
95 valid = valueAsString.equals(value);
96 }
97 else if (propertyValue instanceof Boolean)
98 {
99 valid = Boolean.valueOf(valueAsString);
100 }
101 }
102 }
103 }
104 catch (final Throwable throwable)
105 {
106 valid = false;
107 }
108 return valid;
109 }
110
111
112
113
114
115
116
117
118
119 public void setProperty(
120 final Object object,
121 final String name,
122 final Object value)
123 {
124 this.setNestedProperty(
125 object,
126 name,
127 value);
128 }
129
130
131
132
133 private static final char NESTED_DELIMITER = '.';
134
135
136
137
138
139
140
141
142 private void setNestedProperty(
143 final Object object,
144 String name,
145 final Object value)
146 {
147 if (object != null && StringUtils.isNotBlank(name))
148 {
149 final int dotIndex = name.indexOf(NESTED_DELIMITER);
150 if (dotIndex >= name.length())
151 {
152 throw new IntrospectorException("Invalid property call --> '" + name + '\'');
153 }
154 String[] names = name.split("\\" + NESTED_DELIMITER);
155 Object objectToPopulate = object;
156 for (int ctr = 0; ctr < names.length; ctr++)
157 {
158 name = names[ctr];
159 if (ctr == names.length - 1)
160 {
161 break;
162 }
163 objectToPopulate = this.internalGetProperty(
164 objectToPopulate,
165 name);
166 }
167 this.internalSetProperty(
168 objectToPopulate,
169 name,
170 value);
171 }
172 }
173
174
175
176
177
178
179
180
181 public final Object getProperty(
182 final Object object,
183 final String name)
184 {
185 Object result;
186
187 try
188 {
189 result = this.getNestedProperty(
190 object,
191 name);
192 }
193 catch (final NullPointerException ex)
194 {
195 return "null";
196 }
197 catch (final StackOverflowError ex)
198 {
199 return "StackOverflowError";
200 }
201 catch (final IntrospectorException throwable)
202 {
203
204
205
206 throw throwable;
207 }
208 catch (Throwable throwable)
209 {
210 throwable = ExceptionUtils.getRootCause(throwable);
211
212
213
214 if (throwable instanceof IntrospectorException)
215 {
216 throw (IntrospectorException)throwable;
217 }
218 throw new IntrospectorException(throwable);
219 }
220 return result;
221 }
222
223
224
225
226
227
228
229
230
231 private Object getNestedProperty(
232 final Object object,
233 final String name)
234 {
235 Object property = null;
236 if (object != null && StringUtils.isNotBlank(name))
237 {
238 int dotIndex = name.indexOf(NESTED_DELIMITER);
239 if (dotIndex == -1)
240 {
241 property = this.internalGetProperty(
242 object,
243 name);
244 }
245 else
246 {
247 if (dotIndex >= name.length())
248 {
249 throw new IntrospectorException("Invalid property call --> '" + name + '\'');
250 }
251 final Object nextInstance = this.internalGetProperty(
252 object,
253 name.substring(
254 0,
255 dotIndex));
256 property = getNestedProperty(
257 nextInstance,
258 name.substring(dotIndex + 1));
259 }
260 }
261 return property;
262 }
263
264
265
266
267 private final Map<Class, Map<String, Method>> writeMethodsCache = new HashMap<Class, Map<String, Method>>();
268
269
270
271
272
273
274
275
276 private Method getWriteMethod(
277 final Object object,
278 final String name)
279 {
280 Method writeMethod = null;
281 final Class objectClass = object.getClass();
282 Map<String, Method> classWriteMethods = this.writeMethodsCache.get(objectClass);
283 if (classWriteMethods == null)
284 {
285 classWriteMethods = new HashMap<String, Method>();
286 }
287 else
288 {
289 writeMethod = classWriteMethods.get(name);
290 }
291 if (writeMethod == null)
292 {
293 final PropertyDescriptor descriptor = this.getPropertyDescriptor(
294 object.getClass(),
295 name);
296 writeMethod = descriptor != null ? descriptor.getWriteMethod() : null;
297 if (writeMethod != null)
298 {
299 classWriteMethods.put(
300 name,
301 writeMethod);
302 this.writeMethodsCache.put(
303 objectClass,
304 classWriteMethods);
305 }
306 }
307 return writeMethod;
308 }
309
310
311
312
313
314
315
316
317
318 public boolean isReadable(
319 final Object object,
320 final String name)
321 {
322 return this.getReadMethod(
323 object,
324 name) != null;
325 }
326
327
328
329
330
331
332
333
334
335 public boolean isWritable(
336 final Object object,
337 final String name)
338 {
339 return this.getWriteMethod(
340 object,
341 name) != null;
342 }
343
344
345
346
347 private final Map<Class, Map<String, Method>> readMethodsCache = new HashMap<Class, Map<String, Method>>();
348
349
350
351
352
353
354
355
356 private Method getReadMethod(
357 final Object object,
358 final String name)
359 {
360 Method readMethod = null;
361 final Class objectClass = object.getClass();
362 Map<String, Method> classReadMethods = this.readMethodsCache.get(objectClass);
363 if (classReadMethods == null)
364 {
365 classReadMethods = new HashMap<String, Method>();
366 }
367 else
368 {
369 readMethod = classReadMethods.get(name);
370 }
371 if (readMethod == null)
372 {
373 final PropertyDescriptor descriptor = this.getPropertyDescriptor(
374 object.getClass(),
375 name);
376 readMethod = descriptor != null ? descriptor.getReadMethod() : null;
377 if (readMethod != null)
378 {
379 classReadMethods.put(
380 name,
381 readMethod);
382 this.readMethodsCache.put(
383 objectClass,
384 classReadMethods);
385 }
386 }
387 return readMethod;
388 }
389
390
391
392
393 private final Map<Class, Map<String, PropertyDescriptor>> propertyDescriptorsCache = new HashMap<Class, Map<String, PropertyDescriptor>>();
394
395
396
397
398 private Pattern propertyNamePattern = Pattern.compile("\\p{Lower}\\p{Upper}.*");
399
400
401
402
403
404
405
406
407
408 private PropertyDescriptor getPropertyDescriptor(
409 final Class type,
410 final String name)
411 {
412 PropertyDescriptor propertyDescriptor = null;
413 Map<String, PropertyDescriptor> classPropertyDescriptors = this.propertyDescriptorsCache.get(type);
414 if (classPropertyDescriptors == null)
415 {
416 classPropertyDescriptors = new HashMap<String, PropertyDescriptor>();
417 }
418 else
419 {
420 propertyDescriptor = classPropertyDescriptors.get(name);
421 }
422
423 if (propertyDescriptor == null)
424 {
425 try
426 {
427 final PropertyDescriptor[] descriptors =
428 java.beans.Introspector.getBeanInfo(type).getPropertyDescriptors();
429 final int descriptorNumber = descriptors.length;
430 for (int ctr = 0; ctr < descriptorNumber; ctr++)
431 {
432 final PropertyDescriptor descriptor = descriptors[ctr];
433
434
435 final String compareName =
436 propertyNamePattern.matcher(name).matches() ? StringUtils.capitalize(name) : name;
437 if (descriptor.getName().equals(compareName))
438 {
439 propertyDescriptor = descriptor;
440 break;
441 }
442 }
443 if (propertyDescriptor == null && name.indexOf(NESTED_DELIMITER) != -1)
444 {
445 int dotIndex = name.indexOf(NESTED_DELIMITER);
446 if (dotIndex >= name.length())
447 {
448 throw new IntrospectorException("Invalid property call --> '" + name + '\'');
449 }
450 final PropertyDescriptor nextInstance =
451 this.getPropertyDescriptor(
452 type,
453 name.substring(
454 0,
455 dotIndex));
456 propertyDescriptor =
457 this.getPropertyDescriptor(
458 nextInstance.getPropertyType(),
459 name.substring(dotIndex + 1));
460 }
461 }
462 catch (final IntrospectionException exception)
463 {
464 throw new IntrospectorException(exception);
465 }
466 classPropertyDescriptors.put(
467 name,
468 propertyDescriptor);
469 this.propertyDescriptorsCache.put(
470 type,
471 classPropertyDescriptors);
472 }
473 return propertyDescriptor;
474 }
475
476
477
478
479
480 private final Map<Object, String> evaluatingObjects = new HashMap<Object, String>();
481
482
483
484
485
486
487
488
489
490
491 private Object internalGetProperty(
492 final Object object,
493 final String name)
494 {
495 Object property = null;
496
497
498
499 final Object value = this.evaluatingObjects.get(object);
500 if (value == null || !value.equals(name))
501 {
502 this.evaluatingObjects.put(
503 object,
504 name);
505 if (object != null || StringUtils.isNotBlank(name))
506 {
507 final Method method = this.getReadMethod(
508 object,
509 name);
510 if (method == null)
511 {
512 throw new IntrospectorException("No readable property named '" + name + "', exists on object '" +
513 object + '\'');
514 }
515 try
516 {
517 property = method.invoke(
518 object,
519 (Object[])null);
520 }
521 catch (Throwable throwable)
522 {
523 if (throwable.getCause()!=null)
524 {
525 throwable = throwable.getCause();
526 }
527
528 StackTraceElement[] trace = throwable.getStackTrace();
529 String location = " AT " + trace[0].getClassName() + '.' + trace[0].getMethodName() + ':' + trace[0].getLineNumber();
530 if (throwable.getMessage()!=null)
531 {
532 location += ' ' + throwable.getMessage();
533 }
534 logger.error("Introspector " + throwable + " invoking " + object + " METHOD " + method + " WITH " + name + location);
535
536 if (throwable instanceof Exception)
537 {
538 throw new IntrospectorException(throwable);
539 }
540 }
541 }
542 this.evaluatingObjects.remove(object);
543 }
544 return property;
545 }
546
547
548
549
550
551
552
553
554
555
556 private void internalSetProperty(
557 final Object object,
558 final String name,
559 Object value)
560 {
561 if (object != null || (StringUtils.isNotBlank(name)))
562 {
563 Class expectedType = null;
564 if (value != null && object != null)
565 {
566 final PropertyDescriptor descriptor = this.getPropertyDescriptor(
567 object.getClass(),
568 name);
569 if (descriptor != null)
570 {
571 expectedType = this.getPropertyDescriptor(
572 object.getClass(),
573 name).getPropertyType();
574 value = Converter.convert(
575 value,
576 expectedType);
577 }
578 }
579 final Method method = this.getWriteMethod(
580 object,
581 name);
582 if (method == null)
583 {
584 throw new IntrospectorException("No writeable property named '" + name + "', exists on object '" +
585 object + '\'');
586 }
587 try
588 {
589 method.invoke(
590 object,
591 value);
592 }
593 catch (final Throwable throwable)
594 {
595 throw new IntrospectorException(throwable);
596 }
597 }
598 }
599
600
601
602
603
604 public void shutdown()
605 {
606 this.propertyDescriptorsCache.clear();
607 this.writeMethodsCache.clear();
608 this.readMethodsCache.clear();
609 this.evaluatingObjects.clear();
610 Introspector.instance = null;
611 }
612
613
614
615
616 @Override
617 public String toString()
618 {
619 StringBuilder builder = new StringBuilder();
620 builder.append(super.toString()).append(" [writeMethodsCache=").append(this.writeMethodsCache)
621 .append(", readMethodsCache=").append(this.readMethodsCache)
622 .append(", propertyDescriptorsCache=").append(this.propertyDescriptorsCache)
623 .append(", propertyNamePattern=").append(this.propertyNamePattern)
624 .append(", evaluatingObjects=").append(this.evaluatingObjects).append("]");
625 return builder.toString();
626 }
627 }