mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-04-02 23:04:28 +02:00
176 lines
6.0 KiB
Kotlin
176 lines
6.0 KiB
Kotlin
/*
|
|
* Copyright (c) Kuba Szczodrzyński 2022-10-21.
|
|
*/
|
|
|
|
package pl.szczodrzynski.edziennik.config
|
|
|
|
import com.google.gson.Gson
|
|
import com.google.gson.JsonArray
|
|
import com.google.gson.JsonObject
|
|
import com.google.gson.reflect.TypeToken
|
|
import pl.szczodrzynski.edziennik.ext.*
|
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
|
import java.lang.reflect.ParameterizedType
|
|
import java.lang.reflect.WildcardType
|
|
import kotlin.reflect.KProperty
|
|
|
|
private val gson = Gson()
|
|
|
|
inline fun <reified T> BaseConfig.config(name: String? = null, noinline default: () -> T) = ConfigDelegate(
|
|
config = this,
|
|
type = T::class.java,
|
|
nullable = null is T,
|
|
typeToken = object : TypeToken<T>() {},
|
|
defaultFunc = default,
|
|
defaultValue = null,
|
|
fieldName = name,
|
|
)
|
|
|
|
inline fun <reified T> BaseConfig.config(default: T) = ConfigDelegate(
|
|
config = this,
|
|
type = T::class.java,
|
|
nullable = null is T,
|
|
typeToken = object : TypeToken<T>() {},
|
|
defaultFunc = null,
|
|
defaultValue = default,
|
|
fieldName = null,
|
|
)
|
|
|
|
inline fun <reified T> BaseConfig.config(name: String? = null, default: T) = ConfigDelegate(
|
|
config = this,
|
|
type = T::class.java,
|
|
nullable = null is T,
|
|
typeToken = object : TypeToken<T>() {},
|
|
defaultFunc = null,
|
|
defaultValue = default,
|
|
fieldName = name,
|
|
)
|
|
|
|
@Suppress("UNCHECKED_CAST")
|
|
class ConfigDelegate<T>(
|
|
private val config: BaseConfig,
|
|
private val type: Class<T>,
|
|
private val nullable: Boolean,
|
|
private val typeToken: TypeToken<T>,
|
|
private val defaultFunc: (() -> T)?,
|
|
private val defaultValue: T?,
|
|
private val fieldName: String?,
|
|
) {
|
|
private var value: T? = null
|
|
private var isInitialized = false
|
|
|
|
private fun getDefault(): T = when {
|
|
defaultFunc != null -> defaultFunc.invoke()
|
|
else -> defaultValue as T
|
|
}
|
|
|
|
private fun getGenericType(index: Int = 0): Class<*> {
|
|
val parameterizedType = typeToken.type as ParameterizedType
|
|
val typeArgument = parameterizedType.actualTypeArguments[index] as WildcardType
|
|
return typeArgument.upperBounds[0] as Class<*>
|
|
}
|
|
|
|
operator fun setValue(_thisRef: Any, property: KProperty<*>, newValue: T) {
|
|
value = newValue
|
|
isInitialized = true
|
|
config.set(fieldName ?: property.name, serialize(newValue)?.toString())
|
|
}
|
|
|
|
operator fun getValue(_thisRef: Any, property: KProperty<*>): T {
|
|
if (isInitialized)
|
|
return value as T
|
|
val key = fieldName ?: property.name
|
|
|
|
if (key !in config.values) {
|
|
value = getDefault()
|
|
isInitialized = true
|
|
return value as T
|
|
}
|
|
val str = config.values[key]
|
|
|
|
value = if (str == null && nullable)
|
|
null as T
|
|
else if (str == null)
|
|
getDefault()
|
|
else
|
|
deserialize(str)
|
|
|
|
isInitialized = true
|
|
return value as T
|
|
}
|
|
|
|
private fun <I> serialize(value: I?, serializeObjects: Boolean = true): Any? {
|
|
if (value == null)
|
|
return null
|
|
|
|
return when (value) {
|
|
is String -> value
|
|
is Date -> value.stringY_m_d
|
|
is Time -> value.stringValue
|
|
is JsonObject -> value
|
|
is JsonArray -> value
|
|
// primitives
|
|
is Number -> value
|
|
is Boolean -> value
|
|
// enums, maps & collections
|
|
is Enum<*> -> value.toInt()
|
|
is Collection<*> -> value.map {
|
|
if (it is Number || it is Boolean) it else serialize(it, serializeObjects = false)
|
|
}.toJsonElement()
|
|
is Map<*, *> -> gson.toJson(value.mapValues { (_, it) ->
|
|
if (it is Number || it is Boolean) it else serialize(it, serializeObjects = false)
|
|
})
|
|
// objects or else
|
|
else -> if (serializeObjects) gson.toJson(value) else value
|
|
}
|
|
}
|
|
|
|
private fun <I> deserialize(value: String?, type: Class<*> = this.type): I? {
|
|
if (value == null)
|
|
return null
|
|
|
|
@Suppress("TYPE_MISMATCH_WARNING")
|
|
return when (type) {
|
|
String::class.java -> value
|
|
Date::class.java -> Date.fromY_m_d(value)
|
|
Time::class.java -> Time.fromHms(value)
|
|
JsonObject::class.java -> value.toJsonObject()
|
|
JsonArray::class.java -> value.toJsonArray()
|
|
// primitives
|
|
java.lang.Integer::class.java -> value.toIntOrNull()
|
|
java.lang.Boolean::class.java -> value.toBooleanStrictOrNull()
|
|
java.lang.Long::class.java -> value.toLongOrNull()
|
|
java.lang.Float::class.java -> value.toFloatOrNull()
|
|
// enums, maps & collections
|
|
else -> when {
|
|
Enum::class.java.isAssignableFrom(type) -> value.toIntOrNull()?.toEnum(type) as Enum<*>
|
|
Collection::class.java.isAssignableFrom(type) -> {
|
|
val array = value.toJsonArray()
|
|
val genericType = getGenericType()
|
|
val list = array?.map {
|
|
val str = if (it.isJsonPrimitive) it.asString else it.toString()
|
|
deserialize<Any>(str, genericType)
|
|
}
|
|
when {
|
|
List::class.java.isAssignableFrom(type) -> list
|
|
Set::class.java.isAssignableFrom(type) -> list?.toSet()
|
|
else -> list?.toTypedArray()
|
|
}
|
|
}
|
|
Map::class.java.isAssignableFrom(type) -> {
|
|
val obj = value.toJsonObject()
|
|
val genericType = getGenericType(index = 1)
|
|
val map = obj?.entrySet()?.associate { (key, it) ->
|
|
val str = if (it.isJsonPrimitive) it.asString else it.toString()
|
|
key to deserialize<Any>(str, genericType)
|
|
}
|
|
map
|
|
}
|
|
// objects or else
|
|
else -> gson.fromJson(value, type)
|
|
}
|
|
} as? I
|
|
}
|
|
}
|