diff options
author | Thunderblade73 <85900443+Thunderblade73@users.noreply.github.com> | 2024-06-01 12:23:34 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-01 12:23:34 +0200 |
commit | 1a99b231bd77a8765b1e1f29f2e2759c6351f6ed (patch) | |
tree | a1aa8a72e05bcf7a912ebe3defb3c8b1dc832457 /src/main/java | |
parent | 00c5d1604662af31bfbd3ed151764c414437b721 (diff) | |
download | skyhanni-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/java')
12 files changed, 436 insertions, 92 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() } + } + } |