1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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 }
}
}
|