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 org.modelmapper.Condition;
19 import org.modelmapper.Converter;
20 import org.modelmapper.ExpressionMap;
21 import org.modelmapper.PropertyMap;
22 import org.modelmapper.Provider;
23 import org.modelmapper.TypeMap;
24 import org.modelmapper.internal.util.Assert;
25 import org.modelmapper.internal.util.Objects;
26 import org.modelmapper.internal.util.Types;
27 import org.modelmapper.spi.DestinationSetter;
28 import org.modelmapper.spi.Mapping;
29 import org.modelmapper.spi.PropertyInfo;
30 import org.modelmapper.spi.SourceGetter;
31 import org.modelmapper.spi.TypeSafeSourceGetter;
32
33 import java.util.ArrayList;
34 import java.util.Collections;
35 import java.util.HashSet;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Set;
40 import java.util.Stack;
41 import java.util.TreeMap;
42
43 /**
44  * TypeMap implementation.
45  * 
46  * @author Jonathan Halterman
47  */

48 class TypeMapImpl<S, D> implements TypeMap<S, D> {
49   private final Class<S> sourceType;
50   private final Class<D> destinationType;
51   private final String name;
52   final InheritingConfiguration configuration;
53   private final MappingEngineImpl engine;
54   /** Guarded by "mappings" */
55   private final Map<String, InternalMapping> mappings = new TreeMap<String, InternalMapping>();
56   private Converter<S, D> converter;
57   private Converter<S, D> preConverter;
58   private Converter<S, D> postConverter;
59   private Condition<?, ?> condition;
60   private Provider<D> provider;
61   private Converter<?, ?> propertyConverter;
62   private Condition<?, ?> propertyCondition;
63   private Provider<?> propertyProvider;
64
65   TypeMapImpl(Class<S> sourceType, Class<D> destinationType, String name,
66       InheritingConfiguration configuration, MappingEngineImpl engine) {
67     this.sourceType = sourceType;
68     this.destinationType = destinationType;
69     this.name = name;
70     this.configuration = configuration;
71     this.engine = engine;
72   }
73
74   private TypeMapImpl(TypeMapImpl<? super S, ? super D> baseTypeMap, Class<S> sourceTYpe, Class<D> destinationType) {
75     this.sourceType = sourceTYpe;
76     this.destinationType = destinationType;
77     this.name = baseTypeMap.name;
78     this.configuration = baseTypeMap.configuration;
79     this.engine = baseTypeMap.engine;
80
81     synchronized (baseTypeMap.mappings) {
82       mappings.putAll(baseTypeMap.mappings);
83     }
84   }
85
86   @Override
87   public TypeMap<S, D> addMappings(PropertyMap<S, D> propertyMap) {
88     if (sourceType.isEnum() || destinationType.isEnum())
89       throw new Errors().mappingForEnum().toConfigurationException();
90
91     synchronized (mappings) {
92       for (InternalMapping mapping : ExplicitMappingBuilder.build(sourceType,
93           destinationType, configuration, propertyMap)) {
94         InternalMapping existingMapping = addMapping(mapping);
95         if (existingMapping != null && existingMapping.isExplicit())
96           throw new Errors().duplicateMapping(mapping.getLastDestinationProperty())
97               .toConfigurationException();
98       }
99     }
100     return this;
101   }
102
103   @Override
104   public Condition<?, ?> getCondition() {
105     return condition;
106   }
107
108   @Override
109   public Converter<S, D> getConverter() {
110     return converter;
111   }
112
113   @Override
114   public Class<D> getDestinationType() {
115     return destinationType;
116   }
117
118   @Override
119   public List<Mapping> getMappings() {
120     synchronized (mappings) {
121       return new ArrayList<Mapping>(mappings.values());
122     }
123   }
124
125   @Override
126   public String getName() {
127     return name;
128   }
129
130   @Override
131   public Converter<S, D> getPostConverter() {
132     return postConverter;
133   }
134
135   @Override
136   public Converter<S, D> getPreConverter() {
137     return preConverter;
138   }
139
140   @Override
141   public Condition<?, ?> getPropertyCondition() {
142     return propertyCondition;
143   }
144
145   @Override
146   public Converter<?, ?> getPropertyConverter() {
147     return propertyConverter;
148   }
149
150   @Override
151   public Provider<?> getPropertyProvider() {
152     return propertyProvider;
153   }
154
155   @Override
156   public Provider<D> getProvider() {
157     return provider;
158   }
159
160   @Override
161   public Class<S> getSourceType() {
162     return sourceType;
163   }
164
165   @Override
166   public List<PropertyInfo> getUnmappedProperties() {
167     PathProperties pathProperties = getDestinationProperties();
168
169     synchronized (mappings) {
170       for (Map.Entry<String, InternalMapping> entry : mappings.entrySet()) {
171         pathProperties.matchAndRemove(entry.getKey());
172       }
173     }
174
175     return pathProperties.get();
176   }
177
178   @Override
179   public D map(S source) {
180     Class<S> sourceType = Types.deProxy(source.getClass());
181     MappingContextImpl<S, D> context = new MappingContextImpl<S, D>(source, sourceType, null,
182         destinationType, null, name, engine);
183     D result = null;
184
185     try {
186       result = engine.typeMap(context, this);
187     } catch (Throwable t) {
188       context.errors.errorMapping(sourceType, destinationType, t);
189     }
190
191     context.errors.throwMappingExceptionIfErrorsExist();
192     return result;
193   }
194
195   @Override
196   public void map(S source, D destination) {
197     Class<S> sourceType = Types.deProxy(source.getClass());
198     MappingContextImpl<S, D> context = new MappingContextImpl<S, D>(source, sourceType,
199         destination, destinationType, null, name, engine);
200
201     try {
202       engine.typeMap(context, this);
203     } catch (Throwable t) {
204       context.errors.errorMapping(sourceType, destinationType, t);
205     }
206
207     context.errors.throwMappingExceptionIfErrorsExist();
208   }
209
210   @Override
211   public TypeMap<S, D> setCondition(Condition<?, ?> condition) {
212     this.condition = Assert.notNull(condition, "condition");
213     return this;
214   }
215
216   @Override
217   public TypeMap<S, D> setConverter(Converter<S, D> converter) {
218     this.converter = Assert.notNull(converter, "converter");
219     return this;
220   }
221
222   @Override
223   public TypeMap<S, D> setPostConverter(Converter<S, D> converter) {
224     this.postConverter = Assert.notNull(converter, "converter");
225     return this;
226   }
227
228   @Override
229   public TypeMap<S, D> setPreConverter(Converter<S, D> converter) {
230     this.preConverter = Assert.notNull(converter, "converter");
231     return this;
232   }
233
234   @Override
235   public TypeMap<S, D> setPropertyCondition(Condition<?, ?> condition) {
236     propertyCondition = Assert.notNull(condition, "condition");
237     return this;
238   }
239
240   @Override
241   public TypeMap<S, D> setPropertyConverter(Converter<?, ?> converter) {
242     propertyConverter = Assert.notNull(converter, "converter");
243     return this;
244   }
245
246   @Override
247   public TypeMap<S, D> setPropertyProvider(Provider<?> provider) {
248     propertyProvider = Assert.notNull(provider, "provider");
249     return this;
250   }
251
252   @Override
253   public TypeMap<S, D> setProvider(Provider<D> provider) {
254     this.provider = Assert.notNull(provider, "provider");
255     return this;
256   }
257
258   @Override
259   public <V> TypeMap<S, D> addMapping(SourceGetter<S> sourceGetter, DestinationSetter<D, V> destinationSetter) {
260     new ReferenceMapExpressionImpl<S, D>(this).map(sourceGetter, destinationSetter);
261     return this;
262   }
263
264   @Override
265   public TypeMap<S, D> addMappings(ExpressionMap<S, D> mapper) {
266     mapper.configure(new ConfigurableConditionExpressionImpl<S, D>(this));
267     return this;
268   }
269
270   @Override
271   public String toString() {
272     StringBuilder b = new StringBuilder();
273     b.append("TypeMap[")
274         .append(sourceType.getSimpleName())
275         .append(" -> ")
276         .append(destinationType.getSimpleName());
277     if (name != null)
278       b.append(' ').append(name);
279     return b.append(']').toString();
280   }
281
282   @Override
283   public void validate() {
284     if (converter != null || preConverter != null || postConverter != null)
285       return;
286
287     Errors errors = new Errors();
288     List<PropertyInfo> unmappedProperties = getUnmappedProperties();
289     if (!unmappedProperties.isEmpty())
290       errors.errorUnmappedProperties(this, unmappedProperties);
291
292     errors.throwValidationExceptionIfErrorsExist();
293   }
294
295   @Override
296   public <DS extends S, DD extends D> TypeMap<S, D> include(Class<DS> sourceType, Class<DD> destinationType) {
297     TypeMapImpl<DS, DD> derivedTypeMap = new TypeMapImpl<DS, DD>(this, sourceType, destinationType);
298     configuration.typeMapStore.put(derivedTypeMap);
299     return this;
300   }
301
302   @Override
303   public TypeMap<S, D> include(Class<? super D> baseDestinationType) {
304     if (provider == null)
305       provider = new Provider<D>() {
306         @Override
307         public D get(ProvisionRequest<D> request) {
308           return Objects.instantiate(destinationType);
309         }
310       };
311
312     configuration.typeMapStore.put(sourceType, baseDestinationType, this);
313     return this;
314   }
315
316   @Override
317   public TypeMap<S, D> includeBase(Class<? super S> sourceType, Class<? super D> destinationType) {
318     @SuppressWarnings("unchecked")
319     TypeMapImpl<? super S, ? super D> baseTypeMap = (TypeMapImpl<? super S, ? super D>)
320         configuration.typeMapStore.get(sourceType, destinationType, name);
321
322     Assert.notNull(baseTypeMap, "Cannot find base TypeMap");
323
324     synchronized (baseTypeMap.mappings) {
325       for (Map.Entry<String, InternalMapping> entry : baseTypeMap.mappings.entrySet()) {
326         addMapping(entry.getValue());
327       }
328     }
329
330     return this;
331   }
332
333   @Override
334   public <P> TypeMap<S, D> include(TypeSafeSourceGetter<S, P> sourceGetter, Class<P> propertyType) {
335     @SuppressWarnings("unchecked")
336     TypeMapImpl<? super S, ? super D> childTypeMap = (TypeMapImpl<? super S, ? super D>)
337         configuration.typeMapStore.get(propertyType, destinationType, name);
338     Assert.notNull(childTypeMap, "Cannot find child TypeMap");
339
340     List<Accessor> accessors = PropertyReferenceCollector.collect(this, sourceGetter);
341     for (Mapping mapping : childTypeMap.getMappings()) {
342       InternalMapping internalMapping = (InternalMapping) mapping;
343       addMapping(internalMapping.createMergedCopy(accessors, Collections.<PropertyInfo>emptyList()));
344     }
345     return this;
346   }
347
348   @Override
349   public TypeMap<S, D> implicitMappings() {
350     ImplicitMappingBuilder.build(nullthis, configuration.typeMapStore, configuration.converterStore);
351     return this;
352   }
353
354   void addMappingIfAbsent(InternalMapping mapping) {
355     synchronized (mappings) {
356       if (!mappings.containsKey(mapping.getPath()))
357         mappings.put(mapping.getPath(), mapping);
358     }
359   }
360
361   InternalMapping addMapping(InternalMapping mapping) {
362     synchronized (mappings) {
363       return mappings.put(mapping.getPath(), mapping);
364     }
365   }
366
367   /**
368    * Used by PropertyMapBuilder to determine if a skipped mapping exists for the {@code path}. No
369    * need to synchronize here since the TypeMap is not exposed publicly yet.
370    */

371   boolean isSkipped(String path) {
372     Mapping mapping = mappings.get(path);
373     return mapping != null && mapping.isSkipped();
374   }
375
376   /**
377    * Used by ImplicitMappingBuilder to determine if a mapping for the {@code path} already exists.
378    * No need to synchronize here since the TypeMap is not exposed publicly yet.
379    */

380   Mapping mappingFor(String path) {
381     return mappings.get(path);
382   }
383
384   boolean isFullMatching() {
385     return getUnmappedProperties().isEmpty()
386         || configuration.valueAccessStore.getFirstSupportedReader(sourceType) == null;
387   }
388
389   private PathProperties getDestinationProperties() {
390     PathProperties pathProperties = new PathProperties();
391     Set<Class<?>> classes = new HashSet<Class<?>>();
392
393     Stack<Property> propertyStack = new Stack<Property>();
394     propertyStack.push(new Property("", TypeInfoRegistry.typeInfoFor(destinationType, configuration)));
395
396     while (!propertyStack.isEmpty()) {
397       Property property = propertyStack.pop();
398       classes.add(property.typeInfo.getType());
399       for (Map.Entry<String, Mutator> entry : property.typeInfo.getMutators().entrySet()) {
400         if (entry.getValue() instanceof PropertyInfoImpl.FieldPropertyInfo
401             && !configuration.isFieldMatchingEnabled()) {
402           continue;
403         }
404
405         String path = property.prefix + entry.getKey() + ".";
406         Mutator mutator = entry.getValue();
407         pathProperties.pathProperties.add(new PathProperty(path, mutator));
408
409         if (!classes.contains(mutator.getType())
410             && Types.mightContainsProperties(mutator.getType()))
411           propertyStack.push(new Property(path, TypeInfoRegistry.typeInfoFor(mutator.getType(), configuration)));
412       }
413     }
414     return pathProperties;
415   }
416
417   private static final class Property {
418     String prefix;
419     TypeInfo<?> typeInfo;
420
421     public Property(String prefix, TypeInfo<?> typeInfo) {
422       this.prefix = prefix;
423       this.typeInfo = typeInfo;
424     }
425   }
426
427   private static final class PathProperties {
428     List<PathProperty> pathProperties = new ArrayList<PathProperty>();
429
430     private void matchAndRemove(String path) {
431       int startIndex = 0;
432       int endIndex;
433       while ((endIndex = path.indexOf(".", startIndex)) != -1) {
434         String currentPath = path.substring(0, endIndex + 1);
435
436         Iterator<PathProperty> iterator = pathProperties.iterator();
437         while (iterator.hasNext())
438           if (iterator.next().path.equals(currentPath))
439             iterator.remove();
440
441         startIndex = endIndex + 1;
442       }
443
444       Iterator<PathProperty> iterator = pathProperties.iterator();
445       while (iterator.hasNext())
446         if (iterator.next().path.startsWith(path))
447           iterator.remove();
448     }
449
450     public List<PropertyInfo> get() {
451       List<PropertyInfo> mutators = new ArrayList<PropertyInfo>(pathProperties.size());
452       for (PathProperty pathProperty : pathProperties)
453         mutators.add(pathProperty.mutator);
454       return mutators;
455     }
456   }
457
458   private static final class PathProperty {
459     String path;
460     Mutator mutator;
461
462     private PathProperty(String path, Mutator mutator) {
463       this.path = path;
464       this.mutator = mutator;
465     }
466   }
467 }
468