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

186 lines
4.7 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.util
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet
import com.google.common.collect.Interners
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 isPlainString get() = names.isEmpty()
val value by lazy { resolve { null } }
override fun toString(): String {
return "SBPattern[$raw, $params]"
}
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? {
if (names.isEmpty()) {
return raw
} else if (pieces.size == 1) {
return pieces[0].resolve(values, params::get)
}
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 {
return with(params::get)
}
fun with(params: (String) -> String?): SBPattern {
if (names.isEmpty())
return this
val map = Object2ObjectArrayMap<String, String>()
map.putAll(this.params)
var any = false
for (name in names) {
val get = params.invoke(name)
if (get != null && get != map[name]) {
map[name] = get
any = true
}
}
if (!any)
return this
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!!)
}
}
companion object : TypeAdapterFactory {
private val interner = Interners.newWeakInterner<SBPattern>()
@JvmField
val EMPTY = raw("")
@JvmField
val FRAME = of("<frame>")
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 {
if (raw == "")
return EMPTY
val pieces = ImmutableList.Builder<Piece>()
var i = 0
while (i < raw.length) {
val open = raw.indexOf('<', startIndex = i)
if (open == -1) {
if (i == 0)
pieces.add(Piece(contents = raw))
else
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)))
i = closing + 1
}
}
val built = pieces.build()
return interner.intern(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 {
if (raw == "")
return EMPTY
return SBPattern(raw, ImmutableMap.of(), ImmutableList.of(), ImmutableSet.of())
}
}
}