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.Converter;
19 import org.modelmapper.PropertyMap;
20 import org.modelmapper.TypeMap;
21 import org.modelmapper.internal.util.Primitives;
22 import org.modelmapper.internal.util.Types;
23
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.concurrent.ConcurrentHashMap;
30
31 /**
32  * @author Jonathan Halterman
33  */

34 public final class TypeMapStore {
35   private final Map<TypePair<?, ?>, TypeMap<?, ?>> typeMaps = new ConcurrentHashMap<TypePair<?, ?>, TypeMap<?, ?>>();
36   private final Map<TypePair<?, ?>, TypeMap<?, ?>> immutableTypeMaps = Collections.unmodifiableMap(typeMaps);
37   private final Object lock = new Object();
38   /** Default configuration */
39   private final InheritingConfiguration config;
40
41   TypeMapStore(InheritingConfiguration config) {
42     this.config = config;
43   }
44
45   /**
46    * Creates a TypeMap. If {@code converter} is null, the TypeMap is configured with implicit
47    * mappings, else the {@code converter} is set against the TypeMap.
48    */

49   public <S, D> TypeMap<S, D> create(S source, Class<S> sourceType, Class<D> destinationType,
50       String typeMapName, InheritingConfiguration configuration, MappingEngineImpl engine) {
51     synchronized (lock) {
52       TypeMapImpl<S, D> typeMap = new TypeMapImpl<S, D>(sourceType, destinationType, typeMapName,
53           configuration, engine);
54       if (configuration.isImplicitMappingEnabled()
55           && Types.mightContainsProperties(typeMap.getSourceType())
56           && Types.mightContainsProperties(typeMap.getDestinationType()))
57         ImplicitMappingBuilder.build(source, typeMap, config.typeMapStore, config.converterStore);
58       typeMaps.put(TypePair.of(sourceType, destinationType, typeMapName), typeMap);
59       return typeMap;
60     }
61   }
62
63   /**
64    * Creates a  empty TypeMap. If {@code converter} is null, the TypeMap is configured with implicit
65    * mappings, else the {@code converter} is set against the TypeMap.
66    */

67   public <S, D> TypeMap<S, D> createEmptyTypeMap(Class<S> sourceType, Class<D> destinationType,
68       String typeMapName, InheritingConfiguration configuration, MappingEngineImpl engine) {
69     synchronized (lock) {
70       TypeMapImpl<S, D> typeMap = new TypeMapImpl<S, D>(sourceType, destinationType, typeMapName,
71           configuration, engine);
72       typeMaps.put(TypePair.of(sourceType, destinationType, typeMapName), typeMap);
73       return typeMap;
74     }
75   }
76
77   public Collection<TypeMap<?, ?>> get() {
78     return immutableTypeMaps.values();
79   }
80
81   /**
82    * Returns a TypeMap for the {@code sourceType}, {@code destinationType} and {@code typeMapName},
83    * else null if none exists.
84    */

85   @SuppressWarnings("unchecked")
86   public <S, D> TypeMap<S, D> get(Class<S> sourceType, Class<D> destinationType, String typeMapName) {
87     TypeMap<S, D> typeMap = getTypeMap(sourceType, destinationType, typeMapName);
88     if (typeMap != null)
89       return typeMap;
90
91     for (TypePair<?, ?> typePair : getPrimitiveWrapperTypePairs(sourceType, destinationType, typeMapName)) {
92       typeMap = (TypeMap<S, D>) typeMaps.get(typePair);
93       if (typeMap != null)
94         return typeMap;
95     }
96
97     return null;
98   }
99
100   /**
101    * Gets or creates a TypeMap. If {@code converter} is null, the TypeMap is configured with
102    * implicit mappings, else the {@code converter} is set against the TypeMap.
103    */

104   public <S, D> TypeMap<S, D> getOrCreate(S source, Class<S> sourceType, Class<D> destinationType,
105       String typeMapName, MappingEngineImpl engine) {
106     return getOrCreate(source, sourceType, destinationType, typeMapName, nullnull,
107         engine);
108   }
109
110   /**
111    * Gets or creates a TypeMap. If {@code converter} is null, the TypeMap is configured with
112    * implicit mappings, else the {@code converter} is set against the TypeMap.
113    * 
114    * @param propertyMap to add mappings for (nullable)
115    * @param converter to set (nullable)
116    */

117   @SuppressWarnings("unchecked")
118   public <S, D> TypeMap<S, D> getOrCreate(S source, Class<S> sourceType, Class<D> destinationType,
119       String typeMapName, PropertyMap<S, D> propertyMap, Converter<S, D> converter,
120       MappingEngineImpl engine) {
121     synchronized (lock) {
122       TypeMapImpl<S, D> typeMap = getTypeMap(sourceType, destinationType, typeMapName);
123
124       if (typeMap == null) {
125         typeMap = new TypeMapImpl<S, D>(sourceType, destinationType, typeMapName, config, engine);
126         if (propertyMap != null)
127           typeMap.addMappings(propertyMap);
128         if (converter == null && config.isImplicitMappingEnabled()
129             && Types.mightContainsProperties(typeMap.getSourceType())
130             && Types.mightContainsProperties(typeMap.getDestinationType()))
131           ImplicitMappingBuilder.build(source, typeMap, config.typeMapStore, config.converterStore);
132
133         if (typeMap.isFullMatching()) {
134           typeMaps.put(TypePair.of(sourceType, destinationType, typeMapName), typeMap);
135         }
136       } else if (propertyMap != null) {
137         typeMap.addMappings(propertyMap);
138       }
139
140       if (converter != null)
141         typeMap.setConverter(converter);
142       return typeMap;
143     }
144   }
145
146   /**
147    * Puts a typeMap into store
148    *
149    * @throws IllegalArgumentException if {@link TypePair} of typeMap is already exists in the store
150    */

151   public void put(TypeMap<?, ?> typeMap) {
152     TypePair<?, ?> typePair = TypePair.of(typeMap.getSourceType(),
153         typeMap.getDestinationType(), typeMap.getName());
154     synchronized (lock) {
155       if (typeMaps.containsKey(typePair))
156         throw new IllegalArgumentException("TypeMap exists in the store: " + typePair.toString());
157       typeMaps.put(typePair, typeMap);
158     }
159   }
160
161   /**
162    * Puts a typeMap into store
163    *
164    * @throws IllegalArgumentException if {@link TypePair} of typeMap is already exists in the store
165    */

166   public <S, D> void put(Class<S> sourceType, Class<D> destinationType, TypeMap<S, ? extends D> typeMap) {
167     TypePair<S, D> typePair = TypePair.of(sourceType, destinationType,
168         typeMap.getName());
169     synchronized (lock) {
170       if (typeMaps.containsKey(typePair))
171         throw new IllegalArgumentException("TypeMap exists in the store: " + typePair.toString());
172       typeMaps.put(typePair, typeMap);
173     }
174   }
175
176   private <S, D> List<TypePair<?, ?>> getPrimitiveWrapperTypePairs(Class<S> sourceType, Class<D> destinationType, String typeMapName) {
177     List<TypePair<?, ?>> typePairs = new ArrayList<TypePair<?, ?>>(1);
178     if (Primitives.isPrimitive(sourceType)) {
179       typePairs.add(TypePair.of(Primitives.wrapperFor(sourceType), destinationType, typeMapName));
180     }
181     if (Primitives.isPrimitive(destinationType)) {
182       typePairs.add(TypePair.of(sourceType, Primitives.wrapperFor(destinationType), typeMapName));
183     }
184     if (Primitives.isPrimitive(sourceType) && Primitives.isPrimitiveWrapper(destinationType)) {
185       typePairs.add(TypePair.of(Primitives.wrapperFor(sourceType), Primitives.wrapperFor(destinationType), typeMapName));
186     }
187     return typePairs;
188   }
189
190   @SuppressWarnings("unchecked")
191   private <S, D> TypeMapImpl<S, D> getTypeMap(Class<S> sourceType, Class<D> destinationType, String typeMapName) {
192     TypePair<S, D> typePair = TypePair.of(sourceType, destinationType, typeMapName);
193
194     TypeMapImpl<S, D> typeMap = (TypeMapImpl<S, D>) typeMaps.get(typePair);
195     if (typeMap == null && isAnonymousEnumSubclass(sourceType)) {
196       typeMap = (TypeMapImpl<S, D>) typeMaps.get(
197               TypePair.of((Class<S>) sourceType.getSuperclass(), destinationType, typeMapName)
198       );
199     }
200
201     return typeMap;
202   }
203
204   private <S> boolean isAnonymousEnumSubclass(Class<S> sourceType) {
205     return sourceType.getSuperclass() != null
206         && sourceType.getSuperclass().isEnum()
207         && sourceType.isAnonymousClass();
208   }
209
210 }
211