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

utility.TextInput.kt Maven / Gradle / Ivy

The newest version!
package com.github.fluidsonic.fluid.json

import java.io.*
import kotlin.contracts.*


internal class TextInput(private val source: Reader) : Closeable by source {

	private var bufferEndIndex = 0
	private var bufferIsLocked = false
	private var bufferStartIndex = 0
	private var readCharacterCountNotInBufferAnymore = 0
	private var sourceIsAtEnd = false


	private fun alignBufferStartIfNeeded(): Boolean {
		// note that we keep a character before the read position in order to allow for seeking back one character

		if (bufferIsLocked) {
			return false
		}

		val bufferStartIndex = bufferStartIndex
		if (bufferStartIndex <= 1) {
			return true
		}

		val unreadCharacterCount = bufferEndIndex - bufferStartIndex
		if (unreadCharacterCount <= 0) {
			this.buffer[0] = this.buffer[bufferStartIndex - 1]
			this.bufferEndIndex = 1
			this.bufferStartIndex = 1
			this.readCharacterCountNotInBufferAnymore += bufferStartIndex - 1

			return true
		}

		if (bufferStartIndex >= windowSize + 1) {
			System.arraycopy(buffer, bufferStartIndex - 1, buffer, 0, unreadCharacterCount + 1)

			this.bufferEndIndex = 1 + unreadCharacterCount
			this.bufferStartIndex = 1
			this.readCharacterCountNotInBufferAnymore += bufferStartIndex + 1

			return true
		}

		return false
	}


	var buffer = CharArray((2 * windowSize) + 1)
		private set


	val index
		get() = bufferStartIndex


	fun lockBuffer() {
		check(!bufferIsLocked) { "Input is already locked." }
		bufferIsLocked = true
	}


	fun peekCharacter() =
		if (tryPreloadCharacters(1) > 0)
			buffer[bufferStartIndex].toInt()
		else
			JSONCharacter.end


	fun readCharacter(): Int {
		val character = if (tryPreloadCharacters(1) > 0)
			buffer[bufferStartIndex].toInt()
		else
			JSONCharacter.end

		bufferStartIndex += 1

		return character
	}


	fun seekBackOneCharacter() {
		check(bufferStartIndex > 0)

		bufferStartIndex -= 1
	}


	fun seekTo(index: Int) {
		check(bufferIsLocked) { "This operation is only possible with a locked buffer." }
		check(index in 0 .. (bufferEndIndex - 1)) { "index $index out of bounds 0 ..< $bufferEndIndex" }

		bufferStartIndex = index
	}


	fun skipWhitespaceCharacters() {
		var bufferStartIndex = bufferStartIndex
		var bufferEndIndex = bufferEndIndex

		do {
			if (bufferStartIndex >= bufferEndIndex) {
				this.bufferStartIndex = bufferStartIndex

				if (tryPreloadCharacters() == 0) {
					this.bufferStartIndex = bufferEndIndex
					return
				}

				bufferStartIndex = this.bufferStartIndex
				bufferEndIndex = this.bufferEndIndex
			}

			val character = buffer[bufferStartIndex].toInt()
			bufferStartIndex += 1
		}
		while (JSONCharacter.isWhitespace(character))

		this.bufferStartIndex = bufferStartIndex - 1
	}


	val sourceIndex
		get() = readCharacterCountNotInBufferAnymore + bufferStartIndex


	private fun tryPreloadCharacters(preloadCount: Int = windowSize): Int {
		require(preloadCount in 0 .. windowSize)

		var bufferEndIndex = bufferEndIndex

		val unreadCharacterCount = bufferEndIndex - bufferStartIndex
		if (unreadCharacterCount >= preloadCount) {
			return preloadCount
		}

		if (sourceIsAtEnd) {
			return 0
		}

		var buffer = buffer
		var bufferSize = buffer.size

		if (bufferSize - bufferEndIndex < windowSize) {
			if (alignBufferStartIfNeeded()) {
				bufferEndIndex = this.bufferEndIndex
			}
			else {
				bufferSize += windowSize
				buffer = buffer.copyOf(bufferSize)

				this.buffer = buffer
			}
		}

		val preloadedCount = source.read(buffer, bufferEndIndex, windowSize)
		if (preloadedCount <= 0) {
			check(preloadedCount != 0) { "Reader.read(…) must not return 0." }

			sourceIsAtEnd = true
			return 0
		}

		bufferEndIndex += preloadedCount
		this.bufferEndIndex = bufferEndIndex

		return preloadedCount
	}


	fun unlockBuffer() {
		check(bufferIsLocked) { "Input is not locked." }
		bufferIsLocked = false

		alignBufferStartIfNeeded()
	}


	private companion object {

		const val windowSize = 2048
	}
}


internal fun  TextInput.locked(block: () -> ReturnValue): ReturnValue {
	contract {
		callsInPlace(block, InvocationKind.EXACTLY_ONCE)
	}

	lockBuffer()

	try {
		return block()
	}
	finally {
		unlockBuffer()
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy