View Javadoc

1   /*
2    * Copyright 2004-2005, 2007-2008 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * 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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
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  // fields
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  //	 initialization
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 // basic reflector, decoratedreflector
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 		// try to pull the isReflectable information from the cache
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 // instantiating reflector
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 // bean reflectors
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 			// create an array of all the valid indexes for the container
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 { //don't bother setting if already same or immutable and equal
635 			Object currentValue = get(bean, propertyName);
636 			if (propertyValue == currentValue
637 					|| (ClassUtils.isImmutable(getType(bean, propertyName)) && ObjectUtils
638 							.equals(propertyValue, currentValue))) {
639 				//ignore "this"; else if the property doesn't already exist, this is probably a MapReflector and we want to add anyway
640 				if (BeanReflector.IMPLICIT_PROPERTY_THIS.equals(propertyName)
641 						|| ContainerUtils.contains(getPropertyNames(bean), propertyName)) {
642 					return;
643 				}
644 			}
645 		} catch (ReflectionException e) {
646 			//simply ignore, maybe we can set but not get:
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 			// try to retrieve the property
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 			// if an error occurrs, remember it for later
730 			catch (Exception e) {
731 				exception = e;
732 			}
733 
734 			// if we couldn't get the property and it's a implicit property,
735 			// return the value of the implicit property
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 			// if the returned value was null and the property wasn't a implicit
757 			// property, rethrow any exception that was thrown, or if no
758 			// exception was thrown, return the value (which will be null)
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 		// take care of implicit properties, if applicable
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 		// if we aren't retrieving an implicit property, let the subclass handle
858 		// the request
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 // container reflectors
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 // sizable reflectors
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 // indexed container reflectors
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 // mutable indexed container reflectors
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 { //don't bother setting if already same or immutable and equal
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 			//simply ignore, maybe we can set but not get:
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 // growable container reflectors
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 // getters and setters
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 }