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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
|
package at.hannibal2.skyhanni.utils.repopatterns
import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.config.ConfigManager
import at.hannibal2.skyhanni.config.features.dev.RepoPatternConfig
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.utils.ConditionalUtils.afterChange
import at.hannibal2.skyhanni.utils.LorenzUtils
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 = try {
Launch.blackboard["fml.deobfuscatedEnvironment"] as Boolean
} catch (_: Exception) {
true
}
private val insideTest = Launch.blackboard == null
var inTestDuplicateUsage = true
private val config
get() = if (!insideTest) {
SkyHanniMod.feature.dev.repoPattern
} else {
RepoPatternConfig().apply {
tolerateDuplicateUsage = inTestDuplicateUsage
}
}
val localLoading: Boolean get() = config.forceLocal.get() || (!insideTest && LorenzUtils.isInDevEnvironment())
/**
* 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) {
loadPatternsFromDump(event.getConstant<RepoPatternDump>("regexes"))
}
fun loadPatternsFromDump(dump: RepoPatternDump) {
regexes = null
regexes = dump
reloadPatterns()
}
@SubscribeEvent
fun onConfigLoad(event: ConfigLoadEvent) {
config.forceLocal.afterChange { reloadPatterns() }
}
/**
* Reload patterns in [usedKeys] from [regexes] or their fallbacks.
*/
private fun reloadPatterns() {
val remotePatterns =
if (localLoading) 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-9]+\\.)*[a-z0-9]+$")
/**
* Verify that a key has a valid shape or throw otherwise.
*/
fun verifyKeyShape(key: String) {
require(keyShape.matches(key)) { "pattern key: \"$key\" failed shape requirements" }
}
/**
* 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: PreInitFinishedEvent) {
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 }
}
}
|