From 4ce4c025ee8cae981bca2c107b0fd67f704a9810 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 21 Jun 2020 15:27:19 +0800 Subject: [PATCH] Implement composite Values --- .../net/mamoe/mirai/console/MiraiConsole.kt | 2 +- .../mamoe/mirai/console/setting/Setting.kt | 17 ++- .../net/mamoe/mirai/console/setting/Value.kt | 31 ++-- .../setting/internal/ValueCreatorsImpl.kt | 39 ++++- .../setting/internal/ValueDeclarationsImpl.kt | 94 ++++++++++-- .../setting/internal/collectionUtil.kt | 141 +++++++++++++++++- .../mirai/console/setting/SettingTest.kt | 20 +++ 7 files changed, 308 insertions(+), 36 deletions(-) create mode 100644 backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt index 168d2d568..f63f56688 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt @@ -38,7 +38,7 @@ internal object MiraiConsoleInitializer { internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) @JvmStatic - val buildDate: Date = Date(1592720608995) // 2020-06-21 14:23:28 + val buildDate: Date = Date(1592723625351) // 2020-06-21 15:13:45 const val version: String = "0.5.1" } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt index 2d32ddefc..153d78982 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt @@ -13,11 +13,14 @@ package net.mamoe.mirai.console.setting import net.mamoe.mirai.console.setting.internal.cast import net.mamoe.mirai.console.setting.internal.valueFromKTypeImpl +import net.mamoe.mirai.utils.MiraiExperimentalAPI import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KProperty +import kotlin.reflect.KType import kotlin.reflect.typeOf +// TODO: 2020/6/21 move to JvmPlugin to inherit SettingStorage and CoroutineScope for saving // Shows public APIs such as deciding when to auto-save. abstract class Setting : SettingImpl() @@ -37,6 +40,13 @@ internal abstract class SettingImpl { private val valueNodes: List> = kotlin.run { TODO("reflection") } + + /** + * flatten + */ + internal fun onValueChanged(value: Value<*>) { + + } } @@ -50,7 +60,7 @@ fun Setting.value(value: Int): IntValue = TODO("codegen") /** - * Creates a [Value] with [default]. + * Creates a [Value] with reified type. * * @param T reified param type T. * Supports only primitives, Kotlin built-in collections, @@ -59,4 +69,7 @@ fun Setting.value(value: Int): IntValue = TODO("codegen") */ @LowPriorityInOverloadResolution @OptIn(ExperimentalStdlibApi::class) // stable in 1.4 -inline fun Setting.value(default: T): Value = valueFromKTypeImpl(typeOf()).cast() \ No newline at end of file +inline fun Setting.valueReified(default: T): Value = valueFromKTypeImpl(typeOf()).cast() + +@MiraiExperimentalAPI +fun Setting.valueFromKType(type: KType): Value = valueFromKTypeImpl(type).cast() \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt index 8dee45afd..3383e532e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt @@ -102,28 +102,29 @@ interface StringValue : PrimitiveValue @MiraiExperimentalAPI interface CompositeValue : Value + /** * Superclass of [CompositeListValue], [PrimitiveListValue]. */ -interface ListValue : CompositeValue> +interface ListValue : CompositeValue> /** * Elements can by anything, wrapped as [Value]. - * @param T is not primitive types. + * @param E is not primitive types. */ -interface CompositeListValue : ListValue> +interface CompositeListValue : ListValue /** * Elements can only be primitives, not wrapped. - * @param T is not primitive types. + * @param E is not primitive types. */ -interface PrimitiveListValue : ListValue +interface PrimitiveListValue : ListValue //// region PrimitiveListValue CODEGEN //// -interface PrimitiveIntListValue : PrimitiveListValue -interface PrimitiveLongListValue : PrimitiveListValue +interface PrimitiveIntListValue : PrimitiveListValue +interface PrimitiveLongListValue : PrimitiveListValue // TODO + codegen //// endregion PrimitiveListValue CODEGEN //// @@ -132,25 +133,25 @@ interface PrimitiveLongListValue : PrimitiveListValue /** * Superclass of [CompositeSetValue], [PrimitiveSetValue]. */ -interface SetValue : CompositeValue> +interface SetValue : CompositeValue> /** * Elements can by anything, wrapped as [Value]. - * @param T is not primitive types. + * @param E is not primitive types. */ -interface CompositeSetValue : SetValue> +interface CompositeSetValue : SetValue /** * Elements can only be primitives, not wrapped. - * @param T is not primitive types. + * @param E is not primitive types. */ -interface PrimitiveSetValue : SetValue +interface PrimitiveSetValue : SetValue //// region PrimitiveSetValue CODEGEN //// -interface PrimitiveIntSetValue : PrimitiveSetValue -interface PrimitiveLongSetValue : PrimitiveSetValue +interface PrimitiveIntSetValue : PrimitiveSetValue +interface PrimitiveLongSetValue : PrimitiveSetValue // TODO + codegen //// endregion PrimitiveSetValue CODEGEN //// @@ -161,7 +162,7 @@ interface PrimitiveLongSetValue : PrimitiveSetValue */ interface MapValue : CompositeValue> -interface CompositeMapValue : MapValue, Value> +interface CompositeMapValue : MapValue interface PrimitiveMapValue : MapValue diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/ValueCreatorsImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/ValueCreatorsImpl.kt index e837c8eb1..8d1187d8a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/ValueCreatorsImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/ValueCreatorsImpl.kt @@ -13,6 +13,7 @@ package net.mamoe.mirai.console.setting.internal import net.mamoe.mirai.console.setting.Setting import net.mamoe.mirai.console.setting.Value +import net.mamoe.mirai.console.setting.valueFromKType import kotlin.reflect.KClass import kotlin.reflect.KType @@ -31,21 +32,53 @@ internal fun Setting.valueFromKTypeImpl(type: KType): Value<*> { when { classifier == Map::class -> { + val keyClass = type.arguments[0].type?.classifier + require(keyClass is KClass<*>) - TODO() + val valueClass = type.arguments[1].type?.classifier + require(valueClass is KClass<*>) + + if (keyClass.isPrimitiveOrBuiltInSerializableValue() && valueClass.isPrimitiveOrBuiltInSerializableValue()) { + // PrimitiveIntIntMap + // ... + TODO() + } else { + return createCompositeMapValueImpl( + kToValue = { valueFromKType(type.arguments[0].type!!) }, + vToValue = { valueFromKType(type.arguments[1].type!!) } + ) + } } classifier == List::class -> { + val elementClass = type.arguments[0].type?.classifier + require(elementClass is KClass<*>) - TODO() + if (elementClass.isPrimitiveOrBuiltInSerializableValue()) { + // PrimitiveIntList + // ... + TODO() + } else { + return createCompositeListValueImpl { valueFromKType(type.arguments[0].type!!) } + } } classifier == Set::class -> { - TODO() + val elementClass = type.arguments[0].type?.classifier + require(elementClass is KClass<*>) + + if (elementClass.isPrimitiveOrBuiltInSerializableValue()) { + // PrimitiveIntSet + // ... + TODO() + } else { + return createCompositeSetValueImpl { valueFromKType(type.arguments[0].type!!) } + } } else -> error("Custom composite value is not supported yet (${classifier.qualifiedName})") } } internal fun KClass<*>.isPrimitiveOrBuiltInSerializableValue(): Boolean { + return false // debug when (this) { Byte::class, Short::class, Int::class, Long::class, Boolean::class, diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/ValueDeclarationsImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/ValueDeclarationsImpl.kt index ce7a68627..c4726e915 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/ValueDeclarationsImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/ValueDeclarationsImpl.kt @@ -11,9 +11,7 @@ package net.mamoe.mirai.console.setting.internal -import net.mamoe.mirai.console.setting.CompositeListValue -import net.mamoe.mirai.console.setting.IntValue -import net.mamoe.mirai.console.setting.Value +import net.mamoe.mirai.console.setting.* internal abstract class IntValueImpl : IntValue { constructor() @@ -35,13 +33,91 @@ internal abstract class IntValueImpl : IntValue { protected abstract fun onChanged() } -internal abstract class CompositeListValueImpl( - val tToValue: (T) -> Value -) : CompositeListValue { - private var _value: List> = mutableListOf() - override var value: List> +// type inference bug +internal fun Setting.createCompositeSetValueImpl(tToValue: (T) -> Value): CompositeSetValueImpl { + return object : CompositeSetValueImpl(tToValue) { + override fun onChanged() { + this@createCompositeSetValueImpl.onValueChanged(this) + } + } +} + +internal abstract class CompositeSetValueImpl( + tToValue: (T) -> Value // should override onChanged +) : CompositeSetValue { + private val internalSet: MutableSet> = mutableSetOf() + + private var _value: Set = internalSet.shadowMap({ it.value }, tToValue).observable { onChanged() } + + override var value: Set get() = _value set(v) { - _value = v + if (_value != v) { + onChanged() + _value = v + } } + + protected abstract fun onChanged() +} + + +// type inference bug +internal fun Setting.createCompositeListValueImpl(tToValue: (T) -> Value): CompositeListValueImpl { + return object : CompositeListValueImpl(tToValue) { + override fun onChanged() { + this@createCompositeListValueImpl.onValueChanged(this) + } + } +} + +internal abstract class CompositeListValueImpl( + tToValue: (T) -> Value // should override onChanged +) : CompositeListValue { + private val internalList: MutableList> = mutableListOf() + + private var _value: List = internalList.shadowMap({ it.value }, tToValue).observable { onChanged() } + + override var value: List + get() = _value + set(v) { + if (_value != v) { + onChanged() + _value = v + } + } + + protected abstract fun onChanged() +} + +// workaround to a type inference bug +internal fun Setting.createCompositeMapValueImpl( + kToValue: (K) -> Value, + vToValue: (V) -> Value +): CompositeMapValueImpl { + return object : CompositeMapValueImpl(kToValue, vToValue) { + override fun onChanged() { + this@createCompositeMapValueImpl.onValueChanged(this) + } + } +} + +internal abstract class CompositeMapValueImpl( + kToValue: (K) -> Value, // should override onChanged + vToValue: (V) -> Value // should override onChanged +) : CompositeMapValue { + private val internalList: MutableMap, Value> = mutableMapOf() + + private var _value: Map = + internalList.shadowMap({ it.value }, kToValue, { it.value }, vToValue).observable { onChanged() } + override var value: Map + get() = _value + set(v) { + if (_value != v) { + onChanged() + _value = v + } + } + + protected abstract fun onChanged() } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/collectionUtil.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/collectionUtil.kt index c686d2fdb..75208385a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/collectionUtil.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/collectionUtil.kt @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("DuplicatedCode") + package net.mamoe.mirai.console.setting.internal import kotlinx.serialization.ImplicitReflectionSerializer @@ -14,7 +16,89 @@ import kotlinx.serialization.serializer import net.mamoe.yamlkt.Yaml import kotlin.reflect.KClass -internal fun MutableList.shadowMap(transform: (E) -> R, transformBack: (R) -> E): MutableList { +internal inline fun MutableMap.shadowMap( + crossinline kTransform: (K) -> KR, + crossinline kTransformBack: (KR) -> K, + crossinline vTransform: (V) -> VR, + crossinline vTransformBack: (VR) -> V +): MutableMap { + return object : MutableMap { + override val size: Int get() = this@shadowMap.size + override fun containsKey(key: KR): Boolean = this@shadowMap.containsKey(key.let(kTransformBack)) + override fun containsValue(value: VR): Boolean = this@shadowMap.containsValue(value.let(vTransformBack)) + override fun get(key: KR): VR? = this@shadowMap[key.let(kTransformBack)]?.let(vTransform) + override fun isEmpty(): Boolean = this@shadowMap.isEmpty() + + override val entries: MutableSet> + get() = this@shadowMap.entries.shadowMap( + transform = { entry: MutableMap.MutableEntry -> + object : MutableMap.MutableEntry { + override val key: KR get() = entry.key.let(kTransform) + override val value: VR get() = entry.value.let(vTransform) + override fun setValue(newValue: VR): VR = + entry.setValue(newValue.let(vTransformBack)).let(vTransform) + } + } as ((MutableMap.MutableEntry) -> MutableMap.MutableEntry), // type inference bug + transformBack = { entry -> + object : MutableMap.MutableEntry { + override val key: K get() = entry.key.let(kTransformBack) + override val value: V get() = entry.value.let(vTransformBack) + override fun setValue(newValue: V): V = + entry.setValue(newValue.let(vTransform)).let(vTransformBack) + } + } + ) + override val keys: MutableSet + get() = this@shadowMap.keys.shadowMap(kTransform, kTransformBack) + override val values: MutableCollection + get() = this@shadowMap.values.shadowMap(vTransform, vTransformBack) + + override fun clear() = this@shadowMap.clear() + override fun put(key: KR, value: VR): VR? = + this@shadowMap.put(key.let(kTransformBack), value.let(vTransformBack))?.let(vTransform) + + override fun putAll(from: Map) { + from.forEach { (kr, vr) -> + this@shadowMap[kr.let(kTransformBack)] = vr.let(vTransformBack) + } + } + + override fun remove(key: KR): VR? = this@shadowMap.remove(key.let(kTransformBack))?.let(vTransform) + } +} + +internal inline fun MutableCollection.shadowMap( + crossinline transform: (E) -> R, + crossinline transformBack: (R) -> E +): MutableCollection { + return object : MutableCollection { + override val size: Int get() = this@shadowMap.size + + override fun contains(element: R): Boolean = this@shadowMap.any { it.let(transform) == element } + override fun containsAll(elements: Collection): Boolean = elements.all(::contains) + override fun isEmpty(): Boolean = this@shadowMap.isEmpty() + override fun iterator(): MutableIterator = object : MutableIterator { + private val delegate = this@shadowMap.iterator() + override fun hasNext(): Boolean = delegate.hasNext() + override fun next(): R = delegate.next().let(transform) + override fun remove() = delegate.remove() + } + + override fun add(element: R): Boolean = this@shadowMap.add(element.let(transformBack)) + + override fun addAll(elements: Collection): Boolean = this@shadowMap.addAll(elements.map(transformBack)) + override fun clear() = this@shadowMap.clear() + + override fun remove(element: R): Boolean = this@shadowMap.removeIf { it.let(transform) == element } + override fun removeAll(elements: Collection): Boolean = elements.all(::remove) + override fun retainAll(elements: Collection): Boolean = this@shadowMap.retainAll(elements.map(transformBack)) + } +} + +internal inline fun MutableList.shadowMap( + crossinline transform: (E) -> R, + crossinline transformBack: (R) -> E +): MutableList { return object : MutableList { override val size: Int get() = this@shadowMap.size @@ -78,7 +162,10 @@ internal fun MutableList.shadowMap(transform: (E) -> R, transformBack: } -internal fun MutableSet.shadowMap(transform: (E) -> R, transformBack: (R) -> E): MutableSet { +internal inline fun MutableSet.shadowMap( + crossinline transform: (E) -> R, + crossinline transformBack: (R) -> E +): MutableSet { return object : MutableSet { override val size: Int get() = this@shadowMap.size @@ -102,7 +189,7 @@ internal fun MutableSet.shadowMap(transform: (E) -> R, transformBack: } } -internal fun dynamicList(supplier: () -> List): List { +internal inline fun dynamicList(crossinline supplier: () -> List): List { return object : List { override val size: Int get() = supplier().size override fun contains(element: T): Boolean = supplier().contains(element) @@ -118,7 +205,7 @@ internal fun dynamicList(supplier: () -> List): List { } } -internal fun dynamicSet(supplier: () -> Set): Set { +internal inline fun dynamicSet(crossinline supplier: () -> Set): Set { return object : Set { override val size: Int get() = supplier().size override fun contains(element: T): Boolean = supplier().contains(element) @@ -129,7 +216,7 @@ internal fun dynamicSet(supplier: () -> Set): Set { } -internal fun dynamicMutableList(supplier: () -> MutableList): MutableList { +internal inline fun dynamicMutableList(crossinline supplier: () -> MutableList): MutableList { return object : MutableList { override val size: Int get() = supplier().size override fun contains(element: T): Boolean = supplier().contains(element) @@ -156,7 +243,7 @@ internal fun dynamicMutableList(supplier: () -> MutableList): MutableList } -internal fun dynamicMutableSet(supplier: () -> MutableSet): MutableSet { +internal inline fun dynamicMutableSet(crossinline supplier: () -> MutableSet): MutableSet { return object : MutableSet { override val size: Int get() = supplier().size override fun contains(element: T): Boolean = supplier().contains(element) @@ -172,6 +259,23 @@ internal fun dynamicMutableSet(supplier: () -> MutableSet): MutableSet } } +@Suppress("UNCHECKED_CAST", "USELESS_CAST") // type inference bug +internal inline fun MutableMap.observable(crossinline onChanged: () -> Unit): MutableMap { + return object : MutableMap, Map by (this as Map) { + override val keys: MutableSet + get() = this@observable.keys.observable(onChanged) + override val values: MutableCollection + get() = this@observable.values.observable(onChanged) + override val entries: MutableSet> + get() = this@observable.entries.observable(onChanged) + + override fun clear() = this@observable.clear().also { onChanged() } + override fun put(key: K, value: V): V? = this@observable.put(key, value).also { onChanged() } + override fun putAll(from: Map) = this@observable.putAll(from).also { onChanged() } + override fun remove(key: K): V? = this@observable.remove(key).also { onChanged() } + } +} + internal inline fun MutableList.observable(crossinline onChanged: () -> Unit): MutableList { return object : MutableList { override val size: Int get() = this@observable.size @@ -234,6 +338,31 @@ internal inline fun MutableList.observable(crossinline onChanged: () -> U } } +internal inline fun MutableCollection.observable(crossinline onChanged: () -> Unit): MutableCollection { + return object : MutableCollection { + override val size: Int get() = this@observable.size + override fun contains(element: T): Boolean = this@observable.contains(element) + override fun containsAll(elements: Collection): Boolean = this@observable.containsAll(elements) + override fun isEmpty(): Boolean = this@observable.isEmpty() + override fun iterator(): MutableIterator = object : MutableIterator { + private val delegate = this@observable.iterator() + override fun hasNext(): Boolean = delegate.hasNext() + override fun next(): T = delegate.next() + override fun remove() = delegate.remove().also { onChanged() } + } + + override fun add(element: T): Boolean = this@observable.add(element).also { onChanged() } + override fun addAll(elements: Collection): Boolean = this@observable.addAll(elements).also { onChanged() } + override fun clear() = this@observable.clear().also { onChanged() } + override fun remove(element: T): Boolean = this@observable.remove(element).also { onChanged() } + override fun removeAll(elements: Collection): Boolean = + this@observable.removeAll(elements).also { onChanged() } + + override fun retainAll(elements: Collection): Boolean = + this@observable.retainAll(elements).also { onChanged() } + } +} + internal inline fun MutableSet.observable(crossinline onChanged: () -> Unit): MutableSet { return object : MutableSet { override val size: Int get() = this@observable.size diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt new file mode 100644 index 000000000..370edeb1b --- /dev/null +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.setting + +import org.junit.jupiter.api.Test + +internal class SettingTest { + + @Test + fun testPrimitive() { + + } +} \ No newline at end of file