• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mybatis / mybatis-3 / 2681

28 Jan 2025 05:48PM UTC coverage: 87.102% (-0.1%) from 87.217%
2681

Pull #3379

github

web-flow
Merge 8a7949334 into e20272705
Pull Request #3379: Resolve type handler based on `java.lang.reflect.Type` instead of `Class`

3815 of 4645 branches covered (82.13%)

467 of 525 new or added lines in 37 files covered. (88.95%)

26 existing lines in 5 files now uncovered.

9900 of 11366 relevant lines covered (87.1%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.23
/src/main/java/org/apache/ibatis/type/TypeHandlerRegistry.java
1
/*
2
 *    Copyright 2009-2025 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.ParameterizedType;
23
import java.lang.reflect.Type;
24
import java.math.BigDecimal;
25
import java.math.BigInteger;
26
import java.sql.Time;
27
import java.sql.Timestamp;
28
import java.time.Instant;
29
import java.time.LocalDate;
30
import java.time.LocalDateTime;
31
import java.time.LocalTime;
32
import java.time.Month;
33
import java.time.OffsetDateTime;
34
import java.time.OffsetTime;
35
import java.time.Year;
36
import java.time.YearMonth;
37
import java.time.ZonedDateTime;
38
import java.time.chrono.JapaneseDate;
39
import java.util.Collection;
40
import java.util.Collections;
41
import java.util.Date;
42
import java.util.EnumMap;
43
import java.util.HashMap;
44
import java.util.HashSet;
45
import java.util.Map;
46
import java.util.Map.Entry;
47
import java.util.Set;
48
import java.util.concurrent.ConcurrentHashMap;
49

50
import org.apache.ibatis.binding.MapperMethod.ParamMap;
51
import org.apache.ibatis.io.ResolverUtil;
52
import org.apache.ibatis.io.Resources;
53
import org.apache.ibatis.reflection.TypeParameterResolver;
54
import org.apache.ibatis.session.Configuration;
55

56
/**
57
 * @author Clinton Begin
58
 * @author Kazuki Shimizu
59
 */
60
public final class TypeHandlerRegistry {
61

62
  private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
1✔
63
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
1✔
64
  private final Map<Type, Entry<Constructor<?>, Set<JdbcType>>> smartHandlers = new ConcurrentHashMap<>();
1✔
65
  private final TypeHandler<Object> unknownTypeHandler;
66
  @Deprecated
1✔
67
  private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
68

69
  private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
1✔
70

71
  private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
1✔
72

73
  /**
74
   * The default constructor.
75
   */
76
  public TypeHandlerRegistry() {
77
    this(new Configuration());
1✔
78
  }
1✔
79

80
  /**
81
   * The constructor that pass the MyBatis configuration.
82
   *
83
   * @param configuration
84
   *          a MyBatis configuration
85
   *
86
   * @since 3.5.4
87
   */
88
  public TypeHandlerRegistry(Configuration configuration) {
1✔
89
    this.unknownTypeHandler = new UnknownTypeHandler(configuration);
1✔
90

91
    register(Boolean.class, new BooleanTypeHandler());
1✔
92
    register(boolean.class, new BooleanTypeHandler());
1✔
93
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
1✔
94
    register(JdbcType.BIT, new BooleanTypeHandler());
1✔
95

96
    register(Byte.class, new ByteTypeHandler());
1✔
97
    register(byte.class, new ByteTypeHandler());
1✔
98
    register(JdbcType.TINYINT, new ByteTypeHandler());
1✔
99

100
    register(Short.class, new ShortTypeHandler());
1✔
101
    register(short.class, new ShortTypeHandler());
1✔
102
    register(JdbcType.SMALLINT, new ShortTypeHandler());
1✔
103

104
    register(Integer.class, new IntegerTypeHandler());
1✔
105
    register(int.class, new IntegerTypeHandler());
1✔
106
    register(JdbcType.INTEGER, new IntegerTypeHandler());
1✔
107

108
    register(Long.class, new LongTypeHandler());
1✔
109
    register(long.class, new LongTypeHandler());
1✔
110

111
    register(Float.class, new FloatTypeHandler());
1✔
112
    register(float.class, new FloatTypeHandler());
1✔
113
    register(JdbcType.FLOAT, new FloatTypeHandler());
1✔
114

115
    register(Double.class, new DoubleTypeHandler());
1✔
116
    register(double.class, new DoubleTypeHandler());
1✔
117
    register(JdbcType.DOUBLE, new DoubleTypeHandler());
1✔
118

119
    register(Reader.class, new ClobReaderTypeHandler());
1✔
120
    register(String.class, new StringTypeHandler());
1✔
121
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
1✔
122
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
1✔
123
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
1✔
124
    register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
1✔
125
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
1✔
126
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
1✔
127
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
1✔
128
    register(JdbcType.CHAR, new StringTypeHandler());
1✔
129
    register(JdbcType.VARCHAR, new StringTypeHandler());
1✔
130
    register(JdbcType.CLOB, new ClobTypeHandler());
1✔
131
    register(JdbcType.LONGVARCHAR, new StringTypeHandler());
1✔
132
    register(JdbcType.NVARCHAR, new NStringTypeHandler());
1✔
133
    register(JdbcType.NCHAR, new NStringTypeHandler());
1✔
134
    register(JdbcType.NCLOB, new NClobTypeHandler());
1✔
135

136
    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
1✔
137
    register(JdbcType.ARRAY, new ArrayTypeHandler());
1✔
138

139
    register(BigInteger.class, new BigIntegerTypeHandler());
1✔
140
    register(JdbcType.BIGINT, new LongTypeHandler());
1✔
141

142
    register(BigDecimal.class, new BigDecimalTypeHandler());
1✔
143
    register(JdbcType.REAL, new BigDecimalTypeHandler());
1✔
144
    register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
1✔
145
    register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
1✔
146

147
    register(InputStream.class, new BlobInputStreamTypeHandler());
1✔
148
    register(Byte[].class, new ByteObjectArrayTypeHandler());
1✔
149
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
1✔
150
    register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
1✔
151
    register(byte[].class, new ByteArrayTypeHandler());
1✔
152
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
1✔
153
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
1✔
154
    register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
1✔
155
    register(JdbcType.BLOB, new BlobTypeHandler());
1✔
156

157
    register(Object.class, unknownTypeHandler);
1✔
158
    register(Object.class, JdbcType.OTHER, unknownTypeHandler);
1✔
159
    register(JdbcType.OTHER, unknownTypeHandler);
1✔
160

161
    register(Date.class, new DateTypeHandler());
1✔
162
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
1✔
163
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
1✔
164
    register(JdbcType.TIMESTAMP, new DateTypeHandler());
1✔
165
    register(JdbcType.DATE, new DateOnlyTypeHandler());
1✔
166
    register(JdbcType.TIME, new TimeOnlyTypeHandler());
1✔
167

168
    register(java.sql.Date.class, new SqlDateTypeHandler());
1✔
169
    register(Time.class, new SqlTimeTypeHandler());
1✔
170
    register(Timestamp.class, new SqlTimestampTypeHandler());
1✔
171

172
    register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());
1✔
173

174
    register(Instant.class, new InstantTypeHandler());
1✔
175
    register(LocalDateTime.class, new LocalDateTimeTypeHandler());
1✔
176
    register(LocalDate.class, new LocalDateTypeHandler());
1✔
177
    register(LocalTime.class, new LocalTimeTypeHandler());
1✔
178
    register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());
1✔
179
    register(OffsetTime.class, new OffsetTimeTypeHandler());
1✔
180
    register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());
