View Javadoc

1   /*
2    * Copyright 2004-2005 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.ArrayList;
19  import java.util.Arrays;
20  import java.util.List;
21  import java.util.Locale;
22  import java.util.Map;
23  
24  import net.sf.morph.reflect.Reflector;
25  import net.sf.morph.reflect.reflectors.SimpleDelegatingReflector;
26  import net.sf.morph.reflect.reflectors.SimpleInstantiatingReflector;
27  import net.sf.morph.transform.TransformationException;
28  import net.sf.morph.transform.Transformer;
29  import net.sf.morph.transform.converters.IdentityConverter;
30  import net.sf.morph.transform.copiers.ContainerCopier;
31  import net.sf.morph.transform.copiers.PropertyNameMatchingCopier;
32  import net.sf.morph.util.ClassUtils;
33  import net.sf.morph.util.TransformerUtils;
34  import net.sf.morph.util.TypeMap;
35  
36  /**
37   * <p>
38   * A transformer that performs a deep copy of the data in an object graph while
39   * also allowing the types in the destination graph to be different than the
40   * types in the source graph. A typical application of this transformer is to
41   * transform a domain model into a graph of transfer objects suitable for
42   * transferring to a remote client or another tier of an n-tier architecture.
43   * </p>
44   *
45   * <p>
46   * Note: if you are using this transformer to transformer an object graph
47   * composed of CGLIB proxies (such as an object graph created by Hibernate), you
48   * may need to specify your source types as interfaces, because CGLIB proxies
49   * may not necessarily be instances of the expected class.
50   * </p>
51   *
52   * @author Matt Sgarlata
53   * @since December 1, 2005
54   */
55  public class TypeChangingGraphTransformer extends SimpleDelegatingTransformer {
56  
57  	private Map sourceToDestinationTypeMapping;
58  
59  	/**
60  	 * Construct a new TypeChangingGraphTransformer.
61  	 */
62  	public TypeChangingGraphTransformer() {
63  	}
64  
65  	protected Transformer getTransformer(Class transformerType) {
66  		Object[] components = getComponents();
67  		for (int i = 0; i < components.length; i++) {
68  			if (transformerType.isAssignableFrom(components[i].getClass())) {
69  				return (Transformer) components[i];
70  			}
71  		}
72  		throw new TransformationException(
73  				"Could not find a component of type '"
74  						+ transformerType.getName() + "' in transformer "
75  						+ this);
76  	}
77  
78  	protected ContainerCopier getContainerCopier() {
79  		return (ContainerCopier) getTransformer(ContainerCopier.class);
80  	}
81  
82  	protected PropertyNameMatchingCopier getPropertyNameMatchingCopier() {
83  		return (PropertyNameMatchingCopier) getTransformer(PropertyNameMatchingCopier.class);
84  	}
85  
86  	protected IdentityConverter getIdentityConverter() {
87  		return (IdentityConverter) getTransformer(IdentityConverter.class);
88  	}
89  
90  	protected Reflector createReflector() {
91  		// create a reflector that will instantiate classes of the destination
92  		// types designated by sourceToDestinationTypeMapping when it encounters
93  		// objects that are instances of the source types designated in
94  		// sourceToDestinationTypeMapping
95  		SimpleInstantiatingReflector instantiatingReflector = new SimpleInstantiatingReflector();
96  		instantiatingReflector.setRequestedToInstantiatedTypeMap(sourceToDestinationTypeMapping);
97  		// create a delegating reflector that will first do the instantiations
98  		// as designated above before using other reflectors to perform standard
99  		// reflection operations like reading property names
100 		SimpleDelegatingReflector reflector = new SimpleDelegatingReflector();
101 		List components = new ArrayList(Arrays.asList(reflector.getComponents()));
102 		components.add(0, instantiatingReflector);
103 		reflector.setComponents(components.toArray(new Reflector[components.size()]));
104 		return reflector;
105 	}
106 
107 	protected void initializeImpl() throws Exception {
108 		super.initializeImpl();
109 
110 		Reflector reflector = createReflector();
111 
112 		ContainerCopier cc = getContainerCopier();
113 		cc.setContainedSourceToDestinationTypeMap(getSourceToDestinationTypeMapping());
114 		cc.setReflector(reflector);
115 
116 		PropertyNameMatchingCopier pnmc = getPropertyNameMatchingCopier();
117 		pnmc.setReflector(reflector);
118 
119 		// setup the IdentityConverter so that it will transform null to null
120 		// and other primitives to primitive types but not pick up
121 		// transformations in the sourceToDestinationTypeMap
122 		// TODO think about this; maybe we can make this more explicit to exclude _only_ stuff from the s-to-d map
123 		IdentityConverter identityConverter = getIdentityConverter();
124 		identityConverter.setSourceClasses(ClassUtils.getImmutableTypes());
125 
126 		this.setReflector(reflector);
127 	}
128 
129 	protected Object convertImpl(Class destinationType, Object source, Locale locale) throws Exception {
130 		Class transformedDestinationType =
131 			TransformerUtils.getMappedDestinationType(
132 				getSourceToDestinationTypeMapping(), destinationType);
133 		if (transformedDestinationType == null) {
134 			transformedDestinationType = destinationType;
135 		}
136 		return super.convertImpl(transformedDestinationType, source, locale);
137 	}
138 
139 	public Map getSourceToDestinationTypeMapping() {
140 		return sourceToDestinationTypeMapping;
141 	}
142 
143 	public void setSourceToDestinationTypeMapping(Map sourceToDestinationTypeMapping) {
144 		this.sourceToDestinationTypeMapping = new TypeMap(sourceToDestinationTypeMapping);
145 	}
146 
147 }