aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2023-11-18 06:06:18 +0100
committerLinnea Gräf <nea@nea.moe>2023-12-04 20:46:40 +0100
commitac14fe8fe04c44e2e08fa90b5cc8fdce293c2ab1 (patch)
tree79a72c3593d4e0304419c259559b371f12bd5249 /src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt
parentd9a1a8dda967f40bd4a7a2707bf3ff5411093e7d (diff)
downloadSkyHanni-repopatterns.tar.gz
SkyHanni-repopatterns.tar.bz2
SkyHanni-repopatterns.zip
Add RepoPatternsrepopatterns
RepoPatterns allow us to easily update regexes without creating a new release.
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt153
1 files changed, 153 insertions, 0 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt
new file mode 100644
index 000000000..9ebd6c145
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/utils/repopatterns/RepoPatternManager.kt
@@ -0,0 +1,153 @@
+package at.hannibal2.skyhanni.utils.repopatterns
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.config.ConfigManager
+import at.hannibal2.skyhanni.events.ConfigLoadEvent
+import at.hannibal2.skyhanni.events.LorenzEvent
+import at.hannibal2.skyhanni.events.PreInitFinished
+import at.hannibal2.skyhanni.events.RepositoryReloadEvent
+import at.hannibal2.skyhanni.utils.StringUtils.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.regex.Pattern
+import java.util.regex.PatternSyntaxException
+
+/**
+ * Manages [RepoPattern]s.
+ */
+object RepoPatternManager {
+ val allPatterns: Collection<RepoPatternImpl> get() = usedKeys.values
+
+ /**
+ * Remote loading data that will be used to compile regexes from, once such a regex is needed.
+ */
+ private var regexes: RepoPatternDump? = null
+
+ /**
+ * Map containing the exclusive owner of a regex key
+ */
+ private var exclusivity: MutableMap<String, RepoPatternKeyOwner> = mutableMapOf()
+
+ /**
+ * 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 wasPreinitialized = false
+ private val isInDevEnv = Launch.blackboard["fml.deobfuscatedEnvironment"] as Boolean
+ private val config get() = SkyHanniMod.feature.dev.repoPattern
+
+ /**
+ * Crash if in a development environment, or if inside a guarded event handler.
+ */
+ fun crash(reason: String) {
+ if (isInDevEnv || LorenzEvent.isInGuardedEventHandler)
+ throw RuntimeException(reason)
+ }
+
+ /**
+ * Check that the [owner] has exclusive right to the specified [key], and locks out other code parts from ever
+ * using that [key] again. Thread safe.
+ */
+ 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
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onRepoReload(event: RepositoryReloadEvent) {
+ regexes = null
+ regexes = event.getConstant<RepoPatternDump>("regexes")
+ reloadPatterns()
+ }
+
+
+ @SubscribeEvent
+ fun onConfigInit(event: ConfigLoadEvent) {
+ config.forceLocal.whenChanged { b, b2 -> reloadPatterns() }
+ }
+
+ /**
+ * Reload patterns in [usedKeys] from [regexes] or their fallbacks.
+ */
+ private fun reloadPatterns() {
+ val remotePatterns =
+ 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)
+ }
+ it.compiledPattern = Pattern.compile(it.defaultPattern)
+ it.wasLoadedRemotely = false
+ it.wasOverridden = false
+ }
+ }
+
+ val keyShape = Pattern.compile("^(?:[a-z0-9A-Z]+\\.)*[a-z0-9A-Z]+$")
+
+ /**
+ * Verify that a key has a valid shape or throw otherwise.
+ */
+ fun verifyKeyShape(key: String) {
+ require(keyShape.matches(key))
+ }
+
+ /**
+ * Dump all regexes labeled with the label into the file.
+ */
+ fun dump(sourceLabel: String, file: File) {
+ val data =
+ ConfigManager.gson.toJson(
+ RepoPatternDump(
+ sourceLabel,
+ usedKeys.values.associate { it.key to it.defaultPattern })
+ )
+ file.parentFile.mkdirs()
+ file.writeText(data)
+ }
+
+ @SubscribeEvent
+ fun onPreInitFinished(event: PreInitFinished) {
+ wasPreinitialized = true
+ val dumpDirective = System.getenv("SKYHANNI_DUMP_REGEXES")
+ if (dumpDirective.isNullOrBlank()) return
+ val (sourceLabel, path) = dumpDirective.split(":", limit = 2)
+ dump(sourceLabel, File(path))
+ if (System.getenv("SKYHANNI_DUMP_REGEXES_EXIT") != null) {
+ SkyHanniMod.logger.info("Exiting after dumping RepoPattern regex patterns to $path")
+ FMLCommonHandler.instance().exitJava(0, false)
+ }
+ }
+
+ fun of(key: String, fallback: String): 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 }
+ }
+}