View Javadoc
1   /*
2    *    Copyright 2009-2023 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    *       https://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.apache.ibatis.type;
17  
18  import java.io.InputStream;
19  import java.io.Reader;
20  import java.lang.reflect.Constructor;
21  import java.lang.reflect.Modifier;
22  import java.lang.reflect.Type;
23  import java.math.BigDecimal;
24  import java.math.BigInteger;
25  import java.time.Instant;
26  import java.time.LocalDate;
27  import java.time.LocalDateTime;
28  import java.time.LocalTime;
29  import java.time.Month;
30  import java.time.OffsetDateTime;
31  import java.time.OffsetTime;
32  import java.time.Year;
33  import java.time.YearMonth;
34  import java.time.ZonedDateTime;
35  import java.time.chrono.JapaneseDate;
36  import java.util.Collection;
37  import java.util.Collections;
38  import java.util.Date;
39  import java.util.EnumMap;
40  import java.util.HashMap;
41  import java.util.Map;
42  import java.util.Map.Entry;
43  import java.util.Set;
44  import java.util.concurrent.ConcurrentHashMap;
45  
46  import org.apache.ibatis.binding.MapperMethod.ParamMap;
47  import org.apache.ibatis.io.ResolverUtil;
48  import org.apache.ibatis.io.Resources;
49  import org.apache.ibatis.session.Configuration;
50  
51  /**
52   * @author Clinton Begin
53   * @author Kazuki Shimizu
54   */
55  public final class TypeHandlerRegistry {
56  
57    private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
58    private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
59    private final TypeHandler<Object> unknownTypeHandler;
60    private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
61  
62    private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
63  
64    private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
65  
66    /**
67     * The default constructor.
68     */
69    public TypeHandlerRegistry() {
70      this(new Configuration());
71    }
72  
73    /**
74     * The constructor that pass the MyBatis configuration.
75     *
76     * @param configuration
77     *          a MyBatis configuration
78     *
79     * @since 3.5.4
80     */
81    public TypeHandlerRegistry(Configuration configuration) {
82      this.unknownTypeHandler = new UnknownTypeHandler(configuration);
83  
84      register(Boolean.class, new BooleanTypeHandler());
85      register(boolean.class, new BooleanTypeHandler());
86      register(JdbcType.BOOLEAN, new BooleanTypeHandler());
87      register(JdbcType.BIT, new BooleanTypeHandler());
88  
89      register(Byte.class, new ByteTypeHandler());
90      register(byte.class, new ByteTypeHandler());
91      register(JdbcType.TINYINT, new ByteTypeHandler());
92  
93      register(Short.class, new ShortTypeHandler());
94      register(short.class, new ShortTypeHandler());
95      register(JdbcType.SMALLINT, new ShortTypeHandler());
96  
97      register(Integer.class, new IntegerTypeHandler());
98      register(int.class, new IntegerTypeHandler());
99      register(JdbcType.INTEGER, new IntegerTypeHandler());
100 
101     register(Long.class, new LongTypeHandler());
102     register(long.class, new LongTypeHandler());
103 
104     register(Float.class, new FloatTypeHandler());
105     register(float.class, new FloatTypeHandler());
106     register(JdbcType.FLOAT, new FloatTypeHandler());
107 
108     register(Double.class, new DoubleTypeHandler());
109     register(double.class, new DoubleTypeHandler());
110     register(JdbcType.DOUBLE, new DoubleTypeHandler());
111 
112     register(Reader.class, new ClobReaderTypeHandler());
113     register(String.class, new StringTypeHandler());
114     register(String.class, JdbcType.CHAR, new StringTypeHandler());
115     register(String.class, JdbcType.CLOB, new ClobTypeHandler());
116     register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
117     register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
118     register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
119     register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
120     register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
121     register(JdbcType.CHAR, new StringTypeHandler());
122     register(JdbcType.VARCHAR, new StringTypeHandler());
123     register(JdbcType.CLOB, new ClobTypeHandler());
124     register(JdbcType.LONGVARCHAR, new StringTypeHandler());
125     register(JdbcType.NVARCHAR, new NStringTypeHandler());
126     register(JdbcType.NCHAR, new NStringTypeHandler());
127     register(JdbcType.NCLOB, new NClobTypeHandler());
128 
129     register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
130     register(JdbcType.ARRAY, new ArrayTypeHandler());
131 
132     register(BigInteger.class, new BigIntegerTypeHandler());
133     register(JdbcType.BIGINT, new LongTypeHandler());
134 
135     register(BigDecimal.class, new BigDecimalTypeHandler());
136     register(JdbcType.REAL, new BigDecimalTypeHandler());
137     register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
138     register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
139 
140     register(InputStream.class, new BlobInputStreamTypeHandler());
141     register(Byte[].class, new ByteObjectArrayTypeHandler());
142     register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
143     register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
144     register(byte[].class, new ByteArrayTypeHandler());
145     register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
146     register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
147     register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
148     register(JdbcType.BLOB, new BlobTypeHandler());
149 
150     register(Object.class, unknownTypeHandler);
151     register(Object.class, JdbcType.OTHER, unknownTypeHandler);
152     register(JdbcType.OTHER, unknownTypeHandler);
153 
154     register(Date.class, new DateTypeHandler());
155     register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
156     register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
157     register(JdbcType.TIMESTAMP, new DateTypeHandler());
158     register(JdbcType.DATE, new DateOnlyTypeHandler());
159     register(JdbcType.TIME, new TimeOnlyTypeHandler());
160 
161     register(java.sql.Date.class, new SqlDateTypeHandler());
162     register(java.sql.Time.class, new SqlTimeTypeHandler());
163     register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
164 
165     register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());
166 
167     register(Instant.class, new InstantTypeHandler());
168     register(LocalDateTime.class, new LocalDateTimeTypeHandler());
169     register(LocalDate.class, new LocalDateTypeHandler());
170     register(LocalTime.class, new LocalTimeTypeHandler());
171     register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());
172     register(OffsetTime.class, new OffsetTimeTypeHandler());
173     register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());
174     register(Month.class, new MonthTypeHandler());
175     register(Year.class, new YearTypeHandler());
176     register(YearMonth.class, new YearMonthTypeHandler());
177     register(JapaneseDate.class, new JapaneseDateTypeHandler());
178 
179     // issue #273
180     register(Character.class, new CharacterTypeHandler());
181     register(char.class, new CharacterTypeHandler());
182   }
183 
184   /**
185    * Set a default {@link TypeHandler} class for {@link Enum}. A default {@link TypeHandler} is
186    * {@link org.apache.ibatis.type.EnumTypeHandler}.
187    *
188    * @param typeHandler
189    *          a type handler class for {@link Enum}
190    *
191    * @since 3.4.5
192    */
193   public void setDefaultEnumTypeHandler(Class<? extends TypeHandler> typeHandler) {
194     this.defaultEnumTypeHandler = typeHandler;
195   }
196 
197   public boolean hasTypeHandler(Class<?> javaType) {
198     return hasTypeHandler(javaType, null);
199   }
200 
201   public boolean hasTypeHandler(TypeReference<?> javaTypeReference) {
202     return hasTypeHandler(javaTypeReference, null);
203   }
204 
205   public boolean hasTypeHandler(Class<?> javaType, JdbcType jdbcType) {
206     return javaType != null && getTypeHandler((Type) javaType, jdbcType) != null;
207   }
208 
209   public boolean hasTypeHandler(TypeReference<?> javaTypeReference, JdbcType jdbcType) {
210     return javaTypeReference != null && getTypeHandler(javaTypeReference, jdbcType) != null;
211   }
212 
213   public TypeHandler<?> getMappingTypeHandler(Class<? extends TypeHandler<?>> handlerType) {
214     return allTypeHandlersMap.get(handlerType);
215   }
216 
217   public <T> TypeHandler<T> getTypeHandler(Class<T> type) {
218     return getTypeHandler((Type) type, null);
219   }
220 
221   public <T> TypeHandler<T> getTypeHandler(TypeReference<T> javaTypeReference) {
222     return getTypeHandler(javaTypeReference, null);
223   }
224 
225   public TypeHandler<?> getTypeHandler(JdbcType jdbcType) {
226     return jdbcTypeHandlerMap.get(jdbcType);
227   }
228 
229   public <T> TypeHandler<T> getTypeHandler(Class<T> type, JdbcType jdbcType) {
230     return getTypeHandler((Type) type, jdbcType);
231   }
232 
233   public <T> TypeHandler<T> getTypeHandler(TypeReference<T> javaTypeReference, JdbcType jdbcType) {
234     return getTypeHandler(javaTypeReference.getRawType(), jdbcType);
235   }
236 
237   @SuppressWarnings("unchecked")
238   private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
239     if (ParamMap.class.equals(type)) {
240       return null;
241     }
242     Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
243     TypeHandler<?> handler = null;
244     if (jdbcHandlerMap != null) {
245       handler = jdbcHandlerMap.get(jdbcType);
246       if (handler == null) {
247         handler = jdbcHandlerMap.get(null);
248       }
249       if (handler == null) {
250         // #591
251         handler = pickSoleHandler(jdbcHandlerMap);
252       }
253     }
254     // type drives generics here
255     return (TypeHandler<T>) handler;
256   }
257 
258   private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
259     Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
260     if (jdbcHandlerMap != null) {
261       return NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap) ? null : jdbcHandlerMap;
262     }
263     if (type instanceof Class) {
264       Class<?> clazz = (Class<?>) type;
265       if (Enum.class.isAssignableFrom(clazz)) {
266         if (clazz.isAnonymousClass()) {
267           return getJdbcHandlerMap(clazz.getSuperclass());
268         }
269         jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(clazz, clazz);
270         if (jdbcHandlerMap == null) {
271           register(clazz, getInstance(clazz, defaultEnumTypeHandler));
272           return typeHandlerMap.get(clazz);
273         }
274       } else {
275         jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
276       }
277     }
278     typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
279     return jdbcHandlerMap;
280   }
281 
282   private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForEnumInterfaces(Class<?> clazz, Class<?> enumClazz) {
283     for (Class<?> iface : clazz.getInterfaces()) {
284       Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(iface);
285       if (jdbcHandlerMap == null) {
286         jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(iface, enumClazz);
287       }
288       if (jdbcHandlerMap != null) {
289         // Found a type handler registered to a super interface
290         HashMap<JdbcType, TypeHandler<?>> newMap = new HashMap<>();
291         for (Entry<JdbcType, TypeHandler<?>> entry : jdbcHandlerMap.entrySet()) {
292           // Create a type handler instance with enum type as a constructor arg
293           newMap.put(entry.getKey(), getInstance(enumClazz, entry.getValue().getClass()));
294         }
295         return newMap;
296       }
297     }
298     return null;
299   }
300 
301   private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
302     Class<?> superclass = clazz.getSuperclass();
303     if (superclass == null || Object.class.equals(superclass)) {
304       return null;
305     }
306     Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(superclass);
307     if (jdbcHandlerMap != null) {
308       return jdbcHandlerMap;
309     }
310     return getJdbcHandlerMapForSuperclass(superclass);
311   }
312 
313   private TypeHandler<?> pickSoleHandler(Map<JdbcType, TypeHandler<?>> jdbcHandlerMap) {
314     TypeHandler<?> soleHandler = null;
315     for (TypeHandler<?> handler : jdbcHandlerMap.values()) {
316       if (soleHandler == null) {
317         soleHandler = handler;
318       } else if (!handler.getClass().equals(soleHandler.getClass())) {
319         // More than one type handlers registered.
320         return null;
321       }
322     }
323     return soleHandler;
324   }
325 
326   public TypeHandler<Object> getUnknownTypeHandler() {
327     return unknownTypeHandler;
328   }
329 
330   public void register(JdbcType jdbcType, TypeHandler<?> handler) {
331     jdbcTypeHandlerMap.put(jdbcType, handler);
332   }
333 
334   //
335   // REGISTER INSTANCE
336   //
337 
338   // Only handler
339 
340   @SuppressWarnings("unchecked")
341   public <T> void register(TypeHandler<T> typeHandler) {
342     boolean mappedTypeFound = false;
343     MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
344     if (mappedTypes != null) {
345       for (Class<?> handledType : mappedTypes.value()) {
346         register(handledType, typeHandler);
347         mappedTypeFound = true;
348       }
349     }
350     // @since 3.1.0 - try to auto-discover the mapped type
351     if (!mappedTypeFound && typeHandler instanceof TypeReference) {
352       try {
353         TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
354         register(typeReference.getRawType(), typeHandler);
355         mappedTypeFound = true;
356       } catch (Throwable t) {
357         // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
358       }
359     }
360     if (!mappedTypeFound) {
361       register((Class<T>) null, typeHandler);
362     }
363   }
364 
365   // java type + handler
366 
367   public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
368     register((Type) javaType, typeHandler);
369   }
370 
371   private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
372     MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
373     if (mappedJdbcTypes != null) {
374       for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
375         register(javaType, handledJdbcType, typeHandler);
376       }
377       if (mappedJdbcTypes.includeNullJdbcType()) {
378         register(javaType, null, typeHandler);
379       }
380     } else {
381       register(javaType, null, typeHandler);
382     }
383   }
384 
385   public <T> void register(TypeReference<T> javaTypeReference, TypeHandler<? extends T> handler) {
386     register(javaTypeReference.getRawType(), handler);
387   }
388 
389   // java type + jdbc type + handler
390 
391   // Cast is required here
392   @SuppressWarnings("cast")
393   public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {
394     register((Type) type, jdbcType, handler);
395   }
396 
397   private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
398     if (javaType != null) {
399       Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
400       if (map == null || map == NULL_TYPE_HANDLER_MAP) {
401         map = new HashMap<>();
402       }
403       map.put(jdbcType, handler);
404       typeHandlerMap.put(javaType, map);
405     }
406     allTypeHandlersMap.put(handler.getClass(), handler);
407   }
408 
409   //
410   // REGISTER CLASS
411   //
412 
413   // Only handler type
414 
415   public void register(Class<?> typeHandlerClass) {
416     boolean mappedTypeFound = false;
417     MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
418     if (mappedTypes != null) {
419       for (Class<?> javaTypeClass : mappedTypes.value()) {
420         register(javaTypeClass, typeHandlerClass);
421         mappedTypeFound = true;
422       }
423     }
424     if (!mappedTypeFound) {
425       register(getInstance(null, typeHandlerClass));
426     }
427   }
428 
429   // java type + handler type
430 
431   public void register(String javaTypeClassName, String typeHandlerClassName) throws ClassNotFoundException {
432     register(Resources.classForName(javaTypeClassName), Resources.classForName(typeHandlerClassName));
433   }
434 
435   public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
436     register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));
437   }
438 
439   // java type + jdbc type + handler type
440 
441   public void register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass) {
442     register(javaTypeClass, jdbcType, getInstance(javaTypeClass, typeHandlerClass));
443   }
444 
445   // Construct a handler (used also from Builders)
446 
447   @SuppressWarnings("unchecked")
448   public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
449     if (javaTypeClass != null) {
450       try {
451         Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
452         return (TypeHandler<T>) c.newInstance(javaTypeClass);
453       } catch (NoSuchMethodException ignored) {
454         // ignored
455       } catch (Exception e) {
456         throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
457       }
458     }
459     try {
460       Constructor<?> c = typeHandlerClass.getConstructor();
461       return (TypeHandler<T>) c.newInstance();
462     } catch (Exception e) {
463       throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
464     }
465   }
466 
467   // scan
468 
469   public void register(String packageName) {
470     ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
471     resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
472     Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
473     for (Class<?> type : handlerSet) {
474       // Ignore inner classes and interfaces (including package-info.java) and abstract classes
475       if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
476         register(type);
477       }
478     }
479   }
480 
481   // get information
482 
483   /**
484    * Gets the type handlers.
485    *
486    * @return the type handlers
487    *
488    * @since 3.2.2
489    */
490   public Collection<TypeHandler<?>> getTypeHandlers() {
491     return Collections.unmodifiableCollection(allTypeHandlersMap.values());
492   }
493 
494 }