com.github.erosb.jsonsKema.JsonValue.kt Maven / Gradle / Ivy
Show all versions of json-sKema Show documentation
package com.github.erosb.jsonsKema
import java.math.BigDecimal
import java.net.URI
import java.util.stream.Collectors.joining
data class JsonParseException(override val message: String, val location: TextLocation) : RuntimeException()
data class JsonTypingException(
val expectedType: String,
val actualType: String,
val location: SourceLocation
) : RuntimeException("${location.pointer}: expected $expectedType, found $actualType (line ${location.lineNumber}, position ${location.position})") {
override fun toString() = message ?: ""
}
data class JsonPointer(val segments: List) {
constructor(vararg segments: String): this(listOf(*segments))
override fun toString() = "#" + (if (segments.isEmpty()) "" else "/") + segments.stream().collect(joining("/"))
operator fun plus(additionalSegment: String): JsonPointer = JsonPointer(segments + additionalSegment)
operator fun plus(additionalSegment: Keyword): JsonPointer = plus(additionalSegment.value)
}
fun pointer(vararg segments: String) = JsonPointer(segments.toList())
open class TextLocation(val lineNumber: Int, val position: Int, val documentSource: URI) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TextLocation
if (lineNumber != other.lineNumber) return false
if (position != other.position) return false
return true
}
override fun hashCode(): Int {
var result = lineNumber
result = 31 * result + position
return result
}
override fun toString(): String {
return "line $lineNumber, character $position"
}
}
open class SourceLocation(
lineNumber: Int,
position: Int,
val pointer: JsonPointer,
documentSource: URI = DEFAULT_BASE_URI
) : TextLocation(lineNumber, position, documentSource) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
if (!super.equals(other)) return false
other as SourceLocation
if (lineNumber != other.lineNumber) return false
if (position != other.position) return false
if (pointer != other.pointer) return false
if (documentSource != other.documentSource) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + pointer.hashCode()
result = 31 * result + (documentSource?.hashCode() ?: 0)
result = 31 * result + lineNumber
result = 31 * result + position
return result
}
fun getLocation(): String {
val sb = StringBuilder()
if (documentSource !== null) {
sb.append(documentSource).append(": ")
}
sb.append("Line $lineNumber, character $position")
return sb.toString()
}
override fun toString(): String {
return "line $lineNumber, character $position, pointer: $pointer"
}
internal fun trimPointerSegments(leadingSegmentsToBeRemoved: Int): SourceLocation {
if (leadingSegmentsToBeRemoved > pointer.segments.size) {
throw IllegalArgumentException("can not remove $leadingSegmentsToBeRemoved segment from pointer $pointer")
}
return SourceLocation(
lineNumber,
position,
JsonPointer(pointer.segments.subList(leadingSegmentsToBeRemoved, pointer.segments.size)),
documentSource
)
}
fun withPointer(pointer: JsonPointer): SourceLocation = SourceLocation(lineNumber, position, pointer, documentSource)
}
object UnknownSource : SourceLocation(0, 0, JsonPointer(emptyList()), URI("UNKNOWN")) {
override fun toString(): String = "UNKNOWN"
}
interface IJsonValue {
val location: SourceLocation
private fun unexpectedType(expected: String): JsonTypingException =
JsonTypingException(expected, jsonTypeAsString(), location)
fun requireBoolean(): IJsonBoolean = throw unexpectedType("boolean")
fun requireString(): IJsonString = throw unexpectedType("string")
fun requireNumber(): IJsonNumber = throw unexpectedType("number")
fun requireInt(): Int = requireNumber().value.toInt()
fun requireNull(): IJsonNull = throw unexpectedType("null")
fun requireObject(): IJsonObject<*, *> = throw unexpectedType("object")
fun requireArray(): IJsonArray<*> = throw unexpectedType("array")
fun maybeString(fn: (IJsonString) -> P?): P? = null
fun
maybeNumber(fn: (IJsonNumber) -> P?): P? = null
fun
maybeArray(fn: (IJsonArray<*>) -> P?): P? = null
fun
maybeObject(fn: (IJsonObject<*, *>) -> P?): P? = null
fun
accept(visitor: JsonVisitor
): P?
fun jsonTypeAsString(): String
}
interface IJsonString : IJsonValue {
val value: String
override fun jsonTypeAsString() = "string"
override fun requireString(): IJsonString = this
override fun
maybeString(fn: (IJsonString) -> P?): P? = fn(this)
override fun
accept(visitor: JsonVisitor
): P? = visitor.visitString(this)
}
interface IJsonBoolean : IJsonValue {
val value: Boolean
override fun jsonTypeAsString() = "boolean"
override fun requireBoolean(): IJsonBoolean = this
override fun
accept(visitor: JsonVisitor
): P? = visitor.visitBoolean(this)
}
interface IJsonNumber : IJsonValue {
val value: Number
override fun jsonTypeAsString() = "number"
override fun requireNumber(): IJsonNumber = this
override fun
maybeNumber(fn: (IJsonNumber) -> P?): P? = fn(this)
override fun
accept(visitor: JsonVisitor
): P? = visitor.visitNumber(this)
}
interface IJsonNull : IJsonValue {
override fun jsonTypeAsString() = "null"
override fun requireNull(): IJsonNull = this
override fun
accept(visitor: JsonVisitor
): P? = visitor.visitNull(this)
}
interface IJsonArray : IJsonValue {
val elements: List
override fun jsonTypeAsString() = "array"
override fun requireArray(): IJsonArray = this
override fun maybeArray(fn: (IJsonArray<*>) -> P?): P? = fn(this)
override fun
accept(visitor: JsonVisitor
): P? = visitor.visitArray(this)
operator fun get(index: Int) = elements[index]
fun length() = elements.size
fun markUnevaluated(idx: Int) {}
fun markEvaluated(idx: Int): IJsonValue
fun markAllEvaluated()
}
interface IJsonObject
: IJsonValue {
val properties: Map
override fun jsonTypeAsString() = "object"
override fun requireObject(): IJsonObject
= this
override fun
maybeObject(fn: (IJsonObject<*, *>) -> P?): P? = fn(this)
override fun
accept(visitor: JsonVisitor
): P? = visitor.visitObject(this)
operator fun get(key: String) = properties[JsonString(key) as P]
fun markUnevaluated(propName: String)
fun markEvaluated(propName: String)
}
typealias IJsonObj = IJsonObject<*, *>
interface JsonVisitor
{
fun identity(): P? = null
fun accumulate(previous: P?, current: P?): P? = current ?: previous
fun visitString(str: IJsonString): P?
fun visitBoolean(bool: IJsonBoolean): P?
fun visitNumber(num: IJsonNumber): P?
fun visitNull(nil: IJsonNull): P?
fun visitArray(arr: IJsonArray<*>): P?
fun visitObject(obj: IJsonObject<*, *>): P?
}
abstract class JsonValue(override val location: SourceLocation = UnknownSource) : IJsonValue {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other === null) return false
if (!other.javaClass.isAssignableFrom(javaClass)) return false
return unwrap() == (other as JsonValue).unwrap()
}
final override fun hashCode(): Int {
return unwrap()?.hashCode() ?: 0
}
internal abstract fun unwrap(): Any?
final override fun toString(): String = accept(JsonPrintingVisitor())!!
}
data class JsonNull @JvmOverloads constructor(override val location: SourceLocation = UnknownSource) : IJsonNull, JsonValue(location) {
override fun unwrap(): Any? = null
override fun equals(other: Any?) = super.equals(other)
}
data class JsonBoolean @JvmOverloads constructor(
override val value: Boolean,
override val location: SourceLocation = UnknownSource
) : JsonValue(location), IJsonBoolean {
override fun unwrap(): Any = value
override fun equals(other: Any?) = super.equals(other)
}
data class JsonNumber @JvmOverloads constructor(
override val value: Number,
override val location: SourceLocation = UnknownSource
) : JsonValue(location), IJsonNumber {
override fun unwrap() = value
override fun equals(other: Any?): Boolean {
if (other === this) return true
if (other !is IJsonNumber) return false
return BigDecimal(value.toString()).compareTo(BigDecimal(other.value.toString())) == 0
}
}
data class JsonString @JvmOverloads constructor(
override val value: String,
override val location: SourceLocation = UnknownSource
) : JsonValue(location), IJsonString {
override fun equals(other: Any?) = super.equals(other)
override fun unwrap() = value
}
data class JsonArray @JvmOverloads constructor(
override val elements: List,
override val location: SourceLocation = UnknownSource
) : JsonValue(location), IJsonArray {
override fun unwrap() = elements
override fun markEvaluated(idx: Int): IJsonValue = get(idx)
override fun markAllEvaluated() {
}
override fun equals(other: Any?) = super.equals(other)
}
data class JsonObject @JvmOverloads constructor(
override val properties: Map,
override val location: SourceLocation = UnknownSource
) : JsonValue(location), IJsonObject {
override fun unwrap() = properties
override fun markUnevaluated(propName: String) {}
override fun markEvaluated(propName: String) {}
override fun equals(other: Any?) = super.equals(other)
}