1✔
181
    register(Month.class, new MonthTypeHandler());
1✔
182
    register(Year.class, new YearTypeHandler());
1✔
183
    register(YearMonth.class, new YearMonthTypeHandler());
1✔
184
    register(JapaneseDate.class, new JapaneseDateTypeHandler());
1✔
185

186
    // issue #273
187
    register(Character.class, new CharacterTypeHandler());
1✔
188
    register(char.class, new CharacterTypeHandler());
1✔
189
  }
1✔
190

191
  /**
192
   * Set a default {@link TypeHandler} class for {@link Enum}. A default {@link TypeHandler} is
193
   * {@link org.apache.ibatis.type.EnumTypeHandler}.
194
   *
195
   * @param typeHandler
196
   *          a type handler class for {@link Enum}
197
   *
198
   * @since 3.4.5
199
   */
200
  public void setDefaultEnumTypeHandler(Class<? extends TypeHandler> typeHandler) {
201
    this.defaultEnumTypeHandler = typeHandler;
×
202
  }
×
203

204
  public boolean hasTypeHandler(Class<?> javaType) {
205
    return hasTypeHandler(javaType, null);
1✔
206
  }
207

208
  @Deprecated
209
  public boolean hasTypeHandler(TypeReference<?> javaTypeReference) {
210
    return hasTypeHandler(javaTypeReference, null);
×
211
  }
