1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package net.sf.morph.reflect.reflectors;
17
18 import java.lang.reflect.Proxy;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.Iterator;
22 import java.util.Map;
23
24 import net.sf.composite.util.ObjectUtils;
25 import net.sf.morph.reflect.BeanReflector;
26 import net.sf.morph.reflect.ContainerReflector;
27 import net.sf.morph.reflect.DecoratedReflector;
28 import net.sf.morph.reflect.GrowableContainerReflector;
29 import net.sf.morph.reflect.IndexedContainerReflector;
30 import net.sf.morph.reflect.MutableIndexedContainerReflector;
31 import net.sf.morph.reflect.ReflectionException;
32 import net.sf.morph.reflect.Reflector;
33 import net.sf.morph.reflect.SizableReflector;
34 import net.sf.morph.util.ClassUtils;
35 import net.sf.morph.util.ContainerUtils;
36 import net.sf.morph.util.StringUtils;
37 import net.sf.morph.wrap.Wrapper;
38 import net.sf.morph.wrap.support.DefaultWrapperInvocationHandler;
39 import net.sf.morph.wrap.support.WrapperInvocationHandler;
40
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
43
44 /**
45 * <p>
46 * Convenient base class for Reflectors. Validates arguments and takes care of
47 * logging and exception handling. Also, automatically implements some methods
48 * for certain types of reflectors. Most notably, the BeanReflector interface
49 * is automatically implemented for reflectors that implement
50 * MutableIndexedContainerReflector. See method JavaDoc for more information.
51 * </p>
52 *
53 * @author Matt Sgarlata
54 * @since Nov 14, 2004
55 */
56 public abstract class BaseReflector implements Reflector, DecoratedReflector {
57
58
59
60 private boolean initialized;
61 private boolean cachingIsReflectableCalls = true;
62 private String reflectorName;
63
64 private transient Class[] reflectableClasses;
65 private transient Map reflectableCallCache;
66
67 /** Protected Log instance */
68 protected transient Log log;
69
70 /**
71 * Create a new BaseReflector.
72 */
73 public BaseReflector() {
74 setInitialized(false);
75 setReflectorName(null);
76 }
77
78
79
80 /**
81 * Implementation of {@link #initialize()}.
82 * @throws Exception
83 */
84 protected void initializeImpl() throws Exception {
85 }
86
87 /**
88 * Initialize this Reflector.
89 * @throws ReflectionException
90 */
91 protected final void initialize() throws ReflectionException {
92 if (!initialized) {
93 if (isPerformingLogging() && log.isInfoEnabled()) {
94 log.info("Initializing reflector " + ObjectUtils.getObjectDescription(this));
95 }
96
97 try {
98 initializeImpl();
99 reflectableClasses = getReflectableClassesImpl();
100 reflectableCallCache = Collections.synchronizedMap(new HashMap());
101 setInitialized(true);
102 }
103 catch (ReflectionException e) {
104 throw e;
105 }
106 catch (Exception e) {
107 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
108 throw (RuntimeException) e;
109 }
110 throw new ReflectionException("Could not initialize " + ObjectUtils.getObjectDescription(this), e);
111 }
112 }
113 }
114
115
116
117 /**
118 * {@inheritDoc}
119 * @see net.sf.morph.reflect.Reflector#getReflectableClasses()
120 */
121 public final Class[] getReflectableClasses() {
122 initialize();
123 return reflectableClasses;
124 }
125
126 /**
127 * Implementation of {@link Reflector#getReflectableClasses()}.
128 */
129 protected abstract Class[] getReflectableClassesImpl() throws Exception;
130
131 /**
132 * {@inheritDoc}
133 * @see net.sf.morph.reflect.Reflector#getWrapper(java.lang.Object)
134 */
135 public final Wrapper getWrapper(Object object) {
136 if (log.isTraceEnabled()) {
137 log.trace("Creating wrapper for " + ObjectUtils.getObjectDescription(object));
138 }
139
140 if (object == null) {
141 return null;
142 }
143 checkIsReflectable(object);
144
145 try {
146 return getWrapperImpl(object);
147 }
148 catch (ReflectionException e) {
149 throw e;
150 }
151 catch (Exception e) {
152 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
153 throw (RuntimeException) e;
154 }
155 throw new ReflectionException("Unable to create wrapper for "
156 + ObjectUtils.getObjectDescription(object), e);
157 }
158 }
159
160 /**
161 * Implementation of {@link Reflector#getWrapper(Object)}.
162 */
163 protected Wrapper getWrapperImpl(Object object) throws Exception {
164 WrapperInvocationHandler invocationHandler = createWrapperInvocationHandler(object);
165
166 return (Wrapper) Proxy.newProxyInstance(
167 object.getClass().getClassLoader(),
168 invocationHandler.getInterfaces(object),
169 invocationHandler);
170 }
171
172 /**
173 * Create a WrapperInvocationHandler for the specified Object.
174 * @param object
175 * @return WrapperInvocationHandler
176 */
177 protected WrapperInvocationHandler createWrapperInvocationHandler(Object object) {
178 return new DefaultWrapperInvocationHandler(object, this);
179 }
180
181 /**
182 * {@inheritDoc}
183 * @see net.sf.morph.reflect.DecoratedReflector#isReflectable(java.lang.Class)
184 */
185 public final boolean isReflectable(Class reflectedType) throws ReflectionException {
186 if (isPerformingLogging() && log.isTraceEnabled()) {
187 log.trace("Testing reflectability of " + ObjectUtils.getObjectDescription(reflectedType));
188 }
189 return isReflectableInternal(reflectedType);
190 }
191
192 /**
193 * Implementation of {@link DecoratedReflector#isReflectable(Class)}.
194 */
195 protected boolean isReflectableImpl(Class reflectedType) throws Exception {
196 return ClassUtils.inheritanceContains(getReflectableClasses(), reflectedType);
197 }
198
199 private boolean isReflectableInternal(Class reflectedType) {
200 initialize();
201
202 if (reflectedType == null) {
203 throw new ReflectionException(
204 "Cannot determine if a null reflectedType is reflectable; please supply a reflectedType to the "
205 + getClass().getName() + ".isReflectable method");
206 }
207
208
209 if (isCachingIsReflectableCalls()) {
210 Boolean isReflectable = (Boolean) getReflectableCallCache().get(reflectedType);
211 if (isReflectable != null) {
212 return isReflectable.booleanValue();
213 }
214 }
215
216 try {
217 boolean isReflectable = isReflectableImpl(reflectedType);
218 if (isCachingIsReflectableCalls()) {
219 getReflectableCallCache().put(reflectedType, new Boolean(isReflectable));
220 }
221 return isReflectable;
222 }
223 catch (ReflectionException e) {
224 throw e;
225 }
226 catch (Exception e) {
227 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
228 throw (RuntimeException) e;
229 }
230 throw new ReflectionException("Unable to determine if class '"
231 + reflectedType.getClass().getName() + "' is reflectable", e);
232 }
233 }
234
235 /**
236 * {@link net.sf.morph.reflect.DecoratedReflector#isReflectable(Class)}
237 * @param reflectedType
238 * @param reflectorType
239 * @return
240 * @throws ReflectionException
241 */
242 public final boolean isReflectable(Class reflectedType, Class reflectorType) throws ReflectionException {
243 if (isPerformingLogging() && log.isTraceEnabled()) {
244 log.trace("Testing if "
245 + ObjectUtils.getObjectDescription(reflectedType)
246 + " can be reflected with a "
247 + ObjectUtils.getObjectDescription(reflectorType));
248 }
249
250 if (reflectedType == null) {
251 throw new ReflectionException(
252 "Cannot determine if a null reflectedType is reflectable; please supply a reflectedType to the "
253 + getClass().getName() + ".isReflectable method");
254 }
255 if (reflectorType != null &&
256 !Reflector.class.isAssignableFrom(reflectorType)) {
257 throw new ReflectionException("The reflectorType you specified, "
258 + ObjectUtils.getObjectDescription(reflectorType)
259 + ", is invalid. It must be a child of "
260 + ObjectUtils.getObjectDescription(Reflector.class));
261 }
262
263 try {
264 return reflectorType == null
265 ? isReflectableImpl(reflectedType) : isReflectableImpl(reflectedType, reflectorType);
266 }
267 catch (ReflectionException e) {
268 throw e;
269 }
270 catch (Exception e) {
271 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
272 throw (RuntimeException) e;
273 }
274 throw new ReflectionException("Unable to determine if reflectedType '"
275 + reflectedType.getClass().getName() + "' is reflectable", e);
276 }
277 }
278
279 /**
280 * Implementation of {@link BaseReflector#isReflectable(Class, Class)}.
281 */
282 protected boolean isReflectableImpl(Class reflectedType, Class reflectorType) throws Exception {
283 throw new UnsupportedOperationException();
284 }
285
286 /**
287 * Throws an exception if a the given object is not reflectable by this
288 * reflector. Called before executing each method in this class to ensure
289 * the reflector is being used properly.
290 *
291 * @param object
292 * the object to test
293 * @throws ReflectionException
294 * if the given object is not reflectable by this reflector
295 */
296 protected void checkIsReflectable(Object object) throws ReflectionException {
297 if (!isReflectableInternal(object.getClass())) {
298 throw new ReflectionException("Cannot reflect object "
299 + ObjectUtils.getObjectDescription(object) + " using reflector "
300 + ObjectUtils.getObjectDescription(this));
301 }
302 }
303
304
305
306 /**
307 * {@link net.sf.morph.reflect.InstantiatingReflector#newInstance(Class, Object)}
308 * @param clazz
309 * @param parameters
310 */
311 public final Object newInstance(Class clazz, Object parameters) {
312 if (clazz == null) {
313 throw new ReflectionException(
314 "You must specify the class for which a new instance is to be created");
315 }
316 if (!isReflectableInternal(clazz)) {
317 throw new ReflectionException(
318 ObjectUtils.getObjectDescription(clazz)
319 + " is not reflectable by reflector "
320 + ObjectUtils.getObjectDescription(this));
321 }
322 if (isPerformingLogging() && log.isTraceEnabled()) {
323 log.trace("Creating new instance of '" + ObjectUtils.getObjectDescription(clazz) + "(parameters " + ObjectUtils.getObjectDescription(parameters) + ")");
324 }
325
326 try {
327 Object result = newInstanceImpl(clazz, parameters);
328 if (!clazz.isInstance(result)) {
329 throw new ReflectionException(ObjectUtils.getObjectDescription(result) + " is not an instance of " + clazz);
330 }
331 return result;
332 }
333 catch (ReflectionException e) {
334 throw e;
335 }
336 catch (Exception e) {
337 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
338 throw (RuntimeException) e;
339 }
340 throw new ReflectionException("Unable to create new instance of "
341 + ObjectUtils.getObjectDescription(clazz) + "(parameters " + ObjectUtils.getObjectDescription(parameters) + ")", e);
342 }
343 }
344
345 /**
346 * This method will be removed in a subsequent release of Morph. Left in-place to flag subclasses that require modification.
347 *
348 * @deprecated Use {@link #newInstanceImpl(Class, Object)} instead. Calls to this method will fail with an {@link UnsupportedOperationException}
349 */
350 protected final Object newInstanceImpl(Class clazz) throws Exception {
351 throw new UnsupportedOperationException("Deprecated method - use BaseReflector.newInstanceImpl(Class, Object) instead");
352 }
353
354 /**
355 * Implementation of
356 * {@link net.sf.morph.reflect.InstantiatingReflector#newInstance(Class, Object)}.
357 * Default implementation returns a new instance of the given class by
358 * calling {@link Class#newInstance())}.
359 */
360 protected Object newInstanceImpl(Class clazz, Object parameters) throws Exception {
361 if (isPerformingLogging() && log.isTraceEnabled()) {
362 log.trace("Creating new instance of "
363 + ObjectUtils.getObjectDescription(clazz));
364 }
365 return clazz.newInstance();
366 }
367
368
369
370 /**
371 * {@link net.sf.morph.reflect.BeanReflector#getPropertyNames(Object)}
372 * @param bean
373 * @return
374 * @throws ReflectionException
375 */
376 public final String[] getPropertyNames(Object bean)
377 throws ReflectionException {
378
379 if (bean == null) {
380 throw new ReflectionException(
381 "Cannot determine the properties of a null bean. Please supply a bean to the "
382 + getClass().getName() + ".getPropertyNames method");
383 }
384 checkIsReflectable(bean);
385
386 try {
387 String[] propertyNames = getPropertyNamesImpl(bean);
388 if (isPerformingLogging() && log.isTraceEnabled()) {
389 log.trace("Properties of bean "
390 + ObjectUtils.getObjectDescription(bean) + " are "
391 + StringUtils.englishJoin(propertyNames));
392 }
393 return propertyNames;
394 }
395 catch (ReflectionException e) {
396 throw e;
397 }
398 catch (Exception e) {
399 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
400 throw (RuntimeException) e;
401 }
402 throw new ReflectionException(
403 "Unable to get property names for bean "
404 + ObjectUtils.getObjectDescription(bean), e);
405 }
406 }
407
408 /**
409 * Implementation of {@link BeanReflector#getPropertyNames(Object)}.
410 * Implementation automatically provided for
411 * IndexedContainerReflectors. For other reflectors, throws an
412 * UnsupportedOperationException.
413 */
414 protected String[] getPropertyNamesImpl(Object bean) throws Exception {
415 if (this instanceof IndexedContainerReflector) {
416
417 int size = ((IndexedContainerReflector) this).getSize(bean);
418 String[] propertyNames = new String[size];
419 for (int i = 0; i < size; i++) {
420 propertyNames[i] = Integer.toString(i);
421 }
422 return propertyNames;
423 }
424 throw new UnsupportedOperationException();
425 }
426
427 /**
428 * {@link BeanReflector#isReadable(Object, String)}
429 * @param bean
430 * @param propertyName
431 * @return boolean
432 * @throws ReflectionException
433 */
434 public final boolean isReadable(Object bean, String propertyName)
435 throws ReflectionException {
436
437 if (bean == null && ObjectUtils.isEmpty(propertyName)) {
438 throw new ReflectionException(
439 "Please supply non-null arguments to the "
440 + getClass().getName() + ".isReadable method");
441 }
442 if (bean == null) {
443 throw new ReflectionException("Cannot determine if property '"
444 + propertyName + "' is readable since no bean was specified");
445 }
446 if (ObjectUtils.isEmpty(propertyName)) {
447 throw new ReflectionException(
448 "Please supply a property name to test for readability for bean "
449 + ObjectUtils.getObjectDescription(bean));
450 }
451 checkIsReflectable(bean);
452
453 boolean isReadable;
454 try {
455 if (this instanceof SizableReflector &&
456 propertyName.equals(SizableReflector.IMPLICIT_PROPERTY_SIZE)) {
457 isReadable = true;
458 }
459 else if (this instanceof BeanReflector &&
460 (propertyName.equals(BeanReflector.IMPLICIT_PROPERTY_CLASS) ||
461 propertyName.equals(BeanReflector.IMPLICIT_PROPERTY_PROPERTY_NAMES) ||
462 propertyName.equals(BeanReflector.IMPLICIT_PROPERTY_THIS))) {
463 isReadable = true;
464 }
465 else {
466 isReadable = isReadableImpl(bean, propertyName);
467 }
468
469 if (isPerformingLogging() && log.isTraceEnabled()) {
470 log.trace("Property '" + propertyName + "' is"
471 + (isReadable ? " " : " not ") + "readable in bean "
472 + ObjectUtils.getObjectDescription(bean));
473 }
474 return isReadable;
475 }
476 catch (ReflectionException e) {
477 throw e;
478 }
479 catch (Exception e) {
480 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
481 throw (RuntimeException) e;
482 }
483 throw new ReflectionException("Unable determine if property '"
484 + propertyName + "' is readable in bean "
485 + ObjectUtils.getObjectDescription(bean), e);
486 }
487 }
488
489 /**
490 * Implementation of {@link BeanReflector#isReadable(Object, String)}.
491 * Default implementation assumes that all properties of the bean specified
492 * by {@link BeanReflector#getPropertyNames(Object)} are readable.
493 */
494 protected boolean isReadableImpl(Object bean, String propertyName)
495 throws Exception {
496 return this instanceof IndexedContainerReflector ? isValidIndex(bean, propertyName)
497 : ContainerUtils.contains(getPropertyNames(bean), propertyName);
498 }
499
500 /**
501 * {@link BeanReflector#isWriteable(Object, String)}
502 * @param bean
503 * @param propertyName
504 * @return boolean
505 */
506 public final boolean isWriteable(Object bean, String propertyName) {
507
508 if (bean == null && ObjectUtils.isEmpty(propertyName)) {
509 throw new ReflectionException(
510 "Please supply non-null arguments to the "
511 + getClass().getName() + ".isWriteable method");
512 }
513 if (bean == null) {
514 throw new ReflectionException("Cannot determine if property '"
515 + propertyName + "' is writeable since no bean was specified");
516 }
517 if (ObjectUtils.isEmpty(propertyName)) {
518 throw new ReflectionException(
519 "Please supply a property name to test for writeability for bean "
520 + ObjectUtils.getObjectDescription(bean));
521 }
522 checkIsReflectable(bean);
523
524 try {
525 Boolean isWriteable = null;
526 Exception exception = null;
527
528 try {
529 isWriteable = new Boolean(isWriteableImpl(bean, propertyName));
530 }
531 catch (Exception e) {
532 exception = e;
533 }
534
535 if (isWriteable == null) {
536 if (this instanceof SizableReflector
537 && propertyName.equals(SizableReflector.IMPLICIT_PROPERTY_SIZE)) {
538 return false;
539 }
540 if (this instanceof BeanReflector
541 && (BeanReflector.IMPLICIT_PROPERTY_CLASS.equals(propertyName)
542 || BeanReflector.IMPLICIT_PROPERTY_PROPERTY_NAMES
543 .equals(propertyName) || BeanReflector.IMPLICIT_PROPERTY_THIS
544 .equals(propertyName))) {
545 return false;
546 }
547 }
548 if (exception == null) {
549 if (isPerformingLogging() && log.isTraceEnabled()) {
550 log.trace("Property '" + propertyName + "' is"
551 + (isWriteable.booleanValue() ? " " : " not ")
552 + "writeable in bean "
553 + ObjectUtils.getObjectDescription(bean));
554 }
555 return isWriteable.booleanValue();
556 }
557 throw exception;
558 }
559 catch (ReflectionException e) {
560 throw e;
561 }
562 catch (Exception e) {
563 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
564 throw (RuntimeException) e;
565 }
566 throw new ReflectionException("Unable determine if property '"
567 + propertyName + "' is writeable in bean "
568 + ObjectUtils.getObjectDescription(bean), e);
569 }
570 }
571
572 /**
573 * Implementation of {@link BeanReflector#isWriteable(Object, String)}.
574 * Default implementation assumes that all readable properties are also
575 * writeable. One exception to this is when this reflector is an
576 * IndexedContainerReflector but not a MutableIndexedContainerReflector, in
577 * which case no properties are considered writeable.
578 */
579 protected boolean isWriteableImpl(Object bean, String propertyName)
580 throws Exception {
581 return (!(this instanceof IndexedContainerReflector)
582 || this instanceof MutableIndexedContainerReflector)
583 && isReadableImpl(bean, propertyName);
584 }
585
586 /**
587 * Learn whether <code>propertyName</code> denotes a valid numeric property index for <code>bean</code>.
588 * @param bean
589 * @param propertyName
590 * @return boolean
591 * @throws ReflectionException
592 */
593 protected boolean isValidIndex(Object bean, String propertyName) throws ReflectionException {
594 try {
595 int index = Integer.parseInt(propertyName);
596 return index >= 0 && index < getSize(bean);
597 }
598 catch (NumberFormatException e) {
599 return false;
600 }
601 }
602
603 /**
604 * {@link BeanReflector#set(Object, String, Object)}
605 * @param bean
606 * @param propertyName
607 * @param propertyValue
608 * @throws ReflectionException
609 */
610 public final void set(Object bean, String propertyName, Object propertyValue)
611 throws ReflectionException {
612 if (isPerformingLogging() && log.isTraceEnabled()) {
613 log.trace("Setting property '" + propertyName + "' of bean "
614 + ObjectUtils.getObjectDescription(bean) + " to "
615 + ObjectUtils.getObjectDescription(propertyValue));
616 }
617
618 if (bean == null && ObjectUtils.isEmpty(propertyName)) {
619 throw new ReflectionException(
620 "Please supply non-null arguments to the "
621 + getClass().getName() + ".set method");
622 }
623 if (bean == null) {
624 throw new ReflectionException("Cannot retrieve property '"
625 + propertyName + "' since no bean was specified");
626 }
627 if (ObjectUtils.isEmpty(propertyName)) {
628 throw new ReflectionException(
629 "Please supply a property name to retrieve from bean "
630 + ObjectUtils.getObjectDescription(bean));
631 }
632 checkIsReflectable(bean);
633
634 try {
635 Object currentValue = get(bean, propertyName);
636 if (propertyValue == currentValue
637 || (ClassUtils.isImmutable(getType(bean, propertyName)) && ObjectUtils
638 .equals(propertyValue, currentValue))) {
639
640 if (BeanReflector.IMPLICIT_PROPERTY_THIS.equals(propertyName)
641 || ContainerUtils.contains(getPropertyNames(bean), propertyName)) {
642 return;
643 }
644 }
645 } catch (ReflectionException e) {
646
647 if (isPerformingLogging() && log.isTraceEnabled()) {
648 log.trace("Ignoring exception encountered getting property " + propertyName
649 + " for object " + ObjectUtils.getObjectDescription(bean));
650 }
651 }
652
653 if (!isWriteable(bean, propertyName)) {
654 throw new ReflectionException("The property '" + propertyName
655 + "' is not writeable in bean "
656 + ObjectUtils.getObjectDescription(bean));
657 }
658
659 try {
660 setImpl(bean, propertyName, propertyValue);
661 }
662 catch (ReflectionException e) {
663 throw e;
664 }
665 catch (Exception e) {
666 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
667 throw (RuntimeException) e;
668 }
669 throw new ReflectionException("Unable to set property '"
670 + propertyName + "' of bean "
671 + ObjectUtils.getObjectDescription(bean) + " to "
672 + propertyValue, e);
673 }
674 }
675
676 /**
677 * Implementation of {@link BeanReflector#set(Object, String, Object)}.
678 * Implementation automatically provided for
679 * MutableIndexedContainerReflectors. For other reflectors, throws an
680 * UnsupportedOperationException.
681 */
682 protected void setImpl(Object bean, String propertyName,
683 Object value) throws Exception {
684 if (!(this instanceof MutableIndexedContainerReflector)) {
685 throw new UnsupportedOperationException();
686 }
687 ((MutableIndexedContainerReflector) this).set(bean, Integer.parseInt(propertyName), value);
688 }
689
690 /**
691 * {@link BeanReflector#get(Object, String)}
692 * @param bean
693 * @param propertyName
694 * @return
695 * @throws ReflectionException
696 */
697 public final Object get(Object bean, String propertyName)
698 throws ReflectionException {
699 if (bean == null && ObjectUtils.isEmpty(propertyName)) {
700 throw new ReflectionException(
701 "Please supply non-null arguments to the "
702 + getClass().getName() + ".get method");
703 }
704 if (bean == null) {
705 throw new ReflectionException("Cannot retrieve property '"
706 + propertyName + "' from a null object");
707 }
708 if (ObjectUtils.isEmpty(propertyName)) {
709 throw new ReflectionException(
710 "Please supply a property name to retrieve from bean "
711 + ObjectUtils.getObjectDescription(bean));
712 }
713 checkIsReflectable(bean);
714
715 try {
716 Object value = null;
717 Exception exception = null;
718
719 try {
720 if (!isReadable(bean, propertyName)) {
721 throw new ReflectionException("The property '"
722 + propertyName + "' is not readable in bean "
723 + ObjectUtils.getObjectDescription(bean)
724 + " using reflector "
725 + ObjectUtils.getObjectDescription(this));
726 }
727 value = getImpl(bean, propertyName);
728 }
729
730 catch (Exception e) {
731 exception = e;
732 }
733
734
735
736 if (value == null &&
737 this instanceof BeanReflector &&
738 propertyName.equals(BeanReflector.IMPLICIT_PROPERTY_CLASS)) {
739 return bean.getClass();
740 }
741 if (value == null &&
742 this instanceof BeanReflector &&
743 propertyName.equals(BeanReflector.IMPLICIT_PROPERTY_PROPERTY_NAMES)) {
744 return getPropertyNames(bean);
745 }
746 if (value == null &&
747 this instanceof BeanReflector &&
748 propertyName.equals(BeanReflector.IMPLICIT_PROPERTY_THIS)) {
749 return bean;
750 }
751 if (value == null &&
752 this instanceof SizableReflector &&
753 propertyName.equals(SizableReflector.IMPLICIT_PROPERTY_SIZE)) {
754 return new Integer(getSize(bean));
755 }
756
757
758
759 if (exception == null) {
760 if (isPerformingLogging() && log.isTraceEnabled()) {
761 log.trace("Property '" + propertyName + "' has value "
762 + ObjectUtils.getObjectDescription(value)
763 + " in bean "
764 + ObjectUtils.getObjectDescription(bean));
765 }
766 return value;
767 }
768 throw exception;
769 }
770 catch (ReflectionException e) {
771 throw e;
772 }
773 catch (Exception e) {
774 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
775 throw (RuntimeException) e;
776 }
777 throw new ReflectionException("Unable to retrieve property '"
778 + propertyName + "' from bean "
779 + ObjectUtils.getObjectDescription(bean), e);
780 }
781 }
782
783 /**
784 * Implementation of {@link BeanReflector#get(Object, String)}.
785 * Implementation automatically provided for
786 * IndexedContainerReflectors. For other reflectors, throws an
787 * UnsupportedOperationException.
788 */
789 protected Object getImpl(Object bean, String propertyName)
790 throws Exception {
791 if (!(this instanceof IndexedContainerReflector)) {
792 throw new UnsupportedOperationException();
793 }
794 return BeanReflector.IMPLICIT_PROPERTY_CLASS.equals(propertyName)
795 || BeanReflector.IMPLICIT_PROPERTY_SIZE.equals(propertyName)
796 || BeanReflector.IMPLICIT_PROPERTY_THIS.equals(propertyName) ? null
797 : get(bean, Integer.parseInt(propertyName));
798 }
799
800 /**
801 * {@link BeanReflector#getType(Object, String)}
802 * @param bean
803 * @param propertyName
804 * @return
805 * @throws ReflectionException
806 */
807 public final Class getType(Object bean, String propertyName)
808 throws ReflectionException {
809
810 if (bean == null && ObjectUtils.isEmpty(propertyName)) {
811 throw new ReflectionException(
812 "Please supply non-null arguments to the "
813 + getClass().getName() + ".getType method");
814 }
815 if (bean == null) {
816 throw new ReflectionException("Cannot determine type of property '"
817 + propertyName + "' since no bean was specified");
818 }
819 if (ObjectUtils.isEmpty(propertyName)) {
820 throw new ReflectionException(
821 "Please supply a property name of bean "
822 + ObjectUtils.getObjectDescription(bean)
823 + " for which you would like to know the type");
824 }
825 checkIsReflectable(bean);
826
827 boolean hasPropertyDefined = ContainerUtils.contains(
828 getPropertyNames(bean), propertyName);
829 if (isStrictlyTyped() &&
830 !hasPropertyDefined &&
831 !SizableReflector.IMPLICIT_PROPERTY_SIZE.equals(propertyName) &&
832 !BeanReflector.IMPLICIT_PROPERTY_CLASS.equals(propertyName) &&
833 !BeanReflector.IMPLICIT_PROPERTY_THIS.equals(propertyName) &&
834 !BeanReflector.IMPLICIT_PROPERTY_PROPERTY_NAMES.equals(propertyName)) {
835 throw new ReflectionException("Cannot determine type of property '"
836 + propertyName + "' because it is not a property of "
837 + ObjectUtils.getObjectDescription(bean));
838 }
839
840
841 Class type = null;
842 if (!hasPropertyDefined) {
843 if (propertyName.equals(BeanReflector.IMPLICIT_PROPERTY_CLASS)) {
844 type = Class.class;
845 }
846 else if (propertyName.equals(BeanReflector.IMPLICIT_PROPERTY_PROPERTY_NAMES)) {
847 type = String[].class;
848 }
849 else if (propertyName.equals(BeanReflector.IMPLICIT_PROPERTY_THIS)) {
850 type = ClassUtils.getClass(bean);
851 }
852 else if (propertyName.equals(SizableReflector.IMPLICIT_PROPERTY_SIZE)) {
853 type = Integer.TYPE;
854 }
855 }
856
857
858
859 if (type == null) {
860 try {
861 type = getTypeImpl(bean, propertyName);
862 }
863 catch (ReflectionException e) {
864 throw e;
865 }
866 catch (Exception e) {
867 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
868 throw (RuntimeException) e;
869 }
870 throw new ReflectionException(
871 "Unable to determine type of property '" + propertyName
872 + "' for bean " + ObjectUtils.getObjectDescription(bean),
873 e);
874 }
875 }
876
877 if (isPerformingLogging() && log.isTraceEnabled()) {
878 log.trace("Property '" + propertyName + "' has type "
879 + ObjectUtils.getObjectDescription(type) + " in bean "
880 + ObjectUtils.getObjectDescription(bean));
881 }
882
883 return type;
884 }
885
886 /**
887 * Implementation of {@link BeanReflector#getType(Object, String)}.
888 * Default implementation provided. For IndexedContainerReflectors,
889 * returns the type by calling
890 * {@link net.sf.morph.reflect.ContainerReflector#getContainedType(Class)}.
891 * For other reflectors, checks the type of the property by calling
892 * {@link BaseReflector#get(Object, String)}.
893 */
894 protected Class getTypeImpl(Object bean, String propertyName)
895 throws Exception {
896
897 if (this instanceof IndexedContainerReflector) {
898 if (isValidIndex(bean, propertyName)) {
899 return ((IndexedContainerReflector) this).getContainedType(
900 bean.getClass());
901 }
902 throw new ReflectionException("'" + propertyName
903 + "' is not a valid index in the container "
904 + ObjectUtils.getObjectDescription(bean));
905 }
906 return ClassUtils.getClass(getImpl(bean, propertyName));
907 }
908
909
910
911 /**
912 * {@link ContainerReflector#getContainedType(Class)}
913 * @param clazz
914 * @return
915 * @throws ReflectionException
916 */
917 public final Class getContainedType(Class clazz) throws ReflectionException {
918
919 if (clazz == null) {
920 throw new ReflectionException(
921 "Can't determine the type of a null object");
922 }
923
924 try {
925 Class type = getContainedTypeImpl(clazz);
926 if (isPerformingLogging() && log.isTraceEnabled()) {
927 log.trace("Contained type is "
928 + ObjectUtils.getObjectDescription(type)
929 + " for instances of "
930 + ObjectUtils.getObjectDescription(clazz));
931 }
932 return type;
933 }
934 catch (ReflectionException e) {
935 throw e;
936 }
937 catch (Exception e) {
938 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
939 throw (RuntimeException) e;
940 }
941 throw new ReflectionException(
942 "Could not determine the type of objects contained in the container of "
943 + ObjectUtils.getObjectDescription(clazz));
944 }
945
946 }
947
948 /**
949 * Implementation of {@link net.sf.morph.reflect.ContainerReflector#getContainedType(Class)}.
950 */
951 protected Class getContainedTypeImpl(Class clazz) throws Exception {
952 throw new UnsupportedOperationException();
953 }
954
955 /**
956 * {@link ContainerReflector#getIterator(Object)}
957 * @param container
958 * @return Iterator
959 * @throws ReflectionException
960 */
961 public final Iterator getIterator(Object container)
962 throws ReflectionException {
963 if (isPerformingLogging() && log.isTraceEnabled()) {
964 log.trace("Retrieving iterator for "
965 + ObjectUtils.getObjectDescription(container));
966 }
967 if (container == null) {
968 throw new ReflectionException(
969 "Cannot iterate through the contents of null container");
970 }
971 checkIsReflectable(container);
972
973 try {
974 return getIteratorImpl(container);
975 }
976 catch (ReflectionException e) {
977 throw e;
978 }
979 catch (Exception e) {
980 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
981 throw (RuntimeException) e;
982 }
983 throw new ReflectionException("Could not retrieve iterator for "
984 + ObjectUtils.getObjectDescription(container), e);
985 }
986 }
987
988 /**
989 * Implementation of {@link net.sf.morph.reflect.ContainerReflector#getIterator(Object)}.
990 */
991 protected Iterator getIteratorImpl(Object container)
992 throws Exception {
993 throw new UnsupportedOperationException();
994 }
995
996 /**
997 * Validate <code>index</code> into <code>container</code>.
998 * @param container
999 * @param index
1000 * @throws ReflectionException
1001 */
1002 protected void checkIndex(Object container, int index)
1003 throws ReflectionException {
1004 if (index < 0) {
1005 throw new ReflectionException("Must specify a non-negative index");
1006 }
1007 if (index > getSize(container) - 1) {
1008 throw new ReflectionException("Cannot access element " + index
1009 + " because the container object has only "
1010 + getSize(container) + " elements");
1011 }
1012 }
1013
1014
1015
1016 /**
1017 * {@link SizableReflector#getSize(Object)}
1018 * @param container
1019 * @return
1020 * @throws ReflectionException
1021 */
1022 public final int getSize(Object container) throws ReflectionException {
1023 if (isPerformingLogging() && log.isTraceEnabled()) {
1024 log.trace("Retrieving size of "
1025 + ObjectUtils.getObjectDescription(container));
1026 }
1027
1028 if (container == null) {
1029 throw new ReflectionException(
1030 "Cannot determine the size of a null object");
1031 }
1032 checkIsReflectable(container);
1033
1034 try {
1035 return getSizeImpl(container);
1036 }
1037 catch (ReflectionException e) {
1038 throw e;
1039 }
1040 catch (Exception e) {
1041 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
1042 throw (RuntimeException) e;
1043 }
1044 throw new ReflectionException("Could not determine the size of "
1045 + ObjectUtils.getObjectDescription(container) + " object", e);
1046 }
1047 }
1048
1049 /**
1050 * Implementation of {@link SizableReflector#getSize(Object)}.
1051 */
1052 protected int getSizeImpl(Object container) throws Exception {
1053 if (this instanceof BeanReflector) {
1054 return getPropertyNamesImpl(container).length;
1055 }
1056 throw new UnsupportedOperationException();
1057 }
1058
1059
1060
1061 /**
1062 * {@link IndexedContainerReflector#get(Object, int)}
1063 * @param container
1064 * @param index
1065 * @return
1066 * @throws ReflectionException
1067 */
1068 public final Object get(Object container, int index)
1069 throws ReflectionException {
1070
1071 if (container == null) {
1072 throw new ReflectionException(
1073 "Can't retrieve values from a null object");
1074 }
1075 checkIndex(container, index);
1076 checkIsReflectable(container);
1077
1078 try {
1079 Object value = getImpl(container, index);
1080
1081 if (isPerformingLogging() && log.isTraceEnabled()) {
1082 log.trace("Item at index " + index + " has value "
1083 + ObjectUtils.getObjectDescription(value) + " in container "
1084 + ObjectUtils.getObjectDescription(container));
1085 }
1086
1087 return value;
1088 }
1089 catch (ReflectionException e) {
1090 throw e;
1091 }
1092 catch (Exception e) {
1093 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
1094 throw (RuntimeException) e;
1095 }
1096 throw new ReflectionException("Could not retrieve element " + index
1097 + " from " + ObjectUtils.getObjectDescription(container), e);
1098 }
1099 }
1100
1101 /**
1102 * Implementation of {@link IndexedContainerReflector#get(Object, int)}.
1103 */
1104 protected Object getImpl(Object container, int index) throws Exception {
1105 throw new UnsupportedOperationException();
1106 }
1107
1108
1109
1110 /**
1111 * {@link MutableIndexedContainerReflector#set(Object, int, Object)}
1112 * @param container
1113 * @param index
1114 * @param propertyValue
1115 * @return
1116 * @throws ReflectionException
1117 */
1118 public final Object set(Object container, int index, Object propertyValue)
1119 throws ReflectionException {
1120
1121 if (isPerformingLogging() && log.isTraceEnabled()) {
1122 log.trace("Setting item at index " + index + " for object "
1123 + ObjectUtils.getObjectDescription(container) + " to value "
1124 + ObjectUtils.getObjectDescription(propertyValue));
1125 }
1126
1127 if (container == null) {
1128 throw new ReflectionException("Can't set values of a null object");
1129 }
1130 checkIndex(container, index);
1131 checkIsReflectable(container);
1132
1133 try {
1134 Object currentValue = get(container, index);
1135 if (propertyValue == currentValue
1136 || (ClassUtils.isImmutable(getContainedType(container.getClass())) && ObjectUtils
1137 .equals(propertyValue, currentValue))) {
1138 return currentValue;
1139 }
1140 } catch (ReflectionException e) {
1141
1142 if (isPerformingLogging() && log.isTraceEnabled()) {
1143 log.trace("Ignoring exception encountered getting item at index " + index
1144 + " for object " + ObjectUtils.getObjectDescription(container));
1145 }
1146 }
1147
1148 try {
1149 return setImpl(container, index, propertyValue);
1150 }
1151 catch (ReflectionException e) {
1152 throw e;
1153 }
1154 catch (Exception e) {
1155 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
1156 throw (RuntimeException) e;
1157 }
1158 throw new ReflectionException(
1159 "Could not set element " + index + " of "
1160 + ObjectUtils.getObjectDescription(container) + " to value "
1161 + ObjectUtils.getObjectDescription(propertyValue), e);
1162 }
1163 }
1164
1165 /**
1166 * Implementation of {@link MutableIndexedContainerReflector#set(Object, int, Object)}.
1167 */
1168 protected Object setImpl(Object container, int index, Object propertyValue)
1169 throws Exception {
1170 throw new UnsupportedOperationException();
1171 }
1172
1173
1174
1175 /**
1176 * {@link GrowableContainerReflector#add(Object, Object)}
1177 * @param container
1178 * @param value
1179 * @return
1180 * @throws ReflectionException
1181 */
1182 public final boolean add(Object container, Object value)
1183 throws ReflectionException {
1184
1185 if (isPerformingLogging() && log.isTraceEnabled()) {
1186 log.trace("Adding item " + ObjectUtils.getObjectDescription(value)
1187 + " to container " + ObjectUtils.getObjectDescription(container));
1188 }
1189
1190 if (container == null) {
1191 throw new ReflectionException("Can't add values of a null object");
1192 }
1193 checkIsReflectable(container);
1194
1195 try {
1196 return addImpl(container, value);
1197 }
1198 catch (ReflectionException e) {
1199 throw e;
1200 }
1201 catch (Exception e) {
1202 if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
1203 throw (RuntimeException) e;
1204 }
1205 throw new ReflectionException("Could not add item "
1206 + ObjectUtils.getObjectDescription(value) + " to container "
1207 + ObjectUtils.getObjectDescription(container), e);
1208 }
1209 }
1210
1211 /**
1212 * Implementation of {@link net.sf.morph.wrap.GrowableContainer#add(Object)}.
1213 */
1214 protected boolean addImpl(Object container, Object value) throws Exception {
1215 throw new UnsupportedOperationException();
1216 }
1217
1218
1219
1220 /**
1221 * Indicates whether this reflector is writing log messages
1222 */
1223 protected boolean isPerformingLogging() {
1224 return true;
1225 }
1226
1227 /**
1228 * Indicates whether this reflector is strictly typed. If a reflector is
1229 * strictly typed, the {@link #getType(Object, String)} method will throw
1230 * an exception if the requested property name is not a valid property
1231 * of the object. Default implementation returns <code>false</code>.
1232 * @return <code>false</code>.
1233 */
1234 public boolean isStrictlyTyped() {
1235 return false;
1236 }
1237
1238 /**
1239 * Learn whether this Reflector is initialized.
1240 * @return boolean
1241 */
1242 protected boolean isInitialized() {
1243 return initialized;
1244 }
1245
1246 /**
1247 * Set the initialization status of this Reflector.
1248 * @param initialized
1249 */
1250 protected void setInitialized(boolean initialized) {
1251 this.initialized = initialized;
1252 }
1253
1254 /**
1255 * Learn whether this Reflector is caching {@link #isReflectable(Class)} calls.
1256 * Default <code>true</code>.
1257 * @return boolean
1258 */
1259 public boolean isCachingIsReflectableCalls() {
1260 return cachingIsReflectableCalls;
1261 }
1262
1263 /**
1264 * Set whether this Reflector is caching {@link #isReflectable(Class)} calls.
1265 * @param cachingIsReflectableCalls
1266 */
1267 public void setCachingIsReflectableCalls(boolean cachingIsReflectableCalls) {
1268 this.cachingIsReflectableCalls = cachingIsReflectableCalls;
1269 }
1270
1271 /**
1272 * Get the {@link #isReflectable(Class)} call cache.
1273 * @return Map
1274 */
1275 protected Map getReflectableCallCache() {
1276 return reflectableCallCache;
1277 }
1278
1279 /**
1280 * Set the {@link #isReflectable(Class)} call cache.
1281 * @return Map
1282 */
1283 protected void setReflectableCallCache(Map reflectableCallCache) {
1284 this.reflectableCallCache = reflectableCallCache;
1285 }
1286
1287 /**
1288 * Indicates whether runtime exceptions should be wrapped as
1289 * {@link ReflectionException}s. By default, this method returns
1290 * <code>true</code>.
1291 *
1292 * <p>
1293 * Simple Reflectors in Morph will usually set this value to <code>true</code>
1294 * so that they throw ReflectionExceptions if problems occur. User-written
1295 * Reflectors are encouraged to return <code>false</code> so that runtime
1296 * exceptions are not wrapped. This way, problems accessing data will be
1297 * expressed by the native API of a user's domain objects and avoid the need to
1298 * catch Morph-specific exceptions (assuming the use of runtime exceptions in said
1299 * domain objects).
1300 *
1301 * @return <code>true</code>
1302 * @since Morph 1.1
1303 */
1304 protected boolean isWrappingRuntimeExceptions() {
1305 return true;
1306 }
1307
1308 /**
1309 * Get the reflectorName.
1310 * @return String
1311 * @since Morph 1.1
1312 */
1313 public String getReflectorName() {
1314 return reflectorName;
1315 }
1316
1317 /**
1318 * Set the reflectorName.
1319 * @param reflectorName the String to set
1320 * @since Morph 1.1
1321 */
1322 public void setReflectorName(String reflectorName) {
1323 if (initialized && ObjectUtils.equals(reflectorName, this.reflectorName)) {
1324 return;
1325 }
1326 this.reflectorName = reflectorName;
1327 log = reflectorName == null ? LogFactory.getLog(getClass()) : LogFactory.getLog(reflectorName);
1328 }
1329
1330 /**
1331 * {@inheritDoc}
1332 * @since Morph 1.1
1333 */
1334 public String toString() {
1335 String name = getReflectorName();
1336 return name == null ? super.toString() : name;
1337 }
1338 }