All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sxtanna.db.struct.Resolver.kt Maven / Gradle / Ivy

There is a newer version: 1.6
Show newest version
package com.sxtanna.db.struct

import com.sxtanna.db.ext.*
import com.sxtanna.db.struct.SqlType.*
import java.math.BigDecimal
import java.math.BigInteger
import java.sql.ResultSet
import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.jvm.jvmErasure

/**
 * Resolve types for [SqlType] going to and from the database
 */
@Suppress("RemoveExplicitTypeArguments")
object Resolver {

    /**
     * Defines how to resolve objects from a [ResultSet]
     *
     * Has default implementations for
     *  * [Char]
     *  * [UUID]
     *  * [Boolean]
     *  * Any [Enum]
     *  * [String]
     *  * [Byte]
     *  * [Short]
     *  * [Int]
     *  * [Long]
     *  * [BigInteger]
     *  * [Float]
     *  * [Double]
     *  * [BigDecimal]
     *
     * New implementations can be added using
     * [SqlI.resolve]
     */
    object SqlI {

        @PublishedApi
        internal val adapters = mutableMapOf, ResultSet.(KProperty1<*, *>) -> Any>()


        init {

            resolve {
                getString(it.name)[0]
            }

            resolve {
                UUID.fromString(getString(it.name))
            }

            resolve {
                getBoolean(it.name)
            }

            resolve> {
                val value = getString(it.name)
                checkNotNull((it.returnType.jvmErasure as Enum<*>).javaClass.enumConstants.find { it.name == value })
            }

            resolve {
                getString(it.name)
            }

            resolve {
                getByte(it.name)
            }

            resolve {
                getShort(it.name)
            }

            resolve {
                getInt(it.name)
            }

            resolve {
                getLong(it.name)
            }

            resolve {
                BigInteger(getString(it.name))
            }

            resolve {
                getFloat(it.name)
            }

            resolve {
                getDouble(it.name)
            }

            resolve {
                getBigDecimal(it.name)
            }

        }


        internal operator fun  get(resultSet : ResultSet, property : KProperty1<*, T>) : T {
            val type = property.returnType.jvmErasure
            val adapter = adapters[type] ?: adapters[if (type.isSubclassOf(Enum::class)) Enum::class else Any::class]

            return checkNotNull(adapter) { "No adapter for $type" }.invoke(resultSet, property) as T
        }

        /**
         * Define how to resolve [T] from a [ResultSet]
         */
        inline fun  resolve(noinline block : ResultSet.(KProperty1<*, T>) -> T) {
            adapters[T::class] = (block as ResultSet.(KProperty1<*, *>) -> Any)
        }

    }

    /**
     * Defines how to resolve [SqlType.Cache] from an object's properties
     *
     * Has default implementations for
     *  * [Char]
     *  * [UUID]
     *  * [Boolean]
     *  * Any [Enum]
     *  * [String]
     *  * [Byte]
     *  * [Short]
     *  * [Int]
     *  * [Long]
     *  * [BigInteger]
     *  * [Float]
     *  * [Double]
     *  * [BigDecimal]
     *  * If the object has no resolver, a [SqlVarChar] is used with its [Object.toString] result
     *
     * New implementations can be added using
     * [SqlO.resolve]
     */
    object SqlO {

        @PublishedApi
        internal val adapters = mutableMapOf, KProperty1<*, *>.() -> SqlType.Cache>()