212

213
  public boolean hasTypeHandler(Type javaType, JdbcType jdbcType) {
214
    return javaType != null && getTypeHandler(javaType, jdbcType) != null;
1✔
215
  }
216

217
  @Deprecated
218
  public boolean hasTypeHandler(TypeReference<?> javaTypeReference, JdbcType jdbcType) {
219
    return javaTypeReference != null && getTypeHandler(javaTypeReference, jdbcType) != null;
×
220
  }
221

222
  public TypeHandler<?> getMappingTypeHandler(Class<? extends TypeHandler<?>> handlerType) {
223
    return allTypeHandlersMap.get(handlerType);
1✔
224
  }
225

226
  @Deprecated
227
  @SuppressWarnings("unchecked")
228
  public <T> TypeHandler<T> getTypeHandler(Class<T> clazz) {
229
    return (TypeHandler<T>) getTypeHandler((Type) clazz);
1✔
230
  }
231

232
  public TypeHandler<?> getTypeHandler(Type type) {
233
    return getTypeHandler(type, null);
1✔
234
  }
235

236
  @Deprecated
237
  public <T> TypeHandler<T> getTypeHandler(TypeReference<T> javaTypeReference) {
238
    return getTypeHandler(javaTypeReference, null);
1✔
239
  }
240

241
  public TypeHandler<?> getTypeHandler(JdbcType jdbcType) {
242
    return jdbcTypeHandlerMap.get(jdbcType);
1✔
243
  }
244

245
  @Deprecated
246
  public <T> TypeHandler<T> getTypeHandler(TypeReference<T> javaTypeReference, JdbcType jdbcType) {
247
    return (TypeHandler<T>) getTypeHandler(javaTypeReference.getRawType(), jdbcType);
1✔
248
  }
249

250
  public TypeHandler<?> getTypeHandler(Type type, JdbcType jdbcType, Class<? extends TypeHandler<?>> typeHandlerClass) {
251
    TypeHandler<?> typeHandler = getTypeHandler(type, jdbcType);
1✔
252
    if (typeHandler != null && (typeHandlerClass == null || typeHandler.getClass().equals(typeHandlerClass))) {
1✔
253
      return typeHandler;
1✔
254
    }
255
    if (typeHandlerClass == null) {
1✔
256
      typeHandler = getSmartHandler(type, jdbcType);
1✔
257
    } else {
258
      typeHandler = getMappingTypeHandler(typeHandlerClass);
1✔
259
      if (typeHandler == null) {
1✔
260
        typeHandler = getInstance(type, typeHandlerClass);
1✔
261
      }
262
    }
263
    return typeHandler;
1✔
264
  }
265

