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.transform.transformers;
17  
18  import java.lang.reflect.Method;
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.Locale;
22  import java.util.Map;
23  
24  import net.sf.composite.util.CompositeUtils;
25  import net.sf.composite.util.ObjectPair;
26  import net.sf.composite.util.ObjectUtils;
27  import net.sf.morph.Defaults;
28  import net.sf.morph.reflect.InstantiatingReflector;
29  import net.sf.morph.reflect.ReflectionException;
30  import net.sf.morph.reflect.Reflector;
31  import net.sf.morph.transform.Converter;
32  import net.sf.morph.transform.Copier;
33  import net.sf.morph.transform.DecoratedConverter;
34  import net.sf.morph.transform.DecoratedCopier;
35  import net.sf.morph.transform.DecoratedTransformer;
36  import net.sf.morph.transform.ExplicitTransformer;
37  import net.sf.morph.transform.NodeCopier;
38  import net.sf.morph.transform.TransformationException;
39  import net.sf.morph.transform.Transformer;
40  import net.sf.morph.util.ClassUtils;
41  import net.sf.morph.util.TransformerUtils;
42  
43  import org.apache.commons.logging.Log;
44  import org.apache.commons.logging.LogFactory;
45  
46  /**
47   * <p>
48   * Convenient base class for transformers. This base class offers a number of
49   * convenient features, including those listed below.
50   * <ul>
51   * <li>Automatically performs basic argument checking</li>
52   * <li>Automatically does basic logging</li>
53   * <li>Wraps exceptions in
54   * {@link net.sf.morph.transform.TransformationException}s for you</li>
55   * <li>Exposes the convenient
56   * {@link net.sf.morph.transform.DecoratedTransformer}interface, while only
57   * requiring subclasses to implement the methods in
58   * {@link net.sf.morph.transform.Transformer}.</li>
59   * <li>Provides protected methods that assist in the implementation of
60   * transformers which are in turn composed of other transformers.</li>
61   * <li>Optionally caches results of
62   * {@link ExplicitTransformer#isTransformable(Class, Class)}
63   * for better performance. This feature is turned on by default</li>
64   * </ul>
65   * </p>
66   *
67   * @author Matt Sgarlata
68   * @since Nov 26, 2004
69   */
70  public abstract class BaseTransformer implements Transformer, DecoratedTransformer {
71  
72  	private static final String SPRING_LOCALE_CONTEXT_HOLDER_CLASS = "org.springframework.context.i18n.LocaleContextHolder";
73  
74  	private boolean initialized = false;
75  	private boolean cachingIsTransformableCalls = true;
76  	private Transformer nestedTransformer;
77  	private Reflector reflector;
78  	private String transformerName;
79  
80  	private transient Map transformableCallCache;
81  
82  	/** BaseTransformer log object */
83  	protected transient Log log;
84  
85  	/** Source classes */
86  	protected Class[] sourceClasses;
87  
88  	/** Destination classes */
89  	protected Class[] destinationClasses;
90  
91  	/**
92  	 * Create a new BaseTransformer.
93  	 */
94  	protected BaseTransformer() {
95  		setTransformerName(null);
96  	}
97  	
98  // isTransformable
99  
100 	/**
101 	 * Default implementation for
102 	 * {@link Transformer#isTransformable(Class, Class)} that assumes that each
103 	 * source type can be converted into each destination type.
104 	 *
105 	 * @param destinationType
106 	 *            the destination type to test
107 	 * @param sourceType
108 	 *            the source type to test
109 	 * @return whether the destination type is transformable to the source
110 	 *         type
111 	 * @throws TransformationException
112 	 *             if it could not be determined if <code>sourceType</code>
113 	 *             is transformable into <code>destinationType</code>
114 	 */
115 	protected boolean isTransformableImpl(Class destinationType,
116 		Class sourceType) throws Exception {
117 		return TransformerUtils.isImplicitlyTransformable(this, destinationType,
118 				sourceType);
119 	}
120 
121 	/**
122 	 * {@inheritDoc}
123 	 * @see net.sf.morph.transform.ExplicitTransformer#isTransformable(java.lang.Class, java.lang.Class)
124 	 */
125 	public final boolean isTransformable(Class destinationType,
126 		Class sourceType) throws TransformationException {
127 		initialize();
128 
129 		// Note: null source and destination classes are allowed!
130 
131 		// first, try to pull the source and destination from the cache
132 		ObjectPair pair = null;
133 		if (isCachingIsTransformableCalls()) {
134 			pair = new ObjectPair(destinationType, sourceType);
135 			if (getTransformableCallCache().containsKey(pair)) {
136 				Boolean isTransformable = (Boolean)
137 					getTransformableCallCache().get(pair);
138 				return isTransformable.booleanValue();
139 			}
140 		}
141 
142 		try {
143 			boolean isTransformable = isTransformableImpl(destinationType, sourceType);
144 			if (isCachingIsTransformableCalls()) {
145 				getTransformableCallCache().put(pair, new Boolean(isTransformable));
146 			}
147 			return isTransformable;
148 		}
149 		catch (TransformationException e) {
150 			throw e;
151 		}
152 		catch (StackOverflowError e) {
153 			throw new TransformationException(
154 				"Stack overflow detected.  This usually occurs when a transformer implements "
155 					+ ObjectUtils.getObjectDescription(ExplicitTransformer.class)
156 					+ " but does not override the isTransformableImpl method",
157 				e);
158 		}
159 		catch (Exception e) {
160 			if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
161 				throw (RuntimeException) e;
162 			}
163 			throw new TransformationException("Could not determine if "
164 		    		+ sourceType + " is convertible to "
165 		    		+ destinationType, e);
166 		}
167 	}
168 
169 // source and destination classes
170 
171 	/**
172 	 * {@link Transformer#getSourceClasses()} implementation template method.
173 	 * @return Class[]
174 	 * @throws Exception
175 	 */
176 	protected abstract Class[] getSourceClassesImpl() throws Exception;
177 
178 	/**
179 	 * {@link Transformer#getDestinationClasses()} implementation template method.
180 	 * @return Class[]
181 	 * @throws Exception
182 	 */
183 	protected abstract Class[] getDestinationClassesImpl() throws Exception;
184 
185 	/**
186 	 * {@inheritDoc}
187 	 * @see net.sf.morph.transform.Transformer#getSourceClasses()
188 	 */
189 	public final Class[] getSourceClasses() throws TransformationException {
190 		initialize();
191 		return sourceClasses;
192 	}
193 
194 	/**
195 	 * {@inheritDoc}
196 	 * @see net.sf.morph.transform.Transformer#getDestinationClasses()
197 	 */
198 	public final Class[] getDestinationClasses() throws TransformationException {
199 		initialize();
200 		return destinationClasses;
201 	}
202 
203 	/**
204 	 * Configures the <code>sourceClasses</code> property of this transformer.
205 	 * Note that this method should be called before the transformer is used.
206 	 * Otherwise, if another thread is in the middle of transforming an object
207 	 * graph and this method is called, the behavior of the transformer can
208 	 * change partway through the transformation.
209 	 *
210 	 * @param sourceClasses the new <code>sourceClasses</code> for this transformer
211 	 */
212 	protected synchronized void setSourceClasses(Class[] sourceClasses) {
213 		setInitialized(false);
214 		this.sourceClasses = sourceClasses;
215 	}
216 
217 	/**
218 	 * Configures the <code>destinationClasses</code> property of this
219 	 * transformer. Note that this method should be called before the
220 	 * transformer is used. Otherwise, if another thread is in the middle of
221 	 * transforming an object graph and this method is called, the behavior of
222 	 * the transformer can change partway through the transformation.
223 	 *
224 	 * @param destinationClasses
225 	 *            the new <code>destinationClasses</code> for this transformer
226 	 */
227 	protected synchronized void setDestinationClasses(Class[] destinationClasses) {
228 		setInitialized(false);
229 		this.destinationClasses = destinationClasses;
230 	}
231 
232 // initialize
233 
234 	/**
235 	 * Gives subclasses a chance to perform any computations needed to
236 	 * initialize the transformer.
237 	 */
238 	protected void initializeImpl() throws Exception {
239 	}
240 
241 	/**
242 	 * Initialize this Transformer
243 	 * @throws TransformationException
244 	 */
245 	protected synchronized final void initialize() throws TransformationException {
246 		if (!initialized) {
247 			if (log.isInfoEnabled()) {
248 				log.info("Initializing transformer " + ObjectUtils.getObjectDescription(this));
249 			}
250 
251 			try {
252 				initializeImpl();
253 
254 				if (sourceClasses == null) {
255 					sourceClasses = getSourceClassesImpl();
256 				}
257 				if (destinationClasses == null) {
258 					destinationClasses = getDestinationClassesImpl();
259 				}
260 				if (ObjectUtils.isEmpty(sourceClasses)) {
261 					throw new TransformationException(
262 							"This transformer, "
263 									+ ObjectUtils.getObjectDescription(this)
264 									+ ", is invalid because it does specify any sourceClasses");
265 				}
266 				if (ObjectUtils.isEmpty(destinationClasses)) {
267 					throw new TransformationException(
268 							"This transformer, "
269 									+ ObjectUtils.getObjectDescription(this)
270 									+ ", is invalid because it does specify any destinationClasses");
271 				}
272 
273 				transformableCallCache = Collections.synchronizedMap(new HashMap());
274 
275 				if (nestedTransformer == null) {
276 					nestedTransformer = Defaults.createTransformer();
277 				}
278 
279 				initialized = true;
280 			}
281 			catch (TransformationException e) {
282 				throw e;
283 			}
284 			catch (Exception e) {
285 				if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
286 					throw (RuntimeException) e;
287 				}
288 				throw new TransformationException("Could not initialize transformer "
289 						+ ObjectUtils.getObjectDescription(this), e);
290 			}
291 		}
292 	}
293 
294 	/**
295 	 * Retrieves the current Locale if none is specified in the method arguments
296 	 * for a converter or copier.  Attempts to load the Locale using Spring's
297 	 * {@link org.springframework.context.i18n.LocaleContextHolder}, if Spring
298 	 * is on the classpath.  Otherwise, returns the default Locale by calling
299 	 * {@link Locale#getDefault()}.
300 	 *
301 	 * @return the current Locale
302 	 */
303 	protected Locale getLocale() {
304 		Locale locale = null;
305 		if (ClassUtils.isClassPresent(SPRING_LOCALE_CONTEXT_HOLDER_CLASS)) {
306 			try {
307 				Class contextHolderClass = Class.forName(SPRING_LOCALE_CONTEXT_HOLDER_CLASS);
308 				Method getLocaleMethod =
309 					contextHolderClass.getMethod("getLocale", (Class[]) null);
310 				locale = (Locale) getLocaleMethod.invoke(null, (Object[]) null);
311 			}
312 			catch (Exception e) {
313 				log.warn("Unable to retrieve locale from Spring", e);
314 			}
315 		}
316 
317 		if (locale == null) {
318 			locale = Locale.getDefault();
319 		}
320 
321 		return locale;
322 	}
323 
324 // convert
325 
326 	/**
327 	 * {@link DecoratedConverter#convert(Class, Object)}
328 	 * @param destinationClass
329 	 * @param source
330 	 * @return
331 	 * @throws TransformationException
332 	 */
333 	public final Object convert(Class destinationClass, Object source)
334 			throws TransformationException {
335 		return convert(destinationClass, source, null);
336 	}
337 
338 	/**
339 	 * @{link {@link Converter#convert(Class, Object, Locale)}
340 	 * @param destinationClass
341 	 * @param source
342 	 * @param locale
343 	 * @return
344 	 */
345 	public final Object convert(Class destinationClass, Object source,
346 		Locale locale) {
347 		initialize();
348 
349 		if (isPerformingLogging() && log.isTraceEnabled()) {
350 			log.trace("Converting " + ObjectUtils.getObjectDescription(source)
351 				+ " to destination type "
352 				+ ObjectUtils.getObjectDescription(destinationClass)
353 				+ " in locale " + locale);
354 		}
355 
356 		if (locale == null) {
357 			locale = getLocale();
358 		}
359 
360 		if (source == null && isAutomaticallyHandlingNulls()) {
361 			if (destinationClass != null && destinationClass.isPrimitive()) {
362 				throw new TransformationException(destinationClass, source);
363 			}
364 			return null;
365 		}
366 
367 		try {
368 			return convertImpl(destinationClass, source, locale);
369 		}
370 		catch (TransformationException e) {
371 			throw e;
372 		}
373 		catch (Exception e) {
374 			if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
375 				throw (RuntimeException) e;
376 			}
377 			if (isTransformable(destinationClass, ClassUtils.getClass(source))) {
378 				throw new TransformationException(destinationClass, source, e);
379 			}
380 			throw new TransformationException(
381 					getClass().getName() + " cannot convert "
382 						+ ObjectUtils.getObjectDescription(source)
383 						+ " to an instance of "
384 						+ ObjectUtils.getObjectDescription(destinationClass), e);
385 		}
386 	}
387 
388 	/**
389 	 * The implementation of the <code>convert</code> method, which may omit
390 	 * the invalid argument checks already performed by this base class. By
391 	 * default, this method creates a new instance of the destinationClass and
392 	 * copies information from the source to the destination. This
393 	 * implementation should be fine as-is for Copiers, but Converters will need
394 	 * to implement this method since they will not be implementing the copy
395 	 * method.
396 	 *
397 	 * @param locale
398 	 *            the locale in which the conversion should take place. for
399 	 *            converters that are not locale-aware, the local argument can
400 	 *            simply be ignored
401 	 */
402 	protected Object convertImpl(Class destinationClass, Object source,
403 		Locale locale) throws Exception {
404 		Object reuseableSource = createReusableSource(destinationClass, source);
405 		Object newInstance = createNewInstance(destinationClass, reuseableSource);
406 		copyImpl(newInstance, reuseableSource, locale, Converter.TRANSFORMATION_TYPE_CONVERT);
407 		return newInstance;
408 	}
409 
410 	/**
411 	 * {@link NodeCopier#createReusableSource(Class, Object)}
412 	 * @param destinationClass
413 	 * @param source
414 	 * @return
415 	 */
416 	protected Object createReusableSource(Class destinationClass, Object source) {
417 		return source;
418 	}
419 
420 // equals
421 
422 	/**
423 	 * Test objects for equality.
424 	 * @param object1
425 	 * @param object2
426 	 * @param locale
427 	 * @return <code>true</code> if <code>object1</code> is considered equal to <code>object2</code>.
428 	 */
429 	public boolean equals(Object object1, Object object2, Locale locale) {
430 		if (locale == null) {
431 			locale = getLocale();
432 		}
433 		return object1 == object2
434 				|| (object1 != null && equalsUnidirectionalTest(object1, object2, locale))
435 				|| (object2 != null && equalsUnidirectionalTest(object2, object1, locale));
436 	}
437 
438 	/**
439 	 * Locale-independent test between two objects for equality.
440 	 * @param object1
441 	 * @param object2
442 	 * @return <code>true</code> if <code>object1</code> is considered equal to <code>object2</code>.
443 	 * @throws TransformationException
444 	 */
445 	public final boolean equals(Object object1, Object object2)
446 		throws TransformationException {
447 		return equals(object1, object2, null);
448 	}
449 
450 	/**
451 	 * Helper method for equality testing.
452 	 * @param cannotBeNull
453 	 * @param canBeNull
454 	 * @param locale
455 	 * @return <code>true</code> if <code>object1</code> is considered equal to <code>object2</code>.
456 	 */
457 	protected boolean equalsUnidirectionalTest(Object cannotBeNull, Object canBeNull, Locale locale) {
458 		return cannotBeNull.equals(convert(cannotBeNull.getClass(), canBeNull, locale));
459 	}
460 
461 // copy
462 
463 	/**
464 	 * {@link DecoratedCopier#copy(Object, Object)}
465 	 * @param destination
466 	 * @param source
467 	 * @throws TransformationException
468 	 */
469 	public final void copy(Object destination, Object source) throws TransformationException {
470 		copy(destination, source, null);
471 	}
472 
473 	/**
474 	 * {@link Copier#copy(Object, Object, Locale)}
475 	 * @param destination
476 	 * @param source
477 	 * @param locale
478 	 * @throws TransformationException
479 	 */
480 	public final void copy(Object destination, Object source, Locale locale) throws TransformationException {
481 
482 		initialize();
483 
484 		if (isPerformingLogging() && log.isTraceEnabled()) {
485 			log.trace("Copying information from "
486 				+ ObjectUtils.getObjectDescription(source) + " to destination "
487 				+ ObjectUtils.getObjectDescription(destination) + " in locale "
488 				+ locale);
489 		}
490 
491 		if (destination == null) {
492 			throw new TransformationException("Destination cannot be null");
493 		}
494 
495 		if (source == null && isAutomaticallyHandlingNulls()) {
496 			return;
497 		}
498 
499 		if (locale == null) {
500 			locale = getLocale();
501 		}
502 
503 		try {
504 			copyImpl(destination, source, locale, Copier.TRANSFORMATION_TYPE_COPY);
505 		}
506 		catch (TransformationException e) {
507 			throw e;
508 		}
509 		catch (Exception e) {
510 			if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
511 				throw (RuntimeException) e;
512 			}
513 		    if (isTransformable(destination.getClass(), source.getClass())) {
514 		    	throw new TransformationException("Error copying source "
515 		    			+ ObjectUtils.getObjectDescription(source) + " to destination "
516 		    			+ ObjectUtils.getObjectDescription(destination), e);
517 		    }
518 		    throw new TransformationException("The " + getClass().getName()
519 		    		+ " cannot copy source '" + source + "' (class "
520 		    		+ source.getClass().getName() + ") to destination '"
521 		    		+ destination + "' (class " + destination.getClass().getName()
522 		    		+ ")");
523 		}
524 	}
525 
526 	/**
527 	 * Implementation of the copy method.  By default, this method throws
528 	 * UnsupportedOperationException.
529 	 */
530 	protected void copyImpl(Object destination, Object source, Locale locale, Integer preferredTransformationType) throws Exception {
531 		throw new UnsupportedOperationException();
532 	}
533 
534 // misc utility methods
535 
536 	/**
537 	 * Implement {@link NodeCopier#createNewInstance(Class, Object)}
538 	 * @param destinationClass
539 	 * @param source
540 	 * @return Object
541 	 * @throws Exception
542 	 */
543 	protected Object createNewInstanceImpl(Class destinationClass, Object source) throws Exception {
544 		if (CompositeUtils.isSpecializable(getReflector(), InstantiatingReflector.class)) {
545 			try {
546 				return getInstantiatingReflector().newInstance(destinationClass, source);
547 			}
548 			catch (Exception e) {
549 				// write a warning to the log and fall back to the superclass'
550 				// behavior
551 				if (getLog().isWarnEnabled()) {
552 					getLog().warn(ObjectUtils.getObjectDescription(getReflector())
553 						+ " is exposable as an InstantiatingReflector, but failed to instantiate "
554 						+ ObjectUtils.getObjectDescription(destinationClass), e);
555 				}
556 			}
557 		}
558 		return destinationClass.newInstance();
559 	}
560 
561 	/**
562 	 * {@link NodeCopier#createNewInstance(Class, Object)}
563 	 * @param destinationClass
564 	 * @param source
565 	 * @return Object
566 	 */
567 	public Object createNewInstance(Class destinationClass, Object source) {
568 		try {
569 			return createNewInstanceImpl(destinationClass, source);
570 		}
571 		catch (ReflectionException e) {
572 			throw e;
573 		}
574 		catch (Exception e) {
575 			if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
576 				throw (RuntimeException) e;
577 			}
578 			throw new ReflectionException("Unable to instantiate " + ObjectUtils.getObjectDescription(destinationClass), e);
579 		}
580 	}
581 
582 	/**
583 	 * Implementation of isImpreciseTransformation
584 	 * @param destinationClass
585 	 * @param sourceClass
586 	 * @return boolean
587 	 */
588 	protected boolean isImpreciseTransformationImpl(Class destinationClass, Class sourceClass) {
589 		return destinationClass == null && sourceClass != null;
590 	}
591 
592 	/**
593 	 * Learn whether the specified transformation yields an imprecise result.
594 	 * @param destinationClass
595 	 * @param sourceClass
596 	 * @return boolean
597 	 * @since Morph 1.1
598 	 */
599 	public final boolean isImpreciseTransformation(Class destinationClass, Class sourceClass) {
600 		try {
601 			return isImpreciseTransformationImpl(destinationClass, sourceClass);
602 		} catch (Exception e) {
603 			if (e instanceof RuntimeException && !isWrappingRuntimeExceptions()) {
604 				throw (RuntimeException) e;
605 			}
606 			throw new TransformationException("Could not determine if transformation of "
607 					+ sourceClass + " to " + destinationClass
608 					+ " results in a loss of precision", e);
609 		}
610 	}
611 
612 // property getters and setters
613 
614 	/**
615 	 * Indicates if calls to the main transformation methods (convert, copy)
616 	 * will cause a log message to be recorded
617 	 * @return boolean
618 	 */
619 	protected boolean isPerformingLogging() {
620 		return true;
621 	}
622 
623 	/**
624 	 * Indicates whether <code>null</code> values will automatically be
625 	 * converted to <code>null</code> by this base class before even calling
626 	 * the subclass's {@link #convertImpl(Class, Object, Locale)} method.
627 	 * Subclasses which depend on this behavior (which is all subclasses, by
628 	 * default) should include <code>null</code> as one of their source and
629 	 * destination classes so that the actual behavior of the transformer is
630 	 * consistent with the values that are returned by the
631 	 * {@link #isTransformable(Class, Class)} method. The conversions will
632 	 * happen automatically even if the source and destination classes don't
633 	 * contain <code>null</code>, but for the sake of consistency the
634 	 * <code>null</code>s should be included.
635 	 *
636 	 * @return whether <code>null</code> values will automatically be
637 	 *         converted to <code>null</code> by this base class before even
638 	 *         calling the subclass's
639 	 *         {@link #convertImpl(Class, Object, Locale)} method
640 	 *
641 	 * @since Morph 1.1
642 	 */
643 	protected boolean isAutomaticallyHandlingNulls() {
644 		return true;
645 	}
646 	
647 	/**
648 	 * Indicates whether runtime exceptions should be wrapped as
649 	 * {@link TransformationException}s. By default, this method returns
650 	 * <code>true</code>.
651 	 * 
652 	 * <p>
653 	 * Simple transformers in Morph that operate on JDK types like Numbers and
654 	 * Strings will usually set this value to <code>true</code> so that they
655 	 * throw TransformationExceptions if problems occur. More complex
656 	 * transformers that operate on graphs of objects are encouraged to set this
657 	 * value to <code>false</code> so that runtime exceptions are not wrapped.
658 	 * This way, problems accessing data will be expressed by the native API of
659 	 * a user's domain objects and avoid the need to catch Morph-specific
660 	 * exceptions (assuming the use of runtime exceptions in said domain
661 	 * objects).
662 	 * 
663 	 * @return <code>true</code>
664 	 * @since Morph 1.1
665 	 */
666 	protected boolean isWrappingRuntimeExceptions() {
667 		return true;
668 	}
669 
670 	/**
671 	 * {@link NodeCopier#getNestedTransformer()}
672 	 * @return Transformer
673 	 */
674 	protected Transformer getNestedTransformer() {
675 // can't use default-if-null pattern here; otherwise GraphTransformers won't be able to detect when they
676 // have already set the graph transformer
677 		return nestedTransformer;
678 	}
679 
680 	/**
681 	 * {@link NodeCopier#setNestedTransformer(Transformer)}
682 	 * @param nestedTransformer
683 	 */
684 	protected void setNestedTransformer(Transformer nestedTransformer) {
685 		this.nestedTransformer = nestedTransformer;
686 	}
687 
688 	/**
689 	 * Learn whether this Transformer has been initialized.
690 	 * @return boolean
691 	 */
692 	protected boolean isInitialized() {
693 		return initialized;
694 	}
695 
696 	/**
697 	 * Set whether this Transformer has been initialized.
698 	 * @param initialized
699 	 */
700 	protected void setInitialized(boolean initialized) {
701 		this.initialized = initialized;
702 	}
703 
704 	/**
705 	 * Learn whether this Transformer is caching calls to
706 	 * {@link #isTransformable(Class, Class)}
707 	 * @return boolean
708 	 */
709 	public boolean isCachingIsTransformableCalls() {
710 		return cachingIsTransformableCalls;
711 	}
712 
713 	/**
714 	 * Set whether this Transformer is caching calls to
715 	 * {@link #isTransformable(Class, Class)}
716 	 * @param cachingIsTransformableCalls
717 	 */
718 	public void setCachingIsTransformableCalls(
719 		boolean cachingIsTransformableCalls) {
720 		this.cachingIsTransformableCalls = cachingIsTransformableCalls;
721 	}
722 
723 	/**
724 	 * Get the cache of calls to 
725 	 * {@link #isTransformable(Class, Class)}
726 	 * @return Map
727 	 */
728 	protected Map getTransformableCallCache() {
729 		return transformableCallCache;
730 	}
731 
732 	/**
733 	 * Get the cache of calls to 
734 	 * {@link #isTransformable(Class, Class)}
735 	 * @param transformableCallCache Map
736 	 */
737 	protected void setTransformableCallCache(Map transformableCallCache) {
738 		this.transformableCallCache = transformableCallCache;
739 	}
740 
741 	/**
742 	 * Get the commons-logging Log in use.
743 	 * @return Log
744 	 */
745 	protected Log getLog() {
746 		return log;
747 	}
748 
749 	/**
750 	 * Set the commons-logging Log for this Transformer.
751 	 * @param log
752 	 */
753 	protected void setLog(Log log) {
754 		this.log = log;
755 	}
756 
757 	/**
758 	 * Get the InstantiatingReflector employed by this Transformer.
759 	 * @return InstantiatingReflector
760 	 */
761 	protected InstantiatingReflector getInstantiatingReflector() {
762 		return (InstantiatingReflector) getReflector(InstantiatingReflector.class);
763 	}
764 
765 	/**
766 	 * Get the Reflector of the specified type employed by this Transformer.
767 	 * @param reflectorType
768 	 * @return Reflector of type <code>reflectorType</code>
769 	 */
770 	protected Reflector getReflector(Class reflectorType) {
771 		return (Reflector) CompositeUtils.specialize(getReflector(), reflectorType);
772 	}
773 
774 	/**
775 	 * Get the Reflector employed by this Transformer.
776 	 * @return Reflector
777 	 */
778 	public synchronized Reflector getReflector() {
779 		if (reflector == null) {
780 			setReflector(createDefaultReflector());
781 		}
782 		return reflector;
783 	}
784 
785 	/**
786 	 * Set the Reflector to be used by this Transformer.
787 	 * @param reflector
788 	 */
789 	public synchronized void setReflector(Reflector reflector) {
790 		this.reflector = reflector;
791 	}
792 
793 	/**
794 	 * Create the default reflector instance to be used by this Transformer.
795 	 * @return Reflector
796 	 */
797 	protected Reflector createDefaultReflector() {
798 		return Defaults.createReflector();
799 	}
800 
801 	/**
802 	 * {@inheritDoc}
803 	 * @see java.lang.Object#clone()
804 	 */
805 	protected Object clone() throws CloneNotSupportedException {
806 		BaseTransformer result = (BaseTransformer) super.clone();
807 		result.transformableCallCache = Collections.synchronizedMap(new HashMap());
808 		return result;
809 	}
810 
811 	/**
812 	 * Get the transformerName.
813 	 * @return String
814 	 * @since Morph 1.1
815 	 */
816 	public String getTransformerName() {
817 		return transformerName;
818 	}
819 
820 	/**
821 	 * Set the transformerName.
822 	 * @param transformerName the String to set
823 	 * @since Morph 1.1
824 	 */
825 	public void setTransformerName(String transformerName) {
826 		if (initialized && ObjectUtils.equals(transformerName, this.transformerName)) {
827 			return;
828 		}
829 		this.transformerName = transformerName;
830 		log = transformerName == null ? LogFactory.getLog(getClass()) : LogFactory.getLog(transformerName);
831 	}
832 
833 	/**
834 	 * {@inheritDoc}
835 	 * @since Morph 1.1
836 	 */
837 	public String toString() {
838 		String name = getTransformerName();
839 		return name == null ? super.toString() : name;
840 	}
841 }