Skip to content

Commit

Permalink
refactor: Improve Patch Options
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Sculas committed Sep 7, 2022
1 parent 0e84465 commit 6b909c1
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 27 deletions.
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
11 changes: 10 additions & 1 deletion src/main/kotlin/app/revanced/patcher/patch/Patch.kt
Expand Up @@ -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
}
}
18 changes: 11 additions & 7 deletions src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt
Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -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.
Expand Down
Expand Up @@ -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`() {
Expand All @@ -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()
}
Expand Down
Expand Up @@ -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
Expand All @@ -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")
Expand All @@ -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!!
Expand Down Expand Up @@ -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"
)
)
}
}

0 comments on commit 6b909c1

Please sign in to comment.