266
  public TypeHandler<?> getTypeHandler(Type type, JdbcType jdbcType) {
267
    if (ParamMap.class.equals(type)) {
1✔
268
      return null;
1✔
269
    }
270
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
1✔
271
    TypeHandler<?> handler = null;
1✔
272
    if (jdbcHandlerMap != null) {
1✔
273
      handler = jdbcHandlerMap.get(jdbcType);
1✔
274
      if (handler == null) {
1✔
275
        handler = jdbcHandlerMap.get(null);
1✔
276
      }
277
      if (handler == null) {
1✔
278
        // #591
279
        handler = pickSoleHandler(jdbcHandlerMap);
1✔
280
      }
281
    }
282
    if (handler == null) {
1✔
283
      handler = getSmartHandler(type, jdbcType);
1✔
284
    }
285
    if (handler == null && type instanceof ParameterizedType) {
1✔
286
      return getTypeHandler((Class<?>) ((ParameterizedType) type).getRawType(), jdbcType);
1✔
287
    }
288
    // type drives generics here
289
    return handler;
1✔
290
  }
291

292
  private TypeHandler<?> getSmartHandler(Type type, JdbcType jdbcType) {
293
    Entry<Constructor<?>, Set<JdbcType>> candidate = null;
1✔
294
    for (Entry<Type, Entry<Constructor<?>, Set<JdbcType>>> entry : smartHandlers.entrySet()) {
1✔
295
      Type registeredType = entry.getKey();
1✔
296
      if (registeredType.equals(type)) {
1✔
297
        candidate = entry.getValue();
1✔
298
        break;
1✔
299
      }
300
      if (registeredType instanceof Class) {
1✔
301
        if (type instanceof Class && ((Class<?>) registeredType).isAssignableFrom((Class<?>) type)) {
1!
302
          candidate = entry.getValue();
1✔
303
        }
304
      } else if (registeredType instanceof ParameterizedType) {
1!
305
        Class<?> registeredClass = (Class<?>) ((ParameterizedType) registeredType).getRawType();
1✔
306
        if (type instanceof ParameterizedType) {
1✔
307
          Class<?> clazz = (Class<?>) ((ParameterizedType) type).getRawType();
1✔
308
          if (registeredClass.isAssignableFrom(clazz)) {
1✔
309
            candidate = entry.getValue();
1✔
310
          }
311
        }
312
      }
313
    }
1✔
314
    if (candidate == null) {
1✔
315
      if (type instanceof Class) {
1✔
316
        Class<?> clazz = (Class<?>) type;
1✔
317
        if (Enum.class.isAssignableFrom(clazz)) {
1✔
318
          Class<?> enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz;
1!
319
          Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(enumClass, enumClass);
1✔
320
          if (jdbcHandlerMap == null) {
1!
321
            TypeHandler<?> enumHandler = getInstance(enumClass, defaultEnumTypeHandler);
1✔
322
            register(enumClass, enumHandler);
1✔
323
            return enumHandler;
1✔
324
          }
325
        }
326
      }
327
      return null;
1✔
328
    }
329
    try {
330
      TypeHandler<?> typeHandler = (TypeHandler<?>) candidate.getKey().newInstance(type);
1✔
331
      register(type, jdbcType, typeHandler);
1✔
332
      return typeHandler;
1✔
NEW
333
    } catch (ReflectiveOperationException e) {
×
NEW
334
      throw new TypeException("Failed to invoke constructor " + candidate.getKey().toString(), e);
×
335
    }
336
  }
337

338
  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
339
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
1✔
340
    if (jdbcHandlerMap != null) {
1✔
341
      return NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap) ? null : jdbcHandlerMap;
1✔
342
    }
343
    if (type instanceof Class) {
1✔
344
      Class<?> clazz = (Class<?>) type;
1✔
345
      if (Enum.class.isAssignableFrom(clazz)) {
1✔
346
        // TODO: verify
347
        // if (clazz.isAnonymousClass()) {
348
        // return getJdbcHandlerMap(clazz.getSuperclass());
349
        // }
350
        // jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(clazz, clazz);
351
        // if (jdbcHandlerMap == null) {
352
        // register(clazz, getInstance(clazz, defaultEnumTypeHandler));
353
        // return typeHandlerMap.get(clazz);
354
        // }
355
      } else {
356
        jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
1✔
357
      }
358
    }
359
    typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
1✔
360
    return jdbcHandlerMap;
1✔
361
  }
362

363
  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForEnumInterfaces(Class<?> clazz, Class<?> enumClazz) {
364
    for (Class<?> iface : clazz.getInterfaces()) {
1✔
365
      Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(iface);
1✔
366
      if (jdbcHandlerMap == null) {
1!
367
        jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(iface, enumClazz);
1✔
368
      }
369
      if (jdbcHandlerMap != null) {
1!
370
        // Found a type handler registered to a super interface
UNCOV
371
        HashMap<JdbcType, TypeHandler<?>> newMap = new HashMap<>();
×
UNCOV
372
        for (Entry<JdbcType, TypeHandler<?>> entry : jdbcHandlerMap.entrySet()) {
×
373
          // Create a type handler instance with enum type as a constructor arg
UNCOV
374
          newMap.put(entry.getKey(), getInstance(enumClazz, entry.getValue().getClass()));
×
UNCOV
375
        }
×
UNCOV
376
        return newMap;
×
377
      }
378
    }
379
    return null;
1✔
380
  }
381

382
  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
383
    Class<?> superclass = clazz.getSuperclass();
1✔
384
    if (superclass == null || Object.class.equals(superclass)) {
1✔
385
      return null;
1✔
386
    }
387
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(superclass);
1✔
388
    if (jdbcHandlerMap != null) {
1✔
389
      return jdbcHandlerMap;
1✔
390
    }
391
    return getJdbcHandlerMapForSuperclass(superclass);
1✔
392
  }
393

394
  private TypeHandler<?> pickSoleHandler(Map<JdbcType, TypeHandler<?>> jdbcHandlerMap) {
395
    TypeHandler<?> soleHandler = null;
1✔
396
    for (TypeHandler<?> handler : jdbcHandlerMap.values()) {
1!
UNCOV
397
      if (soleHandler == null) {
×
UNCOV
398
        soleHandler = handler;
×
UNCOV
399
      } else if (!handler.getClass().equals(soleHandler.getClass())) {
×
400
        // More than one type handlers registered.
UNCOV
401
        return null;
×
402
      }
UNCOV
403
    }
×
404
    return soleHandler;
1✔
405
  }
406

407
  public TypeHandler<Object> getUnknownTypeHandler() {
408
    return unknownTypeHandler;
1✔
409
  }
410

411
  public void register(JdbcType jdbcType, TypeHandler<?> handler) {
412
    jdbcTypeHandlerMap.put(jdbcType, handler);
1✔
413
  }
1✔
414

415
  //
416
  // REGISTER INSTANCE
417
  //
418

419
  // Only handler
420

421
  public <T> void register(TypeHandler<T> typeHandler) {
422
    boolean mappedTypeFound = false;
1✔
423
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
1✔
424
    if (mappedTypes != null) {
1!
425
      for (Class<?> handledType : mappedTypes.value()) {
×
426
        register(handledType, typeHandler);
×
427
        mappedTypeFound = true;
×
428
      }
429
    }
430
    if (!mappedTypeFound) {
1!
431
      Type detectedMappedType = TypeParameterResolver.resolveClassTypeParams(TypeHandler.class,
1✔
432
          typeHandler.getClass())[0];
1✔
433
      register(detectedMappedType, typeHandler);
1✔
434
    }
435
    if (!mappedTypeFound) {
1!
436
      register((Class<T>) null, typeHandler);
1✔
437
    }
438
  }
1✔
439

440
  // java type + handler
441

