|
| 1 | +package net.azisaba.vanilife |
| 2 | + |
| 3 | +import com.charleskorn.kaml.PolymorphismStyle |
| 4 | +import com.charleskorn.kaml.Yaml |
| 5 | +import com.charleskorn.kaml.YamlConfiguration |
| 6 | +import kotlinx.serialization.KSerializer |
| 7 | +import net.kyori.adventure.key.Key |
| 8 | +import org.bukkit.plugin.Plugin |
| 9 | +import java.nio.file.Path |
| 10 | +import java.util.concurrent.atomic.AtomicReference |
| 11 | +import kotlin.io.path.* |
| 12 | + |
| 13 | +abstract class DynamicContents<T>( |
| 14 | + val name: String, private val serializer: KSerializer<T>, private val yaml: Yaml = DEFAULT_YAML, |
| 15 | +) { |
| 16 | + private val mapReference: AtomicReference<Map<Key, T>?> = AtomicReference(null) |
| 17 | + |
| 18 | + fun byKey(key: Key): Holder<T>? = requireLoaded()[key]?.let { Holder(key, it) } |
| 19 | + |
| 20 | + fun all(): Collection<T> = requireLoaded().values |
| 21 | + |
| 22 | + fun bootstrap(plugin: Plugin) { |
| 23 | + val contentsRoot = plugin.dataFolder.toPath().resolve(name) |
| 24 | + contentsRoot.createDirectories() |
| 25 | + |
| 26 | + require(contentsRoot.isDirectory()) { |
| 27 | + "Path is not a directory: $contentsRoot" |
| 28 | + } |
| 29 | + |
| 30 | + val newMap = contentsRoot.listDirectoryEntries() |
| 31 | + .filter(Path::isDirectory) |
| 32 | + .flatMap { namespaceDir -> |
| 33 | + namespaceDir.listDirectoryEntries("*.yml") |
| 34 | + .sortedBy(Path::name) |
| 35 | + .map { file -> file.key() to file.deserialized() } |
| 36 | + } |
| 37 | + .toMap() |
| 38 | + |
| 39 | + mapReference.set(newMap) |
| 40 | + } |
| 41 | + |
| 42 | + private fun requireLoaded(): Map<Key, T> = |
| 43 | + mapReference.get() ?: throw IllegalStateException("You are trying to access contents '$name' too early") |
| 44 | + |
| 45 | + private fun Path.key(): Key { |
| 46 | + val namespace = parent?.name ?: throw IllegalArgumentException("Cannot derive namespace from path: $this") |
| 47 | + val value = nameWithoutExtension |
| 48 | + return try { |
| 49 | + Key.key(namespace, value) |
| 50 | + } catch (e: Exception) { |
| 51 | + throw IllegalArgumentException("Invalid content key: $namespace:$value ($this)", e) |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + private fun Path.deserialized(): T { |
| 56 | + val text = try { |
| 57 | + readText() |
| 58 | + } catch (e: Exception) { |
| 59 | + throw IllegalStateException("Failed to read file: $this", e) |
| 60 | + } |
| 61 | + |
| 62 | + return try { |
| 63 | + yaml.decodeFromString(serializer, text) |
| 64 | + } catch (e: Exception) { |
| 65 | + throw IllegalArgumentException("Failed to parse YAML file: $this\n$text", e) |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + private companion object { |
| 70 | + val DEFAULT_YAML: Yaml = Yaml( |
| 71 | + configuration = YamlConfiguration( |
| 72 | + polymorphismStyle = PolymorphismStyle.Property, |
| 73 | + polymorphismPropertyName = "kind", |
| 74 | + ) |
| 75 | + ) |
| 76 | + } |
| 77 | + |
| 78 | + data class Holder<T>(val key: Key, val value: T) |
| 79 | +} |
0 commit comments