Refactor Value.

This commit is contained in:
Alexander Nozik 2020-12-09 12:04:10 +03:00
parent e317b67a48
commit 7c4d69ec1b
20 changed files with 117 additions and 152 deletions

View File

@ -4,6 +4,7 @@
### Added
- Yaml meta format based on yaml.kt
- `Path` builders
- Special ValueType for lists
### Changed
- `ListValue` and `DoubleArrayValue` implement `Iterable`.
@ -11,6 +12,7 @@
- `Meta{}` builder made inline
- Moved `Envelope` builder to a top level function. Companion invoke is deprecated.
- Context logging moved to the extension
- `number` and `string` methods on `Value` moved to extensions (breaking change)
### Deprecated

View File

@ -84,6 +84,6 @@ public fun <T : Any> Provider.top(target: String, type: KClass<out T>): Map<Name
/**
* Typed top level content
*/
public inline fun <reified T : Any> Provider.top(target: String): Map<Name, T> = top(target, T::class)
public inline fun <reified T : Any> Provider.top(target: String ): Map<Name, T> = top(target, T::class)

View File

@ -17,6 +17,7 @@ public class FrontMatterEnvelopeFormat(
) : EnvelopeFormat {
override fun readPartial(input: Input): PartialEnvelope {
@Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER")
var line = ""
var offset = 0u
do {

View File

@ -32,57 +32,56 @@ public object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
writeUtf8String(str)
}
public fun Output.writeValue(value: Value) {
if (value.isList()) {
public fun Output.writeValue(value: Value): Unit = when (value.type) {
ValueType.NUMBER -> when (value.value) {
is Short -> {
writeChar('s')
writeShort(value.short)
}
is Int -> {
writeChar('i')
writeInt(value.int)
}
is Long -> {
writeChar('l')
writeLong(value.long)
}
is Float -> {
writeChar('f')
writeFloat(value.float)
}
else -> {
writeChar('d')
writeDouble(value.double)
}
}
ValueType.STRING -> {
writeChar('S')
writeString(value.string)
}
ValueType.BOOLEAN -> {
if (value.boolean) {
writeChar('+')
} else {
writeChar('-')
}
}
ValueType.NULL -> {
writeChar('N')
}
ValueType.LIST -> {
writeChar('L')
writeInt(value.list.size)
value.list.forEach {
writeValue(it)
}
} else when (value.type) {
ValueType.NUMBER -> when (value.value) {
is Short -> {
writeChar('s')
writeShort(value.number.toShort())
}
is Int -> {
writeChar('i')
writeInt(value.number.toInt())
}
is Long -> {
writeChar('l')
writeLong(value.number.toLong())
}
is Float -> {
writeChar('f')
writeFloat(value.number.toFloat())
}
else -> {
writeChar('d')
writeDouble(value.number.toDouble())
}
}
ValueType.STRING -> {
writeChar('S')
writeString(value.string)
}
ValueType.BOOLEAN -> {
if (value.boolean) {
writeChar('+')
} else {
writeChar('-')
}
}
ValueType.NULL -> {
writeChar('N')
}
}
}
override fun writeMeta(
output: kotlinx.io.Output,
meta: hep.dataforge.meta.Meta,
descriptor: hep.dataforge.meta.descriptors.NodeDescriptor?
descriptor: hep.dataforge.meta.descriptors.NodeDescriptor?,
) {
output.writeChar('M')
output.writeInt(meta.items.size)

View File

@ -29,9 +29,7 @@ public interface Envelope {
/**
* Build a static envelope using provided builder
*/
@Deprecated("Use top level function instead",
replaceWith = ReplaceWith("Envelope(block)", "hep.dataforge.io.Envelope")
)
@Deprecated("Use top level function instead")
public inline operator fun invoke(block: EnvelopeBuilder.() -> Unit): Envelope =
EnvelopeBuilder().apply(block).seal()
}

View File

@ -7,7 +7,7 @@ import kotlin.test.assertEquals
class EnvelopeFormatTest {
val envelope = Envelope.invoke {
val envelope = Envelope {
type = "test.format"
meta{
"d" put 22.2

View File

@ -2,6 +2,7 @@ package hep.dataforge.io
import hep.dataforge.meta.*
import hep.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY
import hep.dataforge.values.number
import kotlinx.io.asBinary
import kotlinx.serialization.json.*
import kotlin.test.Test

View File

@ -817,9 +817,6 @@ public final class hep/dataforge/values/DoubleArrayValue : hep/dataforge/values/
public fun <init> ([D)V
public fun equals (Ljava/lang/Object;)Z
public fun getList ()Ljava/util/List;
public fun getNumber ()Ljava/lang/Double;
public synthetic fun getNumber ()Ljava/lang/Number;
public fun getString ()Ljava/lang/String;
public fun getType ()Lhep/dataforge/values/ValueType;
public synthetic fun getValue ()Ljava/lang/Object;
public fun getValue ()[D
@ -832,8 +829,6 @@ public final class hep/dataforge/values/EnumValue : hep/dataforge/values/Value {
public fun <init> (Ljava/lang/Enum;)V
public fun equals (Ljava/lang/Object;)Z
public fun getList ()Ljava/util/List;
public fun getNumber ()Ljava/lang/Number;
public fun getString ()Ljava/lang/String;
public fun getType ()Lhep/dataforge/values/ValueType;
public fun getValue ()Ljava/lang/Enum;
public synthetic fun getValue ()Ljava/lang/Object;
@ -850,8 +845,6 @@ public final class hep/dataforge/values/False : hep/dataforge/values/Value {
public static final field INSTANCE Lhep/dataforge/values/False;
public fun equals (Ljava/lang/Object;)Z
public fun getList ()Ljava/util/List;
public fun getNumber ()Ljava/lang/Number;
public fun getString ()Ljava/lang/String;
public fun getType ()Lhep/dataforge/values/ValueType;
public fun getValue ()Ljava/lang/Object;
public fun hashCode ()I
@ -862,8 +855,7 @@ public final class hep/dataforge/values/LazyParsedValue : hep/dataforge/values/V
public fun <init> (Ljava/lang/String;)V
public fun equals (Ljava/lang/Object;)Z
public fun getList ()Ljava/util/List;
public fun getNumber ()Ljava/lang/Number;
public fun getString ()Ljava/lang/String;
public final fun getString ()Ljava/lang/String;
public fun getType ()Lhep/dataforge/values/ValueType;
public fun getValue ()Ljava/lang/Object;
public fun hashCode ()I
@ -874,8 +866,6 @@ public final class hep/dataforge/values/ListValue : hep/dataforge/values/Value,
public fun <init> (Ljava/util/List;)V
public fun equals (Ljava/lang/Object;)Z
public fun getList ()Ljava/util/List;
public fun getNumber ()Ljava/lang/Number;
public fun getString ()Ljava/lang/String;
public fun getType ()Lhep/dataforge/values/ValueType;
public synthetic fun getValue ()Ljava/lang/Object;
public fun getValue ()Ljava/util/List;
@ -888,8 +878,6 @@ public final class hep/dataforge/values/Null : hep/dataforge/values/Value {
public static final field INSTANCE Lhep/dataforge/values/Null;
public fun equals (Ljava/lang/Object;)Z
public fun getList ()Ljava/util/List;
public fun getNumber ()Ljava/lang/Number;
public fun getString ()Ljava/lang/String;
public fun getType ()Lhep/dataforge/values/ValueType;
public fun getValue ()Ljava/lang/Object;
public fun hashCode ()I
@ -900,8 +888,7 @@ public final class hep/dataforge/values/NumberValue : hep/dataforge/values/Value
public fun <init> (Ljava/lang/Number;)V
public fun equals (Ljava/lang/Object;)Z
public fun getList ()Ljava/util/List;
public fun getNumber ()Ljava/lang/Number;
public fun getString ()Ljava/lang/String;
public final fun getNumber ()Ljava/lang/Number;
public fun getType ()Lhep/dataforge/values/ValueType;
public fun getValue ()Ljava/lang/Object;
public fun hashCode ()I
@ -912,8 +899,7 @@ public final class hep/dataforge/values/StringValue : hep/dataforge/values/Value
public fun <init> (Ljava/lang/String;)V
public fun equals (Ljava/lang/Object;)Z
public fun getList ()Ljava/util/List;
public fun getNumber ()Ljava/lang/Number;
public fun getString ()Ljava/lang/String;
public final fun getString ()Ljava/lang/String;
public fun getType ()Lhep/dataforge/values/ValueType;
public fun getValue ()Ljava/lang/Object;
public fun hashCode ()I
@ -924,8 +910,6 @@ public final class hep/dataforge/values/True : hep/dataforge/values/Value {
public static final field INSTANCE Lhep/dataforge/values/True;
public fun equals (Ljava/lang/Object;)Z
public fun getList ()Ljava/util/List;
public fun getNumber ()Ljava/lang/Number;
public fun getString ()Ljava/lang/String;
public fun getType ()Lhep/dataforge/values/ValueType;
public fun getValue ()Ljava/lang/Object;
public fun hashCode ()I
@ -934,18 +918,16 @@ public final class hep/dataforge/values/True : hep/dataforge/values/Value {
public abstract interface class hep/dataforge/values/Value {
public static final field Companion Lhep/dataforge/values/Value$Companion;
public static final field TARGET Ljava/lang/String;
public static final field TYPE Ljava/lang/String;
public abstract fun equals (Ljava/lang/Object;)Z
public abstract fun getList ()Ljava/util/List;
public abstract fun getNumber ()Ljava/lang/Number;
public abstract fun getString ()Ljava/lang/String;
public abstract fun getType ()Lhep/dataforge/values/ValueType;
public abstract fun getValue ()Ljava/lang/Object;
public abstract fun hashCode ()I
}
public final class hep/dataforge/values/Value$Companion {
public static final field TARGET Ljava/lang/String;
public static final field TYPE Ljava/lang/String;
public final fun of (Ljava/lang/Object;)Lhep/dataforge/values/Value;
}
@ -978,6 +960,9 @@ public final class hep/dataforge/values/ValueKt {
public static final fun asValue ([I)Lhep/dataforge/values/Value;
public static final fun asValue ([J)Lhep/dataforge/values/Value;
public static final fun asValue ([S)Lhep/dataforge/values/Value;
public static final fun getNumber (Lhep/dataforge/values/Value;)Ljava/lang/Number;
public static final fun getNumberOrNull (Lhep/dataforge/values/Value;)Ljava/lang/Number;
public static final fun getString (Lhep/dataforge/values/Value;)Ljava/lang/String;
public static final fun parseValue (Ljava/lang/String;)Lhep/dataforge/values/Value;
}
@ -993,6 +978,7 @@ public final class hep/dataforge/values/ValueSerializer : kotlinx/serialization/
public final class hep/dataforge/values/ValueType : java/lang/Enum {
public static final field BOOLEAN Lhep/dataforge/values/ValueType;
public static final field Companion Lhep/dataforge/values/ValueType$Companion;
public static final field LIST Lhep/dataforge/values/ValueType;
public static final field NULL Lhep/dataforge/values/ValueType;
public static final field NUMBER Lhep/dataforge/values/ValueType;
public static final field STRING Lhep/dataforge/values/ValueType;

View File

@ -16,20 +16,16 @@ import kotlinx.serialization.json.*
/**
* @param descriptor reserved for custom serialization in future
*/
public fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement {
return if (isList()) {
JsonArray(list.map { it.toJson() })
} else {
when (type) {
ValueType.NUMBER -> JsonPrimitive(number)
ValueType.STRING -> JsonPrimitive(string)
ValueType.BOOLEAN -> JsonPrimitive(boolean)
ValueType.NULL -> JsonNull
}
}
public fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement = when (type) {
ValueType.NUMBER -> JsonPrimitive(numberOrNull)
ValueType.STRING -> JsonPrimitive(string)
ValueType.BOOLEAN -> JsonPrimitive(boolean)
ValueType.LIST -> JsonArray(list.map { it.toJson() })
ValueType.NULL -> JsonNull
}
//Use these methods to customize JSON key mapping
@Suppress("NULLABLE_EXTENSION_OPERATOR_WITH_SAFE_CALL_RECEIVER")
private fun String.toJsonKey(descriptor: ItemDescriptor?) = descriptor?.attributes["jsonName"].string ?: toString()
//private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key)

View File

@ -4,10 +4,7 @@ import hep.dataforge.meta.Meta.Companion.VALUE_KEY
import hep.dataforge.meta.MetaItem.NodeItem
import hep.dataforge.meta.MetaItem.ValueItem
import hep.dataforge.names.*
import hep.dataforge.values.EnumValue
import hep.dataforge.values.Null
import hep.dataforge.values.Value
import hep.dataforge.values.boolean
import hep.dataforge.values.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@ -245,7 +242,7 @@ public val MetaItem<*>?.value: Value?
public val MetaItem<*>?.string: String? get() = value?.string
public val MetaItem<*>?.boolean: Boolean? get() = value?.boolean
public val MetaItem<*>?.number: Number? get() = value?.number
public val MetaItem<*>?.number: Number? get() = value?.numberOrNull
public val MetaItem<*>?.double: Double? get() = number?.toDouble()
public val MetaItem<*>?.float: Float? get() = number?.toFloat()
public val MetaItem<*>?.int: Int? get() = number?.toInt()

View File

@ -3,10 +3,7 @@ package hep.dataforge.meta
import hep.dataforge.meta.transformations.MetaConverter
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.values.DoubleArrayValue
import hep.dataforge.values.Value
import hep.dataforge.values.asValue
import hep.dataforge.values.doubleArray
import hep.dataforge.values.*
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
@ -72,7 +69,6 @@ public fun <R> MutableItemDelegate.convert(
}
/* Read-write delegates for [MutableItemProvider] */
/**
@ -171,7 +167,7 @@ public fun MutableItemProvider.numberList(
vararg default: Number,
key: Name? = null,
): ReadWriteProperty<Any?, List<Number>> = item(key).convert(
reader = { it?.value?.list?.map { value -> value.number } ?: listOf(*default) },
reader = { it?.value?.list?.map { value -> value.numberOrNull ?: Double.NaN } ?: listOf(*default) },
writer = { it.map { num -> num.asValue() }.asValue().asMetaItem() }
)

View File

@ -11,7 +11,7 @@ import kotlinx.serialization.Serializable
*/
@Serializable
public enum class ValueType {
NUMBER, STRING, BOOLEAN, NULL
NUMBER, STRING, BOOLEAN, LIST, NULL
}
/**
@ -31,16 +31,6 @@ public interface Value {
*/
public val type: ValueType
/**
* get this value represented as Number
*/
public val number: Number
/**
* get this value represented as String
*/
public val string: String
/**
* get this value represented as List
*/
@ -51,7 +41,7 @@ public interface Value {
override fun hashCode(): Int
public companion object {
public const val TARGET: String = "value"
public const val TYPE: String = "value"
/**
* Convert object to value
@ -86,16 +76,27 @@ public interface Value {
}
}
public val Value.string: String get() = toString()
/**
* get this value represented as Number
*/
public val Value.numberOrNull: Number?
get() = if (this is NumberValue) number else string.toDoubleOrNull()
/**
* Return [Value] number content or throw error if value is not a number
*/
public val Value.number: Number
get() = (if (this is NumberValue) number else numberOrNull ?: error("The value is not a number"))
/**
* A singleton null value
*/
public object Null : Value {
override val value: Any? get() = null
override val type: ValueType get() = ValueType.NULL
override val number: Number get() = Double.NaN
override val string: String get() = "@null"
override fun toString(): String = value.toString()
override fun toString(): String = "@null"
override fun equals(other: Any?): Boolean = other === Null
override fun hashCode(): Int = 0
@ -107,9 +108,6 @@ public object Null : Value {
public object True : Value {
override val value: Any get() = true
override val type: ValueType get() = ValueType.BOOLEAN
override val number: Number get() = 1.0
override val string: String get() = "true"
override fun toString(): String = value.toString()
override fun equals(other: Any?): Boolean = other === True
@ -122,42 +120,40 @@ public object True : Value {
public object False : Value {
override val value: Any get() = false
override val type: ValueType get() = ValueType.BOOLEAN
override val number: Number get() = -1.0
override val string: String get() = "false"
override fun toString(): String = value.toString()
override fun equals(other: Any?): Boolean = other === False
override fun hashCode(): Int = -1
}
public class NumberValue(override val number: Number) : Value {
public class NumberValue(public val number: Number) : Value {
override val value: Any get() = number
override val type: ValueType get() = ValueType.NUMBER
override val string: String get() = number.toString()
override fun toString(): String = number.toString()
override fun equals(other: Any?): Boolean {
if (other !is Value) return false
return when (number) {
is Short -> number.toShort() == other.number.toShort()
is Long -> number.toLong() == other.number.toLong()
is Byte -> number.toByte() == other.number.toByte()
is Int -> number.toInt() == other.number.toInt()
is Float -> number.toFloat() == other.number.toFloat()
is Double -> number.toDouble() == other.number.toDouble()
else -> number.toString() == other.number.toString()
val otherNumber = other.numberOrNull ?: return false
return when (numberOrNull) {
is Short -> number.toShort() == otherNumber.toShort()
is Long -> number.toLong() == otherNumber.toLong()
is Byte -> number.toByte() == otherNumber.toByte()
is Int -> number.toInt() == otherNumber.toInt()
is Float -> number.toFloat() == otherNumber.toFloat()
is Double -> number.toDouble() == otherNumber.toDouble()
else -> number.toString() == otherNumber.toString()
}
}
override fun hashCode(): Int = number.hashCode()
override fun toString(): String = value.toString()
override fun hashCode(): Int = numberOrNull.hashCode()
}
public class StringValue(override val string: String) : Value {
public class StringValue(public val string: String) : Value {
override val value: Any get() = string
override val type: ValueType get() = ValueType.STRING
override val number: Number get() = string.toDouble()
override fun equals(other: Any?): Boolean {
return this.string == (other as? Value)?.string
@ -165,21 +161,19 @@ public class StringValue(override val string: String) : Value {
override fun hashCode(): Int = string.hashCode()
override fun toString(): String = "\"${value.toString()}\""
override fun toString(): String = string
}
public class EnumValue<E : Enum<*>>(override val value: E) : Value {
override val type: ValueType get() = ValueType.STRING
override val number: Number get() = value.ordinal
override val string: String get() = value.name
override fun toString(): String = value.toString()
override fun equals(other: Any?): Boolean {
return string == (other as? Value)?.string
}
override fun hashCode(): Int = value.hashCode()
override fun toString(): String = value.toString()
}
public class ListValue(override val list: List<Value>) : Value, Iterable<Value> {
@ -188,9 +182,7 @@ public class ListValue(override val list: List<Value>) : Value, Iterable<Value>
}
override val value: List<Value> get() = list
override val type: ValueType get() = list.first().type
override val number: Number get() = list.first().number
override val string: String get() = list.first().string
override val type: ValueType get() = ValueType.LIST
override fun toString(): String = list.joinToString(prefix = "[", postfix = "]")
@ -200,7 +192,7 @@ public class ListValue(override val list: List<Value>) : Value, Iterable<Value>
if (this === other) return true
if (other !is Value) return false
if (other is DoubleArrayValue) {
return DoubleArray(list.size) { list[it].number.toDouble() }.contentEquals(other.value)
return DoubleArray(list.size) { list[it].numberOrNull?.toDouble() ?: Double.NaN }.contentEquals(other.value)
}
return list == other.list
}
@ -208,8 +200,6 @@ public class ListValue(override val list: List<Value>) : Value, Iterable<Value>
override fun hashCode(): Int {
return list.hashCode()
}
}
public fun Number.asValue(): Value = NumberValue(this)

View File

@ -24,6 +24,7 @@ public object ValueSerializer : KSerializer<Value> {
ValueType.NUMBER -> decodeDouble().asValue() //TODO differentiate?
ValueType.BOOLEAN -> decodeBoolean().asValue()
ValueType.STRING -> decodeString().asValue()
ValueType.LIST -> decodeSerializableValue(ListSerializer(ValueSerializer)).asValue()
}
}
@ -45,6 +46,7 @@ public object ValueSerializer : KSerializer<Value> {
ValueType.NUMBER -> encodeDouble(value.double)
ValueType.BOOLEAN -> encodeBoolean(value.boolean)
ValueType.STRING -> encodeString(value.string)
ValueType.LIST -> encodeSerializableValue(ListSerializer(ValueSerializer),value.list)
}
}

View File

@ -4,12 +4,11 @@ package hep.dataforge.values
/**
* A value built from string which content and type are parsed on-demand
*/
public class LazyParsedValue(override val string: String) : Value {
public class LazyParsedValue(public val string: String) : Value {
private val parsedValue by lazy { string.parseValue() }
override val value: Any? get() = parsedValue.value
override val type: ValueType get() = parsedValue.type
override val number: Number get() = parsedValue.number
override fun toString(): String = string
@ -24,9 +23,7 @@ public fun String.lazyParseValue(): LazyParsedValue = LazyParsedValue(this)
* A performance optimized version of list value for doubles
*/
public class DoubleArrayValue(override val value: DoubleArray) : Value, Iterable<Double> {
override val type: ValueType get() = ValueType.NUMBER
override val number: Double get() = value.first()
override val string: String get() = value.first().toString()
override val type: ValueType get() = ValueType.LIST
override val list: List<Value> get() = value.map { NumberValue(it) }
override fun equals(other: Any?): Boolean {

View File

@ -9,10 +9,9 @@ import hep.dataforge.meta.MetaBuilder
public fun Value.isNull(): Boolean = this == Null
/**
* Check if value is list. This method checks the type of the value, not the number of the elements.
* So it will return `true` for empty lists and lists of one elements.
* Check if value is list.
*/
public fun Value.isList(): Boolean = this is Iterable<*>
public fun Value.isList(): Boolean = this.type == ValueType.LIST
public val Value.boolean: Boolean
get() = this == True

View File

@ -19,6 +19,7 @@ class DynamicMetaTest {
d.ob.booleanNode = true
val meta = DynamicMeta(d)
println(meta)
assertEquals(true, meta["ob.booleanNode"].boolean)
assertEquals(2, meta["array"].value?.list?.get(1)?.int)
assertEquals(4, meta.items.size)

View File

@ -32,5 +32,6 @@ public val ColumnHeader<Value>.textWidth: Int
ValueType.STRING -> 16
ValueType.BOOLEAN -> 5
ValueType.NULL -> 5
ValueType.LIST -> 32
null -> 16
}

View File

@ -111,8 +111,8 @@ public class TextTable(
private fun Output.writeValue(value: Value, width: Int, left: Boolean = true) {
require(width > 5) { "Width could not be less than 5" }
val str: String = when (value.type) {
ValueType.NUMBER -> value.number.toString() //TODO apply decimal format
ValueType.STRING -> value.string.take(width)
ValueType.NUMBER -> value.numberOrNull.toString() //TODO apply decimal format
ValueType.STRING, ValueType.LIST -> value.string.take(width)
ValueType.BOOLEAN -> if (value.boolean) {
"true"
} else {

View File

@ -6,6 +6,7 @@ import hep.dataforge.tables.get
import hep.dataforge.tables.row
import hep.dataforge.values.Value
import hep.dataforge.values.int
import hep.dataforge.values.string
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import kotlinx.io.ExperimentalIoApi

View File

@ -16,9 +16,7 @@ import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.reflect.KClass
typealias FileFormatResolver<T> = (Path, Meta) -> IOFormat<T>
public typealias FileFormatResolver<T> = (Path, Meta) -> IOFormat<T>
private fun newZFS(path: Path): FileSystem {
val fsProvider = FileSystemProvider.installedProviders().find { it.scheme == "jar" }
@ -84,7 +82,7 @@ public fun <T : Any> DataTreeBuilder<T>.file(
* Read the directory as a data node. If [path] is a zip archive, read it as directory
*/
@DFExperimental
fun <T : Any> IOPlugin.readDataDirectory(
public fun <T : Any> IOPlugin.readDataDirectory(
path: Path,
type: KClass<out T>,
formatResolver: FileFormatResolver<T>