442
  public void register(Class<?> javaType, TypeHandler<?> typeHandler) {
443
    register((Type) javaType, typeHandler);
1✔
444
  }
1✔
445

446
  private void register(Type javaType, TypeHandler<?> typeHandler) {
447
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
1✔
448
    if (mappedJdbcTypes != null) {
1!
UNCOV
449
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
×
UNCOV
450
        register(javaType, handledJdbcType, typeHandler);
×
451
      }
UNCOV
452
      if (mappedJdbcTypes.includeNullJdbcType()) {
×
UNCOV
453
        register(javaType, null, typeHandler);
×
454
      }
455
    } else {
456
      register(javaType, null, typeHandler);
1✔
457
    }
458
  }
1✔
459

460
  @Deprecated
461
  public <T> void register(TypeReference<T> javaTypeReference, TypeHandler<? extends T> handler) {
462
    register(javaTypeReference.getRawType(), handler);
1✔
463
  }
1✔
464

465
  // java type + jdbc type + handler
466

467
  public void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
468
    if (javaType != null) {
1✔
469
      typeHandlerMap.compute(javaType, (k, v) -> {
1✔
470
        Map<JdbcType, TypeHandler<?>> map = (v == null || v == NULL_TYPE_HANDLER_MAP ? new HashMap<>() : v);
1✔
471
        map.put(jdbcType, handler);
1✔
472
        return map;
1✔
473
      });
474
      if (javaType instanceof ParameterizedType) {
1✔
475
        // MEMO: add annotation to skip this?
476
        Type rawType = ((ParameterizedType) javaType).getRawType();
1✔
477
        typeHandlerMap.compute(rawType, (k, v) -> {
1✔
478
          Map<JdbcType, TypeHandler<?>> map = (v == null || v == NULL_TYPE_HANDLER_MAP ? new HashMap<>() : v);
1!
479
          map.merge(jdbcType, handler, (handler1, handler2) -> handler1.equals(handler2) ? handler1
1!
480
              : new ConflictedTypeHandler((Class<?>) rawType, jdbcType, handler1, handler2));
1✔
481
          return map;
1✔
482
        });
483
      }
484
    }
485
    allTypeHandlersMap.put(handler.getClass(), handler);
1✔
486
  }
1✔
487

488
  //
489
  // REGISTER CLASS
490
  //
491

492
  // Only handler type
493

494
  public void register(Class<?> typeHandlerClass) {
495
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
1✔
496
    if (mappedTypes == null) {
1✔
497
      Type typeFromSignature = TypeParameterResolver.resolveClassTypeParams(TypeHandler.class, typeHandlerClass)[0];
1✔
498
      register(typeFromSignature, typeHandlerClass);
1✔
499
    } else {
1✔
500
      for (Class<?> javaTypeClass : mappedTypes.value()) {
1✔
501
        register(javaTypeClass, typeHandlerClass);
1✔
502
      }
503
    }
504
  }
1✔
505

506
  // java type + handler type
507

508
  public void register(String javaTypeClassName, String typeHandlerClassName) throws ClassNotFoundException {
509
    register(Resources.classForName(javaTypeClassName), Resources.classForName(typeHandlerClassName));
×
510
  }
×
511

512
  public void register(Type javaTypeClass, Class<?> typeHandlerClass) {
513
    // TODO: change the argument type to Class<? extends TypeHandler<?>> ?
514
    if (!TypeHandler.class.isAssignableFrom(typeHandlerClass)) {
1!
NEW
515
      throw new IllegalArgumentException(
×
NEW
516
          String.format("'%s' does not implement TypeHandler.", typeHandlerClass.getName()));
×
517
    }
518
    @SuppressWarnings("unchecked")
519
    Class<? extends TypeHandler<?>> clazz = (Class<? extends TypeHandler<?>>) typeHandlerClass;
1✔
520
    MappedJdbcTypes mappedJdbcTypes = typeHandlerClass.getAnnotation(MappedJdbcTypes.class);
1✔
521
    if (mappedJdbcTypes != null) {
1✔
522
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
1✔
523
        register(javaTypeClass, handledJdbcType, clazz);
1✔
524
      }
525
      if (mappedJdbcTypes.includeNullJdbcType()) {
1✔
526
        register(javaTypeClass, null, clazz);
1✔
527
      }
528
    } else {
529
      register(javaTypeClass, null, clazz);
1✔
530
    }
