KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/defs/SBPattern.kt

151 lines
4.1 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ru.dbotthepony.kstarbound.defs
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet
import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
/**
* Шаблонизировання строка в стиле Starbound'а
*
* Представляет из себя строки вида:
* * `my.thing`
* * `frame_<FrameNumber>`
* * `<frame><effectDirectives>`
*/
class SBPattern private constructor(
val raw: String,
val params: ImmutableMap<String, String>,
val pieces: ImmutableList<Piece>,
val names: ImmutableSet<String>
) {
val value by lazy { resolve { null } }
val hasNames get() = names.isNotEmpty()
override fun toString(): String {
return "SBPattern[$raw]"
}
override fun equals(other: Any?): Boolean {
return other is SBPattern && other.raw == raw && other.names == names && other.pieces == pieces && other.params == params
}
@Volatile
private var calculatedHash = false
@Volatile
private var hash = 0
override fun hashCode(): Int {
if (!calculatedHash) {
hash = raw.hashCode().xor(params.hashCode()).rotateLeft(12).and(pieces.hashCode()).rotateRight(8).xor(names.hashCode())
calculatedHash = true
}
return hash
}
fun resolve(values: (String) -> String?): String? {
val buffer = ArrayList<String>(pieces.size)
for (piece in pieces) {
buffer.add(piece.resolve(values, params::get) ?: return null)
}
var count = 0
for (piece in buffer) count += piece.length
val builder = StringBuilder(count)
for (piece in buffer) builder.append(piece)
return String(builder)
}
fun resolve(values: Map<String, String>): String? {
return resolve(values::get)
}
fun with(params: Map<String, String>): SBPattern {
val map = Object2ObjectArrayMap<String, String>()
map.putAll(this.params)
for ((key, value) in params.entries)
if (names.contains(key))
map[key] = value
return SBPattern(raw, ImmutableMap.copyOf(map), pieces, names)
}
data class Piece(val name: String? = null, val contents: String? = null) {
init {
check(name != null || contents != null) { "Both name and contents are null" }
check(!(name != null && contents != null)) { "Both name and contents are not null" }
}
fun resolve(map0: (String) -> String?, map1: (String) -> String?): String? {
return contents ?: map0.invoke(name!!) ?: map1.invoke(name!!)
}
fun resolve(map0: (String) -> String?): String? {
return contents ?: map0.invoke(name!!)
}
fun resolve(): String? {
return contents
}
}
companion object : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == SBPattern::class.java) {
return object : TypeAdapter<SBPattern>() {
private val strings = gson.getAdapter(String::class.java)
override fun write(out: JsonWriter, value: SBPattern?) {
strings.write(out, value?.raw)
}
override fun read(`in`: JsonReader): SBPattern? {
return of(strings.read(`in`) ?: return null)
}
} as TypeAdapter<T>
}
return null
}
@JvmStatic
fun of(raw: String): SBPattern {
val pieces = ImmutableList.Builder<Piece>()
var i = 0
while (i < raw.length) {
val open = raw.indexOf('<', startIndex = i)
if (open == -1) {
pieces.add(Piece(contents = raw.substring(i)))
break
} else {
val closing = raw.indexOf('>', startIndex = open + 1)
if (closing == -1) {
throw IllegalArgumentException("Malformed pattern string: $raw")
}
pieces.add(Piece(name = raw.substring(open + 1, closing - 1)))
i = closing + 1
}
}
val built = pieces.build()
return SBPattern(raw, pieces = built, params = ImmutableMap.of(), names = built.stream().map { it.name }.filter { it != null }.collect(ImmutableSet.toImmutableSet()))
}
@JvmStatic
fun raw(raw: String): SBPattern = SBPattern(raw, ImmutableMap.of(), ImmutableList.of(), ImmutableSet.of())
}
}