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_` * * `` */ class SBPattern private constructor( val raw: String, val params: ImmutableMap, val pieces: ImmutableList, val names: ImmutableSet ) { 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(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? { return resolve(values::get) } fun with(params: Map): SBPattern { return with(params::get) } fun with(params: (String) -> String?): SBPattern { if (names.isEmpty()) return this val map = Object2ObjectArrayMap() 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() @JvmField val EMPTY = raw("") @JvmField val FRAME = of("") override fun create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType == SBPattern::class.java) { return object : TypeAdapter() { 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 } return null } @JvmStatic fun of(raw: String): SBPattern { if (raw == "") return EMPTY val pieces = ImmutableList.Builder() 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()) } } }