oSumAtrIX a46e948b5a
feat: Add PatchOption#valueType to handle type erasure
Without this new property, it is not possible to infer the type without abusing `ClassCastException` at runtime to infer the type of the option value.

BREAKING CHANGE: This changes the signature of the `PatchOption` constructor.
2023-10-24 22:33:10 +02:00

364 lines
14 KiB
Kotlin

package app.revanced.patcher.patch.options
import app.revanced.patcher.patch.Patch
import kotlin.reflect.KProperty
/**
* A [Patch] option.
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values identified by their string representation.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param valueType The type of the option value (to handle type erasure).
* @param validator The function to validate the option value.
* @param T The value type of the option.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
open class PatchOption<T>(
val key: String,
val default: T?,
val values: Map<String, T?>?,
val title: String?,
val description: String?,
val required: Boolean,
val valueType: String,
val validator: PatchOption<T>.(T?) -> Boolean
) {
/**
* The value of the [PatchOption].
*/
var value: T?
/**
* Set the value of the [PatchOption].
*
* @param value The value to set.
*
* @throws PatchOptionException.ValueRequiredException If the value is required but null.
* @throws PatchOptionException.ValueValidationException If the value is invalid.
*/
set(value) {
assertRequiredButNotNull(value)
assertValid(value)
uncheckedValue = value
}
/**
* Get the value of the [PatchOption].
*
* @return The value.
*
* @throws PatchOptionException.ValueRequiredException If the value is required but null.
* @throws PatchOptionException.ValueValidationException If the value is invalid.
*/
get() {
assertRequiredButNotNull(uncheckedValue)
assertValid(uncheckedValue)
return uncheckedValue
}
// The unchecked value is used to allow setting the value without validation.
private var uncheckedValue = default
/**
* Reset the [PatchOption] to its default value.
* Override this method if you need to mutate the value instead of replacing it.
*/
open fun reset() {
uncheckedValue = default
}
private fun assertRequiredButNotNull(value: T?) {
if (required && value == null) throw PatchOptionException.ValueRequiredException(this)
}
private fun assertValid(value: T?) {
if (!validator(value)) throw PatchOptionException.ValueValidationException(value, this)
}
override fun toString() = value.toString()
operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
this.value = value
}
@Suppress("unused")
companion object PatchExtensions {
/**
* Create a new [PatchOption] with a string value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.stringPatchOption(
key: String,
default: String? = null,
values: Map<String, String?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<String>.(String?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "String", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with an integer value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.intPatchOption(
key: String,
default: Int? = null,
values: Map<String, Int?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Int?>.(Int?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "Int", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with a boolean value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.booleanPatchOption(
key: String,
default: Boolean? = null,
values: Map<String, Boolean?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Boolean?>.(Boolean?) -> Boolean = { true }
) = PatchOption(key, default, values, title, description, required, "Boolean", validator).also {
registerOption(it)
}
/**
* Create a new [PatchOption] with a float value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.floatPatchOption(
key: String,
default: Float? = null,
values: Map<String, Float?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Float?>.(Float?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "Float", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with a long value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.longPatchOption(
key: String,
default: Long? = null,
values: Map<String, Long?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Long?>.(Long?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "Long", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with a string array value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.stringArrayPatchOption(
key: String,
default: Array<String>? = null,
values: Map<String, Array<String>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<String>?>.(Array<String>?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "StringArray", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with an integer array value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.intArrayPatchOption(
key: String,
default: Array<Int>? = null,
values: Map<String, Array<Int>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Int>?>.(Array<Int>?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "IntArray", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with a boolean array value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.booleanArrayPatchOption(
key: String,
default: Array<Boolean>? = null,
values: Map<String, Array<Boolean>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Boolean>?>.(Array<Boolean>?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "BooleanArray", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with a float array value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.floatArrayPatchOption(
key: String,
default: Array<Float>? = null,
values: Map<String, Array<Float>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Float>?>.(Array<Float>?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "FloatArray", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with a long array value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.longArrayPatchOption(
key: String,
default: Array<Long>? = null,
values: Map<String, Array<Long>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Long>?>.(Array<Long>?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "LongArray", validator
).also { registerOption(it) }
private fun <P : Patch<*>> P.registerOption(option: PatchOption<*>) = option.also { options.register(it) }
}
}