1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
198 Class destinationType = getLanguage().getType(destination, destinationProperty);
199
200 Object sourceValue = getLanguage().get(source, sourceProperty);
201
202 Object destinationValue = getLanguage().get(destination, destinationProperty);
203
204
205 Transformer transformer = getNestedTransformer();
206
207 if (!((BeanReflector) getReflector(BeanReflector.class)).isWriteable(destination, destinationProperty)) {
208 preferredTransformationType = TRANSFORMATION_TYPE_COPY;
209 }
210
211 Object newDestinationValue = TransformerUtils.transform(transformer,
212 destinationType, destinationValue, sourceValue, locale,
213 preferredTransformationType);
214
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
299 return true;
300 }
301
302 }