diff options
Diffstat (limited to 'src')
10 files changed, 247 insertions, 91 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt index 7c33cd1b2..4c3b6defb 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt @@ -65,7 +65,7 @@ object StringUtils { /** * From https://stackoverflow.com/questions/10711494/get-values-in-treemap-whose-string-keys-start-with-a-pattern */ - fun <T> subMapWithKeysThatAreSuffixes(prefix: String, map: NavigableMap<String?, T>): Map<String?, T>? { + fun <T> subMapOfStringsStartingWith(prefix: String, map: NavigableMap<String, T>): NavigableMap<String, T> { if ("" == prefix) return map val lastKey = nextLexicographicallyStringWithSameLength(prefix) return map.subMap(prefix, true, lastKey, false) @@ -130,6 +130,12 @@ object StringUtils { return default } + fun String.substringBeforeLastOrNull(needle: String): String? { + val index = this.lastIndexOf(needle) + if (index < 0) return null + return this.substring(0, index) + } + fun encodeBase64(input: String) = Base64.getEncoder().encodeToString(input.toByteArray()) fun decodeBase64(input: String) = Base64.getDecoder().decode(input).decodeToString() diff --git a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/CommonPatternInfo.kt b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/CommonPatternInfo.kt new file mode 100644 index 000000000..ca5460a4f --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/CommonPatternInfo.kt @@ -0,0 +1,42 @@ +package at.hannibal2.skyhanni.utils.repopatterns + +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +sealed class CommonPatternInfo<R, C> : ReadOnlyProperty<Any?, C> { + abstract val isLoadedRemotely: Boolean + + abstract val wasOverridden: Boolean + + abstract val defaultPattern: R + + abstract val key: String + + abstract val value: C + + /** + * Whether the pattern has obtained a lock on a code location and a key. + * Once set, no other code locations can access this repo pattern (and therefore the key). + * @see RepoPatternManager.checkExclusivity + */ + internal var hasObtainedLock = false + + + override fun getValue(thisRef: Any?, property: KProperty<*>): C { + verifyLock(thisRef, property) + return value + } + + /** + * Try to lock the [key] to this key location. + * @see RepoPatternManager.checkExclusivity + */ + private fun verifyLock(thisRef: Any?, property: KProperty<*>) { + if (hasObtainedLock) return + hasObtainedLock = true + val owner = RepoPatternKeyOwner(thisRef?.javaClass, property) + RepoPatternManager.checkExclusivity(owner, key) + } + + abstract fun dump(): Map<String, String> +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPattern.kt b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPattern.kt index 6af2cf2fe..c0db43324 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPattern.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPattern.kt @@ -3,7 +3,6 @@ package at.hannibal2.skyhanni.utils.repopatterns import at.hannibal2.skyhanni.SkyHanniMod import org.intellij.lang.annotations.Language import java.util.regex.Pattern -import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty /** @@ -41,43 +40,38 @@ import kotlin.reflect.KProperty * When accessing the metaobject (the RepoPattern instance itself), then you afford yourself less protection at the cost * of slightly more options. */ -interface RepoPattern : ReadOnlyProperty<Any?, Pattern> { +sealed class RepoPattern : CommonPatternInfo<String, Pattern>() { /** * Check whether [value] has been loaded remotely or from the fallback value at [defaultPattern]. In case this is * accessed off-thread there are no guarantees for the correctness of this value in relation to any specific call * to [value]. */ - val isLoadedRemotely: Boolean + abstract override val isLoadedRemotely: Boolean /** * Check whether [value] was compiled from a value other than the [defaultPattern]. This is `false` even when * loading remotely if the remote pattern matches the local one. */ - val wasOverridden: Boolean + abstract override val wasOverridden: Boolean /** * The default pattern that is specified at compile time. This local pattern will be a fallback in case there is no * remote pattern available or the remote pattern does not compile. */ - val defaultPattern: String + abstract override val defaultPattern: String /** - * Key for this pattern. Used as an identifier when loading from the repo. Should be consistent accross versions. + * Key for this pattern. Used as an identifier when loading from the repo. Should be consistent across versions. */ - val key: String + abstract override val key: String /** * Should not be accessed directly. Instead, use delegation at one code location and share the regex from there. * ```kt - * val actualValue by pattern + * val actualValue: Pattern by pattern * ``` */ - val value: Pattern - - override fun getValue(thisRef: Any?, property: KProperty<*>): Pattern { - return value - } - + abstract override val value: Pattern companion object { /** @@ -88,6 +82,10 @@ interface RepoPattern : ReadOnlyProperty<Any?, Pattern> { return RepoPatternManager.of(key, fallback) } + fun list(key: String, @Language("RegExp") vararg fallbacks: String): RepoPatternList { + return RepoPatternManager.ofList(key, fallbacks) + } + /** * Obtains a [RepoPatternGroup] to allow for easier defining [RepoPattern]s with common prefixes. */ diff --git a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternGui.kt b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternGui.kt index 2a2393686..9e3a36969 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternGui.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternGui.kt @@ -7,6 +7,7 @@ import io.github.moulberry.moulconfig.gui.GuiScreenElementWrapperNew import io.github.moulberry.moulconfig.observer.ObservableList import io.github.moulberry.moulconfig.xml.Bind import io.github.moulberry.moulconfig.xml.XMLUniverse +import net.minecraft.client.Minecraft /** * Gui for analyzing [RepoPattern]s @@ -29,30 +30,43 @@ class RepoPatternGui private constructor() { @field:Bind var search: String = "" private var lastSearch = null as String? - private val allKeys = RepoPatternManager.allPatterns.toList() + private val allKeys = RepoPatternManager.allPatterns + .toList() .sortedBy { it.key } .map { RepoPatternInfo(it) } private var searchCache = ObservableList(mutableListOf<RepoPatternInfo>()) class RepoPatternInfo( - repoPatternImpl: RepoPatternImpl + repoPatternImpl: CommonPatternInfo<*, *> ) { @field:Bind val key: String = repoPatternImpl.key + val remoteData = when (repoPatternImpl) { + is RepoPatternList -> repoPatternImpl.value.map { it.pattern() } + is RepoPattern -> listOf(repoPatternImpl.value.pattern()) + } + @field:Bind - val regex: String = repoPatternImpl.value.pattern() + val regex: String = remoteData.joinToString("\n") @field:Bind - val hoverRegex: List<String> = if (repoPatternImpl.isLoadedRemotely) { - listOf( - "§aLoaded remotely", - "§7Remote: " + repoPatternImpl.compiledPattern.pattern(), - "§7Local: " + repoPatternImpl.defaultPattern, - ) - } else { - listOf("§cLoaded locally", "§7Local: " + repoPatternImpl.defaultPattern) + val hoverRegex: List<String> = run { + val localPatterns = when (repoPatternImpl) { + is RepoPatternList -> repoPatternImpl.defaultPattern + is RepoPattern -> listOf(repoPatternImpl.defaultPattern) + } + if (repoPatternImpl.isLoadedRemotely) { + listOf( + "§aLoaded remotely", + "§7Remote:", + ) + remoteData.map { " §f- $it" } + listOf( + "§7Local:", + ) + localPatterns.map { " §f- $it" } + } else { + listOf("§cLoaded locally", "§7Local:") + localPatterns.map { " §f- $it" } + } } @field:Bind diff --git a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternImpl.kt b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternImpl.kt index 89e9f99ec..c2644f79b 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternImpl.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternImpl.kt @@ -1,7 +1,6 @@ package at.hannibal2.skyhanni.utils.repopatterns import java.util.regex.Pattern -import kotlin.reflect.KProperty /** * Internal class implementing [RepoPattern]. Obtain via [RepoPattern.pattern]. @@ -9,41 +8,11 @@ import kotlin.reflect.KProperty class RepoPatternImpl( override val defaultPattern: String, override val key: String, -) : RepoPattern { - var compiledPattern: Pattern = Pattern.compile(defaultPattern) - var wasLoadedRemotely = false +) : RepoPattern() { override var wasOverridden = false - - /** - * Whether the pattern has obtained a lock on a code location and a key. - * Once set, no other code locations can access this repo pattern (and therefore the key). - * @see RepoPatternManager.checkExclusivity - */ - var hasObtainedLock = false - - override fun getValue(thisRef: Any?, property: KProperty<*>): Pattern { - verifyLock(thisRef, property) - return super.getValue(thisRef, property) - } - - /** - * Try to lock the [key] to this key location. - * @see RepoPatternManager.checkExclusivity - */ - fun verifyLock(thisRef: Any?, property: KProperty<*>) { - if (hasObtainedLock) return - hasObtainedLock = true - val owner = RepoPatternKeyOwner(thisRef?.javaClass, property) - RepoPatternManager.checkExclusivity(owner, key) + override var value: Pattern = Pattern.compile(defaultPattern) + override var isLoadedRemotely: Boolean = false + override fun dump(): Map<String, String> { + return mapOf(key to defaultPattern) } - - - override val value: Pattern - get() { - return compiledPattern - } - override val isLoadedRemotely: Boolean - get() { - return wasLoadedRemotely - } } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternKeyOwner.kt b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternKeyOwner.kt index ccd98ff1c..1847db49f 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternKeyOwner.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternKeyOwner.kt @@ -5,4 +5,5 @@ import kotlin.reflect.KProperty data class RepoPatternKeyOwner( val ownerClass: Class<*>?, val property: KProperty<*>?, + val shared: Boolean = false, ) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternList.kt b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternList.kt new file mode 100644 index 000000000..352592f7e --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternList.kt @@ -0,0 +1,41 @@ +package at.hannibal2.skyhanni.utils.repopatterns + +import java.util.regex.Pattern + +/** + * A list of [RepoPattern]s. It can be used almost identically, but will instead provide a [List] of [Pattern]s + */ +sealed class RepoPatternList : CommonPatternInfo<List<String>, List<Pattern>>() { + /** + * Check whether [value] has been loaded remotely or from the fallback value at [defaultPattern]. In case this is + * accessed off-thread there are no guarantees for the correctness of this value in relation to any specific call + * to [value]. + */ + abstract override val isLoadedRemotely: Boolean + + /** + * Check whether [value] was compiled from a value other than the [defaultPattern]. This is `false` even when + * loading remotely if the remote pattern matches the local one. + */ + abstract override val wasOverridden: Boolean + + /** + * The default patterns that is specified at compile time. This local patterns will be a fallback in case there are + * no remote patterns available or the remote patterns do not compile. + */ + abstract override val defaultPattern: List<String> + + /** + * Key for this pattern list. When loading identifiers from the repo, this will pull all identifiers + * that start with the key, followed by `.{number}`. Should be consistent across versions. + */ + abstract override val key: String + + /** + * Should not be accessed directly. Instead, use delegation at one code location and share the regexes from there. + * ```kt + * val actualValue: List<Pattern> by pattern + * ``` + */ + abstract override val value: List<Pattern> +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternListImpl.kt b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternListImpl.kt new file mode 100644 index 000000000..e35b4e8e2 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternListImpl.kt @@ -0,0 +1,13 @@ +package at.hannibal2.skyhanni.utils.repopatterns + +import java.util.regex.Pattern + +class RepoPatternListImpl(fallback: List<String>, override val key: String) : RepoPatternList() { + override var isLoadedRemotely: Boolean = false + override var wasOverridden: Boolean = false + override val defaultPattern: List<String> = fallback + override var value: List<Pattern> = fallback.map(Pattern::compile) + override fun dump(): Map<String, String> { + return defaultPattern.withIndex().associate { (key + "." + it.index) to it.value } + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt index 5140aa186..6799f42d4 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt @@ -7,11 +7,15 @@ import at.hannibal2.skyhanni.events.LorenzEvent import at.hannibal2.skyhanni.events.PreInitFinishedEvent import at.hannibal2.skyhanni.events.RepositoryReloadEvent import at.hannibal2.skyhanni.utils.LorenzUtils.afterChange +import at.hannibal2.skyhanni.utils.StringUtils import at.hannibal2.skyhanni.utils.StringUtils.matches +import at.hannibal2.skyhanni.utils.StringUtils.substringBeforeLastOrNull import net.minecraft.launchwrapper.Launch import net.minecraftforge.fml.common.FMLCommonHandler import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import java.io.File +import java.util.NavigableMap +import java.util.TreeMap import java.util.regex.Pattern import java.util.regex.PatternSyntaxException @@ -19,7 +23,7 @@ import java.util.regex.PatternSyntaxException * Manages [RepoPattern]s. */ object RepoPatternManager { - val allPatterns: Collection<RepoPatternImpl> get() = usedKeys.values + val allPatterns: Collection<CommonPatternInfo<*, *>> get() = usedKeys.values /** * Remote loading data that will be used to compile regexes from, once such a regex is needed. @@ -35,7 +39,7 @@ object RepoPatternManager { * Map containing all keys and their repo patterns. Used for filling in new regexes after an update, and for * checking duplicate registrations. */ - private var usedKeys = mutableMapOf<String, RepoPatternImpl>() + private var usedKeys: NavigableMap<String, CommonPatternInfo<*, *>> = TreeMap() private var wasPreinitialized = false private val isInDevEnv = Launch.blackboard["fml.deobfuscatedEnvironment"] as Boolean @@ -55,12 +59,24 @@ object RepoPatternManager { */ fun checkExclusivity(owner: RepoPatternKeyOwner, key: String) { synchronized(exclusivity) { - val previousOwner = exclusivity.get(key) - if (previousOwner != owner && previousOwner != null) { - if (!config.tolerateDuplicateUsage) - crash("Non unique access to regex at \"$key\". First obtained by ${previousOwner.ownerClass} / ${previousOwner.property}, tried to use at ${owner.ownerClass} / ${owner.property}") - } else { - exclusivity[key] = owner + run { + val previousOwner = exclusivity[key] + if (previousOwner != owner && previousOwner != null) { + if (!config.tolerateDuplicateUsage) + crash("Non unique access to regex at \"$key\". First obtained by ${previousOwner.ownerClass} / ${previousOwner.property}, tried to use at ${owner.ownerClass} / ${owner.property}") + } else { + exclusivity[key] = owner + } + } + run { + val parent = key.substringBeforeLastOrNull(".") ?: return + val previousParentOwner = exclusivity[parent] + if (previousParentOwner != null && !previousParentOwner.shared) { + if (!config.tolerateDuplicateUsage) + crash("Non unique access to array regex at \"$parent\". First obtained by ${previousParentOwner.ownerClass} / ${previousParentOwner.property}, tried to use at ${owner.ownerClass} / ${owner.property}") + } else { + exclusivity[parent] = owner.copy(shared = true) + } } } } @@ -83,25 +99,65 @@ object RepoPatternManager { */ private fun reloadPatterns() { val remotePatterns = - if (config.forceLocal.get()) mapOf() - else regexes?.regexes ?: mapOf() - + TreeMap( + if (config.forceLocal.get()) mapOf() + else regexes?.regexes ?: mapOf() + ) for (it in usedKeys.values) { - val remotePattern = remotePatterns[it.key] - try { - if (remotePattern != null) { - it.compiledPattern = Pattern.compile(remotePattern) - it.wasLoadedRemotely = true - it.wasOverridden = remotePattern != it.defaultPattern - continue - } - } catch (e: PatternSyntaxException) { - SkyHanniMod.logger.error("Error while loading pattern from repo", e) + when (it) { + is RepoPatternListImpl -> loadArrayPatterns(remotePatterns, it) + is RepoPatternImpl -> loadStandalonePattern(remotePatterns, it) + } + } + } + + private fun loadStandalonePattern(remotePatterns: TreeMap<String, String>, it: RepoPatternImpl) { + val remotePattern = remotePatterns[it.key] + try { + if (remotePattern != null) { + it.value = Pattern.compile(remotePattern) + it.isLoadedRemotely = true + it.wasOverridden = remotePattern != it.defaultPattern + return } - it.compiledPattern = Pattern.compile(it.defaultPattern) - it.wasLoadedRemotely = false - it.wasOverridden = false + } catch (e: PatternSyntaxException) { + SkyHanniMod.logger.error("Error while loading pattern from repo", e) } + it.value = Pattern.compile(it.defaultPattern) + it.isLoadedRemotely = false + it.wasOverridden = false + } + + private fun loadArrayPatterns(remotePatterns: TreeMap<String, String>, arrayPattern: RepoPatternListImpl) { + val prefix = arrayPattern.key + "." + val remotePatternList = StringUtils.subMapOfStringsStartingWith(prefix, remotePatterns) + val patternMap = remotePatternList.mapNotNull { + val index = it.key.removePrefix(prefix).toIntOrNull() + if (index == null) null + else index to it.value + } + + fun setDefaultPatterns() { + arrayPattern.value = arrayPattern.defaultPattern.map(Pattern::compile) + arrayPattern.isLoadedRemotely = false + arrayPattern.wasOverridden = false + } + + if (patternMap.mapTo(mutableSetOf()) { it.first } != patternMap.indices.toSet()) { + SkyHanniMod.logger.error("Incorrect index set for $arrayPattern") + setDefaultPatterns() + } + + val patternStrings = patternMap.sortedBy { it.first }.map { it.second } + try { + arrayPattern.value = patternStrings.map(Pattern::compile) + arrayPattern.isLoadedRemotely = true + arrayPattern.wasOverridden = patternStrings != arrayPattern.defaultPattern + return + } catch (e: PatternSyntaxException) { + SkyHanniMod.logger.error("Error while loading pattern from repo", e) + } + setDefaultPatterns() } val keyShape = Pattern.compile("^(?:[a-z0-9A-Z]+\\.)*[a-z0-9A-Z]+$") @@ -121,7 +177,8 @@ object RepoPatternManager { ConfigManager.gson.toJson( RepoPatternDump( sourceLabel, - usedKeys.values.associate { it.key to it.defaultPattern }) + usedKeys.values.flatMap { it.dump().toList() }.toMap() + ) ) file.parentFile.mkdirs() file.writeText(data) @@ -146,9 +203,24 @@ object RepoPatternManager { crash("Illegal late initialization of repo pattern. Repo pattern needs to be created during pre-initialization.") } if (key in usedKeys) { - exclusivity[key] = RepoPatternKeyOwner(null, null) usedKeys[key]?.hasObtainedLock = false } return RepoPatternImpl(fallback, key).also { usedKeys[key] = it } } + + fun ofList(key: String, fallbacks: Array<out String>): RepoPatternList { + verifyKeyShape(key) + if (wasPreinitialized && !config.tolerateLateRegistration) { + crash("Illegal late initialization of repo pattern. Repo pattern needs to be created during pre-initialization.") + } + if (key in usedKeys) { + usedKeys[key]?.hasObtainedLock = false + } + StringUtils.subMapOfStringsStartingWith(key, usedKeys).forEach { + it.value.hasObtainedLock = false + } + return RepoPatternListImpl(fallbacks.toList(), key).also { usedKeys[key] = it } + + } + } diff --git a/src/main/resources/assets/skyhanni/gui/regexes.xml b/src/main/resources/assets/skyhanni/gui/regexes.xml index 7ba1feaef..d9b20d171 100644 --- a/src/main/resources/assets/skyhanni/gui/regexes.xml +++ b/src/main/resources/assets/skyhanni/gui/regexes.xml @@ -7,14 +7,14 @@ <TextField value="@search"/> <Text text="@poll"/> </Row> - <ScrollPanel width="360" height="266"> + <ScrollPanel width="560" height="400"> <Array data="@searchResults"> <Row> <Hover lines="@keyW"> - <Text text="@key" width="100"/> + <Text text="@key" width="200"/> </Hover> <Hover lines="@hoverRegex"> - <Text text="@regex" width="200"/> + <Text text="@regex" width="300"/> </Hover> <Text text="@overriden" width="50"/> </Row> |
