All Downloads are FREE. Search and download functionalities are using the official Maven repository.

utility.PropertyInjectingJSONReader.kt Maven / Gradle / Ivy

package com.github.fluidsonic.baku

import com.github.fluidsonic.fluid.json.*


internal class PropertyInjectingJSONReader(
	properties: Map,
	private val source: JSONReader
) : JSONReader {

	private var currentProperty: Map.Entry? = null
	private var currentPropertyKeyRead = false
	private var hasReadIsolatedValue = false
	private var isRightAfterCustomProperty = false
	private val propertyIterator = properties.entries.iterator()
	private val rootDepth = source.depth
	private var topLevelIsMap = false
	private var valueIsolationCount = 0


	override fun beginValueIsolation(): JSONDepth {
		val nextToken = nextToken // loads currentProperty

		return if (currentProperty != null) {
			valueIsolationCheck(nextToken != null) { "the root value has already been read" }
			valueIsolationCheck(!isInValueIsolation || !hasReadIsolatedValue) { "cannot begin before previous one has ended" }

			valueIsolationCount += 1

			depth
		}
		else {
			source.beginValueIsolation()
		}
	}


	override fun close() {
		source.close()
	}


	override val depth
		get() = source.depth


	override fun endValueIsolation(depth: JSONDepth) {
		if (currentProperty != null || isRightAfterCustomProperty) {
			valueIsolationCheck(depth <= this.depth) { "lists or maps have been ended prematurely" }
			valueIsolationCheck(this.depth <= depth) { "lists or maps have not been ended properly" }
			valueIsolationCheck(valueIsolationCount > 0) { "cannot end isolation - it either hasn't begun or been ended already" }
			valueIsolationCheck(hasReadIsolatedValue) { "exactly one value has been expected but none was read" }

			val valueIsolationCount = valueIsolationCount - 1
			this.valueIsolationCount = valueIsolationCount

			if (valueIsolationCount == 0) {
				hasReadIsolatedValue = false
			}
		}
		else {
			source.endValueIsolation(depth = depth)
		}
	}


	private fun expectTokenForCustomProperty(expected: JSONToken) {
		currentProperty ?: return

		val token = nextToken
		if (token != expected) {
			throw JSONException.Schema(
				message = "Unexpected $token, expected $expected",
				offset = offset,
				path = path
			)
		}
	}


	override val isInValueIsolation
		get() = valueIsolationCount > 0 || source.isInValueIsolation


	override val nextToken: JSONToken?
		get() {
			val nextToken = source.nextToken
			if (nextToken != JSONToken.mapEnd) return nextToken
			if (depth.value != (rootDepth.value + 1) || !topLevelIsMap) return nextToken

			if (currentProperty == null) {
				if (!propertyIterator.hasNext()) return nextToken
				currentProperty = propertyIterator.next()
				currentPropertyKeyRead = false
			}

			return if (currentPropertyKeyRead)
				JSONToken.stringValue
			else
				JSONToken.mapKey
		}


	override val offset: Int
		get() {
			if (currentProperty == null) {
				return source.offset
			}

			return -1
		}


	override val path: JSONPath
		get() {
			val path = source.path
			val currentProperty = currentProperty ?: return path

			return JSONPath(path.elements + JSONPath.Element.MapKey(currentProperty.key))
		}


	override fun readBoolean(): Boolean {
		expectTokenForCustomProperty(JSONToken.booleanValue)

		return source.readBoolean()
	}


	override fun readDouble(): Double {
		expectTokenForCustomProperty(JSONToken.numberValue)

		return source.readDouble()
	}


	override fun readListEnd() {
		expectTokenForCustomProperty(JSONToken.listEnd)

		source.readListEnd()
	}


	override fun readListStart() {
		expectTokenForCustomProperty(JSONToken.listStart)

		source.readListStart()
	}


	override fun readLong(): Long {
		expectTokenForCustomProperty(JSONToken.numberValue)

		return source.readLong()
	}


	override fun readMapEnd() {
		expectTokenForCustomProperty(JSONToken.mapEnd)

		source.readMapEnd()

		isRightAfterCustomProperty = false

		if (depth == rootDepth) {
			topLevelIsMap = false
		}
	}


	override fun readMapStart() {
		expectTokenForCustomProperty(JSONToken.mapStart)

		if (depth == rootDepth) {
			topLevelIsMap = true
		}

		source.readMapStart()
	}


	override fun readNull(): Nothing? {
		expectTokenForCustomProperty(JSONToken.nullValue)

		return source.readNull()
	}


	override fun readNumber(): Number {
		expectTokenForCustomProperty(JSONToken.numberValue)

		return source.readNumber()
	}


	override fun readString(): String {
		val currentProperty = currentProperty ?: return source.readString()

		valueIsolationCheck(!isInValueIsolation || !hasReadIsolatedValue) { "cannot read more than one value in a context where only one is expected" }

		return if (currentPropertyKeyRead) {
			val value = currentProperty.value
			this.currentProperty = null
			this.currentPropertyKeyRead = false

			isRightAfterCustomProperty = true

			if (valueIsolationCount > 0) {
				hasReadIsolatedValue = true
			}

			value
		}
		else {
			this.currentPropertyKeyRead = true

			isRightAfterCustomProperty = false

			if (valueIsolationCount > 0) {
				hasReadIsolatedValue = true
			}

			currentProperty.key
		}
	}


	override fun terminate() {
		source.terminate()
	}


	private inline fun valueIsolationCheck(value: Boolean, lazyMessage: () -> String) {
		// contract {
		//  returns() implies value
		// }

		if (!value) valueIsolationError(lazyMessage())
	}


	private fun valueIsolationError(message: String): Nothing {
		throw JSONException.Parsing(
			message = "Value isolation failed: $message",
			offset = offset,
			path = path
		)
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy