View Javadoc

1   /*
2    * Copyright 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.copiers;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Iterator;
21  import java.util.Locale;
22  import java.util.Map;
23  
24  import net.sf.composite.util.ObjectUtils;
25  import net.sf.morph.Defaults;
26  import net.sf.morph.lang.DecoratedLanguage;
27  import net.sf.morph.lang.Language;
28  import net.sf.morph.lang.languages.LanguageDecorator;
29  import net.sf.morph.lang.languages.SimpleLanguage;
30  import net.sf.morph.reflect.BeanReflector;
31  import net.sf.morph.reflect.ContainerReflector;
32  import net.sf.morph.reflect.Reflector;
33  import net.sf.morph.reflect.reflectors.ArrayReflector;
34  import net.sf.morph.reflect.reflectors.CollectionReflector;
35  import net.sf.morph.reflect.reflectors.EnumerationReflector;
36  import net.sf.morph.reflect.reflectors.IteratorReflector;
37  import net.sf.morph.reflect.reflectors.ListReflector;
38  import net.sf.morph.reflect.reflectors.ObjectReflector;
39  import net.sf.morph.reflect.reflectors.SetReflector;
40  import net.sf.morph.reflect.reflectors.SimpleDelegatingReflector;
41  import net.sf.morph.reflect.reflectors.SortedSetReflector;
42  import net.sf.morph.transform.Converter;
43  import net.sf.morph.transform.DecoratedConverter;
44  import net.sf.morph.transform.DecoratedCopier;
45  import net.sf.morph.transform.NodeCopier;
46  import net.sf.morph.transform.TransformationException;
47  import net.sf.morph.transform.Transformer;
48  import net.sf.morph.transform.transformers.BaseTransformer;
49  import net.sf.morph.util.TransformerUtils;
50  
51  /**
52   * Maps property expressions between objects using a Morph Language.
53   * The map you specify should have String keys, and in its simplest form,
54   * will have String values as well.  However, the values in the mapping
55   * may be containers of Strings as well, allowing you to map > 1 destination
56   * for a given source property.
57   *
58   * @author Matt Benson
59   * @since Morph 1.1
60   */
61  public class PropertyExpressionMappingCopier extends BaseTransformer implements
62  		DecoratedConverter, DecoratedCopier, NodeCopier {
63  
64  	private static final ContainerReflector DEST_REFLECTOR = new SimpleDelegatingReflector(
65  			new Reflector[] {
66  					new ListReflector(), new SortedSetReflector(), new SetReflector(),
67  					new EnumerationReflector(), new IteratorReflector(),
68  					new ArrayReflector(), new CollectionReflector(),
69  					new ObjectReflector() });
70  
71  	private static final Class[] SOURCE_AND_DEST_CLASSES = new Class[] { Object.class };
72  
73  	private Map mapping;
74  	private Language language;
75  
76  	/**
77  	 * Create a new PropertyExpressionMappingCopier.
78  	 */
79  	public PropertyExpressionMappingCopier() {
80  		super();
81  	}
82  
83  	/**
84  	 * Create a new PropertyExpressionMappingCopier.
85  	 * @param mapping property mapping
86  	 */
87  	public PropertyExpressionMappingCopier(Map mapping) {
88  		this();
89  		setMapping(mapping);
90  	}
91  
92  	/**
93  	 * Get the language of this PropertyExpressionMappingCopier.
94  	 * @return the language
95  	 */
96  	public synchronized Language getLanguage() {
97  		if (language == null) {
98  			SimpleLanguage lang = Defaults.createLanguage();
99  			lang.setConverter((Converter) getNestedTransformer());
100 			lang.setReflector((BeanReflector) getReflector(BeanReflector.class));
101 			setLanguage(lang);
102 		}
103 		return language;
104 	}
105 
106 	/**
107 	 * Set the language of this PropertyExpressionMappingCopier.
108 	 * @param language the language to set
109 	 */
110 	public synchronized void setLanguage(Language language) {
111 		this.language = language instanceof DecoratedLanguage ? language
112 				: new LanguageDecorator(language);
113 	}
114 
115 	/**
116 	 * {@inheritDoc}
117 	 */
118 	protected void initializeImpl() throws Exception {
119 		super.initializeImpl();
120 		if (ObjectUtils.isEmpty(mapping)) {
121 			throw new TransformationException(
122 					"You must specify which properties you would like the "
123 							+ getClass().getName()
124 							+ " to copy by setting the mapping property");
125 		}
126 		ensureOnlyStrings(mapping.keySet());
127 		ensureOnlyStrings(expand(mapping.values()));
128 	}
129 
130 	/**
131 	 * Ensure all collection entries are Strings.
132 	 * @param collection
133 	 */
134 	private void ensureOnlyStrings(Collection collection) {
135 		for (Iterator i = collection.iterator(); i.hasNext();) {
136 			Object value = i.next();
137 			if (!(value instanceof String)) {
138 				throw new TransformationException(
139 						"An invalid mapping element was specified: "
140 								+ ObjectUtils.getObjectDescription(value)
141 								+ ".  Mapping elements must be Strings");
142 			}
143 		}
144 	}
145 
146 	/**
147 	 * Unwrap collections nested 1 level if present.
148 	 * @param collection
149 	 * @return Collection
150 	 */
151 	private Collection expand(Collection collection) {
152 		ArrayList result = new ArrayList(collection.size());
153 		for (Iterator outerIter = collection.iterator(); outerIter.hasNext();) {
154 			for (Iterator innerIter = DEST_REFLECTOR.getIterator(outerIter.next()); innerIter
155 					.hasNext();) {
156 				result.add(innerIter.next());
157 			}
158 		}
159 		return result;
160 	}
161 
162 	/**
163 	 * {@inheritDoc}
164 	 */
165 	protected void copyImpl(Object destination, Object source, Locale locale,
166 			Integer preferredTransformationType) throws Exception {
167 		for (Iterator it = getMapping().entrySet().iterator(); it.hasNext();) {
168 			Map.Entry e = (Map.Entry) it.next();
169 			String sourceProperty = (String) e.getKey();
170 			for (Iterator v = DEST_REFLECTOR.getIterator(e.getValue()); v.hasNext();) {
171 				copyProperty(sourceProperty, source, (String) v.next(), destination,
172 						locale, preferredTransformationType);
173 			}
174 		}
175 	}
176 
177 	/**
178 	 * Copy <code>source.sourceProperty</code> to <code>dest.destProperty</code>.
179 	 * @param sourceProperty
180 	 * @param source
181 	 * @param destinationProperty
182 	 * @param destination
183 	 * @param locale
184 	 * @param preferredTransformationType
185 	 */
186 	protected void copyProperty(String sourceProperty, Object source,
187 			String destinationProperty, Object destination, Locale locale,
188 			Integer preferredTransformationType) throws Exception {
189 		if (getLog().isTraceEnabled()) {
190 			getLog().trace(
191 					"Copying property '" + sourceProperty + "' of "
192 							+ ObjectUtils.getObjectDescription(source) + " to property '"
193 							+ destinationProperty + "' of "
194 							+ ObjectUtils.getObjectDescription(destination));
195 		}
196 
197 		// determine the destination type
198 		Class destinationType = getLanguage().getType(destination, destinationProperty);
199 		// determine the value of the source property
200 		Object sourceValue = getLanguage().get(source, sourceProperty);
201 		// determine the current value of the destination property, if any
202 		Object destinationValue = getLanguage().get(destination, destinationProperty);
203 
204 		// choose a transformer to use
205 		Transformer transformer = getNestedTransformer();
206 
207 		if (!((BeanReflector) getReflector(BeanReflector.class)).isWriteable(destination, destinationProperty)) {
208 			preferredTransformationType = TRANSFORMATION_TYPE_COPY;
209 		}
210 		// determine the new value that will be set on the destination
211 		Object newDestinationValue = TransformerUtils.transform(transformer,
212 				destinationType, destinationValue, sourceValue, locale,
213 				preferredTransformationType);
214 		// set the transformed value on the destination
215 		getLanguage().set(destination, destinationProperty, newDestinationValue);
216 
217 		if (getLog().isTraceEnabled()) {
218 			getLog().trace(
219 					"Done copying property '" + sourceProperty + "' to property '"
220 							+ destinationProperty + "'.  sourceValue was "
221 							+ ObjectUtils.getObjectDescription(sourceValue)
222 							+ " and destinationValue was "
223 							+ ObjectUtils.getObjectDescription(destinationValue));
224 		}
225 	}
226 
227 	/**
228 	 * {@inheritDoc}
229 	 */
230 	protected Class[] getDestinationClassesImpl() throws Exception {
231 		return SOURCE_AND_DEST_CLASSES;
232 	}
233 
234 	/**
235 	 * {@inheritDoc}
236 	 */
237 	protected Class[] getSourceClassesImpl() throws Exception {
238 		return SOURCE_AND_DEST_CLASSES;
239 	}
240 
241 	/**
242 	 * Get the mapping of this PropertyExpressionMappingCopier.
243 	 * @return the mapping
244 	 */
245 	public synchronized Map getMapping() {
246 		return mapping;
247 	}
248 
249 	/**
250 	 * Set the mapping of this PropertyExpressionMappingCopier.
251 	 * @param mapping the mapping to set
252 	 */
253 	public synchronized void setMapping(Map mapping) {
254 		this.mapping = mapping;
255 		setInitialized(false);
256 	}
257 
258 	/**
259 	 * {@inheritDoc}
260 	 */
261 	public synchronized void setSourceClasses(Class[] sourceClasses) {
262 		super.setSourceClasses(sourceClasses);
263 	}
264 
265 	/**
266 	 * {@inheritDoc}
267 	 */
268 	public synchronized void setDestinationClasses(Class[] destinationClasses) {
269 		super.setDestinationClasses(destinationClasses);
270 	}
271 
272 	/**
273 	 * {@inheritDoc}
274 	 */
275 	public Object createReusableSource(Class destinationClass, Object source) {
276 		return super.createReusableSource(destinationClass, source);
277 	}
278 
279 	/**
280 	 * {@inheritDoc}
281 	 */
282 	public void setNestedTransformer(Transformer nestedTransformer) {
283 		super.setNestedTransformer(nestedTransformer);
284 	}
285 
286 	/**
287 	 * {@inheritDoc}
288 	 */
289 	public Transformer getNestedTransformer() {
290 		return super.getNestedTransformer();
291 	}
292 
293 	/**
294 	 * {@inheritDoc}
295 	 * @see net.sf.morph.transform.transformers.BaseTransformer#isWrappingRuntimeExceptions()
296 	 */
297 	protected boolean isWrappingRuntimeExceptions() {
298 		//we throw LanguageExceptions which should be wrapped as TransformationExceptions:
299 	    return true;
300     }
301 
302 }