531
  }
1✔
532

533
  // java type + jdbc type + handler type
534

535
  public void register(Type javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass) {
536
    @SuppressWarnings("unchecked")
537
    Class<? extends TypeHandler<?>> clazz = (Class<? extends TypeHandler<?>>) typeHandlerClass;
1✔
538
    for (Constructor<?> constructor : typeHandlerClass.getConstructors()) {
1✔
539
      if (constructor.getParameterCount() != 1) {
1✔
540
        continue;
1✔
541
      }
542
      Class<?> argType = constructor.getParameterTypes()[0];
1✔
543
      if (Type.class.equals(argType) || Class.class.equals(argType)) {
1!
544
        smartHandlers.computeIfAbsent(javaTypeClass, k -> {
1✔
545
          for (Entry<Constructor<?>, Set<JdbcType>> entry : smartHandlers.values()) {
1✔
546
            if (entry.getKey().equals(constructor)) {
1✔
547
              return entry;
1✔
548
            }
549
          }
1✔
550
          // Might have to Collections.synchronizedSet(new HashSet<>())
551
          return Map.entry(constructor, new HashSet<>());
1✔
552
        }).getValue().add(jdbcType);
1✔
553
        return;
1✔
554
      }
555
    }
556
    register(javaTypeClass, jdbcType, getInstance(javaTypeClass, typeHandlerClass));
1✔
557
  }
1✔
558

559
  // Construct a handler (used also from Builders)
560

561
  @SuppressWarnings("unchecked")
562
  public <T> TypeHandler<T> getInstance(Type javaTypeClass, Class<?> typeHandlerClass) {
563
    Constructor<?> c;
564
    try {
565
      if (javaTypeClass != null) {
1!
566
        try {
567
          c = typeHandlerClass.getConstructor(Type.class);
1✔
568
          return (TypeHandler<T>) c.newInstance(javaTypeClass);
1✔
569
        } catch (NoSuchMethodException ignored) {
1✔
570
        }
571
        if (javaTypeClass instanceof Class) {
1✔
572
          try {
573
            c = typeHandlerClass.getConstructor(Class.class);
1✔
574
            return (TypeHandler<T>) c.newInstance(javaTypeClass);
1✔
575
          } catch (NoSuchMethodException ignored) {
1✔
576
          }
577
        }
578
      }
579
      try {
580
        c = typeHandlerClass.getConstructor();
1✔
581
        return (TypeHandler<T>) c.newInstance();
1✔
NEW
582
      } catch (NoSuchMethodException e) {
×
NEW
583
        throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
×
584
      }
NEW
585
    } catch (ReflectiveOperationException e) {
×
NEW
586
      throw new TypeException("Failed to invoke constructor for handler " + typeHandlerClass, e);
×
587
    }
588
  }
589

590
  // scan
591

592
  public void register(String packageName) {
593
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
1✔
594
    resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
1✔
595
    Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
1✔
596
    for (Class<?> type : handlerSet) {
1✔
597
      // Ignore inner classes and interfaces (including package-info.java) and abstract classes
598
      if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
1!
599
        register(type);
1✔
600
      }
601
    }
1✔
602
  }
1✔
603

604
  // get information
605

606
  /**
607
   * Gets the type handlers.
608
   *
609
   * @return the type handlers
610
   *
611
   * @since 3.2.2
612
   */
613
  public Collection<TypeHandler<?>> getTypeHandlers() {
614
    return Collections.unmodifiableCollection(allTypeHandlersMap.values());
×
615
  }
616

617
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc