1
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
47 public class MappingEngineImpl implements MappingEngine {
48
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
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
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
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
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
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())
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())
181 return;
182 else if (!conditionIsTrue && !mapping.isSkipped()) {
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
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
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
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
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