refactor: Improve Patch Options

It's so much better now. Really happy with the current system.

BREAKING CHANGE: Options has been moved from Patch to a new interface called OptionsContainer and are now handled entirely different. Make sure to check the examples to understand how it works.
This commit is contained in:
Sculas 2022-09-07 20:55:35 +02:00
parent 0e8446516e
commit 6b909c1ee6
No known key found for this signature in database
GPG Key ID: 1530BFF96D1EEB89
5 changed files with 64 additions and 27 deletions

View File

@ -6,8 +6,10 @@ import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.Data
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.patch.OptionsContainer
import app.revanced.patcher.patch.Patch
import kotlin.reflect.KClass
import kotlin.reflect.full.companionObjectInstance
/**
* Recursively find a given annotation on a class.
@ -43,6 +45,9 @@ object PatchExtensions {
val Class<out Patch<Data>>.description get() = recursiveAnnotation(Description::class)?.description
val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.DependsOn::class)?.dependencies
val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
val Class<out Patch<Data>>.options get() = kotlin.companionObjectInstance?.let {
(it as? OptionsContainer)?.options
}
@JvmStatic
fun Class<out Patch<Data>>.dependsOn(patch: Class<out Patch<Data>>): Boolean {

View File

@ -17,9 +17,18 @@ abstract class Patch<out T : Data> {
* The main function of the [Patch] which the patcher will call.
*/
abstract fun execute(data: @UnsafeVariance T): PatchResult
}
abstract class OptionsContainer {
/**
* A list of [PatchOption]s.
* @see PatchOptions
*/
open val options = PatchOptions()
@Suppress("MemberVisibilityCanBePrivate")
val options = PatchOptions()
protected fun option(opt: PatchOption<*>): PatchOption<*> {
options.register(opt)
return opt
}
}

View File

@ -18,13 +18,17 @@ object RequirementNotMetException : Exception("null was passed into an option th
* @param options An array of [PatchOption]s.
*/
class PatchOptions(vararg val options: PatchOption<*>) : Iterable<PatchOption<*>> {
private val register = buildMap {
for (option in options) {
if (containsKey(option.key)) {
throw IllegalStateException("Multiple options found with the same key")
}
put(option.key, option)
private val register = mutableMapOf<String, PatchOption<*>>()
init {
options.forEach { register(it) }
}
internal fun register(option: PatchOption<*>) {
if (register.containsKey(option.key)) {
throw IllegalStateException("Multiple options found with the same key")
}
register[option.key] = option
}
/**
@ -91,7 +95,7 @@ sealed class PatchOption<T>(
* Gets the value of the option.
* Please note that using the wrong value type results in a runtime error.
*/
operator fun <T> getValue(thisRef: Nothing?, property: KProperty<*>) = value as T
operator fun <T> getValue(thisRef: Any?, property: KProperty<*>) = value as T
/**
* Gets the value of the option.

View File

@ -7,7 +7,7 @@ import java.io.File
import kotlin.test.assertNotEquals
internal class PatchOptionsTest {
private val options = ExampleBytecodePatch().options
private val options = ExampleBytecodePatch.options
@Test
fun `should not throw an exception`() {
@ -16,21 +16,25 @@ internal class PatchOptionsTest {
is PatchOption.StringOption -> {
option.value = "Hello World"
}
is PatchOption.BooleanOption -> {
option.value = false
}
is PatchOption.StringListOption -> {
option.value = option.options.first()
for (choice in option.options) {
println(choice)
}
}
is PatchOption.IntListOption -> {
option.value = option.options.first()
for (choice in option.options) {
println(choice)
}
}
is PatchOption.PathOption -> {
option.value = File("test.txt").toPath()
}

View File

@ -7,8 +7,8 @@ import app.revanced.patcher.data.impl.BytecodeData
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.or
import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.patch.OptionsContainer
import app.revanced.patcher.patch.PatchOption
import app.revanced.patcher.patch.PatchOptions
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.DependsOn
@ -33,6 +33,7 @@ import org.jf.dexlib2.immutable.reference.ImmutableStringReference
import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue
import org.jf.dexlib2.util.Preconditions
import java.io.File
import java.nio.file.Path
@Patch
@Name("example-bytecode-patch")
@ -47,6 +48,10 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
// Get the resolved method by its fingerprint from the resolver cache
val result = ExampleFingerprint.result!!
// Patch options
println(key1)
key2 = false
// Get the implementation for the resolved method
val method = result.mutableMethod
val implementation = method.implementation!!
@ -165,21 +170,31 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
)
}
override val options = PatchOptions(
PatchOption.StringOption(
"key1", "default", "title", "description", true
),
PatchOption.BooleanOption(
"key2", true, "title", "description" // required defaults to false
),
PatchOption.StringListOption(
"key3", "TEST", listOf("TEST", "TEST1", "TEST2"), "title", "description"
),
PatchOption.IntListOption(
"key4", 1, listOf(1, 2, 3), "title", "description"
),
PatchOption.PathOption(
"key5", File("test.txt").toPath(), "title", "description"
),
)
companion object : OptionsContainer() {
private var key1: String by option(
PatchOption.StringOption(
"key1", "default", "title", "description", true
)
)
private var key2: Boolean by option(
PatchOption.BooleanOption(
"key2", true, "title", "description" // required defaults to false
)
)
private var key3: List<String> by option(
PatchOption.StringListOption(
"key3", "TEST", listOf("TEST", "TEST1", "TEST2"), "title", "description"
)
)
private var key4: List<Int> by option(
PatchOption.IntListOption(
"key4", 1, listOf(1, 2, 3), "title", "description"
)
)
private var key5: Path by option(
PatchOption.PathOption(
"key5", File("test.txt").toPath(), "title", "description"
)
)
}
}