1 /*
2  * Copyright 2011 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.modelmapper.internal;
17
18 import java.lang.reflect.Constructor;
19 import java.lang.reflect.Type;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.concurrent.ConcurrentHashMap;
23
24 import org.modelmapper.Condition;
25 import org.modelmapper.ConfigurationException;
26 import org.modelmapper.Converter;
27 import org.modelmapper.Provider;
28 import org.modelmapper.TypeMap;
29 import org.modelmapper.TypeToken;
30 import org.modelmapper.internal.converter.ConverterStore;
31 import org.modelmapper.internal.util.Iterables;
32 import org.modelmapper.internal.util.Objects;
33 import org.modelmapper.internal.util.Primitives;
34 import org.modelmapper.internal.util.Types;
35 import org.modelmapper.spi.ConstantMapping;
36 import org.modelmapper.spi.Mapping;
37 import org.modelmapper.spi.MappingContext;
38 import org.modelmapper.spi.MappingEngine;
39 import org.modelmapper.spi.PropertyMapping;
40
41 /**
42  * MappingEngine implementation that caches ConditionalConverters by source and destination type
43  * pairs.
44  * 
45  * @author Jonathan Halterman
46  */

47 public class MappingEngineImpl implements MappingEngine {
48   /** Cache of conditional converters */
49   private final Map<TypePair<?, ?>, Converter<?, ?>> converterCache = new ConcurrentHashMap<TypePair<?, ?>, Converter<?, ?>>();
50   private final InheritingConfiguration configuration;
51   private final TypeMapStore typeMapStore;
52   private final ConverterStore converterStore;
53
54   public MappingEngineImpl(InheritingConfiguration configuration) {
55     this.configuration = configuration;
56     this.typeMapStore = configuration.typeMapStore;
57     this.converterStore = configuration.converterStore;
58   }
59
60   /**
61    * Initial entry point.
62    */

63   public <S, D> D map(S source, Class<S> sourceType, D destination,
64       TypeToken<D> destinationTypeToken, String typeMapName) {
65     MappingContextImpl<S, D> context = new MappingContextImpl<S, D>(source, sourceType,
66         destination, destinationTypeToken.getRawType(), destinationTypeToken.getType(),
67         typeMapName, this);
68     D result = null;
69
70     try {
71       result = map(context);
72     } catch (ConfigurationException e) {
73       throw e;
74     } catch (ErrorsException e) {
75       throw context.errors.toMappingException();
76     } catch (Throwable t) {
77       context.errors.errorMapping(sourceType, destinationTypeToken.getType(), t);
78     }
79
80     context.errors.throwMappingExceptionIfErrorsExist();
81     return result;
82   }
83
84   /**
85    * Performs mapping using a TypeMap if one exists, else a converter if one applies, else a newly
86    * created TypeMap. Recursive entry point.
87    */

88   @Override
89   @SuppressWarnings("unchecked")
90   public <S, D> D map(MappingContext<S, D> context) {
91     MappingContextImpl<S, D> contextImpl = (MappingContextImpl<S, D>) context;
92     Class<D> destinationType = context.getDestinationType();
93
94     // Resolve some circular dependencies
95     if (!Iterables.isIterable(destinationType)) {
96       D circularDest = contextImpl.destinationForSource();
97       if (circularDest != null && circularDest.getClass().isAssignableFrom(contextImpl.getDestinationType()))
98         return circularDest;
99     }
100
101     D destination = null;
102     TypeMap<S, D> typeMap = typeMapStore.get(context.getSourceType(), context.getDestinationType(),
103         context.getTypeMapName());
104     if (typeMap != null) {
105       destination = typeMap(contextImpl, typeMap);
106     } else {
107       Converter<S, D> converter = converterFor(context);
108       if (converter != null && (context.getDestination() == null || context.getParent() != null))
109         destination = convert(context, converter);
110       else if (!Primitives.isPrimitive(context.getSourceType()) && !Primitives.isPrimitive(context.getDestinationType())) {
111         // Call getOrCreate in case TypeMap was created concurrently
112         typeMap = typeMapStore.getOrCreate(context.getSource(), context.getSourceType(),
113             context.getDestinationType(), context.getTypeMapName(), this);
114         destination = typeMap(contextImpl, typeMap);
115       } else if (context.getDestinationType().isAssignableFrom(context.getSourceType()))
116         destination = (D) context.getSource();
117     }
118
119     contextImpl.setDestination(destination, true);
120     return destination;
121   }
122
123   /**
124    * Performs a type mapping for the {@code typeMap} and {@code context}.
125    */

126   <S, D> D typeMap(MappingContextImpl<S, D> context, TypeMap<S, D> typeMap) {
127     if (context.getParent() != null && context.getDestination() == null)
128       context.setDestination(destinationProperty(context), false);
129
130     context.setTypeMap(typeMap);
131
132     @SuppressWarnings("unchecked")
133     Condition<S, D> condition = (Condition<S, D>) typeMap.getCondition();
134     boolean noSkip = condition == null || condition.applies(context);
135
136     if (noSkip && typeMap.getConverter() != null)
137       return convert(context, typeMap.getConverter());
138
139     if (context.getDestination() == null && Types.isInstantiable(context.getDestinationType())) {
140       D destination = createDestination(context);
141       if (destination == null)
142         return null;
143     }
144
145     if (noSkip) {
146       Converter<S, D> converter = typeMap.getPreConverter();
147       if (converter != null)
148         context.setDestination(convert(context, converter), true);
149
150       for (Mapping mapping : typeMap.getMappings())
151         propertyMap(mapping, context);
152
153       converter = typeMap.getPostConverter();
154       if (converter != null)
155         context.setDestination(convert(context, converter), true);
156     }
157
158     return context.getDestination();
159   }
160
161   @SuppressWarnings("unchecked")
162   private <S, D> void propertyMap(Mapping mapping, MappingContextImpl<S, D> context) {
163     MappingImpl mappingImpl = (MappingImpl) mapping;
164     String propertyPath = context.destinationPath + mappingImpl.getPath();
165     if (context.isShaded(propertyPath))
166       return;
167     if (mapping.getCondition() == null && mapping.isSkipped()) // skip()
168       return;
169
170     Object source = resolveSourceValue(context, mapping);
171     MappingContextImpl<Object, Object> propertyContext = propertyContextFor(context, source,
172         mappingImpl);
173
174     Condition<Object, Object> condition = (Condition<Object, Object>) Objects.firstNonNull(
175         mapping.getCondition(),
176         context.getTypeMap().getPropertyCondition(),
177         configuration.getPropertyCondition());
178     if (condition != null) {
179       boolean conditionIsTrue = condition.applies(propertyContext);
180       if (conditionIsTrue && mapping.isSkipped()) // when(condition).skip()
181         return;
182       else if (!conditionIsTrue && !mapping.isSkipped()) { // when(condition)
183         context.shadePath(propertyPath);
184         return;
185       }
186     }
187     setDestinationValue(context, propertyContext, mappingImpl);
188   }
189
190   @SuppressWarnings("unchecked")
191   private Object resolveSourceValue(MappingContextImpl<?, ?> context, Mapping mapping) {
192     Object source = context.getSource();
193     if (mapping instanceof PropertyMappingImpl) {
194       StringBuilder destPathBuilder = new StringBuilder().append(context.destinationPath);
195       for (Accessor accessor : (List<Accessor>) ((PropertyMapping) mapping).getSourceProperties()) {
196         destPathBuilder.append(accessor.getName()).append('.');
197         source = accessor.getValue(source);
198         context.addParentSource(destPathBuilder.toString(), source);
199         if (source == null)
200           return null;
201         if (!Iterables.isIterable(source.getClass())) {
202           Object circularDest = context.sourceToDestination.get(source);
203           if (circularDest != null)
204             context.intermediateDestinations.put(destPathBuilder.toString(), circularDest);
205         }
206       }
207     } else if (mapping instanceof ConstantMapping) {
208       source = ((ConstantMapping) mapping).getConstant();
209       context.addParentSource("", source);
210     }
211     return source;
212   }
213
214   /**
215    * Sets a mapped or converted destination value in the last mapped mutator for the given
216    * {@code mapping}. The final destination value is resolved by walking the {@code mapping}'s
217    * mutator chain and obtaining each destination value in the chain either from the cache, from a
218    * corresponding accessor, from a provider, or by instantiation, in that order.
219    */

220   @SuppressWarnings("unchecked")
221   private <S, D> void setDestinationValue(MappingContextImpl<S, D> context,
222       MappingContextImpl<Object, Object> propertyContext, MappingImpl mapping) {
223     String destPath = context.destinationPath + mapping.getPath();
224     Converter<Object, Object> converter = (Converter<Object, Object>) Objects.firstNonNull(
225         mapping.getConverter(),
226         context.getTypeMap().getPropertyConverter());
227     if (converter != null)
228       context.shadePath(destPath);
229
230     Object destination = propertyContext.getParentDestination();
231     if (destination == null)
232       return;
233
234     Mutator mutator = (Mutator) mapping.getLastDestinationProperty();
235     Accessor accessor = PropertyInfoRegistry.accessorFor(mutator.getInitialType(), mutator.getName(), configuration);
236     Object destinationValue = propertyContext.createDestinationViaProvider();
237     if (destinationValue == null && propertyContext.isProvidedDestination() && accessor != null) {
238       destinationValue = accessor.getValue(destination);
239       propertyContext.setDestination(destinationValue, false);
240     }
241
242     if (converter != null)
243       destinationValue = convert(propertyContext, converter);
244     else if (propertyContext.getSource() != null)
245       destinationValue = map(propertyContext);
246     else {
247       converter = converterFor(propertyContext);
248       if (converter != null)
249         destinationValue = convert(propertyContext, converter);
250     }
251
252     context.destinationCache.put(destPath, destinationValue);
253     if (destinationValue != null || !configuration.isSkipNullEnabled())
254       mutator.setValue(destination,
255           destinationValue == null ? Primitives.defaultValue(mutator.getType())
256               : destinationValue);
257     if (destinationValue == null)
258       context.shadePath(propertyContext.destinationPath);
259   }
260
261   /**
262    * Returns a property context.
263    */

264   @SuppressWarnings({ "rawtypes""unchecked" })
265   private MappingContextImpl<Object, Object> propertyContextFor(MappingContextImpl<?, ?> context,
266       Object source, MappingImpl mapping) {
267     Class<?> sourceType = mapping.getSourceType();
268     if (source != null)
269       sourceType = Types.deProxy(source.getClass());
270     boolean cyclic = mapping instanceof PropertyMapping && ((PropertyMappingImpl) mapping).cyclic;
271     Class<Object> destinationType = (Class<Object>) mapping.getLastDestinationProperty().getType();
272     Type genericDestinationType = context.genericDestinationPropertyType(mapping.getLastDestinationProperty().getGenericType());
273     return new MappingContextImpl(context, source, sourceType, null, destinationType, genericDestinationType,
274         mapping, !cyclic);
275   }
276
277   private <S, D> D destinationProperty(MappingContextImpl<S, D> context) {
278     if (!context.isProvidedDestination() || context.getMapping() == null)
279       return null;
280
281     Object intermediateDest = context.getParent().getDestination();
282     @SuppressWarnings("unchecked")
283     List<Mutator> mutatorChain = (List<Mutator>) context.getMapping().getDestinationProperties();
284     for (Mutator mutator : mutatorChain) {
285       if (intermediateDest == null)
286         break;
287
288       Accessor accessor = TypeInfoRegistry.typeInfoFor(intermediateDest.getClass(),
289           configuration)
290           .getAccessors()
291           .get(mutator.getName());
292       if (accessor != null)
293         intermediateDest = accessor.getValue(intermediateDest);
294     }
295
296     @SuppressWarnings("unchecked")
297     D destinationProperty = (D) intermediateDest;
298     return destinationProperty;
299   }
300
301   /**
302    * Performs a mapping using a Converter.
303    */

304   private <S, D> D convert(MappingContext<S, D> context, Converter<S, D> converter) {
305     try {
306       return converter.convert(context);
307     } catch (ErrorsException e) {
308       throw e;
309     } catch (Exception e) {
310       ((MappingContextImpl<S, D>) context).errors.errorConverting(converter,
311           context.getSourceType(), context.getDestinationType(), e);
312       return null;
313     }
314   }
315
316   /**
317    * Retrieves a converter from the store or from the cache.
318    */

319   @SuppressWarnings("unchecked")
320   private <S, D> Converter<S, D> converterFor(MappingContext<S, D> context) {
321     TypePair<?, ?> typePair = TypePair.of(context.getSourceType(), context.getDestinationType(),
322         context.getTypeMapName());
323     Converter<S, D> converter = (Converter<S, D>) converterCache.get(typePair);
324     if (converter == null) {
325       converter = converterStore.getFirstSupported(context.getSourceType(),
326           context.getDestinationType());
327       if (converter != null)
328         converterCache.put(typePair, converter);
329     }
330
331     return converter;
332   }
333
334   private <T> T instantiate(Class<T> type, Errors errors) {
335     try {
336       Constructor<T> constructor = type.getDeclaredConstructor();
337       if (!constructor.isAccessible())
338         constructor.setAccessible(true);
339       return constructor.newInstance();
340     } catch (Exception e) {
341       errors.errorInstantiatingDestination(type, e);
342       return null;
343     }
344   }
345
346   @Override
347   public <S, D> D createDestination(MappingContext<S, D> context) {
348     MappingContextImpl<S, D> contextImpl = (MappingContextImpl<S, D>) context;
349     D destination = contextImpl.createDestinationViaProvider();
350     if (destination == null)
351       destination = instantiate(context.getDestinationType(), contextImpl.errors);
352
353     contextImpl.setDestination(destination, true);
354     return destination;
355   }
356
357   InheritingConfiguration getConfiguration() {
358     return configuration;
359   }
360
361   @SuppressWarnings("unchecked")
362   <S, D> D createDestinationViaGlobalProvider(S source, Class<D> requestedType,
363       Errors errors) {
364     D destination = null;
365     Provider<D> provider = (Provider<D>) configuration.getProvider();
366     if (provider != null) {
367       destination = provider.get(new ProvisionRequestImpl<D>(source, requestedType));
368       validateDestination(requestedType, destination, errors);
369     }
370     if (destination == null)
371       destination = instantiate(requestedType, errors);
372
373     return destination;
374   }
375
376   void validateDestination(Class<?> destinationType, Object destination, Errors errors) {
377     if (destination != null && !destinationType.isAssignableFrom(destination.getClass()))
378       errors.invalidProvidedDestinationInstance(destination, destinationType);
379   }
380 }
381