From e722e3f4f9dc64acf53595802a0a83cf46ee96b8 Mon Sep 17 00:00:00 2001 From: Sculas Date: Fri, 9 Sep 2022 16:10:30 +0200 Subject: [PATCH] feat: improved Patch Options Removed a lot of the type mess. There's still some duplicated code PatchOption.kt, but I'm afraid there's nothing I can do about that. It's not a big deal anyway. --- .../app/revanced/patcher/patch/Patch.kt | 2 +- .../app/revanced/patcher/patch/PatchOption.kt | 28 +++++++++++++++++-- .../patcher/patch/PatchOptionsTest.kt | 16 +++++++++-- .../usage/bytecode/ExampleBytecodePatch.kt | 10 +++---- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/app/revanced/patcher/patch/Patch.kt b/src/main/kotlin/app/revanced/patcher/patch/Patch.kt index 6daf1cf5..c09524fa 100644 --- a/src/main/kotlin/app/revanced/patcher/patch/Patch.kt +++ b/src/main/kotlin/app/revanced/patcher/patch/Patch.kt @@ -27,7 +27,7 @@ abstract class OptionsContainer { @Suppress("MemberVisibilityCanBePrivate") val options = PatchOptions() - protected fun option(opt: PatchOption<*>): PatchOption<*> { + protected fun option(opt: PatchOption): PatchOption { options.register(opt) return opt } diff --git a/src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt b/src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt index 2f16e4d1..0573dee1 100644 --- a/src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt +++ b/src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt @@ -33,8 +33,22 @@ class PatchOptions(vararg options: PatchOption<*>) : Iterable> { * Get a [PatchOption] by its key. * @param key The key of the [PatchOption]. */ + @JvmName("getUntyped") operator fun get(key: String) = register[key] ?: throw NoSuchOptionException(key) + /** + * Get a [PatchOption] by its key. + * @param key The key of the [PatchOption]. + */ + inline operator fun get(key: String): PatchOption { + val opt = get(key) + if (opt.value !is T) throw InvalidTypeException( + opt.value?.let { it::class.java.canonicalName } ?: "null", + T::class.java.canonicalName + ) + return opt as PatchOption + } + /** * Set the value of a [PatchOption]. * @param key The key of the [PatchOption]. @@ -42,7 +56,7 @@ class PatchOptions(vararg options: PatchOption<*>) : Iterable> { * Please note that using the wrong value type results in a runtime error. */ inline operator fun set(key: String, value: T) { - @Suppress("UNCHECKED_CAST") val opt = get(key) as PatchOption + val opt = get(key) if (opt.value !is T) throw InvalidTypeException( T::class.java.canonicalName, opt.value?.let { it::class.java.canonicalName } ?: "null" @@ -99,7 +113,8 @@ sealed class PatchOption( * Gets the value of the option. * Please note that using the wrong value type results in a runtime error. */ - inline operator fun getValue(thisRef: Any?, property: KProperty<*>): V? { + @JvmName("getValueTyped") + inline operator fun getValue(thisRef: Nothing?, property: KProperty<*>): V? { if (value !is V?) throw InvalidTypeException( V::class.java.canonicalName, value?.let { it::class.java.canonicalName } ?: "null" @@ -107,11 +122,14 @@ sealed class PatchOption( return value as? V? } + operator fun getValue(thisRef: Any?, property: KProperty<*>) = value + /** * Gets the value of the option. * Please note that using the wrong value type results in a runtime error. */ - inline operator fun setValue(thisRef: Any?, property: KProperty<*>, new: V) { + @JvmName("setValueTyped") + inline operator fun setValue(thisRef: Nothing?, property: KProperty<*>, new: V) { if (value !is V) throw InvalidTypeException( V::class.java.canonicalName, value?.let { it::class.java.canonicalName } ?: "null" @@ -119,6 +137,10 @@ sealed class PatchOption( value = new as T } + operator fun setValue(thisRef: Any?, property: KProperty<*>, new: T?) { + value = new + } + /** * A [PatchOption] representing a [String]. * @see PatchOption diff --git a/src/test/kotlin/app/revanced/patcher/patch/PatchOptionsTest.kt b/src/test/kotlin/app/revanced/patcher/patch/PatchOptionsTest.kt index 3ddf3faf..125b59b9 100644 --- a/src/test/kotlin/app/revanced/patcher/patch/PatchOptionsTest.kt +++ b/src/test/kotlin/app/revanced/patcher/patch/PatchOptionsTest.kt @@ -35,7 +35,9 @@ internal class PatchOptionsTest { } } } - val option = options["key1"] + val option = options.get("key1") + // or: val option: String? by options["key1"] + // then you won't need `.value` every time println(option.value) options["key1"] = "Hello, world!" println(option.value) @@ -55,6 +57,9 @@ internal class PatchOptionsTest { // > options["key2"] = null // is not possible because Kotlin // cannot reify the type "Nothing?". + // So we have to do this instead: + options["key2"] = null as Any? + // This is a cleaner replacement for the above: options.nullify("key2") } @@ -66,12 +71,19 @@ internal class PatchOptionsTest { } @Test - fun `should fail because of invalid value type`() { + fun `should fail because of invalid value type when setting an option`() { assertThrows { options["key1"] = 123 } } + @Test + fun `should fail because of invalid value type when getting an option`() { + assertThrows { + options.get("key1") + } + } + @Test fun `should fail because of an illegal value`() { assertThrows { diff --git a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt index a1e581cd..2b3dce82 100644 --- a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt +++ b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt @@ -169,27 +169,27 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) { } companion object : OptionsContainer() { - private var key1: String? by option( + private var key1 by option( PatchOption.StringOption( "key1", "default", "title", "description", true ) ) - private var key2: Boolean? by option( + private var key2 by option( PatchOption.BooleanOption( "key2", true, "title", "description" // required defaults to false ) ) - private var key3: String? by option( + private var key3 by option( PatchOption.StringListOption( "key3", "TEST", listOf("TEST", "TEST1", "TEST2"), "title", "description" ) ) - private var key4: Int? by option( + private var key4 by option( PatchOption.IntListOption( "key4", 1, listOf(1, 2, 3), "title", "description" ) ) - private var key5: String? by option( + private var key5 by option( PatchOption.StringOption( "key5", null, "title", "description", true )