-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathJdbcConverter.kt
97 lines (87 loc) · 3.69 KB
/
JdbcConverter.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package klite.jdbc
import klite.Converter
import klite.Decimal
import klite.annotations.annotation
import klite.d
import klite.unboxInline
import java.lang.reflect.Modifier.STATIC
import java.math.BigDecimal
import java.math.BigInteger
import java.net.URI
import java.net.URL
import java.sql.Connection
import java.sql.Date
import java.sql.Time
import java.sql.Timestamp
import java.time.*
import java.time.ZoneOffset.UTC
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.jvmErasure
typealias ToJdbcConverter<T> = (T, Connection?) -> Any
object JdbcConverter {
val nativeTypes: MutableSet<KClass<*>> = mutableSetOf(
UUID::class, BigDecimal::class, BigInteger::class, LocalDate::class, LocalDateTime::class, LocalTime::class, OffsetDateTime::class
)
private val converters: MutableMap<KClass<*>, ToJdbcConverter<*>> = ConcurrentHashMap()
init {
use<Instant> { v, _ -> v.atOffset(UTC) }
val toString: ToJdbcConverter<Any> = { v, _ -> v.toString() }
use<Period>(toString)
use<Duration>(toString)
use<Currency>(toString)
use<Locale>(toString)
use<URL>(toString)
use<URI>(toString)
}
operator fun <T: Any> set(type: KClass<T>, converter: ToJdbcConverter<T>) { converters[type] = converter }
inline fun <reified T: Any> use(noinline converter: ToJdbcConverter<T>) = set(T::class, converter)
fun to(v: Any?, conn: Connection? = null): Any? = when (v) {
null -> null
is Enum<*> -> v.name
is Collection<*> -> conn!!.createArrayOf(arrayType(v.firstOrNull()?.javaClass), v.map { to(it, conn) }.toTypedArray())
is Array<*> -> conn!!.createArrayOf(arrayType(v.javaClass.componentType), v.map { to(it, conn) }.toTypedArray())
else -> {
val cls = v::class
@Suppress("UNCHECKED_CAST") when {
cls.javaPrimitiveType != null || nativeTypes.contains(cls) -> v
converters.contains(cls) -> (converters[cls] as ToJdbcConverter<Any>).invoke(v, conn)
cls.isValue && cls.hasAnnotation<JvmInline>() -> v.unboxInline()
Converter.supports(v::class) -> v.toString()
else -> v
}
}
}
private fun arrayType(c: Class<*>?): String = when {
c == null -> "varchar"
UUID::class.java.isAssignableFrom(c) -> "uuid"
Number::class.java.isAssignableFrom(c) || c.isPrimitive -> "numeric"
LocalDate::class.java.isAssignableFrom(c) -> "date"
LocalTime::class.java.isAssignableFrom(c) -> "time"
LocalDateTime::class.java.isAssignableFrom(c) -> "timestamp"
Instant::class.java.isAssignableFrom(c) -> "timestamptz"
c.isAnnotationPresent(JvmInline::class.java) -> arrayType(c.declaredFields.find { it.modifiers and STATIC == 0 }?.type)
else -> "varchar"
}
fun from(v: Any?, target: KType): Any? = if (v is java.sql.Array) {
val targetClass = target.jvmErasure
if (targetClass.java.isArray) v.array else {
val list = (v.array as Array<*>).map { from(it, target.arguments[0].type!!) }
if (targetClass == Set::class) list.toSet()
else list
}
} else from(v, target.jvmErasure)
fun from(v: Any?, target: KClass<*>?): Any? = when(target) {
Instant::class -> (v as? Timestamp)?.toInstant()
LocalDate::class -> (v as? Date)?.toLocalDate()
LocalTime::class -> (v as? Time)?.toLocalTime()
LocalDateTime::class -> (v as? Timestamp)?.toLocalDateTime()
Decimal::class -> v?.toString()?.d
else -> if (target?.annotation<JvmInline>() != null || target == Decimal::class) target.primaryConstructor!!.call(v)
else if (v is String && target != null && target != String::class) Converter.from(v, target) else v
}
}