        init {

            resolve {
                SqlChar[1, isNotNull(), isPrimary()]
            }

            resolve {
                SqlChar[36, isNotNull(), isPrimary()]
            }

            resolve {
                SqlBoolean[isNotNull(), isPrimary()]
            }

            resolve> {
                val clazz = returnType.jvmErasure as KClass>
                SqlEnum[clazz, isNotNull(), isPrimary()]
            }

            resolve(Any::class, String::class) {

                val fixed = findAnnotation()?.length

                val type = when {
                    findAnnotation() != null -> SqlTinyText
                    findAnnotation() != null -> SqlText
                    findAnnotation() != null -> SqlMediumText
                    findAnnotation() != null -> SqlLongText
                    else -> null
                }

                if (type != null) {
                    return@resolve type.get(isNotNull(), isPrimary())
                }

                val sizedType = (if (fixed != null) SqlChar else SqlVarChar)
                sizedType[(fixed ?: findAnnotation()?.length ?: 255).coerceIn(1, 255), isNotNull(), isPrimary()]
            }

            resolve {
                val size = findAnnotation()?.length ?: 3
                SqlTinyInt[size.coerceIn(1, 3), isNotNull(), isPrimary(), isUnsigned()]
            }

            resolve {
                val size = findAnnotation()?.length ?: 5
                SqlSmallInt[size.coerceIn(1, 5), isNotNull(), isPrimary(), isUnsigned()]
            }

            resolve {

                val type = when {
                    findAnnotation() != null -> SqlTinyInt
                    findAnnotation() != null -> SqlSmallInt
                    findAnnotation() != null -> SqlMediumInt
                    findAnnotation() != null -> SqlBigInt
                    else -> SqlInt
                }

                val max = when(type) {
                    SqlTinyInt -> 3
                    SqlSmallInt -> 5
                    SqlMediumInt -> 7
                    SqlBigInt -> 19
                    else -> 10
                }

                val size = findAnnotation()?.length ?: max
                type[size.coerceIn(1, max), isNotNull(), isPrimary(), isUnsigned()]
            }

            resolve(Long::class, BigInteger::class) {
                val size = findAnnotation()?.length ?: 19
                SqlBigInt[size.coerceIn(1, 19), isNotNull(), isPrimary(), isUnsigned()]
            }

            resolve {
                val size = findAnnotation()

                val length = (size?.length ?: 14)
                val places = (size?.places ?: 7).coerceAtLeast(0)

                SqlFloat[length.coerceAtLeast(places), places, isNotNull(), isPrimary(), isUnsigned()]
            }

            resolve {
                val size = findAnnotation()

                val length = (size?.length ?: 30)
                val places = (size?.places ?: 15).coerceAtLeast(0)

                SqlDouble[length.coerceAtLeast(places), places, isNotNull(), isPrimary(), isUnsigned()]
            }

            resolve {
                val size = findAnnotation()

                val length = (size?.length ?: 10).coerceIn(1, 65)
                val places = (size?.places ?:  0).coerceIn(0, 30)

                SqlDecimal[length.coerceAtLeast(places), places, isNotNull(), isPrimary(), isUnsigned()]
            }


            // start declared

            resolveWith()

            resolveWith()

            resolve {
                val size = findAnnotation()?.length ?: 7
                SqlMediumInt[size.coerceIn(1, 7), isNotNull(), isPrimary()]
            }

            resolveWith()

            resolveWith()

            resolveWith()

            resolveWith()

            resolveWith()

            resolve {
                val size = findAnnotation()
                SqlChar[size?.length ?: 1, isNotNull(), isPrimary()]
            }

            resolveWith()

            resolve(SqlTinyText::class, SqlText::class, SqlMediumText::class, SqlLongText::class) {
                (returnType.jvmErasure.objectInstance as SqlType)[isNotNull(), isPrimary()]
            }

            resolveWith()

            resolve {
                val values = requireNotNull(findAnnotation()) { "You must specify the set values" }
                SqlSet[values.types, isNotNull(), isPrimary()]
            }

            resolve {
                val clazz = requireNotNull(findAnnotation()) { "You must specify which enum this is for" }
                SqlEnum[clazz.clazz, isNotNull(), isPrimary()]
            }

        }


        internal operator fun get(property : KProperty1<*, *>) : SqlType.Cache {
            val type = property.returnType.jvmErasure
            val adapter = adapters[type] ?: adapters[if (type.isSubclassOf(Enum::class)) Enum::class else Any::class]

            return checkNotNull(adapter) { "Impossible... but for type $type" }.invoke(property)
        }


        /**
         * Resolve type [T] with [block]
         */
        inline fun  resolve(noinline block : KProperty1<*, *>.() -> SqlType.Cache) {
            adapters[T::class] = block
        }

        /**
         * Resolve many [types] using the same [block]
         */
        fun resolve(vararg types : KClass<*>, block : KProperty1<*, *>.() -> SqlType.Cache) {
            types.forEach { adapters[it] = block }
        }


        private inline fun  resolveWith() {
            adapters[T::class] = requireNotNull(adapters[O::class]) { "Oops, no adapter for ${O::class}" }
        }

    }

}