151 lines
5.1 KiB
Kotlin
151 lines
5.1 KiB
Kotlin
package ru.dbotthepony.kstarbound.json
|
|
|
|
import com.google.gson.JsonArray
|
|
import com.google.gson.JsonElement
|
|
import com.google.gson.JsonNull
|
|
import com.google.gson.JsonObject
|
|
import com.google.gson.JsonSyntaxException
|
|
import org.apache.logging.log4j.LogManager
|
|
import ru.dbotthepony.kommons.gson.get
|
|
import ru.dbotthepony.kstarbound.IStarboundFile
|
|
import ru.dbotthepony.kstarbound.Starbound
|
|
|
|
enum class JsonPatch(val key: String) {
|
|
TEST("test") {
|
|
override fun apply(base: JsonElement, data: JsonObject): JsonElement {
|
|
val path = JsonPath.pointer(data.get("path").asString)
|
|
val value = data["value"] ?: JsonNull.INSTANCE
|
|
val inverse = data.get("inverse", false)
|
|
|
|
val get = path.find(base) ?: JsonNull.INSTANCE
|
|
|
|
if (inverse) {
|
|
if (value == JsonNull.INSTANCE && get != JsonNull.INSTANCE) {
|
|
throw JsonTestException("Expected $path to not contain anything")
|
|
} else if (value != JsonNull.INSTANCE && value == get) {
|
|
throw JsonTestException("Expected $path to not contain $value")
|
|
}
|
|
} else {
|
|
if (value == JsonNull.INSTANCE && get == JsonNull.INSTANCE) {
|
|
throw JsonTestException("Expected $path to contain anything")
|
|
} else if (value != JsonNull.INSTANCE && get == JsonNull.INSTANCE) {
|
|
throw JsonTestException("Expected $path to contain '$value', but found nothing")
|
|
} else if (value != JsonNull.INSTANCE && get != JsonNull.INSTANCE && value != get) {
|
|
var text = get.toString()
|
|
|
|
if (text.length > 40) {
|
|
text = text.substring(0, 40) + "..."
|
|
}
|
|
|
|
throw JsonTestException("Expected $path to contain $value, but found $text")
|
|
}
|
|
}
|
|
|
|
return base
|
|
}
|
|
},
|
|
REMOVE("remove") {
|
|
override fun apply(base: JsonElement, data: JsonObject): JsonElement {
|
|
return JsonPath.pointer(data.get("path").asString).remove(base).first
|
|
}
|
|
},
|
|
ADD("add") {
|
|
override fun apply(base: JsonElement, data: JsonObject): JsonElement {
|
|
val value = data["value"] ?: throw JsonSyntaxException("Missing 'value' to 'add' operation")
|
|
return JsonPath.pointer(data.get("path").asString).add(base, value)
|
|
}
|
|
},
|
|
REPLACE("replace") {
|
|
override fun apply(base: JsonElement, data: JsonObject): JsonElement {
|
|
val value = data["value"] ?: throw JsonSyntaxException("Missing 'value' to 'add' operation")
|
|
val path = JsonPath.pointer(data.get("path").asString)
|
|
return path.add(path.remove(base).first, value)
|
|
}
|
|
},
|
|
MOVE("move") {
|
|
override fun apply(base: JsonElement, data: JsonObject): JsonElement {
|
|
val from = JsonPath.pointer(data.get("from").asString)
|
|
val path = JsonPath.pointer(data.get("path").asString)
|
|
val (newBase, removed) = from.remove(base)
|
|
return path.add(newBase, removed)
|
|
}
|
|
},
|
|
COPY("copy") {
|
|
override fun apply(base: JsonElement, data: JsonObject): JsonElement {
|
|
val from = JsonPath.pointer(data.get("from").asString)
|
|
val path = JsonPath.pointer(data.get("path").asString)
|
|
return path.add(base, from.get(base))
|
|
}
|
|
};
|
|
|
|
abstract fun apply(base: JsonElement, data: JsonObject): JsonElement
|
|
|
|
class JsonTestException(message: String) : IllegalStateException(message)
|
|
|
|
companion object {
|
|
private val LOGGER = LogManager.getLogger()
|
|
|
|
fun apply(base: JsonElement, data: JsonObject): JsonElement {
|
|
val op = data["op"]?.asString ?: throw JsonSyntaxException("Missing 'op' in json patch operation")
|
|
val operation = entries.firstOrNull { it.key == op } ?: throw JsonSyntaxException("Unknown patch operation $op!")
|
|
return operation.apply(base, data)
|
|
}
|
|
|
|
@Suppress("NAME_SHADOWING")
|
|
fun apply(base: JsonElement, data: JsonArray, source: IStarboundFile?): JsonElement {
|
|
// original engine decides what to do based on first element encountered
|
|
// in patch array... let's not.
|
|
var base = base
|
|
|
|
for ((i, element) in data.withIndex()) {
|
|
if (element is JsonObject) {
|
|
try {
|
|
base = apply(base, element)
|
|
} catch (err: JsonTestException) {
|
|
LOGGER.debug("Test condition failed in {} at index {}: {}", source, i, err.message)
|
|
} catch (err: Throwable) {
|
|
LOGGER.error("Error while applying JSON patch from $source at index $i: ${err.message}")
|
|
}
|
|
} else if (element is JsonArray) {
|
|
for ((i2, sub) in element.withIndex()) {
|
|
try {
|
|
if (sub !is JsonObject)
|
|
throw JsonSyntaxException("Expected JSON Object as patch data, got $sub")
|
|
|
|
base = apply(base, sub)
|
|
} catch (err: JsonTestException) {
|
|
LOGGER.debug("Test condition failed in {} at index {} -> {}: {}", source, i, i2, err.message)
|
|
break
|
|
} catch (err: Throwable) {
|
|
LOGGER.error("Error while applying JSON patch from $source at index $i -> $i2: ${err.message}")
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
LOGGER.error("Unknown data in $source at index $i")
|
|
}
|
|
}
|
|
|
|
return base
|
|
}
|
|
|
|
@Suppress("NAME_SHADOWING")
|
|
fun apply(base: JsonElement, source: Collection<IStarboundFile>?): JsonElement {
|
|
source ?: return base
|
|
var base = base
|
|
|
|
for (patch in source) {
|
|
val read = Starbound.ELEMENTS_ADAPTER.read(patch.jsonReader())
|
|
|
|
if (read !is JsonArray) {
|
|
LOGGER.error("$patch root element is not an array")
|
|
} else {
|
|
base = apply(base, read, patch)
|
|
}
|
|
}
|
|
|
|
return base
|
|
}
|
|
}
|
|
}
|