1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
130
131
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
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
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
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
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
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
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
550
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
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
676
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 }