diff options
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.kt | 153 |
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 } + } +} |
