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.util.Arrays;
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.Locale;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import net.sf.composite.CompositeException;
26  import net.sf.composite.SpecializableComposite;
27  import net.sf.composite.specialize.Specializer;
28  import net.sf.composite.specialize.specializers.CachingSpecializerProxy;
29  import net.sf.composite.specialize.specializers.CloningSpecializer;
30  import net.sf.composite.util.ObjectPair;
31  import net.sf.composite.util.ObjectUtils;
32  import net.sf.morph.transform.Converter;
33  import net.sf.morph.transform.Copier;
34  import net.sf.morph.transform.DecoratedConverter;
35  import net.sf.morph.transform.DecoratedCopier;
36  import net.sf.morph.transform.ExplicitTransformer;
37  import net.sf.morph.transform.ImpreciseTransformer;
38  import net.sf.morph.transform.NodeCopier;
39  import net.sf.morph.transform.TransformationException;
40  import net.sf.morph.transform.Transformer;
41  import net.sf.morph.transform.converters.DefaultToBooleanConverter;
42  import net.sf.morph.transform.converters.DefaultToTextConverter;
43  import net.sf.morph.transform.converters.IdentityConverter;
44  import net.sf.morph.transform.converters.NumberConverter;
45  import net.sf.morph.transform.converters.NumberToTimeConverter;
46  import net.sf.morph.transform.converters.ObjectToClassConverter;
47  import net.sf.morph.transform.converters.TextConverter;
48  import net.sf.morph.transform.converters.TextToNumberConverter;
49  import net.sf.morph.transform.converters.TextToTimeConverter;
50  import net.sf.morph.transform.converters.TimeConverter;
51  import net.sf.morph.transform.converters.TimeToNumberConverter;
52  import net.sf.morph.transform.copiers.ContainerCopier;
53  import net.sf.morph.transform.copiers.ImmutableComponentArrayCopier;
54  import net.sf.morph.transform.copiers.MapCopier;
55  import net.sf.morph.transform.copiers.PropertyNameMatchingCopier;
56  import net.sf.morph.transform.copiers.TextToContainerCopier;
57  import net.sf.morph.util.ClassUtils;
58  import net.sf.morph.util.ContainerUtils;
59  import net.sf.morph.util.MutableInteger;
60  import net.sf.morph.util.TransformerUtils;
61  
62  /**
63   * <p>
64   * Delegates transformations to a list of transformers. The transformers are
65   * tried in the order in which they appear in the <code>components</code>
66   * property of this transformer.
67   * </p>
68   *
69   * <p>
70   * By default this transformer is initialized with a set of transformers that
71   * will meet basic needs. This list of transformers is subject to change in
72   * future versions of Morph, but we do not anticipate removing transformers,
73   * only adding new ones or modifying existing ones. This means if a
74   * transformation works in an older version of Morph it will also work in a
75   * newer version. A transformation's behavior may in some cases change between
76   * releases, but we will avoid this whenever possible.
77   * </p>
78   *
79   * <p>
80   * The default set of transformers includes both converters and copiers. For
81   * calls to copy methods, only the copiers will be used. For calls to convert
82   * methods, the converters <em>and</em> the copiers will be used, since all
83   * Copiers provided by the Morph framework are also Converters.
84   * </p>
85   *
86   * <p>
87   * Any delegates which implement
88   * {@link net.sf.morph.transform.NodeCopier} will automatically have this
89   * transformer marked as the parent transformer.  This is important for
90   * performing deep copies of object graphs.  Note that it is safe for a SimpleDelegatingTransformer
91   * to implement the NodeCopier interface <em>only</em> if all its components do so.
92   * </p>
93   *
94   * @author Matt Sgarlata
95   * @since Dec 12, 2004
96   */
97  public class SimpleDelegatingTransformer extends BaseCompositeTransformer implements
98  		SpecializableComposite, ExplicitTransformer, Transformer, DecoratedCopier,
99  		DecoratedConverter, Cloneable, ImpreciseTransformer {
100 
101 	//TODO extract BaseDelegatingTransformer with pluggable delegate selection
102 
103 	private static class MapThreadLocal extends ThreadLocal {
104 		protected Object initialValue() {
105 			return new HashMap();
106 		}
107 	}
108 
109 	private static class StackDepthThreadLocal extends ThreadLocal {
110 		protected Object initialValue() {
111 			return new MutableInteger();
112 		}
113 	}
114 
115 	/**
116 	 * Create the default set of Transformer components.
117 	 * @return Transformer[]
118 	 */
119 	protected Transformer[] createDefaultComponents() {
120 		return new Transformer[] {
121 			new DefaultToBooleanConverter(),
122 			new IdentityConverter(),
123 			new ObjectToClassConverter(),
124 			new TextConverter(),
125 			new DefaultToTextConverter(),
126 			new TextToNumberConverter(),
127 			new TextToTimeConverter(),
128 			new NumberToTimeConverter(),
129 			new TimeToNumberConverter(),
130 			new NumberConverter(),
131 			new TimeConverter(),
132 			new TextToContainerCopier(),
133 			new MapCopier(),
134 			new PropertyNameMatchingCopier() {
135 				{ setDestinationClasses(new Class[] { Map.class }); }
136 			},
137 			new ImmutableComponentArrayCopier(),
138 			new ContainerCopier(),
139 			new PropertyNameMatchingCopier()
140 		};
141 	}
142 
143 	private Specializer specializer;
144 	private boolean preferPreciseTransformers;
145 
146 	private transient ThreadLocal visitedSourceToDestinationMapThreadLocal = new MapThreadLocal();
147 	private transient ThreadLocal stackDepthThreadLocal = new StackDepthThreadLocal();
148 
149 	private transient Map copierRegistry = Collections.synchronizedMap(new HashMap());
150 	private transient Map transformerRegistry = Collections.synchronizedMap(new HashMap());
151 
152 	/**
153 	 * Construct a new SimpleDelegatingTransformer.
154 	 */
155 	public SimpleDelegatingTransformer() {
156 		super();
157 	}
158 
159 	/**
160 	 * Construct a new SimpleDelegatingTransformer.
161 	 * @param components
162 	 */
163 	public SimpleDelegatingTransformer(Transformer[] components) {
164 		this(components, false);
165 	}
166 
167 	/**
168 	 * Construct a new SimpleDelegatingTransformer.
169 	 * @param components
170 	 * @param appendDefaultComponents
171 	 */
172 	public SimpleDelegatingTransformer(Transformer[] components, boolean appendDefaultComponents) {
173 		if (appendDefaultComponents) {
174 			Transformer[] defaultComponents = createDefaultComponents();
175 			if (ObjectUtils.isEmpty(components)) {
176 				components = defaultComponents;
177 			}
178 			else {
179 				Transformer[] newComponents = (Transformer[]) ClassUtils.createArray(
180 						getComponentType(), components.length + defaultComponents.length);
181 				System.arraycopy(components, 0, newComponents, 0, components.length);
182 				System.arraycopy(defaultComponents, 0, newComponents, components.length, defaultComponents.length);
183 				components = newComponents;
184 			}
185 		}
186 		setComponents(components);
187 	}
188 
189 	/**
190 	 * {@inheritDoc}
191 	 * @see net.sf.morph.transform.transformers.BaseCompositeTransformer#initializeImpl()
192 	 */
193 	protected void initializeImpl() throws Exception {
194 		super.initializeImpl();
195 		if (getNestedTransformer() == null) {
196 			setNestedTransformer(this);
197 		}
198 	}
199 
200 	/**
201 	 * Determines if one of the delegate transformers is capable of performing
202 	 * the given transformation. This method is necessary because otherwise the
203 	 * delegating transformer would be too eager in what it says it can convert.
204 	 * For example, if a delegate transformer was capable of transforming A to B
205 	 * and another delegate was capable of transforming C to D, this transformer
206 	 * would incorrectly state that transforming A to D and C to B was possible
207 	 * even though they are not.
208 	 *
209 	 * @param destinationType
210 	 *            the destination type to test
211 	 * @param sourceType
212 	 *            the source type to test
213 	 * @return whether the destination type is transformable to the source type
214 	 * @throws TransformationException
215 	 *             if it could not be determined if <code>sourceType</code> is
216 	 *             transformable into <code>destinationType</code>
217 	 */
218 	protected boolean isTransformableImpl(Class destinationType,
219 		Class sourceType) throws Exception {
220 		for (int i = 0; i < getTransformers().length; i++) {
221 			Transformer transformer = getTransformers()[i];
222 			if (TransformerUtils.isTransformable(transformer,
223 				destinationType, sourceType)) {
224 				return true;
225 			}
226 		}
227 		return false;
228 	}
229 
230 	/**
231 	 * {@inheritDoc}
232 	 */
233 	protected boolean isImpreciseTransformationImpl(Class destinationClass, Class sourceClass) {
234 		return TransformerUtils.isImpreciseTransformation(getTransformer(destinationClass,
235 				sourceClass), destinationClass, sourceClass);
236 	}
237 
238 	/**
239 	 * {@inheritDoc}
240 	 * @see net.sf.morph.transform.transformers.BaseTransformer#getSourceClassesImpl()
241 	 */
242 	protected Class[] getSourceClassesImpl() throws Exception {
243 		Set sourceClasses = ContainerUtils.createOrderedSet();
244 		Transformer[] t = getTransformers();
245 		for (int i = 0; i < t.length; i++) {
246 			sourceClasses.addAll(Arrays.asList(t[i].getSourceClasses()));
247 		}
248 		return (Class[]) sourceClasses.toArray(new Class[sourceClasses.size()]);
249 	}
250 
251 	/**
252 	 * {@inheritDoc}
253 	 * @see net.sf.morph.transform.transformers.BaseTransformer#getDestinationClassesImpl()
254 	 */
255 	protected Class[] getDestinationClassesImpl() throws Exception {
256 		Set destinationClasses = ContainerUtils.createOrderedSet();
257 		Transformer[] t = getTransformers();
258 		for (int i = 0; i < t.length; i++) {
259 			destinationClasses.addAll(Arrays.asList(t[i].getDestinationClasses()));
260 		}
261 		return (Class[]) destinationClasses.toArray(new Class[destinationClasses.size()]);
262 	}
263 
264 	/**
265 	 * {@inheritDoc}
266 	 * @see net.sf.morph.transform.transformers.BaseTransformer#copyImpl(java.lang.Object, java.lang.Object, java.util.Locale, java.lang.Integer)
267 	 */
268 	protected void copyImpl(Object destination, Object source, Locale locale, Integer preferredTransformationType)
269 			throws Exception {
270 		incrementStackDepth();
271 		try {
272 			if (!hasVisitedDestination(source, destination)) {
273 				Class destinationType = ClassUtils.getClass(destination);
274 				Copier copier = getCopier(destinationType, ClassUtils.getClass(source));
275 				recordVisit(source, destinationType, destination);
276 				copier.copy(destination, source, locale);
277 			}
278 		} finally {
279 			decrementStackDepth();
280 			clearVisitedSourceToDestinationMapIfNecessary();
281 		}
282 	}
283 
284 	/**
285 	 * {@inheritDoc}
286 	 * @see net.sf.morph.transform.transformers.BaseTransformer#convertImpl(java.lang.Class, java.lang.Object, java.util.Locale)
287 	 */
288 	protected Object convertImpl(Class destinationType, Object source, Locale locale)
289 			throws Exception {
290 		incrementStackDepth();
291 		try {
292 			Class sourceClass = ClassUtils.getClass(source);
293 			Transformer transformer = getTransformer(destinationType, sourceClass);
294 
295 			if (hasVisited(source, destinationType)) {
296 				return getCachedResult(source, destinationType);
297 			}
298 			if (transformer instanceof NodeCopier) {
299 				NodeCopier nodeCopier = (NodeCopier) transformer;
300 				Object reuseableSource = nodeCopier.createReusableSource(destinationType, source);
301 				Object newInstance = nodeCopier.createNewInstance(destinationType,
302 						reuseableSource);
303 				recordVisit(source, destinationType, newInstance);
304 				nodeCopier.copy(newInstance, reuseableSource, locale);
305 				return newInstance;
306 			}
307 			Converter converter = (Converter) transformer;
308 			return converter.convert(destinationType, source, locale);
309 		} finally {
310 			decrementStackDepth();
311 			clearVisitedSourceToDestinationMapIfNecessary();
312 		}
313 	}
314 
315 	/**
316 	 * Increment the depth of the nested copy stack.
317 	 */
318 	protected void incrementStackDepth() {
319 		((MutableInteger) stackDepthThreadLocal.get()).value++;
320 	}
321 
322 	/**
323 	 * Decrement the depth of the nested copy stack.
324 	 */
325 	protected void decrementStackDepth() {
326 		((MutableInteger) stackDepthThreadLocal.get()).value--;
327 	}
328 
329 	/**
330 	 * If we have popped everybody off the stack, clear the cache.
331 	 */
332 	protected void clearVisitedSourceToDestinationMapIfNecessary() {
333 		if (((MutableInteger) stackDepthThreadLocal.get()).value == 0) {
334 			getVisitedSourceToDestinationMap().clear();
335 		}
336 	}
337 
338 	/**
339 	 * Cache destination object for this source/destination class combination.
340 	 * @param source
341 	 * @param destinationType
342 	 * @param destination
343 	 */
344 	protected void recordVisit(Object source, Class destinationType, Object destination) {
345 		Object key = new ObjectPair(source, destinationType);
346 		getVisitedSourceToDestinationMap().put(key, destination);
347 	}
348 
349 	/**
350 	 * Avoid recursion by checking whether we have already begun a conversion
351 	 * of the specified source object to the specified destination class.
352 	 * @param source
353 	 * @param destinationType
354 	 * @return boolean
355 	 */
356 	protected boolean hasVisited(Object source, Class destinationType) {
357 		Object key = new ObjectPair(source, destinationType);
358 		return getVisitedSourceToDestinationMap().containsKey(key);
359 	}
360 
361 	/**
362 	 * Avoid recursion by checking whether we have already begun a copy
363 	 * of the specified source object to the specified destination object.
364 	 * @param source
365 	 * @param destination
366 	 * @return boolean
367 	 */
368 	protected boolean hasVisitedDestination(Object source, Object destination) {
369 		//TODO extend HashSet->IdentitySet to handle e.g.:
370 		// visit destination a, visit destination b, visit destination b (double b won't find recursion)
371 		Class destinationType = ClassUtils.getClass(destination);
372 		return hasVisited(source, destinationType)
373 				&& getCachedResult(source, destinationType) == destination;
374 	}
375 
376 	/**
377 	 * Get the cached object in the process of being transformed 
378 	 * @param source
379 	 * @param destinationType
380 	 * @return
381 	 */
382 	protected Object getCachedResult(Object source, Class destinationType) {
383 		Object key = new ObjectPair(source, destinationType);
384 		if (!getVisitedSourceToDestinationMap().containsKey(key)) {
385 			throw new IllegalArgumentException(
386 					"Cannot return a cached conversion result for "
387 							+ ObjectUtils.getObjectDescription(source)
388 							+ " to destination type '"
389 							+ destinationType
390 							+ "' because that conversion hasn't been performed before");
391 		}
392 		return getVisitedSourceToDestinationMap().get(key);
393 	}
394 
395 	/**
396 	 * {@inheritDoc}
397 	 * @see net.sf.composite.SpecializableComposite#specialize(java.lang.Class)
398 	 */
399 	public Object specialize(Class compositeType) {
400 		return getSpecializer().specialize(this, compositeType);
401 	}
402 
403 	/**
404 	 * {@inheritDoc}
405 	 * @see net.sf.composite.SpecializableComposite#isSpecializable(java.lang.Class)
406 	 */
407 	public boolean isSpecializable(Class type) throws CompositeException {
408 		return getSpecializer().isSpecializable(this, type);
409 	}
410 
411 	/**
412 	 * Finds a transformer of type <code>transformerType</code> that is
413 	 * capable of transforming <code>sourceClass</code> to
414 	 * <code>destinationClass</code>. Caches results in the
415 	 * <code>registry</code>.
416 	 *
417 	 * @param registry
418 	 *            a cache that remembers which transformers can be used for
419 	 *            which transformations
420 	 * @param transformerType
421 	 *            the type of the returned transformer
422 	 * @param destinationClass
423 	 *            the destinationClass of the transformation
424 	 * @param sourceClass
425 	 *            the sourceClass of the transformation
426 	 * @return the transformer of the requested type capable of performing the
427 	 *         requested transformation
428 	 * @throws TransformationException
429 	 *             if no suitable transformer could be found
430 	 */
431 	protected Transformer getTransformer(Map registry, Class transformerType, Class destinationClass, Class sourceClass) {
432 		ObjectPair key = new ObjectPair(destinationClass, sourceClass);
433 		Transformer transformer = (Transformer) registry.get(key);
434 		if (transformer == null) {
435 			transformer = getTransformer(transformerType, destinationClass, sourceClass);
436 			registry.put(key, transformer);
437 		}
438 		return transformer;
439 	}
440 
441 	/**
442 	 * Finds a Copier that is capable of transforming <code>sourceClass</code>
443 	 * to <code>destinationClass</code>.
444 	 *
445 	 * @param destinationClass
446 	 *            the destinationClass of the transformation
447 	 * @param sourceClass
448 	 *            the sourceClass of the transformation
449 	 * @return the transformer of the requested type capable of performing the
450 	 *         requested transformation
451 	 * @throws TransformationException
452 	 *             if no suitable copier could be found
453 	 */
454 	protected Copier getCopier(Class destinationClass, Class sourceClass) {
455 		return (Copier) getTransformer(copierRegistry, Copier.class, destinationClass, sourceClass);
456 	}
457 
458 	/**
459 	 * Finds a Transformer that is capable of transforming
460 	 * <code>sourceClass</code> to <code>destinationClass</code>.
461 	 *
462 	 * @param destinationClass
463 	 *            the destinationClass of the transformation
464 	 * @param sourceClass
465 	 *            the sourceClass of the transformation
466 	 * @return the transformer of the requested type capable of performing the
467 	 *         requested transformation
468 	 * @throws TransformationException
469 	 *             if no suitable transformer could be found
470 	 */
471 	protected Transformer getTransformer(Class destinationClass, Class sourceClass) {
472 		return getTransformer(transformerRegistry, Transformer.class, destinationClass, sourceClass);
473 	}
474 
475 	/**
476 	 * Finds a transformer of type <code>transformerType</code> that is
477 	 * capable of transforming <code>sourceClass</code> to
478 	 * <code>destinationClass</code>.
479 	 *
480 	 * @param transformerType
481 	 *            the type of the returned transformer
482 	 * @param destinationClass
483 	 *            the destinationClass of the transformation
484 	 * @param sourceClass
485 	 *            the sourceClass of the transformation
486 	 * @return the transformer of the requested type capable of performing the
487 	 *         requested transformation
488 	 * @throws TransformationException
489 	 *             if no suitable transformer could be found
490 	 */
491 	private Transformer getTransformer(Class transformerType, Class destinationClass, Class sourceClass) throws TransformationException {
492 		Transformer candidate = null;
493 		for (int i = 0; i < components.length; i++) {
494 			// if the transformer is the correct type
495 			Transformer transformer = (Transformer) components[i];
496 			if (transformerType.isAssignableFrom(transformer.getClass())) {
497 				// if the transformer is capable of performing the transformation
498 				if (TransformerUtils.isTransformable(
499 						transformer, destinationClass, sourceClass)) {
500 					if (isPreferPreciseTransformers()
501 							&& candidate == null
502 							&& TransformerUtils.isImpreciseTransformation(transformer,
503 									destinationClass, sourceClass)) {
504 						candidate = transformer;
505 						continue;
506 					}
507 					if (getLog().isTraceEnabled()) {
508 						getLog().trace("Using "
509 							+ ClassUtils.getUnqualifiedClassName(transformerType)
510 							+ " " + transformer.getClass().getName()
511 							+ " to transform "
512 							+ ObjectUtils.getObjectDescription(sourceClass)
513 							+ " to "
514 							+ ObjectUtils.getObjectDescription(destinationClass));
515 					}
516 					return transformer;
517 				}
518 			}
519 			if (candidate != null) {
520 				return candidate;
521 			}
522 		}
523 		throw new TransformationException(
524 			"Could not find a transformer that can transform objects of "
525 				+ ObjectUtils.getObjectDescription(sourceClass)
526 				+ " to objects of "
527 				+ ObjectUtils.getObjectDescription(destinationClass));
528 	}
529 
530 	/**
531 	 * Get our components as a typesafe Transformer array.
532 	 * @return Transformer[]
533 	 */
534 	public synchronized Transformer[] getTransformers() {
535 		return (Transformer[]) getComponents();
536 	}
537 
538 	/*
539 	 * (non-Javadoc)
540 	 * @see net.sf.morph.transform.transformers.BaseCompositeTransformer#getComponents()
541 	 */
542 	public synchronized Object[] getComponents() {
543 		if (components == null) {
544 			setComponents(createDefaultComponents());
545 		}
546 		return super.getComponents();
547 	}
548 
549 	/*
550 	 * (non-Javadoc)
551 	 * @see net.sf.morph.transform.transformers.BaseCompositeTransformer#setComponents(java.lang.Object[])
552 	 */
553 	public synchronized void setComponents(Object[] components) {
554 		if (this.components == components) {
555 			return;
556 		}
557 		this.components = components;
558 		transformerRegistry.clear();
559 		copierRegistry.clear();
560 
561 		if (components != null) {
562 			updateNestedTransformerComponents(getNestedTransformer(), null);
563 		}
564 	}
565 
566 	/**
567 	 * Let the delegate do the logging
568 	 */
569 	protected boolean isPerformingLogging() {
570 		return false;
571 	}
572 
573 	/**
574 	 * {@inheritDoc}
575 	 * @see net.sf.morph.transform.transformers.BaseTransformer#isAutomaticallyHandlingNulls()
576 	 */
577 	protected boolean isAutomaticallyHandlingNulls() {
578 		return false;
579 	}
580 
581 	/**
582 	 * {@inheritDoc}
583 	 * @see net.sf.morph.transform.transformers.BaseTransformer#clone()
584 	 */
585 	public Object clone() throws CloneNotSupportedException {
586 		SimpleDelegatingTransformer result = (SimpleDelegatingTransformer) super.clone();
587 		result.copierRegistry = Collections.synchronizedMap(new HashMap());
588 		result.transformerRegistry = Collections.synchronizedMap(new HashMap());
589 		result.visitedSourceToDestinationMapThreadLocal = new MapThreadLocal();
590 		result.stackDepthThreadLocal = new StackDepthThreadLocal();
591 		return result;
592 	}
593 
594 	/**
595 	 * Gets a Map of all the nodes in the object graph that have been
596 	 * transformed so far by this transformer. The keys in the Map are the
597 	 * visited source nodes, and the values are the converted representation of
598 	 * the node.
599 	 *
600 	 * @return a Map of all the nodes in the object graph that have been
601 	 *         transformed so far by this transformer
602 	 */
603 	protected Map getVisitedSourceToDestinationMap() {
604 		return (Map) visitedSourceToDestinationMapThreadLocal.get();
605 	}
606 
607 	/**
608 	 * Get the Specializer for this SDT.
609 	 * @return Specializer
610 	 */
611 	public Specializer getSpecializer() {
612 		if (specializer == null) {
613 			specializer = new CachingSpecializerProxy(new CloningSpecializer());
614 		}
615 		return specializer;
616 	}
617 
618 	/**
619 	 * Set the Specializer for this SDT.
620 	 * @param specializer
621 	 */
622 	public void setSpecializer(Specializer specializer) {
623 		this.specializer = specializer;
624 	}
625 
626 	/**
627 	 * {@inheritDoc}
628 	 * @see net.sf.morph.transform.transformers.BaseTransformer#createReusableSource(java.lang.Class, java.lang.Object)
629 	 */
630 	protected Object createReusableSource(Class destinationClass, Object source) {
631 		Transformer t = getTransformer(destinationClass, ClassUtils.getClass(source));
632 		return t instanceof NodeCopier ? ((NodeCopier) t).createReusableSource(
633 				destinationClass, source) : super.createReusableSource(destinationClass,
634 				source);
635 	}
636 
637 	/**
638 	 * Get the preferPreciseTransformers.
639 	 * @return boolean
640 	 */
641 	public boolean isPreferPreciseTransformers() {
642 		return preferPreciseTransformers;
643 	}
644 
645 	/**
646 	 * Set the preferPreciseTransformers. Default false.
647 	 * @param preferPreciseTransformers the boolean to set
648 	 */
649 	public void setPreferPreciseTransformers(boolean preferPreciseTransformers) {
650 		this.preferPreciseTransformers = preferPreciseTransformers;
651 	}
652 
653 }