dorkbox.json.JsonValue.kt Maven / Gradle / Ivy
/*
* Copyright 2023 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*******************************************************************************
* Copyright 2011 Mario Zechner, Nathan Sweet
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.json
import java.io.IOException
import java.io.Writer
/**
* Container for a JSON object, array, string, double, long, boolean, or null.
*
* JsonValue children are a linked list. Iteration of arrays or objects is easily done using a for loop, either with the enhanced
* for loop syntactic sugar or like the example below. This is much more efficient than accessing children by index when there are
* many children.
*
*
* JsonValue map = ...;
* for (JsonValue entry = map.child; entry != null; entry = entry.next)
* System.out.println(entry.name + " = " + entry.asString());
*
* @author Nathan Sweet
*/
class JsonValue : Iterable {
private var type: ValueType? = null
/** May be null. */
private var stringValue: String? = null
private var doubleValue = 0.0
private var longValue: Long = 0
var name: String? = null
/** May be null. */
var child: JsonValue? = null
var parent: JsonValue? = null
/** May be null. When changing this field the parent [.size] may need to be changed. */
var next: JsonValue? = null
var prev: JsonValue? = null
var size = 0
constructor(type: ValueType?) {
this.type = type
}
/**
* @param value May be null.
*/
constructor(value: String?) {
set(value)
}
constructor(value: Double) {
set(value, null)
}
constructor(value: Long) {
set(value, null)
}
constructor(value: Double, stringValue: String?) {
set(value, stringValue)
}
constructor(value: Long, stringValue: String?) {
set(value, stringValue)
}
constructor(value: Boolean) {
set(value)
}
/**
* Returns the child at the specified index. This requires walking the linked list to the specified entry, see
* [JsonValue] for how to iterate efficiently.
*
* @return May be null.
*/
operator fun get(index: Int): JsonValue? {
var index = index
var current = child
while (current != null && index > 0) {
index--
current = current.next
}
return current
}
/**
* Returns the child with the specified name.
*
* @return May be null.
*/
operator fun get(name: String): JsonValue? {
var current = child
while (current != null && (current.name == null || !current.name.equals(name, ignoreCase = true))) current = current.next
return current
}
/**
* Returns true if a child with the specified name exists. */
fun has(name: String): Boolean {
return get(name) != null
}
/**
* Returns the child at the specified index. This requires walking the linked list to the specified entry, see
* [JsonValue] for how to iterate efficiently.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun require(index: Int): JsonValue {
var index = index
var current = child
while (current != null && index > 0) {
index--
current = current.next
}
requireNotNull(current) { "Child not found with index: $index" }
return current
}
/**
* Returns the child with the specified name.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun require(name: String): JsonValue {
var current = child
while (current != null && (current.name == null || !current.name.equals(name, ignoreCase = true))) current = current.next
requireNotNull(current) { "Child not found with name: $name" }
return current
}
/**
* Removes the child with the specified index. This requires walking the linked list to the specified entry, see
* [JsonValue] for how to iterate efficiently.
*
* @return May be null.
*/
fun remove(index: Int): JsonValue? {
val child = get(index) ?: return null
if (child.prev == null) {
this.child = child.next
if (this.child != null) this.child!!.prev = null
} else {
child.prev!!.next = child.next
if (child.next != null) child.next!!.prev = child.prev
}
size--
return child
}
/**
* Removes the child with the specified name.
*
* @return May be null.
*/
fun remove(name: String): JsonValue? {
val child = get(name) ?: return null
if (child.prev == null) {
this.child = child.next
if (this.child != null) this.child!!.prev = null
} else {
child.prev!!.next = child.next
if (child.next != null) child.next!!.prev = child.prev
}
size--
return child
}
/** Removes this value from its parent. */
fun remove() {
checkNotNull(parent)
if (prev == null) {
parent!!.child = next
if (parent!!.child != null) parent!!.child!!.prev = null
} else {
prev!!.next = next
if (next != null) next!!.prev = prev
}
parent!!.size--
}
/** Returns true if there are one or more children in the array or object. */
fun notEmpty(): Boolean {
return size > 0
}
/** Returns true if there are not children in the array or object. */
val isEmpty: Boolean
get() = size == 0
@Deprecated("Use {@link #size} instead. Returns this number of children in the array or object. ")
fun size(): Int {
return size
}
/**
* Returns this value as a string.
*
* @return May be null if this value is null.
* @throws IllegalStateException if this an array or object.
*/
fun asString(): String? {
return when (type) {
ValueType.stringValue -> stringValue
ValueType.doubleValue -> if (stringValue != null) stringValue else doubleValue.toString()
ValueType.longValue -> if (stringValue != null) stringValue else longValue.toString()
ValueType.booleanValue -> if (longValue != 0L) "true" else "false"
ValueType.nullValue -> null
else -> throw IllegalStateException("Value cannot be converted to string: $type")
}
}
/**
* Returns this value as a float.
*
* @throws IllegalStateException if this an array or object.
*/
fun asFloat(): Float {
return when (type) {
ValueType.stringValue -> stringValue!!.toFloat()
ValueType.doubleValue -> doubleValue.toFloat()
ValueType.longValue -> longValue.toFloat()
ValueType.booleanValue -> if (longValue != 0L) 1.toFloat() else 0.toFloat()
else -> throw IllegalStateException("Value cannot be converted to float: $type")
}
}
/**
* Returns this value as a double.
*
* @throws IllegalStateException if this an array or object.
*/
fun asDouble(): Double {
return when (type) {
ValueType.stringValue -> stringValue!!.toDouble()
ValueType.doubleValue -> doubleValue
ValueType.longValue -> longValue.toDouble()
ValueType.booleanValue -> if (longValue != 0L) 1.toDouble() else 0.toDouble()
else -> throw IllegalStateException("Value cannot be converted to double: $type")
}
}
/**
* Returns this value as a long.
*
* @throws IllegalStateException if this an array or object.
*/
fun asLong(): Long {
return when (type) {
ValueType.stringValue -> stringValue!!.toLong()
ValueType.doubleValue -> doubleValue.toLong()
ValueType.longValue -> longValue
ValueType.booleanValue -> if (longValue != 0L) 1L else 0L
else -> throw IllegalStateException("Value cannot be converted to long: $type")
}
}
/**
* Returns this value as an int.
*
* @throws IllegalStateException if this an array or object.
*/
fun asInt(): Int {
return when (type) {
ValueType.stringValue -> stringValue!!.toInt()
ValueType.doubleValue -> doubleValue.toInt()
ValueType.longValue -> longValue.toInt()
ValueType.booleanValue -> if (longValue != 0L) 1 else 0
else -> throw IllegalStateException("Value cannot be converted to int: $type")
}
}
/**
* Returns this value as a boolean.
*
* @throws IllegalStateException if this an array or object.
*/
fun asBoolean(): Boolean {
return when (type) {
ValueType.stringValue -> stringValue.equals("true", ignoreCase = true)
ValueType.doubleValue -> doubleValue != 0.0
ValueType.longValue -> longValue != 0L
ValueType.booleanValue -> longValue != 0L
else -> throw IllegalStateException("Value cannot be converted to boolean: $type")
}
}
/**
* Returns this value as a byte.
*
* @throws IllegalStateException if this an array or object.
*/
fun asByte(): Byte {
return when (type) {
ValueType.stringValue -> stringValue!!.toByte()
ValueType.doubleValue -> doubleValue.toInt().toByte()
ValueType.longValue -> longValue.toByte()
ValueType.booleanValue -> if (longValue != 0L) 1.toByte() else 0.toByte()
else -> throw IllegalStateException("Value cannot be converted to byte: $type")
}
}
/**
* Returns this value as a short.
*
* @throws IllegalStateException if this an array or object.
*/
fun asShort(): Short {
return when (type) {
ValueType.stringValue -> stringValue!!.toShort()
ValueType.doubleValue -> doubleValue.toInt().toShort()
ValueType.longValue -> longValue.toShort()
ValueType.booleanValue -> if (longValue != 0L) 1.toShort() else 0.toShort()
else -> throw IllegalStateException("Value cannot be converted to short: $type")
}
}
/**
* Returns this value as a char.
*
* @throws IllegalStateException if this an array or object.
*/
fun asChar(): Char {
return when (type) {
ValueType.stringValue -> if (stringValue!!.length == 0) 0.toChar() else stringValue!![0]
ValueType.doubleValue -> doubleValue.toInt().toChar()
ValueType.longValue -> Char(longValue.toUShort())
ValueType.booleanValue -> if (longValue != 0L) 1.toChar() else 0.toChar()
else -> throw IllegalStateException("Value cannot be converted to char: $type")
}
}
/**
* Returns the children of this value as a newly allocated String array.
*
* @throws IllegalStateException if this is not an array.
*/
fun asStringArray(): Array {
check(type == ValueType.array) { "Value is not an array: $type" }
val array = arrayOfNulls(size)
var i = 0
var value = child
while (value != null) {
var v: String?
v = when (value.type) {
ValueType.stringValue -> value.stringValue
ValueType.doubleValue -> if (stringValue != null) stringValue else value.doubleValue.toString()
ValueType.longValue -> if (stringValue != null) stringValue else value.longValue.toString()
ValueType.booleanValue -> if (value.longValue != 0L) "true" else "false"
ValueType.nullValue -> null
else -> throw IllegalStateException("Value cannot be converted to string: " + value.type)
}
array[i] = v
value = value.next
i++
}
return array
}
/**
* Returns the children of this value as a newly allocated float array.
*
* @throws IllegalStateException if this is not an array.
*/
fun asFloatArray(): FloatArray {
check(type == ValueType.array) { "Value is not an array: $type" }
val array = FloatArray(size)
var i = 0
var value = child
while (value != null) {
var v: Float
v = when (value.type) {
ValueType.stringValue -> value.stringValue!!.toFloat()
ValueType.doubleValue -> value.doubleValue.toFloat()
ValueType.longValue -> value.longValue.toFloat()
ValueType.booleanValue -> (if (value.longValue != 0L) 1 else 0).toFloat()
else -> throw IllegalStateException("Value cannot be converted to float: " + value.type)
}
array[i] = v
value = value.next
i++
}
return array
}
/**
* Returns the children of this value as a newly allocated double array.
*
* @throws IllegalStateException if this is not an array.
*/
fun asDoubleArray(): DoubleArray {
check(type == ValueType.array) { "Value is not an array: $type" }
val array = DoubleArray(size)
var i = 0
var value = child
while (value != null) {
var v: Double
v = when (value.type) {
ValueType.stringValue -> value.stringValue!!.toDouble()
ValueType.doubleValue -> value.doubleValue
ValueType.longValue -> value.longValue.toDouble()
ValueType.booleanValue -> (if (value.longValue != 0L) 1 else 0).toDouble()
else -> throw IllegalStateException("Value cannot be converted to double: " + value.type)
}
array[i] = v
value = value.next
i++
}
return array
}
/**
* Returns the children of this value as a newly allocated long array.
*
* @throws IllegalStateException if this is not an array.
*/
fun asLongArray(): LongArray {
check(type == ValueType.array) { "Value is not an array: $type" }
val array = LongArray(size)
var i = 0
var value = child
while (value != null) {
var v: Long
v = when (value.type) {
ValueType.stringValue -> value.stringValue!!.toLong()
ValueType.doubleValue -> value.doubleValue.toLong()
ValueType.longValue -> value.longValue
ValueType.booleanValue -> (if (value.longValue != 0L) 1 else 0).toLong()
else -> throw IllegalStateException("Value cannot be converted to long: " + value.type)
}
array[i] = v
value = value.next
i++
}
return array
}
/**
* Returns the children of this value as a newly allocated int array.
*
* @throws IllegalStateException if this is not an array.
*/
fun asIntArray(): IntArray {
check(type == ValueType.array) { "Value is not an array: $type" }
val array = IntArray(size)
var i = 0
var value = child
while (value != null) {
var v: Int
v = when (value.type) {
ValueType.stringValue -> value.stringValue!!.toInt()
ValueType.doubleValue -> value.doubleValue.toInt()
ValueType.longValue -> value.longValue.toInt()
ValueType.booleanValue -> if (value.longValue != 0L) 1 else 0
else -> throw IllegalStateException("Value cannot be converted to int: " + value.type)
}
array[i] = v
value = value.next
i++
}
return array
}
/**
* Returns the children of this value as a newly allocated boolean array.
*
* @throws IllegalStateException if this is not an array.
*/
fun asBooleanArray(): BooleanArray {
check(type == ValueType.array) { "Value is not an array: $type" }
val array = BooleanArray(size)
var i = 0
var value = child
while (value != null) {
var v: Boolean
v = when (value.type) {
ValueType.stringValue -> value.stringValue.toBoolean()
ValueType.doubleValue -> value.doubleValue == 0.0
ValueType.longValue -> value.longValue == 0L
ValueType.booleanValue -> value.longValue != 0L
else -> throw IllegalStateException("Value cannot be converted to boolean: " + value.type)
}
array[i] = v
value = value.next
i++
}
return array
}
/**
* Returns the children of this value as a newly allocated byte array.
*
* @throws IllegalStateException if this is not an array.
*/
fun asByteArray(): ByteArray {
check(type == ValueType.array) { "Value is not an array: $type" }
val array = ByteArray(size)
var i = 0
var value = child
while (value != null) {
var v: Byte
v = when (value.type) {
ValueType.stringValue -> value.stringValue!!.toByte()
ValueType.doubleValue -> value.doubleValue.toInt().toByte()
ValueType.longValue -> value.longValue.toByte()
ValueType.booleanValue -> if (value.longValue != 0L) 1.toByte() else 0
else -> throw IllegalStateException("Value cannot be converted to byte: " + value.type)
}
array[i] = v
value = value.next
i++
}
return array
}
/**
* Returns the children of this value as a newly allocated short array.
*
* @throws IllegalStateException if this is not an array.
*/
fun asShortArray(): ShortArray {
check(type == ValueType.array) { "Value is not an array: $type" }
val array = ShortArray(size)
var i = 0
var value = child
while (value != null) {
var v: Short
v = when (value.type) {
ValueType.stringValue -> value.stringValue!!.toShort()
ValueType.doubleValue -> value.doubleValue.toInt().toShort()
ValueType.longValue -> value.longValue.toShort()
ValueType.booleanValue -> if (value.longValue != 0L) 1.toShort() else 0
else -> throw IllegalStateException("Value cannot be converted to short: " + value.type)
}
array[i] = v
value = value.next
i++
}
return array
}
/**
* Returns the children of this value as a newly allocated char array.
*
* @throws IllegalStateException if this is not an array.
*/
fun asCharArray(): CharArray {
check(type == ValueType.array) { "Value is not an array: $type" }
val array = CharArray(size)
var i = 0
var value = child
while (value != null) {
var v: Char
v = when (value.type) {
ValueType.stringValue -> if (value.stringValue!!.length == 0) 0.toChar() else value.stringValue!![0]
ValueType.doubleValue -> value.doubleValue.toInt().toChar()
ValueType.longValue -> Char(value.longValue.toUShort())
ValueType.booleanValue -> if (value.longValue != 0L) 1.toChar() else 0.toChar()
else -> throw IllegalStateException("Value cannot be converted to char: " + value.type)
}
array[i] = v
value = value.next
i++
}
return array
}
/** Returns true if a child with the specified name exists and has a child. */
fun hasChild(name: String): Boolean {
return getChild(name) != null
}
/**
* Finds the child with the specified name and returns its first child.
*
* @return May be null.
*/
fun getChild(name: String): JsonValue? {
val child = get(name)
return child?.child
}
/**
* Finds the child with the specified name and returns it as a string. Returns defaultValue if not found.
*
* @param defaultValue May be null.
*/
fun getString(name: String, defaultValue: String?): String? {
val child = get(name)
return if (child == null || !child.isValue || child.isNull) defaultValue else child.asString()
}
/**
* Finds the child with the specified name and returns it as a float. Returns defaultValue if not found.
*/
fun getFloat(name: String, defaultValue: Float): Float {
val child = get(name)
return if (child == null || !child.isValue || child.isNull) defaultValue else child.asFloat()
}
/**
* Finds the child with the specified name and returns it as a double. Returns defaultValue if not found.
*/
fun getDouble(name: String, defaultValue: Double): Double {
val child = get(name)
return if (child == null || !child.isValue || child.isNull) defaultValue else child.asDouble()
}
/** Finds the child with the specified name and returns it as a long. Returns defaultValue if not found. */
fun getLong(name: String, defaultValue: Long): Long {
val child = get(name)
return if (child == null || !child.isValue || child.isNull) defaultValue else child.asLong()
}
/** Finds the child with the specified name and returns it as an int. Returns defaultValue if not found. */
fun getInt(name: String, defaultValue: Int): Int {
val child = get(name)
return if (child == null || !child.isValue || child.isNull) defaultValue else child.asInt()
}
/** Finds the child with the specified name and returns it as a boolean. Returns defaultValue if not found. */
fun getBoolean(name: String, defaultValue: Boolean): Boolean {
val child = get(name)
return if (child == null || !child.isValue || child.isNull) defaultValue else child.asBoolean()
}
/** Finds the child with the specified name and returns it as a byte. Returns defaultValue if not found. */
fun getByte(name: String, defaultValue: Byte): Byte {
val child = get(name)
return if (child == null || !child.isValue || child.isNull) defaultValue else child.asByte()
}
/** Finds the child with the specified name and returns it as a short. Returns defaultValue if not found. */
fun getShort(name: String, defaultValue: Short): Short {
val child = get(name)
return if (child == null || !child.isValue || child.isNull) defaultValue else child.asShort()
}
/** Finds the child with the specified name and returns it as a char. Returns defaultValue if not found. */
fun getChar(name: String, defaultValue: Char): Char {
val child = get(name)
return if (child == null || !child.isValue || child.isNull) defaultValue else child.asChar()
}
/**
* Finds the child with the specified name and returns it as a string.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getString(name: String): String? {
val child = get(name) ?: throw IllegalArgumentException("Named value not found: $name")
return child.asString()
}
/**
* Finds the child with the specified name and returns it as a float.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getFloat(name: String): Float {
val child = get(name) ?: throw IllegalArgumentException("Named value not found: $name")
return child.asFloat()
}
/**
* Finds the child with the specified name and returns it as a double.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getDouble(name: String): Double {
val child = get(name) ?: throw IllegalArgumentException("Named value not found: $name")
return child.asDouble()
}
/**
* Finds the child with the specified name and returns it as a long.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getLong(name: String): Long {
val child = get(name) ?: throw IllegalArgumentException("Named value not found: $name")
return child.asLong()
}
/**
* Finds the child with the specified name and returns it as an int.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getInt(name: String): Int {
val child = get(name) ?: throw IllegalArgumentException("Named value not found: $name")
return child.asInt()
}
/**
* Finds the child with the specified name and returns it as a boolean.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getBoolean(name: String): Boolean {
val child = get(name) ?: throw IllegalArgumentException("Named value not found: $name")
return child.asBoolean()
}
/**
* Finds the child with the specified name and returns it as a byte.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getByte(name: String): Byte {
val child = get(name) ?: throw IllegalArgumentException("Named value not found: $name")
return child.asByte()
}
/**
* Finds the child with the specified name and returns it as a short.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getShort(name: String): Short {
val child = get(name) ?: throw IllegalArgumentException("Named value not found: $name")
return child.asShort()
}
/**
* Finds the child with the specified name and returns it as a char.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getChar(name: String): Char {
val child = get(name) ?: throw IllegalArgumentException("Named value not found: $name")
return child.asChar()
}
/**
* Finds the child with the specified index and returns it as a string.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getString(index: Int): String? {
val child = get(index) ?: throw IllegalArgumentException("Indexed value not found: $name")
return child.asString()
}
/**
* Finds the child with the specified index and returns it as a float.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getFloat(index: Int): Float {
val child = get(index) ?: throw IllegalArgumentException("Indexed value not found: $name")
return child.asFloat()
}
/**
* Finds the child with the specified index and returns it as a double.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getDouble(index: Int): Double {
val child = get(index) ?: throw IllegalArgumentException("Indexed value not found: $name")
return child.asDouble()
}
/**
* Finds the child with the specified index and returns it as a long.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getLong(index: Int): Long {
val child = get(index) ?: throw IllegalArgumentException("Indexed value not found: $name")
return child.asLong()
}
/**
* Finds the child with the specified index and returns it as an int.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getInt(index: Int): Int {
val child = get(index) ?: throw IllegalArgumentException("Indexed value not found: $name")
return child.asInt()
}
/**
* Finds the child with the specified index and returns it as a boolean.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getBoolean(index: Int): Boolean {
val child = get(index) ?: throw IllegalArgumentException("Indexed value not found: $name")
return child.asBoolean()
}
/**
* Finds the child with the specified index and returns it as a byte.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getByte(index: Int): Byte {
val child = get(index) ?: throw IllegalArgumentException("Indexed value not found: $name")
return child.asByte()
}
/**
* Finds the child with the specified index and returns it as a short.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getShort(index: Int): Short {
val child = get(index) ?: throw IllegalArgumentException("Indexed value not found: $name")
return child.asShort()
}
/**
* Finds the child with the specified index and returns it as a char.
*
* @throws IllegalArgumentException if the child was not found.
*/
fun getChar(index: Int): Char {
val child = get(index) ?: throw IllegalArgumentException("Indexed value not found: $name")
return child.asChar()
}
fun type(): ValueType? {
return type
}
fun setType(type: ValueType?) {
requireNotNull(type) { "type cannot be null." }
this.type = type
}
val isArray: Boolean
get() = type == ValueType.array
val isObject: Boolean
get() = type == ValueType.`object`
val isString: Boolean
get() = type == ValueType.stringValue
val isNumber: Boolean
/** Returns true if this is a double or long value. */
get() = type == ValueType.doubleValue || type == ValueType.longValue
val isDouble: Boolean
get() = type == ValueType.doubleValue
val isLong: Boolean
get() = type == ValueType.longValue
val isBoolean: Boolean
get() = type == ValueType.booleanValue
val isNull: Boolean
get() = type == ValueType.nullValue
val isValue: Boolean
/** Returns true if this is not an array or object. */
get() {
return when (type) {
ValueType.stringValue, ValueType.doubleValue, ValueType.longValue, ValueType.booleanValue, ValueType.nullValue -> true
else -> false
}
}
/** Sets the name of the specified value and adds it after the last child. */
fun addChild(name: String?, value: JsonValue) {
requireNotNull(name) { "name cannot be null." }
value.name = name
addChild(value)
}
/** Adds the specified value after the last child. */
fun addChild(value: JsonValue) {
value.parent = this
size++
var current = child
if (current == null) child = value else {
while (true) {
if (current!!.next == null) {
current.next = value
value.prev = current
return
}
current = current.next
}
}
}
/**
* @param value May be null.
*/
fun set(value: String?) {
stringValue = value
type = if (value == null) ValueType.nullValue else ValueType.stringValue
}
/**
* @param stringValue May be null if the string representation is the string value of the double (eg, no leading zeros).
*/
operator fun set(value: Double, stringValue: String?) {
doubleValue = value
longValue = value.toLong()
this.stringValue = stringValue
type = ValueType.doubleValue
}
/**
* @param stringValue May be null if the string representation is the string value of the long (eg, no leading zeros).
*/
operator fun set(value: Long, stringValue: String?) {
longValue = value
doubleValue = value.toDouble()
this.stringValue = stringValue
type = ValueType.longValue
}
fun set(value: Boolean) {
longValue = (if (value) 1 else 0).toLong()
type = ValueType.booleanValue
}
fun toJson(outputType: OutputType): String? {
if (isValue) return asString()
val buffer = StringBuilder(512)
json(this, buffer, outputType)
return buffer.toString()
}
private fun json(`object`: JsonValue, buffer: StringBuilder, outputType: OutputType) {
if (`object`.isObject) {
if (`object`.child == null) buffer.append("{}") else {
val start = buffer.length
while (true) {
buffer.append('{')
val i = 0
run {
var child = `object`.child
while (child != null) {
buffer.append(outputType.quoteName(child.name!!))
buffer.append(':')
json(child, buffer, outputType)
if (child.next != null) buffer.append(',')
child = child.next
}
}
break
}
buffer.append('}')
}
} else if (`object`.isArray) {
if (`object`.child == null) buffer.append("[]") else {
val start = buffer.length
while (true) {
buffer.append('[')
run {
var child = `object`.child
while (child != null) {
json(child!!, buffer, outputType)
if (child!!.next != null) buffer.append(',')
child = child!!.next
}
}
break
}
buffer.append(']')
}
} else if (`object`.isString) {
buffer.append(outputType.quoteValue(`object`.asString()))
} else if (`object`.isDouble) {
val doubleValue = `object`.asDouble()
val longValue = `object`.asLong()
buffer.append(if (doubleValue == longValue.toDouble()) longValue else doubleValue)
} else if (`object`.isLong) {
buffer.append(`object`.asLong())
} else if (`object`.isBoolean) {
buffer.append(`object`.asBoolean())
} else if (`object`.isNull) {
buffer.append("null")
} else throw JsonException("Unknown object type: $`object`")
}
override fun iterator(): JsonIterator {
return JsonIterator()
}
override fun toString(): String {
return if (isValue) if (name == null) asString()!! else name + ": " + asString() else (if (name == null) "" else "$name: ") + prettyPrint(
OutputType.minimal, 0
)
}
/** Returns a human readable string representing the path from the root of the JSON object graph to this value. */
fun trace(): String {
if (parent == null) {
if (type == ValueType.array) return "[]"
return if (type == ValueType.`object`) "{}" else ""
}
var trace: String
if (parent!!.type == ValueType.array) {
trace = "[]"
var i = 0
run {
var child = parent!!.child
while (child != null) {
if (child === this) {
trace = "[$i]"
break
}
child = child.next
i++
}
}
} else if (name!!.indexOf('.') != -1) {
trace = ".\"" + name!!.replace("\"", "\\\"") + "\""
} else {
trace = ".$name"
}
return parent!!.trace() + trace
}
fun prettyPrint(outputType: OutputType?, singleLineColumns: Int): String {
val settings = PrettyPrintSettings()
settings.outputType = outputType
settings.singleLineColumns = singleLineColumns
return prettyPrint(settings)
}
fun prettyPrint(settings: PrettyPrintSettings): String {
val buffer = StringBuilder(512)
prettyPrint(this, buffer, 0, settings)
return buffer.toString()
}
private fun prettyPrint(`object`: JsonValue, buffer: StringBuilder, indent: Int, settings: PrettyPrintSettings) {
val outputType = settings.outputType
if (`object`.isObject) {
if (`object`.child == null) buffer.append("{}") else {
var newLines = !isFlat(`object`)
val start = buffer.length
outer@ while (true) {
buffer.append(if (newLines) "{\n" else "{ ")
val i = 0
var child = `object`.child
while (child != null) {
if (newLines) indent(indent, buffer)
buffer.append(outputType!!.quoteName(child.name!!))
buffer.append(": ")
prettyPrint(child, buffer, indent + 1, settings)
if ((!newLines || outputType !== OutputType.minimal) && child.next != null) buffer.append(',')
buffer.append(if (newLines) '\n' else ' ')
if (!newLines && buffer.length - start > settings.singleLineColumns) {
buffer.setLength(start)
newLines = true
continue@outer
}
child = child.next
}
break
}
if (newLines) indent(indent - 1, buffer)
buffer.append('}')
}
} else if (`object`.isArray) {
if (`object`.child == null) buffer.append("[]") else {
var newLines = !isFlat(`object`)
val wrap = settings.wrapNumericArrays || !isNumeric(`object`)
val start = buffer.length
outer@ while (true) {
buffer.append(if (newLines) "[\n" else "[ ")
var child = `object`.child
while (child != null) {
if (newLines) indent(indent, buffer)
prettyPrint(child, buffer, indent + 1, settings)
if ((!newLines || outputType !== OutputType.minimal) && child.next != null) buffer.append(',')
buffer.append(if (newLines) '\n' else ' ')
if (wrap && !newLines && buffer.length - start > settings.singleLineColumns) {
buffer.setLength(start)
newLines = true
continue@outer
}
child = child.next
}
break
}
if (newLines) indent(indent - 1, buffer)
buffer.append(']')
}
} else if (`object`.isString) {
buffer.append(outputType!!.quoteValue(`object`.asString()))
} else if (`object`.isDouble) {
val doubleValue = `object`.asDouble()
val longValue = `object`.asLong()
buffer.append(if (doubleValue == longValue.toDouble()) longValue else doubleValue)
} else if (`object`.isLong) {
buffer.append(`object`.asLong())
} else if (`object`.isBoolean) {
buffer.append(`object`.asBoolean())
} else if (`object`.isNull) {
buffer.append("null")
} else throw JsonException("Unknown object type: $`object`")
}
/** More efficient than [.prettyPrint] but [PrettyPrintSettings.singleLineColumns] and
* [PrettyPrintSettings.wrapNumericArrays] are not supported. */
@Throws(IOException::class)
fun prettyPrint(outputType: OutputType?, writer: Writer) {
val settings = PrettyPrintSettings()
settings.outputType = outputType
prettyPrint(this, writer, 0, settings)
}
@Throws(IOException::class)
private fun prettyPrint(`object`: JsonValue, writer: Writer, indent: Int, settings: PrettyPrintSettings) {
val outputType = settings.outputType
if (`object`.isObject) {
if (`object`.child == null) writer.append("{}") else {
val newLines = !isFlat(`object`) || `object`.size > 6
writer.append(if (newLines) "{\n" else "{ ")
val i = 0
run {
var child = `object`.child
while (child != null) {
if (newLines) indent(indent, writer)
writer.append(outputType!!.quoteName(child.name!!))
writer.append(": ")
prettyPrint(child, writer, indent + 1, settings)
if ((!newLines || outputType !== OutputType.minimal) && child.next != null) writer.append(',')
writer.append(if (newLines) '\n' else ' ')
child = child.next
}
}
if (newLines) indent(indent - 1, writer)
writer.append('}')
}
} else if (`object`.isArray) {
if (`object`.child == null) writer.append("[]") else {
val newLines = !isFlat(`object`)
writer.append(if (newLines) "[\n" else "[ ")
val i = 0
run {
var child = `object`.child
while (child != null) {
if (newLines) indent(indent, writer)
prettyPrint(child!!, writer, indent + 1, settings)
if ((!newLines || outputType !== OutputType.minimal) && child!!.next != null) writer.append(',')
writer.append(if (newLines) '\n' else ' ')
child = child!!.next
}
}
if (newLines) indent(indent - 1, writer)
writer.append(']')
}
} else if (`object`.isString) {
writer.append(outputType!!.quoteValue(`object`.asString()))
} else if (`object`.isDouble) {
val doubleValue = `object`.asDouble()
val longValue = `object`.asLong()
writer.append((if (doubleValue == longValue.toDouble()) longValue else doubleValue).toString())
} else if (`object`.isLong) {
writer.append(`object`.asLong().toString())
} else if (`object`.isBoolean) {
writer.append(`object`.asBoolean().toString())
} else if (`object`.isNull) {
writer.append("null")
} else throw JsonException("Unknown object type: $`object`")
}
inner class JsonIterator : MutableIterator, Iterable {
var entry = child
var current: JsonValue? = null
override fun hasNext(): Boolean {
return entry != null
}
override fun next(): JsonValue {
val current = entry
this.current = current
if (current == null) throw NoSuchElementException()
entry = current.next
return current
}
override fun remove() {
if (current!!.prev == null) {
child = current!!.next
if (child != null) child!!.prev = null
} else {
current!!.prev!!.next = current!!.next
if (current!!.next != null) current!!.next!!.prev = current!!.prev
}
size--
}
override fun iterator(): MutableIterator {
return this
}
}
enum class ValueType {
`object`, array, stringValue, doubleValue, longValue, booleanValue, nullValue
}
class PrettyPrintSettings {
var outputType: OutputType? = null
/** If an object on a single line fits this many columns, it won't wrap. */
var singleLineColumns = 0
/** Arrays of floats won't wrap. */
var wrapNumericArrays = false
}
companion object {
private fun isFlat(`object`: JsonValue): Boolean {
run {
var child = `object`.child
while (child != null) {
if (child.isObject || child.isArray) return false
child = child.next
}
}
return true
}
private fun isNumeric(`object`: JsonValue): Boolean {
run {
var child = `object`.child
while (child != null) {
if (!child.isNumber) return false
child = child.next
}
}
return true
}
private fun indent(count: Int, buffer: StringBuilder) {
for (i in 0 until count) buffer.append('\t')
}
@Throws(IOException::class)
private fun indent(count: Int, buffer: Writer) {
for (i in 0 until count) buffer.append('\t')
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy