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