aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorThunderblade73 <85900443+Thunderblade73@users.noreply.github.com>2024-06-01 12:23:34 +0200
committerGitHub <noreply@github.com>2024-06-01 12:23:34 +0200
commit1a99b231bd77a8765b1e1f29f2e2759c6351f6ed (patch)
treea1aa8a72e05bcf7a912ebe3defb3c8b1dc832457 /src/main
parent00c5d1604662af31bfbd3ed151764c414437b721 (diff)
downloadskyhanni-1a99b231bd77a8765b1e1f29f2e2759c6351f6ed.tar.gz
skyhanni-1a99b231bd77a8765b1e1f29f2e2759c6351f6ed.tar.bz2
skyhanni-1a99b231bd77a8765b1e1f29f2e2759c6351f6ed.zip
Backend: RepoPattern Exclusive Group & RepoPattern List (#1733)
Co-authored-by: Linnea Gräf <nea@nea.moe>
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt24
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/repopatterns/CommonPatternInfo.kt49
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPattern.kt32
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternExclusiveGroup.kt13
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternExclusiveGroupInfo.kt34
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternGroup.kt23
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternGui.kt36
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternImpl.kt42
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternKeyOwner.kt10
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternList.kt41
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternListImpl.kt18
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt206
-rw-r--r--src/main/resources/assets/skyhanni/gui/regexes.xml6
13 files changed, 439 insertions, 95 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt
index 305c825f7..347ce2b35 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt
@@ -14,6 +14,7 @@ import net.minecraft.util.ChatStyle
import net.minecraft.util.EnumChatFormatting
import net.minecraft.util.IChatComponent
import java.util.Base64
+import java.util.NavigableMap
import java.util.UUID
import java.util.function.Predicate
import java.util.regex.Matcher
@@ -98,6 +99,23 @@ object StringUtils {
return cleanedString.toString()
}
+ /**
+ * From https://stackoverflow.com/questions/10711494/get-values-in-treemap-whose-string-keys-start-with-a-pattern
+ */
+ 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)
+ }
+
+ fun nextLexicographicallyStringWithSameLength(input: String): String {
+ val lastCharPosition = input.length - 1
+ val inputWithoutLastChar = input.substring(0, lastCharPosition)
+ val lastChar = input[lastCharPosition]
+ val incrementedLastChar = (lastChar.code + 1).toChar()
+ return inputWithoutLastChar + incrementedLastChar
+ }
+
fun UUID.toDashlessUUID(): String = toString().replace("-", "")
@Deprecated("Use the new one instead", ReplaceWith("RegexUtils.matchMatcher(text, consumer)"))
@@ -171,6 +189,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..a465139ae
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/CommonPatternInfo.kt
@@ -0,0 +1,49 @@
+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 parent: RepoPatternKeyOwner?
+
+ abstract val shares: Boolean
+
+ 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, shares, parent)
+ if (shares) {
+ RepoPatternManager.checkExclusivity(owner, key)
+ } else {
+ RepoPatternManager.checkNameSpaceExclusivity(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 d04384ba0..e21b2dc59 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPattern.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPattern.kt
@@ -3,8 +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
/**
* RepoPattern is our innovative tool to cope with the fucking game updates Hypixel deems to be important enough to brick
@@ -41,43 +39,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 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 {
@@ -94,11 +87,22 @@ 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.
*/
fun group(prefix: String): RepoPatternGroup {
return RepoPatternGroup(prefix)
}
+
+ /**
+ * Obtains a [RepoPatternExclusiveGroup] which functions like a [RepoPatternGroup] but the key namespace can only be used via this object.
+ */
+ fun exclusiveGroup(prefix: String): RepoPatternExclusiveGroupInfo {
+ return RepoPatternExclusiveGroupInfo(prefix, null)
+ }
}
}
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternExclusiveGroup.kt b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternExclusiveGroup.kt
new file mode 100644
index 000000000..b68176fd7
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternExclusiveGroup.kt
@@ -0,0 +1,13 @@
+package at.hannibal2.skyhanni.utils.repopatterns
+
+/**
+ * A utility class for allowing easier definitions of [RepoPattern]s with a common prefix.
+ */
+class RepoPatternExclusiveGroup internal constructor(prefix: String, owner: RepoPatternKeyOwner?) :
+ RepoPatternGroup(prefix, owner) {
+
+ /**
+ * @return returns any pattern on the [prefix] key space (including list or any other complex structure, but as a simple pattern
+ * */
+ fun getUnusedPatterns() = RepoPatternManager.getUnusedPatterns(prefix)
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternExclusiveGroupInfo.kt b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternExclusiveGroupInfo.kt
new file mode 100644
index 000000000..d7b4717ef
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternExclusiveGroupInfo.kt
@@ -0,0 +1,34 @@
+package at.hannibal2.skyhanni.utils.repopatterns
+
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * A utility class for allowing easier definitions of [RepoPattern]s with a common prefix.
+ */
+class RepoPatternExclusiveGroupInfo internal constructor(val prefix: String, val parent: RepoPatternKeyOwner?) :
+ ReadOnlyProperty<Any?, RepoPatternExclusiveGroup> {
+
+ internal var hasObtainedLock = false
+ private lateinit var owner: RepoPatternKeyOwner
+
+ init {
+ RepoPatternManager.verifyKeyShape(prefix)
+ }
+
+ override fun getValue(thisRef: Any?, property: KProperty<*>): RepoPatternExclusiveGroup {
+ verifyLock(thisRef, property)
+ return RepoPatternExclusiveGroup(prefix, owner)
+ }
+
+ /**
+ * Try to lock the [key] to this key location.
+ * @see RepoPatternManager.checkExclusivity
+ */
+ private fun verifyLock(thisRef: Any?, property: KProperty<*>) {
+ if (hasObtainedLock) return
+ hasObtainedLock = true
+ owner = RepoPatternKeyOwner(thisRef?.javaClass, property, false, parent)
+ RepoPatternManager.checkNameSpaceExclusivity(owner, prefix)
+ }
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternGroup.kt b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternGroup.kt
index 1b00e4d83..860cf379e 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternGroup.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternGroup.kt
@@ -5,7 +5,10 @@ import org.intellij.lang.annotations.Language
/**
* A utility class for allowing easier definitions of [RepoPattern]s with a common prefix.
*/
-class RepoPatternGroup internal constructor(val prefix: String) {
+open class RepoPatternGroup internal constructor(
+ val prefix: String,
+ protected var parentGiver: RepoPatternKeyOwner? = null,
+) {
init {
RepoPatternManager.verifyKeyShape(prefix)
@@ -15,13 +18,27 @@ class RepoPatternGroup internal constructor(val prefix: String) {
* Shortcut to [RepoPattern.pattern] prefixed with [prefix].
*/
fun pattern(key: String, @Language("RegExp") fallback: String): RepoPattern {
- return RepoPattern.pattern("$prefix.$key", fallback)
+ return RepoPatternManager.of("$prefix.$key", fallback, parentGiver)
+ }
+
+ /**
+ * Shortcut to [RepoPattern.list] prefixed with [prefix].
+ */
+ fun list(key: String, @Language("RegExp") vararg fallbacks: String): RepoPatternList {
+ return RepoPatternManager.ofList("$prefix.$key", fallbacks, parentGiver)
}
/**
* Shortcut to [RepoPattern.group] prefixed with [prefix].
*/
fun group(subgroup: String): RepoPatternGroup {
- return RepoPatternGroup("$prefix.$subgroup")
+ return RepoPatternGroup("$prefix.$subgroup", parentGiver)
+ }
+
+ /**
+ * Shortcut to [RepoPattern.exclusiveGroup] prefixed with [prefix].
+ */
+ fun exclusiveGroup(subgroup: String): RepoPatternExclusiveGroupInfo {
+ return RepoPatternExclusiveGroupInfo("$prefix.$subgroup", parentGiver)
}
}
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 9dfb38db3..6f5b90172 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.notenoughupdates.moulconfig.gui.GuiContext
import io.github.notenoughupdates.moulconfig.observer.ObservableList
import io.github.notenoughupdates.moulconfig.xml.Bind
import io.github.notenoughupdates.moulconfig.xml.XMLUniverse
+import net.minecraft.client.Minecraft
/**
* Gui for analyzing [RepoPattern]s
@@ -31,30 +32,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 1116bbf76..7a2671854 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,14 @@ import kotlin.reflect.KProperty
class RepoPatternImpl(
override val defaultPattern: String,
override val key: String,
-) : RepoPattern {
+ override val parent: RepoPatternKeyOwner? = null,
+) : RepoPattern() {
- var compiledPattern: Pattern = Pattern.compile(defaultPattern)
- var wasLoadedRemotely = false
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)
+ override var value: Pattern = Pattern.compile(defaultPattern)
+ override var isLoadedRemotely: Boolean = false
+ override val shares = true
+ override fun dump(): Map<String, String> {
+ return mapOf(key to defaultPattern)
}
-
- /**
- * 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 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..255d6fb65 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternKeyOwner.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternKeyOwner.kt
@@ -2,7 +2,17 @@ package at.hannibal2.skyhanni.utils.repopatterns
import kotlin.reflect.KProperty
+/** Declares which class/property owns a repo pattern key.
+ * @param ownerClass the owning class
+ * @param property the property how owns it in the [ownerClass]
+ * @param shares declares if the sub key space is allowed to be used by other [RepoPatternKeyOwner]
+ * @param parent the [RepoPatternKeyOwner] that gives the permission to use a sub key from it sub key space that is locked, as [shares] is false for that[RepoPatternKeyOwner]
+ * @param transient declares if it is just a ghost how can be replaced at any time in the [RepoPatternManager.exclusivity]
+ * */
data class RepoPatternKeyOwner(
val ownerClass: Class<*>?,
val property: KProperty<*>?,
+ val shares: Boolean,
+ val parent: RepoPatternKeyOwner?,
+ val transient: 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..38f4acdbb
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternListImpl.kt
@@ -0,0 +1,18 @@
+package at.hannibal2.skyhanni.utils.repopatterns
+
+import java.util.regex.Pattern
+
+class RepoPatternListImpl(
+ fallback: List<String>,
+ override val key: String,
+ override val parent: RepoPatternKeyOwner? = null,
+) : 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 val shares = false
+ 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 18cfb55c5..931a5aac9 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt
@@ -7,13 +7,18 @@ import at.hannibal2.skyhanni.events.ConfigLoadEvent
import at.hannibal2.skyhanni.events.LorenzEvent
import at.hannibal2.skyhanni.events.PreInitFinishedEvent
import at.hannibal2.skyhanni.events.RepositoryReloadEvent
+import at.hannibal2.skyhanni.test.command.ErrorManager
import at.hannibal2.skyhanni.utils.ConditionalUtils.afterChange
import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.StringUtils
+import at.hannibal2.skyhanni.utils.StringUtils.substringBeforeLastOrNull
import at.hannibal2.skyhanni.utils.RegexUtils.matches
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
@@ -22,7 +27,7 @@ import java.util.regex.PatternSyntaxException
*/
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.
@@ -30,6 +35,15 @@ object RepoPatternManager {
private var regexes: RepoPatternDump? = null
/**
+ * [regexes] but as a NavigableMap. (Creates the Map at call)
+ */
+ private val remotePattern: NavigableMap<String, String>
+ get() = TreeMap(
+ if (localLoading) mapOf()
+ else regexes?.regexes ?: mapOf()
+ )
+
+ /**
* Map containing the exclusive owner of a regex key
*/
private var exclusivity: MutableMap<String, RepoPatternKeyOwner> = mutableMapOf()
@@ -38,7 +52,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 = try {
@@ -75,17 +89,62 @@ object RepoPatternManager {
* using that [key] again. Thread safe.
*/
fun checkExclusivity(owner: RepoPatternKeyOwner, key: String) {
+ val parentKeyHolder = owner.parent
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 && !previousOwner.transient) {
+ 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 transient = owner.copy(shares = true, transient = true)
+ var parent = key
+ var previousParentOwnerMutable: RepoPatternKeyOwner? = null
+ while (previousParentOwnerMutable == null && parent.isNotEmpty()) {
+ parent = parent.substringBeforeLastOrNull(".") ?: return
+ previousParentOwnerMutable = exclusivity[parent]
+ previousParentOwnerMutable ?: run {
+ exclusivity[parent] = transient
+ }
+ }
+ val previousParentOwner = previousParentOwnerMutable
+
+ if (previousParentOwner != null && previousParentOwner != parentKeyHolder && !(previousParentOwner.shares && previousParentOwner.parent == parentKeyHolder)) {
+ 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}" +
+ if (parentKeyHolder != null) "with parentKeyHolder ${parentKeyHolder.ownerClass} / ${parentKeyHolder.property}"
+ else ""
+ )
+ }
}
+
}
}
+ /**
+ * Check that the [owner] has exclusive right to the specified namespace and locks out other code parts from ever
+ * using that [key] prefix again without permission of the [owner]. Thread safe.
+ */
+ fun checkNameSpaceExclusivity(owner: RepoPatternKeyOwner, key: String) {
+ synchronized(exclusivity) {
+ val preRegistered = exclusivity[key]
+ if (preRegistered != null) {
+ if (!config.tolerateDuplicateUsage) crash(
+ "Non unique access to array regex at \"$key\"." +
+ " First obtained by ${preRegistered.ownerClass} / ${preRegistered.property}," +
+ " tried to use at ${owner.ownerClass} / ${owner.property}"
+ )
+ }
+ }
+ checkExclusivity(owner, key)
+ }
+
@SubscribeEvent
fun onRepoReload(event: RepositoryReloadEvent) {
loadPatternsFromDump(event.getConstant<RepoPatternDump>("regexes"))
@@ -106,26 +165,62 @@ object RepoPatternManager {
* Reload patterns in [usedKeys] from [regexes] or their fallbacks.
*/
private fun reloadPatterns() {
- val remotePatterns =
- if (localLoading) mapOf()
- else regexes?.regexes ?: mapOf()
-
+ val remotePatterns = remotePattern
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: NavigableMap<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: NavigableMap<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-9]+\\.)*[a-z0-9]+$")
@@ -145,7 +240,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)
@@ -164,15 +260,67 @@ object RepoPatternManager {
}
}
- fun of(key: String, fallback: String): RepoPattern {
+ fun of(key: String, fallback: String, parentKeyHolder: RepoPatternKeyOwner? = null): RepoPattern {
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) {
- exclusivity[key] = RepoPatternKeyOwner(null, null)
usedKeys[key]?.hasObtainedLock = false
}
- return RepoPatternImpl(fallback, key).also { usedKeys[key] = it }
+ return RepoPatternImpl(fallback, key, parentKeyHolder).also { usedKeys[key] = it }
}
+
+ fun ofList(
+ key: String,
+ fallbacks: Array<out String>,
+ parentKeyHolder: RepoPatternKeyOwner? = null,
+ ): 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, parentKeyHolder).also { usedKeys[key] = it }
+
+ }
+
+ /**
+ * The caller must ensure the exclusivity to the [prefix]!
+ *
+ * @param prefix the prefix to search without the dot at the end (the match includes the .)
+ * @return returns any pattern on the [prefix] key space (including list or any other complex structure, but as a simple pattern
+ * */
+ internal fun getUnusedPatterns(prefix: String): List<Pattern> {
+ if (config.forceLocal.get()) return emptyList()
+ try {
+ verifyKeyShape(prefix)
+ } catch (e: IllegalArgumentException) {
+ ErrorManager.logErrorWithData(e, "getUnusedPatterns failed do to invalid key shape", "prefix" to prefix)
+ return emptyList()
+ }
+ val prefixWithDot = "$prefix."
+ val patterns = StringUtils.subMapOfStringsStartingWith(prefixWithDot, remotePattern)
+ val holders = StringUtils.subMapOfStringsStartingWith(prefixWithDot, usedKeys)
+
+ val noShareHolder = holders.filter { !it.value.shares }.map { it.key.removePrefix(prefixWithDot) }
+ .groupBy { it.count { it == '.' } }
+
+ return patterns.filter { it.key !in holders.keys }.filter { unused ->
+ val dot = unused.key.count { it == '.' }
+ val possibleConflicts = noShareHolder.filter { it.key < dot }.flatMap { it.value }.toSet()
+ var key: String = unused.key.removePrefix(prefixWithDot)
+ while (key.isNotEmpty()) {
+ if (possibleConflicts.contains(key)) return@filter false
+ key = key.substringBeforeLastOrNull(".") ?: return@filter true
+ }
+ true
+ }.map { it.value.toPattern() }
+ }
+
}
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>