From 1320749178929196e750a28c271d5ac883d20800 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Thu, 11 Apr 2024 14:01:15 -0700 Subject: [PATCH 01/53] JSON configuration and Processor This PR adds a declarative JSON configuration object that allows users to specify the schema of a JSON message. It is meant to have good out-of-the-box defaults, while still allowing power users to modify some of the finer parsing details (should this int field be parseable from a string? should null values be allowed? what if a field is missing? etc). The JSON configuration layer is not tied to any specific implementation; it is introspectible, and could have alternative implementations with other parsing backends. (I could imagine a DHE use-case where they do code-generation based on the JSON configuration, somewhat like the DHE avro ObjectProcessor code generator.) Out of the box, there's an ObjectProcessor implementation based on the Jackson streaming APIs; that is, the data flows from byte[]s (or InputStream, relevant for very-large-files) to the output WritableChunks without the need for the intermediating Jackson databind layer (TreeNode). This saves a large layer of allocation that our current kafka json_spec layer relies upon. The ObjectProcessor layer means that this can be used in other places that expose ObjectProcessor layers and want 1-to-1 record-to-row (currently, Kafka). Part of #5222 --- buildSrc/src/main/groovy/Classpaths.groovy | 2 +- engine/processor/build.gradle | 2 + .../processor/NamedObjectProcessor.java | 94 ++ .../deephaven/processor/ObjectProcessor.java | 50 +- .../processor/ObjectProcessorRowLimited.java | 5 + .../processor/ObjectProcessorStrict.java | 15 +- .../processor/ObjectProcessorStrictTest.java | 30 + extensions/bson-jackson/build.gradle | 23 + extensions/bson-jackson/gradle.properties | 1 + .../jackson/JacksonBsonConfiguration.java | 30 + .../bson/jackson/JacksonBsonProvider.java | 33 + .../io/deephaven/bson/jackson/BsonTest.java | 39 + .../io/deephaven/bson/jackson/TestHelper.java | 166 +++ extensions/json-jackson/build.gradle | 32 + extensions/json-jackson/gradle.properties | 1 + .../io/deephaven/json/jackson/AnyMixin.java | 62 + .../io/deephaven/json/jackson/ArrayMixin.java | 183 +++ .../json/jackson/BigDecimalMixin.java | 97 ++ .../json/jackson/BigIntegerMixin.java | 107 ++ .../io/deephaven/json/jackson/BoolMixin.java | 149 +++ .../json/jackson/BoolValueProcessor.java | 38 + .../json/jackson/ByteBufferInputStream.java | 53 + .../io/deephaven/json/jackson/ByteMixin.java | 153 +++ .../json/jackson/ByteValueProcessor.java | 38 + .../io/deephaven/json/jackson/CharMixin.java | 133 ++ .../json/jackson/CharValueProcessor.java | 38 + .../deephaven/json/jackson/DoubleMixin.java | 145 +++ .../json/jackson/DoubleValueProcessor.java | 38 + .../json/jackson/FieldProcessor.java | 31 + .../io/deephaven/json/jackson/FloatMixin.java | 144 +++ .../json/jackson/FloatValueProcessor.java | 38 + .../deephaven/json/jackson/InstantMixin.java | 94 ++ .../json/jackson/InstantNumberMixin.java | 233 ++++ .../io/deephaven/json/jackson/IntMixin.java | 154 +++ .../json/jackson/IntValueProcessor.java | 38 + .../json/jackson/JacksonConfiguration.java | 55 + .../json/jackson/JacksonProvider.java | 148 +++ .../deephaven/json/jackson/JacksonSource.java | 75 ++ .../json/jackson/LocalDateMixin.java | 86 ++ .../io/deephaven/json/jackson/LongMixin.java | 108 ++ .../json/jackson/LongRepeaterImpl.java | 62 + .../json/jackson/LongValueProcessor.java | 43 + .../java/io/deephaven/json/jackson/Mixin.java | 410 ++++++ .../deephaven/json/jackson/ObjectKvMixin.java | 194 +++ .../deephaven/json/jackson/ObjectMixin.java | 421 ++++++ .../json/jackson/ObjectValueProcessor.java | 43 + .../io/deephaven/json/jackson/Parsing.java | 303 +++++ .../json/jackson/RepeaterGenericImpl.java | 59 + .../json/jackson/RepeaterProcessor.java | 29 + .../json/jackson/RepeaterProcessorBase.java | 61 + .../io/deephaven/json/jackson/ShortMixin.java | 153 +++ .../json/jackson/ShortValueProcessor.java | 38 + .../io/deephaven/json/jackson/SkipMixin.java | 145 +++ .../deephaven/json/jackson/StringMixin.java | 114 ++ .../io/deephaven/json/jackson/TupleMixin.java | 245 ++++ .../json/jackson/TypedObjectMixin.java | 256 ++++ .../json/jackson/ValueProcessor.java | 59 + .../json/jackson/ValueProcessorArrayImpl.java | 52 + .../json/jackson/ValueProcessorKvImpl.java | 62 + .../java/io/deephaven/chunk/ChunkEquals.java | 91 ++ .../java/io/deephaven/json/ArrayTest.java | 91 ++ .../deephaven/json/BoolArrayOptionsTest.java | 30 + .../io/deephaven/json/ByteOptionsTest.java | 179 +++ .../io/deephaven/json/CharOptionsTest.java | 130 ++ .../json/DoubleArrayOptionsTest.java | 31 + .../io/deephaven/json/DoubleOptionsTest.java | 118 ++ .../io/deephaven/json/FloatOptionsTest.java | 118 ++ .../json/InstantNumberOptionsTest.java | 106 ++ .../io/deephaven/json/InstantOptionsTest.java | 73 ++ .../deephaven/json/IntArrayOptionsTest.java | 29 + .../io/deephaven/json/IntOptionsTest.java | 179 +++ .../io/deephaven/json/JsonValueTypesTest.java | 45 + .../deephaven/json/LocalDateOptionsTest.java | 67 + .../deephaven/json/LongArrayOptionsTest.java | 29 + .../io/deephaven/json/LongOptionsTest.java | 170 +++ .../json/ObjectFieldOptionsTest.java | 94 ++ .../deephaven/json/ObjectKvOptionsTest.java | 72 ++ .../io/deephaven/json/ObjectOptionsTest.java | 204 +++ .../json/RepeatedProcessorTests.java | 91 ++ .../io/deephaven/json/ShortOptionsTest.java | 179 +++ .../io/deephaven/json/StringOptionsTest.java | 137 ++ .../java/io/deephaven/json/TestHelper.java | 174 +++ .../io/deephaven/json/TupleOptionsTest.java | 40 + .../json/TypedObjectOptionsTest.java | 55 + .../json/jackson/JacksonAnyOptionsTest.java | 107 ++ .../io/deephaven/json/test-array-objects.json | 10 + .../json/test-compact-objects.json.txt | 1 + .../test-double-nested-array-objects.json | 14 + .../json/test-nested-array-objects.json | 12 + .../json/test-newline-objects.json.txt | 8 + .../io/deephaven/json/test-single-object.json | 4 + extensions/json/build.gradle | 19 + extensions/json/gradle.properties | 1 + .../java/io/deephaven/json/AnyOptions.java | 42 + .../java/io/deephaven/json/ArrayOptions.java | 60 + .../io/deephaven/json/BigDecimalOptions.java | 64 + .../io/deephaven/json/BigIntegerOptions.java | 57 + .../java/io/deephaven/json/BoolOptions.java | 91 ++ .../java/io/deephaven/json/ByteOptions.java | 91 ++ .../java/io/deephaven/json/CharOptions.java | 81 ++ .../java/io/deephaven/json/DoubleOptions.java | 91 ++ .../java/io/deephaven/json/FloatOptions.java | 88 ++ .../deephaven/json/InstantNumberOptions.java | 80 ++ .../io/deephaven/json/InstantOptions.java | 83 ++ .../java/io/deephaven/json/IntOptions.java | 94 ++ .../io/deephaven/json/JsonValueTypes.java | 87 ++ .../io/deephaven/json/LocalDateOptions.java | 74 ++ .../java/io/deephaven/json/LongOptions.java | 93 ++ .../io/deephaven/json/ObjectFieldOptions.java | 154 +++ .../io/deephaven/json/ObjectKvOptions.java | 97 ++ .../java/io/deephaven/json/ObjectOptions.java | 161 +++ .../java/io/deephaven/json/ShortOptions.java | 91 ++ .../java/io/deephaven/json/SkipOptions.java | 44 + .../java/io/deephaven/json/StringOptions.java | 65 + .../java/io/deephaven/json/TupleOptions.java | 88 ++ .../io/deephaven/json/TypedObjectOptions.java | 150 +++ .../java/io/deephaven/json/ValueOptions.java | 132 ++ .../ValueOptionsRestrictedUniverseBase.java | 34 + .../json/ValueOptionsSingleValueBase.java | 48 + .../java/io/deephaven/kafka/KafkaTools.java | 57 +- .../KeyOrValueSpecObjectProcessorImpl.java | 27 +- py/server/deephaven/dtypes.py | 2 + py/server/deephaven/json/__init__.py | 1134 +++++++++++++++++ py/server/deephaven/json/jackson.py | 48 + py/server/deephaven/stream/kafka/consumer.py | 16 + server/jetty-app/build.gradle | 6 + settings.gradle | 9 + 127 files changed, 11931 insertions(+), 26 deletions(-) create mode 100644 engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java create mode 100644 extensions/bson-jackson/build.gradle create mode 100644 extensions/bson-jackson/gradle.properties create mode 100644 extensions/bson-jackson/src/main/java/io/deephaven/bson/jackson/JacksonBsonConfiguration.java create mode 100644 extensions/bson-jackson/src/main/java/io/deephaven/bson/jackson/JacksonBsonProvider.java create mode 100644 extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/BsonTest.java create mode 100644 extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java create mode 100644 extensions/json-jackson/build.gradle create mode 100644 extensions/json-jackson/gradle.properties create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolValueProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteBufferInputStream.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FieldProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonConfiguration.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorArrayImpl.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/chunk/ChunkEquals.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/ArrayTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/JsonValueTypesTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/RepeatedProcessorTests.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyOptionsTest.java create mode 100644 extensions/json-jackson/src/test/resources/io/deephaven/json/test-array-objects.json create mode 100644 extensions/json-jackson/src/test/resources/io/deephaven/json/test-compact-objects.json.txt create mode 100644 extensions/json-jackson/src/test/resources/io/deephaven/json/test-double-nested-array-objects.json create mode 100644 extensions/json-jackson/src/test/resources/io/deephaven/json/test-nested-array-objects.json create mode 100644 extensions/json-jackson/src/test/resources/io/deephaven/json/test-newline-objects.json.txt create mode 100644 extensions/json-jackson/src/test/resources/io/deephaven/json/test-single-object.json create mode 100644 extensions/json/build.gradle create mode 100644 extensions/json/gradle.properties create mode 100644 extensions/json/src/main/java/io/deephaven/json/AnyOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/BoolOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/ByteOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/CharOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/FloatOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/InstantOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/IntOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/JsonValueTypes.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/LongOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/ObjectFieldOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/ShortOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/SkipOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/StringOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/TupleOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/ValueOptions.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/ValueOptionsSingleValueBase.java create mode 100644 py/server/deephaven/json/__init__.py create mode 100644 py/server/deephaven/json/jackson.py diff --git a/buildSrc/src/main/groovy/Classpaths.groovy b/buildSrc/src/main/groovy/Classpaths.groovy index 65f1914c285..675b3968311 100644 --- a/buildSrc/src/main/groovy/Classpaths.groovy +++ b/buildSrc/src/main/groovy/Classpaths.groovy @@ -110,7 +110,7 @@ class Classpaths { static final String JACKSON_GROUP = 'com.fasterxml.jackson' static final String JACKSON_NAME = 'jackson-bom' - static final String JACKSON_VERSION = '2.14.1' + static final String JACKSON_VERSION = '2.17.0' static final String SSLCONTEXT_GROUP = 'io.github.hakky54' static final String SSLCONTEXT_VERSION = '8.1.1' diff --git a/engine/processor/build.gradle b/engine/processor/build.gradle index 6e7740b5736..e83b218b6f7 100644 --- a/engine/processor/build.gradle +++ b/engine/processor/build.gradle @@ -8,6 +8,8 @@ dependencies { api project(':qst-type') api project(':engine-chunk') + Classpaths.inheritImmutables(project) + Classpaths.inheritJUnitPlatform(project) Classpaths.inheritAssertJ(project) testImplementation 'org.junit.jupiter:junit-jupiter' diff --git a/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java b/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java new file mode 100644 index 00000000000..caf82ba980b --- /dev/null +++ b/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java @@ -0,0 +1,94 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.processor; + +import io.deephaven.annotations.BuildableStyle; +import io.deephaven.qst.type.Type; +import org.immutables.value.Value.Check; +import org.immutables.value.Value.Immutable; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@Immutable +@BuildableStyle +public abstract class NamedObjectProcessor { + + public static Builder builder() { + return ImmutableNamedObjectProcessor.builder(); + } + + public static NamedObjectProcessor of(ObjectProcessor processor, String... names) { + return NamedObjectProcessor.builder().processor(processor).addNames(names).build(); + } + + public static NamedObjectProcessor of(ObjectProcessor processor, Iterable names) { + return NamedObjectProcessor.builder().processor(processor).addAllNames(names).build(); + } + + public static NamedObjectProcessor prefix(ObjectProcessor processor, String prefix) { + final int size = processor.size(); + if (size == 1) { + return of(processor, prefix); + } + return of(processor, IntStream.range(0, size).mapToObj(ix -> prefix + "_" + ix).collect(Collectors.toList())); + } + + /** + * The name for each output of {@link #processor()}. + */ + public abstract List names(); + + /** + * The object processor. + */ + public abstract ObjectProcessor processor(); + + public interface Builder { + Builder processor(ObjectProcessor processor); + + Builder addNames(String element); + + Builder addNames(String... elements); + + Builder addAllNames(Iterable elements); + + NamedObjectProcessor build(); + } + + public interface Provider extends ObjectProcessor.Provider { + + /** + * The name for each output of the processors. Equivalent to the named processors' + * {@link NamedObjectProcessor#names()}. + * + * @return the names + */ + List names(); + + /** + * Creates a named object processor that can process the {@code inputType}. This will successfully create a + * named processor when {@code inputType} is one of, or extends from one of, {@link #inputTypes()}. Otherwise, + * an {@link IllegalArgumentException} will be thrown. Equivalent to + * {@code NamedObjectProcessor.of(processor(inputType), names())}. + * + * @param inputType the input type + * @return the object processor + * @param the input type + */ + default NamedObjectProcessor named(Type inputType) { + return NamedObjectProcessor.of(processor(inputType), names()); + } + } + + @Check + final void checkSizes() { + if (names().size() != processor().size()) { + throw new IllegalArgumentException( + String.format("Unmatched sizes; columnNames().size()=%d, processor().size()=%d", + names().size(), processor().size())); + } + } +} diff --git a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java index fcb166ec5f0..06c7d7e16f0 100644 --- a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java +++ b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java @@ -19,8 +19,8 @@ import io.deephaven.qst.type.ShortType; import io.deephaven.qst.type.Type; -import java.time.Instant; import java.util.List; +import java.util.Set; /** * An interface for processing data from one or more input objects into output chunks on a 1-to-1 input record to output @@ -141,6 +141,15 @@ static ChunkType chunkType(Type type) { return ObjectProcessorTypes.of(type); } + /** + * The number of outputs. Equivalent to {@code outputTypes().size()}. + * + * @return the number of outputs + */ + default int size() { + return outputTypes().size(); + } + /** * The logical output types {@code this} instance processes. The size and types correspond to the expected size and * {@link io.deephaven.chunk.ChunkType chunk types} for {@link #processAll(ObjectChunk, List)} as specified by @@ -168,4 +177,43 @@ static ChunkType chunkType(Type type) { * at least {@code in.size()} */ void processAll(ObjectChunk in, List> out); + + /** + * An abstraction over {@link ObjectProcessor} that provides the same logical object processor for different input + * types. + */ + interface Provider { + + /** + * The base input types for {@link #processor(Type)}. + * + * @return the input types + */ + Set> inputTypes(); + + /** + * The output types for the processors. Equivalent to the processors' {@link ObjectProcessor#outputTypes()}. + * + * @return the output types + */ + List> outputTypes(); + + /** + * The number of output types for the processors. Equivalent to the processors' {@link ObjectProcessor#size()}. + * + * @return the number of output types + */ + int size(); + + /** + * Creates an object processor that can process the {@code inputType}. This will successfully create a processor + * when {@code inputType} is one of, or extends from one of, {@link #inputTypes()}. Otherwise, an + * {@link IllegalArgumentException} will be thrown. + * + * @param inputType the input type + * @return the object processor + * @param the input type + */ + ObjectProcessor processor(Type inputType); + } } diff --git a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorRowLimited.java b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorRowLimited.java index 7dfd03f1249..6bf446b8415 100644 --- a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorRowLimited.java +++ b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorRowLimited.java @@ -48,6 +48,11 @@ int rowLimit() { return rowLimit; } + @Override + public int size() { + return delegate.size(); + } + @Override public List> outputTypes() { return delegate.outputTypes(); diff --git a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorStrict.java b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorStrict.java index fe1f69277f4..a62083d390d 100644 --- a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorStrict.java +++ b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorStrict.java @@ -27,6 +27,16 @@ static ObjectProcessor create(ObjectProcessor delegate) { ObjectProcessorStrict(ObjectProcessor delegate) { this.delegate = Objects.requireNonNull(delegate); this.outputTypes = List.copyOf(delegate.outputTypes()); + if (delegate.size() != outputTypes.size()) { + throw new IllegalArgumentException( + String.format("Inconsistent size. delegate.size()=%d, delegate.outputTypes().size()=%d", + delegate.size(), outputTypes.size())); + } + } + + @Override + public int size() { + return delegate.size(); } @Override @@ -40,12 +50,13 @@ public List> outputTypes() { @Override public void processAll(ObjectChunk in, List> out) { - final int numColumns = delegate.outputTypes().size(); + final int numColumns = delegate.size(); if (numColumns != out.size()) { throw new IllegalArgumentException(String.format( "Improper number of out chunks. Expected delegate.outputTypes().size() == out.size(). delegate.outputTypes().size()=%d, out.size()=%d", numColumns, out.size())); } + final List> delegateOutputTypes = delegate.outputTypes(); final int[] originalSizes = new int[numColumns]; for (int chunkIx = 0; chunkIx < numColumns; ++chunkIx) { final WritableChunk chunk = out.get(chunkIx); @@ -54,7 +65,7 @@ public void processAll(ObjectChunk in, List> ou "out chunk does not have enough remaining capacity. chunkIx=%d, in.size()=%d, chunk.size()=%d, chunk.capacity()=%d", chunkIx, in.size(), chunk.size(), chunk.capacity())); } - final Type type = delegate.outputTypes().get(chunkIx); + final Type type = delegateOutputTypes.get(chunkIx); final ChunkType expectedChunkType = ObjectProcessor.chunkType(type); final ChunkType actualChunkType = chunk.getChunkType(); if (expectedChunkType != actualChunkType) { diff --git a/engine/processor/src/test/java/io/deephaven/processor/ObjectProcessorStrictTest.java b/engine/processor/src/test/java/io/deephaven/processor/ObjectProcessorStrictTest.java index eb79ed25583..667b077bc50 100644 --- a/engine/processor/src/test/java/io/deephaven/processor/ObjectProcessorStrictTest.java +++ b/engine/processor/src/test/java/io/deephaven/processor/ObjectProcessorStrictTest.java @@ -172,6 +172,11 @@ public void testBadDelegateOutputTypes() { ObjectProcessor strict = ObjectProcessor.strict(new ObjectProcessor<>() { private final List> outputTypes = new ArrayList<>(List.of(Type.intType())); + @Override + public int size() { + return 1; + } + @Override public List> outputTypes() { try { @@ -220,4 +225,29 @@ public void processAll(ObjectChunk in, List> out) { } } } + + @Test + public void testBadDelegateSize() { + try { + ObjectProcessor.strict(new ObjectProcessor<>() { + @Override + public int size() { + return 2; + } + + @Override + public List> outputTypes() { + return List.of(Type.intType()); + } + + @Override + public void processAll(ObjectChunk in, List> out) { + // ignore + } + }); + failBecauseExceptionWasNotThrown(IllegalAccessException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessageContaining("Inconsistent size. delegate.size()=2, delegate.outputTypes().size()=1"); + } + } } diff --git a/extensions/bson-jackson/build.gradle b/extensions/bson-jackson/build.gradle new file mode 100644 index 00000000000..660ef1b207f --- /dev/null +++ b/extensions/bson-jackson/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'java-library' + id 'io.deephaven.project.register' +} + +dependencies { + api project(':extensions-json-jackson') + api project(':engine-processor') + api 'de.undercouch:bson4jackson:2.15.1' + + Classpaths.inheritImmutables(project) + compileOnly 'com.google.code.findbugs:jsr305:3.0.2' + + Classpaths.inheritJacksonPlatform(project, 'testImplementation') + Classpaths.inheritJUnitPlatform(project) + Classpaths.inheritAssertJ(project) + testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'com.fasterxml.jackson.core:jackson-databind' +} + +test { + useJUnitPlatform() +} diff --git a/extensions/bson-jackson/gradle.properties b/extensions/bson-jackson/gradle.properties new file mode 100644 index 00000000000..c186bbfdde1 --- /dev/null +++ b/extensions/bson-jackson/gradle.properties @@ -0,0 +1 @@ +io.deephaven.project.ProjectType=JAVA_PUBLIC diff --git a/extensions/bson-jackson/src/main/java/io/deephaven/bson/jackson/JacksonBsonConfiguration.java b/extensions/bson-jackson/src/main/java/io/deephaven/bson/jackson/JacksonBsonConfiguration.java new file mode 100644 index 00000000000..663a7e02671 --- /dev/null +++ b/extensions/bson-jackson/src/main/java/io/deephaven/bson/jackson/JacksonBsonConfiguration.java @@ -0,0 +1,30 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.bson.jackson; + +import com.fasterxml.jackson.core.ObjectCodec; +import de.undercouch.bson4jackson.BsonFactory; + +import java.lang.reflect.InvocationTargetException; + +final class JacksonBsonConfiguration { + private static final BsonFactory DEFAULT_FACTORY; + + static { + // We'll attach an ObjectMapper if it's on the classpath, this allows parsing of AnyOptions + ObjectCodec objectCodec = null; + try { + final Class clazz = Class.forName("com.fasterxml.jackson.databind.ObjectMapper"); + objectCodec = (ObjectCodec) clazz.getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException + | InvocationTargetException e) { + // ignore + } + DEFAULT_FACTORY = new BsonFactory(objectCodec); + } + + static BsonFactory defaultFactory() { + return DEFAULT_FACTORY; + } +} diff --git a/extensions/bson-jackson/src/main/java/io/deephaven/bson/jackson/JacksonBsonProvider.java b/extensions/bson-jackson/src/main/java/io/deephaven/bson/jackson/JacksonBsonProvider.java new file mode 100644 index 00000000000..b25efc604bc --- /dev/null +++ b/extensions/bson-jackson/src/main/java/io/deephaven/bson/jackson/JacksonBsonProvider.java @@ -0,0 +1,33 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.bson.jackson; + +import de.undercouch.bson4jackson.BsonFactory; +import io.deephaven.json.ValueOptions; +import io.deephaven.json.jackson.JacksonProvider; + +public final class JacksonBsonProvider { + + /** + * Creates a jackson BSON provider using a default factory. + * + * @param options the object options + * @return the jackson BSON provider + * @see #of(ValueOptions, BsonFactory) + */ + public static JacksonProvider of(ValueOptions options) { + return of(options, JacksonBsonConfiguration.defaultFactory()); + } + + /** + * Creates a jackson BSON provider using the provided {@code factory}. + * + * @param options the object options + * @param factory the jackson BSON factory + * @return the jackson BSON provider + */ + public static JacksonProvider of(ValueOptions options, BsonFactory factory) { + return JacksonProvider.of(options, factory); + } +} diff --git a/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/BsonTest.java b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/BsonTest.java new file mode 100644 index 00000000000..c333fbe23a4 --- /dev/null +++ b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/BsonTest.java @@ -0,0 +1,39 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.bson.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.undercouch.bson4jackson.BsonFactory; +import io.deephaven.chunk.IntChunk; +import io.deephaven.chunk.ObjectChunk; +import io.deephaven.json.IntOptions; +import io.deephaven.json.ObjectOptions; +import io.deephaven.json.StringOptions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static io.deephaven.bson.jackson.TestHelper.parse; + +public class BsonTest { + + private static final ObjectOptions OBJECT_NAME_AGE_FIELD = ObjectOptions.builder() + .putFields("name", StringOptions.standard()) + .putFields("age", IntOptions.standard()) + .build(); + + @Test + void bson() throws IOException { + final byte[] bsonExample = new ObjectMapper(new BsonFactory()).writeValueAsBytes(Map.of( + "name", "foo", + "age", 42)); + parse( + JacksonBsonProvider.of(OBJECT_NAME_AGE_FIELD).bytesProcessor(), + List.of(bsonExample), + ObjectChunk.chunkWrap(new String[] {"foo"}), + IntChunk.chunkWrap(new int[] {42})); + } +} diff --git a/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java new file mode 100644 index 00000000000..fe0ae12e30f --- /dev/null +++ b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java @@ -0,0 +1,166 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.bson.jackson; + +import io.deephaven.chunk.BooleanChunk; +import io.deephaven.chunk.ByteChunk; +import io.deephaven.chunk.CharChunk; +import io.deephaven.chunk.Chunk; +import io.deephaven.chunk.DoubleChunk; +import io.deephaven.chunk.FloatChunk; +import io.deephaven.chunk.IntChunk; +import io.deephaven.chunk.LongChunk; +import io.deephaven.chunk.ObjectChunk; +import io.deephaven.chunk.ShortChunk; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableObjectChunk; +import io.deephaven.chunk.attributes.Any; +import io.deephaven.processor.ObjectProcessor; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestHelper { + + public static void parse(ObjectProcessor processor, List rows, Chunk... expectedCols) + throws IOException { + final List> out = processor + .outputTypes() + .stream() + .map(ObjectProcessor::chunkType) + .map(x -> x.makeWritableChunk(rows.size())) + .collect(Collectors.toList()); + try { + assertThat(out.size()).isEqualTo(expectedCols.length); + assertThat(out.stream().map(Chunk::getChunkType).collect(Collectors.toList())) + .isEqualTo(Stream.of(expectedCols).map(Chunk::getChunkType).collect(Collectors.toList())); + for (WritableChunk wc : out) { + wc.setSize(0); + } + try (final WritableObjectChunk in = WritableObjectChunk.makeWritableChunk(rows.size())) { + int i = 0; + for (T input : rows) { + in.set(i, input); + ++i; + } + try { + processor.processAll(in, out); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + for (int i = 0; i < expectedCols.length; ++i) { + check(out.get(i), expectedCols[i]); + } + } finally { + for (WritableChunk wc : out) { + wc.close(); + } + } + } + + static void check(Chunk actual, Chunk expected) { + assertThat(actual.getChunkType()).isEqualTo(expected.getChunkType()); + assertThat(actual.size()).isEqualTo(expected.size()); + switch (actual.getChunkType()) { + case Boolean: + check(actual.asBooleanChunk(), expected.asBooleanChunk()); + break; + case Char: + check(actual.asCharChunk(), expected.asCharChunk()); + break; + case Byte: + check(actual.asByteChunk(), expected.asByteChunk()); + break; + case Short: + check(actual.asShortChunk(), expected.asShortChunk()); + break; + case Int: + check(actual.asIntChunk(), expected.asIntChunk()); + break; + case Long: + check(actual.asLongChunk(), expected.asLongChunk()); + break; + case Float: + check(actual.asFloatChunk(), expected.asFloatChunk()); + break; + case Double: + check(actual.asDoubleChunk(), expected.asDoubleChunk()); + break; + case Object: + check(actual.asObjectChunk(), expected.asObjectChunk()); + break; + default: + throw new IllegalStateException(); + } + } + + private static void check(BooleanChunk actual, BooleanChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(CharChunk actual, CharChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(ByteChunk actual, ByteChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(ShortChunk actual, ShortChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(IntChunk actual, IntChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(LongChunk actual, LongChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(FloatChunk actual, FloatChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(DoubleChunk actual, DoubleChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(ObjectChunk actual, ObjectChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } +} diff --git a/extensions/json-jackson/build.gradle b/extensions/json-jackson/build.gradle new file mode 100644 index 00000000000..0cb2595482f --- /dev/null +++ b/extensions/json-jackson/build.gradle @@ -0,0 +1,32 @@ +plugins { + id 'java-library' + id 'io.deephaven.project.register' +} + +dependencies { + Classpaths.inheritJacksonPlatform(project, 'api') + Classpaths.inheritJacksonPlatform(project, 'testImplementation') + + api 'com.fasterxml.jackson.core:jackson-core' + // https://github.com/FasterXML/jackson-core/issues/1229 + implementation 'ch.randelshofer:fastdoubleparser:1.0.0' + + api project(':extensions-json') + + implementation project(':table-api') // only needs NameValidator, might be worth refactoring? + + implementation project(':engine-query-constants') + implementation project(':engine-time') + Classpaths.inheritImmutables(project) + Classpaths.inheritAutoService(project) + compileOnly 'com.google.code.findbugs:jsr305:3.0.2' + + Classpaths.inheritJUnitPlatform(project) + Classpaths.inheritAssertJ(project) + testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'com.fasterxml.jackson.core:jackson-databind' +} + +test { + useJUnitPlatform() +} diff --git a/extensions/json-jackson/gradle.properties b/extensions/json-jackson/gradle.properties new file mode 100644 index 00000000000..c186bbfdde1 --- /dev/null +++ b/extensions/json-jackson/gradle.properties @@ -0,0 +1 @@ +io.deephaven.project.ProjectType=JAVA_PUBLIC diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java new file mode 100644 index 00000000000..797c1b4b628 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java @@ -0,0 +1,62 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.TreeNode; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.AnyOptions; +import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Stream; + +final class AnyMixin extends Mixin { + public AnyMixin(AnyOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.ofCustom(TreeNode.class)); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return ObjectValueProcessor.of(out.get(0).asWritableObjectChunk(), ToTreeNode.INSTANCE); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new RepeaterGenericImpl<>(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull, null, + null, ToTreeNode.INSTANCE, TreeNode.class, TreeNode[].class); + } + + private enum ToTreeNode implements ToObject { + INSTANCE; + + @Override + public TreeNode parseValue(JsonParser parser) throws IOException { + return parser.readValueAsTree(); + } + + @Override + public TreeNode parseMissing(JsonParser parser) { + return parser.getCodec().missingNode(); + } + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java new file mode 100644 index 00000000000..349497367fd --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java @@ -0,0 +1,183 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.attributes.Any; +import io.deephaven.json.ArrayOptions; +import io.deephaven.processor.ObjectProcessor; +import io.deephaven.qst.type.NativeArrayType; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +final class ArrayMixin extends Mixin { + + public ArrayMixin(ArrayOptions options, JsonFactory factory) { + super(factory, options); + } + + Mixin element() { + return mixin(options.element()); + } + + @Override + public int numColumns() { + return element().numColumns(); + } + + @Override + public Stream> paths() { + return element().paths(); + } + + @Override + public Stream> outputTypesImpl() { + return elementOutputTypes().map(Type::arrayType); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return innerProcessor(out); + } + + Stream> elementOutputTypes() { + return element().outputTypesImpl(); + } + + RepeaterProcessor elementRepeater(List> out) { + return element().repeaterProcessor(allowMissing(), allowNull(), out); + } + + private ValueProcessorArrayImpl innerProcessor(List> out) { + return new ValueProcessorArrayImpl(elementRepeater(out)); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + // For example: + // double (element()) + // double[] (processor()) + // double[][] (arrayProcessor()) + return new ArrayOfArrayProcessor(out, allowMissing, allowNull); + } + + final class ArrayOfArrayProcessor implements RepeaterProcessor { + private final List> out; + private final List> outerTypes; + private final boolean allowMissing; + private final boolean allowNull; + + public ArrayOfArrayProcessor(List> out, boolean allowMissing, boolean allowNull) { + this.out = Objects.requireNonNull(out); + this.outerTypes = outputTypesImpl().map(NativeArrayType::arrayType).collect(Collectors.toList()); + this.allowMissing = allowMissing; + this.allowNull = allowNull; + } + + @Override + public Context start(JsonParser parser) throws IOException { + return new ArrayOfArrayProcessorContext(); + } + + @Override + public void processNullRepeater(JsonParser parser) throws IOException { + if (!allowNull) { + throw Parsing.mismatch(parser, Object.class); + } + for (WritableChunk writableChunk : out) { + writableChunk.asWritableObjectChunk().add(null); + } + } + + @Override + public void processMissingRepeater(JsonParser parser) throws IOException { + if (!allowMissing) { + throw Parsing.mismatchMissing(parser, Object.class); + } + for (WritableChunk writableChunk : out) { + writableChunk.asWritableObjectChunk().add(null); + } + } + + final class ArrayOfArrayProcessorContext implements Context { + + private final List> innerChunks; + + private ValueProcessorArrayImpl innerProcessor; + + public ArrayOfArrayProcessorContext() { + innerChunks = outputTypesImpl() + .map(ObjectProcessor::chunkType) + .map(chunkType -> chunkType.makeWritableChunk(0)) + .collect(Collectors.toList()); + } + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + if (isPow2(index)) { + resize(index); + } + innerProcessor.processCurrentValue(parser); + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + if (isPow2(index)) { + resize(index); + } + innerProcessor.processMissing(parser); + } + + @Override + public void done(JsonParser parser, int length) throws IOException { + final int size = out.size(); + for (int i = 0; i < size; ++i) { + final WritableChunk innerChunk = innerChunks.get(i); + final Object nativeArray = copy(innerChunk, outerTypes.get(i).componentType().clazz(), length); + innerChunk.close(); + out.get(i).asWritableObjectChunk().add(nativeArray); + } + } + + private void resize(int index) { + final int size = out.size(); + for (int i = 0; i < size; i++) { + final WritableChunk innerChunk = innerChunks.get(i); + final WritableChunk resized = resizeCopy(innerChunk, index, Math.max(index * 2, index + 1)); + innerChunk.close(); + innerChunks.set(i, resized); + } + innerProcessor = innerProcessor(Collections.unmodifiableList(innerChunks)); + } + } + } + + private static WritableChunk resizeCopy(WritableChunk in, int inSize, + int outCapacity) { + final WritableChunk out = in.getChunkType().makeWritableChunk(outCapacity); + out.copyFromChunk(in, 0, 0, inSize); + out.setSize(inSize); + return out; + } + + private static Object copy(WritableChunk innerChunk, Class componentClazz, int length) { + final Object dest = Array.newInstance(componentClazz, length); + innerChunk.copyToArray(0, dest, 0, length); + return dest; + } + + private static boolean isPow2(int x) { + // true for 0, 1, 2, 4, 8, 16, ... + return (x & (x - 1)) == 0; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java new file mode 100644 index 00000000000..17a4534c80c --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java @@ -0,0 +1,97 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.BigDecimalOptions; +import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.List; +import java.util.stream.Stream; + +final class BigDecimalMixin extends Mixin implements ToObject { + + public BigDecimalMixin(BigDecimalOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.ofCustom(BigDecimal.class)); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return ObjectValueProcessor.of(out.get(0).asWritableObjectChunk(), this); + } + + @Override + public BigDecimal parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_NUMBER_INT: + case VALUE_NUMBER_FLOAT: + return parseFromNumber(parser); + case VALUE_STRING: + case FIELD_NAME: + return parseFromString(parser); + case VALUE_NULL: + return parseFromNull(parser); + } + throw Parsing.mismatch(parser, BigDecimal.class); + } + + @Override + public BigDecimal parseMissing(JsonParser parser) throws IOException { + return parseFromMissing(parser); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new RepeaterGenericImpl<>(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull, null, + null, this, BigDecimal.class, BigDecimal[].class); + } + + private BigDecimal parseFromNumber(JsonParser parser) throws IOException { + if (!allowNumberInt() && !allowDecimal()) { + throw Parsing.mismatch(parser, BigDecimal.class); + } + return Parsing.parseDecimalAsBigDecimal(parser); + } + + private BigDecimal parseFromString(JsonParser parser) throws IOException { + if (!allowString()) { + throw Parsing.mismatch(parser, BigDecimal.class); + } + return Parsing.parseStringAsBigDecimal(parser); + } + + private BigDecimal parseFromNull(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, BigDecimal.class); + } + return options.onNull().orElse(null); + } + + private BigDecimal parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, BigDecimal.class); + } + return options.onMissing().orElse(null); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java new file mode 100644 index 00000000000..9ebf1713278 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java @@ -0,0 +1,107 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.BigIntegerOptions; +import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.List; +import java.util.stream.Stream; + +final class BigIntegerMixin extends Mixin implements ToObject { + + public BigIntegerMixin(BigIntegerOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.ofCustom(BigInteger.class)); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return ObjectValueProcessor.of(out.get(0).asWritableObjectChunk(), this); + } + + @Override + public BigInteger parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_NUMBER_INT: + return parseFromInt(parser); + case VALUE_NUMBER_FLOAT: + return parseFromDecimal(parser); + case VALUE_STRING: + case FIELD_NAME: + return parseFromString(parser); + case VALUE_NULL: + return parseFromNull(parser); + } + throw Parsing.mismatch(parser, BigInteger.class); + } + + @Override + public BigInteger parseMissing(JsonParser parser) throws IOException { + return parseFromMissing(parser); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new RepeaterGenericImpl<>(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull, null, + null, this, BigInteger.class, BigInteger[].class); + } + + private BigInteger parseFromInt(JsonParser parser) throws IOException { + if (!allowNumberInt()) { + throw Parsing.mismatch(parser, BigInteger.class); + } + return parser.getBigIntegerValue(); + } + + private BigInteger parseFromDecimal(JsonParser parser) throws IOException { + if (!allowDecimal()) { + throw Parsing.mismatch(parser, BigInteger.class); + } + return parser.getBigIntegerValue(); + } + + private BigInteger parseFromString(JsonParser parser) throws IOException { + if (!allowString()) { + throw Parsing.mismatch(parser, BigInteger.class); + } + return allowDecimal() + ? Parsing.parseStringAsTruncatedBigInteger(parser) + : Parsing.parseStringAsBigInteger(parser); + } + + private BigInteger parseFromNull(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, BigInteger.class); + } + return options.onNull().orElse(null); + } + + private BigInteger parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, BigInteger.class); + } + return options.onMissing().orElse(null); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java new file mode 100644 index 00000000000..cedefa7d2f0 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java @@ -0,0 +1,149 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.BoolOptions; +import io.deephaven.json.jackson.ByteValueProcessor.ToByte; +import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; +import io.deephaven.qst.type.Type; +import io.deephaven.util.BooleanUtils; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Stream; + +final class BoolMixin extends Mixin implements ToByte { + public BoolMixin(BoolOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.booleanType().boxedType()); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return new ByteValueProcessor(out.get(0).asWritableByteChunk(), this); + } + + @Override + public byte parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_TRUE: + return BooleanUtils.TRUE_BOOLEAN_AS_BYTE; + case VALUE_FALSE: + return BooleanUtils.FALSE_BOOLEAN_AS_BYTE; + case VALUE_NULL: + return parseFromNull(parser); + case VALUE_STRING: + case FIELD_NAME: + return parseFromString(parser); + } + throw Parsing.mismatch(parser, boolean.class); + } + + @Override + public byte parseMissing(JsonParser parser) throws IOException { + return parseFromMissing(parser); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new RepeaterGenericImpl<>(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull, null, + null, new ToBoolean(), Boolean.class, Boolean[].class); + } + + final class ToBoolean implements ToObject { + @Override + public Boolean parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_TRUE: + return Boolean.TRUE; + case VALUE_FALSE: + return Boolean.FALSE; + case VALUE_NULL: + return parseFromNullBoolean(parser); + case VALUE_STRING: + case FIELD_NAME: + return parseFromStringBoolean(parser); + } + throw Parsing.mismatch(parser, boolean.class); + } + + @Override + public Boolean parseMissing(JsonParser parser) throws IOException { + return parseFromMissingBoolean(parser); + } + } + + private byte parseFromString(JsonParser parser) throws IOException { + if (!allowString()) { + throw Parsing.mismatch(parser, boolean.class); + } + if (!allowNull()) { + final byte res = Parsing.parseStringAsByteBool(parser, BooleanUtils.NULL_BOOLEAN_AS_BYTE); + if (res == BooleanUtils.NULL_BOOLEAN_AS_BYTE) { + throw Parsing.mismatch(parser, boolean.class); + } + return res; + } + return Parsing.parseStringAsByteBool(parser, BooleanUtils.booleanAsByte(options.onNull().orElse(null))); + } + + private byte parseFromNull(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, boolean.class); + } + return BooleanUtils.booleanAsByte(options.onNull().orElse(null)); + } + + private byte parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, boolean.class); + } + return BooleanUtils.booleanAsByte(options.onMissing().orElse(null)); + } + + private Boolean parseFromNullBoolean(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, Boolean.class); + } + return options.onNull().orElse(null); + } + + private Boolean parseFromMissingBoolean(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, Boolean.class); + } + return options.onMissing().orElse(null); + } + + private Boolean parseFromStringBoolean(JsonParser parser) throws IOException { + if (!allowString()) { + throw Parsing.mismatch(parser, Boolean.class); + } + if (!allowNull()) { + final Boolean result = Parsing.parseStringAsBoolean(parser, null); + if (result == null) { + throw Parsing.mismatch(parser, Boolean.class); + } + return result; + } + return Parsing.parseStringAsBoolean(parser, options.onNull().orElse(null)); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolValueProcessor.java new file mode 100644 index 00000000000..b30728c62e1 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolValueProcessor.java @@ -0,0 +1,38 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableByteChunk; + +import java.io.IOException; +import java.util.Objects; + +final class BoolValueProcessor implements ValueProcessor { + + private final WritableByteChunk out; + private final ToByte toChar; + + BoolValueProcessor(WritableByteChunk out, ToByte toByte) { + this.out = Objects.requireNonNull(out); + this.toChar = Objects.requireNonNull(toByte); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + out.add(toChar.parseValue(parser)); + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + out.add(toChar.parseMissing(parser)); + } + + interface ToByte { + + byte parseValue(JsonParser parser) throws IOException; + + byte parseMissing(JsonParser parser) throws IOException; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteBufferInputStream.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteBufferInputStream.java new file mode 100644 index 00000000000..f1705386a19 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteBufferInputStream.java @@ -0,0 +1,53 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Objects; + +final class ByteBufferInputStream extends InputStream { + + public static InputStream of(ByteBuffer buffer) { + if (buffer.hasArray()) { + return new ByteArrayInputStream(buffer.array(), buffer.position(), buffer.remaining()); + } else { + return new ByteBufferInputStream(buffer.slice()); + } + } + + private final ByteBuffer buffer; + + private ByteBufferInputStream(ByteBuffer buf) { + buffer = Objects.requireNonNull(buf); + } + + @Override + public int available() { + return buffer.remaining(); + } + + @Override + public int read() { + return buffer.hasRemaining() ? buffer.get() & 0xFF : -1; + } + + @Override + public int read(byte[] bytes, int off, int len) { + if (!buffer.hasRemaining()) { + return -1; + } + len = Math.min(len, buffer.remaining()); + buffer.get(bytes, off, len); + return len; + } + + @Override + public long skip(long n) { + n = Math.min(n, buffer.remaining()); + buffer.position(buffer.position() + (int) n); + return n; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java new file mode 100644 index 00000000000..e4f9980a613 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java @@ -0,0 +1,153 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.ArrayUtil; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.ByteOptions; +import io.deephaven.json.jackson.ByteValueProcessor.ToByte; +import io.deephaven.qst.type.Type; +import io.deephaven.util.QueryConstants; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_BYTE_ARRAY; + +final class ByteMixin extends Mixin implements ToByte { + public ByteMixin(ByteOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.byteType()); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return new ByteValueProcessor(out.get(0).asWritableByteChunk(), this); + } + + @Override + public byte parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_NUMBER_INT: + return parseFromInt(parser); + case VALUE_NUMBER_FLOAT: + return parseFromDecimal(parser); + case VALUE_STRING: + case FIELD_NAME: + return parseFromString(parser); + case VALUE_NULL: + return parseFromNull(parser); + } + throw Parsing.mismatch(parser, int.class); + } + + @Override + public byte parseMissing(JsonParser parser) throws IOException { + return parseFromMissing(parser); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new ByteRepeaterProcessorImpl(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull); + } + + final class ByteRepeaterProcessorImpl extends RepeaterProcessorBase { + + public ByteRepeaterProcessorImpl(Consumer consumer, boolean allowMissing, boolean allowNull) { + super(consumer, allowMissing, allowNull, null, null); + } + + @Override + public ByteArrayContext newContext() { + return new ByteArrayContext(); + } + + final class ByteArrayContext extends RepeaterContextBase { + private byte[] arr = EMPTY_BYTE_ARRAY; + private int len = 0; + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, ByteMixin.this.parseValue(parser)); + ++len; + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, ByteMixin.this.parseMissing(parser)); + ++len; + } + + @Override + public byte[] onDone(int length) { + if (length != len) { + throw new IllegalStateException(); + } + return arr.length == len ? arr : Arrays.copyOf(arr, len); + } + } + } + + private byte parseFromInt(JsonParser parser) throws IOException { + if (!allowNumberInt()) { + throw Parsing.mismatch(parser, byte.class); + } + return Parsing.parseIntAsByte(parser); + } + + private byte parseFromDecimal(JsonParser parser) throws IOException { + if (!allowDecimal()) { + throw Parsing.mismatch(parser, byte.class); + } + return Parsing.parseDecimalAsTruncatedByte(parser); + } + + private byte parseFromString(JsonParser parser) throws IOException { + if (!allowString()) { + throw Parsing.mismatch(parser, byte.class); + } + return allowDecimal() + ? Parsing.parseDecimalStringAsTruncatedByte(parser) + : Parsing.parseStringAsByte(parser); + } + + private byte parseFromNull(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, byte.class); + } + return options.onNull().orElse(QueryConstants.NULL_BYTE); + } + + private byte parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, byte.class); + } + return options.onMissing().orElse(QueryConstants.NULL_BYTE); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java new file mode 100644 index 00000000000..b75624b0f75 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java @@ -0,0 +1,38 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableByteChunk; + +import java.io.IOException; +import java.util.Objects; + +final class ByteValueProcessor implements ValueProcessor { + + private final WritableByteChunk out; + private final ToByte toByte; + + ByteValueProcessor(WritableByteChunk out, ToByte toByte) { + this.out = Objects.requireNonNull(out); + this.toByte = Objects.requireNonNull(toByte); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + out.add(toByte.parseValue(parser)); + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + out.add(toByte.parseMissing(parser)); + } + + interface ToByte { + + byte parseValue(JsonParser parser) throws IOException; + + byte parseMissing(JsonParser parser) throws IOException; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java new file mode 100644 index 00000000000..26c8d6c7d35 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java @@ -0,0 +1,133 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.ArrayUtil; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.CharOptions; +import io.deephaven.json.jackson.CharValueProcessor.ToChar; +import io.deephaven.qst.type.Type; +import io.deephaven.util.QueryConstants; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_CHAR_ARRAY; + +final class CharMixin extends Mixin implements ToChar { + public CharMixin(CharOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.charType()); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return new CharValueProcessor(out.get(0).asWritableCharChunk(), this); + } + + @Override + public char parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_STRING: + case FIELD_NAME: + return parseFromString(parser); + case VALUE_NULL: + return parseFromNull(parser); + } + throw Parsing.mismatch(parser, int.class); + } + + @Override + public char parseMissing(JsonParser parser) throws IOException { + return parseFromMissing(parser); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new CharRepeaterImpl(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull); + } + + final class CharRepeaterImpl extends RepeaterProcessorBase { + + public CharRepeaterImpl(Consumer consumer, boolean allowMissing, boolean allowNull) { + super(consumer, allowMissing, allowNull, null, null); + } + + @Override + public CharArrayContext newContext() { + return new CharArrayContext(); + } + + final class CharArrayContext extends RepeaterContextBase { + private char[] arr = EMPTY_CHAR_ARRAY; + private int len = 0; + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, CharMixin.this.parseValue(parser)); + ++len; + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, CharMixin.this.parseMissing(parser)); + ++len; + } + + @Override + public char[] onDone(int length) { + if (length != len) { + throw new IllegalStateException(); + } + return arr.length == len ? arr : Arrays.copyOf(arr, len); + } + } + } + + private char parseFromString(JsonParser parser) throws IOException { + if (!allowString()) { + throw Parsing.mismatch(parser, char.class); + } + return Parsing.parseStringAsChar(parser); + } + + private char parseFromNull(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, char.class); + } + return options.onNull().orElse(QueryConstants.NULL_CHAR); + } + + private char parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, char.class); + } + return options.onMissing().orElse(QueryConstants.NULL_CHAR); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java new file mode 100644 index 00000000000..83e5dba0855 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java @@ -0,0 +1,38 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableCharChunk; + +import java.io.IOException; +import java.util.Objects; + +final class CharValueProcessor implements ValueProcessor { + + private final WritableCharChunk out; + private final ToChar toChar; + + CharValueProcessor(WritableCharChunk out, ToChar toChar) { + this.out = Objects.requireNonNull(out); + this.toChar = Objects.requireNonNull(toChar); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + out.add(toChar.parseValue(parser)); + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + out.add(toChar.parseMissing(parser)); + } + + interface ToChar { + + char parseValue(JsonParser parser) throws IOException; + + char parseMissing(JsonParser parser) throws IOException; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java new file mode 100644 index 00000000000..2016aeb8bc0 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java @@ -0,0 +1,145 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.ArrayUtil; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.DoubleOptions; +import io.deephaven.json.jackson.DoubleValueProcessor.ToDouble; +import io.deephaven.qst.type.Type; +import io.deephaven.util.QueryConstants; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_DOUBLE_ARRAY; + +final class DoubleMixin extends Mixin implements ToDouble { + + public DoubleMixin(DoubleOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.doubleType()); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return new DoubleValueProcessor(out.get(0).asWritableDoubleChunk(), this); + } + + @Override + public double parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_NUMBER_INT: + case VALUE_NUMBER_FLOAT: + return parseFromNumber(parser); + case VALUE_STRING: + case FIELD_NAME: + return parseFromString(parser); + case VALUE_NULL: + return parseFromNull(parser); + } + throw Parsing.mismatch(parser, double.class); + } + + @Override + public double parseMissing(JsonParser parser) throws IOException { + return parseFromMissing(parser); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new DoubleRepeaterImpl(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull); + } + + final class DoubleRepeaterImpl extends RepeaterProcessorBase { + + public DoubleRepeaterImpl(Consumer consumer, boolean allowMissing, boolean allowNull) { + super(consumer, allowMissing, allowNull, null, null); + } + + @Override + public DoubleArrayContext newContext() { + return new DoubleArrayContext(); + } + + final class DoubleArrayContext extends RepeaterContextBase { + private double[] arr = EMPTY_DOUBLE_ARRAY; + private int len = 0; + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, DoubleMixin.this.parseValue(parser)); + ++len; + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, DoubleMixin.this.parseMissing(parser)); + ++len; + } + + @Override + public double[] onDone(int length) { + if (length != len) { + throw new IllegalStateException(); + } + return arr.length == len ? arr : Arrays.copyOf(arr, len); + } + } + } + + private double parseFromNumber(JsonParser parser) throws IOException { + if (!allowDecimal() && !allowNumberInt()) { + throw Parsing.mismatch(parser, double.class); + } + // TODO: improve after https://github.com/FasterXML/jackson-core/issues/1229 + return Parsing.parseNumberAsDouble(parser); + } + + private double parseFromString(JsonParser parser) throws IOException { + if (!allowString()) { + throw Parsing.mismatch(parser, double.class); + } + return Parsing.parseStringAsDouble(parser); + } + + private double parseFromNull(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, double.class); + } + return options.onNull().orElse(QueryConstants.NULL_DOUBLE); + } + + private double parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, double.class); + } + return options.onMissing().orElse(QueryConstants.NULL_DOUBLE); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java new file mode 100644 index 00000000000..ecc36e2a957 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java @@ -0,0 +1,38 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableDoubleChunk; + +import java.io.IOException; +import java.util.Objects; + +final class DoubleValueProcessor implements ValueProcessor { + + private final WritableDoubleChunk out; + private final ToDouble toDouble; + + DoubleValueProcessor(WritableDoubleChunk out, ToDouble toDouble) { + this.out = Objects.requireNonNull(out); + this.toDouble = Objects.requireNonNull(toDouble); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + out.add(toDouble.parseValue(parser)); + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + out.add(toDouble.parseMissing(parser)); + } + + interface ToDouble { + + double parseValue(JsonParser parser) throws IOException; + + double parseMissing(JsonParser parser) throws IOException; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FieldProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FieldProcessor.java new file mode 100644 index 00000000000..f11354212f0 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FieldProcessor.java @@ -0,0 +1,31 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +import java.io.IOException; + +import static io.deephaven.json.jackson.Parsing.assertCurrentToken; + +interface FieldProcessor { + static void processObject(JsonParser parser, FieldProcessor fieldProcess) throws IOException { + Parsing.assertCurrentToken(parser, JsonToken.START_OBJECT); + parser.nextToken(); + processFields(parser, fieldProcess); + } + + static void processFields(JsonParser parser, FieldProcessor fieldProcess) throws IOException { + while (parser.hasToken(JsonToken.FIELD_NAME)) { + final String fieldName = parser.currentName(); + parser.nextToken(); + fieldProcess.process(fieldName, parser); + parser.nextToken(); + } + assertCurrentToken(parser, JsonToken.END_OBJECT); + } + + void process(String fieldName, JsonParser parser) throws IOException; +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java new file mode 100644 index 00000000000..95fe494051f --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java @@ -0,0 +1,144 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.ArrayUtil; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.FloatOptions; +import io.deephaven.json.jackson.FloatValueProcessor.ToFloat; +import io.deephaven.qst.type.Type; +import io.deephaven.util.QueryConstants; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_FLOAT_ARRAY; + +final class FloatMixin extends Mixin implements ToFloat { + + public FloatMixin(FloatOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.floatType()); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return new FloatValueProcessor(out.get(0).asWritableFloatChunk(), this); + } + + @Override + public float parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_NUMBER_INT: + case VALUE_NUMBER_FLOAT: + return parseFromNumber(parser); + case VALUE_STRING: + case FIELD_NAME: + return parseFromString(parser); + case VALUE_NULL: + return parseFromNull(parser); + } + throw Parsing.mismatch(parser, float.class); + } + + @Override + public float parseMissing(JsonParser parser) throws IOException { + return parseFromMissing(parser); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new FloatRepeaterImpl(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull); + } + + final class FloatRepeaterImpl extends RepeaterProcessorBase { + + public FloatRepeaterImpl(Consumer consumer, boolean allowMissing, boolean allowNull) { + super(consumer, allowMissing, allowNull, null, null); + } + + @Override + public FloatArrayContext newContext() { + return new FloatArrayContext(); + } + + final class FloatArrayContext extends RepeaterContextBase { + private float[] arr = EMPTY_FLOAT_ARRAY; + private int len = 0; + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, FloatMixin.this.parseValue(parser)); + ++len; + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, FloatMixin.this.parseMissing(parser)); + ++len; + } + + @Override + public float[] onDone(int length) { + if (length != len) { + throw new IllegalStateException(); + } + return arr.length == len ? arr : Arrays.copyOf(arr, len); + } + } + } + + private float parseFromNumber(JsonParser parser) throws IOException { + if (!allowDecimal() && !allowNumberInt()) { + throw Parsing.mismatch(parser, float.class); + } + return Parsing.parseNumberAsFloat(parser); + } + + private float parseFromString(JsonParser parser) throws IOException { + if (!allowString()) { + throw Parsing.mismatch(parser, float.class); + } + return Parsing.parseStringAsFloat(parser); + } + + private float parseFromNull(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, float.class); + } + return options.onNull().orElse(QueryConstants.NULL_FLOAT); + } + + private float parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, float.class); + } + return options.onMissing().orElse(QueryConstants.NULL_FLOAT); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java new file mode 100644 index 00000000000..51f94e07c2d --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java @@ -0,0 +1,38 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableFloatChunk; + +import java.io.IOException; +import java.util.Objects; + +final class FloatValueProcessor implements ValueProcessor { + + private final WritableFloatChunk out; + private final ToFloat toFloat; + + FloatValueProcessor(WritableFloatChunk out, ToFloat toFloat) { + this.out = Objects.requireNonNull(out); + this.toFloat = Objects.requireNonNull(toFloat); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + out.add(toFloat.parseValue(parser)); + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + out.add(toFloat.parseMissing(parser)); + } + + interface ToFloat { + + float parseValue(JsonParser parser) throws IOException; + + float parseMissing(JsonParser parser) throws IOException; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java new file mode 100644 index 00000000000..424902492fa --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java @@ -0,0 +1,94 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.InstantOptions; +import io.deephaven.json.jackson.LongValueProcessor.ToLong; +import io.deephaven.qst.type.Type; +import io.deephaven.time.DateTimeUtils; + +import java.io.IOException; +import java.time.Instant; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import java.util.List; +import java.util.stream.Stream; + +final class InstantMixin extends Mixin implements ToLong { + + private final long onNull; + private final long onMissing; + + public InstantMixin(InstantOptions options, JsonFactory factory) { + super(factory, options); + onNull = DateTimeUtils.epochNanos(options.onNull().orElse(null)); + onMissing = DateTimeUtils.epochNanos(options.onMissing().orElse(null)); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.instantType()); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return LongValueProcessor.of(out.get(0).asWritableLongChunk(), this); + } + + @Override + public long parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_STRING: + case FIELD_NAME: + return parseFromString(parser); + case VALUE_NULL: + return parseFromNull(parser); + } + throw Parsing.mismatch(parser, Instant.class); + } + + @Override + public long parseMissing(JsonParser parser) throws IOException { + return parseFromMissing(parser); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new LongRepeaterImpl(this, allowMissing, allowNull, out.get(0).asWritableObjectChunk()::add); + } + + private long parseFromString(JsonParser parser) throws IOException { + final TemporalAccessor accessor = options.dateTimeFormatter().parse(Parsing.textAsCharSequence(parser)); + final long epochSeconds = accessor.getLong(ChronoField.INSTANT_SECONDS); + final int nanoOfSecond = accessor.get(ChronoField.NANO_OF_SECOND); + return epochSeconds * 1_000_000_000L + nanoOfSecond; + } + + private long parseFromNull(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, Instant.class); + } + return onNull; + } + + private long parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, Instant.class); + } + return onMissing; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java new file mode 100644 index 00000000000..8ebe1b546aa --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java @@ -0,0 +1,233 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.InstantNumberOptions; +import io.deephaven.qst.type.Type; +import io.deephaven.time.DateTimeUtils; + +import java.io.IOException; +import java.time.Instant; +import java.util.List; +import java.util.stream.Stream; + +final class InstantNumberMixin extends Mixin { + + private final long onNull; + private final long onMissing; + + public InstantNumberMixin(InstantNumberOptions options, JsonFactory factory) { + super(factory, options); + onNull = DateTimeUtils.epochNanos(options.onNull().orElse(null)); + onMissing = DateTimeUtils.epochNanos(options.onMissing().orElse(null)); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.instantType()); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return LongValueProcessor.of(out.get(0).asWritableLongChunk(), function()); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new LongRepeaterImpl(function(), allowMissing, allowNull, out.get(0).asWritableObjectChunk()::add); + } + + private LongValueProcessor.ToLong function() { + switch (options.format()) { + case EPOCH_SECONDS: + return new EpochSeconds(); + case EPOCH_MILLIS: + return new EpochMillis(); + case EPOCH_MICROS: + return new EpochMicros(); + case EPOCH_NANOS: + return new EpochNanos(); + default: + throw new IllegalStateException(); + } + } + + private abstract class Base implements LongValueProcessor.ToLong { + + abstract long parseFromInt(JsonParser parser) throws IOException; + + abstract long parseFromDecimal(JsonParser parser) throws IOException; + + abstract long parseFromString(JsonParser parser) throws IOException; + + abstract long parseFromDecimalString(JsonParser parser) throws IOException; + + @Override + public final long parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_NUMBER_INT: + if (!allowNumberInt()) { + throw Parsing.mismatch(parser, Instant.class); + } + return parseFromInt(parser); + case VALUE_NUMBER_FLOAT: + if (!allowDecimal()) { + throw Parsing.mismatch(parser, Instant.class); + } + return parseFromDecimal(parser); + case VALUE_STRING: + case FIELD_NAME: + if (!allowString()) { + throw Parsing.mismatch(parser, Instant.class); + } + return allowDecimal() + ? parseFromDecimalString(parser) + : parseFromString(parser); + case VALUE_NULL: + if (!allowNull()) { + throw Parsing.mismatch(parser, Instant.class); + } + return onNull; + } + throw Parsing.mismatch(parser, Instant.class); + } + + @Override + public final long parseMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, Instant.class); + } + return onMissing; + } + } + + // We need to parse w/ BigDecimal in the case of VALUE_NUMBER_FLOAT, otherwise we might lose accuracy + // jshell> (long)(1703292532.123456789 * 1000000000) + // $4 ==> 1703292532123456768 + // See InstantNumberOptionsTest + + private class EpochSeconds extends Base { + + private static final int SCALED = 9; + private static final int MULT = 1_000_000_000; + + private long epochNanos(long epochSeconds) { + return MULT * epochSeconds; + } + + @Override + long parseFromInt(JsonParser parser) throws IOException { + return epochNanos(Parsing.parseIntAsLong(parser)); + } + + @Override + long parseFromDecimal(JsonParser parser) throws IOException { + return Parsing.parseDecimalAsScaledTruncatedLong(parser, SCALED); + } + + @Override + long parseFromString(JsonParser parser) throws IOException { + return epochNanos(Parsing.parseStringAsLong(parser)); + } + + @Override + long parseFromDecimalString(JsonParser parser) throws IOException { + return Parsing.parseDecimalStringAsScaledTruncatedLong(parser, SCALED); + } + } + + private class EpochMillis extends Base { + private static final int SCALED = 6; + private static final int MULT = 1_000_000; + + private long epochNanos(long epochMillis) { + return MULT * epochMillis; + } + + @Override + long parseFromInt(JsonParser parser) throws IOException { + return epochNanos(Parsing.parseIntAsLong(parser)); + } + + @Override + long parseFromDecimal(JsonParser parser) throws IOException { + return Parsing.parseDecimalAsScaledTruncatedLong(parser, SCALED); + } + + @Override + long parseFromString(JsonParser parser) throws IOException { + return epochNanos(Parsing.parseStringAsLong(parser)); + } + + @Override + long parseFromDecimalString(JsonParser parser) throws IOException { + return Parsing.parseDecimalStringAsScaledTruncatedLong(parser, SCALED); + } + } + + private class EpochMicros extends Base { + private static final int SCALED = 3; + private static final int MULT = 1_000; + + private long epochNanos(long epochMicros) { + return MULT * epochMicros; + } + + @Override + long parseFromInt(JsonParser parser) throws IOException { + return epochNanos(Parsing.parseIntAsLong(parser)); + } + + @Override + long parseFromDecimal(JsonParser parser) throws IOException { + return Parsing.parseDecimalAsScaledTruncatedLong(parser, SCALED); + } + + @Override + long parseFromString(JsonParser parser) throws IOException { + return epochNanos(Parsing.parseStringAsLong(parser)); + } + + @Override + long parseFromDecimalString(JsonParser parser) throws IOException { + return Parsing.parseDecimalStringAsScaledTruncatedLong(parser, SCALED); + } + } + + private class EpochNanos extends Base { + + @Override + long parseFromInt(JsonParser parser) throws IOException { + return Parsing.parseIntAsLong(parser); + } + + @Override + long parseFromDecimal(JsonParser parser) throws IOException { + return Parsing.parseDecimalAsTruncatedLong(parser); + } + + @Override + long parseFromString(JsonParser parser) throws IOException { + return Parsing.parseStringAsLong(parser); + } + + @Override + long parseFromDecimalString(JsonParser parser) throws IOException { + return Parsing.parseDecimalStringAsTruncatedLong(parser); + } + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java new file mode 100644 index 00000000000..36893e14a9a --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -0,0 +1,154 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.ArrayUtil; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.IntOptions; +import io.deephaven.json.jackson.IntValueProcessor.ToInt; +import io.deephaven.qst.type.Type; +import io.deephaven.util.QueryConstants; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_INT_ARRAY; + +final class IntMixin extends Mixin implements ToInt { + + public IntMixin(IntOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.intType()); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return new IntValueProcessor(out.get(0).asWritableIntChunk(), this); + } + + @Override + public int parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_NUMBER_INT: + return parseFromInt(parser); + case VALUE_NUMBER_FLOAT: + return parseFromDecimal(parser); + case VALUE_STRING: + case FIELD_NAME: + return parseFromString(parser); + case VALUE_NULL: + return parseFromNull(parser); + } + throw Parsing.mismatch(parser, int.class); + } + + @Override + public int parseMissing(JsonParser parser) throws IOException { + return parseFromMissing(parser); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new IntRepeaterImpl(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull); + } + + final class IntRepeaterImpl extends RepeaterProcessorBase { + + public IntRepeaterImpl(Consumer consumer, boolean allowMissing, boolean allowNull) { + super(consumer, allowMissing, allowNull, null, null); + } + + @Override + public IntArrayContext newContext() { + return new IntArrayContext(); + } + + final class IntArrayContext extends RepeaterContextBase { + private int[] arr = EMPTY_INT_ARRAY; + private int len = 0; + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, IntMixin.this.parseValue(parser)); + ++len; + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, IntMixin.this.parseMissing(parser)); + ++len; + } + + @Override + public int[] onDone(int length) { + if (length != len) { + throw new IllegalStateException(); + } + return arr.length == len ? arr : Arrays.copyOf(arr, len); + } + } + } + + private int parseFromInt(JsonParser parser) throws IOException { + if (!allowNumberInt()) { + throw Parsing.mismatch(parser, int.class); + } + return Parsing.parseIntAsInt(parser); + } + + private int parseFromDecimal(JsonParser parser) throws IOException { + if (!allowDecimal()) { + throw Parsing.mismatch(parser, int.class); + } + return Parsing.parseDecimalAsTruncatedInt(parser); + } + + private int parseFromString(JsonParser parser) throws IOException { + if (!allowString()) { + throw Parsing.mismatch(parser, int.class); + } + return allowDecimal() + ? Parsing.parseDecimalStringAsTruncatedInt(parser) + : Parsing.parseStringAsInt(parser); + } + + private int parseFromNull(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, int.class); + } + return options.onNull().orElse(QueryConstants.NULL_INT); + } + + private int parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, int.class); + } + return options.onMissing().orElse(QueryConstants.NULL_INT); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java new file mode 100644 index 00000000000..e3d1be94cd0 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java @@ -0,0 +1,38 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableIntChunk; + +import java.io.IOException; +import java.util.Objects; + +final class IntValueProcessor implements ValueProcessor { + + private final WritableIntChunk out; + private final ToInt toInt; + + IntValueProcessor(WritableIntChunk out, ToInt toInt) { + this.out = Objects.requireNonNull(out); + this.toInt = Objects.requireNonNull(toInt); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + out.add(toInt.parseValue(parser)); + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + out.add(toInt.parseMissing(parser)); + } + + interface ToInt { + + int parseValue(JsonParser parser) throws IOException; + + int parseMissing(JsonParser parser) throws IOException; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonConfiguration.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonConfiguration.java new file mode 100644 index 00000000000..d507d0f8fcb --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonConfiguration.java @@ -0,0 +1,55 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonFactoryBuilder; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.StreamReadFeature; + +import java.lang.reflect.InvocationTargetException; + +public final class JacksonConfiguration { + + private static final JsonFactory DEFAULT_FACTORY; + + static { + // We'll attach an ObjectMapper if it's on the classpath, this allows parsing of AnyOptions + ObjectCodec objectCodec = null; + try { + final Class clazz = Class.forName("com.fasterxml.jackson.databind.ObjectMapper"); + objectCodec = (ObjectCodec) clazz.getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException + | InvocationTargetException e) { + // ignore + } + DEFAULT_FACTORY = defaultFactoryBuilder().build().setCodec(objectCodec); + } + + /** + * Constructs a Deephaven-configured json factory builder. This currently includes + * {@link StreamReadFeature#USE_FAST_DOUBLE_PARSER} and {@link StreamReadFeature#USE_FAST_BIG_NUMBER_PARSER}. The + * specific configuration may change in the future. + * + * @return the Deephaven-configured json factory builder + */ + public static JsonFactoryBuilder defaultFactoryBuilder() { + return new JsonFactoryBuilder() + .enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER) + .enable(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER); + } + + // Not currently public, but javadoc still useful to ensure internal callers don't modify. + /** + * Returns a Deephaven-configured json factory singleton. Callers should not modify the returned factory in any way. + * This has been constructed as the singleton-equivalent of {@code defaultFactoryBuilder().build()}, with an + * ObjectMapper set as the codec if it is on the classpath. + * + * @return the Deephaven-configured json factory singleton + * @see #defaultFactoryBuilder() + */ + static JsonFactory defaultFactory() { + return DEFAULT_FACTORY; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java new file mode 100644 index 00000000000..8f88f900d9c --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java @@ -0,0 +1,148 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import io.deephaven.json.ValueOptions; +import io.deephaven.processor.NamedObjectProcessor; +import io.deephaven.processor.ObjectProcessor; +import io.deephaven.qst.type.Type; + +import java.io.File; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +/** + * A specific JSON processor implementation using Jackson. + */ +public interface JacksonProvider extends NamedObjectProcessor.Provider { + + /** + * Creates a jackson provider using a default factory. Equivalent to + * {@code of(options, JacksonConfiguration.defaultFactoryBuilder().build())}. + * + * @param options the object options + * @return the jackson provider + * @see #of(ValueOptions, JsonFactory) + * @see JacksonConfiguration#defaultFactoryBuilder() + */ + static JacksonProvider of(ValueOptions options) { + return of(options, JacksonConfiguration.defaultFactory()); + } + + /** + * Creates a jackson provider using the provided {@code factory}. + * + * @param options the object options + * @param factory the jackson factory + * @return the jackson provider + */ + static JacksonProvider of(ValueOptions options, JsonFactory factory) { + return Mixin.of(options, factory); + } + + /** + * The supported types. Includes {@link String}, {@code byte[]}, {@code char[]}, {@link File}, {@link Path}, + * {@link URL}, and {@link ByteBuffer}. + * + * @return the supported types + */ + static Set> getInputTypes() { + return Set.of( + Type.stringType(), + Type.byteType().arrayType(), + Type.charType().arrayType(), + Type.ofCustom(File.class), + Type.ofCustom(Path.class), + Type.ofCustom(URL.class), + Type.ofCustom(ByteBuffer.class)); + } + + /** + * The supported types. Equivalent to {@link #getInputTypes()}. + * + * @return the supported types + */ + @Override + default Set> inputTypes() { + return getInputTypes(); + } + + /** + * Creates an object processor based on the {@code inputType} with a default {@link JsonFactory}. + * + * @param inputType the input type + * @return the object processor + * @param the input type + * @see #stringProcessor() + * @see #bytesProcessor() + * @see #charsProcessor() + * @see #fileProcessor() + * @see #pathProcessor() + * @see #urlProcessor() + * @see #byteBufferProcessor() + */ + @Override + ObjectProcessor processor(Type inputType); + + List names(Function, String> f); + + /** + * Creates a {@link String} json object processor. + * + * @return the object processor + * @see JsonFactory#createParser(String) + */ + ObjectProcessor stringProcessor(); + + /** + * Creates a {@code byte[]} json object processor. + * + * @return the object processor + * @see JsonFactory#createParser(byte[]) + */ + ObjectProcessor bytesProcessor(); + + /** + * Creates a {@code char[]} json object processor. + * + * @return the object processor + * @see JsonFactory#createParser(char[]) + */ + ObjectProcessor charsProcessor(); + + /** + * Creates a {@link File} json object processor. + * + * @return the object processor + * @see JsonFactory#createParser(File) + */ + ObjectProcessor fileProcessor(); + + /** + * Creates a {@link Path} json object processor. + * + * @return the object processor + */ + ObjectProcessor pathProcessor(); + + /** + * Creates a {@link URL} json object processor. + * + * @return the object processor + * @see JsonFactory#createParser(URL) + */ + ObjectProcessor urlProcessor(); + + /** + * Creates a {@link ByteBuffer} json object processor. + * + * @return the object processor + */ + ObjectProcessor byteBufferProcessor(); +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java new file mode 100644 index 00000000000..69633da6d47 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java @@ -0,0 +1,75 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.StreamReadFeature; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; + +final class JacksonSource { + + public static JsonParser of(JsonFactory factory, String content) throws IOException { + return factory.createParser(content); + } + + public static JsonParser of(JsonFactory factory, File file) throws IOException { + return factory.createParser(file); + } + + public static JsonParser of(JsonFactory factory, Path path) throws IOException { + // TODO: suggest jackson build this in + if (FileSystems.getDefault() == path.getFileSystem()) { + return of(factory, path.toFile()); + } + if (!factory.isEnabled(StreamReadFeature.AUTO_CLOSE_SOURCE)) { + throw new RuntimeException(String.format("Unable to create Path-based parser when '%s' is not enabled", + StreamReadFeature.AUTO_CLOSE_SOURCE)); + } + // jackson buffers internally + return factory.createParser(Files.newInputStream(path)); + } + + public static JsonParser of(JsonFactory factory, InputStream inputStream) throws IOException { + return factory.createParser(inputStream); + } + + public static JsonParser of(JsonFactory factory, URL url) throws IOException { + return factory.createParser(url); + } + + public static JsonParser of(JsonFactory factory, byte[] array, int pos, int len) throws IOException { + return factory.createParser(array, pos, len); + } + + public static JsonParser of(JsonFactory factory, ByteBuffer buffer) throws IOException { + // TODO: suggest jackson build this in + if (buffer.hasArray()) { + return of(factory, buffer.array(), buffer.position(), buffer.remaining()); + } + return of(factory, ByteBufferInputStream.of(buffer)); + } + + public static JsonParser of(JsonFactory factory, char[] array, int pos, int len) throws IOException { + return factory.createParser(array, pos, len); + } + + public static JsonParser of(JsonFactory factory, CharBuffer buffer) throws IOException { + // TODO: suggest jackson build this in + if (buffer.hasArray()) { + return of(factory, buffer.array(), buffer.position(), buffer.remaining()); + } + // We could build CharBufferReader. Surprised it's not build into JDK. + throw new RuntimeException("Only supports CharBuffer when backed by array"); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java new file mode 100644 index 00000000000..bfcd0066ba4 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java @@ -0,0 +1,86 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.LocalDateOptions; +import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.temporal.TemporalAccessor; +import java.util.List; +import java.util.stream.Stream; + +final class LocalDateMixin extends Mixin implements ToObject { + + public LocalDateMixin(LocalDateOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.ofCustom(LocalDate.class)); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return ObjectValueProcessor.of(out.get(0).asWritableObjectChunk(), this); + } + + @Override + public LocalDate parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_STRING: + case FIELD_NAME: + return parseFromString(parser); + case VALUE_NULL: + return parseFromNull(parser); + } + throw Parsing.mismatch(parser, LocalDateOptions.class); + } + + @Override + public LocalDate parseMissing(JsonParser parser) throws IOException { + return parseFromMissing(parser); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new RepeaterGenericImpl<>(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull, null, + null, this, LocalDate.class, LocalDate[].class); + } + + private LocalDate parseFromString(JsonParser parser) throws IOException { + final TemporalAccessor accessor = options.dateTimeFormatter().parse(Parsing.textAsCharSequence(parser)); + return LocalDate.from(accessor); + } + + private LocalDate parseFromNull(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, LocalDate.class); + } + return options.onNull().orElse(null); + } + + private LocalDate parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, LocalDate.class); + } + return options.onMissing().orElse(null); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java new file mode 100644 index 00000000000..56cc0fdf6f8 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java @@ -0,0 +1,108 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.LongOptions; +import io.deephaven.qst.type.Type; +import io.deephaven.util.QueryConstants; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Stream; + +final class LongMixin extends Mixin implements LongValueProcessor.ToLong { + + public LongMixin(LongOptions options, JsonFactory config) { + super(config, options); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.longType()); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return LongValueProcessor.of(out.get(0).asWritableLongChunk(), this); + } + + @Override + public long parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_NUMBER_INT: + return parseFromInt(parser); + case VALUE_NUMBER_FLOAT: + return parseFromDecimal(parser); + case VALUE_STRING: + case FIELD_NAME: + return parseFromString(parser); + case VALUE_NULL: + return parseFromNull(parser); + } + throw Parsing.mismatch(parser, long.class); + } + + @Override + public long parseMissing(JsonParser parser) throws IOException { + return parseFromMissing(parser); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new LongRepeaterImpl(this, allowMissing, allowNull, out.get(0).asWritableObjectChunk()::add); + } + + private long parseFromInt(JsonParser parser) throws IOException { + if (!allowNumberInt()) { + throw Parsing.mismatch(parser, long.class); + } + return Parsing.parseIntAsLong(parser); + } + + private long parseFromDecimal(JsonParser parser) throws IOException { + if (!allowDecimal()) { + throw Parsing.mismatch(parser, long.class); + } + // TODO: allow caller to configure between lossy long and truncated long? + return Parsing.parseDecimalAsLossyLong(parser); + } + + private long parseFromString(JsonParser parser) throws IOException { + if (!allowString()) { + throw Parsing.mismatch(parser, long.class); + } + return allowDecimal() + // TODO: allow caller to configure between lossy long and truncated long? + // ? Helpers.parseDecimalStringAsLossyLong(parser) + ? Parsing.parseDecimalStringAsTruncatedLong(parser) + : Parsing.parseStringAsLong(parser); + } + + private long parseFromNull(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, long.class); + } + return options.onNull().orElse(QueryConstants.NULL_LONG); + } + + private long parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, long.class); + } + return options.onMissing().orElse(QueryConstants.NULL_LONG); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java new file mode 100644 index 00000000000..87af3d69e14 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java @@ -0,0 +1,62 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.ArrayUtil; +import io.deephaven.json.jackson.LongValueProcessor.ToLong; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Objects; +import java.util.function.Consumer; + +import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_LONG_ARRAY; + +final class LongRepeaterImpl extends RepeaterProcessorBase { + + private final ToLong toLong; + + public LongRepeaterImpl(ToLong toLong, boolean allowMissing, boolean allowNull, + Consumer consumer) { + super(consumer, allowMissing, allowNull, null, null); + this.toLong = Objects.requireNonNull(toLong); + } + + @Override + public LongArrayContext newContext() { + return new LongArrayContext(); + } + + final class LongArrayContext extends RepeaterContextBase { + private long[] arr = EMPTY_LONG_ARRAY; + private int len = 0; + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, toLong.parseValue(parser)); + ++len; + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, toLong.parseMissing(parser)); + ++len; + } + + @Override + public long[] onDone(int length) { + if (length != len) { + throw new IllegalStateException(); + } + return arr.length == len ? arr : Arrays.copyOf(arr, len); + } + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java new file mode 100644 index 00000000000..7e29e663f89 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java @@ -0,0 +1,43 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableLongChunk; + +import java.io.IOException; +import java.util.Objects; +import java.util.function.LongConsumer; + +final class LongValueProcessor implements ValueProcessor { + + public static LongValueProcessor of(WritableLongChunk out, ToLong toLong) { + return new LongValueProcessor(out::add, toLong); + } + + private final LongConsumer out; + private final ToLong toLong; + + LongValueProcessor(LongConsumer out, ToLong toLong) { + this.out = Objects.requireNonNull(out); + this.toLong = Objects.requireNonNull(toLong); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + out.accept(toLong.parseValue(parser)); + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + out.accept(toLong.parseMissing(parser)); + } + + interface ToLong { + + long parseValue(JsonParser parser) throws IOException; + + long parseMissing(JsonParser parser) throws IOException; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java new file mode 100644 index 00000000000..4cb2915e6d3 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -0,0 +1,410 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.api.util.NameValidator; +import io.deephaven.chunk.ObjectChunk; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.AnyOptions; +import io.deephaven.json.ArrayOptions; +import io.deephaven.json.BigDecimalOptions; +import io.deephaven.json.BigIntegerOptions; +import io.deephaven.json.BoolOptions; +import io.deephaven.json.ByteOptions; +import io.deephaven.json.CharOptions; +import io.deephaven.json.DoubleOptions; +import io.deephaven.json.FloatOptions; +import io.deephaven.json.InstantNumberOptions; +import io.deephaven.json.InstantOptions; +import io.deephaven.json.IntOptions; +import io.deephaven.json.JsonValueTypes; +import io.deephaven.json.LocalDateOptions; +import io.deephaven.json.LongOptions; +import io.deephaven.json.ObjectFieldOptions; +import io.deephaven.json.ObjectKvOptions; +import io.deephaven.json.ObjectOptions; +import io.deephaven.json.ShortOptions; +import io.deephaven.json.SkipOptions; +import io.deephaven.json.StringOptions; +import io.deephaven.json.TupleOptions; +import io.deephaven.json.TypedObjectOptions; +import io.deephaven.json.ValueOptions; +import io.deephaven.processor.ObjectProcessor; +import io.deephaven.qst.type.Type; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +abstract class Mixin implements JacksonProvider { + + static final Function, String> TO_COLUMN_NAME = Mixin::toColumnName; + + public static String toColumnName(List path) { + return path.isEmpty() ? "Value" : String.join("_", path); + } + + static Mixin of(ValueOptions options, JsonFactory factory) { + return options.walk(new MixinImpl(factory)); + } + + private final JsonFactory factory; + final T options; + + Mixin(JsonFactory factory, T options) { + this.factory = Objects.requireNonNull(factory); + this.options = Objects.requireNonNull(options); + } + + @Override + public final int size() { + return numColumns(); + } + + @Override + public final List> outputTypes() { + return outputTypesImpl().collect(Collectors.toList()); + } + + @Override + public final List names() { + return names(TO_COLUMN_NAME); + } + + @Override + public final List names(Function, String> f) { + return Arrays.asList(NameValidator.legalizeColumnNames(paths().map(f).toArray(String[]::new), true)); + } + + @SuppressWarnings("unchecked") + @Override + public final ObjectProcessor processor(Type inputType) { + final Class clazz = inputType.clazz(); + if (String.class.isAssignableFrom(clazz)) { + return (ObjectProcessor) stringProcessor(); + } + if (byte[].class.isAssignableFrom(clazz)) { + return (ObjectProcessor) bytesProcessor(); + } + if (char[].class.isAssignableFrom(clazz)) { + return (ObjectProcessor) charsProcessor(); + } + if (File.class.isAssignableFrom(clazz)) { + return (ObjectProcessor) fileProcessor(); + } + if (Path.class.isAssignableFrom(clazz)) { + return (ObjectProcessor) pathProcessor(); + } + if (URL.class.isAssignableFrom(clazz)) { + return (ObjectProcessor) urlProcessor(); + } + if (ByteBuffer.class.isAssignableFrom(clazz)) { + return (ObjectProcessor) byteBufferProcessor(); + } + throw new IllegalArgumentException("Unable to create JSON processor from type " + inputType); + } + + @Override + public final ObjectProcessor stringProcessor() { + return new StringIn(); + } + + @Override + public final ObjectProcessor bytesProcessor() { + return new BytesIn(); + } + + @Override + public final ObjectProcessor charsProcessor() { + return new CharsIn(); + } + + @Override + public final ObjectProcessor fileProcessor() { + return new FileIn(); + } + + @Override + public final ObjectProcessor pathProcessor() { + return new PathIn(); + } + + @Override + public final ObjectProcessor urlProcessor() { + return new URLIn(); + } + + @Override + public final ObjectProcessor byteBufferProcessor() { + return new ByteBufferIn(); + } + + final Mixin mixin(ValueOptions options) { + return of(options, factory); + } + + abstract ValueProcessor processor(String context, List> out); + + abstract RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out); + + abstract int numColumns(); + + abstract Stream> paths(); + + abstract Stream> outputTypesImpl(); + + final boolean allowNull() { + return options.allowedTypes().contains(JsonValueTypes.NULL); + } + + final boolean allowMissing() { + return options.allowMissing(); + } + + final boolean allowString() { + return options.allowedTypes().contains(JsonValueTypes.STRING); + } + + final boolean allowNumberInt() { + return options.allowedTypes().contains(JsonValueTypes.INT); + } + + final boolean allowDecimal() { + return options.allowedTypes().contains(JsonValueTypes.DECIMAL); + } + + final boolean allowBool() { + return options.allowedTypes().contains(JsonValueTypes.BOOL); + } + + final boolean allowObject() { + return options.allowedTypes().contains(JsonValueTypes.OBJECT); + } + + final boolean allowArray() { + return options.allowedTypes().contains(JsonValueTypes.ARRAY); + } + + static List prefixWith(String prefix, List path) { + return Stream.concat(Stream.of(prefix), path.stream()).collect(Collectors.toList()); + } + + Stream> prefixWithKeys(Collection fields) { + final List>> paths = new ArrayList<>(fields.size()); + for (ObjectFieldOptions field : fields) { + final Stream> prefixedPaths = + mixin(field.options()).paths().map(x -> prefixWith(field.name(), x)); + paths.add(prefixedPaths); + } + return paths.stream().flatMap(Function.identity()); + } + + private class StringIn extends ObjectProcessorJsonValue { + @Override + protected JsonParser createParser(String in) throws IOException { + return JacksonSource.of(factory, in); + } + } + + private class BytesIn extends ObjectProcessorJsonValue { + @Override + protected JsonParser createParser(byte[] in) throws IOException { + return JacksonSource.of(factory, in, 0, in.length); + } + } + + private class ByteBufferIn extends ObjectProcessorJsonValue { + @Override + protected JsonParser createParser(ByteBuffer in) throws IOException { + return JacksonSource.of(factory, in); + } + } + + private class CharsIn extends ObjectProcessorJsonValue { + @Override + protected JsonParser createParser(char[] in) throws IOException { + return JacksonSource.of(factory, in, 0, in.length); + } + } + + private class FileIn extends ObjectProcessorJsonValue { + @Override + protected JsonParser createParser(File in) throws IOException { + return JacksonSource.of(factory, in); + } + } + + private class PathIn extends ObjectProcessorJsonValue { + @Override + protected JsonParser createParser(Path in) throws IOException { + return JacksonSource.of(factory, in); + } + } + + private class URLIn extends ObjectProcessorJsonValue { + @Override + protected JsonParser createParser(URL in) throws IOException { + return JacksonSource.of(factory, in); + } + } + + private abstract class ObjectProcessorJsonValue implements ObjectProcessor { + + protected abstract JsonParser createParser(X in) throws IOException; + + @Override + public final int size() { + return Mixin.this.size(); + } + + @Override + public final List> outputTypes() { + return Mixin.this.outputTypes(); + } + + @Override + public final void processAll(ObjectChunk in, List> out) { + try { + processAllImpl(in, out); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + void processAllImpl(ObjectChunk in, List> out) throws IOException { + final ValueProcessor valueProcessor = processor("", out); + for (int i = 0; i < in.size(); ++i) { + try (final JsonParser parser = createParser(in.get(i))) { + ValueProcessor.processFullJson(parser, valueProcessor); + } + } + } + } + + private static class MixinImpl implements ValueOptions.Visitor> { + private final JsonFactory factory; + + public MixinImpl(JsonFactory factory) { + this.factory = Objects.requireNonNull(factory); + } + + @Override + public StringMixin visit(StringOptions _string) { + return new StringMixin(_string, factory); + } + + @Override + public Mixin visit(BoolOptions _bool) { + return new BoolMixin(_bool, factory); + } + + @Override + public Mixin visit(ByteOptions _byte) { + return new ByteMixin(_byte, factory); + } + + @Override + public Mixin visit(CharOptions _char) { + return new CharMixin(_char, factory); + } + + @Override + public Mixin visit(ShortOptions _short) { + return new ShortMixin(_short, factory); + } + + @Override + public IntMixin visit(IntOptions _int) { + return new IntMixin(_int, factory); + } + + @Override + public LongMixin visit(LongOptions _long) { + return new LongMixin(_long, factory); + } + + @Override + public FloatMixin visit(FloatOptions _float) { + return new FloatMixin(_float, factory); + } + + @Override + public DoubleMixin visit(DoubleOptions _double) { + return new DoubleMixin(_double, factory); + } + + @Override + public ObjectMixin visit(ObjectOptions object) { + return new ObjectMixin(object, factory); + } + + @Override + public Mixin visit(ObjectKvOptions objectKv) { + return new ObjectKvMixin(objectKv, factory); + } + + @Override + public InstantMixin visit(InstantOptions instant) { + return new InstantMixin(instant, factory); + } + + @Override + public InstantNumberMixin visit(InstantNumberOptions instantNumber) { + return new InstantNumberMixin(instantNumber, factory); + } + + @Override + public BigIntegerMixin visit(BigIntegerOptions bigInteger) { + return new BigIntegerMixin(bigInteger, factory); + } + + @Override + public BigDecimalMixin visit(BigDecimalOptions bigDecimal) { + return new BigDecimalMixin(bigDecimal, factory); + } + + @Override + public SkipMixin visit(SkipOptions skip) { + return new SkipMixin(skip, factory); + } + + @Override + public TupleMixin visit(TupleOptions tuple) { + return new TupleMixin(tuple, factory); + } + + @Override + public TypedObjectMixin visit(TypedObjectOptions typedObject) { + return new TypedObjectMixin(typedObject, factory); + } + + @Override + public LocalDateMixin visit(LocalDateOptions localDate) { + return new LocalDateMixin(localDate, factory); + } + + @Override + public ArrayMixin visit(ArrayOptions array) { + return new ArrayMixin(array, factory); + } + + @Override + public AnyMixin visit(AnyOptions any) { + return new AnyMixin(any, factory); + } + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java new file mode 100644 index 00000000000..aa178a497a7 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java @@ -0,0 +1,194 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.attributes.Any; +import io.deephaven.json.ObjectKvOptions; +import io.deephaven.processor.ObjectProcessor; +import io.deephaven.qst.type.NativeArrayType; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +final class ObjectKvMixin extends Mixin { + + public ObjectKvMixin(ObjectKvOptions options, JsonFactory factory) { + super(factory, options); + } + + public Mixin keyMixin() { + return mixin(options.key()); + } + + public Mixin valueMixin() { + return mixin(options.value()); + } + + @Override + public Stream> outputTypesImpl() { + return keyValueOutputTypes().map(Type::arrayType); + } + + @Override + public int numColumns() { + return keyMixin().numColumns() + valueMixin().numColumns(); + } + + @Override + public Stream> paths() { + final Stream> keyPath = + keyMixin().numColumns() == 1 && keyMixin().paths().findFirst().orElseThrow().isEmpty() + ? Stream.of(List.of("Key")) + : keyMixin().paths(); + final Stream> valuePath = + valueMixin().numColumns() == 1 && valueMixin().paths().findFirst().orElseThrow().isEmpty() + ? Stream.of(List.of("Value")) + : valueMixin().paths(); + return Stream.concat(keyPath, valuePath); + } + + @Override + public ValueProcessorKvImpl processor(String context, List> out) { + return innerProcessor(out); + } + + Stream> keyValueOutputTypes() { + return Stream.concat(keyMixin().outputTypesImpl(), valueMixin().outputTypesImpl()); + } + + private ValueProcessorKvImpl innerProcessor(List> out) { + final Mixin key = keyMixin(); + final Mixin value = valueMixin(); + final List> keyColumns = out.subList(0, key.numColumns()); + final List> valueColumns = + out.subList(key.numColumns(), key.numColumns() + value.numColumns()); + final RepeaterProcessor kp = key.repeaterProcessor(allowMissing(), allowNull(), keyColumns); + final RepeaterProcessor vp = value.repeaterProcessor(allowMissing(), allowNull(), valueColumns); + return new ValueProcessorKvImpl(kp, vp); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new RepeaterImpl(out, allowMissing, allowNull); + } + + final class RepeaterImpl implements RepeaterProcessor { + private final List> out; + private final List> outerTypes; + private final boolean allowMissing; + private final boolean allowNull; + + public RepeaterImpl(List> out, boolean allowMissing, boolean allowNull) { + this.out = Objects.requireNonNull(out); + this.outerTypes = outputTypesImpl().map(NativeArrayType::arrayType).collect(Collectors.toList()); + this.allowMissing = allowMissing; + this.allowNull = allowNull; + } + + @Override + public Context start(JsonParser parser) throws IOException { + return new ContextImpl(); + } + + @Override + public void processNullRepeater(JsonParser parser) throws IOException { + if (!allowNull) { + throw Parsing.mismatch(parser, Object.class); + } + for (WritableChunk writableChunk : out) { + writableChunk.asWritableObjectChunk().add(null); + } + } + + @Override + public void processMissingRepeater(JsonParser parser) throws IOException { + if (!allowMissing) { + throw Parsing.mismatchMissing(parser, Object.class); + } + for (WritableChunk writableChunk : out) { + writableChunk.asWritableObjectChunk().add(null); + } + } + + final class ContextImpl implements Context { + + private final List> innerChunks; + + private ValueProcessorKvImpl innerProcessor; + + public ContextImpl() { + innerChunks = outputTypesImpl() + .map(ObjectProcessor::chunkType) + .map(chunkType -> chunkType.makeWritableChunk(0)) + .collect(Collectors.toList()); + } + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + if (isPow2(index)) { + resize(index); + } + innerProcessor.processCurrentValue(parser); + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + if (isPow2(index)) { + resize(index); + } + innerProcessor.processMissing(parser); + } + + @Override + public void done(JsonParser parser, int length) throws IOException { + final int size = out.size(); + for (int i = 0; i < size; ++i) { + final WritableChunk innerChunk = innerChunks.get(i); + final Object nativeArray = copy(innerChunk, outerTypes.get(i).componentType().clazz(), length); + innerChunk.close(); + out.get(i).asWritableObjectChunk().add(nativeArray); + } + } + + private void resize(int index) { + final int size = out.size(); + for (int i = 0; i < size; i++) { + final WritableChunk innerChunk = innerChunks.get(i); + final WritableChunk resized = resizeCopy(innerChunk, index, Math.max(index * 2, index + 1)); + innerChunk.close(); + innerChunks.set(i, resized); + } + innerProcessor = innerProcessor(Collections.unmodifiableList(innerChunks)); + } + } + } + + private static WritableChunk resizeCopy(WritableChunk in, int inSize, + int outCapacity) { + final WritableChunk out = in.getChunkType().makeWritableChunk(outCapacity); + out.copyFromChunk(in, 0, 0, inSize); + out.setSize(inSize); + return out; + } + + private static Object copy(WritableChunk innerChunk, Class componentClazz, int length) { + final Object dest = Array.newInstance(componentClazz, length); + innerChunk.copyToArray(0, dest, 0, length); + return dest; + } + + private static boolean isPow2(int x) { + // true for 0, 1, 2, 4, 8, 16, ... + return (x & (x - 1)) == 0; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java new file mode 100644 index 00000000000..6679ff84eac --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -0,0 +1,421 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.ObjectFieldOptions; +import io.deephaven.json.ObjectFieldOptions.RepeatedBehavior; +import io.deephaven.json.ObjectOptions; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Stream; + +final class ObjectMixin extends Mixin { + + public ObjectMixin(ObjectOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public Stream> outputTypesImpl() { + return options.fields() + .stream() + .map(ObjectFieldOptions::options) + .map(this::mixin) + .flatMap(Mixin::outputTypesImpl); + } + + @Override + public int numColumns() { + return options.fields() + .stream() + .map(ObjectFieldOptions::options) + .map(this::mixin) + .mapToInt(Mixin::numColumns) + .sum(); + } + + @Override + public Stream> paths() { + return prefixWithKeys(options.fields()); + } + + @Override + public ValueProcessor processor(String context, List> out) { + if (out.size() != numColumns()) { + throw new IllegalArgumentException(); + } + final Map processors = new LinkedHashMap<>(options.fields().size()); + int ix = 0; + for (ObjectFieldOptions field : options.fields()) { + final Mixin opts = mixin(field.options()); + final int numTypes = opts.numColumns(); + final ValueProcessor fieldProcessor = + opts.processor(context + "/" + field.name(), out.subList(ix, ix + numTypes)); + processors.put(field, fieldProcessor); + ix += numTypes; + } + if (ix != out.size()) { + throw new IllegalStateException(); + } + return processorImpl(processors); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + if (out.size() != numColumns()) { + throw new IllegalArgumentException(); + } + final Map processors = + new LinkedHashMap<>(options.fields().size()); + int ix = 0; + + for (ObjectFieldOptions field : options.fields()) { + final Mixin opts = mixin(field.options()); + final int numTypes = opts.numColumns(); + final RepeaterProcessor fieldProcessor = + opts.repeaterProcessor(allowMissing, allowNull, out.subList(ix, ix + numTypes)); + processors.put(field, fieldProcessor); + ix += numTypes; + } + if (ix != out.size()) { + throw new IllegalStateException(); + } + return new ObjectValueRepeaterProcessor(processors); + } + + private boolean allCaseSensitive() { + return options.fields().stream().allMatch(ObjectFieldOptions::caseSensitive); + } + + ObjectValueFieldProcessor processorImpl(Map fields) { + return new ObjectValueFieldProcessor(fields); + } + + final class ObjectValueFieldProcessor implements ValueProcessor { + private final Map fields; + private final Map map; + + ObjectValueFieldProcessor(Map fields) { + this.fields = fields; + this.map = allCaseSensitive() + ? new HashMap<>() + : new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + for (Entry e : fields.entrySet()) { + final ObjectFieldOptions field = e.getKey(); + map.put(field.name(), field); + for (String alias : field.aliases()) { + map.put(alias, field); + } + } + } + + private ObjectFieldOptions lookupField(String fieldName) { + final ObjectFieldOptions field = map.get(fieldName); + if (field == null) { + return null; + } + if (!field.caseSensitive()) { + return field; + } + // Need to handle the case where some fields are case-insensitive, but this one is _not_. + if (field.name().equals(fieldName) || field.aliases().contains(fieldName)) { + return field; + } + return null; + } + + private ValueProcessor processor(ObjectFieldOptions options) { + return Objects.requireNonNull(fields.get(options)); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + // see com.fasterxml.jackson.databind.JsonDeserializer.deserialize(com.fasterxml.jackson.core.JsonParser, + // com.fasterxml.jackson.databind.DeserializationContext) + // for notes on FIELD_NAME + switch (parser.currentToken()) { + case START_OBJECT: + if (parser.nextToken() == JsonToken.END_OBJECT) { + processEmptyObject(parser); + return; + } + if (!parser.hasToken(JsonToken.FIELD_NAME)) { + throw new IllegalStateException(); + } + // fall-through + case FIELD_NAME: + processObjectFields(parser); + return; + case VALUE_NULL: + processNullObject(parser); + return; + default: + throw Parsing.mismatch(parser, Object.class); + } + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, Object.class); + } + for (ValueProcessor value : fields.values()) { + value.processMissing(parser); + } + } + + private void processNullObject(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, Object.class); + } + for (ValueProcessor value : fields.values()) { + value.processCurrentValue(parser); + } + } + + private void processEmptyObject(JsonParser parser) throws IOException { + // This logic should be equivalent to processObjectFields, but where we know there are no fields + for (ValueProcessor value : fields.values()) { + value.processMissing(parser); + } + } + + private void processObjectFields(JsonParser parser) throws IOException { + final State state = new State(); + FieldProcessor.processFields(parser, state); + state.processMissing(parser); + } + + private class State implements FieldProcessor { + // Note: we could try to build a stricter implementation that doesn't use Set; if all of the fields disallow + // missing and the user knows that the data doesn't have any repeated fields, we could use a simple + // counter to ensure all field processors were invoked. + private final Set visited = new HashSet<>(fields.size()); + + @Override + public void process(String fieldName, JsonParser parser) throws IOException { + final ObjectFieldOptions field = lookupField(fieldName); + if (field == null) { + if (!options.allowUnknownFields()) { + throw new IOException( + String.format("Unexpected field '%s' and allowUnknownFields == false", fieldName)); + } + parser.skipChildren(); + } else if (visited.add(field)) { + // First time seeing field + processor(field).processCurrentValue(parser); + } else if (field.repeatedBehavior() == RepeatedBehavior.USE_FIRST) { + parser.skipChildren(); + } else { + throw new IOException( + String.format("Field '%s' has already been visited and repeatedBehavior == %s", fieldName, + field.repeatedBehavior())); + } + } + + void processMissing(JsonParser parser) throws IOException { + if (visited.size() == fields.size()) { + // All fields visited, none missing + return; + } + for (Entry e : fields.entrySet()) { + if (!visited.contains(e.getKey())) { + e.getValue().processMissing(parser); + } + } + } + } + } + + final class ObjectValueRepeaterProcessor implements RepeaterProcessor { + private final Map fields; + + public ObjectValueRepeaterProcessor(Map fields) { + this.fields = Objects.requireNonNull(fields); + } + + private Collection processors() { + return fields.values(); + } + + @Override + public Context start(JsonParser parser) throws IOException { + final Map contexts = new LinkedHashMap<>(fields.size()); + for (Entry e : fields.entrySet()) { + contexts.put(e.getKey(), e.getValue().start(parser)); + } + return new ObjectArrayContext(contexts); + } + + @Override + public void processNullRepeater(JsonParser parser) throws IOException { + for (RepeaterProcessor p : processors()) { + p.processNullRepeater(parser); + } + } + + @Override + public void processMissingRepeater(JsonParser parser) throws IOException { + for (RepeaterProcessor p : processors()) { + p.processMissingRepeater(parser); + } + } + + final class ObjectArrayContext implements Context { + private final Map contexts; + private final Map map; + + public ObjectArrayContext(Map contexts) { + this.contexts = Objects.requireNonNull(contexts); + this.map = allCaseSensitive() + ? new HashMap<>() + : new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + for (Entry e : fields.entrySet()) { + final ObjectFieldOptions field = e.getKey(); + map.put(field.name(), field); + for (String alias : field.aliases()) { + map.put(alias, field); + } + } + } + + private ObjectFieldOptions lookupField(String fieldName) { + final ObjectFieldOptions field = map.get(fieldName); + if (field == null) { + return null; + } + if (!field.caseSensitive()) { + return field; + } + // Need to handle the case where some fields are case-insensitive, but this one is _not_. + if (field.name().equals(fieldName) || field.aliases().contains(fieldName)) { + return field; + } + return null; + } + + private Context context(ObjectFieldOptions o) { + return Objects.requireNonNull(contexts.get(o)); + } + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + // see + // com.fasterxml.jackson.databind.JsonDeserializer.deserialize(com.fasterxml.jackson.core.JsonParser, + // com.fasterxml.jackson.databind.DeserializationContext) + // for notes on FIELD_NAME + switch (parser.currentToken()) { + case START_OBJECT: + if (parser.nextToken() == JsonToken.END_OBJECT) { + processEmptyObject(parser, index); + return; + } + if (!parser.hasToken(JsonToken.FIELD_NAME)) { + throw new IllegalStateException(); + } + // fall-through + case FIELD_NAME: + processObjectFields(parser, index); + return; + case VALUE_NULL: + processNullObject(parser, index); + return; + default: + throw Parsing.mismatch(parser, Object.class); + } + } + + private void processNullObject(JsonParser parser, int ix) throws IOException { + // element is null + // pass-through JsonToken.VALUE_NULL + for (Context context : contexts.values()) { + context.processElement(parser, ix); + } + } + + private void processEmptyObject(JsonParser parser, int ix) throws IOException { + // This logic should be equivalent to processObjectFields, but where we know there are no fields + for (Context context : contexts.values()) { + context.processElementMissing(parser, ix); + } + } + + private void processObjectFields(JsonParser parser, int ix) throws IOException { + final State state = new State(ix); + FieldProcessor.processFields(parser, state); + state.processMissing(parser); + } + + private class State implements FieldProcessor { + // Note: we could try to build a stricter implementation that doesn't use Set; if the user can guarantee + // that none of the fields will be missing and there won't be any repeated fields, we could use a simple + // counter to ensure all field processors were invoked. + private final Set visited = new HashSet<>(contexts.size()); + private final int ix; + + public State(int ix) { + this.ix = ix; + } + + @Override + public void process(String fieldName, JsonParser parser) throws IOException { + final ObjectFieldOptions field = lookupField(fieldName); + if (field == null) { + if (!options.allowUnknownFields()) { + throw new IOException( + String.format("Unexpected field '%s' and allowUnknownFields == false", fieldName)); + } + parser.skipChildren(); + } else if (visited.add(field)) { + // First time seeing field + context(field).processElement(parser, ix); + } else if (field.repeatedBehavior() == RepeatedBehavior.USE_FIRST) { + parser.skipChildren(); + } else { + throw new IOException( + String.format("Field '%s' has already been visited and repeatedBehavior == %s", + fieldName, field.repeatedBehavior())); + } + } + + void processMissing(JsonParser parser) throws IOException { + for (Entry e : contexts.entrySet()) { + if (!visited.contains(e.getKey())) { + e.getValue().processElementMissing(parser, ix); + } + } + } + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + for (Context context : contexts.values()) { + context.processElementMissing(parser, index); + } + } + + @Override + public void done(JsonParser parser, int length) throws IOException { + for (Context context : contexts.values()) { + context.done(parser, length); + } + } + } + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java new file mode 100644 index 00000000000..5f094750faa --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java @@ -0,0 +1,43 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableObjectChunk; + +import java.io.IOException; +import java.util.Objects; +import java.util.function.Consumer; + +final class ObjectValueProcessor implements ValueProcessor { + + public static ObjectValueProcessor of(WritableObjectChunk chunk, ToObject toObj) { + return new ObjectValueProcessor<>(chunk::add, toObj); + } + + private final Consumer out; + private final ToObject toObj; + + ObjectValueProcessor(Consumer out, ToObject toObj) { + this.out = Objects.requireNonNull(out); + this.toObj = Objects.requireNonNull(toObj); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + out.accept(toObj.parseValue(parser)); + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + out.accept(toObj.parseMissing(parser)); + } + + interface ToObject { + + T parseValue(JsonParser parser) throws IOException; + + T parseMissing(JsonParser parser) throws IOException; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java new file mode 100644 index 00000000000..8e48ef1de84 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java @@ -0,0 +1,303 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import ch.randelshofer.fastdoubleparser.JavaBigDecimalParser; +import ch.randelshofer.fastdoubleparser.JavaBigIntegerParser; +import ch.randelshofer.fastdoubleparser.JavaDoubleParser; +import ch.randelshofer.fastdoubleparser.JavaFloatParser; +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.StreamReadFeature; +import io.deephaven.util.BooleanUtils; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.CharBuffer; + +final class Parsing { + + static void assertNoCurrentToken(JsonParser parser) { + if (parser.hasCurrentToken()) { + throw new IllegalStateException( + String.format("Expected no current token. actual=%s", parser.currentToken())); + } + } + + static void assertNextToken(JsonParser parser, JsonToken expected) throws IOException { + final JsonToken actual = parser.nextToken(); + if (actual != expected) { + throw new IllegalStateException( + String.format("Unexpected next token. expected=%s, actual=%s", expected, actual)); + } + } + + static void assertCurrentToken(JsonParser parser, JsonToken expected) { + if (!parser.hasToken(expected)) { + throw new IllegalStateException( + String.format("Unexpected current token. expected=%s, actual=%s", expected, parser.currentToken())); + } + } + + static CharSequence textAsCharSequence(JsonParser parser) throws IOException { + return parser.hasTextCharacters() + ? CharBuffer.wrap(parser.getTextCharacters(), parser.getTextOffset(), parser.getTextLength()) + : parser.getText(); + } + + static class UnexpectedToken extends JsonProcessingException { + public UnexpectedToken(String msg, JsonLocation loc) { + super(msg, loc); + } + } + + static IOException mismatch(JsonParser parser, Class clazz) { + final JsonLocation location = parser.currentLocation(); + final String msg = String.format("Unexpected token '%s'", parser.currentToken()); + return new UnexpectedToken(msg, location); + } + + static IOException mismatchMissing(JsonParser parser, Class clazz) { + final JsonLocation location = parser.currentLocation(); + return new UnexpectedToken("Unexpected missing token", location); + } + + static byte parseStringAsByteBool(JsonParser parser, byte onNull) throws IOException { + final String text = parser.getText().trim(); + if ("true".equalsIgnoreCase(text)) { + return BooleanUtils.TRUE_BOOLEAN_AS_BYTE; + } + if ("false".equalsIgnoreCase(text)) { + return BooleanUtils.FALSE_BOOLEAN_AS_BYTE; + } + if ("null".equalsIgnoreCase(text)) { + return onNull; + } + throw new IOException(String.format("Unexpected string as boolean '%s'", text)); + } + + static Boolean parseStringAsBoolean(JsonParser parser, Boolean onNull) throws IOException { + final String text = parser.getText().trim(); + if ("true".equalsIgnoreCase(text)) { + return Boolean.TRUE; + } + if ("false".equalsIgnoreCase(text)) { + return Boolean.FALSE; + } + if ("null".equalsIgnoreCase(text)) { + return onNull; + } + throw new IOException(String.format("Unexpected string as boolean '%s'", text)); + } + + static char parseStringAsChar(JsonParser parser) throws IOException { + if (parser.hasTextCharacters()) { + final int textLength = parser.getTextLength(); + if (textLength != 1) { + throw new IOException( + String.format("Expected char to be string of length 1, is instead %d", textLength)); + } + return parser.getTextCharacters()[parser.getTextOffset()]; + } + final String text = parser.getText(); + if (text.length() != 1) { + throw new IOException( + String.format("Expected char to be string of length 1, is instead %d", text.length())); + } + return text.charAt(0); + } + + static byte parseIntAsByte(JsonParser parser) throws IOException { + return parser.getByteValue(); + } + + static byte parseDecimalAsTruncatedByte(JsonParser parser) throws IOException { + return parser.getByteValue(); + } + + static byte parseDecimalStringAsTruncatedByte(JsonParser parser) throws IOException { + // parse as float then cast to byte; no loss of whole number part (32 bit -> 8 bit) if in range + return (byte) Parsing.parseStringAsFloat(parser); + } + + static byte parseStringAsByte(JsonParser parser) throws IOException { + return (byte) parseStringAsInt(parser); + } + + static short parseIntAsShort(JsonParser parser) throws IOException { + return parser.getShortValue(); + } + + static short parseDecimalAsTruncatedShort(JsonParser parser) throws IOException { + return parser.getShortValue(); + } + + static short parseDecimalStringAsTruncatedShort(JsonParser parser) throws IOException { + // parse as float then cast to short; no loss of whole number part (32 bit -> 16 bit) if in range + return (short) Parsing.parseStringAsFloat(parser); + } + + static short parseStringAsShort(JsonParser parser) throws IOException { + return (short) parseStringAsInt(parser); + } + + static int parseIntAsInt(JsonParser parser) throws IOException { + return parser.getIntValue(); + } + + static int parseDecimalAsTruncatedInt(JsonParser parser) throws IOException { + // parser as double then cast to int; no loss of whole number part (64 bit -> 32 bit) + return parser.getIntValue(); + } + + static long parseIntAsLong(JsonParser parser) throws IOException { + return parser.getLongValue(); + } + + static long parseDecimalAsLossyLong(JsonParser parser) throws IOException { + // parser as double then cast to long; loses info (64 bit -> 64 bit) + return parser.getLongValue(); + } + + static long parseDecimalAsTruncatedLong(JsonParser parser) throws IOException { + // io.deephaven.json.InstantNumberOptionsTest.epochNanosDecimal fails + // return parser.getLongValue(); + return parser.getDecimalValue().longValue(); + } + + static long parseDecimalAsScaledTruncatedLong(JsonParser parser, int n) throws IOException { + return parser.getDecimalValue().scaleByPowerOfTen(n).longValue(); + } + + static String parseStringAsString(JsonParser parser) throws IOException { + return parser.getText(); + } + + static String parseIntAsString(JsonParser parser) throws IOException { + return parser.getText(); + } + + static String parseDecimalAsString(JsonParser parser) throws IOException { + return parser.getText(); + } + + static String parseBoolAsString(JsonParser parser) throws IOException { + return parser.getText(); + } + + static BigDecimal parseDecimalAsBigDecimal(JsonParser parser) throws IOException { + return parser.getDecimalValue(); + } + + static float parseNumberAsFloat(JsonParser parser) throws IOException { + // TODO: improve after https://github.com/FasterXML/jackson-core/issues/1229 + return parser.getFloatValue(); + } + + static double parseNumberAsDouble(JsonParser parser) throws IOException { + // TODO: improve after https://github.com/FasterXML/jackson-core/issues/1229 + return parser.getDoubleValue(); + } + + static int parseStringAsInt(JsonParser parser) throws IOException { + if (parser.hasTextCharacters()) { + // TODO: potential to write parseInt optimized for char[] + final int len = parser.getTextLength(); + final CharSequence cs = CharBuffer.wrap(parser.getTextCharacters(), parser.getTextOffset(), len); + return Integer.parseInt(cs, 0, len, 10); + } else { + return Integer.parseInt(parser.getText()); + } + } + + static int parseDecimalStringAsTruncatedInt(JsonParser parser) throws IOException { + // parse as double then cast to int; no loss of whole number part (64 bit -> 32 bit) + return (int) Parsing.parseStringAsDouble(parser); + } + + static long parseStringAsLong(JsonParser parser) throws IOException { + if (parser.hasTextCharacters()) { + // TODO: potential to write parseInt optimized for char[] + final int len = parser.getTextLength(); + final CharSequence cs = CharBuffer.wrap(parser.getTextCharacters(), parser.getTextOffset(), len); + return Long.parseLong(cs, 0, len, 10); + } else { + return Long.parseLong(parser.getText()); + } + } + + static long parseDecimalStringAsLossyLong(JsonParser parser) throws IOException { + // parser as double then cast to long; loses info (64 bit -> 64 bit) + return (long) parseStringAsDouble(parser); + } + + static long parseDecimalStringAsTruncatedLong(JsonParser parser) throws IOException { + // To ensure 64-bit in cases where the string is a decimal, we need BigDecimal + return parseStringAsBigDecimal(parser).longValue(); + } + + static long parseDecimalStringAsScaledTruncatedLong(JsonParser parser, int n) throws IOException { + // To ensure 64-bit in cases where the string is a decimal, we need BigDecimal + return parseStringAsBigDecimal(parser).scaleByPowerOfTen(n).longValue(); + } + + static float parseStringAsFloat(JsonParser parser) throws IOException { + // TODO: improve after https://github.com/FasterXML/jackson-core/issues/1229 + return parser.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER) + ? parseStringAsFloatFast(parser) + : Float.parseFloat(parser.getText()); + } + + static double parseStringAsDouble(JsonParser parser) throws IOException { + // TODO: improve after https://github.com/FasterXML/jackson-core/issues/1229 + return parser.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER) + ? parseStringAsDoubleFast(parser) + : Double.parseDouble(parser.getText()); + } + + static BigInteger parseStringAsBigInteger(JsonParser parser) throws IOException { + // TODO: improve after https://github.com/FasterXML/jackson-core/issues/1229 + return parser.isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER) + ? parseStringAsBigIntegerFast(parser) + : new BigInteger(parser.getText()); + } + + static BigInteger parseStringAsTruncatedBigInteger(JsonParser parser) throws IOException { + return parseStringAsBigDecimal(parser).toBigInteger(); + } + + static BigDecimal parseStringAsBigDecimal(JsonParser parser) throws IOException { + // TODO: improve after https://github.com/FasterXML/jackson-core/issues/1229 + return parser.isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER) + ? parseStringAsBigDecimalFast(parser) + : new BigDecimal(parser.getText()); + } + + private static float parseStringAsFloatFast(JsonParser p) throws IOException { + return p.hasTextCharacters() + ? JavaFloatParser.parseFloat(p.getTextCharacters(), p.getTextOffset(), p.getTextLength()) + : JavaFloatParser.parseFloat(p.getText()); + } + + private static double parseStringAsDoubleFast(JsonParser p) throws IOException { + return p.hasTextCharacters() + ? JavaDoubleParser.parseDouble(p.getTextCharacters(), p.getTextOffset(), p.getTextLength()) + : JavaDoubleParser.parseDouble(p.getText()); + } + + private static BigInteger parseStringAsBigIntegerFast(JsonParser p) throws IOException { + return p.hasTextCharacters() + ? JavaBigIntegerParser.parseBigInteger(p.getTextCharacters(), p.getTextOffset(), p.getTextLength()) + : JavaBigIntegerParser.parseBigInteger(p.getText()); + } + + private static BigDecimal parseStringAsBigDecimalFast(JsonParser p) throws IOException { + return p.hasTextCharacters() + ? JavaBigDecimalParser.parseBigDecimal(p.getTextCharacters(), p.getTextOffset(), p.getTextLength()) + : JavaBigDecimalParser.parseBigDecimal(p.getText()); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java new file mode 100644 index 00000000000..b7b0badb120 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java @@ -0,0 +1,59 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.ArrayUtil; +import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Objects; +import java.util.function.Consumer; + +final class RepeaterGenericImpl extends RepeaterProcessorBase { + private final ToObject toObject; + private final Class componentClazz; + private final Class arrayClazz; + + public RepeaterGenericImpl(Consumer consumer, boolean allowMissing, boolean allowNull, + T[] onMissing, T[] onNull, ToObject toObject, Class componentClazz, Class arrayClazz) { + super(consumer, allowMissing, allowNull, onMissing, onNull); + this.toObject = Objects.requireNonNull(toObject); + this.componentClazz = Objects.requireNonNull(componentClazz); + this.arrayClazz = Objects.requireNonNull(arrayClazz); + } + + @Override + public GenericRepeaterContext newContext() { + return new GenericRepeaterContext(); + } + + final class GenericRepeaterContext extends RepeaterContextBase { + @SuppressWarnings("unchecked") + private T[] arr = (T[]) Array.newInstance(componentClazz, 0); + private int len; + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + arr = ArrayUtil.put(arr, index, toObject.parseValue(parser), componentClazz); + ++len; + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + arr = ArrayUtil.put(arr, index, toObject.parseMissing(parser), componentClazz); + ++len; + } + + @Override + public T[] onDone(int length) { + if (length != len) { + throw new IllegalStateException(); + } + return len == arr.length ? arr : Arrays.copyOf(arr, len, arrayClazz); + } + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java new file mode 100644 index 00000000000..a2d714120f0 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java @@ -0,0 +1,29 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; + +import java.io.IOException; + +// generalized for array vs object-kv +interface RepeaterProcessor { + + Context start(JsonParser parser) throws IOException; + + void processNullRepeater(JsonParser parser) throws IOException; + + void processMissingRepeater(JsonParser parser) throws IOException; + + interface Context { + + void processElement(JsonParser parser, int index) throws IOException; + + // While a traditional arrays can't have missing elements, when an object is an array, a field may be missing: + // [ { "foo": 1, "bar": 2 }, {"bar": 3} ] + void processElementMissing(JsonParser parser, int index) throws IOException; + + void done(JsonParser parser, int length) throws IOException; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java new file mode 100644 index 00000000000..d437474b615 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java @@ -0,0 +1,61 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; + +import java.io.IOException; +import java.util.Objects; +import java.util.function.Consumer; + +abstract class RepeaterProcessorBase implements RepeaterProcessor { + + private final Consumer consumer; + private final boolean allowMissing; + private final boolean allowNull; + private final T onMissing; + private final T onNull; + + public RepeaterProcessorBase(Consumer consumer, boolean allowMissing, boolean allowNull, T onMissing, + T onNull) { + this.consumer = Objects.requireNonNull(consumer); + this.onMissing = onMissing; + this.onNull = onNull; + this.allowNull = allowNull; + this.allowMissing = allowMissing; + } + + public abstract RepeaterContextBase newContext(); + + @Override + public final Context start(JsonParser parser) throws IOException { + return newContext(); + } + + @Override + public final void processMissingRepeater(JsonParser parser) throws IOException { + if (!allowMissing) { + throw Parsing.mismatchMissing(parser, void.class); + } + consumer.accept(onMissing); + } + + @Override + public final void processNullRepeater(JsonParser parser) throws IOException { + if (!allowNull) { + throw Parsing.mismatch(parser, void.class); + } + consumer.accept(onNull); + } + + public abstract class RepeaterContextBase implements Context { + + @Override + public final void done(JsonParser parser, int length) throws IOException { + consumer.accept(onDone(length)); + } + + public abstract T onDone(int length); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java new file mode 100644 index 00000000000..26a5a984c77 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java @@ -0,0 +1,153 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.ArrayUtil; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.ShortOptions; +import io.deephaven.json.jackson.ShortValueProcessor.ToShort; +import io.deephaven.qst.type.Type; +import io.deephaven.util.QueryConstants; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_SHORT_ARRAY; + +final class ShortMixin extends Mixin implements ToShort { + public ShortMixin(ShortOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.shortType()); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return new ShortValueProcessor(out.get(0).asWritableShortChunk(), this); + } + + @Override + public short parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_NUMBER_INT: + return parseFromInt(parser); + case VALUE_NUMBER_FLOAT: + return parseFromDecimal(parser); + case VALUE_STRING: + case FIELD_NAME: + return parseFromString(parser); + case VALUE_NULL: + return parseFromNull(parser); + } + throw Parsing.mismatch(parser, int.class); + } + + @Override + public short parseMissing(JsonParser parser) throws IOException { + return parseFromMissing(parser); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new ShortRepeaterImpl(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull); + } + + final class ShortRepeaterImpl extends RepeaterProcessorBase { + + public ShortRepeaterImpl(Consumer consumer, boolean allowMissing, boolean allowNull) { + super(consumer, allowMissing, allowNull, null, null); + } + + @Override + public ShortArrayContext newContext() { + return new ShortArrayContext(); + } + + final class ShortArrayContext extends RepeaterContextBase { + private short[] arr = EMPTY_SHORT_ARRAY; + private int len = 0; + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, ShortMixin.this.parseValue(parser)); + ++len; + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + if (index != len) { + throw new IllegalStateException(); + } + arr = ArrayUtil.put(arr, len, ShortMixin.this.parseMissing(parser)); + ++len; + } + + @Override + public short[] onDone(int length) { + if (length != len) { + throw new IllegalStateException(); + } + return arr.length == len ? arr : Arrays.copyOf(arr, len); + } + } + } + + private short parseFromInt(JsonParser parser) throws IOException { + if (!allowNumberInt()) { + throw Parsing.mismatch(parser, short.class); + } + return Parsing.parseIntAsShort(parser); + } + + private short parseFromDecimal(JsonParser parser) throws IOException { + if (!allowDecimal()) { + throw Parsing.mismatch(parser, short.class); + } + return Parsing.parseDecimalAsTruncatedShort(parser); + } + + private short parseFromString(JsonParser parser) throws IOException { + if (!allowString()) { + throw Parsing.mismatch(parser, short.class); + } + return allowDecimal() + ? Parsing.parseDecimalStringAsTruncatedShort(parser) + : Parsing.parseStringAsShort(parser); + } + + private short parseFromNull(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, short.class); + } + return options.onNull().orElse(QueryConstants.NULL_SHORT); + } + + private short parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, short.class); + } + return options.onMissing().orElse(QueryConstants.NULL_SHORT); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java new file mode 100644 index 00000000000..48287655f4d --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java @@ -0,0 +1,38 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableShortChunk; + +import java.io.IOException; +import java.util.Objects; + +final class ShortValueProcessor implements ValueProcessor { + + private final WritableShortChunk out; + private final ToShort toShort; + + ShortValueProcessor(WritableShortChunk out, ToShort toShort) { + this.out = Objects.requireNonNull(out); + this.toShort = Objects.requireNonNull(toShort); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + out.add(toShort.parseValue(parser)); + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + out.add(toShort.parseMissing(parser)); + } + + interface ToShort { + + short parseValue(JsonParser parser) throws IOException; + + short parseMissing(JsonParser parser) throws IOException; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java new file mode 100644 index 00000000000..663646f4dd4 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java @@ -0,0 +1,145 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.SkipOptions; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Stream; + +final class SkipMixin extends Mixin implements ValueProcessor, RepeaterProcessor.Context { + + public SkipMixin(SkipOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 0; + } + + @Override + public Stream> paths() { + return Stream.empty(); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.empty(); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return this; + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new SkipArray(allowMissing, allowNull); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case START_OBJECT: + if (!allowObject()) { + throw Parsing.mismatch(parser, void.class); + } + parser.skipChildren(); + break; + case START_ARRAY: + if (!allowArray()) { + throw Parsing.mismatch(parser, void.class); + } + parser.skipChildren(); + break; + case VALUE_STRING: + case FIELD_NAME: + if (!allowString()) { + throw Parsing.mismatch(parser, void.class); + } + break; + case VALUE_NUMBER_INT: + if (!allowNumberInt()) { + throw Parsing.mismatch(parser, void.class); + } + break; + case VALUE_NUMBER_FLOAT: + if (!allowDecimal()) { + throw Parsing.mismatch(parser, void.class); + } + break; + case VALUE_TRUE: + case VALUE_FALSE: + if (!allowBool()) { + throw Parsing.mismatch(parser, void.class); + } + break; + case VALUE_NULL: + if (!allowNull()) { + throw Parsing.mismatch(parser, void.class); + } + break; + default: + throw Parsing.mismatch(parser, void.class); + } + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, void.class); + } + } + + private final class SkipArray implements RepeaterProcessor { + private final boolean allowMissing; + private final boolean allowNull; + + public SkipArray(boolean allowMissing, boolean allowNull) { + this.allowMissing = allowMissing; + this.allowNull = allowNull; + } + + @Override + public Context start(JsonParser parser) throws IOException { + return SkipMixin.this; + } + + @Override + public void processNullRepeater(JsonParser parser) throws IOException { + if (!allowNull) { + throw Parsing.mismatch(parser, void.class); + } + } + + @Override + public void processMissingRepeater(JsonParser parser) throws IOException { + if (!allowMissing) { + throw Parsing.mismatch(parser, void.class); + } + } + } + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + processCurrentValue(parser); + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + processMissing(parser); + } + + @Override + public void done(JsonParser parser, int length) throws IOException { + // no-op + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java new file mode 100644 index 00000000000..d5a40386a74 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java @@ -0,0 +1,114 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.StringOptions; +import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Stream; + +final class StringMixin extends Mixin implements ToObject { + + public StringMixin(StringOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 1; + } + + @Override + public Stream> paths() { + return Stream.of(List.of()); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.of(Type.stringType()); + } + + @Override + public ValueProcessor processor(String context, List> out) { + return ObjectValueProcessor.of(out.get(0).asWritableObjectChunk(), this); + } + + @Override + public String parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_STRING: + case FIELD_NAME: + return parseFromString(parser); + case VALUE_NUMBER_INT: + return parseFromInt(parser); + case VALUE_NUMBER_FLOAT: + return parseFromDecimal(parser); + case VALUE_TRUE: + case VALUE_FALSE: + return parseFromBool(parser); + case VALUE_NULL: + return parseFromNull(parser); + } + throw Parsing.mismatch(parser, String.class); + } + + @Override + public String parseMissing(JsonParser parser) throws IOException { + return parseFromMissing(parser); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + return new RepeaterGenericImpl<>(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull, null, + null, this, String.class, String[].class); + } + + private String parseFromString(JsonParser parser) throws IOException { + if (!allowString()) { + throw Parsing.mismatch(parser, String.class); + } + return Parsing.parseStringAsString(parser); + } + + private String parseFromInt(JsonParser parser) throws IOException { + if (!allowNumberInt()) { + throw Parsing.mismatch(parser, String.class); + } + return Parsing.parseIntAsString(parser); + } + + private String parseFromDecimal(JsonParser parser) throws IOException { + if (!allowDecimal()) { + throw Parsing.mismatch(parser, String.class); + } + return Parsing.parseDecimalAsString(parser); + } + + private String parseFromBool(JsonParser parser) throws IOException { + if (!allowBool()) { + throw Parsing.mismatch(parser, String.class); + } + return Parsing.parseBoolAsString(parser); + } + + private String parseFromNull(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, String.class); + } + return options.onNull().orElse(null); + } + + private String parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, String.class); + } + return options.onMissing().orElse(null); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java new file mode 100644 index 00000000000..33a0cd4c369 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java @@ -0,0 +1,245 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.TupleOptions; +import io.deephaven.json.ValueOptions; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Stream; + +import static io.deephaven.json.jackson.Parsing.assertCurrentToken; + +final class TupleMixin extends Mixin { + + public TupleMixin(TupleOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return options.namedValues().values().stream().map(this::mixin).mapToInt(Mixin::numColumns).sum(); + } + + @Override + public Stream> paths() { + if (options.namedValues().size() == 1) { + return mixin(options.namedValues().get(0)).paths(); + } + final List>> prefixed = new ArrayList<>(); + for (Entry e : options.namedValues().entrySet()) { + prefixed.add(mixin(e.getValue()).paths().map(x -> prefixWith(e.getKey(), x))); + } + return prefixed.stream().flatMap(Function.identity()); + } + + @Override + public Stream> outputTypesImpl() { + return options.namedValues().values().stream().map(this::mixin).flatMap(Mixin::outputTypesImpl); + } + + @Override + public ValueProcessor processor(String context, List> out) { + if (out.size() != numColumns()) { + throw new IllegalArgumentException(); + } + final List processors = new ArrayList<>(options.namedValues().size()); + int ix = 0; + for (Entry e : options.namedValues().entrySet()) { + final Mixin mixin = mixin(e.getValue()); + final int numTypes = mixin.numColumns(); + final ValueProcessor processor = + mixin.processor(context + "[" + e.getKey() + "]", out.subList(ix, ix + numTypes)); + processors.add(processor); + ix += numTypes; + } + if (ix != out.size()) { + throw new IllegalStateException(); + } + return new TupleProcessor(processors); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + if (out.size() != numColumns()) { + throw new IllegalArgumentException(); + } + final List processors = new ArrayList<>(options.namedValues().size()); + int ix = 0; + for (Entry e : options.namedValues().entrySet()) { + final Mixin mixin = mixin(e.getValue()); + final int numTypes = mixin.numColumns(); + final RepeaterProcessor processor = + mixin.repeaterProcessor(allowMissing, allowNull, out.subList(ix, ix + numTypes)); + processors.add(processor); + ix += numTypes; + } + if (ix != out.size()) { + throw new IllegalStateException(); + } + return new TupleArrayProcessor(processors); + } + + private class TupleProcessor implements ValueProcessor { + private final List values; + + public TupleProcessor(List values) { + this.values = Objects.requireNonNull(values); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case START_ARRAY: + processTuple(parser); + return; + case VALUE_NULL: + processNullTuple(parser); + return; + default: + throw Parsing.mismatch(parser, Object.class); + } + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + parseFromMissing(parser); + } + + private void processTuple(JsonParser parser) throws IOException { + for (ValueProcessor value : values) { + parser.nextToken(); + value.processCurrentValue(parser); + } + parser.nextToken(); + assertCurrentToken(parser, JsonToken.END_ARRAY); + } + + private void processNullTuple(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, Object.class); + } + // Note: we are treating a null tuple the same as a tuple of null objects + // null ~= [null, ..., null] + for (ValueProcessor value : values) { + value.processCurrentValue(parser); + } + } + + private void parseFromMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, Object.class); + } + // Note: we are treating a missing tuple the same as a tuple of missing objects (which, is technically + // impossible w/ native json, but it's the semantics we are exposing). + // ~= [, ..., ] + for (ValueProcessor value : values) { + value.processMissing(parser); + } + } + } + + final class TupleArrayProcessor implements RepeaterProcessor { + private final List values; + + public TupleArrayProcessor(List values) { + this.values = Objects.requireNonNull(values); + } + + @Override + public TupleArrayContext start(JsonParser parser) throws IOException { + final List contexts = new ArrayList<>(values.size()); + for (RepeaterProcessor value : values) { + contexts.add(value.start(parser)); + } + return new TupleArrayContext(contexts); + } + + @Override + public void processNullRepeater(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatch(parser, Object.class); + } + for (RepeaterProcessor value : values) { + value.processNullRepeater(parser); + } + } + + @Override + public void processMissingRepeater(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, Object.class); + } + for (RepeaterProcessor value : values) { + value.processMissingRepeater(parser); + } + } + + final class TupleArrayContext implements Context { + private final List contexts; + + public TupleArrayContext(List contexts) { + this.contexts = Objects.requireNonNull(contexts); + } + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + switch (parser.currentToken()) { + case START_ARRAY: + processTuple(parser, index); + return; + case VALUE_NULL: + processNullTuple(parser, index); + return; + default: + throw Parsing.mismatch(parser, Object.class); + } + } + + private void processTuple(JsonParser parser, int index) throws IOException { + for (Context context : contexts) { + parser.nextToken(); + context.processElement(parser, index); + } + parser.nextToken(); + assertCurrentToken(parser, JsonToken.END_ARRAY); + } + + private void processNullTuple(JsonParser parser, int index) throws IOException { + // Note: we are treating a null tuple the same as a tuple of null objects + // null ~= [null, ..., null] + for (Context context : contexts) { + context.processElement(parser, index); + } + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + // Note: we are treating a missing tuple the same as a tuple of missing objects (which, is technically + // impossible w/ native json, but it's the semantics we are exposing). + // ~= [, ..., ] + for (Context context : contexts) { + context.processElementMissing(parser, index); + } + } + + @Override + public void done(JsonParser parser, int length) throws IOException { + for (Context context : contexts) { + context.done(parser, length); + } + } + } + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java new file mode 100644 index 00000000000..42cb8f70bbc --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java @@ -0,0 +1,256 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableObjectChunk; +import io.deephaven.json.ObjectFieldOptions; +import io.deephaven.json.ObjectOptions; +import io.deephaven.json.TypedObjectOptions; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +final class TypedObjectMixin extends Mixin { + public TypedObjectMixin(TypedObjectOptions options, JsonFactory factory) { + super(factory, options); + } + + @Override + public int numColumns() { + return 1 + + options.sharedFields() + .stream() + .map(ObjectFieldOptions::options) + .map(this::mixin) + .mapToInt(Mixin::numColumns) + .sum() + + options.objects() + .values() + .stream() + .map(this::mixin) + .mapToInt(Mixin::numColumns) + .sum(); + } + + @Override + public Stream> paths() { + return Stream.concat( + Stream.of(List.of(options.typeFieldName())), + Stream.concat( + prefixWithKeys(options.sharedFields()), + prefixWithKeys(options.objects().values().stream().map(ObjectOptions::fields) + .flatMap(Collection::stream).collect(Collectors.toList())))); + } + + @Override + public Stream> outputTypesImpl() { + return Stream.concat( + Stream.of(Type.stringType()), + Stream.concat( + options.sharedFields().stream().map(ObjectFieldOptions::options).map(this::mixin) + .flatMap(Mixin::outputTypesImpl), + options.objects().values().stream().map(this::mixin).flatMap(Mixin::outputTypesImpl))); + } + + @Override + public ValueProcessor processor(String context, List> out) { + final WritableObjectChunk typeOut = out.get(0).asWritableObjectChunk(); + final List> sharedFields = out.subList(1, 1 + options.sharedFields().size()); + final Map processors = new LinkedHashMap<>(options.objects().size()); + int outIx = 1 + sharedFields.size(); + for (Entry e : options.objects().entrySet()) { + final String type = e.getKey(); + final ObjectOptions specificOpts = e.getValue(); + final int numSpecificFields = mixin(specificOpts).numColumns(); + final List> specificChunks = out.subList(outIx, outIx + numSpecificFields); + final List> allChunks = concat(sharedFields, specificChunks); + final ObjectOptions combinedObject = combinedObject(specificOpts); + final ValueProcessor processor = mixin(combinedObject).processor(context + "[" + type + "]", allChunks); + processors.put(type, new Processor(processor, specificChunks)); + outIx += numSpecificFields; + } + if (outIx != out.size()) { + throw new IllegalStateException(); + } + return new DiscriminatedProcessor(typeOut, processors); + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + throw new UnsupportedOperationException(); + } + + private static List concat(List x, List y) { + if (x.isEmpty()) { + return y; + } + if (y.isEmpty()) { + return x; + } + final List out = new ArrayList<>(x.size() + y.size()); + out.addAll(x); + out.addAll(y); + return out; + } + + private ObjectOptions combinedObject(ObjectOptions objectOpts) { + final Set sharedFields = options.sharedFields(); + if (sharedFields.isEmpty()) { + return objectOpts; + } + return ObjectOptions.builder() + .allowUnknownFields(objectOpts.allowUnknownFields()) + .allowMissing(objectOpts.allowMissing()) + .allowedTypes(objectOpts.allowedTypes()) + .addAllFields(sharedFields) + .addAllFields(objectOpts.fields()) + .build(); + } + + private String parseTypeField(JsonParser parser) throws IOException { + final String currentFieldName = parser.currentName(); + if (!options.typeFieldName().equals(currentFieldName)) { + throw new IOException("Can only process when first field in object is the type"); + } + switch (parser.nextToken()) { + case VALUE_STRING: + case FIELD_NAME: + return parser.getText(); + case VALUE_NULL: + if (!allowNull()) { + throw Parsing.mismatch(parser, String.class); + } + return null; + default: + throw Parsing.mismatch(parser, String.class); + } + } + + private static class Processor { + private final ValueProcessor valueProcessor; + private final List> specificChunks; + + public Processor(ValueProcessor valueProcessor, List> specificChunks) { + this.valueProcessor = Objects.requireNonNull(valueProcessor); + this.specificChunks = Objects.requireNonNull(specificChunks); + } + + ValueProcessor processor() { + return valueProcessor; + } + + void notApplicable() { + for (WritableChunk wc : specificChunks) { + final int size = wc.size(); + wc.fillWithNullValue(size, 1); + wc.setSize(size + 1); + } + } + } + + private class DiscriminatedProcessor implements ValueProcessor { + + private final WritableObjectChunk typeOut; + private final Map processors; + + public DiscriminatedProcessor(WritableObjectChunk typeOut, Map processors) { + this.typeOut = Objects.requireNonNull(typeOut); + this.processors = Objects.requireNonNull(processors); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case START_OBJECT: + if (parser.nextToken() == JsonToken.END_OBJECT) { + processEmptyObject(parser); + return; + } + if (!parser.hasToken(JsonToken.FIELD_NAME)) { + throw new IllegalStateException(); + } + // fall-through + case FIELD_NAME: + processObjectFields(parser); + break; + case VALUE_NULL: + processNullObject(parser); + break; + default: + throw Parsing.mismatch(parser, Object.class); + } + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, Object.class); + } + // onMissingType()? + typeOut.add(null); + for (Processor processor : processors.values()) { + processor.notApplicable(); + } + } + + private void processNullObject(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, Object.class); + } + // onNullType()? + typeOut.add(null); + for (Processor processor : processors.values()) { + processor.notApplicable(); + } + } + + private void processEmptyObject(JsonParser parser) throws IOException { + // this logic should be equivalent to processObjectFields w/ no fields + // suggests that maybe this branch should be an error b/c we _need_ type field? + throw new IOException("no field"); + } + + private void processObjectFields(JsonParser parser) throws IOException { + final String typeFieldValue = parseTypeField(parser); + if (!options.allowUnknownTypes()) { + if (!processors.containsKey(typeFieldValue)) { + throw new IOException(String.format("Unmapped type '%s'", typeFieldValue)); + } + } + typeOut.add(typeFieldValue); + if (parser.nextToken() == JsonToken.END_OBJECT) { + for (Processor processor : processors.values()) { + processor.notApplicable(); + } + return; + } + if (!parser.hasToken(JsonToken.FIELD_NAME)) { + throw new IllegalStateException(); + } + for (Entry e : processors.entrySet()) { + final String processorType = e.getKey(); + final Processor processor = e.getValue(); + if (processorType.equals(typeFieldValue)) { + processor.processor().processCurrentValue(parser); + } else { + processor.notApplicable(); + } + } + } + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessor.java new file mode 100644 index 00000000000..a292be173f4 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessor.java @@ -0,0 +1,59 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +import java.io.IOException; + +import static io.deephaven.json.jackson.Parsing.assertNextToken; +import static io.deephaven.json.jackson.Parsing.assertNoCurrentToken; + +interface ValueProcessor { + + static void processFullJson(JsonParser parser, ValueProcessor processor) throws IOException { + assertNoCurrentToken(parser); + final JsonToken startToken = parser.nextToken(); + if (startToken == null) { + processor.processMissing(parser); + return; + } + processor.processCurrentValue(parser); + // note: AnyOptions impl which is based on com.fasterxml.jackson.core.JsonParser.readValueAsTree + // clears out the token, so we can't necessarily check it. + // parser.getLastClearedToken() + // assertCurrentToken(parser, endToken(startToken)); + assertNextToken(parser, null); + } + + // semantically _similar_ to + // com.fasterxml.jackson.databind.JsonDeserializer.deserialize(com.fasterxml.jackson.core.JsonParser, + // com.fasterxml.jackson.databind.DeserializationContext), + // but not functional (want to destructure efficiently) + + /** + * Called when the JSON value is present; the current token should be one of {@link JsonToken#START_OBJECT}, + * {@link JsonToken#START_ARRAY}, {@link JsonToken#VALUE_STRING}, {@link JsonToken#VALUE_NUMBER_INT}, + * {@link JsonToken#VALUE_NUMBER_FLOAT}, {@link JsonToken#VALUE_TRUE}, {@link JsonToken#VALUE_FALSE}, or + * {@link JsonToken#VALUE_NULL}. When the current token is {@link JsonToken#START_OBJECT}, the implementation must + * end with the corresponding {@link JsonToken#END_OBJECT} as the current token; when the current token is + * {@link JsonToken#START_ARRAY}, the implementation must end with the corresponding {@link JsonToken#END_ARRAY} as + * the current token; otherwise, the implementation must not change the current token. + * + * @param parser the parser + * @throws IOException if an IOException occurs + */ + void processCurrentValue(JsonParser parser) throws IOException; + + /** + * Called when the JSON value is missing; the current token may or may not be {@code null}. For example, if a + * field is missing from a JSON object, it's likely that missing values will be notified when the current token is + * {@link JsonToken#END_OBJECT}. Implementations must not modify the state of {@code parser}. + * + * @param parser the parser + * @throws IOException if an IOException occurs + */ + void processMissing(JsonParser parser) throws IOException; +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorArrayImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorArrayImpl.java new file mode 100644 index 00000000000..7f7fe6a6a36 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorArrayImpl.java @@ -0,0 +1,52 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import io.deephaven.json.jackson.RepeaterProcessor.Context; + +import java.io.IOException; +import java.util.Objects; + +final class ValueProcessorArrayImpl implements ValueProcessor { + + static void processArray2( + JsonParser parser, + RepeaterProcessor elementProcessor, + Runnable processElementCallback) throws IOException { + Parsing.assertCurrentToken(parser, JsonToken.START_ARRAY); + final Context context = elementProcessor.start(parser); + parser.nextToken(); + int ix; + for (ix = 0; !parser.hasToken(JsonToken.END_ARRAY); ++ix) { + context.processElement(parser, ix); + parser.nextToken(); + if (processElementCallback != null) { + processElementCallback.run(); + } + } + context.done(parser, ix); + } + + private final RepeaterProcessor elementProcessor; + + ValueProcessorArrayImpl(RepeaterProcessor elementProcessor) { + this.elementProcessor = Objects.requireNonNull(elementProcessor); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + if (parser.hasToken(JsonToken.VALUE_NULL)) { + elementProcessor.processNullRepeater(parser); + return; + } + processArray2(parser, elementProcessor, null); + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + elementProcessor.processMissingRepeater(parser); + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java new file mode 100644 index 00000000000..34528d9f070 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java @@ -0,0 +1,62 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import io.deephaven.json.jackson.RepeaterProcessor.Context; + +import java.io.IOException; +import java.util.Objects; + +final class ValueProcessorKvImpl implements ValueProcessor { + + public static void processKeyValues2( + JsonParser parser, + RepeaterProcessor keyProcessor, + RepeaterProcessor valueProcessor, + Runnable processElementCallback) throws IOException { + Parsing.assertCurrentToken(parser, JsonToken.START_OBJECT); + final Context keyContext = keyProcessor.start(parser); + final Context valueContext = valueProcessor.start(parser); + parser.nextToken(); + int ix; + for (ix = 0; !parser.hasToken(JsonToken.END_OBJECT); ++ix) { + Parsing.assertCurrentToken(parser, JsonToken.FIELD_NAME); + keyContext.processElement(parser, ix); + parser.nextToken(); + valueContext.processElement(parser, ix); + parser.nextToken(); + if (processElementCallback != null) { + processElementCallback.run(); + } + } + keyContext.done(parser, ix); + valueContext.done(parser, ix); + } + + private final RepeaterProcessor keyProcessor; + private final RepeaterProcessor valueProcessor; + + public ValueProcessorKvImpl(RepeaterProcessor keyProcessor, RepeaterProcessor valueProcessor) { + this.keyProcessor = Objects.requireNonNull(keyProcessor); + this.valueProcessor = Objects.requireNonNull(valueProcessor); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + if (parser.hasToken(JsonToken.VALUE_NULL)) { + keyProcessor.processNullRepeater(parser); + valueProcessor.processNullRepeater(parser); + return; + } + processKeyValues2(parser, keyProcessor, valueProcessor, null); + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + keyProcessor.processMissingRepeater(parser); + valueProcessor.processMissingRepeater(parser); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/chunk/ChunkEquals.java b/extensions/json-jackson/src/test/java/io/deephaven/chunk/ChunkEquals.java new file mode 100644 index 00000000000..4d68feab523 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/chunk/ChunkEquals.java @@ -0,0 +1,91 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.chunk; + +import java.util.Arrays; +import java.util.Objects; + +// todo: current dupe w/ extensions-kafka-v2 +public class ChunkEquals { + + // todo: should probably be moved into respective classes + + + + public static boolean equals(Chunk x, Chunk y) { + if (!x.getChunkType().equals(y.getChunkType())) { + return false; + } + switch (x.getChunkType()) { + case Boolean: + return equals((BooleanChunk) x, (BooleanChunk) y); + case Char: + return equals((CharChunk) x, (CharChunk) y); + case Byte: + return equals((ByteChunk) x, (ByteChunk) y); + case Short: + return equals((ShortChunk) x, (ShortChunk) y); + case Int: + return equals((IntChunk) x, (IntChunk) y); + case Long: + return equals((LongChunk) x, (LongChunk) y); + case Float: + return equals((FloatChunk) x, (FloatChunk) y); + case Double: + return equals((DoubleChunk) x, (DoubleChunk) y); + case Object: + // Note: we can't be this precise b/c io.deephaven.chunk.ObjectChunk.makeArray doesn't actually use the + // typed + // class + /* + * if (!actualObjectChunk.data.getClass().equals(expectedObjectChunk.data.getClass())) { return false; } + */ + return equals((ObjectChunk) x, (ObjectChunk) y); + default: + throw new IllegalStateException(); + } + } + + public static boolean equals(BooleanChunk actual, BooleanChunk expected) { + return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); + } + + public static boolean equals(ByteChunk actual, ByteChunk expected) { + return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); + } + + public static boolean equals(CharChunk actual, CharChunk expected) { + return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); + } + + public static boolean equals(ShortChunk actual, ShortChunk expected) { + return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); + } + + public static boolean equals(IntChunk actual, IntChunk expected) { + return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); + } + + public static boolean equals(LongChunk actual, LongChunk expected) { + return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); + } + + public static boolean equals(FloatChunk actual, FloatChunk expected) { + return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); + } + + public static boolean equals(DoubleChunk actual, DoubleChunk expected) { + return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); + } + + public static boolean equals(ObjectChunk actual, ObjectChunk expected) { + // this is probably only suitable for unit testing, not something we'd want in real code? + return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size, + ChunkEquals::fakeDeepCompareForEquals); + } + + private static int fakeDeepCompareForEquals(Object a, Object b) { + return Objects.deepEquals(a, b) ? 0 : 1; + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ArrayTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ArrayTest.java new file mode 100644 index 00000000000..275ee65f49d --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ArrayTest.java @@ -0,0 +1,91 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.ObjectChunk; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Map; + +import static io.deephaven.json.TestHelper.parse; + +public class ArrayTest { + + @Test + void primitive() throws IOException { + // [1.1, null, 3.3] + parse(DoubleOptions.standard().array(), + "[1.1, null, 3.3]", + ObjectChunk.chunkWrap(new Object[] {new double[] {1.1, QueryConstants.NULL_DOUBLE, 3.3}})); + } + + @Test + void bool() throws IOException { + // [true, false, null] + parse(BoolOptions.standard().array(), + "[true, false, null]", + ObjectChunk.chunkWrap(new Object[] {new Boolean[] {true, false, null}})); + } + + @Test + void tuple() throws IOException { + // [[1, 1.1], null, [3, 3.3]] + parse(TupleOptions.of(IntOptions.standard(), DoubleOptions.standard()).array(), + "[[1, 1.1], null, [3, 3.3]]", + ObjectChunk.chunkWrap(new Object[] {new int[] {1, QueryConstants.NULL_INT, 3}}), + ObjectChunk.chunkWrap(new Object[] {new double[] {1.1, QueryConstants.NULL_DOUBLE, 3.3}})); + } + + @Test + void object() throws IOException { + // [{"int": 1, "double": 1.1}, null, {}, {"int": 4, "double": 4.4}] + parse(ObjectOptions.builder() + .putFields("int", IntOptions.standard()) + .putFields("double", DoubleOptions.standard()) + .build() + .array(), + "[{\"int\": 1, \"double\": 1.1}, null, {}, {\"int\": 4, \"double\": 4.4}]", + ObjectChunk + .chunkWrap(new Object[] {new int[] {1, QueryConstants.NULL_INT, QueryConstants.NULL_INT, 4}}), + ObjectChunk.chunkWrap(new Object[] { + new double[] {1.1, QueryConstants.NULL_DOUBLE, QueryConstants.NULL_DOUBLE, 4.4}})); + } + + // missing feature + @Disabled + @Test + void typedObject() throws IOException { + // [ {"type": "int", "value": 42}, {"type": "string", "value": "foo"} ] + parse(TypedObjectOptions.builder() + .typeFieldName("type") + .putObjects("int", ObjectOptions.standard(Map.of("value", IntOptions.standard()))) + .putObjects("string", ObjectOptions.standard(Map.of("value", StringOptions.standard()))) + .build() + .array(), + "[ {\"type\": \"int\", \"value\": 42}, {\"type\": \"string\", \"value\": \"foo\"} ]", + ObjectChunk.chunkWrap(new Object[] {new String[] {"int", "string"}}), + ObjectChunk + .chunkWrap(new Object[] {new int[] {42, QueryConstants.NULL_INT}}), + ObjectChunk.chunkWrap(new Object[] {new String[] {null, "foo"}})); + } + + @Test + void instant() throws IOException { + // ["2009-02-13T23:31:30.123456789Z", null] + parse(InstantOptions.standard().array(), + "[\"2009-02-13T23:31:30.123456789Z\", null]", + ObjectChunk.chunkWrap(new Object[] {new long[] {1234567890123456789L, QueryConstants.NULL_LONG}})); + } + + @Test + void tupleSkip() throws IOException { + // [[1, 1], [2, 2.2], [3, "foo"], [4, true], [5, false], [6, {}], [7, []], [8, null], null] + parse(TupleOptions.of(IntOptions.standard(), SkipOptions.lenient()).array(), + "[[1, 1], [2, 2.2], [3, \"foo\"], [4, true], [5, false], [6, {}], [7, []], [8, null], null]", + ObjectChunk.chunkWrap(new Object[] {new int[] {1, 2, 3, 4, 5, 6, 7, 8, QueryConstants.NULL_INT}})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayOptionsTest.java new file mode 100644 index 00000000000..1bdb4533fd5 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayOptionsTest.java @@ -0,0 +1,30 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.ObjectChunk; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static io.deephaven.json.TestHelper.parse; + +public class BoolArrayOptionsTest { + + @Test + void standard() throws IOException { + parse(BoolOptions.standard().array(), "[true, null, false]", + ObjectChunk.chunkWrap(new Object[] {new Boolean[] {true, null, false}})); + } + + @Test + void standardMissing() throws IOException { + parse(BoolOptions.standard().array(), "", ObjectChunk.chunkWrap(new Object[] {null})); + } + + @Test + void standardNull() throws IOException { + parse(BoolOptions.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] {null})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java new file mode 100644 index 00000000000..3f3dd6f06f5 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java @@ -0,0 +1,179 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import com.fasterxml.jackson.core.exc.InputCoercionException; +import io.deephaven.chunk.ByteChunk; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class ByteOptionsTest { + + @Test + void standard() throws IOException { + parse(ByteOptions.standard(), "42", ByteChunk.chunkWrap(new byte[] {42})); + } + + @Test + void standardMissing() throws IOException { + parse(ByteOptions.standard(), "", ByteChunk.chunkWrap(new byte[] {QueryConstants.NULL_BYTE})); + } + + @Test + void standardNull() throws IOException { + parse(ByteOptions.standard(), "null", ByteChunk.chunkWrap(new byte[] {QueryConstants.NULL_BYTE})); + } + + @Test + void customMissing() throws IOException { + parse(ByteOptions.builder().onMissing((byte) -1).build(), "", ByteChunk.chunkWrap(new byte[] {-1})); + } + + @Test + void customNull() throws IOException { + parse(ByteOptions.builder().onNull((byte) -2).build(), "null", ByteChunk.chunkWrap(new byte[] {-2})); + } + + @Test + void strict() throws IOException { + parse(ByteOptions.strict(), "42", ByteChunk.chunkWrap(new byte[] {42})); + } + + @Test + void strictMissing() throws IOException { + try { + parse(ByteOptions.strict(), "", ByteChunk.chunkWrap(new byte[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected missing token"); + } + } + + @Test + void strictNull() throws IOException { + try { + parse(ByteOptions.strict(), "null", ByteChunk.chunkWrap(new byte[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + } + } + + @Test + void standardOverflow() throws IOException { + try { + parse(ByteOptions.standard(), "2147483648", ByteChunk.chunkWrap(new byte[1])); + } catch (InputCoercionException e) { + assertThat(e).hasMessageContaining( + "Numeric value (2147483648) out of range of int (-2147483648 - 2147483647)"); + } + } + + @Test + void standardString() throws IOException { + try { + parse(ByteOptions.standard(), "\"42\"", ByteChunk.chunkWrap(new byte[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); + } + } + + @Test + void standardTrue() throws IOException { + try { + parse(ByteOptions.standard(), "true", ByteChunk.chunkWrap(new byte[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + } + } + + @Test + void standardFalse() throws IOException { + try { + parse(ByteOptions.standard(), "false", ByteChunk.chunkWrap(new byte[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + } + } + + @Test + void standardFloat() throws IOException { + try { + parse(ByteOptions.standard(), "42.0", ByteChunk.chunkWrap(new byte[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); + } + } + + @Test + void standardObject() throws IOException { + try { + parse(ByteOptions.standard(), "{}", ByteChunk.chunkWrap(new byte[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + } + } + + @Test + void standardArray() throws IOException { + try { + parse(ByteOptions.standard(), "[]", ByteChunk.chunkWrap(new byte[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + } + } + + @Test + void lenientString() throws IOException { + parse(ByteOptions.lenient(), List.of("\"42\"", "\"43\""), ByteChunk.chunkWrap(new byte[] {42, 43})); + } + + @Test + void allowDecimal() throws IOException { + parse(ByteOptions.builder() + .allowedTypes(JsonValueTypes.INT, JsonValueTypes.DECIMAL) + .build(), List.of("42.42", "43.999"), ByteChunk.chunkWrap(new byte[] {42, 43})); + } + + @Test + void allowDecimalString() throws IOException { + parse(ByteOptions.builder() + .allowedTypes(JsonValueTypes.STRING, JsonValueTypes.INT, JsonValueTypes.DECIMAL) + .build(), + List.of("\"42.42\"", "\"43.999\""), ByteChunk.chunkWrap(new byte[] {42, 43})); + } + + @Test + void decimalStringLimitsNearMinValue() throws IOException { + for (int i = 0; i < 100; ++i) { + parse(ByteOptions.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) + .build(), + List.of(String.format("\"%d.0\"", Byte.MIN_VALUE + i)), + ByteChunk.chunkWrap(new byte[] {(byte) (Byte.MIN_VALUE + i)})); + } + } + + @Test + void decimalStringLimitsNearMaxValue() throws IOException { + for (int i = 0; i < 100; ++i) { + parse(ByteOptions.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) + .build(), + List.of(String.format("\"%d.0\"", Byte.MAX_VALUE - i)), + ByteChunk.chunkWrap(new byte[] {(byte) (Byte.MAX_VALUE - i)})); + } + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java new file mode 100644 index 00000000000..99bf77b4dd9 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java @@ -0,0 +1,130 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.CharChunk; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static io.deephaven.json.TestHelper.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class CharOptionsTest { + + @Test + void standard() throws IOException { + parse(CharOptions.standard(), "\"c\"", CharChunk.chunkWrap(new char[] {'c'})); + } + + @Test + void standardMissing() throws IOException { + parse(CharOptions.standard(), "", CharChunk.chunkWrap(new char[] {QueryConstants.NULL_CHAR})); + } + + @Test + void standardNull() throws IOException { + parse(CharOptions.standard(), "null", CharChunk.chunkWrap(new char[] {QueryConstants.NULL_CHAR})); + } + + + @Test + void customMissing() throws IOException { + parse(CharOptions.builder().onMissing('m').build(), "", CharChunk.chunkWrap(new char[] {'m'})); + } + + @Test + void customNull() throws IOException { + parse(CharOptions.builder().onNull('n').build(), "null", CharChunk.chunkWrap(new char[] {'n'})); + } + + @Test + void strict() throws IOException { + parse(CharOptions.strict(), "\"c\"", CharChunk.chunkWrap(new char[] {'c'})); + } + + @Test + void strictMissing() throws IOException { + try { + parse(CharOptions.strict(), "", CharChunk.chunkWrap(new char[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected missing token"); + } + } + + @Test + void strictNull() throws IOException { + try { + parse(CharOptions.strict(), "null", CharChunk.chunkWrap(new char[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + } + } + + + @Test + void standardInt() throws IOException { + try { + parse(CharOptions.standard(), "42", CharChunk.chunkWrap(new char[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_INT'"); + } + } + + @Test + void standardFloat() throws IOException { + try { + parse(CharOptions.standard(), "42.42", CharChunk.chunkWrap(new char[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); + } + } + + + @Test + void standardTrue() throws IOException { + try { + parse(CharOptions.standard(), "true", CharChunk.chunkWrap(new char[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + } + } + + @Test + void standardFalse() throws IOException { + try { + parse(CharOptions.standard(), "false", CharChunk.chunkWrap(new char[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + } + } + + @Test + void standardObject() throws IOException { + try { + parse(CharOptions.standard(), "{}", CharChunk.chunkWrap(new char[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + } + } + + @Test + void standardArray() throws IOException { + try { + parse(CharOptions.standard(), "[]", CharChunk.chunkWrap(new char[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + } + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayOptionsTest.java new file mode 100644 index 00000000000..dca0c0e7270 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayOptionsTest.java @@ -0,0 +1,31 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.ObjectChunk; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static io.deephaven.json.TestHelper.parse; + +public class DoubleArrayOptionsTest { + + @Test + void standard() throws IOException { + parse(DoubleOptions.standard().array(), "[42.1, null, 43.2]", + ObjectChunk.chunkWrap(new Object[] {new double[] {42.1, QueryConstants.NULL_DOUBLE, 43.2}})); + } + + @Test + void standardMissing() throws IOException { + parse(DoubleOptions.standard().array(), "", ObjectChunk.chunkWrap(new Object[] {null})); + } + + @Test + void standardNull() throws IOException { + parse(DoubleOptions.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] {null})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java new file mode 100644 index 00000000000..c03f08b3954 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java @@ -0,0 +1,118 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.DoubleChunk; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class DoubleOptionsTest { + + @Test + void standard() throws IOException { + parse(DoubleOptions.standard(), List.of("42", "42.42"), DoubleChunk.chunkWrap(new double[] {42, 42.42})); + } + + @Test + void standardMissing() throws IOException { + parse(DoubleOptions.standard(), "", DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE})); + } + + @Test + void standardNull() throws IOException { + parse(DoubleOptions.standard(), "null", DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE})); + } + + @Test + void customMissing() throws IOException { + parse(DoubleOptions.builder().onMissing(-1.0).build(), "", DoubleChunk.chunkWrap(new double[] {-1})); + } + + @Test + void strict() throws IOException { + parse(DoubleOptions.strict(), "42", DoubleChunk.chunkWrap(new double[] {42})); + } + + @Test + void strictMissing() throws IOException { + try { + parse(DoubleOptions.strict(), "", DoubleChunk.chunkWrap(new double[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected missing token"); + } + } + + @Test + void strictNull() throws IOException { + try { + parse(DoubleOptions.strict(), "null", DoubleChunk.chunkWrap(new double[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + } + } + + @Test + void standardString() throws IOException { + try { + parse(DoubleOptions.standard(), "\"42\"", DoubleChunk.chunkWrap(new double[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); + } + } + + @Test + void standardTrue() throws IOException { + try { + parse(DoubleOptions.standard(), "true", DoubleChunk.chunkWrap(new double[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + } + } + + @Test + void standardFalse() throws IOException { + try { + parse(DoubleOptions.standard(), "false", DoubleChunk.chunkWrap(new double[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + } + } + + @Test + void standardObject() throws IOException { + try { + parse(DoubleOptions.standard(), "{}", DoubleChunk.chunkWrap(new double[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + } + } + + @Test + void standardArray() throws IOException { + try { + parse(DoubleOptions.standard(), "[]", DoubleChunk.chunkWrap(new double[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + } + } + + @Test + void lenientString() throws IOException { + parse(DoubleOptions.lenient(), List.of("\"42\"", "\"42.42\""), DoubleChunk.chunkWrap(new double[] {42, 42.42})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java new file mode 100644 index 00000000000..b14a9d417bf --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java @@ -0,0 +1,118 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.FloatChunk; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class FloatOptionsTest { + + @Test + void standard() throws IOException { + parse(FloatOptions.standard(), List.of("42", "42.42"), FloatChunk.chunkWrap(new float[] {42, 42.42f})); + } + + @Test + void standardMissing() throws IOException { + parse(FloatOptions.standard(), "", FloatChunk.chunkWrap(new float[] {QueryConstants.NULL_FLOAT})); + } + + @Test + void standardNull() throws IOException { + parse(FloatOptions.standard(), "null", FloatChunk.chunkWrap(new float[] {QueryConstants.NULL_FLOAT})); + } + + @Test + void customMissing() throws IOException { + parse(FloatOptions.builder().onMissing(-1.0f).build(), "", FloatChunk.chunkWrap(new float[] {-1})); + } + + @Test + void strict() throws IOException { + parse(FloatOptions.strict(), "42", FloatChunk.chunkWrap(new float[] {42})); + } + + @Test + void strictMissing() throws IOException { + try { + parse(FloatOptions.strict(), "", FloatChunk.chunkWrap(new float[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected missing token"); + } + } + + @Test + void strictNull() throws IOException { + try { + parse(FloatOptions.strict(), "null", FloatChunk.chunkWrap(new float[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + } + } + + @Test + void standardString() throws IOException { + try { + parse(FloatOptions.standard(), "\"42\"", FloatChunk.chunkWrap(new float[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); + } + } + + @Test + void standardTrue() throws IOException { + try { + parse(FloatOptions.standard(), "true", FloatChunk.chunkWrap(new float[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + } + } + + @Test + void standardFalse() throws IOException { + try { + parse(FloatOptions.standard(), "false", FloatChunk.chunkWrap(new float[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + } + } + + @Test + void standardObject() throws IOException { + try { + parse(FloatOptions.standard(), "{}", FloatChunk.chunkWrap(new float[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + } + } + + @Test + void standardArray() throws IOException { + try { + parse(FloatOptions.standard(), "[]", FloatChunk.chunkWrap(new float[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + } + } + + @Test + void lenientString() throws IOException { + parse(FloatOptions.lenient(), List.of("\"42\"", "\"42.42\""), FloatChunk.chunkWrap(new float[] {42, 42.42f})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberOptionsTest.java new file mode 100644 index 00000000000..6cb16691885 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberOptionsTest.java @@ -0,0 +1,106 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.LongChunk; +import io.deephaven.json.InstantNumberOptions.Format; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static io.deephaven.json.TestHelper.parse; + +public class InstantNumberOptionsTest { + private static final long WITH_SECONDS = 1703292532000000000L; + private static final long WITH_MILLIS = 1703292532123000000L; + private static final long WITH_MICROS = 1703292532123456000L; + private static final long WITH_NANOS = 1703292532123456789L; + + @Test + void epochSeconds() throws IOException { + parse(Format.EPOCH_SECONDS.standard(false), "1703292532", LongChunk.chunkWrap(new long[] {WITH_SECONDS})); + } + + @Test + void epochSecondsDecimal() throws IOException { + parse(Format.EPOCH_SECONDS.standard(true), "1703292532.123456789", + LongChunk.chunkWrap(new long[] {WITH_NANOS})); + } + + @Test + void epochSecondsString() throws IOException { + parse(Format.EPOCH_SECONDS.lenient(false), "\"1703292532\"", LongChunk.chunkWrap(new long[] {WITH_SECONDS})); + } + + @Test + void epochSecondsStringDecimal() throws IOException { + parse(Format.EPOCH_SECONDS.lenient(true), "\"1703292532.123456789\"", + LongChunk.chunkWrap(new long[] {WITH_NANOS})); + } + + @Test + void epochMillis() throws IOException { + parse(Format.EPOCH_MILLIS.standard(false), "1703292532123", LongChunk.chunkWrap(new long[] {WITH_MILLIS})); + } + + @Test + void epochMillisDecimal() throws IOException { + parse(Format.EPOCH_MILLIS.standard(true), "1703292532123.456789", LongChunk.chunkWrap(new long[] {WITH_NANOS})); + } + + @Test + void epochMillisString() throws IOException { + parse(Format.EPOCH_MILLIS.lenient(false), "\"1703292532123\"", LongChunk.chunkWrap(new long[] {WITH_MILLIS})); + } + + @Test + void epochMillisStringDecimal() throws IOException { + parse(Format.EPOCH_MILLIS.lenient(true), "\"1703292532123.456789\"", + LongChunk.chunkWrap(new long[] {WITH_NANOS})); + } + + @Test + void epochMicros() throws IOException { + parse(Format.EPOCH_MICROS.standard(false), "1703292532123456", LongChunk.chunkWrap(new long[] {WITH_MICROS})); + } + + @Test + void epochMicrosDecimal() throws IOException { + parse(Format.EPOCH_MICROS.standard(true), "1703292532123456.789", LongChunk.chunkWrap(new long[] {WITH_NANOS})); + } + + @Test + void epochMicrosString() throws IOException { + parse(Format.EPOCH_MICROS.lenient(false), "\"1703292532123456\"", + LongChunk.chunkWrap(new long[] {WITH_MICROS})); + } + + @Test + void epochMicrosStringDecimal() throws IOException { + parse(Format.EPOCH_MICROS.lenient(true), "\"1703292532123456.789\"", + LongChunk.chunkWrap(new long[] {WITH_NANOS})); + } + + @Test + void epochNanos_() throws IOException { + parse(Format.EPOCH_NANOS.standard(false), "1703292532123456789", LongChunk.chunkWrap(new long[] {WITH_NANOS})); + } + + @Test + void epochNanosDecimal() throws IOException { + parse(Format.EPOCH_NANOS.standard(true), "1703292532123456789.0", LongChunk.chunkWrap(new long[] {WITH_NANOS})); + } + + @Test + void epochNanosString() throws IOException { + parse(Format.EPOCH_NANOS.lenient(false), "\"1703292532123456789\"", + LongChunk.chunkWrap(new long[] {WITH_NANOS})); + } + + @Test + void epochNanosStringDecimal() throws IOException { + parse(Format.EPOCH_NANOS.lenient(true), "\"1703292532123456789.0\"", + LongChunk.chunkWrap(new long[] {WITH_NANOS})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java new file mode 100644 index 00000000000..697c4985d7e --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java @@ -0,0 +1,73 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.LongChunk; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.time.Instant; + +import static io.deephaven.json.TestHelper.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class InstantOptionsTest { + + private static final String XYZ_STR = "2009-02-13T23:31:30.123456789"; + private static final long XYZ_NANOS = 1234567890L * 1_000_000_000 + 123456789; + + @Test + void iso8601() throws IOException { + parse(InstantOptions.standard(), "\"" + XYZ_STR + "Z\"", LongChunk.chunkWrap(new long[] {XYZ_NANOS})); + } + + @Test + void iso8601WithOffset() throws IOException { + parse(InstantOptions.standard(), "\"" + XYZ_STR + "+00:00\"", LongChunk.chunkWrap(new long[] {XYZ_NANOS})); + } + + @Test + void standardNull() throws IOException { + parse(InstantOptions.standard(), "null", LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG})); + } + + @Test + void standardMissing() throws IOException { + parse(InstantOptions.standard(), "", LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG})); + } + + @Test + void strictNull() throws IOException { + try { + parse(InstantOptions.strict(), "null", LongChunk.chunkWrap(new long[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + } + } + + @Test + void strictMissing() throws IOException { + try { + parse(InstantOptions.strict(), "", LongChunk.chunkWrap(new long[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected missing token"); + } + } + + @Test + void customNull() throws IOException { + parse(InstantOptions.builder().onNull(Instant.ofEpochMilli(0)).build(), "null", + LongChunk.chunkWrap(new long[] {0})); + } + + @Test + void customMissing() throws IOException { + parse(InstantOptions.builder().onMissing(Instant.ofEpochMilli(0)).build(), "", + LongChunk.chunkWrap(new long[] {0})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayOptionsTest.java new file mode 100644 index 00000000000..fad43a276a1 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayOptionsTest.java @@ -0,0 +1,29 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.ObjectChunk; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static io.deephaven.json.TestHelper.parse; + +public class IntArrayOptionsTest { + + @Test + void standard() throws IOException { + parse(IntOptions.standard().array(), "[42, 43]", ObjectChunk.chunkWrap(new Object[] {new int[] {42, 43}})); + } + + // @Test + // void standardMissing() throws IOException { + // parse(IntOptions.standard().array(), "", ObjectChunk.chunkWrap(new Object[] { null })); + // } + // + // @Test + // void standardNull() throws IOException { + // parse(IntOptions.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] { null })); + // } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java new file mode 100644 index 00000000000..6944875f7df --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java @@ -0,0 +1,179 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import com.fasterxml.jackson.core.exc.InputCoercionException; +import io.deephaven.chunk.IntChunk; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class IntOptionsTest { + + @Test + void standard() throws IOException { + parse(IntOptions.standard(), "42", IntChunk.chunkWrap(new int[] {42})); + } + + @Test + void standardMissing() throws IOException { + parse(IntOptions.standard(), "", IntChunk.chunkWrap(new int[] {QueryConstants.NULL_INT})); + } + + @Test + void standardNull() throws IOException { + parse(IntOptions.standard(), "null", IntChunk.chunkWrap(new int[] {QueryConstants.NULL_INT})); + } + + @Test + void customMissing() throws IOException { + parse(IntOptions.builder().onMissing(-1).build(), "", IntChunk.chunkWrap(new int[] {-1})); + } + + @Test + void customNull() throws IOException { + parse(IntOptions.builder().onNull(-2).build(), "null", IntChunk.chunkWrap(new int[] {-2})); + } + + @Test + void strict() throws IOException { + parse(IntOptions.strict(), "42", IntChunk.chunkWrap(new int[] {42})); + } + + @Test + void strictMissing() throws IOException { + try { + parse(IntOptions.strict(), "", IntChunk.chunkWrap(new int[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected missing token"); + } + } + + @Test + void strictNull() throws IOException { + try { + parse(IntOptions.strict(), "null", IntChunk.chunkWrap(new int[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + } + } + + @Test + void standardOverflow() throws IOException { + try { + parse(IntOptions.standard(), "2147483648", IntChunk.chunkWrap(new int[1])); + } catch (InputCoercionException e) { + assertThat(e).hasMessageContaining( + "Numeric value (2147483648) out of range of int (-2147483648 - 2147483647)"); + } + } + + @Test + void standardString() throws IOException { + try { + parse(IntOptions.standard(), "\"42\"", IntChunk.chunkWrap(new int[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); + } + } + + @Test + void standardTrue() throws IOException { + try { + parse(IntOptions.standard(), "true", IntChunk.chunkWrap(new int[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + } + } + + @Test + void standardFalse() throws IOException { + try { + parse(IntOptions.standard(), "false", IntChunk.chunkWrap(new int[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + } + } + + @Test + void standardFloat() throws IOException { + try { + parse(IntOptions.standard(), "42.0", IntChunk.chunkWrap(new int[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); + } + } + + @Test + void standardObject() throws IOException { + try { + parse(IntOptions.standard(), "{}", IntChunk.chunkWrap(new int[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + } + } + + @Test + void standardArray() throws IOException { + try { + parse(IntOptions.standard(), "[]", IntChunk.chunkWrap(new int[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + } + } + + @Test + void lenientString() throws IOException { + parse(IntOptions.lenient(), List.of("\"42\"", "\"43\""), IntChunk.chunkWrap(new int[] {42, 43})); + } + + @Test + void allowDecimal() throws IOException { + parse(IntOptions.builder() + .allowedTypes(JsonValueTypes.INT, JsonValueTypes.DECIMAL) + .build(), List.of("42.42", "43.999"), IntChunk.chunkWrap(new int[] {42, 43})); + } + + @Test + void allowDecimalString() throws IOException { + parse(IntOptions.builder() + .allowedTypes(JsonValueTypes.STRING, JsonValueTypes.INT, JsonValueTypes.DECIMAL) + .build(), + List.of("\"42.42\"", "\"43.999\""), IntChunk.chunkWrap(new int[] {42, 43})); + } + + @Test + void decimalStringLimitsNearMinValue() throws IOException { + for (int i = 0; i < 100; ++i) { + parse(IntOptions.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) + .build(), + List.of(String.format("\"%d.0\"", Integer.MIN_VALUE + i)), + IntChunk.chunkWrap(new int[] {Integer.MIN_VALUE + i})); + } + } + + @Test + void decimalStringLimitsNearMaxValue() throws IOException { + for (int i = 0; i < 100; ++i) { + parse(IntOptions.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) + .build(), + List.of(String.format("\"%d.0\"", Integer.MAX_VALUE - i)), + IntChunk.chunkWrap(new int[] {Integer.MAX_VALUE - i})); + } + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/JsonValueTypesTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/JsonValueTypesTest.java new file mode 100644 index 00000000000..75daf99067f --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/JsonValueTypesTest.java @@ -0,0 +1,45 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import org.junit.jupiter.api.Test; + +public class JsonValueTypesTest { + + @Test + public void all() { + JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.ALL); + } + + @Test + public void numberLike() { + JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.NUMBER_LIKE); + } + + @Test + public void stringLike() { + JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.STRING_LIKE); + } + + @Test + public void stringOrNull() { + JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.STRING_OR_NULL); + } + + @Test + public void objectOrNull() { + JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.OBJECT_OR_NULL); + } + + @Test + public void arrayOrNull() { + JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.ARRAY_OR_NULL); + } + + @Test + public void numberIntOrNull() { + JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.INT_OR_NULL); + } + +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java new file mode 100644 index 00000000000..7126903fb79 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java @@ -0,0 +1,67 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.ObjectChunk; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.time.LocalDate; + +import static io.deephaven.json.TestHelper.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class LocalDateOptionsTest { + + private static final String XYZ_STR = "2009-02-13"; + + @Test + void iso8601() throws IOException { + parse(LocalDateOptions.standard(), "\"" + XYZ_STR + "\"", + ObjectChunk.chunkWrap(new LocalDate[] {LocalDate.of(2009, 2, 13)})); + } + + @Test + void standardNull() throws IOException { + parse(LocalDateOptions.standard(), "null", ObjectChunk.chunkWrap(new LocalDate[] {null})); + } + + @Test + void standardMissing() throws IOException { + parse(LocalDateOptions.standard(), "", ObjectChunk.chunkWrap(new LocalDate[] {null})); + } + + @Test + void strictNull() throws IOException { + try { + parse(LocalDateOptions.strict(), "null", ObjectChunk.chunkWrap(new LocalDate[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + } + } + + @Test + void strictMissing() throws IOException { + try { + parse(LocalDateOptions.strict(), "", ObjectChunk.chunkWrap(new LocalDate[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected missing token"); + } + } + + @Test + void customNull() throws IOException { + parse(LocalDateOptions.builder().onNull(LocalDate.ofEpochDay(0)).build(), "null", + ObjectChunk.chunkWrap(new LocalDate[] {LocalDate.ofEpochDay(0)})); + } + + @Test + void customMissing() throws IOException { + parse(LocalDateOptions.builder().onMissing(LocalDate.ofEpochDay(0)).build(), "", + ObjectChunk.chunkWrap(new LocalDate[] {LocalDate.ofEpochDay(0)})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayOptionsTest.java new file mode 100644 index 00000000000..16b9a349271 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayOptionsTest.java @@ -0,0 +1,29 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.ObjectChunk; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static io.deephaven.json.TestHelper.parse; + +public class LongArrayOptionsTest { + + @Test + void standard() throws IOException { + parse(LongOptions.standard().array(), "[42, 43]", ObjectChunk.chunkWrap(new Object[] {new long[] {42, 43}})); + } + + @Test + void standardMissing() throws IOException { + parse(LongOptions.standard().array(), "", ObjectChunk.chunkWrap(new Object[] {null})); + } + + @Test + void standardNull() throws IOException { + parse(LongOptions.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] {null})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java new file mode 100644 index 00000000000..3d067370382 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java @@ -0,0 +1,170 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import com.fasterxml.jackson.core.exc.InputCoercionException; +import io.deephaven.chunk.LongChunk; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class LongOptionsTest { + + @Test + void standard() throws IOException { + parse(LongOptions.standard(), List.of("42", "43"), LongChunk.chunkWrap(new long[] {42, 43})); + } + + @Test + void standardMissing() throws IOException { + parse(LongOptions.standard(), "", LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG})); + } + + @Test + void standardNull() throws IOException { + parse(LongOptions.standard(), "null", LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG})); + } + + @Test + void customMissing() throws IOException { + parse(LongOptions.builder().onMissing(-1L).build(), "", LongChunk.chunkWrap(new long[] {-1})); + } + + @Test + void strict() throws IOException { + parse(LongOptions.strict(), "42", LongChunk.chunkWrap(new long[] {42})); + } + + @Test + void strictMissing() throws IOException { + try { + parse(LongOptions.strict(), "", LongChunk.chunkWrap(new long[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected missing token"); + } + } + + @Test + void strictNull() throws IOException { + try { + parse(LongOptions.strict(), "null", LongChunk.chunkWrap(new long[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + } + } + + @Test + void strictOverflow() throws IOException { + try { + parse(LongOptions.strict(), "9223372036854775808", LongChunk.chunkWrap(new long[1])); + } catch (InputCoercionException e) { + assertThat(e).hasMessageContaining( + "Numeric value (9223372036854775808) out of range of long (-9223372036854775808 - 9223372036854775807)"); + } + } + + @Test + void standardString() throws IOException { + try { + parse(LongOptions.standard(), "\"42\"", LongChunk.chunkWrap(new long[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); + } + } + + @Test + void standardTrue() throws IOException { + try { + parse(LongOptions.standard(), "true", LongChunk.chunkWrap(new long[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + } + } + + @Test + void standardFalse() throws IOException { + try { + parse(LongOptions.standard(), "false", LongChunk.chunkWrap(new long[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + } + } + + @Test + void standardFloat() throws IOException { + try { + parse(LongOptions.standard(), "42.0", LongChunk.chunkWrap(new long[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); + } + } + + @Test + void standardObject() throws IOException { + try { + parse(LongOptions.standard(), "{}", LongChunk.chunkWrap(new long[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + } + } + + @Test + void standardArray() throws IOException { + try { + parse(LongOptions.standard(), "[]", LongChunk.chunkWrap(new long[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + } + } + + @Test + void lenientString() throws IOException { + parse(LongOptions.lenient(), List.of("\"42\"", "\"43\""), LongChunk.chunkWrap(new long[] {42, 43})); + } + + @Test + void allowDecimal() throws IOException { + parse(LongOptions.builder().allowedTypes(JsonValueTypes.INT, JsonValueTypes.DECIMAL).build(), + List.of("42.42", "43.999"), + LongChunk.chunkWrap(new long[] {42, 43})); + } + + @Test + void allowDecimalString() throws IOException { + parse(LongOptions.builder().allowedTypes(JsonValueTypes.NUMBER_LIKE).build(), + List.of("\"42.42\"", "\"43.999\""), LongChunk.chunkWrap(new long[] {42, 43})); + } + + @Test + void decimalStringLimitsNearMinValue() throws IOException { + for (int i = 0; i < 100; ++i) { + parse(LongOptions.builder().allowedTypes(JsonValueTypes.NUMBER_LIKE).build(), + List.of(String.format("\"%d.0\"", Long.MIN_VALUE + i)), + LongChunk.chunkWrap(new long[] {Long.MIN_VALUE + i})); + } + } + + @Test + void decimalStringLimitsNearMaxValue() throws IOException { + for (int i = 0; i < 100; ++i) { + parse(LongOptions.builder().allowedTypes(JsonValueTypes.NUMBER_LIKE).build(), + List.of(String.format("\"%d.0\"", Long.MAX_VALUE - i)), + LongChunk.chunkWrap(new long[] {Long.MAX_VALUE - i})); + } + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldOptionsTest.java new file mode 100644 index 00000000000..efaafca3dfb --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldOptionsTest.java @@ -0,0 +1,94 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.json.ObjectFieldOptions.RepeatedBehavior; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class ObjectFieldOptionsTest { + @Test + void basic() { + final ObjectFieldOptions field = ObjectFieldOptions.of("Foo", IntOptions.standard()); + assertThat(field.name()).isEqualTo("Foo"); + assertThat(field.options()).isEqualTo(IntOptions.standard()); + assertThat(field.aliases()).isEmpty(); + assertThat(field.caseSensitive()).isTrue(); + assertThat(field.repeatedBehavior()).isEqualTo(RepeatedBehavior.ERROR); + } + + @Test + void caseInsensitiveMatch() { + final ObjectFieldOptions field = ObjectFieldOptions.builder() + .name("Foo") + .options(IntOptions.standard()) + .caseSensitive(false) + .build(); + assertThat(field.name()).isEqualTo("Foo"); + assertThat(field.options()).isEqualTo(IntOptions.standard()); + assertThat(field.aliases()).isEmpty(); + assertThat(field.caseSensitive()).isFalse(); + assertThat(field.repeatedBehavior()).isEqualTo(RepeatedBehavior.ERROR); + } + + @Test + void repeatedBehavior() { + final ObjectFieldOptions field = ObjectFieldOptions.builder() + .name("Foo") + .options(IntOptions.standard()) + .repeatedBehavior(RepeatedBehavior.ERROR) + .build(); + assertThat(field.name()).isEqualTo("Foo"); + assertThat(field.options()).isEqualTo(IntOptions.standard()); + assertThat(field.aliases()).isEmpty(); + assertThat(field.caseSensitive()).isTrue(); + assertThat(field.repeatedBehavior()).isEqualTo(RepeatedBehavior.ERROR); + } + + @Test + void alias() { + final ObjectFieldOptions field = ObjectFieldOptions.builder() + .name("SomeName") + .options(IntOptions.standard()) + .addAliases("someName") + .build(); + assertThat(field.name()).isEqualTo("SomeName"); + assertThat(field.options()).isEqualTo(IntOptions.standard()); + assertThat(field.aliases()).containsExactly("someName"); + assertThat(field.caseSensitive()).isTrue(); + assertThat(field.repeatedBehavior()).isEqualTo(RepeatedBehavior.ERROR); + } + + @Test + void badAliasRepeated() { + try { + ObjectFieldOptions.builder() + .name("SomeName") + .options(IntOptions.standard()) + .addAliases("SomeName") + .build(); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessageContaining("name and aliases must be non-overlapping"); + } + } + + @Test + void badAliasCaseInsensitive() { + try { + // this is similar to the alias() test, but we are explicitly marking it as case-insensitive + ObjectFieldOptions.builder() + .name("SomeName") + .options(IntOptions.standard()) + .addAliases("someName") + .caseSensitive(false) + .build(); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessageContaining("name and aliases must be non-overlapping"); + } + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvOptionsTest.java new file mode 100644 index 00000000000..db4291f74ee --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvOptionsTest.java @@ -0,0 +1,72 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.ObjectChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; +import static org.assertj.core.api.Assertions.assertThat; + +public class ObjectKvOptionsTest { + + private static final ObjectKvOptions STRING_INT_KV = + ObjectKvOptions.standard(IntOptions.standard()); + + private static final ObjectOptions NAME_AGE_OBJ = ObjectOptions.builder() + .putFields("name", StringOptions.standard()) + .putFields("age", IntOptions.standard()) + .build(); + + private static final ObjectKvOptions STRING_OBJ_KV = ObjectKvOptions.standard(NAME_AGE_OBJ); + + @Test + void kvPrimitiveValue() throws IOException { + parse(STRING_INT_KV, List.of( + "{\"A\": 42, \"B\": null}"), + ObjectChunk.chunkWrap(new Object[] {new String[] {"A", "B"}}), + ObjectChunk.chunkWrap(new Object[] {new int[] {42, QueryConstants.NULL_INT}})); + } + + @Test + void kvObjectValue() throws IOException { + parse(STRING_OBJ_KV, List.of( + "{\"A\": {\"name\": \"Foo\", \"age\": 42}, \"B\": {}, \"C\": null}"), + ObjectChunk.chunkWrap(new Object[] {new String[] {"A", "B", "C"}}), + ObjectChunk.chunkWrap(new Object[] {new String[] {"Foo", null, null}}), + ObjectChunk.chunkWrap(new Object[] {new int[] {42, QueryConstants.NULL_INT, QueryConstants.NULL_INT}})); + } + + @Test + void kvPrimitiveKey() throws IOException { + parse(ObjectKvOptions.builder().key(IntOptions.lenient()).value(SkipOptions.lenient()).build(), List.of( + "{\"42\": null, \"43\": null}"), + ObjectChunk.chunkWrap(new Object[] {new int[] {42, 43}})); + } + + @Test + void kvObjectKey() throws IOException { + parse(ObjectKvOptions.builder().key(InstantOptions.standard()).value(SkipOptions.lenient()).build(), List.of( + "{\"2009-02-13T23:31:30.123456788Z\": null, \"2009-02-13T23:31:30.123456789Z\": null}"), + ObjectChunk.chunkWrap(new Object[] {new long[] {1234567890123456788L, 1234567890123456789L}})); + } + + @Test + void columnNames() { + assertThat(JacksonProvider.of(STRING_INT_KV).named(Type.stringType()).names()).containsExactly("Key", + "Value"); + } + + @Test + void columnNamesValueIsObject() { + assertThat(JacksonProvider.of(STRING_OBJ_KV).named(Type.stringType()).names()).containsExactly("Key", + "name", "age"); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java new file mode 100644 index 00000000000..b6bf7010224 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java @@ -0,0 +1,204 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.IntChunk; +import io.deephaven.chunk.ObjectChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class ObjectOptionsTest { + + public static final ObjectOptions OBJECT_AGE_FIELD = ObjectOptions.builder() + .putFields("age", IntOptions.standard()) + .build(); + + private static final ObjectOptions OBJECT_NAME_AGE_FIELD = ObjectOptions.builder() + .putFields("name", StringOptions.standard()) + .putFields("age", IntOptions.standard()) + .build(); + + @Test + void ofAge() throws IOException { + parse(OBJECT_AGE_FIELD, List.of( + "", + "null", + "{}", + "{\"age\": 42}", + "{\"name\": \"Devin\", \"age\": 43}"), + IntChunk.chunkWrap( + new int[] {QueryConstants.NULL_INT, QueryConstants.NULL_INT, QueryConstants.NULL_INT, 42, 43})); + } + + @Test + void ofNameAge() throws IOException { + parse(OBJECT_NAME_AGE_FIELD, List.of( + // "", + // "null", + // "{}", + // "{\"age\": 42}", + "{\"name\": \"Devin\", \"age\": 43}"), + ObjectChunk.chunkWrap(new String[] {"Devin"}), + IntChunk.chunkWrap( + new int[] {43})); + } + + @Test + void caseInsensitive() throws IOException { + final ObjectOptions options = ObjectOptions.builder() + .addFields(ObjectFieldOptions.builder() + .name("Foo") + .options(IntOptions.standard()) + .caseSensitive(false) + .build()) + .build(); + parse(options, List.of("{\"Foo\": 42}", "{\"fOO\": 43}"), + IntChunk.chunkWrap(new int[] {42, 43})); + } + + @Test + void caseSensitive() throws IOException { + final ObjectFieldOptions f1 = ObjectFieldOptions.of("Foo", IntOptions.standard()); + final ObjectFieldOptions f2 = ObjectFieldOptions.of("foo", IntOptions.standard()); + final ObjectOptions options = ObjectOptions.builder() + .addFields(f1) + .addFields(f2) + .build(); + parse(options, List.of("{\"Foo\": 42, \"foo\": 43}"), + IntChunk.chunkWrap(new int[] {42}), + IntChunk.chunkWrap(new int[] {43})); + } + + @Test + void alias() throws IOException { + final ObjectOptions options = ObjectOptions.builder() + .addFields(ObjectFieldOptions.builder() + .name("FooBar") + .options(IntOptions.standard()) + .addAliases("Foo_Bar") + .build()) + .build(); + parse(options, List.of("{\"FooBar\": 42}", "{\"Foo_Bar\": 43}"), + IntChunk.chunkWrap(new int[] {42, 43})); + } + + @Test + void caseInsensitiveAlias() throws IOException { + final ObjectOptions options = ObjectOptions.builder() + .addFields(ObjectFieldOptions.builder() + .name("FooBar") + .options(IntOptions.standard()) + .addAliases("Foo_Bar") + .caseSensitive(false) + .build()) + .build(); + parse(options, List.of("{\"fooBar\": 42}", "{\"fOO_BAR\": 43}"), + IntChunk.chunkWrap(new int[] {42, 43})); + } + + @Test + void caseSensitiveFields() { + final ObjectFieldOptions f1 = ObjectFieldOptions.of("Foo", IntOptions.standard()); + final ObjectFieldOptions f2 = ObjectFieldOptions.of("foo", IntOptions.standard()); + final ObjectOptions options = ObjectOptions.builder() + .addFields(f1) + .addFields(f2) + .build(); + assertThat(options.fields()).containsExactly(f1, f2); + } + + @Test + void caseInsensitiveOverlap() { + final ObjectFieldOptions f1 = ObjectFieldOptions.of("Foo", IntOptions.standard()); + final ObjectFieldOptions f2 = ObjectFieldOptions.builder() + .name("foo") + .options(IntOptions.standard()) + .caseSensitive(false) + .build(); + try { + ObjectOptions.builder() + .addFields(f1) + .addFields(f2) + .build(); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessageContaining("Found overlapping field name 'foo'"); + } + } + + @Test + void objectFields() throws IOException { + // { "prices": [1.1, 2.2, 3.3], "other": [2, 4, 8, 16] } + parse(ObjectOptions.builder() + .putFields("prices", DoubleOptions.standard().array()) + .putFields("other", LongOptions.standard().array()) + .build(), + "{ \"prices\": [1.1, 2.2, 3.3], \"other\": [2, 4, 8, 16] }", + ObjectChunk + .chunkWrap(new Object[] {new double[] {1.1, 2.2, 3.3}}), + ObjectChunk.chunkWrap(new Object[] { + new long[] {2, 4, 8, 16}})); + } + + @Test + void objectFieldsArrayGroup() throws IOException { + // Note: array groups don't cause any difference wrt ObjectProcessor based destructuring + // { "prices": [1.1, 2.2, 3.3], "sizes": [2, 4, 8] } + parse(ObjectOptions.builder() + .addFields(ObjectFieldOptions.builder() + .name("prices") + .options(DoubleOptions.standard().array()) + .arrayGroup("prices_and_sizes") + .build()) + .addFields(ObjectFieldOptions.builder() + .name("sizes") + .options(LongOptions.standard().array()) + .arrayGroup("prices_and_sizes") + .build()) + .build(), + "{ \"prices\": [1.1, 2.2, 3.3], \"sizes\": [2, 4, 8] }", + ObjectChunk + .chunkWrap(new Object[] {new double[] {1.1, 2.2, 3.3}}), + ObjectChunk.chunkWrap(new Object[] { + new long[] {2, 4, 8}})); + } + + @Test + void columnNames() { + assertThat(JacksonProvider.of(OBJECT_NAME_AGE_FIELD).named(Type.stringType()).names()) + .containsExactly("name", "age"); + } + + @Test + void columnNamesAlternateName() { + final ObjectOptions obj = ObjectOptions.builder() + .addFields(ObjectFieldOptions.builder() + .name("MyName") + .addAliases("name") + .options(StringOptions.standard()) + .build()) + .addFields(ObjectFieldOptions.of("age", IntOptions.standard())) + .build(); + assertThat(JacksonProvider.of(obj).named(Type.stringType()).names()).containsExactly("MyName", "age"); + } + + @Test + void columnNamesWithFieldThatIsNotColumnNameCompatible() { + final ObjectOptions objPlusOneMinusOneCount = ObjectOptions.builder() + .putFields("+1", IntOptions.standard()) + .putFields("-1", IntOptions.standard()) + .build(); + assertThat(JacksonProvider.of(objPlusOneMinusOneCount).named(Type.stringType()).names()) + .containsExactly("column_1", "column_12"); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/RepeatedProcessorTests.java b/extensions/json-jackson/src/test/java/io/deephaven/json/RepeatedProcessorTests.java new file mode 100644 index 00000000000..ed5c997f94f --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/RepeatedProcessorTests.java @@ -0,0 +1,91 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.ObjectChunk; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static io.deephaven.json.TestHelper.parse; + +public class RepeatedProcessorTests { + + @Test + void arrayArrayPrimitive() throws IOException { + // [[1.1], null, [], [2.2, 3.3]] + parse(DoubleOptions.standard().array().array(), + "[[1.1], null, [], [2.2, 3.3]]", + ObjectChunk.chunkWrap(new Object[] { + new double[][] {new double[] {1.1}, null, new double[0], new double[] {2.2, 3.3}}})); + } + + @Test + void arrayKvPrimitive() throws IOException { + // [{"a": 1.1}, null, {}, {"b": 2.2, "c": 3.3}] + parse(ObjectKvOptions.builder().key(SkipOptions.lenient()).value(DoubleOptions.standard()).build().array(), + "[{\"a\": 1.1}, null, {}, {\"b\": 2.2, \"c\": 3.3}]", + ObjectChunk.chunkWrap(new Object[] { + new double[][] {new double[] {1.1}, null, new double[0], new double[] {2.2, 3.3}}})); + } + + @Test + void kvArrayPrimitive() throws IOException { + // {"a": [1.1], "b": null, "c": [], "d": [2.2, 3.3]} + parse(ObjectKvOptions.standard(DoubleOptions.standard().array()), + "{\"a\": [1.1], \"b\": null, \"c\": [], \"d\": [2.2, 3.3]}", + ObjectChunk.chunkWrap(new Object[] { + new String[] {"a", "b", "c", "d"}}), + ObjectChunk.chunkWrap(new Object[] { + new double[][] {new double[] {1.1}, null, new double[0], new double[] {2.2, 3.3}}})); + } + + @Test + void tuple() throws IOException { + // [[[1, 1.1]], null, [], [[2, 2.2], [3, 3.3]]] + parse(TupleOptions.of(IntOptions.standard(), DoubleOptions.standard()).array().array(), + "[[[1, 1.1]], null, [], [[2, 2.2], [3, 3.3]]]", + ObjectChunk.chunkWrap(new Object[] {new int[][] {new int[] {1}, null, new int[0], new int[] {2, 3}}}), + ObjectChunk.chunkWrap(new Object[] { + new double[][] {new double[] {1.1}, null, new double[0], new double[] {2.2, 3.3}}})); + } + + @Test + void object() throws IOException { + // [[{"int": 1, "double": 1.1}], null, [], [{"int": 2}, {"double": 3.3}], [{"int": 4, "double": 4.4}, {"int": 5, + // "double": 5.5}]] + parse(ObjectOptions.builder() + .putFields("int", IntOptions.standard()) + .putFields("double", DoubleOptions.standard()) + .build() + .array() + .array(), + "[[{\"int\": 1, \"double\": 1.1}], null, [], [{\"int\": 2}, {\"double\": 3.3}], [{\"int\": 4, \"double\": 4.4}, {\"int\": 5, \"double\": 5.5}]]", + ObjectChunk.chunkWrap(new Object[] {new int[][] {new int[] {1}, null, new int[0], + new int[] {2, QueryConstants.NULL_INT}, new int[] {4, 5}}}), + ObjectChunk.chunkWrap(new Object[] {new double[][] {new double[] {1.1}, null, new double[0], + new double[] {QueryConstants.NULL_DOUBLE, 3.3}, new double[] {4.4, 5.5}}})); + } + + + @Test + void differentNesting() throws IOException { + // [ { "foo": [ { "bar": 41 }, {} ], "baz": 1.1 }, null, {}, { "foo": [] }, { "foo": [ { "bar": 43 } ], "baz": + // 3.3 }] + parse(ObjectOptions.builder() + .putFields("foo", ObjectOptions.builder() + .putFields("bar", IntOptions.standard()) + .build() + .array()) + .putFields("baz", DoubleOptions.standard()) + .build() + .array(), + "[ { \"foo\": [ { \"bar\": 41 }, {} ], \"baz\": 1.1 }, null, {}, { \"foo\": [] }, { \"foo\": [ { \"bar\": 43 } ], \"baz\": 3.3 }]", + ObjectChunk.chunkWrap(new Object[] { + new int[][] {new int[] {41, QueryConstants.NULL_INT}, null, null, new int[0], new int[] {43}}}), + ObjectChunk.chunkWrap(new Object[] {new double[] {1.1, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE, QueryConstants.NULL_DOUBLE, 3.3}})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java new file mode 100644 index 00000000000..c049a699ea4 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java @@ -0,0 +1,179 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import com.fasterxml.jackson.core.exc.InputCoercionException; +import io.deephaven.chunk.ShortChunk; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class ShortOptionsTest { + + @Test + void standard() throws IOException { + parse(ShortOptions.standard(), "42", ShortChunk.chunkWrap(new short[] {42})); + } + + @Test + void standardMissing() throws IOException { + parse(ShortOptions.standard(), "", ShortChunk.chunkWrap(new short[] {QueryConstants.NULL_SHORT})); + } + + @Test + void standardNull() throws IOException { + parse(ShortOptions.standard(), "null", ShortChunk.chunkWrap(new short[] {QueryConstants.NULL_SHORT})); + } + + @Test + void customMissing() throws IOException { + parse(ShortOptions.builder().onMissing((short) -1).build(), "", ShortChunk.chunkWrap(new short[] {-1})); + } + + @Test + void customNull() throws IOException { + parse(ShortOptions.builder().onNull((short) -2).build(), "null", ShortChunk.chunkWrap(new short[] {-2})); + } + + @Test + void strict() throws IOException { + parse(ShortOptions.strict(), "42", ShortChunk.chunkWrap(new short[] {42})); + } + + @Test + void strictMissing() throws IOException { + try { + parse(ShortOptions.strict(), "", ShortChunk.chunkWrap(new short[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected missing token"); + } + } + + @Test + void strictNull() throws IOException { + try { + parse(ShortOptions.strict(), "null", ShortChunk.chunkWrap(new short[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + } + } + + @Test + void standardOverflow() throws IOException { + try { + parse(ShortOptions.standard(), "2147483648", ShortChunk.chunkWrap(new short[1])); + } catch (InputCoercionException e) { + assertThat(e).hasMessageContaining( + "Numeric value (2147483648) out of range of int (-2147483648 - 2147483647)"); + } + } + + @Test + void standardString() throws IOException { + try { + parse(ShortOptions.standard(), "\"42\"", ShortChunk.chunkWrap(new short[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); + } + } + + @Test + void standardTrue() throws IOException { + try { + parse(ShortOptions.standard(), "true", ShortChunk.chunkWrap(new short[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + } + } + + @Test + void standardFalse() throws IOException { + try { + parse(ShortOptions.standard(), "false", ShortChunk.chunkWrap(new short[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + } + } + + @Test + void standardFloat() throws IOException { + try { + parse(ShortOptions.standard(), "42.0", ShortChunk.chunkWrap(new short[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); + } + } + + @Test + void standardObject() throws IOException { + try { + parse(ShortOptions.standard(), "{}", ShortChunk.chunkWrap(new short[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + } + } + + @Test + void standardArray() throws IOException { + try { + parse(ShortOptions.standard(), "[]", ShortChunk.chunkWrap(new short[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + } + } + + @Test + void lenientString() throws IOException { + parse(ShortOptions.lenient(), List.of("\"42\"", "\"43\""), ShortChunk.chunkWrap(new short[] {42, 43})); + } + + @Test + void allowDecimal() throws IOException { + parse(ShortOptions.builder() + .allowedTypes(JsonValueTypes.INT, JsonValueTypes.DECIMAL) + .build(), List.of("42.42", "43.999"), ShortChunk.chunkWrap(new short[] {42, 43})); + } + + @Test + void allowDecimalString() throws IOException { + parse(ShortOptions.builder() + .allowedTypes(JsonValueTypes.STRING, JsonValueTypes.INT, JsonValueTypes.DECIMAL) + .build(), + List.of("\"42.42\"", "\"43.999\""), ShortChunk.chunkWrap(new short[] {42, 43})); + } + + @Test + void decimalStringLimitsNearMinValue() throws IOException { + for (int i = 0; i < 100; ++i) { + parse(ShortOptions.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) + .build(), + List.of(String.format("\"%d.0\"", Short.MIN_VALUE + i)), + ShortChunk.chunkWrap(new short[] {(short) (Short.MIN_VALUE + i)})); + } + } + + @Test + void decimalStringLimitsNearMaxValue() throws IOException { + for (int i = 0; i < 100; ++i) { + parse(ShortOptions.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) + .build(), + List.of(String.format("\"%d.0\"", Short.MAX_VALUE - i)), + ShortChunk.chunkWrap(new short[] {(short) (Short.MAX_VALUE - i)})); + } + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java new file mode 100644 index 00000000000..8d0d7ad4ba0 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java @@ -0,0 +1,137 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.ObjectChunk; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class StringOptionsTest { + + @Test + void standard() throws IOException { + parse(StringOptions.standard(), "\"foo\"", ObjectChunk.chunkWrap(new String[] {"foo"})); + } + + @Test + void standardMissing() throws IOException { + parse(StringOptions.standard(), "", ObjectChunk.chunkWrap(new String[] {null})); + } + + @Test + void standardNull() throws IOException { + parse(StringOptions.standard(), "null", ObjectChunk.chunkWrap(new String[] {null})); + } + + + @Test + void customMissing() throws IOException { + parse(StringOptions.builder().onMissing("").build(), "", + ObjectChunk.chunkWrap(new String[] {""})); + } + + @Test + void customNull() throws IOException { + parse(StringOptions.builder().onNull("").build(), "null", ObjectChunk.chunkWrap(new String[] {""})); + } + + @Test + void strict() throws IOException { + parse(StringOptions.strict(), "\"foo\"", ObjectChunk.chunkWrap(new String[] {"foo"})); + } + + @Test + void strictMissing() throws IOException { + try { + parse(StringOptions.strict(), "", ObjectChunk.chunkWrap(new String[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected missing token"); + } + } + + @Test + void strictNull() throws IOException { + try { + parse(StringOptions.strict(), "null", ObjectChunk.chunkWrap(new String[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + } + } + + + @Test + void standardInt() throws IOException { + try { + parse(StringOptions.standard(), "42", ObjectChunk.chunkWrap(new String[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_INT'"); + } + } + + @Test + void standardFloat() throws IOException { + try { + parse(StringOptions.standard(), "42.42", ObjectChunk.chunkWrap(new String[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); + } + } + + + @Test + void standardTrue() throws IOException { + try { + parse(StringOptions.standard(), "true", ObjectChunk.chunkWrap(new String[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + } + } + + @Test + void standardFalse() throws IOException { + try { + parse(StringOptions.standard(), "false", ObjectChunk.chunkWrap(new String[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + } + } + + @Test + void standardObject() throws IOException { + try { + parse(StringOptions.standard(), "{}", ObjectChunk.chunkWrap(new String[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + } + } + + @Test + void standardArray() throws IOException { + try { + parse(StringOptions.standard(), "[]", ObjectChunk.chunkWrap(new String[1])); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + } + } + + @Test + void lenientString() throws IOException { + parse(StringOptions.lenient(), List.of("42", "42.42", "true", "false"), + ObjectChunk.chunkWrap(new String[] {"42", "42.42", "true", "false"})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java new file mode 100644 index 00000000000..d7bea99563f --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java @@ -0,0 +1,174 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.BooleanChunk; +import io.deephaven.chunk.ByteChunk; +import io.deephaven.chunk.CharChunk; +import io.deephaven.chunk.Chunk; +import io.deephaven.chunk.DoubleChunk; +import io.deephaven.chunk.FloatChunk; +import io.deephaven.chunk.IntChunk; +import io.deephaven.chunk.LongChunk; +import io.deephaven.chunk.ObjectChunk; +import io.deephaven.chunk.ShortChunk; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableObjectChunk; +import io.deephaven.chunk.attributes.Any; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.processor.ObjectProcessor; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestHelper { + public static void parse(ValueOptions options, String json, Chunk... expected) throws IOException { + parse(options, List.of(json), expected); + } + + public static void parse(ValueOptions options, List jsonRows, Chunk... expectedCols) throws IOException { + parse(JacksonProvider.of(options).stringProcessor(), jsonRows, expectedCols); + } + + public static void parse(ObjectProcessor processor, List rows, Chunk... expectedCols) + throws IOException { + final List> out = processor + .outputTypes() + .stream() + .map(ObjectProcessor::chunkType) + .map(x -> x.makeWritableChunk(rows.size())) + .collect(Collectors.toList()); + try { + assertThat(out.size()).isEqualTo(expectedCols.length); + assertThat(out.stream().map(Chunk::getChunkType).collect(Collectors.toList())) + .isEqualTo(Stream.of(expectedCols).map(Chunk::getChunkType).collect(Collectors.toList())); + for (WritableChunk wc : out) { + wc.setSize(0); + } + try (final WritableObjectChunk in = WritableObjectChunk.makeWritableChunk(rows.size())) { + int i = 0; + for (T input : rows) { + in.set(i, input); + ++i; + } + try { + processor.processAll(in, out); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + for (int i = 0; i < expectedCols.length; ++i) { + check(out.get(i), expectedCols[i]); + } + } finally { + for (WritableChunk wc : out) { + wc.close(); + } + } + } + + static void check(Chunk actual, Chunk expected) { + assertThat(actual.getChunkType()).isEqualTo(expected.getChunkType()); + assertThat(actual.size()).isEqualTo(expected.size()); + switch (actual.getChunkType()) { + case Boolean: + check(actual.asBooleanChunk(), expected.asBooleanChunk()); + break; + case Char: + check(actual.asCharChunk(), expected.asCharChunk()); + break; + case Byte: + check(actual.asByteChunk(), expected.asByteChunk()); + break; + case Short: + check(actual.asShortChunk(), expected.asShortChunk()); + break; + case Int: + check(actual.asIntChunk(), expected.asIntChunk()); + break; + case Long: + check(actual.asLongChunk(), expected.asLongChunk()); + break; + case Float: + check(actual.asFloatChunk(), expected.asFloatChunk()); + break; + case Double: + check(actual.asDoubleChunk(), expected.asDoubleChunk()); + break; + case Object: + check(actual.asObjectChunk(), expected.asObjectChunk()); + break; + default: + throw new IllegalStateException(); + } + } + + private static void check(BooleanChunk actual, BooleanChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(CharChunk actual, CharChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(ByteChunk actual, ByteChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(ShortChunk actual, ShortChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(IntChunk actual, IntChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(LongChunk actual, LongChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(FloatChunk actual, FloatChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(DoubleChunk actual, DoubleChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } + + private static void check(ObjectChunk actual, ObjectChunk expected) { + final int size = actual.size(); + for (int i = 0; i < size; ++i) { + assertThat(actual.get(i)).isEqualTo(expected.get(i)); + } + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java new file mode 100644 index 00000000000..1d2c6f90798 --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java @@ -0,0 +1,40 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.IntChunk; +import io.deephaven.chunk.ObjectChunk; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; + +public class TupleOptionsTest { + + private static final TupleOptions STRING_INT_TUPLE = + TupleOptions.of(StringOptions.standard(), IntOptions.standard()); + + private static final TupleOptions STRING_SKIPINT_TUPLE = + TupleOptions.of(StringOptions.standard(), IntOptions.standard().skip()); + + @Test + void stringIntTuple() throws IOException { + parse(STRING_INT_TUPLE, List.of( + "[\"foo\", 42]", + "[\"bar\", 43]"), + ObjectChunk.chunkWrap(new String[] {"foo", "bar"}), + IntChunk.chunkWrap(new int[] {42, 43})); + + } + + @Test + void stringSkipIntTuple() throws IOException { + parse(STRING_SKIPINT_TUPLE, List.of( + "[\"foo\", 42]", + "[\"bar\", 43]"), + ObjectChunk.chunkWrap(new String[] {"foo", "bar"})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java new file mode 100644 index 00000000000..bc01404cc8e --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java @@ -0,0 +1,55 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.DoubleChunk; +import io.deephaven.chunk.ObjectChunk; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; + +public class TypedObjectOptionsTest { + private static final ObjectOptions QUOTE_OBJECT = ObjectOptions.builder() + .putFields("symbol", StringOptions.strict()) + .putFields("bid", DoubleOptions.standard()) + .putFields("ask", DoubleOptions.standard()) + .build(); + + private static final ObjectOptions TRADE_OBJECT = ObjectOptions.builder() + .putFields("symbol", StringOptions.strict()) + .putFields("price", DoubleOptions.standard()) + .putFields("size", DoubleOptions.standard()) + .build(); + + private static final TypedObjectOptions QUOTE_OR_TRADE_OBJECT = + TypedObjectOptions.strict("type", new LinkedHashMap<>() { + { + put("quote", QUOTE_OBJECT); + put("trade", TRADE_OBJECT); + } + }); + + @Test + void typeDiscriminationQuoteTrade() throws IOException { + parse(QUOTE_OR_TRADE_OBJECT, List.of( + // "", + // "null", + // "{}", + // "{\"type\": null}", + // "{\"type\": \"other\"}", + "{\"type\": \"quote\", \"symbol\": \"foo\", \"bid\": 1.01, \"ask\": 1.05}", + "{\"type\": \"trade\", \"symbol\": \"bar\", \"price\": 42.42, \"size\": 123}"), + ObjectChunk.chunkWrap(new String[] {"quote", "trade"}), // type + ObjectChunk.chunkWrap(new String[] {"foo", "bar"}), // symbol + DoubleChunk.chunkWrap(new double[] {1.01, QueryConstants.NULL_DOUBLE}), // quote/bid + DoubleChunk.chunkWrap(new double[] {1.05, QueryConstants.NULL_DOUBLE}), // quote/ask + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 42.42}), // trade/price + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 123})); // trade/size + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyOptionsTest.java new file mode 100644 index 00000000000..24f31c5905d --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyOptionsTest.java @@ -0,0 +1,107 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.BooleanNode; +import com.fasterxml.jackson.databind.node.DoubleNode; +import com.fasterxml.jackson.databind.node.IntNode; +import com.fasterxml.jackson.databind.node.MissingNode; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import io.deephaven.chunk.DoubleChunk; +import io.deephaven.chunk.IntChunk; +import io.deephaven.chunk.ObjectChunk; +import io.deephaven.json.AnyOptions; +import io.deephaven.json.DoubleOptions; +import io.deephaven.json.IntOptions; +import io.deephaven.json.ObjectOptions; +import io.deephaven.json.TestHelper; +import io.deephaven.json.TupleOptions; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class JacksonAnyOptionsTest { + + @Test + void anyMissing() throws IOException { + checkAny("", MissingNode.getInstance()); + } + + @Test + void anyNull() throws IOException { + checkAny("null", NullNode.getInstance()); + } + + @Test + void anyTrue() throws IOException { + checkAny("true", BooleanNode.getTrue()); + } + + @Test + void anyFalse() throws IOException { + checkAny("false", BooleanNode.getFalse()); + } + + @Test + void anyNumberInt() throws IOException { + checkAny("42", IntNode.valueOf(42)); + } + + @Test + void anyNumberFloat() throws IOException { + checkAny("42.42", DoubleNode.valueOf(42.42)); + } + + @Test + void anyNumberString() throws IOException { + checkAny("\"my string\"", TextNode.valueOf("my string")); + } + + @Test + void anyObject() throws IOException { + checkAny("{}", new ObjectNode(null, Map.of())); + checkAny("{\"foo\": 42}", new ObjectNode(null, Map.of("foo", IntNode.valueOf(42)))); + } + + @Test + void anyArray() throws IOException { + checkAny("[]", new ArrayNode(null, List.of())); + checkAny("[42]", new ArrayNode(null, List.of(IntNode.valueOf(42)))); + } + + @Test + void anyInTuple() throws IOException { + final TupleOptions options = TupleOptions.of(IntOptions.standard(), AnyOptions.of(), DoubleOptions.standard()); + TestHelper.parse(options, List.of("", "[42, {\"zip\": 43}, 44.44]"), + IntChunk.chunkWrap(new int[] {QueryConstants.NULL_INT, 42}), + ObjectChunk.chunkWrap(new TreeNode[] {MissingNode.getInstance(), + new ObjectNode(null, Map.of("zip", IntNode.valueOf(43)))}), + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 44.44})); + } + + @Test + void anyInObject() throws IOException { + final ObjectOptions options = ObjectOptions.builder() + .putFields("foo", IntOptions.standard()) + .putFields("bar", AnyOptions.of()) + .putFields("baz", DoubleOptions.standard()) + .build(); + TestHelper.parse(options, List.of("", "{\"foo\": 42, \"bar\": {\"zip\": 43}, \"baz\": 44.44}"), + IntChunk.chunkWrap(new int[] {QueryConstants.NULL_INT, 42}), + ObjectChunk.chunkWrap(new TreeNode[] {MissingNode.getInstance(), + new ObjectNode(null, Map.of("zip", IntNode.valueOf(43)))}), + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 44.44})); + } + + private static void checkAny(String json, TreeNode expected) throws IOException { + TestHelper.parse(AnyOptions.of(), json, ObjectChunk.chunkWrap(new TreeNode[] {expected})); + } +} diff --git a/extensions/json-jackson/src/test/resources/io/deephaven/json/test-array-objects.json b/extensions/json-jackson/src/test/resources/io/deephaven/json/test-array-objects.json new file mode 100644 index 00000000000..7ae79bc9505 --- /dev/null +++ b/extensions/json-jackson/src/test/resources/io/deephaven/json/test-array-objects.json @@ -0,0 +1,10 @@ +[ + { + "name": "foo", + "age": 42 + }, + { + "name": "bar", + "age": 43 + } +] diff --git a/extensions/json-jackson/src/test/resources/io/deephaven/json/test-compact-objects.json.txt b/extensions/json-jackson/src/test/resources/io/deephaven/json/test-compact-objects.json.txt new file mode 100644 index 00000000000..2033322908e --- /dev/null +++ b/extensions/json-jackson/src/test/resources/io/deephaven/json/test-compact-objects.json.txt @@ -0,0 +1 @@ +{"name":"foo","age":42}{"name":"bar","age":43} \ No newline at end of file diff --git a/extensions/json-jackson/src/test/resources/io/deephaven/json/test-double-nested-array-objects.json b/extensions/json-jackson/src/test/resources/io/deephaven/json/test-double-nested-array-objects.json new file mode 100644 index 00000000000..1b5138db604 --- /dev/null +++ b/extensions/json-jackson/src/test/resources/io/deephaven/json/test-double-nested-array-objects.json @@ -0,0 +1,14 @@ +{ + "outer": { + "inner": [ + { + "name": "foo", + "age": 42 + }, + { + "name": "bar", + "age": 43 + } + ] + } +} \ No newline at end of file diff --git a/extensions/json-jackson/src/test/resources/io/deephaven/json/test-nested-array-objects.json b/extensions/json-jackson/src/test/resources/io/deephaven/json/test-nested-array-objects.json new file mode 100644 index 00000000000..8b2b3745ba9 --- /dev/null +++ b/extensions/json-jackson/src/test/resources/io/deephaven/json/test-nested-array-objects.json @@ -0,0 +1,12 @@ +{ + "data": [ + { + "name": "foo", + "age": 42 + }, + { + "name": "bar", + "age": 43 + } + ] +} \ No newline at end of file diff --git a/extensions/json-jackson/src/test/resources/io/deephaven/json/test-newline-objects.json.txt b/extensions/json-jackson/src/test/resources/io/deephaven/json/test-newline-objects.json.txt new file mode 100644 index 00000000000..6ea285be438 --- /dev/null +++ b/extensions/json-jackson/src/test/resources/io/deephaven/json/test-newline-objects.json.txt @@ -0,0 +1,8 @@ +{ + "name": "foo", + "age": 42 +} +{ + "name": "bar", + "age": 43 +} diff --git a/extensions/json-jackson/src/test/resources/io/deephaven/json/test-single-object.json b/extensions/json-jackson/src/test/resources/io/deephaven/json/test-single-object.json new file mode 100644 index 00000000000..cabe16dfbc3 --- /dev/null +++ b/extensions/json-jackson/src/test/resources/io/deephaven/json/test-single-object.json @@ -0,0 +1,4 @@ +{ + "name": "foo", + "age": 42 +} diff --git a/extensions/json/build.gradle b/extensions/json/build.gradle new file mode 100644 index 00000000000..fb8e227a7d0 --- /dev/null +++ b/extensions/json/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java-library' + id 'io.deephaven.project.register' +} + +dependencies { + api project(':engine-processor') + + Classpaths.inheritImmutables(project) + compileOnly 'com.google.code.findbugs:jsr305:3.0.2' + + Classpaths.inheritJUnitPlatform(project) + Classpaths.inheritAssertJ(project) + testImplementation 'org.junit.jupiter:junit-jupiter' +} + +test { + useJUnitPlatform() +} diff --git a/extensions/json/gradle.properties b/extensions/json/gradle.properties new file mode 100644 index 00000000000..c186bbfdde1 --- /dev/null +++ b/extensions/json/gradle.properties @@ -0,0 +1 @@ +io.deephaven.project.ProjectType=JAVA_PUBLIC diff --git a/extensions/json/src/main/java/io/deephaven/json/AnyOptions.java b/extensions/json/src/main/java/io/deephaven/json/AnyOptions.java new file mode 100644 index 00000000000..3c9198fa9ba --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/AnyOptions.java @@ -0,0 +1,42 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.SimpleStyle; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; + +/** + * Processes a JSON value as an implementation-specific object. + */ +@Immutable +@SimpleStyle +public abstract class AnyOptions extends ValueOptions { + + public static AnyOptions of() { + return ImmutableAnyOptions.of(); + } + + /** + * Always {@link JsonValueTypes#ALL}. + */ + @Override + public final EnumSet allowedTypes() { + return JsonValueTypes.ALL; + } + + /** + * Always {@code true}. + */ + @Override + public final boolean allowMissing() { + return true; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java b/extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java new file mode 100644 index 00000000000..f6b29d23232 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java @@ -0,0 +1,60 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; + +@Immutable +@BuildableStyle +public abstract class ArrayOptions extends ValueOptionsRestrictedUniverseBase { + + public static Builder builder() { + return ImmutableArrayOptions.builder(); + } + + public static ArrayOptions standard(ValueOptions element) { + return builder().element(element).build(); + } + + public static ArrayOptions strict(ValueOptions element) { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.ARRAY) + .element(element) + .build(); + } + + public abstract ValueOptions element(); + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#ARRAY_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.ARRAY_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#ARRAY_OR_NULL}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.ARRAY_OR_NULL; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptions.Builder { + + Builder element(ValueOptions options); + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java b/extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java new file mode 100644 index 00000000000..8ee26f9ae2f --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java @@ -0,0 +1,64 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.math.BigDecimal; +import java.util.EnumSet; + +/** + * Processes a JSON value as a {@link BigDecimal}. + */ +@Immutable +@BuildableStyle +public abstract class BigDecimalOptions extends ValueOptionsSingleValueBase { + + public static Builder builder() { + return ImmutableBigDecimalOptions.builder(); + } + + public static BigDecimalOptions lenient() { + return builder().allowedTypes(JsonValueTypes.NUMBER_LIKE).build(); + } + + public static BigDecimalOptions standard() { + return builder().build(); + } + + public static BigDecimalOptions strict() { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.NUMBER) + .build(); + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#NUMBER_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.NUMBER_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.NUMBER_LIKE; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptionsSingleValueBase.Builder { + + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java b/extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java new file mode 100644 index 00000000000..b1bc759858a --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java @@ -0,0 +1,57 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Immutable; + +import java.math.BigInteger; +import java.util.EnumSet; + +/** + * Processes a JSON value as a {@link BigInteger}. + */ +@Immutable +@BuildableStyle +public abstract class BigIntegerOptions extends ValueOptionsSingleValueBase { + public static Builder builder() { + return ImmutableBigIntegerOptions.builder(); + } + + public static BigIntegerOptions lenient(boolean allowDecimal) { + return builder() + .allowedTypes(allowDecimal ? JsonValueTypes.NUMBER_LIKE : JsonValueTypes.INT_LIKE) + .build(); + } + + public static BigIntegerOptions standard(boolean allowDecimal) { + return builder() + .allowedTypes(allowDecimal ? JsonValueTypes.NUMBER_OR_NULL : JsonValueTypes.INT_OR_NULL) + .build(); + } + + public static BigIntegerOptions strict(boolean allowDecimal) { + return builder() + .allowMissing(false) + .allowedTypes(allowDecimal ? JsonValueTypes.NUMBER : EnumSet.of(JsonValueTypes.INT)) + .build(); + } + + /** + * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.NUMBER_LIKE; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptionsSingleValueBase.Builder { + + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java b/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java new file mode 100644 index 00000000000..14bc4dea56d --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java @@ -0,0 +1,91 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; + +/** + * Processes a JSON value as a {@code boolean}. + */ +@Immutable +@BuildableStyle +public abstract class BoolOptions extends ValueOptionsSingleValueBase { + + public static Builder builder() { + return ImmutableBoolOptions.builder(); + } + + /** + * The lenient bool options. + * + * @return the lenient bool options + */ + public static BoolOptions lenient() { + return builder() + .allowedTypes(JsonValueTypes.BOOL_LIKE) + .build(); + } + + /** + * The standard bool options. + * + * @return the standard bool options + */ + public static BoolOptions standard() { + return builder().build(); + } + + /** + * The strict bool options. + * + * @return the strict bool options + */ + public static BoolOptions strict() { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.BOOL) + .build(); + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#BOOL_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.BOOL_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#BOOL_LIKE}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.BOOL_LIKE; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptionsSingleValueBase.Builder { + + Builder onNull(boolean onNull); + + Builder onMissing(boolean onMissing); + + default Builder onNull(Boolean onNull) { + return onNull((boolean) onNull); + } + + default Builder onMissing(Boolean onMissing) { + return onMissing((boolean) onMissing); + } + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java b/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java new file mode 100644 index 00000000000..7008f3546f1 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java @@ -0,0 +1,91 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; + +/** + * Processes a JSON value as a {@code byte}. + */ +@Immutable +@BuildableStyle +public abstract class ByteOptions extends ValueOptionsSingleValueBase { + + public static Builder builder() { + return ImmutableByteOptions.builder(); + } + + /** + * The lenient byte options. + * + * @return the lenient byte options + */ + public static ByteOptions lenient() { + return builder() + .allowedTypes(JsonValueTypes.INT_LIKE) + .build(); + } + + /** + * The standard byte options. + * + * @return the standard byte options + */ + public static ByteOptions standard() { + return builder().build(); + } + + /** + * The strict byte options. + * + * @return the strict byte options + */ + public static ByteOptions strict() { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.INT) + .build(); + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#INT_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.INT_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.NUMBER_LIKE; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptionsSingleValueBase.Builder { + + Builder onNull(byte onNull); + + Builder onMissing(byte onMissing); + + default Builder onNull(Byte onNull) { + return onNull((byte) onNull); + } + + default Builder onMissing(Byte onMissing) { + return onMissing((byte) onMissing); + } + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/CharOptions.java b/extensions/json/src/main/java/io/deephaven/json/CharOptions.java new file mode 100644 index 00000000000..f59457b0de8 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/CharOptions.java @@ -0,0 +1,81 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; + +/** + * Processes a JSON value as a {@code char}. + */ +@Immutable +@BuildableStyle +public abstract class CharOptions extends ValueOptionsSingleValueBase { + + public static Builder builder() { + return ImmutableCharOptions.builder(); + } + + + /** + * The standard char options. + * + * @return the standard char options + */ + public static CharOptions standard() { + return builder().build(); + } + + /** + * The strict char options. + * + * @return the strict char options + */ + public static CharOptions strict() { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.STRING) + .build(); + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#STRING_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.STRING_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#STRING_OR_NULL}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.STRING_OR_NULL; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptionsSingleValueBase.Builder { + + Builder onNull(char onNull); + + Builder onMissing(char onMissing); + + default Builder onNull(Character onNull) { + return onNull((char) onNull); + } + + default Builder onMissing(Character onMissing) { + return onMissing((char) onMissing); + } + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java b/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java new file mode 100644 index 00000000000..b77f68a5639 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java @@ -0,0 +1,91 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; + +/** + * Processes a JSON value as a {@code double}. + */ +@Immutable +@BuildableStyle +public abstract class DoubleOptions extends ValueOptionsSingleValueBase { + + + public static Builder builder() { + return ImmutableDoubleOptions.builder(); + } + + /** + * The lenient double options. + * + * @return the lenient double options + */ + public static DoubleOptions lenient() { + return builder().allowedTypes(JsonValueTypes.NUMBER_LIKE).build(); + } + + /** + * The standard double options. + * + * @return the standard double options + */ + public static DoubleOptions standard() { + return builder().build(); + } + + /** + * The strict double options. + * + * @return the strict double options + */ + public static DoubleOptions strict() { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.NUMBER) + .build(); + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#NUMBER_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.NUMBER_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.NUMBER_LIKE; + } + + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptionsSingleValueBase.Builder { + + Builder onNull(double onNull); + + Builder onMissing(double onMissing); + + default Builder onNull(Double onNull) { + return onNull((double) onNull); + } + + default Builder onMissing(Double onMissing) { + return onMissing((double) onMissing); + } + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java b/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java new file mode 100644 index 00000000000..6af05b516a9 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java @@ -0,0 +1,88 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; + +/** + * Processes a JSON value as a {@code float}. + */ +@Immutable +@BuildableStyle +public abstract class FloatOptions extends ValueOptionsSingleValueBase { + + public static Builder builder() { + return ImmutableFloatOptions.builder(); + } + + /** + * The lenient float options. + * + * @return the lenient float options + */ + public static FloatOptions lenient() { + return builder().allowedTypes(JsonValueTypes.NUMBER_LIKE).build(); + } + + /** + * The standard float options.. + * + * @return the standard float options + */ + public static FloatOptions standard() { + return builder().build(); + } + + /** + * The strict float options. + * + * @return the strict float options + */ + public static FloatOptions strict() { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.NUMBER) + .build(); + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#NUMBER_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.NUMBER_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.NUMBER_LIKE; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptions.Builder { + Builder onNull(float onNull); + + Builder onMissing(float onMissing); + + default Builder onNull(Float onNull) { + return onNull((float) onNull); + } + + default Builder onMissing(Float onMissing) { + return onMissing((float) onMissing); + } + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java b/extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java new file mode 100644 index 00000000000..5ed86326f00 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java @@ -0,0 +1,80 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.time.Instant; +import java.util.EnumSet; + +/** + * Processes a JSON number as an {@link Instant}. + */ +@Immutable +@BuildableStyle +public abstract class InstantNumberOptions extends ValueOptionsSingleValueBase { + + public enum Format { + EPOCH_SECONDS, EPOCH_MILLIS, EPOCH_MICROS, EPOCH_NANOS; + + public InstantNumberOptions lenient(boolean allowDecimal) { + return builder() + .format(this) + .allowedTypes(allowDecimal ? JsonValueTypes.NUMBER_LIKE : JsonValueTypes.INT_LIKE) + .build(); + } + + public InstantNumberOptions standard(boolean allowDecimal) { + return builder() + .format(this) + .allowedTypes(allowDecimal ? JsonValueTypes.NUMBER_OR_NULL : JsonValueTypes.INT_OR_NULL) + .build(); + } + + public InstantNumberOptions strict(boolean allowDecimal) { + return builder() + .format(this) + .allowMissing(false) + .allowedTypes(allowDecimal ? JsonValueTypes.NUMBER : EnumSet.of(JsonValueTypes.INT)) + .build(); + } + } + + public static Builder builder() { + return ImmutableInstantNumberOptions.builder(); + } + + /** + * The format to use. + */ + public abstract Format format(); + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#INT_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.INT_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.NUMBER_LIKE; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptionsSingleValueBase.Builder { + Builder format(Format format); + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/InstantOptions.java b/extensions/json/src/main/java/io/deephaven/json/InstantOptions.java new file mode 100644 index 00000000000..b7376148557 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/InstantOptions.java @@ -0,0 +1,83 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.lang.Runtime.Version; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.EnumSet; + +/** + * Processes a JSON string as an {@link Instant}. + */ +@Immutable +@BuildableStyle +public abstract class InstantOptions extends ValueOptionsSingleValueBase { + + private static final Version VERSION_12 = Version.parse("12"); + + public static Builder builder() { + return ImmutableInstantOptions.builder(); + } + + public static InstantOptions standard() { + return builder().build(); + } + + public static InstantOptions strict() { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.STRING) + .build(); + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#STRING_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.STRING_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#STRING_OR_NULL}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.STRING_OR_NULL; + } + + /** + * The date-time formatter to use for {@link DateTimeFormatter#parse(CharSequence) parsing}. The parsed result must + * support extracting {@link java.time.temporal.ChronoField#INSTANT_SECONDS INSTANT_SECONDS} and + * {@link java.time.temporal.ChronoField#NANO_OF_SECOND NANO_OF_SECOND} fields. Defaults to + * {@link DateTimeFormatter#ISO_INSTANT} for java versions 12+, and {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME} + * otherwise. These defaults will parse offsets, converting to UTC as necessary. + * + * @return the date-time formatter + */ + @Default + public DateTimeFormatter dateTimeFormatter() { + // ISO_INSTANT became more versatile in 12+ (handling the parsing of offsets), and is likely more efficient, so + // we should choose to use it when we can. + return Runtime.version().compareTo(VERSION_12) >= 0 + ? DateTimeFormatter.ISO_INSTANT + : DateTimeFormatter.ISO_OFFSET_DATE_TIME; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptionsSingleValueBase.Builder { + + Builder dateTimeFormatter(DateTimeFormatter formatter); + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/IntOptions.java b/extensions/json/src/main/java/io/deephaven/json/IntOptions.java new file mode 100644 index 00000000000..39c915043d1 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/IntOptions.java @@ -0,0 +1,94 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Check; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; +import java.util.OptionalInt; + +/** + * Processes a JSON value as an {@code int}. + */ +@Immutable +@BuildableStyle +public abstract class IntOptions extends ValueOptionsSingleValueBase { + + public static Builder builder() { + return ImmutableIntOptions.builder(); + } + + /** + * The lenient int options. + * + * @return the lenient int options + */ + public static IntOptions lenient() { + return builder() + .allowedTypes(JsonValueTypes.INT_LIKE) + .build(); + } + + /** + * The standard int options. + * + * @return the standard int options + */ + public static IntOptions standard() { + return builder().build(); + } + + /** + * The strict int options. + * + * @return the strict int options + */ + public static IntOptions strict() { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.INT) + .build(); + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#INT_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.INT_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.NUMBER_LIKE; + } + + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptionsSingleValueBase.Builder { + + Builder onNull(int onNull); + + Builder onMissing(int onMissing); + + default Builder onNull(Integer onNull) { + return onNull((int) onNull); + } + + default Builder onMissing(Integer onMissing) { + return onMissing((int) onMissing); + } + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/JsonValueTypes.java b/extensions/json/src/main/java/io/deephaven/json/JsonValueTypes.java new file mode 100644 index 00000000000..c1f30ac62c2 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/JsonValueTypes.java @@ -0,0 +1,87 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import java.util.EnumSet; + +/** + * The JSON value types. + */ +public enum JsonValueTypes { + OBJECT, ARRAY, STRING, INT, DECIMAL, BOOL, NULL; + + /** + * The set of all {@link JsonValueTypes}. + */ + public static final EnumSet ALL = EnumSet.allOf(JsonValueTypes.class); + + /** + * The set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#NULL}. + */ + public static final EnumSet INT_OR_NULL = EnumSet.of(INT, NULL); + + /** + * The set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#STRING}, {@link JsonValueTypes#NULL}. + */ + public static final EnumSet INT_LIKE = EnumSet.of(INT, STRING, NULL); + + /** + * The set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#DECIMAL}. + */ + public static final EnumSet NUMBER = EnumSet.of(INT, DECIMAL); + + /** + * The set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#DECIMAL}, {@link JsonValueTypes#NULL}. + */ + public static final EnumSet NUMBER_OR_NULL = EnumSet.of(INT, DECIMAL, NULL); + + /** + * The set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#DECIMAL}, {@link JsonValueTypes#STRING}, + * {@link JsonValueTypes#NULL}. + */ + public static final EnumSet NUMBER_LIKE = EnumSet.of(INT, DECIMAL, STRING, NULL); + + /** + * The set of {@link JsonValueTypes#STRING}, {@link JsonValueTypes#NULL}. + */ + public static final EnumSet STRING_OR_NULL = EnumSet.of(STRING, NULL); + + /** + * The set of {@link JsonValueTypes#STRING}, {@link JsonValueTypes#INT}, {@link JsonValueTypes#DECIMAL}, + * {@link JsonValueTypes#BOOL}, {@link JsonValueTypes#NULL}. + */ + public static final EnumSet STRING_LIKE = EnumSet.of(STRING, INT, DECIMAL, BOOL, NULL); + + /** + * The set of {@link JsonValueTypes#BOOL}, {@link JsonValueTypes#NULL}. + */ + public static final EnumSet BOOL_OR_NULL = EnumSet.of(BOOL, NULL); + + /** + * The set of {@link JsonValueTypes#BOOL}, {@link JsonValueTypes#STRING}, {@link JsonValueTypes#NULL}. + */ + public static final EnumSet BOOL_LIKE = EnumSet.of(STRING, BOOL, NULL); + + /** + * The set of {@link JsonValueTypes#OBJECT}, {@link JsonValueTypes#NULL}. + */ + public static final EnumSet OBJECT_OR_NULL = EnumSet.of(OBJECT, NULL); + + /** + * The set of {@link JsonValueTypes#ARRAY}, {@link JsonValueTypes#NULL}. + */ + public static final EnumSet ARRAY_OR_NULL = EnumSet.of(ARRAY, NULL); + + static void checkAllowedTypeInvariants(EnumSet allowedTypes) { + if (allowedTypes.isEmpty()) { + throw new IllegalArgumentException("allowedTypes is empty"); + } + if (allowedTypes.size() == 1 && allowedTypes.contains(JsonValueTypes.NULL)) { + throw new IllegalArgumentException("allowedTypes is only accepting NULL"); + } + if (allowedTypes.contains(JsonValueTypes.DECIMAL) && !allowedTypes.contains(JsonValueTypes.INT)) { + throw new IllegalArgumentException("allowedTypes is accepting DECIMAL but not INT"); + } + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java b/extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java new file mode 100644 index 00000000000..85dadab4f77 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java @@ -0,0 +1,74 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoField; +import java.util.EnumSet; + +/** + * Processes a JSON string as an {@link LocalDate}. + */ +@Immutable +@BuildableStyle +public abstract class LocalDateOptions extends ValueOptionsSingleValueBase { + public static Builder builder() { + return ImmutableLocalDateOptions.builder(); + } + + public static LocalDateOptions standard() { + return builder().build(); + } + + public static LocalDateOptions strict() { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.STRING) + .build(); + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#STRING_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.STRING_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#STRING_OR_NULL}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.STRING_OR_NULL; + } + + /** + * The date-time formatter to use for {@link DateTimeFormatter#parse(CharSequence) parsing}. The parsed result must + * support extracting an {@link ChronoField#EPOCH_DAY EPOCH_DAY} field. Defaults to + * {@link DateTimeFormatter#ISO_LOCAL_DATE}. + * + * @return the date-time formatter + */ + @Default + public DateTimeFormatter dateTimeFormatter() { + return DateTimeFormatter.ISO_LOCAL_DATE; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptionsSingleValueBase.Builder { + + Builder dateTimeFormatter(DateTimeFormatter formatter); + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/LongOptions.java b/extensions/json/src/main/java/io/deephaven/json/LongOptions.java new file mode 100644 index 00000000000..7a2951d3438 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/LongOptions.java @@ -0,0 +1,93 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Check; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; +import java.util.OptionalLong; + +/** + * Processes a JSON value as a {@code long}. + */ +@Immutable +@BuildableStyle +public abstract class LongOptions extends ValueOptionsSingleValueBase { + + public static Builder builder() { + return ImmutableLongOptions.builder(); + } + + /** + * The lenient long options. + * + * @return the lenient long options + */ + public static LongOptions lenient() { + return builder() + .allowedTypes(JsonValueTypes.INT_LIKE) + .build(); + } + + /** + * The standard long options. + * + * @return the standard long options + */ + public static LongOptions standard() { + return builder().build(); + } + + /** + * The strict long options. + * + * @return the strict long options + */ + public static LongOptions strict() { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.INT) + .build(); + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#INT_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.INT_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.NUMBER_LIKE; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptions.Builder { + + Builder onNull(long onNull); + + Builder onMissing(long onMissing); + + default Builder onNull(Long onNull) { + return onNull((long) onNull); + } + + default Builder onMissing(Long onMissing) { + return onMissing((long) onMissing); + } + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/ObjectFieldOptions.java b/extensions/json/src/main/java/io/deephaven/json/ObjectFieldOptions.java new file mode 100644 index 00000000000..3fd9d9c82da --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/ObjectFieldOptions.java @@ -0,0 +1,154 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Check; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; + +@Immutable +@BuildableStyle +public abstract class ObjectFieldOptions { + + public static Builder builder() { + return ImmutableObjectFieldOptions.builder(); + } + + /** + * Creates a field with a single {@code name}. Equivalent to {@code builder().name(name).options(options).build()}. + * + * @param name the name + * @param options the options + * @return the field options + */ + public static ObjectFieldOptions of(String name, ValueOptions options) { + return builder().name(name).options(options).build(); + } + + /** + * The canonical field name. + */ + public abstract String name(); + + /** + * The value options. + */ + public abstract ValueOptions options(); + + /** + * The field name aliases. + */ + public abstract Set aliases(); + + /** + * If the field name and aliases should be compared using case-sensitive equality. By default is {@code true}. + */ + @Default + public boolean caseSensitive() { + return true; + } + + /** + * The behavior when a repeated field is encountered. By default is {@link RepeatedBehavior#ERROR}. + */ + @Default + public RepeatedBehavior repeatedBehavior() { + return RepeatedBehavior.ERROR; + } + + /** + * The array group for {@code this} field. This is useful in scenarios where {@code this} field's array is + * guaranteed to have the same cardinality as one or more other array fields. For example, in the following snippet, + * we might model "prices" and "quantities" as having the same array group: + * + *
+     * {
+     *   "prices": [1.1, 2.2, 3.3],
+     *   "quantities": [9, 5, 42]
+     * }
+     * 
+ */ + public abstract Optional arrayGroup(); + + /** + * The behavior when a repeated field is encountered in a JSON object. For example, as in: + * + *
+     * {
+     *   "foo": 1,
+     *   "foo": 2
+     * }
+     * 
+ */ + public enum RepeatedBehavior { + /** + * Throws an error if a repeated field is encountered + */ + ERROR, + + /** + * Uses the first field of a given name, ignores the rest + */ + USE_FIRST, + + // /** + // * Uses the last field of a given name, ignores the rest. Not currently supported. + // */ + // USE_LAST + } + + public interface Builder { + Builder name(String name); + + Builder options(ValueOptions options); + + Builder addAliases(String element); + + Builder addAliases(String... elements); + + Builder addAllAliases(Iterable elements); + + Builder repeatedBehavior(RepeatedBehavior repeatedBehavior); + + Builder caseSensitive(boolean caseSensitive); + + Builder arrayGroup(Object arrayGroup); + + ObjectFieldOptions build(); + } + + @Check + final void checkNonOverlapping() { + if (caseSensitive()) { + if (aliases().contains(name())) { + throw new IllegalArgumentException( + String.format("name and aliases must be non-overlapping, found '%s' overlaps", name())); + } + } else { + final Set set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + set.add(name()); + for (String alias : aliases()) { + if (!set.add(alias)) { + throw new IllegalArgumentException( + String.format("name and aliases must be non-overlapping, found '%s' overlaps", alias)); + } + } + } + } + + @Check + final void checkArrayGroup() { + if (arrayGroup().isEmpty()) { + return; + } + if (!(options() instanceof ArrayOptions)) { + throw new IllegalArgumentException("arrayGroup is only valid with ArrayOptions"); + } + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java b/extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java new file mode 100644 index 00000000000..2430a1cfff3 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java @@ -0,0 +1,97 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import io.deephaven.json.ObjectFieldOptions.RepeatedBehavior; +import org.immutables.value.Value.Check; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; + +/** + * Processes a JSON object of variable size with a given key and value options. For example, when a JSON object + * structure represents a typed map-like structure (as opposed to a set of typed fields): + * + *
+ * {
+ *   "foo": 1,
+ *   "bar": 42,
+ *   "baz": 3,
+ *   ...
+ *   "xyz": 100
+ * }
+ * 
+ */ +@Immutable +@BuildableStyle +public abstract class ObjectKvOptions extends ValueOptionsRestrictedUniverseBase { + + public static Builder builder() { + return ImmutableObjectKvOptions.builder(); + } + + public static ObjectKvOptions standard(ValueOptions value) { + return builder().value(value).build(); + } + + public static ObjectKvOptions strict(ValueOptions value) { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.OBJECT) + .value(value) + .build(); + } + + /** + * The key options which must minimally support {@link JsonValueTypes#STRING}. By default is + * {@link StringOptions#standard()}. + */ + @Default + public ValueOptions key() { + return StringOptions.standard(); + } + + /** + * The value options. + */ + public abstract ValueOptions value(); + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#OBJECT_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.OBJECT_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#OBJECT_OR_NULL}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.OBJECT_OR_NULL; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptions.Builder { + + Builder key(ValueOptions key); + + Builder value(ValueOptions value); + } + + @Check + final void checkKey() { + if (!key().allowedTypes().contains(JsonValueTypes.STRING)) { + throw new IllegalArgumentException("key argument must support STRING"); + } + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java b/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java new file mode 100644 index 00000000000..fd08f63b754 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java @@ -0,0 +1,161 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import io.deephaven.json.ObjectFieldOptions.RepeatedBehavior; +import org.immutables.value.Value.Check; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; + +/** + * Processes a JSON object as set of named fields. For example: + * + *
+ * {
+ *     "name": "Foo",
+ *     "age": 42,
+ *     "height": 6.5
+ * }
+ * 
+ */ +@Immutable +@BuildableStyle +public abstract class ObjectOptions extends ValueOptionsRestrictedUniverseBase { + + public static Builder builder() { + return ImmutableObjectOptions.builder(); + } + + /** + * The lenient object options. The object fields are constructed with {@link ObjectFieldOptions#caseSensitive()} as + * {@code false} and {@link ObjectFieldOptions#repeatedBehavior()} as + * {@link ObjectFieldOptions.RepeatedBehavior#USE_FIRST}. + * + * @param fields the fields + * @return the lenient object options + */ + public static ObjectOptions lenient(Map fields) { + final Builder builder = builder(); + for (Entry e : fields.entrySet()) { + builder.addFields(ObjectFieldOptions.builder() + .name(e.getKey()) + .options(e.getValue()) + .caseSensitive(false) + .repeatedBehavior(RepeatedBehavior.USE_FIRST) + .build()); + } + return builder.build(); + } + + /** + * The standard object options. + * + * @param fields the fields + * @return the standard object options + */ + public static ObjectOptions standard(Map fields) { + final Builder builder = builder(); + for (Entry e : fields.entrySet()) { + builder.addFields(ObjectFieldOptions.of(e.getKey(), e.getValue())); + } + return builder.build(); + } + + /** + * The strict object options. + * + * @param fields the fields + * @return the strict object options + */ + public static ObjectOptions strict(Map fields) { + final Builder builder = builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.OBJECT); + for (Entry e : fields.entrySet()) { + builder.addFields(ObjectFieldOptions.of(e.getKey(), e.getValue())); + } + return builder.build(); + } + + /** + * The fields. + */ + public abstract Set fields(); + + /** + * If unknown fields are allowed. By default is {@code true}. + */ + @Default + public boolean allowUnknownFields() { + return true; + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#OBJECT_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.OBJECT_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#OBJECT_OR_NULL}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.OBJECT_OR_NULL; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + // not extending value options + public interface Builder extends ValueOptions.Builder { + + Builder allowUnknownFields(boolean allowUnknownFields); + + /** + * A convenience method, equivalent to {@code addFields(ObjectFieldOptions.of(key, value))}. + */ + default Builder putFields(String key, ValueOptions value) { + return addFields(ObjectFieldOptions.of(key, value)); + } + + Builder addFields(ObjectFieldOptions element); + + Builder addFields(ObjectFieldOptions... elements); + + Builder addAllFields(Iterable elements); + } + + @Check + final void checkNonOverlapping() { + // We need to make sure there is no inter-field overlapping. We will be stricter if _any_ field is + // case-insensitive. + final Set keys = fields().stream().allMatch(ObjectFieldOptions::caseSensitive) + ? new HashSet<>() + : new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + for (ObjectFieldOptions field : fields()) { + if (!keys.add(field.name())) { + throw new IllegalArgumentException(String.format("Found overlapping field name '%s'", field.name())); + } + for (String alias : field.aliases()) { + if (!keys.add(alias)) { + throw new IllegalArgumentException(String.format("Found overlapping field alias '%s'", alias)); + } + } + } + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java b/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java new file mode 100644 index 00000000000..968e31b0f16 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java @@ -0,0 +1,91 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; + +/** + * Processes a JSON value as an {@code short}. + */ +@Immutable +@BuildableStyle +public abstract class ShortOptions extends ValueOptionsSingleValueBase { + + public static Builder builder() { + return ImmutableShortOptions.builder(); + } + + /** + * The lenient short options. + * + * @return the lenient short options + */ + public static ShortOptions lenient() { + return builder() + .allowedTypes(JsonValueTypes.INT_LIKE) + .build(); + } + + /** + * The standard short options. + * + * @return the standard short options + */ + public static ShortOptions standard() { + return builder().build(); + } + + /** + * The strict short options. + * + * @return the strict short options + */ + public static ShortOptions strict() { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.INT) + .build(); + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#INT_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.INT_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.NUMBER_LIKE; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptionsSingleValueBase.Builder { + + Builder onNull(short onNull); + + Builder onMissing(short onMissing); + + default Builder onNull(Short onNull) { + return onNull((short) onNull); + } + + default Builder onMissing(Short onMissing) { + return onMissing((short) onMissing); + } + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/SkipOptions.java b/extensions/json/src/main/java/io/deephaven/json/SkipOptions.java new file mode 100644 index 00000000000..bdf57cff08c --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/SkipOptions.java @@ -0,0 +1,44 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; + +/** + * Processes a JSON value by skipping it. + */ +@Immutable +@BuildableStyle +public abstract class SkipOptions extends ValueOptions { + + public static Builder builder() { + return ImmutableSkipOptions.builder(); + } + + public static SkipOptions lenient() { + return builder().build(); + } + + /** + * The allowed types. By default is {@link JsonValueTypes#ALL}. + */ + @Override + @Default + public EnumSet allowedTypes() { + return JsonValueTypes.ALL; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptions.Builder { + + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/StringOptions.java b/extensions/json/src/main/java/io/deephaven/json/StringOptions.java new file mode 100644 index 00000000000..c949d70fc93 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/StringOptions.java @@ -0,0 +1,65 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; + +/** + * Processes a JSON value as a {@link String}. + */ +@Immutable +@BuildableStyle +public abstract class StringOptions extends ValueOptionsSingleValueBase { + + public static Builder builder() { + return ImmutableStringOptions.builder(); + } + + public static StringOptions lenient() { + return builder() + .allowedTypes(JsonValueTypes.STRING_LIKE) + .build(); + } + + public static StringOptions standard() { + return builder().build(); + } + + public static StringOptions strict() { + return builder() + .allowMissing(false) + .allowedTypes(JsonValueTypes.STRING) + .build(); + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#STRING_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.STRING_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#STRING_LIKE}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.STRING_LIKE; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptionsSingleValueBase.Builder { + + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/TupleOptions.java b/extensions/json/src/main/java/io/deephaven/json/TupleOptions.java new file mode 100644 index 00000000000..4f0df6b0787 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/TupleOptions.java @@ -0,0 +1,88 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.Map; + +/** + * Processes a JSON array as a tuple. + */ +@Immutable +@BuildableStyle +public abstract class TupleOptions extends ValueOptionsRestrictedUniverseBase { + + public static Builder builder() { + return ImmutableTupleOptions.builder(); + } + + /** + * Creates a tuple of the given {@code values}, with name incrementing, starting from "0". + * + * @param values the values + * @return the tuple options + */ + public static TupleOptions of(ValueOptions... values) { + return of(Arrays.asList(values)); + } + + /** + * Creates a tuple of the given {@code values}, with name incrementing, starting from "0". + * + * @param values the values + * @return the tuple options + */ + public static TupleOptions of(Iterable values) { + final Builder builder = builder(); + final Iterator it = values.iterator(); + for (int i = 0; it.hasNext(); ++i) { + builder.putNamedValues(Integer.toString(i), it.next()); + } + return builder.build(); + } + + /** + * The named, ordered values of the tuple. + */ + public abstract Map namedValues(); + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#ARRAY_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.ARRAY_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#ARRAY_OR_NULL}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.ARRAY_OR_NULL; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptions.Builder { + + Builder putNamedValues(String key, ValueOptions value); + + Builder putNamedValues(Map.Entry entry); + + Builder putAllNamedValues(Map entries); + + TupleOptions build(); + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java b/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java new file mode 100644 index 00000000000..48b94e75cd9 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java @@ -0,0 +1,150 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Immutable; + +import java.util.EnumSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * A type-discriminated object. + */ +@Immutable +@BuildableStyle +public abstract class TypedObjectOptions extends ValueOptionsRestrictedUniverseBase { + + public static Builder builder() { + return ImmutableTypedObjectOptions.builder(); + } + + /** + * Creates a new builder with the {@link #typeFieldName()} set to {@code typeFieldName}, {@link #sharedFields()} + * inferred from {@code objects} based on {@link ObjectFieldOptions} equality, and {@link #objects()} set to + * {@code objects} with the shared fields removed. + * + * @param typeFieldName the type field name + * @param objects the objects + * @return the builder + */ + public static Builder builder(String typeFieldName, Map objects) { + final Builder builder = builder().typeFieldName(typeFieldName); + final Set sharedFields = new LinkedHashSet<>(); + final ObjectOptions first = objects.values().iterator().next(); + for (ObjectFieldOptions field : first.fields()) { + boolean isShared = true; + for (ObjectOptions obj : objects.values()) { + if (!obj.fields().contains(field)) { + isShared = false; + break; + } + } + if (isShared) { + sharedFields.add(field); + } + } + for (Entry e : objects.entrySet()) { + builder.putObjects(e.getKey(), without(e.getValue(), sharedFields)); + } + return builder.addAllSharedFields(sharedFields); + } + + /** + * Creates a typed object by inferring the shared fields. Equivalent to + * {@code builder(typeFieldName, objects).build()}. + * + * @param typeFieldName the type field name + * @param objects the objects + * @return the typed object + */ + public static TypedObjectOptions standard(String typeFieldName, Map objects) { + return builder(typeFieldName, objects).build(); + } + + /** + * Creates a typed object by inferring the shared fields. Equivalent to + * {@code builder(typeFieldName, objects).allowUnknownTypes(false).allowMissing(false).desiredTypes(JsonValueTypes.OBJECT).build()}. + * + * @param typeFieldName the type field name + * @param objects the objects + * @return the typed object + */ + public static TypedObjectOptions strict(String typeFieldName, Map objects) { + return builder(typeFieldName, objects) + .allowUnknownTypes(false) + .allowMissing(false) + .allowedTypes(JsonValueTypes.OBJECT) + .build(); + } + + public abstract String typeFieldName(); + + public abstract Set sharedFields(); + + // canonical name + public abstract Map objects(); + + /** + * If unknown fields are allowed. By default is {@code true}. + */ + @Default + public boolean allowUnknownTypes() { + return true; + } + + /** + * {@inheritDoc} By default is {@link JsonValueTypes#OBJECT_OR_NULL}. + */ + @Default + @Override + public EnumSet allowedTypes() { + return JsonValueTypes.OBJECT_OR_NULL; + } + + /** + * The universe, is {@link JsonValueTypes#OBJECT_OR_NULL}. + */ + @Override + public final EnumSet universe() { + return JsonValueTypes.OBJECT_OR_NULL; + } + + @Override + public final T walk(Visitor visitor) { + return visitor.visit(this); + } + + public interface Builder extends ValueOptions.Builder { + + Builder typeFieldName(String typeFieldName); + + Builder addSharedFields(ObjectFieldOptions element); + + Builder addSharedFields(ObjectFieldOptions... elements); + + Builder addAllSharedFields(Iterable elements); + + Builder putObjects(String key, ObjectOptions value); + + Builder allowUnknownTypes(boolean allowUnknownTypes); + } + + private static ObjectOptions without(ObjectOptions options, Set excludedFields) { + final ObjectOptions.Builder builder = ObjectOptions.builder() + .allowUnknownFields(options.allowUnknownFields()) + .allowMissing(options.allowMissing()) + .allowedTypes(options.allowedTypes()); + for (ObjectFieldOptions field : options.fields()) { + if (!excludedFields.contains(field)) { + builder.addFields(field); + } + } + return builder.build(); + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/ValueOptions.java b/extensions/json/src/main/java/io/deephaven/json/ValueOptions.java new file mode 100644 index 00000000000..a31f287c2ca --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/ValueOptions.java @@ -0,0 +1,132 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import org.immutables.value.Value.Check; +import org.immutables.value.Value.Default; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Map; + +/** + * The base configuration for JSON values. + */ +public abstract class ValueOptions { + + /** + * The allowed types. + */ + public abstract EnumSet allowedTypes(); + + /** + * If the processor should allow a missing JSON value. By default is {@code true}. + */ + @Default + public boolean allowMissing() { + return true; + } + + /** + * Wraps the allowed values of {@code this} as {@link SkipOptions}. Equivalent to + * {@code SkipOptions.builder().allowMissing(allowMissing()).allowedTypes(allowedTypes()).build()}. + * + * @return this allowed values of this as skip options + */ + public final SkipOptions skip() { + return SkipOptions.builder() + .allowMissing(allowMissing()) + .allowedTypes(allowedTypes()) + .build(); + } + + /** + * Wraps {@code this} as the value of an {@link ArrayOptions}. Equivalent to {@code ArrayOptions.standard(this)}. + * + * @return this as the value of an array options + * @see ArrayOptions#standard(ValueOptions) + */ + public final ArrayOptions array() { + return ArrayOptions.standard(this); + } + + /** + * Wraps {@code this} as a singular field of an {@link ObjectOptions}. Equivalent to + * {@code ObjectOptions.standard(Map.of(name, this))}. + * + * @param name the field name + * @return this as the singular field of an object options + * @see ObjectOptions#standard(Map) + */ + public final ObjectOptions field(String name) { + return ObjectOptions.standard(Map.of(name, this)); + } + + public abstract T walk(Visitor visitor); + + public interface Visitor { + + T visit(StringOptions _string); + + T visit(BoolOptions _bool); + + T visit(CharOptions _char); + + T visit(ByteOptions _byte); + + T visit(ShortOptions _short); + + T visit(IntOptions _int); + + T visit(LongOptions _long); + + T visit(FloatOptions _float); + + T visit(DoubleOptions _double); + + T visit(ObjectOptions object); + + T visit(ObjectKvOptions objectKv); + + T visit(InstantOptions instant); + + T visit(InstantNumberOptions instantNumber); + + T visit(BigIntegerOptions bigInteger); + + T visit(BigDecimalOptions bigDecimal); + + T visit(SkipOptions skip); + + T visit(TupleOptions tuple); + + T visit(TypedObjectOptions typedObject); + + T visit(LocalDateOptions localDate); + + T visit(ArrayOptions array); + + T visit(AnyOptions any); + } + + public interface Builder> { + + B allowMissing(boolean allowMissing); + + B allowedTypes(EnumSet allowedTypes); + + default B allowedTypes(JsonValueTypes... allowedTypes) { + final EnumSet set = EnumSet.noneOf(JsonValueTypes.class); + set.addAll(Arrays.asList(allowedTypes)); + return allowedTypes(set); + } + + V build(); + } + + @Check + final void checkAllowedTypeInvariants() { + JsonValueTypes.checkAllowedTypeInvariants(allowedTypes()); + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java b/extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java new file mode 100644 index 00000000000..077c47526c3 --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java @@ -0,0 +1,34 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import org.immutables.value.Value.Check; + +import java.util.EnumSet; + +/** + * A base {@link ValueOptions} where the implementation has a clearly defined {@link #universe()}. + */ +public abstract class ValueOptionsRestrictedUniverseBase extends ValueOptions { + + /** + * The allowed types. Must be a subset of {@link #universe()}. + */ + @Override + public abstract EnumSet allowedTypes(); + + /** + * The universe of possible allowed types. + */ + public abstract EnumSet universe(); + + @Check + void checkAllowedTypes() { + for (JsonValueTypes type : allowedTypes()) { + if (!universe().contains(type)) { + throw new IllegalArgumentException("Unexpected type " + type); + } + } + } +} diff --git a/extensions/json/src/main/java/io/deephaven/json/ValueOptionsSingleValueBase.java b/extensions/json/src/main/java/io/deephaven/json/ValueOptionsSingleValueBase.java new file mode 100644 index 00000000000..a812f70345f --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/ValueOptionsSingleValueBase.java @@ -0,0 +1,48 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import org.immutables.value.Value.Check; + +import java.util.Optional; + +/** + * A base {@link ValueOptions} where the JSON value represents a single value. + * + * @param the value type + */ +public abstract class ValueOptionsSingleValueBase extends ValueOptionsRestrictedUniverseBase { + + /** + * The value to use when {@link JsonValueTypes#NULL} is encountered. {@link #allowedTypes()} must contain + * {@link JsonValueTypes#NULL}. + */ + public abstract Optional onNull(); + + /** + * The value to use when a value is missing. {@link #allowMissing()} must be {@code true}. + */ + public abstract Optional onMissing(); + + public interface Builder, B extends Builder> + extends ValueOptions.Builder { + B onNull(T onNull); + + B onMissing(T onMissing); + } + + @Check + final void checkOnNull() { + if (!allowedTypes().contains(JsonValueTypes.NULL) && onNull().isPresent()) { + throw new IllegalArgumentException("onNull set, but NULL is not allowed"); + } + } + + @Check + final void checkOnMissing() { + if (!allowMissing() && onMissing().isPresent()) { + throw new IllegalArgumentException("onMissing set, but allowMissing is false"); + } + } +} diff --git a/extensions/kafka/src/main/java/io/deephaven/kafka/KafkaTools.java b/extensions/kafka/src/main/java/io/deephaven/kafka/KafkaTools.java index eb95d8b2584..3c772410198 100644 --- a/extensions/kafka/src/main/java/io/deephaven/kafka/KafkaTools.java +++ b/extensions/kafka/src/main/java/io/deephaven/kafka/KafkaTools.java @@ -70,9 +70,11 @@ import io.deephaven.kafka.publish.KafkaPublisherException; import io.deephaven.kafka.publish.KeyOrValueSerializer; import io.deephaven.kafka.publish.PublishToKafka; +import io.deephaven.processor.NamedObjectProcessor; import io.deephaven.processor.ObjectProcessor; import io.deephaven.protobuf.ProtobufDescriptorParserOptions; import io.deephaven.qst.column.header.ColumnHeader; +import io.deephaven.qst.type.Type; import io.deephaven.stream.StreamChunkUtils; import io.deephaven.stream.StreamConsumer; import io.deephaven.stream.StreamPublisher; @@ -580,8 +582,7 @@ public static KeyOrValueSpec rawSpec(ColumnHeader header, Class - * The respective column definitions are derived from the combination of {@code columnNames} and - * {@link ObjectProcessor#outputTypes()}. + * Equivalent to {@code objectProcessorSpec(deserializer, NamedObjectProcessor.of(processor, columnNames))}. * * @param deserializer the deserializer * @param processor the object processor @@ -593,14 +594,14 @@ public static KeyOrValueSpec objectProcessorSpec( Deserializer deserializer, ObjectProcessor processor, List columnNames) { - return new KeyOrValueSpecObjectProcessorImpl<>(deserializer, processor, columnNames); + return objectProcessorSpec(deserializer, NamedObjectProcessor.of(processor, columnNames)); } /** * Creates a kafka key or value spec implementation from a byte-array {@link ObjectProcessor}. * *

- * Equivalent to {@code objectProcessorSpec(new ByteArrayDeserializer(), processor, columnNames)}. + * Equivalent to {@code objectProcessorSpec(NamedObjectProcessor.of(processor, columnNames))}. * * @param processor the byte-array object processor * @param columnNames the column names @@ -608,8 +609,52 @@ public static KeyOrValueSpec objectProcessorSpec( */ @SuppressWarnings("unused") public static KeyOrValueSpec objectProcessorSpec(ObjectProcessor processor, - List columnNames) { - return objectProcessorSpec(new ByteArrayDeserializer(), processor, columnNames); + List columnNames) { + return objectProcessorSpec(NamedObjectProcessor.of(processor, columnNames)); + } + + /** + * Creates a kafka key or value spec implementation from a {@link NamedObjectProcessor}. + * + * @param deserializer the deserializer + * @param processor the named object processor + * @return the Kafka key or value spec + * @param the object type + */ + public static KeyOrValueSpec objectProcessorSpec( + Deserializer deserializer, + NamedObjectProcessor processor) { + return new KeyOrValueSpecObjectProcessorImpl<>(deserializer, processor); + } + + /** + * Creates a kafka key or value spec implementation from the named object processor. + * + *

+ * Equivalent to {@code objectProcessorSpec(new ByteArrayDeserializer(), processor)}. + * + * @param processor the named object processor + * @return the Kafka key or value spec + * @see #objectProcessorSpec(Deserializer, NamedObjectProcessor) + * @see ByteArrayDeserializer + */ + public static KeyOrValueSpec objectProcessorSpec(NamedObjectProcessor processor) { + return objectProcessorSpec(new ByteArrayDeserializer(), processor); + } + + /** + * Creates a kafka key or value spec implementation from a named object processor provider. It must be capable + * of supporting {@code byte[]}. + * + *

+ * Equivalent to {@code objectProcessorSpec(provider.named(Type.byteType().arrayType()))}. + * + * @param provider the named object processor provider + * @return the Kafka key or value spec + * @see #objectProcessorSpec(NamedObjectProcessor) + */ + public static KeyOrValueSpec objectProcessorSpec(NamedObjectProcessor.Provider provider) { + return objectProcessorSpec(provider.named(Type.byteType().arrayType())); } } diff --git a/extensions/kafka/src/main/java/io/deephaven/kafka/KeyOrValueSpecObjectProcessorImpl.java b/extensions/kafka/src/main/java/io/deephaven/kafka/KeyOrValueSpecObjectProcessorImpl.java index 9d924f2c460..84615a55e09 100644 --- a/extensions/kafka/src/main/java/io/deephaven/kafka/KeyOrValueSpecObjectProcessorImpl.java +++ b/extensions/kafka/src/main/java/io/deephaven/kafka/KeyOrValueSpecObjectProcessorImpl.java @@ -16,6 +16,7 @@ import io.deephaven.kafka.ingest.KafkaStreamPublisher; import io.deephaven.kafka.ingest.KeyOrValueProcessor; import io.deephaven.kafka.ingest.MultiFieldChunkAdapter; +import io.deephaven.processor.NamedObjectProcessor; import io.deephaven.processor.ObjectProcessor; import io.deephaven.qst.type.Type; import org.apache.commons.lang3.mutable.MutableInt; @@ -33,27 +34,19 @@ /** * This implementation is useful for presenting an easier onboarding ramp and better (and public) interface - * {@link KafkaTools.Consume#objectProcessorSpec(Deserializer, ObjectProcessor, List)} for end-users. The + * {@link KafkaTools.Consume#objectProcessorSpec(Deserializer, NamedObjectProcessor)} for end-users. The * {@link ObjectProcessor} is a user-visible replacement for {@link KeyOrValueProcessor}. In the meantime though, we are * adapting into a {@link KeyOrValueProcessor} until such a time when {@link KafkaStreamPublisher} can be re-written to * take advantage of these better interfaces. */ class KeyOrValueSpecObjectProcessorImpl extends KeyOrValueSpec { private final Deserializer deserializer; - private final ObjectProcessor processor; - private final List columnNames; + private final NamedObjectProcessor processor; - KeyOrValueSpecObjectProcessorImpl( - Deserializer deserializer, ObjectProcessor processor, List columnNames) { - if (columnNames.size() != processor.outputTypes().size()) { - throw new IllegalArgumentException("Expected columnNames and processor.outputTypes() to be the same size"); - } - if (columnNames.stream().distinct().count() != columnNames.size()) { - throw new IllegalArgumentException("Expected columnNames to have distinct values"); - } + KeyOrValueSpecObjectProcessorImpl(Deserializer deserializer, + NamedObjectProcessor processor) { this.deserializer = Objects.requireNonNull(deserializer); this.processor = Objects.requireNonNull(processor); - this.columnNames = List.copyOf(columnNames); } @Override @@ -73,10 +66,12 @@ protected KeyOrValueIngestData getIngestData(KeyOrValue keyOrValue, SchemaRegist Map configs, MutableInt nextColumnIndexMut, List> columnDefinitionsOut) { final KeyOrValueIngestData data = new KeyOrValueIngestData(); data.fieldPathToColumnName = new LinkedHashMap<>(); - final int L = columnNames.size(); + final List names = processor.names(); + final List> types = processor.processor().outputTypes(); + final int L = names.size(); for (int i = 0; i < L; ++i) { - final String columnName = columnNames.get(i); - final Type type = processor.outputTypes().get(i); + final String columnName = names.get(i); + final Type type = types.get(i); data.fieldPathToColumnName.put(columnName, columnName); columnDefinitionsOut.add(ColumnDefinition.of(columnName, type)); } @@ -101,7 +96,7 @@ public void handleChunk(ObjectChunk inputChunk, WritableChunk in = (ObjectChunk) inputChunk; // we except isInOrder to be true, so apply should be an O(1) op no matter how many columns there are. - processor.processAll(in, offsetsAdapter.apply(publisherChunks)); + processor.processor().processAll(in, offsetsAdapter.apply(publisherChunks)); } } diff --git a/py/server/deephaven/dtypes.py b/py/server/deephaven/dtypes.py index 2114502de30..f30d91c637f 100644 --- a/py/server/deephaven/dtypes.py +++ b/py/server/deephaven/dtypes.py @@ -101,6 +101,8 @@ def __call__(self, *args, **kwargs): """String type""" Character = DType(j_name="java.lang.Character") """Character type""" +BigInteger = DType(j_name="java.math.BigInteger") +"""Java BigInteger type""" BigDecimal = DType(j_name="java.math.BigDecimal") """Java BigDecimal type""" StringSet = DType(j_name="io.deephaven.stringset.StringSet") diff --git a/py/server/deephaven/json/__init__.py b/py/server/deephaven/json/__init__.py new file mode 100644 index 00000000000..59040b71543 --- /dev/null +++ b/py/server/deephaven/json/__init__.py @@ -0,0 +1,1134 @@ +# +# Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +# + +"""The deephaven JSON module presents a declarative and composable configuration layer for describing the structure of a +JSON value. It is meant to have sane defaults while also providing finer-grained configuration options for typical +scenarios. The primary purpose of this module is to provide a common layer that various consumers can use to parse JSON +values into appropriate Deephaven structures. As such (and by the very nature of JSON), these types represent a superset +of JSON. This module can also service other use cases where the JSON structuring is necessary (for example, producing a +JSON value from a Deephaven structure). +""" + +# todo: should be on the classpath by default, but doesn't have to be +# todo: would be nice if this code could live in the JSON jar + +import jpy +from dataclasses import dataclass, field +from datetime import datetime +from enum import Enum +from typing import Dict, List, Union, Tuple, Optional, Literal, Any + +from deephaven import dtypes +from deephaven._wrapper import JObjectWrapper +from deephaven.time import to_j_instant + +__all__ = [ + "string_", + "bool_", + "char_", + "byte_", + "short_", + "int_", + "long_", + "float_", + "double_", + "instant_", + "big_integer_", + "big_decimal_", + "array_", + "object_", + "object_kv_", + "tuple_", + "any_", + "skip_", + "json", + "JsonOptions", + "JsonValueType", + "RepeatedFieldBehavior", + "FieldOptions", +] + +# https://deephaven.atlassian.net/browse/DH-15061 +# It is important that ValueOptions gets imported before the others. +_JValueOptions = jpy.get_type("io.deephaven.json.ValueOptions") +_JObjectOptions = jpy.get_type("io.deephaven.json.ObjectOptions") +_JArrayOptions = jpy.get_type("io.deephaven.json.ArrayOptions") +_JObjectKvOptions = jpy.get_type("io.deephaven.json.ObjectKvOptions") +_JTupleOptions = jpy.get_type("io.deephaven.json.TupleOptions") +_JObjectFieldOptions = jpy.get_type("io.deephaven.json.ObjectFieldOptions") +_JRepeatedFieldBehavior = jpy.get_type( + "io.deephaven.json.ObjectFieldOptions$RepeatedBehavior" +) +_JJsonValueTypes = jpy.get_type("io.deephaven.json.JsonValueTypes") +_JBoolOptions = jpy.get_type("io.deephaven.json.BoolOptions") +_JCharOptions = jpy.get_type("io.deephaven.json.CharOptions") +_JByteOptions = jpy.get_type("io.deephaven.json.ByteOptions") +_JShortOptions = jpy.get_type("io.deephaven.json.ShortOptions") +_JIntOptions = jpy.get_type("io.deephaven.json.IntOptions") +_JLongOptions = jpy.get_type("io.deephaven.json.LongOptions") +_JFloatOptions = jpy.get_type("io.deephaven.json.FloatOptions") +_JDoubleOptions = jpy.get_type("io.deephaven.json.DoubleOptions") +_JStringOptions = jpy.get_type("io.deephaven.json.StringOptions") +_JSkipOptions = jpy.get_type("io.deephaven.json.SkipOptions") +_JInstantOptions = jpy.get_type("io.deephaven.json.InstantOptions") +_JInstantNumberOptions = jpy.get_type("io.deephaven.json.InstantNumberOptions") +_JInstantNumberOptionsFormat = jpy.get_type( + "io.deephaven.json.InstantNumberOptions$Format" +) +_JBigIntegerOptions = jpy.get_type("io.deephaven.json.BigIntegerOptions") +_JBigDecimalOptions = jpy.get_type("io.deephaven.json.BigDecimalOptions") +_JAnyOptions = jpy.get_type("io.deephaven.json.AnyOptions") + + +_VALUE_STRING = _JJsonValueTypes.STRING +_VALUE_NULL = _JJsonValueTypes.NULL +_VALUE_INT = _JJsonValueTypes.INT +_VALUE_DECIMAL = _JJsonValueTypes.DECIMAL +_VALUE_BOOL = _JJsonValueTypes.BOOL +_VALUE_OBJECT = _JJsonValueTypes.OBJECT +_VALUE_ARRAY = _JJsonValueTypes.ARRAY + +_EPOCH_SECONDS = _JInstantNumberOptionsFormat.EPOCH_SECONDS +_EPOCH_MILLIS = _JInstantNumberOptionsFormat.EPOCH_MILLIS +_EPOCH_MICROS = _JInstantNumberOptionsFormat.EPOCH_MICROS +_EPOCH_NANOS = _JInstantNumberOptionsFormat.EPOCH_NANOS + + +class JsonOptions(JObjectWrapper): + """The JSON options object. Provides a named object processor provider.""" + + j_object_type = _JValueOptions + + def __init__(self, j_options: jpy.JType): + self.j_options = j_options + + @property + def j_object(self) -> jpy.JType: + return self.j_options + + +# todo use type alias instead of Any in the future +# todo named tuple +JsonValueType = Union[ + JsonOptions, + dtypes.DType, + type, + Dict[str, Union["JsonValueType", "FieldOptions"]], + List["JsonValueType"], + Tuple["JsonValueType", ...], +] + + +class RepeatedFieldBehavior(Enum): + """ + The behavior to use when a repeated field is encountered in a JSON object. For example, + .. code-block:: json + { + "foo": 42, + "foo": 43 + } + """ + + USE_FIRST = _JRepeatedFieldBehavior.USE_FIRST + """Use the first field""" + + ERROR = _JRepeatedFieldBehavior.ERROR + """Raise an error""" + + +@dataclass +class FieldOptions: + """The object field options. + + In contexts where the user needs to create an object field value and isn't changing any default values, the user can + simplify by just using the JsonValueType. For example, + + .. code-block:: python + { + "name": FieldOptions(str), + "age": FieldOptions(int), + } + + could be simplified to + + .. code-block:: python + { + "name": str, + "age": int, + } + """ + + value: JsonValueType + """The json value type""" + aliases: Union[str, List[str]] = field(default_factory=list) + """The field name aliases. By default, is an empty list.""" + repeated_behavior: RepeatedFieldBehavior = RepeatedFieldBehavior.ERROR + """The repeated field behavior. By default, is RepeatedFieldBehavior.ERROR.""" + case_sensitive: bool = True + """If the field name and aliases should be compared using case-sensitive equality. By default, is True.""" + + def _j_field_options(self, name: str) -> jpy.JType: + builder = ( + _JObjectFieldOptions.builder() + .name(name) + .options(json(self.value).j_options) + .repeatedBehavior(self.repeated_behavior.value) + .caseSensitive(self.case_sensitive) + ) + if self.aliases: + builder.addAliases( + [self.aliases] if isinstance(self.aliases, str) else self.aliases + ) + return builder.build() + + +def _build( + builder, + allow_missing: bool, + allow_null: bool, + allow_int: bool = False, + allow_decimal: bool = False, + allow_string: bool = False, + allow_bool: bool = False, + allow_object: bool = False, + allow_array: bool = False, +): + builder.allowMissing(allow_missing) + builder.allowedTypes( + ([_VALUE_STRING] if allow_string else []) + + ([_VALUE_NULL] if allow_null else []) + + ([_VALUE_INT] if allow_int else []) + + ([_VALUE_DECIMAL] if allow_decimal else []) + + ([_VALUE_BOOL] if allow_bool else []) + + ([_VALUE_OBJECT] if allow_object else []) + + ([_VALUE_ARRAY] if allow_array else []) + ) + + +def object_( + fields: Dict[str, Union[JsonValueType, FieldOptions]], + allow_unknown_fields: bool = True, + allow_missing: bool = True, + allow_null: bool = True, + repeated_field_behavior: RepeatedFieldBehavior = RepeatedFieldBehavior.ERROR, + case_sensitive: bool = True, +) -> JsonOptions: + """Creates an object options. For example, the JSON object + + .. code-block:: json + { "name": "foo", "age": 42 } + + might be modelled as the object type + + .. code-block:: python + object_({ "name": str, "age": int }) + + In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can + simplify by using a Dict[str, Union[JsonValueType, FieldOptions]]. For example, + + .. code-block:: python + some_method(object_({ "name": str, "age": int })) + + could be simplified to + + .. code-block:: python + some_method({ "name": str, "age": int }) + + Args: + fields (Dict[str, Union[JsonValueType, FieldOptions]]): the fields + allow_unknown_fields (bool): if unknown fields are allow, by default is True + allow_missing (bool): if the object is allowed to be missing, by default is True + allow_null (bool): if the object is allowed to be a JSON null type, by default is True + repeated_field_behavior (RepeatedFieldBehavior): the default repeated field behavior, only used for fields that + are specified using JsonValueType, by default is RepeatedFieldBehavior.ERROR + case_sensitive (bool): if default to use for field case-sensitivity. Only used for fields that are specified + using JsonValueType, by default is True + + Returns: + the object options + """ + builder = _JObjectOptions.builder() + _build(builder, allow_missing, allow_null, allow_object=True) + builder.allowUnknownFields(allow_unknown_fields) + for field_name, field_opts in fields.items(): + field_opts = ( + field_opts + if isinstance(field_opts, FieldOptions) + else FieldOptions( + field_opts, + repeated_behavior=repeated_field_behavior, + case_sensitive=case_sensitive, + ) + ) + # noinspection PyProtectedMember + builder.addFields(field_opts._j_field_options(field_name)) + return JsonOptions(builder.build()) + + +def array_( + element: JsonValueType, + allow_missing: bool = True, + allow_null: bool = True, +) -> JsonOptions: + """Creates a array of element options. For example, the JSON array + + .. code-block:: json + [1, 42, 43, 13] + + might be modelled as an array of ints + + .. code-block:: python + array_(int) + + In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can + simplify by using a list with a single element type. For example, + + .. code-block:: python + some_method(array_(element)) + + could be simplified to + + .. code-block:: python + some_method([element]) + + Args: + element (JsonValueType): the array element type + allow_missing (bool): if the array is allowed to be missing, by default is True + allow_null (bool): if the array is allowed to be a JSON null type, by default is True + + Returns: + the array options + """ + builder = _JArrayOptions.builder() + builder.element(json(element).j_options) + _build(builder, allow_missing, allow_null, allow_array=True) + return JsonOptions(builder.build()) + + +def object_kv_( + key_type: JsonValueType = str, + value_type: Optional[JsonValueType] = None, + allow_missing: bool = True, + allow_null: bool = True, +) -> JsonOptions: + """Creates an object key-value options. This is used in situations where the number of fields in an object is + variable and all the values types are the same. For example, the JSON object + + .. code-block:: json + { + "foo": 1, + "bar": 42, + "baz": 3, + ... + "xyz": 100 + } + + might be modelled as the object kv type + + .. code-block:: python + object_kv_(value_element=int) + + Args: + key_type (JsonValueType): the key element, by defaults is type str + value_type (Optional[JsonValueType]): the value element, required + allow_missing (bool): if the object is allowed to be missing, by default is True + allow_null (bool): if the object is allowed to be a JSON null type, by default is True + + Returns: + the object kv options + """ + builder = _JObjectKvOptions.builder() + builder.key(json(key_type).j_options) + builder.value(json(value_type).j_options) + _build(builder, allow_missing, allow_null, allow_object=True) + return JsonOptions(builder.build()) + + +def tuple_( + values: Union[Tuple[JsonValueType, ...], Dict[str, JsonValueType]], + allow_missing: bool = True, + allow_null: bool = True, +) -> JsonOptions: + """Creates a tuple options. For example, the JSON array + + .. code-block:: json + ["foo", 42, 5.72] + + might be modelled as the tuple type + + .. code-block:: python + tuple_((str, int, float)) + + To provide meaningful names, a dictionary can be used: + + .. code-block:: python + tuple_({"name": str, "age": int, "height": float}) + + In contexts where the user needs to create a JsonValueType and isn't changing any default values nor is setting + names, the user can simplify passing through a python tuple type. For example, + + .. code-block:: python + some_method(tuple_((tuple_type_1, tuple_type_2))) + + could be simplified to + + .. code-block:: python + some_method((tuple_type_1, tuple_type_2)) + + Args: + values (Union[Tuple[JsonValueType, ...], Dict[str, JsonValueType]]): the tuple value types + allow_missing (bool): if the array is allowed to be missing, by default is True + allow_null (bool): if the array is allowed to be a JSON null type, by default is True + Returns: + the tuple options + """ + if isinstance(values, Tuple): + kvs = enumerate(values) + elif isinstance(values, Dict): + kvs = values.items() + else: + raise TypeError(f"Invalid tuple type: {type(values)}") + builder = _JTupleOptions.builder() + _build( + builder, + allow_missing, + allow_null, + allow_array=True, + ) + for name, json_value_type in kvs: + builder.putNamedValues(str(name), json(json_value_type).j_options) + return JsonOptions(builder.build()) + + +def bool_( + allow_string: bool = False, + allow_missing: bool = True, + allow_null: bool = True, + on_missing: Optional[bool] = None, + on_null: Optional[bool] = None, +) -> JsonOptions: + """Creates a bool options. For example, the JSON boolean + + .. code-block:: json + True + + might be modelled as the bool type + + .. code-block:: python + bool_() + + In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can + simplify by using the python built-in bool type. For example, + + .. code-block:: python + some_method(bool_()) + + could be simplified to + + .. code-block:: python + some_method(bool) + + Args: + allow_string (bool): if the bool value is allowed to be a JSON string type, default is False + allow_missing (bool): if the bool value is allowed to be missing, default is True + allow_null (bool): if the bool value is allowed to be a JSON null type, default is True + on_missing (Optional[bool]): the value to use when the JSON value is missing and allow_missing is True, default is None + on_null (Optional[bool]): the value to use when the JSON value is null and allow_null is True, default is None + + Returns: + the bool options + """ + builder = _JBoolOptions.builder() + _build( + builder, + allow_missing, + allow_null, + allow_bool=True, + allow_string=allow_string, + ) + if on_null: + builder.onNull(onNull) + if on_missing: + builder.onMissing(on_missing) + return JsonOptions(builder.build()) + + +def char_( + allow_missing: bool = True, + allow_null: bool = True, + on_missing: Optional[str] = None, + on_null: Optional[str] = None, +) -> JsonOptions: + """Creates a char options. For example, the JSON string + + .. code-block:: json + "F" + + might be modelled as the char type + + .. code-block:: python + char_() + + Args: + allow_missing (bool): if the char value is allowed to be missing, default is True + allow_null (bool): if the char value is allowed to be a JSON null type, default is True + on_missing (Optional[str]): the value to use when the JSON value is missing and allow_missing is True, default is None. If specified, must be a single character. + on_null (Optional[str]): the value to use when the JSON value is null and allow_null is True, default is None. If specified, must be a single character. + + Returns: + the char options + """ + builder = _JCharOptions.builder() + _build( + builder, + allow_missing, + allow_null, + allow_string=True, + ) + if on_null: + builder.onNull(onNull) + if on_missing: + builder.onMissing(on_missing) + return JsonOptions(builder.build()) + + +def byte_( + allow_decimal: bool = False, + allow_string: bool = False, + allow_missing: bool = True, + allow_null: bool = True, + on_missing: Optional[int] = None, + on_null: Optional[int] = None, +) -> JsonOptions: + """Creates a byte (signed 8-bit) options. For example, the JSON integer + + .. code-block:: json + 42 + + might be modelled as the byte type + + .. code-block:: python + byte_() + + Args: + allow_decimal (bool): if the byte value is allowed to be a JSON decimal type, default is False + allow_string (bool): if the byte value is allowed to be a JSON string type, default is False + allow_missing (bool): if the byte value is allowed to be missing, default is True + allow_null (bool): if the byte value is allowed to be a JSON null type, default is True + on_missing (Optional[int]): the value to use when the JSON value is missing and allow_missing is True, default is None. + on_null (Optional[int]): the value to use when the JSON value is null and allow_null is True, default is None. + + Returns: + the byte options + """ + builder = _JByteOptions.builder() + _build( + builder, + allow_missing, + allow_null, + allow_int=True, + allow_decimal=allow_decimal, + allow_string=allow_string, + ) + if on_null: + builder.onNull(onNull) + if on_missing: + builder.onMissing(on_missing) + return JsonOptions(builder.build()) + + +def short_( + allow_decimal: bool = False, + allow_string: bool = False, + allow_missing: bool = True, + allow_null: bool = True, + on_missing: Optional[int] = None, + on_null: Optional[int] = None, +) -> JsonOptions: + """Creates a short (signed 16-bit) options. For example, the JSON integer + + .. code-block:: json + 30000 + + might be modelled as the short type + + .. code-block:: python + short_() + + Args: + allow_decimal (bool): if the short value is allowed to be a JSON decimal type, default is False + allow_string (bool): if the short value is allowed to be a JSON string type, default is False + allow_missing (bool): if the short value is allowed to be missing, default is True + allow_null (bool): if the short value is allowed to be a JSON null type, default is True + on_missing (Optional[int]): the value to use when the JSON value is missing and allow_missing is True, default is None. + on_null (Optional[int]): the value to use when the JSON value is null and allow_null is True, default is None. + + Returns: + the short options + """ + builder = _JShortOptions.builder() + _build( + builder, + allow_missing, + allow_null, + allow_int=True, + allow_decimal=allow_decimal, + allow_string=allow_string, + ) + if on_null: + builder.onNull(onNull) + if on_missing: + builder.onMissing(on_missing) + return JsonOptions(builder.build()) + + +def int_( + allow_decimal: bool = False, + allow_string: bool = False, + allow_missing: bool = True, + allow_null: bool = True, + on_missing: Optional[int] = None, + on_null: Optional[int] = None, +) -> JsonOptions: + """Creates an int (signed 32-bit) options. For example, the JSON integer + + .. code-block:: json + 100000 + + might be modelled as the int type + + .. code-block:: python + int_() + + Args: + allow_decimal (bool): if the int value is allowed to be a JSON decimal type, default is False + allow_string (bool): if the int value is allowed to be a JSON string type, default is False + allow_missing (bool): if the int value is allowed to be missing, default is True + allow_null (bool): if the int value is allowed to be a JSON null type, default is True + on_missing (Optional[int]): the value to use when the JSON value is missing and allow_missing is True, default is None. + on_null (Optional[int]): the value to use when the JSON value is null and allow_null is True, default is None. + + Returns: + the int options + """ + builder = _JIntOptions.builder() + _build( + builder, + allow_missing, + allow_null, + allow_int=True, + allow_decimal=allow_decimal, + allow_string=allow_string, + ) + if on_null: + builder.onNull(onNull) + if on_missing: + builder.onMissing(on_missing) + return JsonOptions(builder.build()) + + +def long_( + allow_decimal: bool = False, + allow_string: bool = False, + allow_missing: bool = True, + allow_null: bool = True, + on_missing: Optional[int] = None, + on_null: Optional[int] = None, +) -> JsonOptions: + """Creates a long (signed 64-bit) options. For example, the JSON integer + + .. code-block:: json + 8000000000 + + might be modelled as the long type + + .. code-block:: python + long_() + + In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can + simplify by using the python built-in long type. For example, + + .. code-block:: python + some_method(long_()) + + could be simplified to + + .. code-block:: python + some_method(int) + + Args: + allow_decimal (bool): if the long value is allowed to be a JSON decimal type, default is False + allow_string (bool): if the long value is allowed to be a JSON string type, default is False + allow_missing (bool): if the long value is allowed to be missing, default is True + allow_null (bool): if the long value is allowed to be a JSON null type, default is True + on_missing (Optional[int]): the value to use when the JSON value is missing and allow_missing is True, default is None. + on_null (Optional[int]): the value to use when the JSON value is null and allow_null is True, default is None. + + Returns: + the long options + """ + builder = _JLongOptions.builder() + _build( + builder, + allow_missing, + allow_null, + allow_int=True, + allow_decimal=allow_decimal, + allow_string=allow_string, + ) + if on_null: + builder.onNull(onNull) + if on_missing: + builder.onMissing(on_missing) + return JsonOptions(builder.build()) + + +def float_( + allow_string: bool = False, + allow_missing: bool = True, + allow_null: bool = True, + on_missing: Optional[float] = None, + on_null: Optional[float] = None, +) -> JsonOptions: + """Creates a float (signed 32-bit) options. For example, the JSON decimal + + .. code-block:: json + 42.42 + + might be modelled as the float type + + .. code-block:: python + float_() + + Args: + allow_string (bool): if the float value is allowed to be a JSON string type, default is False + allow_missing (bool): if the float value is allowed to be missing, default is True + allow_null (bool): if the float value is allowed to be a JSON null type, default is True + on_missing (Optional[float]): the value to use when the JSON value is missing and allow_missing is True, default is None. + on_null (Optional[float]): the value to use when the JSON value is null and allow_null is True, default is None. + + Returns: + the float options + """ + builder = _JFloatOptions.builder() + _build( + builder, + allow_missing, + allow_null, + allow_decimal=True, + allow_int=True, + allow_string=allow_string, + ) + if on_null: + builder.onNull(onNull) + if on_missing: + builder.onMissing(on_missing) + return JsonOptions(builder.build()) + + +def double_( + allow_string: bool = False, + allow_missing: bool = True, + allow_null: bool = True, + on_missing: Optional[float] = None, + on_null: Optional[float] = None, +) -> JsonOptions: + """Creates a double (signed 64-bit) options. For example, the JSON decimal + + .. code-block:: json + 42.42424242 + + might be modelled as the double type + + .. code-block:: python + double_() + + In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can + simplify by using the python built-in float type. For example, + + .. code-block:: python + some_method(double_()) + + could be simplified to + + .. code-block:: python + some_method(float) + + Args: + allow_string (bool): if the double value is allowed to be a JSON string type, default is False + allow_missing (bool): if the double value is allowed to be missing, default is True + allow_null (bool): if the double value is allowed to be a JSON null type, default is True + on_missing (Optional[int]): the value to use when the JSON value is missing and allow_missing is True, default is None. + on_null (Optional[int]): the value to use when the JSON value is null and allow_null is True, default is None. + + Returns: + the double options + """ + builder = _JDoubleOptions.builder() + _build( + builder, + allow_missing, + allow_null, + allow_decimal=True, + allow_int=True, + allow_string=allow_string, + ) + if on_null: + builder.onNull(onNull) + if on_missing: + builder.onMissing(on_missing) + return JsonOptions(builder.build()) + + +def string_( + allow_int: bool = False, + allow_decimal: bool = False, + allow_bool: bool = False, + allow_missing: bool = True, + allow_null: bool = True, + on_missing: Optional[str] = None, + on_null: Optional[str] = None, +) -> JsonOptions: + """Creates a String options. For example, the JSON string + + .. code-block:: json + "Hello, world!" + + might be modelled as the string type + + .. code-block:: python + string_() + + In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can + simplify by using the python built-in str type. For example, + + .. code-block:: python + some_method(string_()) + + could be simplified to + + .. code-block:: python + some_method(str) + + Args: + allow_int (bool): if the string value is allowed to be a JSON integer type, default is False + allow_decimal (bool): if the string value is allowed to be a JSON decimal type, default is False + allow_bool (bool): if the string value is allowed to be a JSON boolean type, default is False + allow_missing (bool): if the double value is allowed to be missing, default is True + allow_null (bool): if the double value is allowed to be a JSON null type, default is True + on_missing (Optional[int]): the value to use when the JSON value is missing and allow_missing is True, default is None. + on_null (Optional[int]): the value to use when the JSON value is null and allow_null is True, default is None. + + Returns: + the double options + """ + builder = _JStringOptions.builder() + _build( + builder, + allow_missing, + allow_null, + allow_string=True, + allow_int=allow_int, + allow_decimal=allow_decimal, + allow_bool=allow_bool, + ) + if on_null: + builder.onNull(onNull) + if on_missing: + builder.onMissing(on_missing) + return JsonOptions(builder.build()) + + +# TODO(deephaven-core#5269): Create deephaven.time time-type aliases +def instant_( + allow_missing: bool = True, + allow_null: bool = True, + number_format: Literal[None, "s", "ms", "us", "ns"] = None, + allow_decimal: bool = False, + on_missing: Optional[Any] = None, + on_null: Optional[Any] = None, +) -> JsonOptions: + """Creates an Instant options. For example, the JSON string + + .. code-block:: json + "2009-02-13T23:31:30.123456789Z" + + might be modelled as the Instant type + + .. code-block:: python + instant_() + + In another example, the JSON decimal + + .. code-block:: json + 1234567890.123456789 + + might be modelled as the Instant type + + .. code-block:: python + instant_(number_format="s", allow_decimal=True) + + In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can + simplify by using the python datetime type. For example, + + .. code-block:: python + some_method(instant_()) + + could be simplified to + + .. code-block:: python + some_method(datetime) + + Args: + allow_missing (bool): if the Instant value is allowed to be missing, default is True + allow_null (bool): if the Instant value is allowed to be a JSON null type, default is True + number_format (Literal[None, "s", "ms", "us", "ns"]): when set, signifies that a JSON numeric type is expected. + "s" is for seconds, "ms" is for milliseconds, "us" is for microseconds, and "ns" is for nanoseconds. + allow_decimal (bool): if the Instant value is allowed to be a JSON decimal type, default is False. Only valid + when number_format is specified. + on_missing (Optional[Any]): the value to use when the JSON value is missing and allow_missing is True, default is None. + on_null (Optional[Any]): the value to use when the JSON value is null and allow_null is True, default is None. + + Returns: + the Instant options + """ + if number_format: + builder = _JInstantNumberOptions.builder() + if on_missing: + builder.onMull(to_j_instant(on_missing)) + if on_null: + builder.onNull(to_j_instant(on_null)) + _build( + builder, + allow_missing, + allow_null, + allow_int=True, + allow_decimal=allow_decimal, + ) + if number_format == "s": + builder.format(_EPOCH_SECONDS) + elif number_format == "ms": + builder.format(_EPOCH_MILLIS) + elif number_format == "us": + builder.format(_EPOCH_MICROS) + elif number_format == "ns": + builder.format(_EPOCH_NANOS) + else: + raise TypeError(f"Invalid number format: {number_format}") + return JsonOptions(builder.build()) + else: + if allow_decimal: + raise TypeError("allow_decimal is only valid when using number_format") + builder = _JInstantOptions.builder() + if on_missing: + builder.onMull(to_j_instant(on_missing)) + if on_null: + builder.onNull(to_j_instant(on_null)) + _build( + builder, + allow_missing, + allow_null, + allow_string=True, + ) + return JsonOptions(builder.build()) + + +def big_integer_( + allow_string: bool = False, + allow_decimal: bool = False, + allow_missing: bool = True, + allow_null: bool = True, + # todo on_null, on_missing +) -> JsonOptions: + """Creates a BigInteger options. For example, the JSON integer + + .. code-block:: json + 123456789012345678901 + + might be modelled as the BigInteger type + + .. code-block:: python + big_integer_() + + Args: + allow_string (bool): if the BigInteger value is allowed to be a JSON string type, default is False. + allow_decimal (bool): if the BigInteger value is allowed to be a JSON decimal type, default is False. + allow_missing (bool): if the BigInteger value is allowed to be missing, default is True + allow_null (bool): if the BigInteger value is allowed to be a JSON null type, default is True + + Returns: + the BigInteger options + """ + builder = _JBigIntegerOptions.builder() + _build( + builder, + allow_missing, + allow_null, + allow_int=True, + allow_decimal=allow_decimal, + allow_string=allow_string, + ) + return JsonOptions(builder.build()) + + +def big_decimal_( + allow_string: bool = False, + allow_missing: bool = True, + allow_null: bool = True, + # todo on_null, on_missing +) -> JsonOptions: + """Creates a BigDecimal options. For example, the JSON decimal + + .. code-block:: json + 123456789012345678901.42 + + might be modelled as the BigDecimal type + + .. code-block:: python + big_decimal_() + + Args: + allow_string (bool): if the BigDecimal value is allowed to be a JSON string type, default is False. + allow_missing (bool): if the BigDecimal value is allowed to be missing, default is True + allow_null (bool): if the BigDecimal value is allowed to be a JSON null type, default is True + + Returns: + the BigDecimal options + """ + builder = _JBigDecimalOptions.builder() + _build( + builder, + allow_missing, + allow_null, + allow_int=True, + allow_decimal=True, + allow_string=allow_string, + ) + return JsonOptions(builder.build()) + + +def any_() -> JsonOptions: + """Creates an "any" options. The resulting type is implementation dependant. + + Returns: + the "any" options + """ + return JsonOptions(_JAnyOptions.of()) + + +def skip_( + allow_missing: Optional[bool] = None, + allow_null: Optional[bool] = None, + allow_int: Optional[bool] = None, + allow_decimal: Optional[bool] = None, + allow_string: Optional[bool] = None, + allow_bool: Optional[bool] = None, + allow_object: Optional[bool] = None, + allow_array: Optional[bool] = None, + allow_by_default: bool = True, +) -> JsonOptions: + """Creates a "skip" type. No resulting type will be returned, but the JSON types will be validated as configured. + This may be useful in combination with an object type where allow_unknown_fields=False. For example, the JSON object + + .. code-block:: json + { "name": "foo", "age": 42 } + + might be modelled as the object type + + .. code-block:: python + object_({ "name": str, "age": skip_() }, allow_unknown_fields=False) + + Args: + allow_missing (Optional[bool]): if a missing JSON value is allowed, by default is None + allow_null (Optional[bool]): if a JSON null type is allowed, by default is None + allow_int (Optional[bool]): if a JSON integer type is allowed, by default is None + allow_decimal (Optional[bool]): if a JSON decimal type is allowed, by default is None + allow_string (Optional[bool]): if a JSON string type is allowed, by default is None + allow_bool (Optional[bool]): if a JSON boolean type is allowed, by default is None + allow_object (Optional[bool]): if a JSON object type is allowed, by default is None + allow_array (Optional[bool]): if a JSON array type is allowed, by default is None + allow_by_default (bool): the default behavior for the other arguments when they are set to None, by default is True + + Returns: + the "skip" options + """ + + def _allow(x: Optional[bool]) -> bool: + return x if x is not None else allow_by_default + + builder = _JSkipOptions.builder() + _build( + builder, + allow_missing=_allow(allow_missing), + allow_null=_allow(allow_null), + allow_int=_allow(allow_int), + allow_decimal=_allow(allow_decimal), + allow_string=_allow(allow_string), + allow_bool=_allow(allow_bool), + allow_object=_allow(allow_object), + allow_array=_allow(allow_array), + ) + return JsonOptions(builder.build()) + + +def json(json_value_type: JsonValueType) -> JsonOptions: + """Creates a JsonOptions from a JsonValueType. + + Args: + json_value_type (JsonValueType): the JSON value type + + Returns: + the JSON options + """ + if isinstance(json_value_type, JsonOptions): + return json_value_type + if isinstance(json_value_type, dtypes.DType): + return _dtype_dict[json_value_type] + if isinstance(json_value_type, type): + return _type_dict[json_value_type] + if isinstance(json_value_type, Dict): + return object_(json_value_type) + if isinstance(json_value_type, List): + if len(json_value_type) is not 1: + raise TypeError("Expected List as json type to have exactly one element") + return array_(json_value_type[0]) + if isinstance(json_value_type, Tuple): + return tuple_(json_value_type) + raise TypeError(f"Unsupported JSON value type {type(json_value_type)}") + + +_dtype_dict = { + dtypes.bool_: bool_(), + dtypes.char: char_(), + dtypes.int8: byte_(), + dtypes.int16: short_(), + dtypes.int32: int_(), + dtypes.int64: long_(), + dtypes.float32: float_(), + dtypes.float64: double_(), + dtypes.string: string_(), + dtypes.Instant: instant_(), + dtypes.BigInteger: big_integer_(), + dtypes.BigDecimal: big_decimal_(), + dtypes.JObject: any_(), + dtypes.bool_array: array_(bool_()), + dtypes.char_array: array_(char_()), + dtypes.int8_array: array_(byte_()), + dtypes.int16_array: array_(short_()), + dtypes.int32_array: array_(int_()), + dtypes.int64_array: array_(long_()), + dtypes.float32_array: array_(float_()), + dtypes.float64_array: array_(double_()), + dtypes.string_array: array_(string_()), + dtypes.instant_array: array_(instant_()), +} + +_type_dict = { + bool: bool_(), + int: long_(), + float: double_(), + str: string_(), + datetime: instant_(), + object: any_(), +} diff --git a/py/server/deephaven/json/jackson.py b/py/server/deephaven/json/jackson.py new file mode 100644 index 00000000000..1449b1c0d3a --- /dev/null +++ b/py/server/deephaven/json/jackson.py @@ -0,0 +1,48 @@ +# +# Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +# +import jpy + +from typing import Optional + +from . import JsonValueType, json as json_ + + +def json(json_value: JsonValueType, factory: Optional[jpy.JType] = None) -> jpy.JType: + """Creates a jackson JSON named object processor provider. + + Args: + json_value(JsonValueType): the JSON value + factory(Optional[jpy.JType]): the factory (java type "com.fasterxml.jackson.core.JsonFactory"), by default is None + + Returns: + the jackson JSON named object processor provider + """ + # todo: should be on the classpath by default, but doesn't have to be + # todo: would be nice if this code could live in the JSON jar + _JProvider = jpy.get_type("io.deephaven.json.jackson.JacksonProvider") + return ( + _JProvider.of(json_(json_value).j_options, factory) + if factory + else _JProvider.of(json_(json_value).j_options) + ) + + +def bson(json_value: JsonValueType, factory: Optional[jpy.JType] = None) -> jpy.JType: + """Creates a jackson BSON named object processor provider. + + Args: + json_value(JsonValueType): the JSON value + factory(Optional[jpy.JType]): the factory (java type "de.undercouch.bson4jackson.BsonFactory"), by default is None + + Returns: + the jackson BSON named object processor provider + """ + # todo: not on the classpath by default + # todo: would be nice if this code could live in the BSON jar + _JProvider = jpy.get_type("io.deephaven.bson.jackson.JacksonBsonProvider") + return ( + _JProvider.of(json_(json_value).j_options, factory) + if factory + else _JProvider.of(json_(json_value).j_options) + ) diff --git a/py/server/deephaven/stream/kafka/consumer.py b/py/server/deephaven/stream/kafka/consumer.py index 35f8623648a..8da2a3ec23b 100644 --- a/py/server/deephaven/stream/kafka/consumer.py +++ b/py/server/deephaven/stream/kafka/consumer.py @@ -483,3 +483,19 @@ def simple_spec(col_name: str, data_type: DType = None) -> KeyValueSpec: ) except Exception as e: raise DHError(e, "failed to create a Kafka key/value spec") from e + + +def object_processor_spec(provider: jpy.JType) -> KeyValueSpec: + """Creates a kafka key or value spec implementation from a named object processor provider. It must be capable of + supporting a byte array. + + Args: + provider (jpy.JType): the named object processor provider + + Returns: + a KeyValueSpec + + Raises: + DHError + """ + return KeyValueSpec(j_spec=_JKafkaTools_Consume.objectProcessorSpec(provider)) diff --git a/server/jetty-app/build.gradle b/server/jetty-app/build.gradle index 407235af4ae..b8bb03926d3 100644 --- a/server/jetty-app/build.gradle +++ b/server/jetty-app/build.gradle @@ -57,6 +57,12 @@ if (!hasProperty('excludeS3')) { } } +if (!hasProperty('excludeJson')) { + dependencies { + runtimeOnly project(':extensions-json-jackson') + } +} + def authHandlers = [] def authConfigs = ['AuthHandlers'] if (hasProperty('anonymous')) { diff --git a/settings.gradle b/settings.gradle index e382aaa86fb..11615bdf4e0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -266,6 +266,15 @@ project(':extensions-trackedfile').projectDir = file('extensions/trackedfile') include(':extensions-s3') project(':extensions-s3').projectDir = file('extensions/s3') +include(':extensions-json') +project(':extensions-json').projectDir = file('extensions/json') + +include(':extensions-json-jackson') +project(':extensions-json-jackson').projectDir = file('extensions/json-jackson') + +include(':extensions-bson-jackson') +project(':extensions-bson-jackson').projectDir = file('extensions/bson-jackson') + include(':plugin') include(':plugin-dagger') From bb1d02e508f52ec3ecfd0c802507edd90060321d Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Thu, 11 Apr 2024 14:11:01 -0700 Subject: [PATCH 02/53] spotless --- .../kafka/src/main/java/io/deephaven/kafka/KafkaTools.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/kafka/src/main/java/io/deephaven/kafka/KafkaTools.java b/extensions/kafka/src/main/java/io/deephaven/kafka/KafkaTools.java index 3c772410198..bba716bc076 100644 --- a/extensions/kafka/src/main/java/io/deephaven/kafka/KafkaTools.java +++ b/extensions/kafka/src/main/java/io/deephaven/kafka/KafkaTools.java @@ -609,7 +609,7 @@ public static KeyOrValueSpec objectProcessorSpec( */ @SuppressWarnings("unused") public static KeyOrValueSpec objectProcessorSpec(ObjectProcessor processor, - List columnNames) { + List columnNames) { return objectProcessorSpec(NamedObjectProcessor.of(processor, columnNames)); } From 5c7dde93eaf39f5b75cd36d9010960358dcb995b Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Fri, 12 Apr 2024 08:41:23 -0700 Subject: [PATCH 03/53] review response --- .../io/deephaven/json/JsonValueTypesTest.java | 45 ----- .../io/deephaven/json/LongOptionsTest.java | 6 +- .../java/io/deephaven/json/AnyOptions.java | 17 +- .../java/io/deephaven/json/ArrayOptions.java | 40 +++- .../io/deephaven/json/BigDecimalOptions.java | 35 +++- .../io/deephaven/json/BigIntegerOptions.java | 42 +++- .../java/io/deephaven/json/BoolOptions.java | 26 +-- .../java/io/deephaven/json/ByteOptions.java | 26 +-- .../java/io/deephaven/json/CharOptions.java | 22 +-- .../java/io/deephaven/json/DoubleOptions.java | 29 +-- .../java/io/deephaven/json/FloatOptions.java | 28 +-- .../deephaven/json/InstantNumberOptions.java | 63 ++++-- .../io/deephaven/json/InstantOptions.java | 28 ++- .../java/io/deephaven/json/IntOptions.java | 29 ++- .../io/deephaven/json/JsonValueTypes.java | 180 +++++++++++++++--- .../io/deephaven/json/LocalDateOptions.java | 18 +- .../java/io/deephaven/json/LongOptions.java | 28 ++- .../io/deephaven/json/ObjectKvOptions.java | 19 +- .../java/io/deephaven/json/ObjectOptions.java | 33 ++-- .../java/io/deephaven/json/ShortOptions.java | 26 +-- .../java/io/deephaven/json/SkipOptions.java | 13 +- .../java/io/deephaven/json/StringOptions.java | 35 +++- .../java/io/deephaven/json/TupleOptions.java | 22 ++- .../io/deephaven/json/TypedObjectOptions.java | 17 +- .../java/io/deephaven/json/ValueOptions.java | 9 +- .../ValueOptionsRestrictedUniverseBase.java | 21 +- .../java/io/deephaven/json/package-info.java | 23 +++ .../io/deephaven/json/JsonValueTypesTest.java | 28 +++ .../json/TypedObjectOptionsTest.java | 46 +++++ py/server/deephaven/json/__init__.py | 2 +- 30 files changed, 640 insertions(+), 316 deletions(-) delete mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/JsonValueTypesTest.java create mode 100644 extensions/json/src/main/java/io/deephaven/json/package-info.java create mode 100644 extensions/json/src/test/java/io/deephaven/json/JsonValueTypesTest.java create mode 100644 extensions/json/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/JsonValueTypesTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/JsonValueTypesTest.java deleted file mode 100644 index 75daf99067f..00000000000 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/JsonValueTypesTest.java +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json; - -import org.junit.jupiter.api.Test; - -public class JsonValueTypesTest { - - @Test - public void all() { - JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.ALL); - } - - @Test - public void numberLike() { - JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.NUMBER_LIKE); - } - - @Test - public void stringLike() { - JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.STRING_LIKE); - } - - @Test - public void stringOrNull() { - JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.STRING_OR_NULL); - } - - @Test - public void objectOrNull() { - JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.OBJECT_OR_NULL); - } - - @Test - public void arrayOrNull() { - JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.ARRAY_OR_NULL); - } - - @Test - public void numberIntOrNull() { - JsonValueTypes.checkAllowedTypeInvariants(JsonValueTypes.INT_OR_NULL); - } - -} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java index 3d067370382..c2a7ba42579 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java @@ -146,14 +146,14 @@ void allowDecimal() throws IOException { @Test void allowDecimalString() throws IOException { - parse(LongOptions.builder().allowedTypes(JsonValueTypes.NUMBER_LIKE).build(), + parse(LongOptions.builder().allowedTypes(JsonValueTypes.numberLike()).build(), List.of("\"42.42\"", "\"43.999\""), LongChunk.chunkWrap(new long[] {42, 43})); } @Test void decimalStringLimitsNearMinValue() throws IOException { for (int i = 0; i < 100; ++i) { - parse(LongOptions.builder().allowedTypes(JsonValueTypes.NUMBER_LIKE).build(), + parse(LongOptions.builder().allowedTypes(JsonValueTypes.numberLike()).build(), List.of(String.format("\"%d.0\"", Long.MIN_VALUE + i)), LongChunk.chunkWrap(new long[] {Long.MIN_VALUE + i})); } @@ -162,7 +162,7 @@ void decimalStringLimitsNearMinValue() throws IOException { @Test void decimalStringLimitsNearMaxValue() throws IOException { for (int i = 0; i < 100; ++i) { - parse(LongOptions.builder().allowedTypes(JsonValueTypes.NUMBER_LIKE).build(), + parse(LongOptions.builder().allowedTypes(JsonValueTypes.numberLike()).build(), List.of(String.format("\"%d.0\"", Long.MAX_VALUE - i)), LongChunk.chunkWrap(new long[] {Long.MAX_VALUE - i})); } diff --git a/extensions/json/src/main/java/io/deephaven/json/AnyOptions.java b/extensions/json/src/main/java/io/deephaven/json/AnyOptions.java index 3c9198fa9ba..e54ba4f420f 100644 --- a/extensions/json/src/main/java/io/deephaven/json/AnyOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/AnyOptions.java @@ -3,28 +3,33 @@ // package io.deephaven.json; -import io.deephaven.annotations.SimpleStyle; +import io.deephaven.annotations.SingletonStyle; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON value as an implementation-specific object. */ @Immutable -@SimpleStyle +@SingletonStyle public abstract class AnyOptions extends ValueOptions { + /** + * Allows missing and accepts {@link JsonValueTypes#all()}. + * + * @return the any options + */ public static AnyOptions of() { return ImmutableAnyOptions.of(); } /** - * Always {@link JsonValueTypes#ALL}. + * Always {@link JsonValueTypes#all()}. */ @Override - public final EnumSet allowedTypes() { - return JsonValueTypes.ALL; + public final Set allowedTypes() { + return JsonValueTypes.all(); } /** diff --git a/extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java b/extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java index f6b29d23232..3c9d24b2eee 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java @@ -7,8 +7,15 @@ import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; +import java.util.Set; +/** + * A "typed array", where all the elements in the {@link JsonValueTypes#ARRAY} have the same {@link #element()} type. + * + *

+ * For example, the JSON value {@code [1, 42, 43, 13]} might be modelled as + * {@code ArrayOptions.standard(IntOptions.standard())}. + */ @Immutable @BuildableStyle public abstract class ArrayOptions extends ValueOptionsRestrictedUniverseBase { @@ -17,35 +24,50 @@ public static Builder builder() { return ImmutableArrayOptions.builder(); } + /** + * The standard array options. Allows missing and accepts {@link JsonValueTypes#arrayOrNull()}. + * + * @param element the element type + * @return the standard array options + */ public static ArrayOptions standard(ValueOptions element) { return builder().element(element).build(); } + /** + * The strict array options. Disallows missing and accepts {@link JsonValueTypes#array()}. + * + * @param element the element type + * @return the strict array options + */ public static ArrayOptions strict(ValueOptions element) { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.ARRAY) + .allowedTypes(JsonValueTypes.array()) .element(element) .build(); } + /** + * The type for the elements of the array. + */ public abstract ValueOptions element(); /** - * {@inheritDoc} By default is {@link JsonValueTypes#ARRAY_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#arrayOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.ARRAY_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.arrayOrNull(); } /** - * The universe, is {@link JsonValueTypes#ARRAY_OR_NULL}. + * {@inheritDoc} Is {@link JsonValueTypes#arrayOrNull()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.ARRAY_OR_NULL; + public final Set universe() { + return JsonValueTypes.arrayOrNull(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java b/extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java index 8ee26f9ae2f..4ee7b6e526b 100644 --- a/extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java @@ -8,7 +8,7 @@ import org.immutables.value.Value.Immutable; import java.math.BigDecimal; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON value as a {@link BigDecimal}. @@ -21,36 +21,51 @@ public static Builder builder() { return ImmutableBigDecimalOptions.builder(); } + /** + * The lenient {@link BigDecimal} options. Allows missing and accepts {@link JsonValueTypes#numberLike()}. + * + * @return the lenient BigDecimal options + */ public static BigDecimalOptions lenient() { - return builder().allowedTypes(JsonValueTypes.NUMBER_LIKE).build(); + return builder().allowedTypes(JsonValueTypes.numberLike()).build(); } + /** + * The standard {@link BigDecimal} options. Allows missing and accepts {@link JsonValueTypes#numberOrNull()}. + * + * @return the standard BigDecimal options + */ public static BigDecimalOptions standard() { return builder().build(); } + /** + * The strict {@link BigDecimal} options. Disallows missing and accepts {@link JsonValueTypes#number()}. + * + * @return the strict BigDecimal options + */ public static BigDecimalOptions strict() { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.NUMBER) + .allowedTypes(JsonValueTypes.number()) .build(); } /** - * {@inheritDoc} By default is {@link JsonValueTypes#NUMBER_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#numberOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.NUMBER_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.numberOrNull(); } /** - * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.NUMBER_LIKE; + public final Set universe() { + return JsonValueTypes.numberLike(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java b/extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java index b1bc759858a..d7bc4bce5e6 100644 --- a/extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java @@ -4,10 +4,11 @@ package io.deephaven.json; import io.deephaven.annotations.BuildableStyle; +import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; import java.math.BigInteger; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON value as a {@link BigInteger}. @@ -19,31 +20,58 @@ public static Builder builder() { return ImmutableBigIntegerOptions.builder(); } + /** + * The lenient {@link BigInteger} options. Allows missing. If {@code allowDecimal}, accepts + * {@link JsonValueTypes#numberLike()}, otherwise accepts {@link JsonValueTypes#intLike()}. + * + * @return the lenient BigInteger options + */ public static BigIntegerOptions lenient(boolean allowDecimal) { return builder() - .allowedTypes(allowDecimal ? JsonValueTypes.NUMBER_LIKE : JsonValueTypes.INT_LIKE) + .allowedTypes(allowDecimal ? JsonValueTypes.numberLike() : JsonValueTypes.intLike()) .build(); } + /** + * The standard {@link BigInteger} options. Allows missing. If {@code allowDecimal}, accepts + * {@link JsonValueTypes#numberOrNull()}, otherwise accepts {@link JsonValueTypes#intOrNull()}. + * + * @return the standard BigInteger options + */ public static BigIntegerOptions standard(boolean allowDecimal) { return builder() - .allowedTypes(allowDecimal ? JsonValueTypes.NUMBER_OR_NULL : JsonValueTypes.INT_OR_NULL) + .allowedTypes(allowDecimal ? JsonValueTypes.numberOrNull() : JsonValueTypes.intOrNull()) .build(); } + /** + * The strict {@link BigInteger} options. Allows missing. If {@code allowDecimal}, accepts + * {@link JsonValueTypes#number()}, otherwise accepts {@link JsonValueTypes#int_()}. + * + * @return the strict BigInteger options + */ public static BigIntegerOptions strict(boolean allowDecimal) { return builder() .allowMissing(false) - .allowedTypes(allowDecimal ? JsonValueTypes.NUMBER : EnumSet.of(JsonValueTypes.INT)) + .allowedTypes(allowDecimal ? JsonValueTypes.number() : JsonValueTypes.int_()) .build(); } /** - * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + * {@inheritDoc} By default is {@link JsonValueTypes#intOrNull()}. + */ + @Override + @Default + public Set allowedTypes() { + return JsonValueTypes.intOrNull(); + } + + /** + * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.NUMBER_LIKE; + public final Set universe() { + return JsonValueTypes.numberLike(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java b/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java index 14bc4dea56d..86dc74f3d07 100644 --- a/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java @@ -7,7 +7,7 @@ import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON value as a {@code boolean}. @@ -21,18 +21,18 @@ public static Builder builder() { } /** - * The lenient bool options. + * The lenient bool options. Allows missing and accepts {@link JsonValueTypes#boolLike()}. * * @return the lenient bool options */ public static BoolOptions lenient() { return builder() - .allowedTypes(JsonValueTypes.BOOL_LIKE) + .allowedTypes(JsonValueTypes.boolLike()) .build(); } /** - * The standard bool options. + * The standard bool options. Allows missing and accepts {@link JsonValueTypes#boolOrNull()}. * * @return the standard bool options */ @@ -41,32 +41,32 @@ public static BoolOptions standard() { } /** - * The strict bool options. + * The strict bool options. Disallows missing and accepts {@link JsonValueTypes#bool()}. * * @return the strict bool options */ public static BoolOptions strict() { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.BOOL) + .allowedTypes(JsonValueTypes.bool()) .build(); } /** - * {@inheritDoc} By default is {@link JsonValueTypes#BOOL_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#boolOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.BOOL_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.boolOrNull(); } /** - * The universe, is {@link JsonValueTypes#BOOL_LIKE}. + * {@inheritDoc} Is {@link JsonValueTypes#boolLike()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.BOOL_LIKE; + public final Set universe() { + return JsonValueTypes.boolLike(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java b/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java index 7008f3546f1..a3d9cd8a5a9 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java @@ -7,7 +7,7 @@ import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON value as a {@code byte}. @@ -21,18 +21,18 @@ public static Builder builder() { } /** - * The lenient byte options. + * The lenient byte options. Allows missing and accepts {@link JsonValueTypes#intLike()}. * * @return the lenient byte options */ public static ByteOptions lenient() { return builder() - .allowedTypes(JsonValueTypes.INT_LIKE) + .allowedTypes(JsonValueTypes.intLike()) .build(); } /** - * The standard byte options. + * The standard byte options. Allows missing and accepts {@link JsonValueTypes#intOrNull()}. * * @return the standard byte options */ @@ -41,32 +41,32 @@ public static ByteOptions standard() { } /** - * The strict byte options. + * The strict byte options. Disallows missing and accepts {@link JsonValueTypes#int_()}. * * @return the strict byte options */ public static ByteOptions strict() { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.INT) + .allowedTypes(JsonValueTypes.int_()) .build(); } /** - * {@inheritDoc} By default is {@link JsonValueTypes#INT_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#intOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.INT_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.intOrNull(); } /** - * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.NUMBER_LIKE; + public final Set universe() { + return JsonValueTypes.numberLike(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/CharOptions.java b/extensions/json/src/main/java/io/deephaven/json/CharOptions.java index f59457b0de8..d878d9d9d46 100644 --- a/extensions/json/src/main/java/io/deephaven/json/CharOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/CharOptions.java @@ -7,7 +7,7 @@ import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON value as a {@code char}. @@ -22,7 +22,7 @@ public static Builder builder() { /** - * The standard char options. + * The standard char options. Allows missing and accepts {@link JsonValueTypes#stringOrNull()}. * * @return the standard char options */ @@ -31,32 +31,32 @@ public static CharOptions standard() { } /** - * The strict char options. + * The strict char options. Disallows missing and accepts {@link JsonValueTypes#string()}. * * @return the strict char options */ public static CharOptions strict() { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.STRING) + .allowedTypes(JsonValueTypes.string()) .build(); } /** - * {@inheritDoc} By default is {@link JsonValueTypes#STRING_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#stringOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.STRING_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.stringOrNull(); } /** - * The universe, is {@link JsonValueTypes#STRING_OR_NULL}. + * {@inheritDoc}. Is {@link JsonValueTypes#stringOrNull()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.STRING_OR_NULL; + public final Set universe() { + return JsonValueTypes.stringOrNull(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java b/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java index b77f68a5639..dbfdf0c2fbe 100644 --- a/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java @@ -7,7 +7,7 @@ import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON value as a {@code double}. @@ -22,16 +22,18 @@ public static Builder builder() { } /** - * The lenient double options. + * The lenient double options. Allows missing and accepts {@link JsonValueTypes#numberLike()}. * * @return the lenient double options */ public static DoubleOptions lenient() { - return builder().allowedTypes(JsonValueTypes.NUMBER_LIKE).build(); + return builder() + .allowedTypes(JsonValueTypes.numberLike()) + .build(); } /** - * The standard double options. + * The standard double options. Allows missing and accepts {@link JsonValueTypes#numberOrNull()}. * * @return the standard double options */ @@ -40,35 +42,34 @@ public static DoubleOptions standard() { } /** - * The strict double options. + * The strict double options. Disallows missing and accepts {@link JsonValueTypes#number()}. * * @return the strict double options */ public static DoubleOptions strict() { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.NUMBER) + .allowedTypes(JsonValueTypes.number()) .build(); } /** - * {@inheritDoc} By default is {@link JsonValueTypes#NUMBER_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#numberOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.NUMBER_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.numberOrNull(); } /** - * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.NUMBER_LIKE; + public final Set universe() { + return JsonValueTypes.numberLike(); } - @Override public final T walk(Visitor visitor) { return visitor.visit(this); diff --git a/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java b/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java index 6af05b516a9..0c9ad4e0269 100644 --- a/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java @@ -7,7 +7,7 @@ import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON value as a {@code float}. @@ -21,16 +21,18 @@ public static Builder builder() { } /** - * The lenient float options. + * The lenient float options. Allows missing and accepts {@link JsonValueTypes#numberLike()}. * * @return the lenient float options */ public static FloatOptions lenient() { - return builder().allowedTypes(JsonValueTypes.NUMBER_LIKE).build(); + return builder() + .allowedTypes(JsonValueTypes.numberLike()) + .build(); } /** - * The standard float options.. + * The standard float options. Allows missing and accepts {@link JsonValueTypes#numberOrNull()}. * * @return the standard float options */ @@ -39,32 +41,32 @@ public static FloatOptions standard() { } /** - * The strict float options. + * The strict float options. Disallows missing and accepts {@link JsonValueTypes#number()}. * * @return the strict float options */ public static FloatOptions strict() { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.NUMBER) + .allowedTypes(JsonValueTypes.number()) .build(); } /** - * {@inheritDoc} By default is {@link JsonValueTypes#NUMBER_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#numberOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.NUMBER_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.numberOrNull(); } /** - * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.NUMBER_LIKE; + public final Set universe() { + return JsonValueTypes.numberLike(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java b/extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java index 5ed86326f00..21aa6873d22 100644 --- a/extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java @@ -8,7 +8,7 @@ import org.immutables.value.Value.Immutable; import java.time.Instant; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON number as an {@link Instant}. @@ -18,27 +18,66 @@ public abstract class InstantNumberOptions extends ValueOptionsSingleValueBase { public enum Format { - EPOCH_SECONDS, EPOCH_MILLIS, EPOCH_MICROS, EPOCH_NANOS; + /** + * Seconds from the epoch of 1970-01-01T00:00:00Z. + */ + EPOCH_SECONDS, + /** + * Milliseconds from the epoch of 1970-01-01T00:00:00Z. + */ + EPOCH_MILLIS, + + /** + * Microseconds from the epoch of 1970-01-01T00:00:00Z. + */ + EPOCH_MICROS, + + /** + * Nanoseconds from the epoch of 1970-01-01T00:00:00Z. + */ + EPOCH_NANOS; + + /** + * The lenient {@link Instant} number options. Allows missing. If {@code allowDecimal}, accepts + * {@link JsonValueTypes#numberLike()}, otherwise accepts {@link JsonValueTypes#intLike()}. + * + * @param allowDecimal if decimals should be allowed + * @return the lenient Instant number options + */ public InstantNumberOptions lenient(boolean allowDecimal) { return builder() .format(this) - .allowedTypes(allowDecimal ? JsonValueTypes.NUMBER_LIKE : JsonValueTypes.INT_LIKE) + .allowedTypes(allowDecimal ? JsonValueTypes.numberLike() : JsonValueTypes.intLike()) .build(); } + /** + * The standard {@link Instant} number options. Allows missing. If {@code allowDecimal}, accepts + * {@link JsonValueTypes#numberOrNull()}, otherwise accepts {@link JsonValueTypes#intOrNull()}. + * + * @param allowDecimal if decimals should be allowed + * @return the standard Instant number options + */ public InstantNumberOptions standard(boolean allowDecimal) { return builder() .format(this) - .allowedTypes(allowDecimal ? JsonValueTypes.NUMBER_OR_NULL : JsonValueTypes.INT_OR_NULL) + .allowedTypes(allowDecimal ? JsonValueTypes.numberOrNull() : JsonValueTypes.intOrNull()) .build(); } + /** + * The strict {@link Instant} number options. Disallows missing. If {@code allowDecimal}, accepts + * {@link JsonValueTypes#number()}, otherwise accepts {@link JsonValueTypes#int_()}. + * + * @param allowDecimal if decimals should be allowed + * @return the lenient Instant number options + */ public InstantNumberOptions strict(boolean allowDecimal) { return builder() .format(this) .allowMissing(false) - .allowedTypes(allowDecimal ? JsonValueTypes.NUMBER : EnumSet.of(JsonValueTypes.INT)) + .allowedTypes(allowDecimal ? JsonValueTypes.number() : JsonValueTypes.int_()) .build(); } } @@ -53,20 +92,20 @@ public static Builder builder() { public abstract Format format(); /** - * {@inheritDoc} By default is {@link JsonValueTypes#INT_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#intOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.INT_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.intOrNull(); } /** - * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.NUMBER_LIKE; + public final Set universe() { + return JsonValueTypes.numberLike(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/InstantOptions.java b/extensions/json/src/main/java/io/deephaven/json/InstantOptions.java index b7376148557..fa4e4f28646 100644 --- a/extensions/json/src/main/java/io/deephaven/json/InstantOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/InstantOptions.java @@ -10,7 +10,7 @@ import java.lang.Runtime.Version; import java.time.Instant; import java.time.format.DateTimeFormatter; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON string as an {@link Instant}. @@ -25,32 +25,42 @@ public static Builder builder() { return ImmutableInstantOptions.builder(); } + /** + * The standard {@link Instant} options. Allows missing and accepts {@link JsonValueTypes#stringOrNull()}. + * + * @return the standard Instant options + */ public static InstantOptions standard() { return builder().build(); } + /** + * The strict {@link Instant} options. Disallows missing and accepts {@link JsonValueTypes#string()}. + * + * @return the strict Instant options + */ public static InstantOptions strict() { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.STRING) + .allowedTypes(JsonValueTypes.string()) .build(); } /** - * {@inheritDoc} By default is {@link JsonValueTypes#STRING_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#stringOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.STRING_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.stringOrNull(); } /** - * The universe, is {@link JsonValueTypes#STRING_OR_NULL}. + * {@inheritDoc} Is {@link JsonValueTypes#stringOrNull()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.STRING_OR_NULL; + public final Set universe() { + return JsonValueTypes.stringOrNull(); } /** diff --git a/extensions/json/src/main/java/io/deephaven/json/IntOptions.java b/extensions/json/src/main/java/io/deephaven/json/IntOptions.java index 39c915043d1..de63c49fade 100644 --- a/extensions/json/src/main/java/io/deephaven/json/IntOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/IntOptions.java @@ -4,12 +4,10 @@ package io.deephaven.json; import io.deephaven.annotations.BuildableStyle; -import org.immutables.value.Value.Check; import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; -import java.util.OptionalInt; +import java.util.Set; /** * Processes a JSON value as an {@code int}. @@ -23,18 +21,18 @@ public static Builder builder() { } /** - * The lenient int options. + * The lenient int options. Allows missing and accepts {@link JsonValueTypes#intLike()}. * * @return the lenient int options */ public static IntOptions lenient() { return builder() - .allowedTypes(JsonValueTypes.INT_LIKE) + .allowedTypes(JsonValueTypes.intLike()) .build(); } /** - * The standard int options. + * The standard int options. Allows missing and accepts {@link JsonValueTypes#intOrNull()}. * * @return the standard int options */ @@ -43,35 +41,34 @@ public static IntOptions standard() { } /** - * The strict int options. + * The strict int options. Disallows missing and accepts {@link JsonValueTypes#int_()}. * * @return the strict int options */ public static IntOptions strict() { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.INT) + .allowedTypes(JsonValueTypes.int_()) .build(); } /** - * {@inheritDoc} By default is {@link JsonValueTypes#INT_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#intOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.INT_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.intOrNull(); } /** - * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.NUMBER_LIKE; + public final Set universe() { + return JsonValueTypes.numberLike(); } - @Override public final T walk(Visitor visitor) { return visitor.visit(this); diff --git a/extensions/json/src/main/java/io/deephaven/json/JsonValueTypes.java b/extensions/json/src/main/java/io/deephaven/json/JsonValueTypes.java index c1f30ac62c2..45c25f8e81c 100644 --- a/extensions/json/src/main/java/io/deephaven/json/JsonValueTypes.java +++ b/extensions/json/src/main/java/io/deephaven/json/JsonValueTypes.java @@ -3,77 +3,171 @@ // package io.deephaven.json; +import java.util.Collections; import java.util.EnumSet; +import java.util.Set; /** * The JSON value types. */ public enum JsonValueTypes { - OBJECT, ARRAY, STRING, INT, DECIMAL, BOOL, NULL; + /** + * A JSON object type. + */ + OBJECT, + + /** + * A JSON array type. + */ + ARRAY, + + /** + * A JSON string type. + */ + STRING, + + /** + * A JSON number type without a decimal. + */ + INT, /** - * The set of all {@link JsonValueTypes}. + * A JSON number type with a decimal. */ - public static final EnumSet ALL = EnumSet.allOf(JsonValueTypes.class); + DECIMAL, /** - * The set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#NULL}. + * The JSON literal 'true' or 'false' type. */ - public static final EnumSet INT_OR_NULL = EnumSet.of(INT, NULL); + BOOL, /** - * The set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#STRING}, {@link JsonValueTypes#NULL}. + * The JSON literal 'null' type. */ - public static final EnumSet INT_LIKE = EnumSet.of(INT, STRING, NULL); + NULL; /** - * The set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#DECIMAL}. + * An unmodifiable set of all {@link JsonValueTypes}. */ - public static final EnumSet NUMBER = EnumSet.of(INT, DECIMAL); + public static Set all() { + return ALL; + } /** - * The set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#DECIMAL}, {@link JsonValueTypes#NULL}. + * An unmodifiable set of {@link JsonValueTypes#INT}. */ - public static final EnumSet NUMBER_OR_NULL = EnumSet.of(INT, DECIMAL, NULL); + public static Set int_() { + return INT_SET; + } /** - * The set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#DECIMAL}, {@link JsonValueTypes#STRING}, + * An unmodifiable set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#NULL}. + */ + public static Set intOrNull() { + return INT_OR_NULL; + } + + /** + * An unmodifiable set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#STRING}, {@link JsonValueTypes#NULL} + */ + public static Set intLike() { + return INT_LIKE; + } + + /** + * An unmodifiable set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#DECIMAL}. + */ + public static Set number() { + return NUMBER; + } + + /** + * An unmodifiable set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#DECIMAL}, {@link JsonValueTypes#NULL}. + */ + public static Set numberOrNull() { + return NUMBER_OR_NULL; + } + + /** + * An unmodifiable set of {@link JsonValueTypes#INT}, {@link JsonValueTypes#DECIMAL}, {@link JsonValueTypes#STRING}, * {@link JsonValueTypes#NULL}. */ - public static final EnumSet NUMBER_LIKE = EnumSet.of(INT, DECIMAL, STRING, NULL); + public static Set numberLike() { + return NUMBER_LIKE; + } + + /** + * An unmodifiable set of {@link JsonValueTypes#STRING}. + */ + public static Set string() { + return STRING_SET; + } /** - * The set of {@link JsonValueTypes#STRING}, {@link JsonValueTypes#NULL}. + * An unmodifiable set of {@link JsonValueTypes#STRING}, {@link JsonValueTypes#NULL}. */ - public static final EnumSet STRING_OR_NULL = EnumSet.of(STRING, NULL); + public static Set stringOrNull() { + return STRING_OR_NULL; + } /** - * The set of {@link JsonValueTypes#STRING}, {@link JsonValueTypes#INT}, {@link JsonValueTypes#DECIMAL}, + * An unmodifiable set of {@link JsonValueTypes#STRING}, {@link JsonValueTypes#INT}, {@link JsonValueTypes#DECIMAL}, * {@link JsonValueTypes#BOOL}, {@link JsonValueTypes#NULL}. */ - public static final EnumSet STRING_LIKE = EnumSet.of(STRING, INT, DECIMAL, BOOL, NULL); + public static Set stringLike() { + return STRING_LIKE; + } /** - * The set of {@link JsonValueTypes#BOOL}, {@link JsonValueTypes#NULL}. + * An unmodifiable set of {@link JsonValueTypes#BOOL}. */ - public static final EnumSet BOOL_OR_NULL = EnumSet.of(BOOL, NULL); + public static Set bool() { + return BOOL_SET; + } /** - * The set of {@link JsonValueTypes#BOOL}, {@link JsonValueTypes#STRING}, {@link JsonValueTypes#NULL}. + * An unmodifiable set of {@link JsonValueTypes#BOOL}, {@link JsonValueTypes#NULL}. */ - public static final EnumSet BOOL_LIKE = EnumSet.of(STRING, BOOL, NULL); + public static Set boolOrNull() { + return BOOL_OR_NULL; + } /** - * The set of {@link JsonValueTypes#OBJECT}, {@link JsonValueTypes#NULL}. + * An unmodifiable set of {@link JsonValueTypes#BOOL}, {@link JsonValueTypes#STRING}, {@link JsonValueTypes#NULL}. */ - public static final EnumSet OBJECT_OR_NULL = EnumSet.of(OBJECT, NULL); + public static Set boolLike() { + return BOOL_LIKE; + } /** - * The set of {@link JsonValueTypes#ARRAY}, {@link JsonValueTypes#NULL}. + * An unmodifiable set of {@link JsonValueTypes#OBJECT}. */ - public static final EnumSet ARRAY_OR_NULL = EnumSet.of(ARRAY, NULL); + public static Set object() { + return OBJECT_SET; + } + + /** + * An unmodifiable set of {@link JsonValueTypes#OBJECT}, {@link JsonValueTypes#NULL}. + */ + public static Set objectOrNull() { + return OBJECT_OR_NULL; + } + + /** + * An unmodifiable set of {@link JsonValueTypes#ARRAY}. + */ + public static Set array() { + return ARRAY_SET; + } + + /** + * An unmodifiable set of {@link JsonValueTypes#ARRAY}, {@link JsonValueTypes#NULL}. + */ + public static Set arrayOrNull() { + return ARRAY_OR_NULL; + } - static void checkAllowedTypeInvariants(EnumSet allowedTypes) { + static void checkAllowedTypeInvariants(Set allowedTypes) { if (allowedTypes.isEmpty()) { throw new IllegalArgumentException("allowedTypes is empty"); } @@ -84,4 +178,38 @@ static void checkAllowedTypeInvariants(EnumSet allowedTypes) { throw new IllegalArgumentException("allowedTypes is accepting DECIMAL but not INT"); } } + + private static final Set ALL = Collections.unmodifiableSet(EnumSet.allOf(JsonValueTypes.class)); + + private static final Set INT_SET = Collections.unmodifiableSet(EnumSet.of(INT)); + + private static final Set INT_OR_NULL = Collections.unmodifiableSet(EnumSet.of(INT, NULL)); + + private static final Set INT_LIKE = Collections.unmodifiableSet(EnumSet.of(INT, STRING, NULL)); + + private static final Set NUMBER = Collections.unmodifiableSet(EnumSet.of(INT, DECIMAL)); + + private static final Set NUMBER_OR_NULL = Collections.unmodifiableSet(EnumSet.of(INT, DECIMAL, NULL)); + + private static final Set NUMBER_LIKE = Collections.unmodifiableSet(EnumSet.of(INT, DECIMAL, STRING, NULL)); + + private static final Set STRING_SET = Collections.unmodifiableSet(EnumSet.of(STRING)); + + private static final Set STRING_OR_NULL = Collections.unmodifiableSet(EnumSet.of(STRING, NULL)); + + private static final Set STRING_LIKE = Collections.unmodifiableSet(EnumSet.of(STRING, INT, DECIMAL, BOOL, NULL)); + + private static final Set BOOL_SET = Collections.unmodifiableSet(EnumSet.of(BOOL)); + + private static final Set BOOL_OR_NULL = Collections.unmodifiableSet(EnumSet.of(BOOL, NULL)); + + private static final Set BOOL_LIKE = Collections.unmodifiableSet(EnumSet.of(BOOL, STRING, NULL)); + + private static final Set OBJECT_SET = Collections.unmodifiableSet(EnumSet.of(OBJECT)); + + private static final Set OBJECT_OR_NULL = Collections.unmodifiableSet(EnumSet.of(OBJECT, NULL)); + + private static final Set ARRAY_SET = Collections.unmodifiableSet(EnumSet.of(ARRAY)); + + private static final Set ARRAY_OR_NULL = Collections.unmodifiableSet(EnumSet.of(ARRAY, NULL)); } diff --git a/extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java b/extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java index 85dadab4f77..1a6979b7088 100644 --- a/extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java @@ -10,7 +10,7 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON string as an {@link LocalDate}. @@ -29,25 +29,25 @@ public static LocalDateOptions standard() { public static LocalDateOptions strict() { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.STRING) + .allowedTypes(JsonValueTypes.string()) .build(); } /** - * {@inheritDoc} By default is {@link JsonValueTypes#STRING_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#stringOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.STRING_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.stringOrNull(); } /** - * The universe, is {@link JsonValueTypes#STRING_OR_NULL}. + * {@inheritDoc} Is {@link JsonValueTypes#stringOrNull()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.STRING_OR_NULL; + public final Set universe() { + return JsonValueTypes.stringOrNull(); } /** diff --git a/extensions/json/src/main/java/io/deephaven/json/LongOptions.java b/extensions/json/src/main/java/io/deephaven/json/LongOptions.java index 7a2951d3438..eade25446f5 100644 --- a/extensions/json/src/main/java/io/deephaven/json/LongOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/LongOptions.java @@ -4,12 +4,10 @@ package io.deephaven.json; import io.deephaven.annotations.BuildableStyle; -import org.immutables.value.Value.Check; import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; -import java.util.OptionalLong; +import java.util.Set; /** * Processes a JSON value as a {@code long}. @@ -23,18 +21,18 @@ public static Builder builder() { } /** - * The lenient long options. + * The lenient long options. Allows missing and accepts {@link JsonValueTypes#intLike()}. * * @return the lenient long options */ public static LongOptions lenient() { return builder() - .allowedTypes(JsonValueTypes.INT_LIKE) + .allowedTypes(JsonValueTypes.intLike()) .build(); } /** - * The standard long options. + * The standard long options. Allows missing and accepts {@link JsonValueTypes#intOrNull()}. * * @return the standard long options */ @@ -43,32 +41,32 @@ public static LongOptions standard() { } /** - * The strict long options. + * The strict long options. Disallows missing and accepts {@link JsonValueTypes#int_()}. * * @return the strict long options */ public static LongOptions strict() { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.INT) + .allowedTypes(JsonValueTypes.int_()) .build(); } /** - * {@inheritDoc} By default is {@link JsonValueTypes#INT_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#intOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.INT_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.intOrNull(); } /** - * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.NUMBER_LIKE; + public final Set universe() { + return JsonValueTypes.numberLike(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java b/extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java index 2430a1cfff3..7efc3693d1f 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java @@ -4,12 +4,11 @@ package io.deephaven.json; import io.deephaven.annotations.BuildableStyle; -import io.deephaven.json.ObjectFieldOptions.RepeatedBehavior; import org.immutables.value.Value.Check; import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON object of variable size with a given key and value options. For example, when a JSON object @@ -40,7 +39,7 @@ public static ObjectKvOptions standard(ValueOptions value) { public static ObjectKvOptions strict(ValueOptions value) { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.OBJECT) + .allowedTypes(JsonValueTypes.object()) .value(value) .build(); } @@ -60,20 +59,20 @@ public ValueOptions key() { public abstract ValueOptions value(); /** - * {@inheritDoc} By default is {@link JsonValueTypes#OBJECT_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#objectOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.OBJECT_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.objectOrNull(); } /** - * The universe, is {@link JsonValueTypes#OBJECT_OR_NULL}. + * {@inheritDoc} Is {@link JsonValueTypes#objectOrNull()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.OBJECT_OR_NULL; + public final Set universe() { + return JsonValueTypes.objectOrNull(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java b/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java index fd08f63b754..cf0d9033fca 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java @@ -9,7 +9,6 @@ import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; @@ -36,9 +35,9 @@ public static Builder builder() { } /** - * The lenient object options. The object fields are constructed with {@link ObjectFieldOptions#caseSensitive()} as - * {@code false} and {@link ObjectFieldOptions#repeatedBehavior()} as - * {@link ObjectFieldOptions.RepeatedBehavior#USE_FIRST}. + * The lenient object options. Allows missing, accepts {@link JsonValueTypes#objectOrNull()}, and allows unknown + * fields. The object fields are constructed with {@link ObjectFieldOptions#caseSensitive()} as {@code false} and + * {@link ObjectFieldOptions#repeatedBehavior()} as {@link ObjectFieldOptions.RepeatedBehavior#USE_FIRST}. * * @param fields the fields * @return the lenient object options @@ -57,7 +56,8 @@ public static ObjectOptions lenient(Map fields) { } /** - * The standard object options. + * The standard object options. Allows missing, accepts {@link JsonValueTypes#objectOrNull()}, and allows unknown + * fields. The object fields are constructed with {@link ObjectFieldOptions#of(String, ValueOptions)}. * * @param fields the fields * @return the standard object options @@ -65,13 +65,14 @@ public static ObjectOptions lenient(Map fields) { public static ObjectOptions standard(Map fields) { final Builder builder = builder(); for (Entry e : fields.entrySet()) { - builder.addFields(ObjectFieldOptions.of(e.getKey(), e.getValue())); + builder.putFields(e.getKey(), e.getValue()); } return builder.build(); } /** - * The strict object options. + * The strict object options. Disallows missing, accepts {@link JsonValueTypes#object()}, and disallows unknown + * fields. The object fields are constructed with {@link ObjectFieldOptions#of(String, ValueOptions)}. * * @param fields the fields * @return the strict object options @@ -79,9 +80,9 @@ public static ObjectOptions standard(Map fields) { public static ObjectOptions strict(Map fields) { final Builder builder = builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.OBJECT); + .allowedTypes(JsonValueTypes.object()); for (Entry e : fields.entrySet()) { - builder.addFields(ObjectFieldOptions.of(e.getKey(), e.getValue())); + builder.putFields(e.getKey(), e.getValue()); } return builder.build(); } @@ -100,20 +101,20 @@ public boolean allowUnknownFields() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#OBJECT_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#objectOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.OBJECT_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.objectOrNull(); } /** - * The universe, is {@link JsonValueTypes#OBJECT_OR_NULL}. + * {@inheritDoc} Is {@link JsonValueTypes#objectOrNull()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.OBJECT_OR_NULL; + public final Set universe() { + return JsonValueTypes.objectOrNull(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java b/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java index 968e31b0f16..3809eb02bf9 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java @@ -7,7 +7,7 @@ import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON value as an {@code short}. @@ -21,18 +21,18 @@ public static Builder builder() { } /** - * The lenient short options. + * The lenient short options. Allows missing and accepts {@link JsonValueTypes#intLike()}. * * @return the lenient short options */ public static ShortOptions lenient() { return builder() - .allowedTypes(JsonValueTypes.INT_LIKE) + .allowedTypes(JsonValueTypes.intLike()) .build(); } /** - * The standard short options. + * The standard short options. Allows missing and accepts {@link JsonValueTypes#intOrNull()}. * * @return the standard short options */ @@ -41,32 +41,32 @@ public static ShortOptions standard() { } /** - * The strict short options. + * The strict short options. Disallows missing and accepts {@link JsonValueTypes#int_()}. * * @return the strict short options */ public static ShortOptions strict() { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.INT) + .allowedTypes(JsonValueTypes.int_()) .build(); } /** - * {@inheritDoc} By default is {@link JsonValueTypes#INT_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#intOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.INT_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.intOrNull(); } /** - * The universe, is {@link JsonValueTypes#NUMBER_LIKE}. + * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.NUMBER_LIKE; + public final Set universe() { + return JsonValueTypes.numberLike(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/SkipOptions.java b/extensions/json/src/main/java/io/deephaven/json/SkipOptions.java index bdf57cff08c..9cc2f84e401 100644 --- a/extensions/json/src/main/java/io/deephaven/json/SkipOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/SkipOptions.java @@ -7,7 +7,7 @@ import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON value by skipping it. @@ -20,17 +20,22 @@ public static Builder builder() { return ImmutableSkipOptions.builder(); } + /** + * The lenient skip options. Allows missing and accepts {@link JsonValueTypes#all()}. + * + * @return the lenient skip options + */ public static SkipOptions lenient() { return builder().build(); } /** - * The allowed types. By default is {@link JsonValueTypes#ALL}. + * {@inheritDoc} By default is {@link JsonValueTypes#all()}. */ @Override @Default - public EnumSet allowedTypes() { - return JsonValueTypes.ALL; + public Set allowedTypes() { + return JsonValueTypes.all(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/StringOptions.java b/extensions/json/src/main/java/io/deephaven/json/StringOptions.java index c949d70fc93..607ed549baf 100644 --- a/extensions/json/src/main/java/io/deephaven/json/StringOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/StringOptions.java @@ -7,7 +7,7 @@ import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; +import java.util.Set; /** * Processes a JSON value as a {@link String}. @@ -20,38 +20,53 @@ public static Builder builder() { return ImmutableStringOptions.builder(); } + /** + * The lenient {@link String} options. Allows missing and accepts {@link JsonValueTypes#stringLike()}. + * + * @return the lenient String options + */ public static StringOptions lenient() { return builder() - .allowedTypes(JsonValueTypes.STRING_LIKE) + .allowedTypes(JsonValueTypes.stringLike()) .build(); } + /** + * The standard {@link String} options. Allows missing and accepts {@link JsonValueTypes#stringOrNull()}. + * + * @return the standard String options + */ public static StringOptions standard() { return builder().build(); } + /** + * The strict {@link String} options. Disallows missing and accepts {@link JsonValueTypes#string()}. + * + * @return the strict String options + */ public static StringOptions strict() { return builder() .allowMissing(false) - .allowedTypes(JsonValueTypes.STRING) + .allowedTypes(JsonValueTypes.string()) .build(); } /** - * {@inheritDoc} By default is {@link JsonValueTypes#STRING_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#stringOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.STRING_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.stringOrNull(); } /** - * The universe, is {@link JsonValueTypes#STRING_LIKE}. + * {@inheritDoc} Is {@link JsonValueTypes#stringLike()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.STRING_LIKE; + public final Set universe() { + return JsonValueTypes.stringLike(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/TupleOptions.java b/extensions/json/src/main/java/io/deephaven/json/TupleOptions.java index 4f0df6b0787..c6ebd1d444f 100644 --- a/extensions/json/src/main/java/io/deephaven/json/TupleOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/TupleOptions.java @@ -8,12 +8,16 @@ import org.immutables.value.Value.Immutable; import java.util.Arrays; -import java.util.EnumSet; import java.util.Iterator; import java.util.Map; +import java.util.Set; /** - * Processes a JSON array as a tuple. + * A "tuple", where an {@link JsonValueTypes#ARRAY} is a known size and each element has a defined type. + * + *

+ * For example, the JSON value {@code ["foo", 42, 5.72]} might be modelled as + * {@code TupleOptions.of(StringOptions.standard(), IntOptions.standard(), DoubleOptions.standard())}. */ @Immutable @BuildableStyle @@ -54,20 +58,20 @@ public static TupleOptions of(Iterable values) { public abstract Map namedValues(); /** - * {@inheritDoc} By default is {@link JsonValueTypes#ARRAY_OR_NULL}. + * {@inheritDoc} By default is {@link JsonValueTypes#arrayOrNull()}. */ - @Default @Override - public EnumSet allowedTypes() { - return JsonValueTypes.ARRAY_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.arrayOrNull(); } /** - * The universe, is {@link JsonValueTypes#ARRAY_OR_NULL}. + * {@inheritDoc} Is {@link JsonValueTypes#arrayOrNull()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.ARRAY_OR_NULL; + public final Set universe() { + return JsonValueTypes.arrayOrNull(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java b/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java index 48b94e75cd9..dd3a8240f66 100644 --- a/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java @@ -7,7 +7,6 @@ import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; -import java.util.EnumSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; @@ -79,7 +78,7 @@ public static TypedObjectOptions strict(String typeFieldName, Map allowedTypes() { - return JsonValueTypes.OBJECT_OR_NULL; + @Default + public Set allowedTypes() { + return JsonValueTypes.objectOrNull(); } /** - * The universe, is {@link JsonValueTypes#OBJECT_OR_NULL}. + * {@inheritDoc} Is {@link JsonValueTypes#objectOrNull()}. */ @Override - public final EnumSet universe() { - return JsonValueTypes.OBJECT_OR_NULL; + public final Set universe() { + return JsonValueTypes.objectOrNull(); } @Override diff --git a/extensions/json/src/main/java/io/deephaven/json/ValueOptions.java b/extensions/json/src/main/java/io/deephaven/json/ValueOptions.java index a31f287c2ca..44907e602ee 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ValueOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ValueOptions.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.EnumSet; import java.util.Map; +import java.util.Set; /** * The base configuration for JSON values. @@ -18,7 +19,7 @@ public abstract class ValueOptions { /** * The allowed types. */ - public abstract EnumSet allowedTypes(); + public abstract Set allowedTypes(); /** * If the processor should allow a missing JSON value. By default is {@code true}. @@ -114,12 +115,10 @@ public interface Builder> { B allowMissing(boolean allowMissing); - B allowedTypes(EnumSet allowedTypes); + B allowedTypes(Set allowedTypes); default B allowedTypes(JsonValueTypes... allowedTypes) { - final EnumSet set = EnumSet.noneOf(JsonValueTypes.class); - set.addAll(Arrays.asList(allowedTypes)); - return allowedTypes(set); + return allowedTypes(EnumSet.copyOf(Arrays.asList(allowedTypes))); } V build(); diff --git a/extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java b/extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java index 077c47526c3..495387fc1d9 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java +++ b/extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java @@ -5,7 +5,9 @@ import org.immutables.value.Value.Check; -import java.util.EnumSet; +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; /** * A base {@link ValueOptions} where the implementation has a clearly defined {@link #universe()}. @@ -13,22 +15,25 @@ public abstract class ValueOptionsRestrictedUniverseBase extends ValueOptions { /** - * The allowed types. Must be a subset of {@link #universe()}. + * {@inheritDoc} Must be a subset of {@link #universe()}. */ @Override - public abstract EnumSet allowedTypes(); + public abstract Set allowedTypes(); /** * The universe of possible allowed types. */ - public abstract EnumSet universe(); + public abstract Set universe(); @Check void checkAllowedTypes() { - for (JsonValueTypes type : allowedTypes()) { - if (!universe().contains(type)) { - throw new IllegalArgumentException("Unexpected type " + type); - } + if (!universe().containsAll(allowedTypes())) { + throw new IllegalArgumentException(String.format("Unexpected allowedTypes=%s, universe=%s", + toString(allowedTypes()), toString(universe()))); } } + + private static String toString(Collection> s) { + return s.stream().map(Enum::name).collect(Collectors.joining(",", "[", "]")); + } } diff --git a/extensions/json/src/main/java/io/deephaven/json/package-info.java b/extensions/json/src/main/java/io/deephaven/json/package-info.java new file mode 100644 index 00000000000..59b5347485c --- /dev/null +++ b/extensions/json/src/main/java/io/deephaven/json/package-info.java @@ -0,0 +1,23 @@ +/** + * The deephaven JSON package presents a declarative and composable configuration layer for describing the structure of + * a JSON value. It is meant to have sane defaults while also providing finer-grained configuration options for typical + * scenarios. The primary purpose of this package is to provide a common layer that various consumers can use to parse + * JSON values into appropriate Deephaven structures. As such (and by the very nature of JSON), these types represent a + * superset of JSON. This package can also service other use cases where the JSON structuring is necessary (for example, + * producing a JSON value from a Deephaven structure). + * + *

+ * Most of the configuration layers allow the user the choice of a "standard" option, a "strict" option, a "lenient" + * option, and a "builder" option. The "standard" option is typically configured to allow missing values and to accept + * the typically expected {@link io.deephaven.json.JsonValueTypes}, including + * {@link io.deephaven.json.JsonValueTypes#NULL}. The "strict" option is typically configured to disallow missing values + * and to accept the typically expected {@link io.deephaven.json.JsonValueTypes}, excluding + * {@link io.deephaven.json.JsonValueTypes#NULL}. The "lenient" option is typically configured to allow missing values, + * and to accept values a wide range of {@link io.deephaven.json.JsonValueTypes} by coercing atypical types into the + * requested type (for example, parsing a {@link io.deephaven.json.JsonValueTypes#STRING} into an {@code int}). The + * "builder" option allows the user fine-grained control over the behavior, and otherwise uses the "standard" options + * when the user does not override. + * + * @see io.deephaven.json.ValueOptions + */ +package io.deephaven.json; diff --git a/extensions/json/src/test/java/io/deephaven/json/JsonValueTypesTest.java b/extensions/json/src/test/java/io/deephaven/json/JsonValueTypesTest.java new file mode 100644 index 00000000000..87e7306df97 --- /dev/null +++ b/extensions/json/src/test/java/io/deephaven/json/JsonValueTypesTest.java @@ -0,0 +1,28 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import org.junit.jupiter.api.Test; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JsonValueTypesTest { + + @Test + void checkAllowedTypeInvariants() throws InvocationTargetException, IllegalAccessException { + int count = 0; + for (Method declaredMethod : JsonValueTypes.class.getDeclaredMethods()) { + if (declaredMethod.getReturnType().equals(Set.class)) { + final Set set = (Set) declaredMethod.invoke(null); + JsonValueTypes.checkAllowedTypeInvariants(set); + ++count; + } + } + assertThat(count).isEqualTo(17); + } +} diff --git a/extensions/json/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java b/extensions/json/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java new file mode 100644 index 00000000000..c1d90d8f349 --- /dev/null +++ b/extensions/json/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java @@ -0,0 +1,46 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TypedObjectOptionsTest { + + + public static final ObjectOptions TRADE = ObjectOptions.builder() + .putFields("symbol", StringOptions.strict()) + .putFields("price", DoubleOptions.strict()) + .putFields("size", LongOptions.strict()) + .build(); + + public static final ObjectOptions QUOTE = ObjectOptions.builder() + .putFields("symbol", StringOptions.strict()) + .putFields("bid", DoubleOptions.strict()) + .putFields("ask", DoubleOptions.strict()) + .build(); + + public static final TypedObjectOptions COMBINED = TypedObjectOptions.builder() + .typeFieldName("type") + .addSharedFields(ObjectFieldOptions.of("symbol", StringOptions.strict())) + .putObjects("trade", ObjectOptions.builder() + .putFields("price", DoubleOptions.strict()) + .putFields("size", LongOptions.strict()) + .build()) + .putObjects("quote", ObjectOptions.builder() + .putFields("bid", DoubleOptions.strict()) + .putFields("ask", DoubleOptions.strict()) + .build()) + .build(); + + @Test + void builderHelper() { + final TypedObjectOptions combined = + TypedObjectOptions.builder("type", Map.of("quote", QUOTE, "trade", TRADE)).build(); + assertThat(combined).isEqualTo(COMBINED); + } +} diff --git a/py/server/deephaven/json/__init__.py b/py/server/deephaven/json/__init__.py index 59040b71543..ee442da63ce 100644 --- a/py/server/deephaven/json/__init__.py +++ b/py/server/deephaven/json/__init__.py @@ -271,7 +271,7 @@ def array_( allow_missing: bool = True, allow_null: bool = True, ) -> JsonOptions: - """Creates a array of element options. For example, the JSON array + """Creates a "typed array", where all elements of the array have the same element type. For example, the JSON array .. code-block:: json [1, 42, 43, 13] From 44f4bc943fd482d09badc8bb4b597bad635acc75 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Mon, 15 Apr 2024 13:43:08 -0700 Subject: [PATCH 04/53] spotless --- .../src/main/java/io/deephaven/json/JsonValueTypes.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/extensions/json/src/main/java/io/deephaven/json/JsonValueTypes.java b/extensions/json/src/main/java/io/deephaven/json/JsonValueTypes.java index 45c25f8e81c..1527910a627 100644 --- a/extensions/json/src/main/java/io/deephaven/json/JsonValueTypes.java +++ b/extensions/json/src/main/java/io/deephaven/json/JsonValueTypes.java @@ -189,15 +189,18 @@ static void checkAllowedTypeInvariants(Set allowedTypes) { private static final Set NUMBER = Collections.unmodifiableSet(EnumSet.of(INT, DECIMAL)); - private static final Set NUMBER_OR_NULL = Collections.unmodifiableSet(EnumSet.of(INT, DECIMAL, NULL)); + private static final Set NUMBER_OR_NULL = + Collections.unmodifiableSet(EnumSet.of(INT, DECIMAL, NULL)); - private static final Set NUMBER_LIKE = Collections.unmodifiableSet(EnumSet.of(INT, DECIMAL, STRING, NULL)); + private static final Set NUMBER_LIKE = + Collections.unmodifiableSet(EnumSet.of(INT, DECIMAL, STRING, NULL)); private static final Set STRING_SET = Collections.unmodifiableSet(EnumSet.of(STRING)); private static final Set STRING_OR_NULL = Collections.unmodifiableSet(EnumSet.of(STRING, NULL)); - private static final Set STRING_LIKE = Collections.unmodifiableSet(EnumSet.of(STRING, INT, DECIMAL, BOOL, NULL)); + private static final Set STRING_LIKE = + Collections.unmodifiableSet(EnumSet.of(STRING, INT, DECIMAL, BOOL, NULL)); private static final Set BOOL_SET = Collections.unmodifiableSet(EnumSet.of(BOOL)); From c567c21a0b13b828b3c6ef683ef5241eff7e8724 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Mon, 15 Apr 2024 13:43:27 -0700 Subject: [PATCH 05/53] Boxed builders --- .../json/src/main/java/io/deephaven/json/BoolOptions.java | 4 ++-- .../json/src/main/java/io/deephaven/json/ByteOptions.java | 4 ++-- .../json/src/main/java/io/deephaven/json/CharOptions.java | 4 ++-- .../json/src/main/java/io/deephaven/json/DoubleOptions.java | 4 ++-- .../json/src/main/java/io/deephaven/json/FloatOptions.java | 4 ++-- .../json/src/main/java/io/deephaven/json/IntOptions.java | 4 ++-- .../json/src/main/java/io/deephaven/json/LongOptions.java | 4 ++-- .../json/src/main/java/io/deephaven/json/ShortOptions.java | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java b/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java index 86dc74f3d07..c53dba02d9d 100644 --- a/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java @@ -81,11 +81,11 @@ public interface Builder extends ValueOptionsSingleValueBase.Builder { Builder onMissing(float onMissing); default Builder onNull(Float onNull) { - return onNull((float) onNull); + return onNull == null ? this : onNull((float) onNull); } default Builder onMissing(Float onMissing) { - return onMissing((float) onMissing); + return onMissing == null ? this : onMissing((float) onMissing); } } } diff --git a/extensions/json/src/main/java/io/deephaven/json/IntOptions.java b/extensions/json/src/main/java/io/deephaven/json/IntOptions.java index de63c49fade..44a361ae446 100644 --- a/extensions/json/src/main/java/io/deephaven/json/IntOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/IntOptions.java @@ -81,11 +81,11 @@ public interface Builder extends ValueOptionsSingleValueBase.Builder { Builder onMissing(long onMissing); default Builder onNull(Long onNull) { - return onNull((long) onNull); + return onNull == null ? this : onNull((long) onNull); } default Builder onMissing(Long onMissing) { - return onMissing((long) onMissing); + return onMissing == null ? this : onMissing((long) onMissing); } } } diff --git a/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java b/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java index 3809eb02bf9..f46351aa54e 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java @@ -81,11 +81,11 @@ public interface Builder extends ValueOptionsSingleValueBase.Builder Date: Mon, 15 Apr 2024 13:51:54 -0700 Subject: [PATCH 06/53] Make universe implementation / documentation detail --- .../main/java/io/deephaven/json/ArrayOptions.java | 8 +++----- .../java/io/deephaven/json/BigDecimalOptions.java | 8 +++----- .../java/io/deephaven/json/BigIntegerOptions.java | 8 +++----- .../main/java/io/deephaven/json/BoolOptions.java | 8 +++----- .../main/java/io/deephaven/json/ByteOptions.java | 8 +++----- .../main/java/io/deephaven/json/CharOptions.java | 8 +++----- .../main/java/io/deephaven/json/DoubleOptions.java | 8 +++----- .../main/java/io/deephaven/json/FloatOptions.java | 8 +++----- .../io/deephaven/json/InstantNumberOptions.java | 8 +++----- .../main/java/io/deephaven/json/InstantOptions.java | 8 +++----- .../src/main/java/io/deephaven/json/IntOptions.java | 8 +++----- .../java/io/deephaven/json/LocalDateOptions.java | 8 +++----- .../main/java/io/deephaven/json/LongOptions.java | 8 +++----- .../java/io/deephaven/json/ObjectKvOptions.java | 8 +++----- .../main/java/io/deephaven/json/ObjectOptions.java | 8 +++----- .../main/java/io/deephaven/json/ShortOptions.java | 8 +++----- .../main/java/io/deephaven/json/StringOptions.java | 8 +++----- .../main/java/io/deephaven/json/TupleOptions.java | 8 +++----- .../java/io/deephaven/json/TypedObjectOptions.java | 8 +++----- .../json/ValueOptionsRestrictedUniverseBase.java | 13 ++----------- 20 files changed, 59 insertions(+), 106 deletions(-) diff --git a/extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java b/extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java index 3c9d24b2eee..501220815bb 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java @@ -54,7 +54,8 @@ public static ArrayOptions strict(ValueOptions element) { public abstract ValueOptions element(); /** - * {@inheritDoc} By default is {@link JsonValueTypes#arrayOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#arrayOrNull()}. By default is + * {@link JsonValueTypes#arrayOrNull()}. */ @Override @Default @@ -62,11 +63,8 @@ public Set allowedTypes() { return JsonValueTypes.arrayOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#arrayOrNull()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.arrayOrNull(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java b/extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java index 4ee7b6e526b..d24e960f9b6 100644 --- a/extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java @@ -52,7 +52,8 @@ public static BigDecimalOptions strict() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#numberOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#numberLike()}. By default is + * {@link JsonValueTypes#numberOrNull()}. */ @Override @Default @@ -60,11 +61,8 @@ public Set allowedTypes() { return JsonValueTypes.numberOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.numberLike(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java b/extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java index d7bc4bce5e6..b10017191c5 100644 --- a/extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java @@ -58,7 +58,8 @@ public static BigIntegerOptions strict(boolean allowDecimal) { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#intOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#numberLike()}. By default is + * {@link JsonValueTypes#intOrNull()}. */ @Override @Default @@ -66,11 +67,8 @@ public Set allowedTypes() { return JsonValueTypes.intOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.numberLike(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java b/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java index c53dba02d9d..a1eae3fc468 100644 --- a/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java @@ -53,7 +53,8 @@ public static BoolOptions strict() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#boolOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#boolLike()}. By default is + * {@link JsonValueTypes#boolOrNull()}. */ @Override @Default @@ -61,11 +62,8 @@ public Set allowedTypes() { return JsonValueTypes.boolOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#boolLike()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.boolLike(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java b/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java index 7b97eb6fe86..e6e7354d3a9 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java @@ -53,7 +53,8 @@ public static ByteOptions strict() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#intOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#numberLike()}. By default is + * {@link JsonValueTypes#intOrNull()}. */ @Override @Default @@ -61,11 +62,8 @@ public Set allowedTypes() { return JsonValueTypes.intOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.numberLike(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/CharOptions.java b/extensions/json/src/main/java/io/deephaven/json/CharOptions.java index be637d66a02..620c8a63686 100644 --- a/extensions/json/src/main/java/io/deephaven/json/CharOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/CharOptions.java @@ -43,7 +43,8 @@ public static CharOptions strict() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#stringOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#stringOrNull()}. By default is + * {@link JsonValueTypes#stringOrNull()}. */ @Override @Default @@ -51,11 +52,8 @@ public Set allowedTypes() { return JsonValueTypes.stringOrNull(); } - /** - * {@inheritDoc}. Is {@link JsonValueTypes#stringOrNull()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.stringOrNull(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java b/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java index 6374eabe035..c36179608a2 100644 --- a/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java @@ -54,7 +54,8 @@ public static DoubleOptions strict() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#numberOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#numberLike()}. By default is + * {@link JsonValueTypes#numberOrNull()}. */ @Override @Default @@ -62,11 +63,8 @@ public Set allowedTypes() { return JsonValueTypes.numberOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.numberLike(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java b/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java index 362af37ab6e..c50524e4cb8 100644 --- a/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java @@ -53,7 +53,8 @@ public static FloatOptions strict() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#numberOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#numberLike()}. By default is + * {@link JsonValueTypes#numberOrNull()}. */ @Override @Default @@ -61,11 +62,8 @@ public Set allowedTypes() { return JsonValueTypes.numberOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.numberLike(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java b/extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java index 21aa6873d22..976628c386c 100644 --- a/extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java @@ -92,7 +92,8 @@ public static Builder builder() { public abstract Format format(); /** - * {@inheritDoc} By default is {@link JsonValueTypes#intOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#numberLike()}. By default is + * {@link JsonValueTypes#intOrNull()}. */ @Override @Default @@ -100,11 +101,8 @@ public Set allowedTypes() { return JsonValueTypes.intOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.numberLike(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/InstantOptions.java b/extensions/json/src/main/java/io/deephaven/json/InstantOptions.java index fa4e4f28646..e6619f06c53 100644 --- a/extensions/json/src/main/java/io/deephaven/json/InstantOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/InstantOptions.java @@ -47,7 +47,8 @@ public static InstantOptions strict() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#stringOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#stringOrNull()}. By default is + * {@link JsonValueTypes#stringOrNull()}. */ @Override @Default @@ -55,11 +56,8 @@ public Set allowedTypes() { return JsonValueTypes.stringOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#stringOrNull()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.stringOrNull(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/IntOptions.java b/extensions/json/src/main/java/io/deephaven/json/IntOptions.java index 44a361ae446..7edd3aca6dd 100644 --- a/extensions/json/src/main/java/io/deephaven/json/IntOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/IntOptions.java @@ -53,7 +53,8 @@ public static IntOptions strict() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#intOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#numberLike()}. By default is + * {@link JsonValueTypes#intOrNull()}. */ @Override @Default @@ -61,11 +62,8 @@ public Set allowedTypes() { return JsonValueTypes.intOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.numberLike(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java b/extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java index 1a6979b7088..e83daafdbe4 100644 --- a/extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java @@ -34,7 +34,8 @@ public static LocalDateOptions strict() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#stringOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#stringOrNull()}. By default is + * {@link JsonValueTypes#stringOrNull()}. */ @Override @Default @@ -42,11 +43,8 @@ public Set allowedTypes() { return JsonValueTypes.stringOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#stringOrNull()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.stringOrNull(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/LongOptions.java b/extensions/json/src/main/java/io/deephaven/json/LongOptions.java index 947f3286250..da865dc681e 100644 --- a/extensions/json/src/main/java/io/deephaven/json/LongOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/LongOptions.java @@ -53,7 +53,8 @@ public static LongOptions strict() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#intOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#numberLike()}. By default is + * {@link JsonValueTypes#intOrNull()}. */ @Override @Default @@ -61,11 +62,8 @@ public Set allowedTypes() { return JsonValueTypes.intOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.numberLike(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java b/extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java index 7efc3693d1f..517885210d4 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java @@ -59,7 +59,8 @@ public ValueOptions key() { public abstract ValueOptions value(); /** - * {@inheritDoc} By default is {@link JsonValueTypes#objectOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#objectOrNull()}. By default is + * {@link JsonValueTypes#objectOrNull()}. */ @Override @Default @@ -67,11 +68,8 @@ public Set allowedTypes() { return JsonValueTypes.objectOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#objectOrNull()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.objectOrNull(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java b/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java index cf0d9033fca..d9aee29c297 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java @@ -101,7 +101,8 @@ public boolean allowUnknownFields() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#objectOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#objectOrNull()}. By default is + * {@link JsonValueTypes#objectOrNull()}. */ @Override @Default @@ -109,11 +110,8 @@ public Set allowedTypes() { return JsonValueTypes.objectOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#objectOrNull()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.objectOrNull(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java b/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java index f46351aa54e..872305764d8 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java @@ -53,7 +53,8 @@ public static ShortOptions strict() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#intOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#numberLike()}. By default is + * {@link JsonValueTypes#intOrNull()}. */ @Override @Default @@ -61,11 +62,8 @@ public Set allowedTypes() { return JsonValueTypes.intOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#numberLike()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.numberLike(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/StringOptions.java b/extensions/json/src/main/java/io/deephaven/json/StringOptions.java index 607ed549baf..ce6b4fd135a 100644 --- a/extensions/json/src/main/java/io/deephaven/json/StringOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/StringOptions.java @@ -53,7 +53,8 @@ public static StringOptions strict() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#stringOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#stringLike()}. By default is + * {@link JsonValueTypes#stringOrNull()}. */ @Override @Default @@ -61,11 +62,8 @@ public Set allowedTypes() { return JsonValueTypes.stringOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#stringLike()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.stringLike(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/TupleOptions.java b/extensions/json/src/main/java/io/deephaven/json/TupleOptions.java index c6ebd1d444f..53c02cd7866 100644 --- a/extensions/json/src/main/java/io/deephaven/json/TupleOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/TupleOptions.java @@ -58,7 +58,8 @@ public static TupleOptions of(Iterable values) { public abstract Map namedValues(); /** - * {@inheritDoc} By default is {@link JsonValueTypes#arrayOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#arrayOrNull()}. By default is + * {@link JsonValueTypes#arrayOrNull()}. */ @Override @Default @@ -66,11 +67,8 @@ public Set allowedTypes() { return JsonValueTypes.arrayOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#arrayOrNull()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.arrayOrNull(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java b/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java index dd3a8240f66..202b388570c 100644 --- a/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java @@ -98,7 +98,8 @@ public boolean allowUnknownTypes() { } /** - * {@inheritDoc} By default is {@link JsonValueTypes#objectOrNull()}. + * {@inheritDoc} Must be a subset of {@link JsonValueTypes#objectOrNull()}. By default is + * {@link JsonValueTypes#objectOrNull()}. */ @Override @Default @@ -106,11 +107,8 @@ public Set allowedTypes() { return JsonValueTypes.objectOrNull(); } - /** - * {@inheritDoc} Is {@link JsonValueTypes#objectOrNull()}. - */ @Override - public final Set universe() { + final Set universe() { return JsonValueTypes.objectOrNull(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java b/extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java index 495387fc1d9..1af2a70f9ec 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java +++ b/extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java @@ -10,20 +10,11 @@ import java.util.stream.Collectors; /** - * A base {@link ValueOptions} where the implementation has a clearly defined {@link #universe()}. + * A base {@link ValueOptions} where the implementation has a clearly defined universe. */ public abstract class ValueOptionsRestrictedUniverseBase extends ValueOptions { - /** - * {@inheritDoc} Must be a subset of {@link #universe()}. - */ - @Override - public abstract Set allowedTypes(); - - /** - * The universe of possible allowed types. - */ - public abstract Set universe(); + abstract Set universe(); @Check void checkAllowedTypes() { From 2358a2439b2bf748cb8b89c26ec6a31b6916c253 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Tue, 16 Apr 2024 07:22:43 -0700 Subject: [PATCH 07/53] f --- extensions/json-jackson/build.gradle | 2 ++ .../io/deephaven/json/jackson/IntMixin.java | 4 +++ .../deephaven/json/jackson/JacksonSource.java | 4 ++- .../deephaven/json/jackson/ObjectMixin.java | 4 +++ extensions/json/build.gradle | 2 -- .../java/io/deephaven/json/BoolOptions.java | 10 +------- .../java/io/deephaven/json/ByteOptions.java | 10 +------- .../java/io/deephaven/json/CharOptions.java | 10 +------- .../java/io/deephaven/json/DoubleOptions.java | 10 +------- .../java/io/deephaven/json/FloatOptions.java | 10 +------- .../java/io/deephaven/json/IntOptions.java | 10 +------- .../java/io/deephaven/json/LongOptions.java | 10 +------- .../java/io/deephaven/json/ObjectOptions.java | 6 ++++- .../java/io/deephaven/json/ShortOptions.java | 10 +------- .../java/io/deephaven/json/StringOptions.java | 5 +++- .../io/deephaven/json/TypedObjectOptions.java | 25 ++++++++++++++++++- .../json/ValueOptionsSingleValueBase.java | 15 +++++++++++ 17 files changed, 69 insertions(+), 78 deletions(-) diff --git a/extensions/json-jackson/build.gradle b/extensions/json-jackson/build.gradle index 0cb2595482f..bd18f5a88b4 100644 --- a/extensions/json-jackson/build.gradle +++ b/extensions/json-jackson/build.gradle @@ -4,6 +4,8 @@ plugins { } dependencies { + api project(':engine-processor') + Classpaths.inheritJacksonPlatform(project, 'api') Classpaths.inheritJacksonPlatform(project, 'testImplementation') diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java index 36893e14a9a..ef8380fc86d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.base.ArrayUtil; import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.sized.SizedIntChunk; import io.deephaven.json.IntOptions; import io.deephaven.json.jackson.IntValueProcessor.ToInt; import io.deephaven.qst.type.Type; @@ -84,6 +85,9 @@ public IntArrayContext newContext() { } final class IntArrayContext extends RepeaterContextBase { + + private final SizedIntChunk chunk = new SizedIntChunk<>(); + private int[] arr = EMPTY_INT_ARRAY; private int len = 0; diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java index 69633da6d47..c26c5454ca7 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java @@ -55,7 +55,7 @@ public static JsonParser of(JsonFactory factory, byte[] array, int pos, int len) public static JsonParser of(JsonFactory factory, ByteBuffer buffer) throws IOException { // TODO: suggest jackson build this in if (buffer.hasArray()) { - return of(factory, buffer.array(), buffer.position(), buffer.remaining()); + return of(factory, buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); } return of(factory, ByteBufferInputStream.of(buffer)); } @@ -70,6 +70,8 @@ public static JsonParser of(JsonFactory factory, CharBuffer buffer) throws IOExc return of(factory, buffer.array(), buffer.position(), buffer.remaining()); } // We could build CharBufferReader. Surprised it's not build into JDK. + + throw new RuntimeException("Only supports CharBuffer when backed by array"); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java index 6679ff84eac..dfe2cfe7b4d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -395,6 +395,10 @@ public void process(String fieldName, JsonParser parser) throws IOException { } void processMissing(JsonParser parser) throws IOException { + if (visited.size() == fields.size()) { + // All fields visited, none missing + return; + } for (Entry e : contexts.entrySet()) { if (!visited.contains(e.getKey())) { e.getValue().processElementMissing(parser, ix); diff --git a/extensions/json/build.gradle b/extensions/json/build.gradle index fb8e227a7d0..2eb199e160e 100644 --- a/extensions/json/build.gradle +++ b/extensions/json/build.gradle @@ -4,8 +4,6 @@ plugins { } dependencies { - api project(':engine-processor') - Classpaths.inheritImmutables(project) compileOnly 'com.google.code.findbugs:jsr305:3.0.2' diff --git a/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java b/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java index a1eae3fc468..e9367530ee7 100644 --- a/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java @@ -72,18 +72,10 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptionsSingleValueBase.Builder { + public interface Builder extends BuilderSpecial { Builder onNull(boolean onNull); Builder onMissing(boolean onMissing); - - default Builder onNull(Boolean onNull) { - return onNull == null ? this : onNull((boolean) onNull); - } - - default Builder onMissing(Boolean onMissing) { - return onMissing == null ? this : onMissing((boolean) onMissing); - } } } diff --git a/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java b/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java index e6e7354d3a9..90d081cb28a 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java @@ -72,18 +72,10 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptionsSingleValueBase.Builder { + public interface Builder extends BuilderSpecial { Builder onNull(byte onNull); Builder onMissing(byte onMissing); - - default Builder onNull(Byte onNull) { - return onNull == null ? this : onNull((byte) onNull); - } - - default Builder onMissing(Byte onMissing) { - return onMissing == null ? this : onMissing((byte) onMissing); - } } } diff --git a/extensions/json/src/main/java/io/deephaven/json/CharOptions.java b/extensions/json/src/main/java/io/deephaven/json/CharOptions.java index 620c8a63686..8f95a9f1c92 100644 --- a/extensions/json/src/main/java/io/deephaven/json/CharOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/CharOptions.java @@ -62,18 +62,10 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptionsSingleValueBase.Builder { + public interface Builder extends BuilderSpecial { Builder onNull(char onNull); Builder onMissing(char onMissing); - - default Builder onNull(Character onNull) { - return onNull == null ? this : onNull((char) onNull); - } - - default Builder onMissing(Character onMissing) { - return onMissing == null ? this : onMissing((char) onMissing); - } } } diff --git a/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java b/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java index c36179608a2..5495b6990a7 100644 --- a/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java @@ -73,18 +73,10 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptionsSingleValueBase.Builder { + public interface Builder extends BuilderSpecial { Builder onNull(double onNull); Builder onMissing(double onMissing); - - default Builder onNull(Double onNull) { - return onNull == null ? this : onNull((double) onNull); - } - - default Builder onMissing(Double onMissing) { - return onMissing == null ? this : onMissing((double) onMissing); - } } } diff --git a/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java b/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java index c50524e4cb8..efcb200bfe8 100644 --- a/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java @@ -72,17 +72,9 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptions.Builder { + public interface Builder extends BuilderSpecial { Builder onNull(float onNull); Builder onMissing(float onMissing); - - default Builder onNull(Float onNull) { - return onNull == null ? this : onNull((float) onNull); - } - - default Builder onMissing(Float onMissing) { - return onMissing == null ? this : onMissing((float) onMissing); - } } } diff --git a/extensions/json/src/main/java/io/deephaven/json/IntOptions.java b/extensions/json/src/main/java/io/deephaven/json/IntOptions.java index 7edd3aca6dd..199933d2f53 100644 --- a/extensions/json/src/main/java/io/deephaven/json/IntOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/IntOptions.java @@ -72,18 +72,10 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptionsSingleValueBase.Builder { + public interface Builder extends BuilderSpecial { Builder onNull(int onNull); Builder onMissing(int onMissing); - - default Builder onNull(Integer onNull) { - return onNull == null ? this : onNull((int) onNull); - } - - default Builder onMissing(Integer onMissing) { - return onMissing == null ? this : onMissing((int) onMissing); - } } } diff --git a/extensions/json/src/main/java/io/deephaven/json/LongOptions.java b/extensions/json/src/main/java/io/deephaven/json/LongOptions.java index da865dc681e..9e37f91d0e3 100644 --- a/extensions/json/src/main/java/io/deephaven/json/LongOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/LongOptions.java @@ -72,18 +72,10 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptions.Builder { + public interface Builder extends BuilderSpecial { Builder onNull(long onNull); Builder onMissing(long onMissing); - - default Builder onNull(Long onNull) { - return onNull == null ? this : onNull((long) onNull); - } - - default Builder onMissing(Long onMissing) { - return onMissing == null ? this : onMissing((long) onMissing); - } } } diff --git a/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java b/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java index d9aee29c297..96b0b0184f3 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java @@ -16,7 +16,11 @@ import java.util.TreeSet; /** - * Processes a JSON object as set of named fields. For example: + * Processes a JSON object as set of named fields. + * + *

+ * For example, the following might be modelled as an object with a String "name" field, an int "age" field, and a + * double "height" field: * *

  * {
diff --git a/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java b/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java
index 872305764d8..a71f1fbd8cc 100644
--- a/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java
+++ b/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java
@@ -72,18 +72,10 @@ public final  T walk(Visitor visitor) {
         return visitor.visit(this);
     }
 
-    public interface Builder extends ValueOptionsSingleValueBase.Builder {
+    public interface Builder extends BuilderSpecial {
 
         Builder onNull(short onNull);
 
         Builder onMissing(short onMissing);
-
-        default Builder onNull(Short onNull) {
-            return onNull == null ? this : onNull((short) onNull);
-        }
-
-        default Builder onMissing(Short onMissing) {
-            return onMissing == null ? this : onMissing((short) onMissing);
-        }
     }
 }
diff --git a/extensions/json/src/main/java/io/deephaven/json/StringOptions.java b/extensions/json/src/main/java/io/deephaven/json/StringOptions.java
index ce6b4fd135a..b5da14e9cda 100644
--- a/extensions/json/src/main/java/io/deephaven/json/StringOptions.java
+++ b/extensions/json/src/main/java/io/deephaven/json/StringOptions.java
@@ -72,7 +72,10 @@ public final  T walk(Visitor visitor) {
         return visitor.visit(this);
     }
 
-    public interface Builder extends ValueOptionsSingleValueBase.Builder {
+    public interface Builder extends BuilderSpecial {
 
+        Builder onNull(String onNull);
+
+        Builder onMissing(String onMissing);
     }
 }
diff --git a/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java b/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java
index 202b388570c..f3090760aae 100644
--- a/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java
+++ b/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java
@@ -13,7 +13,30 @@
 import java.util.Set;
 
 /**
- * A type-discriminated object.
+ * A type-discriminated object is a JSON object whose fields depend on a specific type field.
+ *
+ * 

+ * For example, the following might be modelled as a type-discriminated object with "type" as the type field, "symbol" + * as a shared field, with a "trade" object containing a "bid" and an "ask" field, and with a "quote" object containing + * a "price" and a "size" field: + * + *

+ * {
+ *   "type": "trade",
+ *   "symbol": "FOO",
+ *   "price": 70.03,
+ *   "size": 42
+ * }
+ * 
+ * + *
+ * {
+ *   "type": "quote",
+ *   "symbol": "BAR",
+ *   "bid": 10.01,
+ *   "ask": 10.05
+ * }
+ * 
*/ @Immutable @BuildableStyle diff --git a/extensions/json/src/main/java/io/deephaven/json/ValueOptionsSingleValueBase.java b/extensions/json/src/main/java/io/deephaven/json/ValueOptionsSingleValueBase.java index a812f70345f..c85b193a91b 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ValueOptionsSingleValueBase.java +++ b/extensions/json/src/main/java/io/deephaven/json/ValueOptionsSingleValueBase.java @@ -29,7 +29,22 @@ public interface Builder, B extends extends ValueOptions.Builder { B onNull(T onNull); + B onNull(Optional onNull); + B onMissing(T onMissing); + + B onMissing(Optional onMissing); + } + + public interface BuilderSpecial, B extends BuilderSpecial> + extends ValueOptions.Builder { + + // Immutables has special handling for primitive types and some "special" types like String. + // This differs from the above Builder where the Optional generic is "? extends T". + + B onNull(Optional onNull); + + B onMissing(Optional onMissing); } @Check From 50ec4cb1cb9fba31f648dced6e9f370ee45f4c63 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Tue, 16 Apr 2024 09:36:05 -0700 Subject: [PATCH 08/53] Rename to Value --- .../bson/jackson/JacksonBsonProvider.java | 8 +- .../io/deephaven/bson/jackson/BsonTest.java | 12 +-- .../io/deephaven/json/jackson/AnyMixin.java | 6 +- .../io/deephaven/json/jackson/ArrayMixin.java | 6 +- .../json/jackson/BigDecimalMixin.java | 6 +- .../json/jackson/BigIntegerMixin.java | 6 +- .../io/deephaven/json/jackson/BoolMixin.java | 6 +- .../io/deephaven/json/jackson/ByteMixin.java | 6 +- .../io/deephaven/json/jackson/CharMixin.java | 6 +- .../deephaven/json/jackson/DoubleMixin.java | 6 +- .../io/deephaven/json/jackson/FloatMixin.java | 6 +- .../deephaven/json/jackson/InstantMixin.java | 6 +- .../json/jackson/InstantNumberMixin.java | 6 +- .../io/deephaven/json/jackson/IntMixin.java | 6 +- .../json/jackson/JacksonProvider.java | 8 +- .../json/jackson/LocalDateMixin.java | 8 +- .../io/deephaven/json/jackson/LongMixin.java | 6 +- .../java/io/deephaven/json/jackson/Mixin.java | 100 +++++++++--------- .../deephaven/json/jackson/ObjectKvMixin.java | 6 +- .../deephaven/json/jackson/ObjectMixin.java | 78 +++++++------- .../io/deephaven/json/jackson/ShortMixin.java | 6 +- .../io/deephaven/json/jackson/SkipMixin.java | 7 +- .../deephaven/json/jackson/StringMixin.java | 6 +- .../io/deephaven/json/jackson/TupleMixin.java | 14 +-- .../json/jackson/TypedObjectMixin.java | 28 ++--- .../java/io/deephaven/json/ArrayTest.java | 22 ++-- .../deephaven/json/BoolArrayOptionsTest.java | 6 +- .../io/deephaven/json/ByteOptionsTest.java | 40 +++---- .../io/deephaven/json/CharOptionsTest.java | 28 ++--- .../json/DoubleArrayOptionsTest.java | 6 +- .../io/deephaven/json/DoubleOptionsTest.java | 26 ++--- .../io/deephaven/json/FloatOptionsTest.java | 26 ++--- .../json/InstantNumberOptionsTest.java | 2 +- .../io/deephaven/json/InstantOptionsTest.java | 16 +-- .../deephaven/json/IntArrayOptionsTest.java | 2 +- .../io/deephaven/json/IntOptionsTest.java | 40 +++---- .../deephaven/json/LocalDateOptionsTest.java | 14 +-- .../deephaven/json/LongArrayOptionsTest.java | 6 +- .../io/deephaven/json/LongOptionsTest.java | 38 +++---- .../json/ObjectFieldOptionsTest.java | 32 +++--- .../deephaven/json/ObjectKvOptionsTest.java | 16 +-- .../io/deephaven/json/ObjectOptionsTest.java | 78 +++++++------- .../json/RepeatedProcessorTests.java | 22 ++-- .../io/deephaven/json/ShortOptionsTest.java | 40 +++---- .../io/deephaven/json/StringOptionsTest.java | 30 +++--- .../java/io/deephaven/json/TestHelper.java | 4 +- .../io/deephaven/json/TupleOptionsTest.java | 8 +- .../json/TypedObjectOptionsTest.java | 20 ++-- .../json/jackson/JacksonAnyOptionsTest.java | 22 ++-- .../json/{AnyOptions.java => AnyValue.java} | 6 +- .../{ArrayOptions.java => ArrayValue.java} | 14 +-- ...cimalOptions.java => BigDecimalValue.java} | 12 +-- ...tegerOptions.java => BigIntegerValue.java} | 12 +-- .../json/{BoolOptions.java => BoolValue.java} | 12 +-- .../json/{ByteOptions.java => ByteValue.java} | 12 +-- .../json/{CharOptions.java => CharValue.java} | 10 +- .../{DoubleOptions.java => DoubleValue.java} | 12 +-- .../{FloatOptions.java => FloatValue.java} | 12 +-- ...erOptions.java => InstantNumberValue.java} | 12 +-- ...{InstantOptions.java => InstantValue.java} | 10 +- .../json/{IntOptions.java => IntValue.java} | 12 +-- ...alDateOptions.java => LocalDateValue.java} | 10 +- .../json/{LongOptions.java => LongValue.java} | 12 +-- ...jectFieldOptions.java => ObjectField.java} | 14 +-- ...bjectKvOptions.java => ObjectKvValue.java} | 22 ++-- .../{ObjectOptions.java => ObjectValue.java} | 46 ++++---- .../{ShortOptions.java => ShortValue.java} | 12 +-- .../json/{SkipOptions.java => SkipValue.java} | 8 +- .../{StringOptions.java => StringValue.java} | 12 +-- .../{TupleOptions.java => TupleValue.java} | 22 ++-- ...jectOptions.java => TypedObjectValue.java} | 42 ++++---- .../json/{ValueOptions.java => Value.java} | 70 ++++++------ ....java => ValueRestrictedUniverseBase.java} | 4 +- ...lueBase.java => ValueSingleValueBase.java} | 12 +-- .../java/io/deephaven/json/package-info.java | 2 +- .../json/TypedObjectOptionsTest.java | 46 -------- .../deephaven/json/TypedObjectValueTest.java | 45 ++++++++ 77 files changed, 708 insertions(+), 710 deletions(-) rename extensions/json/src/main/java/io/deephaven/json/{AnyOptions.java => AnyValue.java} (86%) rename extensions/json/src/main/java/io/deephaven/json/{ArrayOptions.java => ArrayValue.java} (80%) rename extensions/json/src/main/java/io/deephaven/json/{BigDecimalOptions.java => BigDecimalValue.java} (81%) rename extensions/json/src/main/java/io/deephaven/json/{BigIntegerOptions.java => BigIntegerValue.java} (82%) rename extensions/json/src/main/java/io/deephaven/json/{BoolOptions.java => BoolValue.java} (86%) rename extensions/json/src/main/java/io/deephaven/json/{ByteOptions.java => ByteValue.java} (83%) rename extensions/json/src/main/java/io/deephaven/json/{CharOptions.java => CharValue.java} (86%) rename extensions/json/src/main/java/io/deephaven/json/{DoubleOptions.java => DoubleValue.java} (86%) rename extensions/json/src/main/java/io/deephaven/json/{FloatOptions.java => FloatValue.java} (86%) rename extensions/json/src/main/java/io/deephaven/json/{InstantNumberOptions.java => InstantNumberValue.java} (87%) rename extensions/json/src/main/java/io/deephaven/json/{InstantOptions.java => InstantValue.java} (88%) rename extensions/json/src/main/java/io/deephaven/json/{IntOptions.java => IntValue.java} (86%) rename extensions/json/src/main/java/io/deephaven/json/{LocalDateOptions.java => LocalDateValue.java} (83%) rename extensions/json/src/main/java/io/deephaven/json/{LongOptions.java => LongValue.java} (83%) rename extensions/json/src/main/java/io/deephaven/json/{ObjectFieldOptions.java => ObjectField.java} (91%) rename extensions/json/src/main/java/io/deephaven/json/{ObjectKvOptions.java => ObjectKvValue.java} (75%) rename extensions/json/src/main/java/io/deephaven/json/{ObjectOptions.java => ObjectValue.java} (73%) rename extensions/json/src/main/java/io/deephaven/json/{ShortOptions.java => ShortValue.java} (86%) rename extensions/json/src/main/java/io/deephaven/json/{SkipOptions.java => SkipValue.java} (79%) rename extensions/json/src/main/java/io/deephaven/json/{StringOptions.java => StringValue.java} (86%) rename extensions/json/src/main/java/io/deephaven/json/{TupleOptions.java => TupleValue.java} (74%) rename extensions/json/src/main/java/io/deephaven/json/{TypedObjectOptions.java => TypedObjectValue.java} (74%) rename extensions/json/src/main/java/io/deephaven/json/{ValueOptions.java => Value.java} (50%) rename extensions/json/src/main/java/io/deephaven/json/{ValueOptionsRestrictedUniverseBase.java => ValueRestrictedUniverseBase.java} (81%) rename extensions/json/src/main/java/io/deephaven/json/{ValueOptionsSingleValueBase.java => ValueSingleValueBase.java} (74%) delete mode 100644 extensions/json/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java create mode 100644 extensions/json/src/test/java/io/deephaven/json/TypedObjectValueTest.java diff --git a/extensions/bson-jackson/src/main/java/io/deephaven/bson/jackson/JacksonBsonProvider.java b/extensions/bson-jackson/src/main/java/io/deephaven/bson/jackson/JacksonBsonProvider.java index b25efc604bc..567d3a94bd4 100644 --- a/extensions/bson-jackson/src/main/java/io/deephaven/bson/jackson/JacksonBsonProvider.java +++ b/extensions/bson-jackson/src/main/java/io/deephaven/bson/jackson/JacksonBsonProvider.java @@ -4,7 +4,7 @@ package io.deephaven.bson.jackson; import de.undercouch.bson4jackson.BsonFactory; -import io.deephaven.json.ValueOptions; +import io.deephaven.json.Value; import io.deephaven.json.jackson.JacksonProvider; public final class JacksonBsonProvider { @@ -14,9 +14,9 @@ public final class JacksonBsonProvider { * * @param options the object options * @return the jackson BSON provider - * @see #of(ValueOptions, BsonFactory) + * @see #of(Value, BsonFactory) */ - public static JacksonProvider of(ValueOptions options) { + public static JacksonProvider of(Value options) { return of(options, JacksonBsonConfiguration.defaultFactory()); } @@ -27,7 +27,7 @@ public static JacksonProvider of(ValueOptions options) { * @param factory the jackson BSON factory * @return the jackson BSON provider */ - public static JacksonProvider of(ValueOptions options, BsonFactory factory) { + public static JacksonProvider of(Value options, BsonFactory factory) { return JacksonProvider.of(options, factory); } } diff --git a/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/BsonTest.java b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/BsonTest.java index c333fbe23a4..1ac72354c1a 100644 --- a/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/BsonTest.java +++ b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/BsonTest.java @@ -7,9 +7,9 @@ import de.undercouch.bson4jackson.BsonFactory; import io.deephaven.chunk.IntChunk; import io.deephaven.chunk.ObjectChunk; -import io.deephaven.json.IntOptions; -import io.deephaven.json.ObjectOptions; -import io.deephaven.json.StringOptions; +import io.deephaven.json.IntValue; +import io.deephaven.json.ObjectValue; +import io.deephaven.json.StringValue; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -20,9 +20,9 @@ public class BsonTest { - private static final ObjectOptions OBJECT_NAME_AGE_FIELD = ObjectOptions.builder() - .putFields("name", StringOptions.standard()) - .putFields("age", IntOptions.standard()) + private static final ObjectValue OBJECT_NAME_AGE_FIELD = ObjectValue.builder() + .putFields("name", StringValue.standard()) + .putFields("age", IntValue.standard()) .build(); @Test diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java index 797c1b4b628..5a31559c0ce 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.TreeNode; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.AnyOptions; +import io.deephaven.json.AnyValue; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; @@ -15,8 +15,8 @@ import java.util.List; import java.util.stream.Stream; -final class AnyMixin extends Mixin { - public AnyMixin(AnyOptions options, JsonFactory factory) { +final class AnyMixin extends Mixin { + public AnyMixin(AnyValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java index 349497367fd..acb0ffb681f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.attributes.Any; -import io.deephaven.json.ArrayOptions; +import io.deephaven.json.ArrayValue; import io.deephaven.processor.ObjectProcessor; import io.deephaven.qst.type.NativeArrayType; import io.deephaven.qst.type.Type; @@ -20,9 +20,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -final class ArrayMixin extends Mixin { +final class ArrayMixin extends Mixin { - public ArrayMixin(ArrayOptions options, JsonFactory factory) { + public ArrayMixin(ArrayValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java index 17a4534c80c..3f2a0c87b95 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.BigDecimalOptions; +import io.deephaven.json.BigDecimalValue; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; @@ -15,9 +15,9 @@ import java.util.List; import java.util.stream.Stream; -final class BigDecimalMixin extends Mixin implements ToObject { +final class BigDecimalMixin extends Mixin implements ToObject { - public BigDecimalMixin(BigDecimalOptions options, JsonFactory factory) { + public BigDecimalMixin(BigDecimalValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java index 9ebf1713278..1fc7af4d522 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.BigIntegerOptions; +import io.deephaven.json.BigIntegerValue; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; @@ -15,9 +15,9 @@ import java.util.List; import java.util.stream.Stream; -final class BigIntegerMixin extends Mixin implements ToObject { +final class BigIntegerMixin extends Mixin implements ToObject { - public BigIntegerMixin(BigIntegerOptions options, JsonFactory factory) { + public BigIntegerMixin(BigIntegerValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java index cedefa7d2f0..8d2ef9e9241 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.BoolOptions; +import io.deephaven.json.BoolValue; import io.deephaven.json.jackson.ByteValueProcessor.ToByte; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; @@ -16,8 +16,8 @@ import java.util.List; import java.util.stream.Stream; -final class BoolMixin extends Mixin implements ToByte { - public BoolMixin(BoolOptions options, JsonFactory factory) { +final class BoolMixin extends Mixin implements ToByte { + public BoolMixin(BoolValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java index e4f9980a613..1604c4618ef 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.base.ArrayUtil; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.ByteOptions; +import io.deephaven.json.ByteValue; import io.deephaven.json.jackson.ByteValueProcessor.ToByte; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -20,8 +20,8 @@ import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_BYTE_ARRAY; -final class ByteMixin extends Mixin implements ToByte { - public ByteMixin(ByteOptions options, JsonFactory factory) { +final class ByteMixin extends Mixin implements ToByte { + public ByteMixin(ByteValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java index 26c8d6c7d35..f37bd045141 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.base.ArrayUtil; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.CharOptions; +import io.deephaven.json.CharValue; import io.deephaven.json.jackson.CharValueProcessor.ToChar; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -20,8 +20,8 @@ import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_CHAR_ARRAY; -final class CharMixin extends Mixin implements ToChar { - public CharMixin(CharOptions options, JsonFactory factory) { +final class CharMixin extends Mixin implements ToChar { + public CharMixin(CharValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java index 2016aeb8bc0..00ee2eebdcc 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.base.ArrayUtil; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.DoubleOptions; +import io.deephaven.json.DoubleValue; import io.deephaven.json.jackson.DoubleValueProcessor.ToDouble; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -20,9 +20,9 @@ import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_DOUBLE_ARRAY; -final class DoubleMixin extends Mixin implements ToDouble { +final class DoubleMixin extends Mixin implements ToDouble { - public DoubleMixin(DoubleOptions options, JsonFactory factory) { + public DoubleMixin(DoubleValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java index 95fe494051f..b2eaaf36bec 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.base.ArrayUtil; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.FloatOptions; +import io.deephaven.json.FloatValue; import io.deephaven.json.jackson.FloatValueProcessor.ToFloat; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -20,9 +20,9 @@ import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_FLOAT_ARRAY; -final class FloatMixin extends Mixin implements ToFloat { +final class FloatMixin extends Mixin implements ToFloat { - public FloatMixin(FloatOptions options, JsonFactory factory) { + public FloatMixin(FloatValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java index 424902492fa..beb0cf0beff 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.InstantOptions; +import io.deephaven.json.InstantValue; import io.deephaven.json.jackson.LongValueProcessor.ToLong; import io.deephaven.qst.type.Type; import io.deephaven.time.DateTimeUtils; @@ -18,12 +18,12 @@ import java.util.List; import java.util.stream.Stream; -final class InstantMixin extends Mixin implements ToLong { +final class InstantMixin extends Mixin implements ToLong { private final long onNull; private final long onMissing; - public InstantMixin(InstantOptions options, JsonFactory factory) { + public InstantMixin(InstantValue options, JsonFactory factory) { super(factory, options); onNull = DateTimeUtils.epochNanos(options.onNull().orElse(null)); onMissing = DateTimeUtils.epochNanos(options.onMissing().orElse(null)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java index 8ebe1b546aa..a4edc4e9ead 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.InstantNumberOptions; +import io.deephaven.json.InstantNumberValue; import io.deephaven.qst.type.Type; import io.deephaven.time.DateTimeUtils; @@ -15,12 +15,12 @@ import java.util.List; import java.util.stream.Stream; -final class InstantNumberMixin extends Mixin { +final class InstantNumberMixin extends Mixin { private final long onNull; private final long onMissing; - public InstantNumberMixin(InstantNumberOptions options, JsonFactory factory) { + public InstantNumberMixin(InstantNumberValue options, JsonFactory factory) { super(factory, options); onNull = DateTimeUtils.epochNanos(options.onNull().orElse(null)); onMissing = DateTimeUtils.epochNanos(options.onMissing().orElse(null)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java index ef8380fc86d..d4d15aaf59f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -8,7 +8,7 @@ import io.deephaven.base.ArrayUtil; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.sized.SizedIntChunk; -import io.deephaven.json.IntOptions; +import io.deephaven.json.IntValue; import io.deephaven.json.jackson.IntValueProcessor.ToInt; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -21,9 +21,9 @@ import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_INT_ARRAY; -final class IntMixin extends Mixin implements ToInt { +final class IntMixin extends Mixin implements ToInt { - public IntMixin(IntOptions options, JsonFactory factory) { + public IntMixin(IntValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java index 8f88f900d9c..0fa604c5511 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java @@ -4,7 +4,7 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonFactory; -import io.deephaven.json.ValueOptions; +import io.deephaven.json.Value; import io.deephaven.processor.NamedObjectProcessor; import io.deephaven.processor.ObjectProcessor; import io.deephaven.qst.type.Type; @@ -28,10 +28,10 @@ public interface JacksonProvider extends NamedObjectProcessor.Provider { * * @param options the object options * @return the jackson provider - * @see #of(ValueOptions, JsonFactory) + * @see #of(Value, JsonFactory) * @see JacksonConfiguration#defaultFactoryBuilder() */ - static JacksonProvider of(ValueOptions options) { + static JacksonProvider of(Value options) { return of(options, JacksonConfiguration.defaultFactory()); } @@ -42,7 +42,7 @@ static JacksonProvider of(ValueOptions options) { * @param factory the jackson factory * @return the jackson provider */ - static JacksonProvider of(ValueOptions options, JsonFactory factory) { + static JacksonProvider of(Value options, JsonFactory factory) { return Mixin.of(options, factory); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java index bfcd0066ba4..ed54ca9dc8f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.LocalDateOptions; +import io.deephaven.json.LocalDateValue; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; @@ -16,9 +16,9 @@ import java.util.List; import java.util.stream.Stream; -final class LocalDateMixin extends Mixin implements ToObject { +final class LocalDateMixin extends Mixin implements ToObject { - public LocalDateMixin(LocalDateOptions options, JsonFactory factory) { + public LocalDateMixin(LocalDateValue options, JsonFactory factory) { super(factory, options); } @@ -51,7 +51,7 @@ public LocalDate parseValue(JsonParser parser) throws IOException { case VALUE_NULL: return parseFromNull(parser); } - throw Parsing.mismatch(parser, LocalDateOptions.class); + throw Parsing.mismatch(parser, LocalDateValue.class); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java index 56cc0fdf6f8..702b54ada67 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.LongOptions; +import io.deephaven.json.LongValue; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -14,9 +14,9 @@ import java.util.List; import java.util.stream.Stream; -final class LongMixin extends Mixin implements LongValueProcessor.ToLong { +final class LongMixin extends Mixin implements LongValueProcessor.ToLong { - public LongMixin(LongOptions options, JsonFactory config) { + public LongMixin(LongValue options, JsonFactory config) { super(config, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index 4cb2915e6d3..ee25c192ff3 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -8,30 +8,30 @@ import io.deephaven.api.util.NameValidator; import io.deephaven.chunk.ObjectChunk; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.AnyOptions; -import io.deephaven.json.ArrayOptions; -import io.deephaven.json.BigDecimalOptions; -import io.deephaven.json.BigIntegerOptions; -import io.deephaven.json.BoolOptions; -import io.deephaven.json.ByteOptions; -import io.deephaven.json.CharOptions; -import io.deephaven.json.DoubleOptions; -import io.deephaven.json.FloatOptions; -import io.deephaven.json.InstantNumberOptions; -import io.deephaven.json.InstantOptions; -import io.deephaven.json.IntOptions; +import io.deephaven.json.AnyValue; +import io.deephaven.json.ArrayValue; +import io.deephaven.json.BigDecimalValue; +import io.deephaven.json.BigIntegerValue; +import io.deephaven.json.BoolValue; +import io.deephaven.json.ByteValue; +import io.deephaven.json.CharValue; +import io.deephaven.json.DoubleValue; +import io.deephaven.json.FloatValue; +import io.deephaven.json.InstantNumberValue; +import io.deephaven.json.InstantValue; +import io.deephaven.json.IntValue; import io.deephaven.json.JsonValueTypes; -import io.deephaven.json.LocalDateOptions; -import io.deephaven.json.LongOptions; -import io.deephaven.json.ObjectFieldOptions; -import io.deephaven.json.ObjectKvOptions; -import io.deephaven.json.ObjectOptions; -import io.deephaven.json.ShortOptions; -import io.deephaven.json.SkipOptions; -import io.deephaven.json.StringOptions; -import io.deephaven.json.TupleOptions; -import io.deephaven.json.TypedObjectOptions; -import io.deephaven.json.ValueOptions; +import io.deephaven.json.LocalDateValue; +import io.deephaven.json.LongValue; +import io.deephaven.json.ObjectField; +import io.deephaven.json.ObjectKvValue; +import io.deephaven.json.ObjectValue; +import io.deephaven.json.ShortValue; +import io.deephaven.json.SkipValue; +import io.deephaven.json.StringValue; +import io.deephaven.json.TupleValue; +import io.deephaven.json.TypedObjectValue; +import io.deephaven.json.Value; import io.deephaven.processor.ObjectProcessor; import io.deephaven.qst.type.Type; @@ -50,7 +50,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -abstract class Mixin implements JacksonProvider { +abstract class Mixin implements JacksonProvider { static final Function, String> TO_COLUMN_NAME = Mixin::toColumnName; @@ -58,7 +58,7 @@ public static String toColumnName(List path) { return path.isEmpty() ? "Value" : String.join("_", path); } - static Mixin of(ValueOptions options, JsonFactory factory) { + static Mixin of(Value options, JsonFactory factory) { return options.walk(new MixinImpl(factory)); } @@ -153,7 +153,7 @@ public final ObjectProcessor byteBufferProcessor() { return new ByteBufferIn(); } - final Mixin mixin(ValueOptions options) { + final Mixin mixin(Value options) { return of(options, factory); } @@ -203,9 +203,9 @@ static List prefixWith(String prefix, List path) { return Stream.concat(Stream.of(prefix), path.stream()).collect(Collectors.toList()); } - Stream> prefixWithKeys(Collection fields) { + Stream> prefixWithKeys(Collection fields) { final List>> paths = new ArrayList<>(fields.size()); - for (ObjectFieldOptions field : fields) { + for (ObjectField field : fields) { final Stream> prefixedPaths = mixin(field.options()).paths().map(x -> prefixWith(field.name(), x)); paths.add(prefixedPaths); @@ -295,7 +295,7 @@ void processAllImpl(ObjectChunk in, List> out) } } - private static class MixinImpl implements ValueOptions.Visitor> { + private static class MixinImpl implements Value.Visitor> { private final JsonFactory factory; public MixinImpl(JsonFactory factory) { @@ -303,107 +303,107 @@ public MixinImpl(JsonFactory factory) { } @Override - public StringMixin visit(StringOptions _string) { + public StringMixin visit(StringValue _string) { return new StringMixin(_string, factory); } @Override - public Mixin visit(BoolOptions _bool) { + public Mixin visit(BoolValue _bool) { return new BoolMixin(_bool, factory); } @Override - public Mixin visit(ByteOptions _byte) { + public Mixin visit(ByteValue _byte) { return new ByteMixin(_byte, factory); } @Override - public Mixin visit(CharOptions _char) { + public Mixin visit(CharValue _char) { return new CharMixin(_char, factory); } @Override - public Mixin visit(ShortOptions _short) { + public Mixin visit(ShortValue _short) { return new ShortMixin(_short, factory); } @Override - public IntMixin visit(IntOptions _int) { + public IntMixin visit(IntValue _int) { return new IntMixin(_int, factory); } @Override - public LongMixin visit(LongOptions _long) { + public LongMixin visit(LongValue _long) { return new LongMixin(_long, factory); } @Override - public FloatMixin visit(FloatOptions _float) { + public FloatMixin visit(FloatValue _float) { return new FloatMixin(_float, factory); } @Override - public DoubleMixin visit(DoubleOptions _double) { + public DoubleMixin visit(DoubleValue _double) { return new DoubleMixin(_double, factory); } @Override - public ObjectMixin visit(ObjectOptions object) { + public ObjectMixin visit(ObjectValue object) { return new ObjectMixin(object, factory); } @Override - public Mixin visit(ObjectKvOptions objectKv) { + public Mixin visit(ObjectKvValue objectKv) { return new ObjectKvMixin(objectKv, factory); } @Override - public InstantMixin visit(InstantOptions instant) { + public InstantMixin visit(InstantValue instant) { return new InstantMixin(instant, factory); } @Override - public InstantNumberMixin visit(InstantNumberOptions instantNumber) { + public InstantNumberMixin visit(InstantNumberValue instantNumber) { return new InstantNumberMixin(instantNumber, factory); } @Override - public BigIntegerMixin visit(BigIntegerOptions bigInteger) { + public BigIntegerMixin visit(BigIntegerValue bigInteger) { return new BigIntegerMixin(bigInteger, factory); } @Override - public BigDecimalMixin visit(BigDecimalOptions bigDecimal) { + public BigDecimalMixin visit(BigDecimalValue bigDecimal) { return new BigDecimalMixin(bigDecimal, factory); } @Override - public SkipMixin visit(SkipOptions skip) { + public SkipMixin visit(SkipValue skip) { return new SkipMixin(skip, factory); } @Override - public TupleMixin visit(TupleOptions tuple) { + public TupleMixin visit(TupleValue tuple) { return new TupleMixin(tuple, factory); } @Override - public TypedObjectMixin visit(TypedObjectOptions typedObject) { + public TypedObjectMixin visit(TypedObjectValue typedObject) { return new TypedObjectMixin(typedObject, factory); } @Override - public LocalDateMixin visit(LocalDateOptions localDate) { + public LocalDateMixin visit(LocalDateValue localDate) { return new LocalDateMixin(localDate, factory); } @Override - public ArrayMixin visit(ArrayOptions array) { + public ArrayMixin visit(ArrayValue array) { return new ArrayMixin(array, factory); } @Override - public AnyMixin visit(AnyOptions any) { + public AnyMixin visit(AnyValue any) { return new AnyMixin(any, factory); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java index aa178a497a7..86e108b8e56 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.attributes.Any; -import io.deephaven.json.ObjectKvOptions; +import io.deephaven.json.ObjectKvValue; import io.deephaven.processor.ObjectProcessor; import io.deephaven.qst.type.NativeArrayType; import io.deephaven.qst.type.Type; @@ -20,9 +20,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -final class ObjectKvMixin extends Mixin { +final class ObjectKvMixin extends Mixin { - public ObjectKvMixin(ObjectKvOptions options, JsonFactory factory) { + public ObjectKvMixin(ObjectKvValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java index dfe2cfe7b4d..5a9307d172f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -7,9 +7,9 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.ObjectFieldOptions; -import io.deephaven.json.ObjectFieldOptions.RepeatedBehavior; -import io.deephaven.json.ObjectOptions; +import io.deephaven.json.ObjectField; +import io.deephaven.json.ObjectField.RepeatedBehavior; +import io.deephaven.json.ObjectValue; import io.deephaven.qst.type.Type; import java.io.IOException; @@ -25,9 +25,9 @@ import java.util.TreeMap; import java.util.stream.Stream; -final class ObjectMixin extends Mixin { +final class ObjectMixin extends Mixin { - public ObjectMixin(ObjectOptions options, JsonFactory factory) { + public ObjectMixin(ObjectValue options, JsonFactory factory) { super(factory, options); } @@ -35,7 +35,7 @@ public ObjectMixin(ObjectOptions options, JsonFactory factory) { public Stream> outputTypesImpl() { return options.fields() .stream() - .map(ObjectFieldOptions::options) + .map(ObjectField::options) .map(this::mixin) .flatMap(Mixin::outputTypesImpl); } @@ -44,7 +44,7 @@ public Stream> outputTypesImpl() { public int numColumns() { return options.fields() .stream() - .map(ObjectFieldOptions::options) + .map(ObjectField::options) .map(this::mixin) .mapToInt(Mixin::numColumns) .sum(); @@ -60,9 +60,9 @@ public ValueProcessor processor(String context, List> out) { if (out.size() != numColumns()) { throw new IllegalArgumentException(); } - final Map processors = new LinkedHashMap<>(options.fields().size()); + final Map processors = new LinkedHashMap<>(options.fields().size()); int ix = 0; - for (ObjectFieldOptions field : options.fields()) { + for (ObjectField field : options.fields()) { final Mixin opts = mixin(field.options()); final int numTypes = opts.numColumns(); final ValueProcessor fieldProcessor = @@ -81,11 +81,11 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, Lis if (out.size() != numColumns()) { throw new IllegalArgumentException(); } - final Map processors = + final Map processors = new LinkedHashMap<>(options.fields().size()); int ix = 0; - for (ObjectFieldOptions field : options.fields()) { + for (ObjectField field : options.fields()) { final Mixin opts = mixin(field.options()); final int numTypes = opts.numColumns(); final RepeaterProcessor fieldProcessor = @@ -100,24 +100,24 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, Lis } private boolean allCaseSensitive() { - return options.fields().stream().allMatch(ObjectFieldOptions::caseSensitive); + return options.fields().stream().allMatch(ObjectField::caseSensitive); } - ObjectValueFieldProcessor processorImpl(Map fields) { + ObjectValueFieldProcessor processorImpl(Map fields) { return new ObjectValueFieldProcessor(fields); } final class ObjectValueFieldProcessor implements ValueProcessor { - private final Map fields; - private final Map map; + private final Map fields; + private final Map map; - ObjectValueFieldProcessor(Map fields) { + ObjectValueFieldProcessor(Map fields) { this.fields = fields; this.map = allCaseSensitive() ? new HashMap<>() : new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - for (Entry e : fields.entrySet()) { - final ObjectFieldOptions field = e.getKey(); + for (Entry e : fields.entrySet()) { + final ObjectField field = e.getKey(); map.put(field.name(), field); for (String alias : field.aliases()) { map.put(alias, field); @@ -125,8 +125,8 @@ final class ObjectValueFieldProcessor implements ValueProcessor { } } - private ObjectFieldOptions lookupField(String fieldName) { - final ObjectFieldOptions field = map.get(fieldName); + private ObjectField lookupField(String fieldName) { + final ObjectField field = map.get(fieldName); if (field == null) { return null; } @@ -140,7 +140,7 @@ private ObjectFieldOptions lookupField(String fieldName) { return null; } - private ValueProcessor processor(ObjectFieldOptions options) { + private ValueProcessor processor(ObjectField options) { return Objects.requireNonNull(fields.get(options)); } @@ -206,11 +206,11 @@ private class State implements FieldProcessor { // Note: we could try to build a stricter implementation that doesn't use Set; if all of the fields disallow // missing and the user knows that the data doesn't have any repeated fields, we could use a simple // counter to ensure all field processors were invoked. - private final Set visited = new HashSet<>(fields.size()); + private final Set visited = new HashSet<>(fields.size()); @Override public void process(String fieldName, JsonParser parser) throws IOException { - final ObjectFieldOptions field = lookupField(fieldName); + final ObjectField field = lookupField(fieldName); if (field == null) { if (!options.allowUnknownFields()) { throw new IOException( @@ -234,7 +234,7 @@ void processMissing(JsonParser parser) throws IOException { // All fields visited, none missing return; } - for (Entry e : fields.entrySet()) { + for (Entry e : fields.entrySet()) { if (!visited.contains(e.getKey())) { e.getValue().processMissing(parser); } @@ -244,9 +244,9 @@ void processMissing(JsonParser parser) throws IOException { } final class ObjectValueRepeaterProcessor implements RepeaterProcessor { - private final Map fields; + private final Map fields; - public ObjectValueRepeaterProcessor(Map fields) { + public ObjectValueRepeaterProcessor(Map fields) { this.fields = Objects.requireNonNull(fields); } @@ -256,8 +256,8 @@ private Collection processors() { @Override public Context start(JsonParser parser) throws IOException { - final Map contexts = new LinkedHashMap<>(fields.size()); - for (Entry e : fields.entrySet()) { + final Map contexts = new LinkedHashMap<>(fields.size()); + for (Entry e : fields.entrySet()) { contexts.put(e.getKey(), e.getValue().start(parser)); } return new ObjectArrayContext(contexts); @@ -278,16 +278,16 @@ public void processMissingRepeater(JsonParser parser) throws IOException { } final class ObjectArrayContext implements Context { - private final Map contexts; - private final Map map; + private final Map contexts; + private final Map map; - public ObjectArrayContext(Map contexts) { + public ObjectArrayContext(Map contexts) { this.contexts = Objects.requireNonNull(contexts); this.map = allCaseSensitive() ? new HashMap<>() : new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - for (Entry e : fields.entrySet()) { - final ObjectFieldOptions field = e.getKey(); + for (Entry e : fields.entrySet()) { + final ObjectField field = e.getKey(); map.put(field.name(), field); for (String alias : field.aliases()) { map.put(alias, field); @@ -295,8 +295,8 @@ public ObjectArrayContext(Map contexts) { } } - private ObjectFieldOptions lookupField(String fieldName) { - final ObjectFieldOptions field = map.get(fieldName); + private ObjectField lookupField(String fieldName) { + final ObjectField field = map.get(fieldName); if (field == null) { return null; } @@ -310,7 +310,7 @@ private ObjectFieldOptions lookupField(String fieldName) { return null; } - private Context context(ObjectFieldOptions o) { + private Context context(ObjectField o) { return Objects.requireNonNull(contexts.get(o)); } @@ -366,7 +366,7 @@ private class State implements FieldProcessor { // Note: we could try to build a stricter implementation that doesn't use Set; if the user can guarantee // that none of the fields will be missing and there won't be any repeated fields, we could use a simple // counter to ensure all field processors were invoked. - private final Set visited = new HashSet<>(contexts.size()); + private final Set visited = new HashSet<>(contexts.size()); private final int ix; public State(int ix) { @@ -375,7 +375,7 @@ public State(int ix) { @Override public void process(String fieldName, JsonParser parser) throws IOException { - final ObjectFieldOptions field = lookupField(fieldName); + final ObjectField field = lookupField(fieldName); if (field == null) { if (!options.allowUnknownFields()) { throw new IOException( @@ -399,7 +399,7 @@ void processMissing(JsonParser parser) throws IOException { // All fields visited, none missing return; } - for (Entry e : contexts.entrySet()) { + for (Entry e : contexts.entrySet()) { if (!visited.contains(e.getKey())) { e.getValue().processElementMissing(parser, ix); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java index 26a5a984c77..221a42ee0a2 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.base.ArrayUtil; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.ShortOptions; +import io.deephaven.json.ShortValue; import io.deephaven.json.jackson.ShortValueProcessor.ToShort; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -20,8 +20,8 @@ import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_SHORT_ARRAY; -final class ShortMixin extends Mixin implements ToShort { - public ShortMixin(ShortOptions options, JsonFactory factory) { +final class ShortMixin extends Mixin implements ToShort { + public ShortMixin(ShortValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java index 663646f4dd4..421e1a2eec1 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java @@ -5,18 +5,17 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.SkipOptions; +import io.deephaven.json.SkipValue; import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; import java.util.stream.Stream; -final class SkipMixin extends Mixin implements ValueProcessor, RepeaterProcessor.Context { +final class SkipMixin extends Mixin implements ValueProcessor, RepeaterProcessor.Context { - public SkipMixin(SkipOptions options, JsonFactory factory) { + public SkipMixin(SkipValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java index d5a40386a74..ba1daa3e802 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.StringOptions; +import io.deephaven.json.StringValue; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; @@ -14,9 +14,9 @@ import java.util.List; import java.util.stream.Stream; -final class StringMixin extends Mixin implements ToObject { +final class StringMixin extends Mixin implements ToObject { - public StringMixin(StringOptions options, JsonFactory factory) { + public StringMixin(StringValue options, JsonFactory factory) { super(factory, options); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java index 33a0cd4c369..43b84ba2c0e 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java @@ -7,8 +7,8 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.TupleOptions; -import io.deephaven.json.ValueOptions; +import io.deephaven.json.TupleValue; +import io.deephaven.json.Value; import io.deephaven.qst.type.Type; import java.io.IOException; @@ -21,9 +21,9 @@ import static io.deephaven.json.jackson.Parsing.assertCurrentToken; -final class TupleMixin extends Mixin { +final class TupleMixin extends Mixin { - public TupleMixin(TupleOptions options, JsonFactory factory) { + public TupleMixin(TupleValue options, JsonFactory factory) { super(factory, options); } @@ -38,7 +38,7 @@ public Stream> paths() { return mixin(options.namedValues().get(0)).paths(); } final List>> prefixed = new ArrayList<>(); - for (Entry e : options.namedValues().entrySet()) { + for (Entry e : options.namedValues().entrySet()) { prefixed.add(mixin(e.getValue()).paths().map(x -> prefixWith(e.getKey(), x))); } return prefixed.stream().flatMap(Function.identity()); @@ -56,7 +56,7 @@ public ValueProcessor processor(String context, List> out) { } final List processors = new ArrayList<>(options.namedValues().size()); int ix = 0; - for (Entry e : options.namedValues().entrySet()) { + for (Entry e : options.namedValues().entrySet()) { final Mixin mixin = mixin(e.getValue()); final int numTypes = mixin.numColumns(); final ValueProcessor processor = @@ -77,7 +77,7 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, Lis } final List processors = new ArrayList<>(options.namedValues().size()); int ix = 0; - for (Entry e : options.namedValues().entrySet()) { + for (Entry e : options.namedValues().entrySet()) { final Mixin mixin = mixin(e.getValue()); final int numTypes = mixin.numColumns(); final RepeaterProcessor processor = diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java index 42cb8f70bbc..23742ffb879 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java @@ -8,9 +8,9 @@ import com.fasterxml.jackson.core.JsonToken; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableObjectChunk; -import io.deephaven.json.ObjectFieldOptions; -import io.deephaven.json.ObjectOptions; -import io.deephaven.json.TypedObjectOptions; +import io.deephaven.json.ObjectField; +import io.deephaven.json.ObjectValue; +import io.deephaven.json.TypedObjectValue; import io.deephaven.qst.type.Type; import java.io.IOException; @@ -25,8 +25,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -final class TypedObjectMixin extends Mixin { - public TypedObjectMixin(TypedObjectOptions options, JsonFactory factory) { +final class TypedObjectMixin extends Mixin { + public TypedObjectMixin(TypedObjectValue options, JsonFactory factory) { super(factory, options); } @@ -35,7 +35,7 @@ public int numColumns() { return 1 + options.sharedFields() .stream() - .map(ObjectFieldOptions::options) + .map(ObjectField::options) .map(this::mixin) .mapToInt(Mixin::numColumns) .sum() @@ -53,7 +53,7 @@ public Stream> paths() { Stream.of(List.of(options.typeFieldName())), Stream.concat( prefixWithKeys(options.sharedFields()), - prefixWithKeys(options.objects().values().stream().map(ObjectOptions::fields) + prefixWithKeys(options.objects().values().stream().map(ObjectValue::fields) .flatMap(Collection::stream).collect(Collectors.toList())))); } @@ -62,7 +62,7 @@ public Stream> outputTypesImpl() { return Stream.concat( Stream.of(Type.stringType()), Stream.concat( - options.sharedFields().stream().map(ObjectFieldOptions::options).map(this::mixin) + options.sharedFields().stream().map(ObjectField::options).map(this::mixin) .flatMap(Mixin::outputTypesImpl), options.objects().values().stream().map(this::mixin).flatMap(Mixin::outputTypesImpl))); } @@ -73,13 +73,13 @@ public ValueProcessor processor(String context, List> out) { final List> sharedFields = out.subList(1, 1 + options.sharedFields().size()); final Map processors = new LinkedHashMap<>(options.objects().size()); int outIx = 1 + sharedFields.size(); - for (Entry e : options.objects().entrySet()) { + for (Entry e : options.objects().entrySet()) { final String type = e.getKey(); - final ObjectOptions specificOpts = e.getValue(); + final ObjectValue specificOpts = e.getValue(); final int numSpecificFields = mixin(specificOpts).numColumns(); final List> specificChunks = out.subList(outIx, outIx + numSpecificFields); final List> allChunks = concat(sharedFields, specificChunks); - final ObjectOptions combinedObject = combinedObject(specificOpts); + final ObjectValue combinedObject = combinedObject(specificOpts); final ValueProcessor processor = mixin(combinedObject).processor(context + "[" + type + "]", allChunks); processors.put(type, new Processor(processor, specificChunks)); outIx += numSpecificFields; @@ -108,12 +108,12 @@ private static List concat(List x, List y) { return out; } - private ObjectOptions combinedObject(ObjectOptions objectOpts) { - final Set sharedFields = options.sharedFields(); + private ObjectValue combinedObject(ObjectValue objectOpts) { + final Set sharedFields = options.sharedFields(); if (sharedFields.isEmpty()) { return objectOpts; } - return ObjectOptions.builder() + return ObjectValue.builder() .allowUnknownFields(objectOpts.allowUnknownFields()) .allowMissing(objectOpts.allowMissing()) .allowedTypes(objectOpts.allowedTypes()) diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ArrayTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ArrayTest.java index 275ee65f49d..8ec596966be 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ArrayTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ArrayTest.java @@ -18,7 +18,7 @@ public class ArrayTest { @Test void primitive() throws IOException { // [1.1, null, 3.3] - parse(DoubleOptions.standard().array(), + parse(DoubleValue.standard().array(), "[1.1, null, 3.3]", ObjectChunk.chunkWrap(new Object[] {new double[] {1.1, QueryConstants.NULL_DOUBLE, 3.3}})); } @@ -26,7 +26,7 @@ void primitive() throws IOException { @Test void bool() throws IOException { // [true, false, null] - parse(BoolOptions.standard().array(), + parse(BoolValue.standard().array(), "[true, false, null]", ObjectChunk.chunkWrap(new Object[] {new Boolean[] {true, false, null}})); } @@ -34,7 +34,7 @@ void bool() throws IOException { @Test void tuple() throws IOException { // [[1, 1.1], null, [3, 3.3]] - parse(TupleOptions.of(IntOptions.standard(), DoubleOptions.standard()).array(), + parse(TupleValue.of(IntValue.standard(), DoubleValue.standard()).array(), "[[1, 1.1], null, [3, 3.3]]", ObjectChunk.chunkWrap(new Object[] {new int[] {1, QueryConstants.NULL_INT, 3}}), ObjectChunk.chunkWrap(new Object[] {new double[] {1.1, QueryConstants.NULL_DOUBLE, 3.3}})); @@ -43,9 +43,9 @@ void tuple() throws IOException { @Test void object() throws IOException { // [{"int": 1, "double": 1.1}, null, {}, {"int": 4, "double": 4.4}] - parse(ObjectOptions.builder() - .putFields("int", IntOptions.standard()) - .putFields("double", DoubleOptions.standard()) + parse(ObjectValue.builder() + .putFields("int", IntValue.standard()) + .putFields("double", DoubleValue.standard()) .build() .array(), "[{\"int\": 1, \"double\": 1.1}, null, {}, {\"int\": 4, \"double\": 4.4}]", @@ -60,10 +60,10 @@ void object() throws IOException { @Test void typedObject() throws IOException { // [ {"type": "int", "value": 42}, {"type": "string", "value": "foo"} ] - parse(TypedObjectOptions.builder() + parse(TypedObjectValue.builder() .typeFieldName("type") - .putObjects("int", ObjectOptions.standard(Map.of("value", IntOptions.standard()))) - .putObjects("string", ObjectOptions.standard(Map.of("value", StringOptions.standard()))) + .putObjects("int", ObjectValue.standard(Map.of("value", IntValue.standard()))) + .putObjects("string", ObjectValue.standard(Map.of("value", StringValue.standard()))) .build() .array(), "[ {\"type\": \"int\", \"value\": 42}, {\"type\": \"string\", \"value\": \"foo\"} ]", @@ -76,7 +76,7 @@ void typedObject() throws IOException { @Test void instant() throws IOException { // ["2009-02-13T23:31:30.123456789Z", null] - parse(InstantOptions.standard().array(), + parse(InstantValue.standard().array(), "[\"2009-02-13T23:31:30.123456789Z\", null]", ObjectChunk.chunkWrap(new Object[] {new long[] {1234567890123456789L, QueryConstants.NULL_LONG}})); } @@ -84,7 +84,7 @@ void instant() throws IOException { @Test void tupleSkip() throws IOException { // [[1, 1], [2, 2.2], [3, "foo"], [4, true], [5, false], [6, {}], [7, []], [8, null], null] - parse(TupleOptions.of(IntOptions.standard(), SkipOptions.lenient()).array(), + parse(TupleValue.of(IntValue.standard(), SkipValue.lenient()).array(), "[[1, 1], [2, 2.2], [3, \"foo\"], [4, true], [5, false], [6, {}], [7, []], [8, null], null]", ObjectChunk.chunkWrap(new Object[] {new int[] {1, 2, 3, 4, 5, 6, 7, 8, QueryConstants.NULL_INT}})); } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayOptionsTest.java index 1bdb4533fd5..67a413d8718 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayOptionsTest.java @@ -14,17 +14,17 @@ public class BoolArrayOptionsTest { @Test void standard() throws IOException { - parse(BoolOptions.standard().array(), "[true, null, false]", + parse(BoolValue.standard().array(), "[true, null, false]", ObjectChunk.chunkWrap(new Object[] {new Boolean[] {true, null, false}})); } @Test void standardMissing() throws IOException { - parse(BoolOptions.standard().array(), "", ObjectChunk.chunkWrap(new Object[] {null})); + parse(BoolValue.standard().array(), "", ObjectChunk.chunkWrap(new Object[] {null})); } @Test void standardNull() throws IOException { - parse(BoolOptions.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] {null})); + parse(BoolValue.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] {null})); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java index 3f3dd6f06f5..2a6c757a52d 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java @@ -19,38 +19,38 @@ public class ByteOptionsTest { @Test void standard() throws IOException { - parse(ByteOptions.standard(), "42", ByteChunk.chunkWrap(new byte[] {42})); + parse(ByteValue.standard(), "42", ByteChunk.chunkWrap(new byte[] {42})); } @Test void standardMissing() throws IOException { - parse(ByteOptions.standard(), "", ByteChunk.chunkWrap(new byte[] {QueryConstants.NULL_BYTE})); + parse(ByteValue.standard(), "", ByteChunk.chunkWrap(new byte[] {QueryConstants.NULL_BYTE})); } @Test void standardNull() throws IOException { - parse(ByteOptions.standard(), "null", ByteChunk.chunkWrap(new byte[] {QueryConstants.NULL_BYTE})); + parse(ByteValue.standard(), "null", ByteChunk.chunkWrap(new byte[] {QueryConstants.NULL_BYTE})); } @Test void customMissing() throws IOException { - parse(ByteOptions.builder().onMissing((byte) -1).build(), "", ByteChunk.chunkWrap(new byte[] {-1})); + parse(ByteValue.builder().onMissing((byte) -1).build(), "", ByteChunk.chunkWrap(new byte[] {-1})); } @Test void customNull() throws IOException { - parse(ByteOptions.builder().onNull((byte) -2).build(), "null", ByteChunk.chunkWrap(new byte[] {-2})); + parse(ByteValue.builder().onNull((byte) -2).build(), "null", ByteChunk.chunkWrap(new byte[] {-2})); } @Test void strict() throws IOException { - parse(ByteOptions.strict(), "42", ByteChunk.chunkWrap(new byte[] {42})); + parse(ByteValue.strict(), "42", ByteChunk.chunkWrap(new byte[] {42})); } @Test void strictMissing() throws IOException { try { - parse(ByteOptions.strict(), "", ByteChunk.chunkWrap(new byte[1])); + parse(ByteValue.strict(), "", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected missing token"); @@ -60,7 +60,7 @@ void strictMissing() throws IOException { @Test void strictNull() throws IOException { try { - parse(ByteOptions.strict(), "null", ByteChunk.chunkWrap(new byte[1])); + parse(ByteValue.strict(), "null", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); @@ -70,7 +70,7 @@ void strictNull() throws IOException { @Test void standardOverflow() throws IOException { try { - parse(ByteOptions.standard(), "2147483648", ByteChunk.chunkWrap(new byte[1])); + parse(ByteValue.standard(), "2147483648", ByteChunk.chunkWrap(new byte[1])); } catch (InputCoercionException e) { assertThat(e).hasMessageContaining( "Numeric value (2147483648) out of range of int (-2147483648 - 2147483647)"); @@ -80,7 +80,7 @@ void standardOverflow() throws IOException { @Test void standardString() throws IOException { try { - parse(ByteOptions.standard(), "\"42\"", ByteChunk.chunkWrap(new byte[1])); + parse(ByteValue.standard(), "\"42\"", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); @@ -90,7 +90,7 @@ void standardString() throws IOException { @Test void standardTrue() throws IOException { try { - parse(ByteOptions.standard(), "true", ByteChunk.chunkWrap(new byte[1])); + parse(ByteValue.standard(), "true", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); @@ -100,7 +100,7 @@ void standardTrue() throws IOException { @Test void standardFalse() throws IOException { try { - parse(ByteOptions.standard(), "false", ByteChunk.chunkWrap(new byte[1])); + parse(ByteValue.standard(), "false", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); @@ -110,7 +110,7 @@ void standardFalse() throws IOException { @Test void standardFloat() throws IOException { try { - parse(ByteOptions.standard(), "42.0", ByteChunk.chunkWrap(new byte[1])); + parse(ByteValue.standard(), "42.0", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); @@ -120,7 +120,7 @@ void standardFloat() throws IOException { @Test void standardObject() throws IOException { try { - parse(ByteOptions.standard(), "{}", ByteChunk.chunkWrap(new byte[1])); + parse(ByteValue.standard(), "{}", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); @@ -130,7 +130,7 @@ void standardObject() throws IOException { @Test void standardArray() throws IOException { try { - parse(ByteOptions.standard(), "[]", ByteChunk.chunkWrap(new byte[1])); + parse(ByteValue.standard(), "[]", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); @@ -139,19 +139,19 @@ void standardArray() throws IOException { @Test void lenientString() throws IOException { - parse(ByteOptions.lenient(), List.of("\"42\"", "\"43\""), ByteChunk.chunkWrap(new byte[] {42, 43})); + parse(ByteValue.lenient(), List.of("\"42\"", "\"43\""), ByteChunk.chunkWrap(new byte[] {42, 43})); } @Test void allowDecimal() throws IOException { - parse(ByteOptions.builder() + parse(ByteValue.builder() .allowedTypes(JsonValueTypes.INT, JsonValueTypes.DECIMAL) .build(), List.of("42.42", "43.999"), ByteChunk.chunkWrap(new byte[] {42, 43})); } @Test void allowDecimalString() throws IOException { - parse(ByteOptions.builder() + parse(ByteValue.builder() .allowedTypes(JsonValueTypes.STRING, JsonValueTypes.INT, JsonValueTypes.DECIMAL) .build(), List.of("\"42.42\"", "\"43.999\""), ByteChunk.chunkWrap(new byte[] {42, 43})); @@ -160,7 +160,7 @@ void allowDecimalString() throws IOException { @Test void decimalStringLimitsNearMinValue() throws IOException { for (int i = 0; i < 100; ++i) { - parse(ByteOptions.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) + parse(ByteValue.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) .build(), List.of(String.format("\"%d.0\"", Byte.MIN_VALUE + i)), ByteChunk.chunkWrap(new byte[] {(byte) (Byte.MIN_VALUE + i)})); @@ -170,7 +170,7 @@ void decimalStringLimitsNearMinValue() throws IOException { @Test void decimalStringLimitsNearMaxValue() throws IOException { for (int i = 0; i < 100; ++i) { - parse(ByteOptions.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) + parse(ByteValue.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) .build(), List.of(String.format("\"%d.0\"", Byte.MAX_VALUE - i)), ByteChunk.chunkWrap(new byte[] {(byte) (Byte.MAX_VALUE - i)})); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java index 99bf77b4dd9..9b640f08805 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java @@ -17,39 +17,39 @@ public class CharOptionsTest { @Test void standard() throws IOException { - parse(CharOptions.standard(), "\"c\"", CharChunk.chunkWrap(new char[] {'c'})); + parse(CharValue.standard(), "\"c\"", CharChunk.chunkWrap(new char[] {'c'})); } @Test void standardMissing() throws IOException { - parse(CharOptions.standard(), "", CharChunk.chunkWrap(new char[] {QueryConstants.NULL_CHAR})); + parse(CharValue.standard(), "", CharChunk.chunkWrap(new char[] {QueryConstants.NULL_CHAR})); } @Test void standardNull() throws IOException { - parse(CharOptions.standard(), "null", CharChunk.chunkWrap(new char[] {QueryConstants.NULL_CHAR})); + parse(CharValue.standard(), "null", CharChunk.chunkWrap(new char[] {QueryConstants.NULL_CHAR})); } @Test void customMissing() throws IOException { - parse(CharOptions.builder().onMissing('m').build(), "", CharChunk.chunkWrap(new char[] {'m'})); + parse(CharValue.builder().onMissing('m').build(), "", CharChunk.chunkWrap(new char[] {'m'})); } @Test void customNull() throws IOException { - parse(CharOptions.builder().onNull('n').build(), "null", CharChunk.chunkWrap(new char[] {'n'})); + parse(CharValue.builder().onNull('n').build(), "null", CharChunk.chunkWrap(new char[] {'n'})); } @Test void strict() throws IOException { - parse(CharOptions.strict(), "\"c\"", CharChunk.chunkWrap(new char[] {'c'})); + parse(CharValue.strict(), "\"c\"", CharChunk.chunkWrap(new char[] {'c'})); } @Test void strictMissing() throws IOException { try { - parse(CharOptions.strict(), "", CharChunk.chunkWrap(new char[1])); + parse(CharValue.strict(), "", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected missing token"); @@ -59,7 +59,7 @@ void strictMissing() throws IOException { @Test void strictNull() throws IOException { try { - parse(CharOptions.strict(), "null", CharChunk.chunkWrap(new char[1])); + parse(CharValue.strict(), "null", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); @@ -70,7 +70,7 @@ void strictNull() throws IOException { @Test void standardInt() throws IOException { try { - parse(CharOptions.standard(), "42", CharChunk.chunkWrap(new char[1])); + parse(CharValue.standard(), "42", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_INT'"); @@ -80,7 +80,7 @@ void standardInt() throws IOException { @Test void standardFloat() throws IOException { try { - parse(CharOptions.standard(), "42.42", CharChunk.chunkWrap(new char[1])); + parse(CharValue.standard(), "42.42", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); @@ -91,7 +91,7 @@ void standardFloat() throws IOException { @Test void standardTrue() throws IOException { try { - parse(CharOptions.standard(), "true", CharChunk.chunkWrap(new char[1])); + parse(CharValue.standard(), "true", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); @@ -101,7 +101,7 @@ void standardTrue() throws IOException { @Test void standardFalse() throws IOException { try { - parse(CharOptions.standard(), "false", CharChunk.chunkWrap(new char[1])); + parse(CharValue.standard(), "false", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); @@ -111,7 +111,7 @@ void standardFalse() throws IOException { @Test void standardObject() throws IOException { try { - parse(CharOptions.standard(), "{}", CharChunk.chunkWrap(new char[1])); + parse(CharValue.standard(), "{}", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); @@ -121,7 +121,7 @@ void standardObject() throws IOException { @Test void standardArray() throws IOException { try { - parse(CharOptions.standard(), "[]", CharChunk.chunkWrap(new char[1])); + parse(CharValue.standard(), "[]", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayOptionsTest.java index dca0c0e7270..9ec4e7253cb 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayOptionsTest.java @@ -15,17 +15,17 @@ public class DoubleArrayOptionsTest { @Test void standard() throws IOException { - parse(DoubleOptions.standard().array(), "[42.1, null, 43.2]", + parse(DoubleValue.standard().array(), "[42.1, null, 43.2]", ObjectChunk.chunkWrap(new Object[] {new double[] {42.1, QueryConstants.NULL_DOUBLE, 43.2}})); } @Test void standardMissing() throws IOException { - parse(DoubleOptions.standard().array(), "", ObjectChunk.chunkWrap(new Object[] {null})); + parse(DoubleValue.standard().array(), "", ObjectChunk.chunkWrap(new Object[] {null})); } @Test void standardNull() throws IOException { - parse(DoubleOptions.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] {null})); + parse(DoubleValue.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] {null})); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java index c03f08b3954..fbe0405709e 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java @@ -18,33 +18,33 @@ public class DoubleOptionsTest { @Test void standard() throws IOException { - parse(DoubleOptions.standard(), List.of("42", "42.42"), DoubleChunk.chunkWrap(new double[] {42, 42.42})); + parse(DoubleValue.standard(), List.of("42", "42.42"), DoubleChunk.chunkWrap(new double[] {42, 42.42})); } @Test void standardMissing() throws IOException { - parse(DoubleOptions.standard(), "", DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE})); + parse(DoubleValue.standard(), "", DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE})); } @Test void standardNull() throws IOException { - parse(DoubleOptions.standard(), "null", DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE})); + parse(DoubleValue.standard(), "null", DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE})); } @Test void customMissing() throws IOException { - parse(DoubleOptions.builder().onMissing(-1.0).build(), "", DoubleChunk.chunkWrap(new double[] {-1})); + parse(DoubleValue.builder().onMissing(-1.0).build(), "", DoubleChunk.chunkWrap(new double[] {-1})); } @Test void strict() throws IOException { - parse(DoubleOptions.strict(), "42", DoubleChunk.chunkWrap(new double[] {42})); + parse(DoubleValue.strict(), "42", DoubleChunk.chunkWrap(new double[] {42})); } @Test void strictMissing() throws IOException { try { - parse(DoubleOptions.strict(), "", DoubleChunk.chunkWrap(new double[1])); + parse(DoubleValue.strict(), "", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected missing token"); @@ -54,7 +54,7 @@ void strictMissing() throws IOException { @Test void strictNull() throws IOException { try { - parse(DoubleOptions.strict(), "null", DoubleChunk.chunkWrap(new double[1])); + parse(DoubleValue.strict(), "null", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); @@ -64,7 +64,7 @@ void strictNull() throws IOException { @Test void standardString() throws IOException { try { - parse(DoubleOptions.standard(), "\"42\"", DoubleChunk.chunkWrap(new double[1])); + parse(DoubleValue.standard(), "\"42\"", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); @@ -74,7 +74,7 @@ void standardString() throws IOException { @Test void standardTrue() throws IOException { try { - parse(DoubleOptions.standard(), "true", DoubleChunk.chunkWrap(new double[1])); + parse(DoubleValue.standard(), "true", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); @@ -84,7 +84,7 @@ void standardTrue() throws IOException { @Test void standardFalse() throws IOException { try { - parse(DoubleOptions.standard(), "false", DoubleChunk.chunkWrap(new double[1])); + parse(DoubleValue.standard(), "false", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); @@ -94,7 +94,7 @@ void standardFalse() throws IOException { @Test void standardObject() throws IOException { try { - parse(DoubleOptions.standard(), "{}", DoubleChunk.chunkWrap(new double[1])); + parse(DoubleValue.standard(), "{}", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); @@ -104,7 +104,7 @@ void standardObject() throws IOException { @Test void standardArray() throws IOException { try { - parse(DoubleOptions.standard(), "[]", DoubleChunk.chunkWrap(new double[1])); + parse(DoubleValue.standard(), "[]", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); @@ -113,6 +113,6 @@ void standardArray() throws IOException { @Test void lenientString() throws IOException { - parse(DoubleOptions.lenient(), List.of("\"42\"", "\"42.42\""), DoubleChunk.chunkWrap(new double[] {42, 42.42})); + parse(DoubleValue.lenient(), List.of("\"42\"", "\"42.42\""), DoubleChunk.chunkWrap(new double[] {42, 42.42})); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java index b14a9d417bf..63ab9a90ffd 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java @@ -18,33 +18,33 @@ public class FloatOptionsTest { @Test void standard() throws IOException { - parse(FloatOptions.standard(), List.of("42", "42.42"), FloatChunk.chunkWrap(new float[] {42, 42.42f})); + parse(FloatValue.standard(), List.of("42", "42.42"), FloatChunk.chunkWrap(new float[] {42, 42.42f})); } @Test void standardMissing() throws IOException { - parse(FloatOptions.standard(), "", FloatChunk.chunkWrap(new float[] {QueryConstants.NULL_FLOAT})); + parse(FloatValue.standard(), "", FloatChunk.chunkWrap(new float[] {QueryConstants.NULL_FLOAT})); } @Test void standardNull() throws IOException { - parse(FloatOptions.standard(), "null", FloatChunk.chunkWrap(new float[] {QueryConstants.NULL_FLOAT})); + parse(FloatValue.standard(), "null", FloatChunk.chunkWrap(new float[] {QueryConstants.NULL_FLOAT})); } @Test void customMissing() throws IOException { - parse(FloatOptions.builder().onMissing(-1.0f).build(), "", FloatChunk.chunkWrap(new float[] {-1})); + parse(FloatValue.builder().onMissing(-1.0f).build(), "", FloatChunk.chunkWrap(new float[] {-1})); } @Test void strict() throws IOException { - parse(FloatOptions.strict(), "42", FloatChunk.chunkWrap(new float[] {42})); + parse(FloatValue.strict(), "42", FloatChunk.chunkWrap(new float[] {42})); } @Test void strictMissing() throws IOException { try { - parse(FloatOptions.strict(), "", FloatChunk.chunkWrap(new float[1])); + parse(FloatValue.strict(), "", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected missing token"); @@ -54,7 +54,7 @@ void strictMissing() throws IOException { @Test void strictNull() throws IOException { try { - parse(FloatOptions.strict(), "null", FloatChunk.chunkWrap(new float[1])); + parse(FloatValue.strict(), "null", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); @@ -64,7 +64,7 @@ void strictNull() throws IOException { @Test void standardString() throws IOException { try { - parse(FloatOptions.standard(), "\"42\"", FloatChunk.chunkWrap(new float[1])); + parse(FloatValue.standard(), "\"42\"", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); @@ -74,7 +74,7 @@ void standardString() throws IOException { @Test void standardTrue() throws IOException { try { - parse(FloatOptions.standard(), "true", FloatChunk.chunkWrap(new float[1])); + parse(FloatValue.standard(), "true", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); @@ -84,7 +84,7 @@ void standardTrue() throws IOException { @Test void standardFalse() throws IOException { try { - parse(FloatOptions.standard(), "false", FloatChunk.chunkWrap(new float[1])); + parse(FloatValue.standard(), "false", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); @@ -94,7 +94,7 @@ void standardFalse() throws IOException { @Test void standardObject() throws IOException { try { - parse(FloatOptions.standard(), "{}", FloatChunk.chunkWrap(new float[1])); + parse(FloatValue.standard(), "{}", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); @@ -104,7 +104,7 @@ void standardObject() throws IOException { @Test void standardArray() throws IOException { try { - parse(FloatOptions.standard(), "[]", FloatChunk.chunkWrap(new float[1])); + parse(FloatValue.standard(), "[]", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); @@ -113,6 +113,6 @@ void standardArray() throws IOException { @Test void lenientString() throws IOException { - parse(FloatOptions.lenient(), List.of("\"42\"", "\"42.42\""), FloatChunk.chunkWrap(new float[] {42, 42.42f})); + parse(FloatValue.lenient(), List.of("\"42\"", "\"42.42\""), FloatChunk.chunkWrap(new float[] {42, 42.42f})); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberOptionsTest.java index 6cb16691885..776d2bba1a1 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberOptionsTest.java @@ -4,7 +4,7 @@ package io.deephaven.json; import io.deephaven.chunk.LongChunk; -import io.deephaven.json.InstantNumberOptions.Format; +import io.deephaven.json.InstantNumberValue.Format; import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java index 697c4985d7e..eda06779356 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java @@ -21,28 +21,28 @@ public class InstantOptionsTest { @Test void iso8601() throws IOException { - parse(InstantOptions.standard(), "\"" + XYZ_STR + "Z\"", LongChunk.chunkWrap(new long[] {XYZ_NANOS})); + parse(InstantValue.standard(), "\"" + XYZ_STR + "Z\"", LongChunk.chunkWrap(new long[] {XYZ_NANOS})); } @Test void iso8601WithOffset() throws IOException { - parse(InstantOptions.standard(), "\"" + XYZ_STR + "+00:00\"", LongChunk.chunkWrap(new long[] {XYZ_NANOS})); + parse(InstantValue.standard(), "\"" + XYZ_STR + "+00:00\"", LongChunk.chunkWrap(new long[] {XYZ_NANOS})); } @Test void standardNull() throws IOException { - parse(InstantOptions.standard(), "null", LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG})); + parse(InstantValue.standard(), "null", LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG})); } @Test void standardMissing() throws IOException { - parse(InstantOptions.standard(), "", LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG})); + parse(InstantValue.standard(), "", LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG})); } @Test void strictNull() throws IOException { try { - parse(InstantOptions.strict(), "null", LongChunk.chunkWrap(new long[1])); + parse(InstantValue.strict(), "null", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); @@ -52,7 +52,7 @@ void strictNull() throws IOException { @Test void strictMissing() throws IOException { try { - parse(InstantOptions.strict(), "", LongChunk.chunkWrap(new long[1])); + parse(InstantValue.strict(), "", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected missing token"); @@ -61,13 +61,13 @@ void strictMissing() throws IOException { @Test void customNull() throws IOException { - parse(InstantOptions.builder().onNull(Instant.ofEpochMilli(0)).build(), "null", + parse(InstantValue.builder().onNull(Instant.ofEpochMilli(0)).build(), "null", LongChunk.chunkWrap(new long[] {0})); } @Test void customMissing() throws IOException { - parse(InstantOptions.builder().onMissing(Instant.ofEpochMilli(0)).build(), "", + parse(InstantValue.builder().onMissing(Instant.ofEpochMilli(0)).build(), "", LongChunk.chunkWrap(new long[] {0})); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayOptionsTest.java index fad43a276a1..f621b120292 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayOptionsTest.java @@ -14,7 +14,7 @@ public class IntArrayOptionsTest { @Test void standard() throws IOException { - parse(IntOptions.standard().array(), "[42, 43]", ObjectChunk.chunkWrap(new Object[] {new int[] {42, 43}})); + parse(IntValue.standard().array(), "[42, 43]", ObjectChunk.chunkWrap(new Object[] {new int[] {42, 43}})); } // @Test diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java index 6944875f7df..e2b181e71b6 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java @@ -19,38 +19,38 @@ public class IntOptionsTest { @Test void standard() throws IOException { - parse(IntOptions.standard(), "42", IntChunk.chunkWrap(new int[] {42})); + parse(IntValue.standard(), "42", IntChunk.chunkWrap(new int[] {42})); } @Test void standardMissing() throws IOException { - parse(IntOptions.standard(), "", IntChunk.chunkWrap(new int[] {QueryConstants.NULL_INT})); + parse(IntValue.standard(), "", IntChunk.chunkWrap(new int[] {QueryConstants.NULL_INT})); } @Test void standardNull() throws IOException { - parse(IntOptions.standard(), "null", IntChunk.chunkWrap(new int[] {QueryConstants.NULL_INT})); + parse(IntValue.standard(), "null", IntChunk.chunkWrap(new int[] {QueryConstants.NULL_INT})); } @Test void customMissing() throws IOException { - parse(IntOptions.builder().onMissing(-1).build(), "", IntChunk.chunkWrap(new int[] {-1})); + parse(IntValue.builder().onMissing(-1).build(), "", IntChunk.chunkWrap(new int[] {-1})); } @Test void customNull() throws IOException { - parse(IntOptions.builder().onNull(-2).build(), "null", IntChunk.chunkWrap(new int[] {-2})); + parse(IntValue.builder().onNull(-2).build(), "null", IntChunk.chunkWrap(new int[] {-2})); } @Test void strict() throws IOException { - parse(IntOptions.strict(), "42", IntChunk.chunkWrap(new int[] {42})); + parse(IntValue.strict(), "42", IntChunk.chunkWrap(new int[] {42})); } @Test void strictMissing() throws IOException { try { - parse(IntOptions.strict(), "", IntChunk.chunkWrap(new int[1])); + parse(IntValue.strict(), "", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected missing token"); @@ -60,7 +60,7 @@ void strictMissing() throws IOException { @Test void strictNull() throws IOException { try { - parse(IntOptions.strict(), "null", IntChunk.chunkWrap(new int[1])); + parse(IntValue.strict(), "null", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); @@ -70,7 +70,7 @@ void strictNull() throws IOException { @Test void standardOverflow() throws IOException { try { - parse(IntOptions.standard(), "2147483648", IntChunk.chunkWrap(new int[1])); + parse(IntValue.standard(), "2147483648", IntChunk.chunkWrap(new int[1])); } catch (InputCoercionException e) { assertThat(e).hasMessageContaining( "Numeric value (2147483648) out of range of int (-2147483648 - 2147483647)"); @@ -80,7 +80,7 @@ void standardOverflow() throws IOException { @Test void standardString() throws IOException { try { - parse(IntOptions.standard(), "\"42\"", IntChunk.chunkWrap(new int[1])); + parse(IntValue.standard(), "\"42\"", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); @@ -90,7 +90,7 @@ void standardString() throws IOException { @Test void standardTrue() throws IOException { try { - parse(IntOptions.standard(), "true", IntChunk.chunkWrap(new int[1])); + parse(IntValue.standard(), "true", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); @@ -100,7 +100,7 @@ void standardTrue() throws IOException { @Test void standardFalse() throws IOException { try { - parse(IntOptions.standard(), "false", IntChunk.chunkWrap(new int[1])); + parse(IntValue.standard(), "false", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); @@ -110,7 +110,7 @@ void standardFalse() throws IOException { @Test void standardFloat() throws IOException { try { - parse(IntOptions.standard(), "42.0", IntChunk.chunkWrap(new int[1])); + parse(IntValue.standard(), "42.0", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); @@ -120,7 +120,7 @@ void standardFloat() throws IOException { @Test void standardObject() throws IOException { try { - parse(IntOptions.standard(), "{}", IntChunk.chunkWrap(new int[1])); + parse(IntValue.standard(), "{}", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); @@ -130,7 +130,7 @@ void standardObject() throws IOException { @Test void standardArray() throws IOException { try { - parse(IntOptions.standard(), "[]", IntChunk.chunkWrap(new int[1])); + parse(IntValue.standard(), "[]", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); @@ -139,19 +139,19 @@ void standardArray() throws IOException { @Test void lenientString() throws IOException { - parse(IntOptions.lenient(), List.of("\"42\"", "\"43\""), IntChunk.chunkWrap(new int[] {42, 43})); + parse(IntValue.lenient(), List.of("\"42\"", "\"43\""), IntChunk.chunkWrap(new int[] {42, 43})); } @Test void allowDecimal() throws IOException { - parse(IntOptions.builder() + parse(IntValue.builder() .allowedTypes(JsonValueTypes.INT, JsonValueTypes.DECIMAL) .build(), List.of("42.42", "43.999"), IntChunk.chunkWrap(new int[] {42, 43})); } @Test void allowDecimalString() throws IOException { - parse(IntOptions.builder() + parse(IntValue.builder() .allowedTypes(JsonValueTypes.STRING, JsonValueTypes.INT, JsonValueTypes.DECIMAL) .build(), List.of("\"42.42\"", "\"43.999\""), IntChunk.chunkWrap(new int[] {42, 43})); @@ -160,7 +160,7 @@ void allowDecimalString() throws IOException { @Test void decimalStringLimitsNearMinValue() throws IOException { for (int i = 0; i < 100; ++i) { - parse(IntOptions.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) + parse(IntValue.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) .build(), List.of(String.format("\"%d.0\"", Integer.MIN_VALUE + i)), IntChunk.chunkWrap(new int[] {Integer.MIN_VALUE + i})); @@ -170,7 +170,7 @@ void decimalStringLimitsNearMinValue() throws IOException { @Test void decimalStringLimitsNearMaxValue() throws IOException { for (int i = 0; i < 100; ++i) { - parse(IntOptions.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) + parse(IntValue.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) .build(), List.of(String.format("\"%d.0\"", Integer.MAX_VALUE - i)), IntChunk.chunkWrap(new int[] {Integer.MAX_VALUE - i})); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java index 7126903fb79..f43b5191fba 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java @@ -19,24 +19,24 @@ public class LocalDateOptionsTest { @Test void iso8601() throws IOException { - parse(LocalDateOptions.standard(), "\"" + XYZ_STR + "\"", + parse(LocalDateValue.standard(), "\"" + XYZ_STR + "\"", ObjectChunk.chunkWrap(new LocalDate[] {LocalDate.of(2009, 2, 13)})); } @Test void standardNull() throws IOException { - parse(LocalDateOptions.standard(), "null", ObjectChunk.chunkWrap(new LocalDate[] {null})); + parse(LocalDateValue.standard(), "null", ObjectChunk.chunkWrap(new LocalDate[] {null})); } @Test void standardMissing() throws IOException { - parse(LocalDateOptions.standard(), "", ObjectChunk.chunkWrap(new LocalDate[] {null})); + parse(LocalDateValue.standard(), "", ObjectChunk.chunkWrap(new LocalDate[] {null})); } @Test void strictNull() throws IOException { try { - parse(LocalDateOptions.strict(), "null", ObjectChunk.chunkWrap(new LocalDate[1])); + parse(LocalDateValue.strict(), "null", ObjectChunk.chunkWrap(new LocalDate[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); @@ -46,7 +46,7 @@ void strictNull() throws IOException { @Test void strictMissing() throws IOException { try { - parse(LocalDateOptions.strict(), "", ObjectChunk.chunkWrap(new LocalDate[1])); + parse(LocalDateValue.strict(), "", ObjectChunk.chunkWrap(new LocalDate[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected missing token"); @@ -55,13 +55,13 @@ void strictMissing() throws IOException { @Test void customNull() throws IOException { - parse(LocalDateOptions.builder().onNull(LocalDate.ofEpochDay(0)).build(), "null", + parse(LocalDateValue.builder().onNull(LocalDate.ofEpochDay(0)).build(), "null", ObjectChunk.chunkWrap(new LocalDate[] {LocalDate.ofEpochDay(0)})); } @Test void customMissing() throws IOException { - parse(LocalDateOptions.builder().onMissing(LocalDate.ofEpochDay(0)).build(), "", + parse(LocalDateValue.builder().onMissing(LocalDate.ofEpochDay(0)).build(), "", ObjectChunk.chunkWrap(new LocalDate[] {LocalDate.ofEpochDay(0)})); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayOptionsTest.java index 16b9a349271..fcd31d0942f 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayOptionsTest.java @@ -14,16 +14,16 @@ public class LongArrayOptionsTest { @Test void standard() throws IOException { - parse(LongOptions.standard().array(), "[42, 43]", ObjectChunk.chunkWrap(new Object[] {new long[] {42, 43}})); + parse(LongValue.standard().array(), "[42, 43]", ObjectChunk.chunkWrap(new Object[] {new long[] {42, 43}})); } @Test void standardMissing() throws IOException { - parse(LongOptions.standard().array(), "", ObjectChunk.chunkWrap(new Object[] {null})); + parse(LongValue.standard().array(), "", ObjectChunk.chunkWrap(new Object[] {null})); } @Test void standardNull() throws IOException { - parse(LongOptions.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] {null})); + parse(LongValue.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] {null})); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java index c2a7ba42579..8fd74d3b633 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java @@ -19,33 +19,33 @@ public class LongOptionsTest { @Test void standard() throws IOException { - parse(LongOptions.standard(), List.of("42", "43"), LongChunk.chunkWrap(new long[] {42, 43})); + parse(LongValue.standard(), List.of("42", "43"), LongChunk.chunkWrap(new long[] {42, 43})); } @Test void standardMissing() throws IOException { - parse(LongOptions.standard(), "", LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG})); + parse(LongValue.standard(), "", LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG})); } @Test void standardNull() throws IOException { - parse(LongOptions.standard(), "null", LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG})); + parse(LongValue.standard(), "null", LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG})); } @Test void customMissing() throws IOException { - parse(LongOptions.builder().onMissing(-1L).build(), "", LongChunk.chunkWrap(new long[] {-1})); + parse(LongValue.builder().onMissing(-1L).build(), "", LongChunk.chunkWrap(new long[] {-1})); } @Test void strict() throws IOException { - parse(LongOptions.strict(), "42", LongChunk.chunkWrap(new long[] {42})); + parse(LongValue.strict(), "42", LongChunk.chunkWrap(new long[] {42})); } @Test void strictMissing() throws IOException { try { - parse(LongOptions.strict(), "", LongChunk.chunkWrap(new long[1])); + parse(LongValue.strict(), "", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected missing token"); @@ -55,7 +55,7 @@ void strictMissing() throws IOException { @Test void strictNull() throws IOException { try { - parse(LongOptions.strict(), "null", LongChunk.chunkWrap(new long[1])); + parse(LongValue.strict(), "null", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); @@ -65,7 +65,7 @@ void strictNull() throws IOException { @Test void strictOverflow() throws IOException { try { - parse(LongOptions.strict(), "9223372036854775808", LongChunk.chunkWrap(new long[1])); + parse(LongValue.strict(), "9223372036854775808", LongChunk.chunkWrap(new long[1])); } catch (InputCoercionException e) { assertThat(e).hasMessageContaining( "Numeric value (9223372036854775808) out of range of long (-9223372036854775808 - 9223372036854775807)"); @@ -75,7 +75,7 @@ void strictOverflow() throws IOException { @Test void standardString() throws IOException { try { - parse(LongOptions.standard(), "\"42\"", LongChunk.chunkWrap(new long[1])); + parse(LongValue.standard(), "\"42\"", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); @@ -85,7 +85,7 @@ void standardString() throws IOException { @Test void standardTrue() throws IOException { try { - parse(LongOptions.standard(), "true", LongChunk.chunkWrap(new long[1])); + parse(LongValue.standard(), "true", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); @@ -95,7 +95,7 @@ void standardTrue() throws IOException { @Test void standardFalse() throws IOException { try { - parse(LongOptions.standard(), "false", LongChunk.chunkWrap(new long[1])); + parse(LongValue.standard(), "false", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); @@ -105,7 +105,7 @@ void standardFalse() throws IOException { @Test void standardFloat() throws IOException { try { - parse(LongOptions.standard(), "42.0", LongChunk.chunkWrap(new long[1])); + parse(LongValue.standard(), "42.0", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); @@ -115,7 +115,7 @@ void standardFloat() throws IOException { @Test void standardObject() throws IOException { try { - parse(LongOptions.standard(), "{}", LongChunk.chunkWrap(new long[1])); + parse(LongValue.standard(), "{}", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); @@ -125,7 +125,7 @@ void standardObject() throws IOException { @Test void standardArray() throws IOException { try { - parse(LongOptions.standard(), "[]", LongChunk.chunkWrap(new long[1])); + parse(LongValue.standard(), "[]", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); @@ -134,26 +134,26 @@ void standardArray() throws IOException { @Test void lenientString() throws IOException { - parse(LongOptions.lenient(), List.of("\"42\"", "\"43\""), LongChunk.chunkWrap(new long[] {42, 43})); + parse(LongValue.lenient(), List.of("\"42\"", "\"43\""), LongChunk.chunkWrap(new long[] {42, 43})); } @Test void allowDecimal() throws IOException { - parse(LongOptions.builder().allowedTypes(JsonValueTypes.INT, JsonValueTypes.DECIMAL).build(), + parse(LongValue.builder().allowedTypes(JsonValueTypes.INT, JsonValueTypes.DECIMAL).build(), List.of("42.42", "43.999"), LongChunk.chunkWrap(new long[] {42, 43})); } @Test void allowDecimalString() throws IOException { - parse(LongOptions.builder().allowedTypes(JsonValueTypes.numberLike()).build(), + parse(LongValue.builder().allowedTypes(JsonValueTypes.numberLike()).build(), List.of("\"42.42\"", "\"43.999\""), LongChunk.chunkWrap(new long[] {42, 43})); } @Test void decimalStringLimitsNearMinValue() throws IOException { for (int i = 0; i < 100; ++i) { - parse(LongOptions.builder().allowedTypes(JsonValueTypes.numberLike()).build(), + parse(LongValue.builder().allowedTypes(JsonValueTypes.numberLike()).build(), List.of(String.format("\"%d.0\"", Long.MIN_VALUE + i)), LongChunk.chunkWrap(new long[] {Long.MIN_VALUE + i})); } @@ -162,7 +162,7 @@ void decimalStringLimitsNearMinValue() throws IOException { @Test void decimalStringLimitsNearMaxValue() throws IOException { for (int i = 0; i < 100; ++i) { - parse(LongOptions.builder().allowedTypes(JsonValueTypes.numberLike()).build(), + parse(LongValue.builder().allowedTypes(JsonValueTypes.numberLike()).build(), List.of(String.format("\"%d.0\"", Long.MAX_VALUE - i)), LongChunk.chunkWrap(new long[] {Long.MAX_VALUE - i})); } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldOptionsTest.java index efaafca3dfb..4f4fac27dfc 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldOptionsTest.java @@ -3,7 +3,7 @@ // package io.deephaven.json; -import io.deephaven.json.ObjectFieldOptions.RepeatedBehavior; +import io.deephaven.json.ObjectField.RepeatedBehavior; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -12,9 +12,9 @@ public class ObjectFieldOptionsTest { @Test void basic() { - final ObjectFieldOptions field = ObjectFieldOptions.of("Foo", IntOptions.standard()); + final ObjectField field = ObjectField.of("Foo", IntValue.standard()); assertThat(field.name()).isEqualTo("Foo"); - assertThat(field.options()).isEqualTo(IntOptions.standard()); + assertThat(field.options()).isEqualTo(IntValue.standard()); assertThat(field.aliases()).isEmpty(); assertThat(field.caseSensitive()).isTrue(); assertThat(field.repeatedBehavior()).isEqualTo(RepeatedBehavior.ERROR); @@ -22,13 +22,13 @@ void basic() { @Test void caseInsensitiveMatch() { - final ObjectFieldOptions field = ObjectFieldOptions.builder() + final ObjectField field = ObjectField.builder() .name("Foo") - .options(IntOptions.standard()) + .options(IntValue.standard()) .caseSensitive(false) .build(); assertThat(field.name()).isEqualTo("Foo"); - assertThat(field.options()).isEqualTo(IntOptions.standard()); + assertThat(field.options()).isEqualTo(IntValue.standard()); assertThat(field.aliases()).isEmpty(); assertThat(field.caseSensitive()).isFalse(); assertThat(field.repeatedBehavior()).isEqualTo(RepeatedBehavior.ERROR); @@ -36,13 +36,13 @@ void caseInsensitiveMatch() { @Test void repeatedBehavior() { - final ObjectFieldOptions field = ObjectFieldOptions.builder() + final ObjectField field = ObjectField.builder() .name("Foo") - .options(IntOptions.standard()) + .options(IntValue.standard()) .repeatedBehavior(RepeatedBehavior.ERROR) .build(); assertThat(field.name()).isEqualTo("Foo"); - assertThat(field.options()).isEqualTo(IntOptions.standard()); + assertThat(field.options()).isEqualTo(IntValue.standard()); assertThat(field.aliases()).isEmpty(); assertThat(field.caseSensitive()).isTrue(); assertThat(field.repeatedBehavior()).isEqualTo(RepeatedBehavior.ERROR); @@ -50,13 +50,13 @@ void repeatedBehavior() { @Test void alias() { - final ObjectFieldOptions field = ObjectFieldOptions.builder() + final ObjectField field = ObjectField.builder() .name("SomeName") - .options(IntOptions.standard()) + .options(IntValue.standard()) .addAliases("someName") .build(); assertThat(field.name()).isEqualTo("SomeName"); - assertThat(field.options()).isEqualTo(IntOptions.standard()); + assertThat(field.options()).isEqualTo(IntValue.standard()); assertThat(field.aliases()).containsExactly("someName"); assertThat(field.caseSensitive()).isTrue(); assertThat(field.repeatedBehavior()).isEqualTo(RepeatedBehavior.ERROR); @@ -65,9 +65,9 @@ void alias() { @Test void badAliasRepeated() { try { - ObjectFieldOptions.builder() + ObjectField.builder() .name("SomeName") - .options(IntOptions.standard()) + .options(IntValue.standard()) .addAliases("SomeName") .build(); failBecauseExceptionWasNotThrown(IllegalArgumentException.class); @@ -80,9 +80,9 @@ void badAliasRepeated() { void badAliasCaseInsensitive() { try { // this is similar to the alias() test, but we are explicitly marking it as case-insensitive - ObjectFieldOptions.builder() + ObjectField.builder() .name("SomeName") - .options(IntOptions.standard()) + .options(IntValue.standard()) .addAliases("someName") .caseSensitive(false) .build(); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvOptionsTest.java index db4291f74ee..cd24f67f6fd 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvOptionsTest.java @@ -17,15 +17,15 @@ public class ObjectKvOptionsTest { - private static final ObjectKvOptions STRING_INT_KV = - ObjectKvOptions.standard(IntOptions.standard()); + private static final ObjectKvValue STRING_INT_KV = + ObjectKvValue.standard(IntValue.standard()); - private static final ObjectOptions NAME_AGE_OBJ = ObjectOptions.builder() - .putFields("name", StringOptions.standard()) - .putFields("age", IntOptions.standard()) + private static final ObjectValue NAME_AGE_OBJ = ObjectValue.builder() + .putFields("name", StringValue.standard()) + .putFields("age", IntValue.standard()) .build(); - private static final ObjectKvOptions STRING_OBJ_KV = ObjectKvOptions.standard(NAME_AGE_OBJ); + private static final ObjectKvValue STRING_OBJ_KV = ObjectKvValue.standard(NAME_AGE_OBJ); @Test void kvPrimitiveValue() throws IOException { @@ -46,14 +46,14 @@ void kvObjectValue() throws IOException { @Test void kvPrimitiveKey() throws IOException { - parse(ObjectKvOptions.builder().key(IntOptions.lenient()).value(SkipOptions.lenient()).build(), List.of( + parse(ObjectKvValue.builder().key(IntValue.lenient()).value(SkipValue.lenient()).build(), List.of( "{\"42\": null, \"43\": null}"), ObjectChunk.chunkWrap(new Object[] {new int[] {42, 43}})); } @Test void kvObjectKey() throws IOException { - parse(ObjectKvOptions.builder().key(InstantOptions.standard()).value(SkipOptions.lenient()).build(), List.of( + parse(ObjectKvValue.builder().key(InstantValue.standard()).value(SkipValue.lenient()).build(), List.of( "{\"2009-02-13T23:31:30.123456788Z\": null, \"2009-02-13T23:31:30.123456789Z\": null}"), ObjectChunk.chunkWrap(new Object[] {new long[] {1234567890123456788L, 1234567890123456789L}})); } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java index b6bf7010224..4620dccc83a 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java @@ -19,13 +19,13 @@ public class ObjectOptionsTest { - public static final ObjectOptions OBJECT_AGE_FIELD = ObjectOptions.builder() - .putFields("age", IntOptions.standard()) + public static final ObjectValue OBJECT_AGE_FIELD = ObjectValue.builder() + .putFields("age", IntValue.standard()) .build(); - private static final ObjectOptions OBJECT_NAME_AGE_FIELD = ObjectOptions.builder() - .putFields("name", StringOptions.standard()) - .putFields("age", IntOptions.standard()) + private static final ObjectValue OBJECT_NAME_AGE_FIELD = ObjectValue.builder() + .putFields("name", StringValue.standard()) + .putFields("age", IntValue.standard()) .build(); @Test @@ -55,10 +55,10 @@ void ofNameAge() throws IOException { @Test void caseInsensitive() throws IOException { - final ObjectOptions options = ObjectOptions.builder() - .addFields(ObjectFieldOptions.builder() + final ObjectValue options = ObjectValue.builder() + .addFields(ObjectField.builder() .name("Foo") - .options(IntOptions.standard()) + .options(IntValue.standard()) .caseSensitive(false) .build()) .build(); @@ -68,9 +68,9 @@ void caseInsensitive() throws IOException { @Test void caseSensitive() throws IOException { - final ObjectFieldOptions f1 = ObjectFieldOptions.of("Foo", IntOptions.standard()); - final ObjectFieldOptions f2 = ObjectFieldOptions.of("foo", IntOptions.standard()); - final ObjectOptions options = ObjectOptions.builder() + final ObjectField f1 = ObjectField.of("Foo", IntValue.standard()); + final ObjectField f2 = ObjectField.of("foo", IntValue.standard()); + final ObjectValue options = ObjectValue.builder() .addFields(f1) .addFields(f2) .build(); @@ -81,10 +81,10 @@ void caseSensitive() throws IOException { @Test void alias() throws IOException { - final ObjectOptions options = ObjectOptions.builder() - .addFields(ObjectFieldOptions.builder() + final ObjectValue options = ObjectValue.builder() + .addFields(ObjectField.builder() .name("FooBar") - .options(IntOptions.standard()) + .options(IntValue.standard()) .addAliases("Foo_Bar") .build()) .build(); @@ -94,10 +94,10 @@ void alias() throws IOException { @Test void caseInsensitiveAlias() throws IOException { - final ObjectOptions options = ObjectOptions.builder() - .addFields(ObjectFieldOptions.builder() + final ObjectValue options = ObjectValue.builder() + .addFields(ObjectField.builder() .name("FooBar") - .options(IntOptions.standard()) + .options(IntValue.standard()) .addAliases("Foo_Bar") .caseSensitive(false) .build()) @@ -108,9 +108,9 @@ void caseInsensitiveAlias() throws IOException { @Test void caseSensitiveFields() { - final ObjectFieldOptions f1 = ObjectFieldOptions.of("Foo", IntOptions.standard()); - final ObjectFieldOptions f2 = ObjectFieldOptions.of("foo", IntOptions.standard()); - final ObjectOptions options = ObjectOptions.builder() + final ObjectField f1 = ObjectField.of("Foo", IntValue.standard()); + final ObjectField f2 = ObjectField.of("foo", IntValue.standard()); + final ObjectValue options = ObjectValue.builder() .addFields(f1) .addFields(f2) .build(); @@ -119,14 +119,14 @@ void caseSensitiveFields() { @Test void caseInsensitiveOverlap() { - final ObjectFieldOptions f1 = ObjectFieldOptions.of("Foo", IntOptions.standard()); - final ObjectFieldOptions f2 = ObjectFieldOptions.builder() + final ObjectField f1 = ObjectField.of("Foo", IntValue.standard()); + final ObjectField f2 = ObjectField.builder() .name("foo") - .options(IntOptions.standard()) + .options(IntValue.standard()) .caseSensitive(false) .build(); try { - ObjectOptions.builder() + ObjectValue.builder() .addFields(f1) .addFields(f2) .build(); @@ -139,9 +139,9 @@ void caseInsensitiveOverlap() { @Test void objectFields() throws IOException { // { "prices": [1.1, 2.2, 3.3], "other": [2, 4, 8, 16] } - parse(ObjectOptions.builder() - .putFields("prices", DoubleOptions.standard().array()) - .putFields("other", LongOptions.standard().array()) + parse(ObjectValue.builder() + .putFields("prices", DoubleValue.standard().array()) + .putFields("other", LongValue.standard().array()) .build(), "{ \"prices\": [1.1, 2.2, 3.3], \"other\": [2, 4, 8, 16] }", ObjectChunk @@ -154,15 +154,15 @@ void objectFields() throws IOException { void objectFieldsArrayGroup() throws IOException { // Note: array groups don't cause any difference wrt ObjectProcessor based destructuring // { "prices": [1.1, 2.2, 3.3], "sizes": [2, 4, 8] } - parse(ObjectOptions.builder() - .addFields(ObjectFieldOptions.builder() + parse(ObjectValue.builder() + .addFields(ObjectField.builder() .name("prices") - .options(DoubleOptions.standard().array()) + .options(DoubleValue.standard().array()) .arrayGroup("prices_and_sizes") .build()) - .addFields(ObjectFieldOptions.builder() + .addFields(ObjectField.builder() .name("sizes") - .options(LongOptions.standard().array()) + .options(LongValue.standard().array()) .arrayGroup("prices_and_sizes") .build()) .build(), @@ -181,22 +181,22 @@ void columnNames() { @Test void columnNamesAlternateName() { - final ObjectOptions obj = ObjectOptions.builder() - .addFields(ObjectFieldOptions.builder() + final ObjectValue obj = ObjectValue.builder() + .addFields(ObjectField.builder() .name("MyName") .addAliases("name") - .options(StringOptions.standard()) + .options(StringValue.standard()) .build()) - .addFields(ObjectFieldOptions.of("age", IntOptions.standard())) + .addFields(ObjectField.of("age", IntValue.standard())) .build(); assertThat(JacksonProvider.of(obj).named(Type.stringType()).names()).containsExactly("MyName", "age"); } @Test void columnNamesWithFieldThatIsNotColumnNameCompatible() { - final ObjectOptions objPlusOneMinusOneCount = ObjectOptions.builder() - .putFields("+1", IntOptions.standard()) - .putFields("-1", IntOptions.standard()) + final ObjectValue objPlusOneMinusOneCount = ObjectValue.builder() + .putFields("+1", IntValue.standard()) + .putFields("-1", IntValue.standard()) .build(); assertThat(JacksonProvider.of(objPlusOneMinusOneCount).named(Type.stringType()).names()) .containsExactly("column_1", "column_12"); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/RepeatedProcessorTests.java b/extensions/json-jackson/src/test/java/io/deephaven/json/RepeatedProcessorTests.java index ed5c997f94f..f7c0a087875 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/RepeatedProcessorTests.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/RepeatedProcessorTests.java @@ -16,7 +16,7 @@ public class RepeatedProcessorTests { @Test void arrayArrayPrimitive() throws IOException { // [[1.1], null, [], [2.2, 3.3]] - parse(DoubleOptions.standard().array().array(), + parse(DoubleValue.standard().array().array(), "[[1.1], null, [], [2.2, 3.3]]", ObjectChunk.chunkWrap(new Object[] { new double[][] {new double[] {1.1}, null, new double[0], new double[] {2.2, 3.3}}})); @@ -25,7 +25,7 @@ void arrayArrayPrimitive() throws IOException { @Test void arrayKvPrimitive() throws IOException { // [{"a": 1.1}, null, {}, {"b": 2.2, "c": 3.3}] - parse(ObjectKvOptions.builder().key(SkipOptions.lenient()).value(DoubleOptions.standard()).build().array(), + parse(ObjectKvValue.builder().key(SkipValue.lenient()).value(DoubleValue.standard()).build().array(), "[{\"a\": 1.1}, null, {}, {\"b\": 2.2, \"c\": 3.3}]", ObjectChunk.chunkWrap(new Object[] { new double[][] {new double[] {1.1}, null, new double[0], new double[] {2.2, 3.3}}})); @@ -34,7 +34,7 @@ void arrayKvPrimitive() throws IOException { @Test void kvArrayPrimitive() throws IOException { // {"a": [1.1], "b": null, "c": [], "d": [2.2, 3.3]} - parse(ObjectKvOptions.standard(DoubleOptions.standard().array()), + parse(ObjectKvValue.standard(DoubleValue.standard().array()), "{\"a\": [1.1], \"b\": null, \"c\": [], \"d\": [2.2, 3.3]}", ObjectChunk.chunkWrap(new Object[] { new String[] {"a", "b", "c", "d"}}), @@ -45,7 +45,7 @@ void kvArrayPrimitive() throws IOException { @Test void tuple() throws IOException { // [[[1, 1.1]], null, [], [[2, 2.2], [3, 3.3]]] - parse(TupleOptions.of(IntOptions.standard(), DoubleOptions.standard()).array().array(), + parse(TupleValue.of(IntValue.standard(), DoubleValue.standard()).array().array(), "[[[1, 1.1]], null, [], [[2, 2.2], [3, 3.3]]]", ObjectChunk.chunkWrap(new Object[] {new int[][] {new int[] {1}, null, new int[0], new int[] {2, 3}}}), ObjectChunk.chunkWrap(new Object[] { @@ -56,9 +56,9 @@ void tuple() throws IOException { void object() throws IOException { // [[{"int": 1, "double": 1.1}], null, [], [{"int": 2}, {"double": 3.3}], [{"int": 4, "double": 4.4}, {"int": 5, // "double": 5.5}]] - parse(ObjectOptions.builder() - .putFields("int", IntOptions.standard()) - .putFields("double", DoubleOptions.standard()) + parse(ObjectValue.builder() + .putFields("int", IntValue.standard()) + .putFields("double", DoubleValue.standard()) .build() .array() .array(), @@ -74,12 +74,12 @@ void object() throws IOException { void differentNesting() throws IOException { // [ { "foo": [ { "bar": 41 }, {} ], "baz": 1.1 }, null, {}, { "foo": [] }, { "foo": [ { "bar": 43 } ], "baz": // 3.3 }] - parse(ObjectOptions.builder() - .putFields("foo", ObjectOptions.builder() - .putFields("bar", IntOptions.standard()) + parse(ObjectValue.builder() + .putFields("foo", ObjectValue.builder() + .putFields("bar", IntValue.standard()) .build() .array()) - .putFields("baz", DoubleOptions.standard()) + .putFields("baz", DoubleValue.standard()) .build() .array(), "[ { \"foo\": [ { \"bar\": 41 }, {} ], \"baz\": 1.1 }, null, {}, { \"foo\": [] }, { \"foo\": [ { \"bar\": 43 } ], \"baz\": 3.3 }]", diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java index c049a699ea4..1bebca975f5 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java @@ -19,38 +19,38 @@ public class ShortOptionsTest { @Test void standard() throws IOException { - parse(ShortOptions.standard(), "42", ShortChunk.chunkWrap(new short[] {42})); + parse(ShortValue.standard(), "42", ShortChunk.chunkWrap(new short[] {42})); } @Test void standardMissing() throws IOException { - parse(ShortOptions.standard(), "", ShortChunk.chunkWrap(new short[] {QueryConstants.NULL_SHORT})); + parse(ShortValue.standard(), "", ShortChunk.chunkWrap(new short[] {QueryConstants.NULL_SHORT})); } @Test void standardNull() throws IOException { - parse(ShortOptions.standard(), "null", ShortChunk.chunkWrap(new short[] {QueryConstants.NULL_SHORT})); + parse(ShortValue.standard(), "null", ShortChunk.chunkWrap(new short[] {QueryConstants.NULL_SHORT})); } @Test void customMissing() throws IOException { - parse(ShortOptions.builder().onMissing((short) -1).build(), "", ShortChunk.chunkWrap(new short[] {-1})); + parse(ShortValue.builder().onMissing((short) -1).build(), "", ShortChunk.chunkWrap(new short[] {-1})); } @Test void customNull() throws IOException { - parse(ShortOptions.builder().onNull((short) -2).build(), "null", ShortChunk.chunkWrap(new short[] {-2})); + parse(ShortValue.builder().onNull((short) -2).build(), "null", ShortChunk.chunkWrap(new short[] {-2})); } @Test void strict() throws IOException { - parse(ShortOptions.strict(), "42", ShortChunk.chunkWrap(new short[] {42})); + parse(ShortValue.strict(), "42", ShortChunk.chunkWrap(new short[] {42})); } @Test void strictMissing() throws IOException { try { - parse(ShortOptions.strict(), "", ShortChunk.chunkWrap(new short[1])); + parse(ShortValue.strict(), "", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected missing token"); @@ -60,7 +60,7 @@ void strictMissing() throws IOException { @Test void strictNull() throws IOException { try { - parse(ShortOptions.strict(), "null", ShortChunk.chunkWrap(new short[1])); + parse(ShortValue.strict(), "null", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); @@ -70,7 +70,7 @@ void strictNull() throws IOException { @Test void standardOverflow() throws IOException { try { - parse(ShortOptions.standard(), "2147483648", ShortChunk.chunkWrap(new short[1])); + parse(ShortValue.standard(), "2147483648", ShortChunk.chunkWrap(new short[1])); } catch (InputCoercionException e) { assertThat(e).hasMessageContaining( "Numeric value (2147483648) out of range of int (-2147483648 - 2147483647)"); @@ -80,7 +80,7 @@ void standardOverflow() throws IOException { @Test void standardString() throws IOException { try { - parse(ShortOptions.standard(), "\"42\"", ShortChunk.chunkWrap(new short[1])); + parse(ShortValue.standard(), "\"42\"", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); @@ -90,7 +90,7 @@ void standardString() throws IOException { @Test void standardTrue() throws IOException { try { - parse(ShortOptions.standard(), "true", ShortChunk.chunkWrap(new short[1])); + parse(ShortValue.standard(), "true", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); @@ -100,7 +100,7 @@ void standardTrue() throws IOException { @Test void standardFalse() throws IOException { try { - parse(ShortOptions.standard(), "false", ShortChunk.chunkWrap(new short[1])); + parse(ShortValue.standard(), "false", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); @@ -110,7 +110,7 @@ void standardFalse() throws IOException { @Test void standardFloat() throws IOException { try { - parse(ShortOptions.standard(), "42.0", ShortChunk.chunkWrap(new short[1])); + parse(ShortValue.standard(), "42.0", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); @@ -120,7 +120,7 @@ void standardFloat() throws IOException { @Test void standardObject() throws IOException { try { - parse(ShortOptions.standard(), "{}", ShortChunk.chunkWrap(new short[1])); + parse(ShortValue.standard(), "{}", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); @@ -130,7 +130,7 @@ void standardObject() throws IOException { @Test void standardArray() throws IOException { try { - parse(ShortOptions.standard(), "[]", ShortChunk.chunkWrap(new short[1])); + parse(ShortValue.standard(), "[]", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); @@ -139,19 +139,19 @@ void standardArray() throws IOException { @Test void lenientString() throws IOException { - parse(ShortOptions.lenient(), List.of("\"42\"", "\"43\""), ShortChunk.chunkWrap(new short[] {42, 43})); + parse(ShortValue.lenient(), List.of("\"42\"", "\"43\""), ShortChunk.chunkWrap(new short[] {42, 43})); } @Test void allowDecimal() throws IOException { - parse(ShortOptions.builder() + parse(ShortValue.builder() .allowedTypes(JsonValueTypes.INT, JsonValueTypes.DECIMAL) .build(), List.of("42.42", "43.999"), ShortChunk.chunkWrap(new short[] {42, 43})); } @Test void allowDecimalString() throws IOException { - parse(ShortOptions.builder() + parse(ShortValue.builder() .allowedTypes(JsonValueTypes.STRING, JsonValueTypes.INT, JsonValueTypes.DECIMAL) .build(), List.of("\"42.42\"", "\"43.999\""), ShortChunk.chunkWrap(new short[] {42, 43})); @@ -160,7 +160,7 @@ void allowDecimalString() throws IOException { @Test void decimalStringLimitsNearMinValue() throws IOException { for (int i = 0; i < 100; ++i) { - parse(ShortOptions.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) + parse(ShortValue.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) .build(), List.of(String.format("\"%d.0\"", Short.MIN_VALUE + i)), ShortChunk.chunkWrap(new short[] {(short) (Short.MIN_VALUE + i)})); @@ -170,7 +170,7 @@ void decimalStringLimitsNearMinValue() throws IOException { @Test void decimalStringLimitsNearMaxValue() throws IOException { for (int i = 0; i < 100; ++i) { - parse(ShortOptions.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) + parse(ShortValue.builder().allowedTypes(JsonValueTypes.STRING, JsonValueTypes.DECIMAL, JsonValueTypes.INT) .build(), List.of(String.format("\"%d.0\"", Short.MAX_VALUE - i)), ShortChunk.chunkWrap(new short[] {(short) (Short.MAX_VALUE - i)})); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java index 8d0d7ad4ba0..867165bfbe1 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java @@ -17,40 +17,40 @@ public class StringOptionsTest { @Test void standard() throws IOException { - parse(StringOptions.standard(), "\"foo\"", ObjectChunk.chunkWrap(new String[] {"foo"})); + parse(StringValue.standard(), "\"foo\"", ObjectChunk.chunkWrap(new String[] {"foo"})); } @Test void standardMissing() throws IOException { - parse(StringOptions.standard(), "", ObjectChunk.chunkWrap(new String[] {null})); + parse(StringValue.standard(), "", ObjectChunk.chunkWrap(new String[] {null})); } @Test void standardNull() throws IOException { - parse(StringOptions.standard(), "null", ObjectChunk.chunkWrap(new String[] {null})); + parse(StringValue.standard(), "null", ObjectChunk.chunkWrap(new String[] {null})); } @Test void customMissing() throws IOException { - parse(StringOptions.builder().onMissing("").build(), "", + parse(StringValue.builder().onMissing("").build(), "", ObjectChunk.chunkWrap(new String[] {""})); } @Test void customNull() throws IOException { - parse(StringOptions.builder().onNull("").build(), "null", ObjectChunk.chunkWrap(new String[] {""})); + parse(StringValue.builder().onNull("").build(), "null", ObjectChunk.chunkWrap(new String[] {""})); } @Test void strict() throws IOException { - parse(StringOptions.strict(), "\"foo\"", ObjectChunk.chunkWrap(new String[] {"foo"})); + parse(StringValue.strict(), "\"foo\"", ObjectChunk.chunkWrap(new String[] {"foo"})); } @Test void strictMissing() throws IOException { try { - parse(StringOptions.strict(), "", ObjectChunk.chunkWrap(new String[1])); + parse(StringValue.strict(), "", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected missing token"); @@ -60,7 +60,7 @@ void strictMissing() throws IOException { @Test void strictNull() throws IOException { try { - parse(StringOptions.strict(), "null", ObjectChunk.chunkWrap(new String[1])); + parse(StringValue.strict(), "null", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); @@ -71,7 +71,7 @@ void strictNull() throws IOException { @Test void standardInt() throws IOException { try { - parse(StringOptions.standard(), "42", ObjectChunk.chunkWrap(new String[1])); + parse(StringValue.standard(), "42", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_INT'"); @@ -81,7 +81,7 @@ void standardInt() throws IOException { @Test void standardFloat() throws IOException { try { - parse(StringOptions.standard(), "42.42", ObjectChunk.chunkWrap(new String[1])); + parse(StringValue.standard(), "42.42", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); @@ -92,7 +92,7 @@ void standardFloat() throws IOException { @Test void standardTrue() throws IOException { try { - parse(StringOptions.standard(), "true", ObjectChunk.chunkWrap(new String[1])); + parse(StringValue.standard(), "true", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); @@ -102,7 +102,7 @@ void standardTrue() throws IOException { @Test void standardFalse() throws IOException { try { - parse(StringOptions.standard(), "false", ObjectChunk.chunkWrap(new String[1])); + parse(StringValue.standard(), "false", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); @@ -112,7 +112,7 @@ void standardFalse() throws IOException { @Test void standardObject() throws IOException { try { - parse(StringOptions.standard(), "{}", ObjectChunk.chunkWrap(new String[1])); + parse(StringValue.standard(), "{}", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); @@ -122,7 +122,7 @@ void standardObject() throws IOException { @Test void standardArray() throws IOException { try { - parse(StringOptions.standard(), "[]", ObjectChunk.chunkWrap(new String[1])); + parse(StringValue.standard(), "[]", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); @@ -131,7 +131,7 @@ void standardArray() throws IOException { @Test void lenientString() throws IOException { - parse(StringOptions.lenient(), List.of("42", "42.42", "true", "false"), + parse(StringValue.lenient(), List.of("42", "42.42", "true", "false"), ObjectChunk.chunkWrap(new String[] {"42", "42.42", "true", "false"})); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java index d7bea99563f..c608196092b 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java @@ -28,11 +28,11 @@ import static org.assertj.core.api.Assertions.assertThat; public class TestHelper { - public static void parse(ValueOptions options, String json, Chunk... expected) throws IOException { + public static void parse(Value options, String json, Chunk... expected) throws IOException { parse(options, List.of(json), expected); } - public static void parse(ValueOptions options, List jsonRows, Chunk... expectedCols) throws IOException { + public static void parse(Value options, List jsonRows, Chunk... expectedCols) throws IOException { parse(JacksonProvider.of(options).stringProcessor(), jsonRows, expectedCols); } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java index 1d2c6f90798..bcd302eff24 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java @@ -14,11 +14,11 @@ public class TupleOptionsTest { - private static final TupleOptions STRING_INT_TUPLE = - TupleOptions.of(StringOptions.standard(), IntOptions.standard()); + private static final TupleValue STRING_INT_TUPLE = + TupleValue.of(StringValue.standard(), IntValue.standard()); - private static final TupleOptions STRING_SKIPINT_TUPLE = - TupleOptions.of(StringOptions.standard(), IntOptions.standard().skip()); + private static final TupleValue STRING_SKIPINT_TUPLE = + TupleValue.of(StringValue.standard(), IntValue.standard().skip()); @Test void stringIntTuple() throws IOException { diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java index bc01404cc8e..72f229dc30f 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java @@ -15,20 +15,20 @@ import static io.deephaven.json.TestHelper.parse; public class TypedObjectOptionsTest { - private static final ObjectOptions QUOTE_OBJECT = ObjectOptions.builder() - .putFields("symbol", StringOptions.strict()) - .putFields("bid", DoubleOptions.standard()) - .putFields("ask", DoubleOptions.standard()) + private static final ObjectValue QUOTE_OBJECT = ObjectValue.builder() + .putFields("symbol", StringValue.strict()) + .putFields("bid", DoubleValue.standard()) + .putFields("ask", DoubleValue.standard()) .build(); - private static final ObjectOptions TRADE_OBJECT = ObjectOptions.builder() - .putFields("symbol", StringOptions.strict()) - .putFields("price", DoubleOptions.standard()) - .putFields("size", DoubleOptions.standard()) + private static final ObjectValue TRADE_OBJECT = ObjectValue.builder() + .putFields("symbol", StringValue.strict()) + .putFields("price", DoubleValue.standard()) + .putFields("size", DoubleValue.standard()) .build(); - private static final TypedObjectOptions QUOTE_OR_TRADE_OBJECT = - TypedObjectOptions.strict("type", new LinkedHashMap<>() { + private static final TypedObjectValue QUOTE_OR_TRADE_OBJECT = + TypedObjectValue.strict("type", new LinkedHashMap<>() { { put("quote", QUOTE_OBJECT); put("trade", TRADE_OBJECT); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyOptionsTest.java index 24f31c5905d..62517be351c 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyOptionsTest.java @@ -15,12 +15,12 @@ import io.deephaven.chunk.DoubleChunk; import io.deephaven.chunk.IntChunk; import io.deephaven.chunk.ObjectChunk; -import io.deephaven.json.AnyOptions; -import io.deephaven.json.DoubleOptions; -import io.deephaven.json.IntOptions; -import io.deephaven.json.ObjectOptions; +import io.deephaven.json.AnyValue; +import io.deephaven.json.DoubleValue; +import io.deephaven.json.IntValue; +import io.deephaven.json.ObjectValue; import io.deephaven.json.TestHelper; -import io.deephaven.json.TupleOptions; +import io.deephaven.json.TupleValue; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -79,7 +79,7 @@ void anyArray() throws IOException { @Test void anyInTuple() throws IOException { - final TupleOptions options = TupleOptions.of(IntOptions.standard(), AnyOptions.of(), DoubleOptions.standard()); + final TupleValue options = TupleValue.of(IntValue.standard(), AnyValue.of(), DoubleValue.standard()); TestHelper.parse(options, List.of("", "[42, {\"zip\": 43}, 44.44]"), IntChunk.chunkWrap(new int[] {QueryConstants.NULL_INT, 42}), ObjectChunk.chunkWrap(new TreeNode[] {MissingNode.getInstance(), @@ -89,10 +89,10 @@ void anyInTuple() throws IOException { @Test void anyInObject() throws IOException { - final ObjectOptions options = ObjectOptions.builder() - .putFields("foo", IntOptions.standard()) - .putFields("bar", AnyOptions.of()) - .putFields("baz", DoubleOptions.standard()) + final ObjectValue options = ObjectValue.builder() + .putFields("foo", IntValue.standard()) + .putFields("bar", AnyValue.of()) + .putFields("baz", DoubleValue.standard()) .build(); TestHelper.parse(options, List.of("", "{\"foo\": 42, \"bar\": {\"zip\": 43}, \"baz\": 44.44}"), IntChunk.chunkWrap(new int[] {QueryConstants.NULL_INT, 42}), @@ -102,6 +102,6 @@ void anyInObject() throws IOException { } private static void checkAny(String json, TreeNode expected) throws IOException { - TestHelper.parse(AnyOptions.of(), json, ObjectChunk.chunkWrap(new TreeNode[] {expected})); + TestHelper.parse(AnyValue.of(), json, ObjectChunk.chunkWrap(new TreeNode[] {expected})); } } diff --git a/extensions/json/src/main/java/io/deephaven/json/AnyOptions.java b/extensions/json/src/main/java/io/deephaven/json/AnyValue.java similarity index 86% rename from extensions/json/src/main/java/io/deephaven/json/AnyOptions.java rename to extensions/json/src/main/java/io/deephaven/json/AnyValue.java index e54ba4f420f..b1a0440cc1d 100644 --- a/extensions/json/src/main/java/io/deephaven/json/AnyOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/AnyValue.java @@ -13,15 +13,15 @@ */ @Immutable @SingletonStyle -public abstract class AnyOptions extends ValueOptions { +public abstract class AnyValue extends Value { /** * Allows missing and accepts {@link JsonValueTypes#all()}. * * @return the any options */ - public static AnyOptions of() { - return ImmutableAnyOptions.of(); + public static AnyValue of() { + return ImmutableAnyValue.of(); } /** diff --git a/extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java b/extensions/json/src/main/java/io/deephaven/json/ArrayValue.java similarity index 80% rename from extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java rename to extensions/json/src/main/java/io/deephaven/json/ArrayValue.java index 501220815bb..096c519e111 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ArrayOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ArrayValue.java @@ -18,10 +18,10 @@ */ @Immutable @BuildableStyle -public abstract class ArrayOptions extends ValueOptionsRestrictedUniverseBase { +public abstract class ArrayValue extends ValueRestrictedUniverseBase { public static Builder builder() { - return ImmutableArrayOptions.builder(); + return ImmutableArrayValue.builder(); } /** @@ -30,7 +30,7 @@ public static Builder builder() { * @param element the element type * @return the standard array options */ - public static ArrayOptions standard(ValueOptions element) { + public static ArrayValue standard(Value element) { return builder().element(element).build(); } @@ -40,7 +40,7 @@ public static ArrayOptions standard(ValueOptions element) { * @param element the element type * @return the strict array options */ - public static ArrayOptions strict(ValueOptions element) { + public static ArrayValue strict(Value element) { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.array()) @@ -51,7 +51,7 @@ public static ArrayOptions strict(ValueOptions element) { /** * The type for the elements of the array. */ - public abstract ValueOptions element(); + public abstract Value element(); /** * {@inheritDoc} Must be a subset of {@link JsonValueTypes#arrayOrNull()}. By default is @@ -73,8 +73,8 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptions.Builder { + public interface Builder extends Value.Builder { - Builder element(ValueOptions options); + Builder element(Value options); } } diff --git a/extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java b/extensions/json/src/main/java/io/deephaven/json/BigDecimalValue.java similarity index 81% rename from extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java rename to extensions/json/src/main/java/io/deephaven/json/BigDecimalValue.java index d24e960f9b6..3454df81169 100644 --- a/extensions/json/src/main/java/io/deephaven/json/BigDecimalOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/BigDecimalValue.java @@ -15,10 +15,10 @@ */ @Immutable @BuildableStyle -public abstract class BigDecimalOptions extends ValueOptionsSingleValueBase { +public abstract class BigDecimalValue extends ValueSingleValueBase { public static Builder builder() { - return ImmutableBigDecimalOptions.builder(); + return ImmutableBigDecimalValue.builder(); } /** @@ -26,7 +26,7 @@ public static Builder builder() { * * @return the lenient BigDecimal options */ - public static BigDecimalOptions lenient() { + public static BigDecimalValue lenient() { return builder().allowedTypes(JsonValueTypes.numberLike()).build(); } @@ -35,7 +35,7 @@ public static BigDecimalOptions lenient() { * * @return the standard BigDecimal options */ - public static BigDecimalOptions standard() { + public static BigDecimalValue standard() { return builder().build(); } @@ -44,7 +44,7 @@ public static BigDecimalOptions standard() { * * @return the strict BigDecimal options */ - public static BigDecimalOptions strict() { + public static BigDecimalValue strict() { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.number()) @@ -71,7 +71,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptionsSingleValueBase.Builder { + public interface Builder extends ValueSingleValueBase.Builder { } } diff --git a/extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java b/extensions/json/src/main/java/io/deephaven/json/BigIntegerValue.java similarity index 82% rename from extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java rename to extensions/json/src/main/java/io/deephaven/json/BigIntegerValue.java index b10017191c5..5b87ec26a27 100644 --- a/extensions/json/src/main/java/io/deephaven/json/BigIntegerOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/BigIntegerValue.java @@ -15,9 +15,9 @@ */ @Immutable @BuildableStyle -public abstract class BigIntegerOptions extends ValueOptionsSingleValueBase { +public abstract class BigIntegerValue extends ValueSingleValueBase { public static Builder builder() { - return ImmutableBigIntegerOptions.builder(); + return ImmutableBigIntegerValue.builder(); } /** @@ -26,7 +26,7 @@ public static Builder builder() { * * @return the lenient BigInteger options */ - public static BigIntegerOptions lenient(boolean allowDecimal) { + public static BigIntegerValue lenient(boolean allowDecimal) { return builder() .allowedTypes(allowDecimal ? JsonValueTypes.numberLike() : JsonValueTypes.intLike()) .build(); @@ -38,7 +38,7 @@ public static BigIntegerOptions lenient(boolean allowDecimal) { * * @return the standard BigInteger options */ - public static BigIntegerOptions standard(boolean allowDecimal) { + public static BigIntegerValue standard(boolean allowDecimal) { return builder() .allowedTypes(allowDecimal ? JsonValueTypes.numberOrNull() : JsonValueTypes.intOrNull()) .build(); @@ -50,7 +50,7 @@ public static BigIntegerOptions standard(boolean allowDecimal) { * * @return the strict BigInteger options */ - public static BigIntegerOptions strict(boolean allowDecimal) { + public static BigIntegerValue strict(boolean allowDecimal) { return builder() .allowMissing(false) .allowedTypes(allowDecimal ? JsonValueTypes.number() : JsonValueTypes.int_()) @@ -77,7 +77,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptionsSingleValueBase.Builder { + public interface Builder extends ValueSingleValueBase.Builder { } } diff --git a/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java b/extensions/json/src/main/java/io/deephaven/json/BoolValue.java similarity index 86% rename from extensions/json/src/main/java/io/deephaven/json/BoolOptions.java rename to extensions/json/src/main/java/io/deephaven/json/BoolValue.java index e9367530ee7..2b263349616 100644 --- a/extensions/json/src/main/java/io/deephaven/json/BoolOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/BoolValue.java @@ -14,10 +14,10 @@ */ @Immutable @BuildableStyle -public abstract class BoolOptions extends ValueOptionsSingleValueBase { +public abstract class BoolValue extends ValueSingleValueBase { public static Builder builder() { - return ImmutableBoolOptions.builder(); + return ImmutableBoolValue.builder(); } /** @@ -25,7 +25,7 @@ public static Builder builder() { * * @return the lenient bool options */ - public static BoolOptions lenient() { + public static BoolValue lenient() { return builder() .allowedTypes(JsonValueTypes.boolLike()) .build(); @@ -36,7 +36,7 @@ public static BoolOptions lenient() { * * @return the standard bool options */ - public static BoolOptions standard() { + public static BoolValue standard() { return builder().build(); } @@ -45,7 +45,7 @@ public static BoolOptions standard() { * * @return the strict bool options */ - public static BoolOptions strict() { + public static BoolValue strict() { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.bool()) @@ -72,7 +72,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends BuilderSpecial { + public interface Builder extends BuilderSpecial { Builder onNull(boolean onNull); diff --git a/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java b/extensions/json/src/main/java/io/deephaven/json/ByteValue.java similarity index 83% rename from extensions/json/src/main/java/io/deephaven/json/ByteOptions.java rename to extensions/json/src/main/java/io/deephaven/json/ByteValue.java index 90d081cb28a..75b7be59e78 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ByteOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ByteValue.java @@ -14,10 +14,10 @@ */ @Immutable @BuildableStyle -public abstract class ByteOptions extends ValueOptionsSingleValueBase { +public abstract class ByteValue extends ValueSingleValueBase { public static Builder builder() { - return ImmutableByteOptions.builder(); + return ImmutableByteValue.builder(); } /** @@ -25,7 +25,7 @@ public static Builder builder() { * * @return the lenient byte options */ - public static ByteOptions lenient() { + public static ByteValue lenient() { return builder() .allowedTypes(JsonValueTypes.intLike()) .build(); @@ -36,7 +36,7 @@ public static ByteOptions lenient() { * * @return the standard byte options */ - public static ByteOptions standard() { + public static ByteValue standard() { return builder().build(); } @@ -45,7 +45,7 @@ public static ByteOptions standard() { * * @return the strict byte options */ - public static ByteOptions strict() { + public static ByteValue strict() { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.int_()) @@ -72,7 +72,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends BuilderSpecial { + public interface Builder extends BuilderSpecial { Builder onNull(byte onNull); diff --git a/extensions/json/src/main/java/io/deephaven/json/CharOptions.java b/extensions/json/src/main/java/io/deephaven/json/CharValue.java similarity index 86% rename from extensions/json/src/main/java/io/deephaven/json/CharOptions.java rename to extensions/json/src/main/java/io/deephaven/json/CharValue.java index 8f95a9f1c92..85c7a60fbda 100644 --- a/extensions/json/src/main/java/io/deephaven/json/CharOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/CharValue.java @@ -14,10 +14,10 @@ */ @Immutable @BuildableStyle -public abstract class CharOptions extends ValueOptionsSingleValueBase { +public abstract class CharValue extends ValueSingleValueBase { public static Builder builder() { - return ImmutableCharOptions.builder(); + return ImmutableCharValue.builder(); } @@ -26,7 +26,7 @@ public static Builder builder() { * * @return the standard char options */ - public static CharOptions standard() { + public static CharValue standard() { return builder().build(); } @@ -35,7 +35,7 @@ public static CharOptions standard() { * * @return the strict char options */ - public static CharOptions strict() { + public static CharValue strict() { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.string()) @@ -62,7 +62,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends BuilderSpecial { + public interface Builder extends BuilderSpecial { Builder onNull(char onNull); diff --git a/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java b/extensions/json/src/main/java/io/deephaven/json/DoubleValue.java similarity index 86% rename from extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java rename to extensions/json/src/main/java/io/deephaven/json/DoubleValue.java index 5495b6990a7..9c3e0a0f635 100644 --- a/extensions/json/src/main/java/io/deephaven/json/DoubleOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/DoubleValue.java @@ -14,11 +14,11 @@ */ @Immutable @BuildableStyle -public abstract class DoubleOptions extends ValueOptionsSingleValueBase { +public abstract class DoubleValue extends ValueSingleValueBase { public static Builder builder() { - return ImmutableDoubleOptions.builder(); + return ImmutableDoubleValue.builder(); } /** @@ -26,7 +26,7 @@ public static Builder builder() { * * @return the lenient double options */ - public static DoubleOptions lenient() { + public static DoubleValue lenient() { return builder() .allowedTypes(JsonValueTypes.numberLike()) .build(); @@ -37,7 +37,7 @@ public static DoubleOptions lenient() { * * @return the standard double options */ - public static DoubleOptions standard() { + public static DoubleValue standard() { return builder().build(); } @@ -46,7 +46,7 @@ public static DoubleOptions standard() { * * @return the strict double options */ - public static DoubleOptions strict() { + public static DoubleValue strict() { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.number()) @@ -73,7 +73,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends BuilderSpecial { + public interface Builder extends BuilderSpecial { Builder onNull(double onNull); diff --git a/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java b/extensions/json/src/main/java/io/deephaven/json/FloatValue.java similarity index 86% rename from extensions/json/src/main/java/io/deephaven/json/FloatOptions.java rename to extensions/json/src/main/java/io/deephaven/json/FloatValue.java index efcb200bfe8..49b145fca30 100644 --- a/extensions/json/src/main/java/io/deephaven/json/FloatOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/FloatValue.java @@ -14,10 +14,10 @@ */ @Immutable @BuildableStyle -public abstract class FloatOptions extends ValueOptionsSingleValueBase { +public abstract class FloatValue extends ValueSingleValueBase { public static Builder builder() { - return ImmutableFloatOptions.builder(); + return ImmutableFloatValue.builder(); } /** @@ -25,7 +25,7 @@ public static Builder builder() { * * @return the lenient float options */ - public static FloatOptions lenient() { + public static FloatValue lenient() { return builder() .allowedTypes(JsonValueTypes.numberLike()) .build(); @@ -36,7 +36,7 @@ public static FloatOptions lenient() { * * @return the standard float options */ - public static FloatOptions standard() { + public static FloatValue standard() { return builder().build(); } @@ -45,7 +45,7 @@ public static FloatOptions standard() { * * @return the strict float options */ - public static FloatOptions strict() { + public static FloatValue strict() { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.number()) @@ -72,7 +72,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends BuilderSpecial { + public interface Builder extends BuilderSpecial { Builder onNull(float onNull); Builder onMissing(float onMissing); diff --git a/extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java b/extensions/json/src/main/java/io/deephaven/json/InstantNumberValue.java similarity index 87% rename from extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java rename to extensions/json/src/main/java/io/deephaven/json/InstantNumberValue.java index 976628c386c..6e56d3806a1 100644 --- a/extensions/json/src/main/java/io/deephaven/json/InstantNumberOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/InstantNumberValue.java @@ -15,7 +15,7 @@ */ @Immutable @BuildableStyle -public abstract class InstantNumberOptions extends ValueOptionsSingleValueBase { +public abstract class InstantNumberValue extends ValueSingleValueBase { public enum Format { /** @@ -45,7 +45,7 @@ public enum Format { * @param allowDecimal if decimals should be allowed * @return the lenient Instant number options */ - public InstantNumberOptions lenient(boolean allowDecimal) { + public InstantNumberValue lenient(boolean allowDecimal) { return builder() .format(this) .allowedTypes(allowDecimal ? JsonValueTypes.numberLike() : JsonValueTypes.intLike()) @@ -59,7 +59,7 @@ public InstantNumberOptions lenient(boolean allowDecimal) { * @param allowDecimal if decimals should be allowed * @return the standard Instant number options */ - public InstantNumberOptions standard(boolean allowDecimal) { + public InstantNumberValue standard(boolean allowDecimal) { return builder() .format(this) .allowedTypes(allowDecimal ? JsonValueTypes.numberOrNull() : JsonValueTypes.intOrNull()) @@ -73,7 +73,7 @@ public InstantNumberOptions standard(boolean allowDecimal) { * @param allowDecimal if decimals should be allowed * @return the lenient Instant number options */ - public InstantNumberOptions strict(boolean allowDecimal) { + public InstantNumberValue strict(boolean allowDecimal) { return builder() .format(this) .allowMissing(false) @@ -83,7 +83,7 @@ public InstantNumberOptions strict(boolean allowDecimal) { } public static Builder builder() { - return ImmutableInstantNumberOptions.builder(); + return ImmutableInstantNumberValue.builder(); } /** @@ -111,7 +111,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptionsSingleValueBase.Builder { + public interface Builder extends ValueSingleValueBase.Builder { Builder format(Format format); } } diff --git a/extensions/json/src/main/java/io/deephaven/json/InstantOptions.java b/extensions/json/src/main/java/io/deephaven/json/InstantValue.java similarity index 88% rename from extensions/json/src/main/java/io/deephaven/json/InstantOptions.java rename to extensions/json/src/main/java/io/deephaven/json/InstantValue.java index e6619f06c53..19a303a93d2 100644 --- a/extensions/json/src/main/java/io/deephaven/json/InstantOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/InstantValue.java @@ -17,12 +17,12 @@ */ @Immutable @BuildableStyle -public abstract class InstantOptions extends ValueOptionsSingleValueBase { +public abstract class InstantValue extends ValueSingleValueBase { private static final Version VERSION_12 = Version.parse("12"); public static Builder builder() { - return ImmutableInstantOptions.builder(); + return ImmutableInstantValue.builder(); } /** @@ -30,7 +30,7 @@ public static Builder builder() { * * @return the standard Instant options */ - public static InstantOptions standard() { + public static InstantValue standard() { return builder().build(); } @@ -39,7 +39,7 @@ public static InstantOptions standard() { * * @return the strict Instant options */ - public static InstantOptions strict() { + public static InstantValue strict() { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.string()) @@ -84,7 +84,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptionsSingleValueBase.Builder { + public interface Builder extends ValueSingleValueBase.Builder { Builder dateTimeFormatter(DateTimeFormatter formatter); } diff --git a/extensions/json/src/main/java/io/deephaven/json/IntOptions.java b/extensions/json/src/main/java/io/deephaven/json/IntValue.java similarity index 86% rename from extensions/json/src/main/java/io/deephaven/json/IntOptions.java rename to extensions/json/src/main/java/io/deephaven/json/IntValue.java index 199933d2f53..67988a87b74 100644 --- a/extensions/json/src/main/java/io/deephaven/json/IntOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/IntValue.java @@ -14,10 +14,10 @@ */ @Immutable @BuildableStyle -public abstract class IntOptions extends ValueOptionsSingleValueBase { +public abstract class IntValue extends ValueSingleValueBase { public static Builder builder() { - return ImmutableIntOptions.builder(); + return ImmutableIntValue.builder(); } /** @@ -25,7 +25,7 @@ public static Builder builder() { * * @return the lenient int options */ - public static IntOptions lenient() { + public static IntValue lenient() { return builder() .allowedTypes(JsonValueTypes.intLike()) .build(); @@ -36,7 +36,7 @@ public static IntOptions lenient() { * * @return the standard int options */ - public static IntOptions standard() { + public static IntValue standard() { return builder().build(); } @@ -45,7 +45,7 @@ public static IntOptions standard() { * * @return the strict int options */ - public static IntOptions strict() { + public static IntValue strict() { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.int_()) @@ -72,7 +72,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends BuilderSpecial { + public interface Builder extends BuilderSpecial { Builder onNull(int onNull); diff --git a/extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java b/extensions/json/src/main/java/io/deephaven/json/LocalDateValue.java similarity index 83% rename from extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java rename to extensions/json/src/main/java/io/deephaven/json/LocalDateValue.java index e83daafdbe4..d30d7fb58d6 100644 --- a/extensions/json/src/main/java/io/deephaven/json/LocalDateOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/LocalDateValue.java @@ -17,16 +17,16 @@ */ @Immutable @BuildableStyle -public abstract class LocalDateOptions extends ValueOptionsSingleValueBase { +public abstract class LocalDateValue extends ValueSingleValueBase { public static Builder builder() { - return ImmutableLocalDateOptions.builder(); + return ImmutableLocalDateValue.builder(); } - public static LocalDateOptions standard() { + public static LocalDateValue standard() { return builder().build(); } - public static LocalDateOptions strict() { + public static LocalDateValue strict() { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.string()) @@ -65,7 +65,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptionsSingleValueBase.Builder { + public interface Builder extends ValueSingleValueBase.Builder { Builder dateTimeFormatter(DateTimeFormatter formatter); } diff --git a/extensions/json/src/main/java/io/deephaven/json/LongOptions.java b/extensions/json/src/main/java/io/deephaven/json/LongValue.java similarity index 83% rename from extensions/json/src/main/java/io/deephaven/json/LongOptions.java rename to extensions/json/src/main/java/io/deephaven/json/LongValue.java index 9e37f91d0e3..132903d6c4b 100644 --- a/extensions/json/src/main/java/io/deephaven/json/LongOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/LongValue.java @@ -14,10 +14,10 @@ */ @Immutable @BuildableStyle -public abstract class LongOptions extends ValueOptionsSingleValueBase { +public abstract class LongValue extends ValueSingleValueBase { public static Builder builder() { - return ImmutableLongOptions.builder(); + return ImmutableLongValue.builder(); } /** @@ -25,7 +25,7 @@ public static Builder builder() { * * @return the lenient long options */ - public static LongOptions lenient() { + public static LongValue lenient() { return builder() .allowedTypes(JsonValueTypes.intLike()) .build(); @@ -36,7 +36,7 @@ public static LongOptions lenient() { * * @return the standard long options */ - public static LongOptions standard() { + public static LongValue standard() { return builder().build(); } @@ -45,7 +45,7 @@ public static LongOptions standard() { * * @return the strict long options */ - public static LongOptions strict() { + public static LongValue strict() { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.int_()) @@ -72,7 +72,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends BuilderSpecial { + public interface Builder extends BuilderSpecial { Builder onNull(long onNull); diff --git a/extensions/json/src/main/java/io/deephaven/json/ObjectFieldOptions.java b/extensions/json/src/main/java/io/deephaven/json/ObjectField.java similarity index 91% rename from extensions/json/src/main/java/io/deephaven/json/ObjectFieldOptions.java rename to extensions/json/src/main/java/io/deephaven/json/ObjectField.java index 3fd9d9c82da..538f6e731b0 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ObjectFieldOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ObjectField.java @@ -14,10 +14,10 @@ @Immutable @BuildableStyle -public abstract class ObjectFieldOptions { +public abstract class ObjectField { public static Builder builder() { - return ImmutableObjectFieldOptions.builder(); + return ImmutableObjectField.builder(); } /** @@ -27,7 +27,7 @@ public static Builder builder() { * @param options the options * @return the field options */ - public static ObjectFieldOptions of(String name, ValueOptions options) { + public static ObjectField of(String name, Value options) { return builder().name(name).options(options).build(); } @@ -39,7 +39,7 @@ public static ObjectFieldOptions of(String name, ValueOptions options) { /** * The value options. */ - public abstract ValueOptions options(); + public abstract Value options(); /** * The field name aliases. @@ -106,7 +106,7 @@ public enum RepeatedBehavior { public interface Builder { Builder name(String name); - Builder options(ValueOptions options); + Builder options(Value options); Builder addAliases(String element); @@ -120,7 +120,7 @@ public interface Builder { Builder arrayGroup(Object arrayGroup); - ObjectFieldOptions build(); + ObjectField build(); } @Check @@ -147,7 +147,7 @@ final void checkArrayGroup() { if (arrayGroup().isEmpty()) { return; } - if (!(options() instanceof ArrayOptions)) { + if (!(options() instanceof ArrayValue)) { throw new IllegalArgumentException("arrayGroup is only valid with ArrayOptions"); } } diff --git a/extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java b/extensions/json/src/main/java/io/deephaven/json/ObjectKvValue.java similarity index 75% rename from extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java rename to extensions/json/src/main/java/io/deephaven/json/ObjectKvValue.java index 517885210d4..2c24def5cf3 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ObjectKvOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ObjectKvValue.java @@ -26,17 +26,17 @@ */ @Immutable @BuildableStyle -public abstract class ObjectKvOptions extends ValueOptionsRestrictedUniverseBase { +public abstract class ObjectKvValue extends ValueRestrictedUniverseBase { public static Builder builder() { - return ImmutableObjectKvOptions.builder(); + return ImmutableObjectKvValue.builder(); } - public static ObjectKvOptions standard(ValueOptions value) { + public static ObjectKvValue standard(Value value) { return builder().value(value).build(); } - public static ObjectKvOptions strict(ValueOptions value) { + public static ObjectKvValue strict(Value value) { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.object()) @@ -46,17 +46,17 @@ public static ObjectKvOptions strict(ValueOptions value) { /** * The key options which must minimally support {@link JsonValueTypes#STRING}. By default is - * {@link StringOptions#standard()}. + * {@link StringValue#standard()}. */ @Default - public ValueOptions key() { - return StringOptions.standard(); + public Value key() { + return StringValue.standard(); } /** * The value options. */ - public abstract ValueOptions value(); + public abstract Value value(); /** * {@inheritDoc} Must be a subset of {@link JsonValueTypes#objectOrNull()}. By default is @@ -78,11 +78,11 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptions.Builder { + public interface Builder extends Value.Builder { - Builder key(ValueOptions key); + Builder key(Value key); - Builder value(ValueOptions value); + Builder value(Value value); } @Check diff --git a/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java b/extensions/json/src/main/java/io/deephaven/json/ObjectValue.java similarity index 73% rename from extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java rename to extensions/json/src/main/java/io/deephaven/json/ObjectValue.java index 96b0b0184f3..5c0eb3ae670 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ObjectOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ObjectValue.java @@ -4,7 +4,7 @@ package io.deephaven.json; import io.deephaven.annotations.BuildableStyle; -import io.deephaven.json.ObjectFieldOptions.RepeatedBehavior; +import io.deephaven.json.ObjectField.RepeatedBehavior; import org.immutables.value.Value.Check; import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; @@ -32,24 +32,24 @@ */ @Immutable @BuildableStyle -public abstract class ObjectOptions extends ValueOptionsRestrictedUniverseBase { +public abstract class ObjectValue extends ValueRestrictedUniverseBase { public static Builder builder() { - return ImmutableObjectOptions.builder(); + return ImmutableObjectValue.builder(); } /** * The lenient object options. Allows missing, accepts {@link JsonValueTypes#objectOrNull()}, and allows unknown - * fields. The object fields are constructed with {@link ObjectFieldOptions#caseSensitive()} as {@code false} and - * {@link ObjectFieldOptions#repeatedBehavior()} as {@link ObjectFieldOptions.RepeatedBehavior#USE_FIRST}. + * fields. The object fields are constructed with {@link ObjectField#caseSensitive()} as {@code false} and + * {@link ObjectField#repeatedBehavior()} as {@link ObjectField.RepeatedBehavior#USE_FIRST}. * * @param fields the fields * @return the lenient object options */ - public static ObjectOptions lenient(Map fields) { + public static ObjectValue lenient(Map fields) { final Builder builder = builder(); - for (Entry e : fields.entrySet()) { - builder.addFields(ObjectFieldOptions.builder() + for (Entry e : fields.entrySet()) { + builder.addFields(ObjectField.builder() .name(e.getKey()) .options(e.getValue()) .caseSensitive(false) @@ -61,14 +61,14 @@ public static ObjectOptions lenient(Map fields) { /** * The standard object options. Allows missing, accepts {@link JsonValueTypes#objectOrNull()}, and allows unknown - * fields. The object fields are constructed with {@link ObjectFieldOptions#of(String, ValueOptions)}. + * fields. The object fields are constructed with {@link ObjectField#of(String, Value)}. * * @param fields the fields * @return the standard object options */ - public static ObjectOptions standard(Map fields) { + public static ObjectValue standard(Map fields) { final Builder builder = builder(); - for (Entry e : fields.entrySet()) { + for (Entry e : fields.entrySet()) { builder.putFields(e.getKey(), e.getValue()); } return builder.build(); @@ -76,16 +76,16 @@ public static ObjectOptions standard(Map fields) { /** * The strict object options. Disallows missing, accepts {@link JsonValueTypes#object()}, and disallows unknown - * fields. The object fields are constructed with {@link ObjectFieldOptions#of(String, ValueOptions)}. + * fields. The object fields are constructed with {@link ObjectField#of(String, Value)}. * * @param fields the fields * @return the strict object options */ - public static ObjectOptions strict(Map fields) { + public static ObjectValue strict(Map fields) { final Builder builder = builder() .allowMissing(false) .allowedTypes(JsonValueTypes.object()); - for (Entry e : fields.entrySet()) { + for (Entry e : fields.entrySet()) { builder.putFields(e.getKey(), e.getValue()); } return builder.build(); @@ -94,7 +94,7 @@ public static ObjectOptions strict(Map fields) { /** * The fields. */ - public abstract Set fields(); + public abstract Set fields(); /** * If unknown fields are allowed. By default is {@code true}. @@ -125,32 +125,32 @@ public final T walk(Visitor visitor) { } // not extending value options - public interface Builder extends ValueOptions.Builder { + public interface Builder extends Value.Builder { Builder allowUnknownFields(boolean allowUnknownFields); /** * A convenience method, equivalent to {@code addFields(ObjectFieldOptions.of(key, value))}. */ - default Builder putFields(String key, ValueOptions value) { - return addFields(ObjectFieldOptions.of(key, value)); + default Builder putFields(String key, Value value) { + return addFields(ObjectField.of(key, value)); } - Builder addFields(ObjectFieldOptions element); + Builder addFields(ObjectField element); - Builder addFields(ObjectFieldOptions... elements); + Builder addFields(ObjectField... elements); - Builder addAllFields(Iterable elements); + Builder addAllFields(Iterable elements); } @Check final void checkNonOverlapping() { // We need to make sure there is no inter-field overlapping. We will be stricter if _any_ field is // case-insensitive. - final Set keys = fields().stream().allMatch(ObjectFieldOptions::caseSensitive) + final Set keys = fields().stream().allMatch(ObjectField::caseSensitive) ? new HashSet<>() : new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - for (ObjectFieldOptions field : fields()) { + for (ObjectField field : fields()) { if (!keys.add(field.name())) { throw new IllegalArgumentException(String.format("Found overlapping field name '%s'", field.name())); } diff --git a/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java b/extensions/json/src/main/java/io/deephaven/json/ShortValue.java similarity index 86% rename from extensions/json/src/main/java/io/deephaven/json/ShortOptions.java rename to extensions/json/src/main/java/io/deephaven/json/ShortValue.java index a71f1fbd8cc..a3e110051dd 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ShortOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/ShortValue.java @@ -14,10 +14,10 @@ */ @Immutable @BuildableStyle -public abstract class ShortOptions extends ValueOptionsSingleValueBase { +public abstract class ShortValue extends ValueSingleValueBase { public static Builder builder() { - return ImmutableShortOptions.builder(); + return ImmutableShortValue.builder(); } /** @@ -25,7 +25,7 @@ public static Builder builder() { * * @return the lenient short options */ - public static ShortOptions lenient() { + public static ShortValue lenient() { return builder() .allowedTypes(JsonValueTypes.intLike()) .build(); @@ -36,7 +36,7 @@ public static ShortOptions lenient() { * * @return the standard short options */ - public static ShortOptions standard() { + public static ShortValue standard() { return builder().build(); } @@ -45,7 +45,7 @@ public static ShortOptions standard() { * * @return the strict short options */ - public static ShortOptions strict() { + public static ShortValue strict() { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.int_()) @@ -72,7 +72,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends BuilderSpecial { + public interface Builder extends BuilderSpecial { Builder onNull(short onNull); diff --git a/extensions/json/src/main/java/io/deephaven/json/SkipOptions.java b/extensions/json/src/main/java/io/deephaven/json/SkipValue.java similarity index 79% rename from extensions/json/src/main/java/io/deephaven/json/SkipOptions.java rename to extensions/json/src/main/java/io/deephaven/json/SkipValue.java index 9cc2f84e401..be988287712 100644 --- a/extensions/json/src/main/java/io/deephaven/json/SkipOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/SkipValue.java @@ -14,10 +14,10 @@ */ @Immutable @BuildableStyle -public abstract class SkipOptions extends ValueOptions { +public abstract class SkipValue extends Value { public static Builder builder() { - return ImmutableSkipOptions.builder(); + return ImmutableSkipValue.builder(); } /** @@ -25,7 +25,7 @@ public static Builder builder() { * * @return the lenient skip options */ - public static SkipOptions lenient() { + public static SkipValue lenient() { return builder().build(); } @@ -43,7 +43,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptions.Builder { + public interface Builder extends Value.Builder { } } diff --git a/extensions/json/src/main/java/io/deephaven/json/StringOptions.java b/extensions/json/src/main/java/io/deephaven/json/StringValue.java similarity index 86% rename from extensions/json/src/main/java/io/deephaven/json/StringOptions.java rename to extensions/json/src/main/java/io/deephaven/json/StringValue.java index b5da14e9cda..80dffdd63af 100644 --- a/extensions/json/src/main/java/io/deephaven/json/StringOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/StringValue.java @@ -14,10 +14,10 @@ */ @Immutable @BuildableStyle -public abstract class StringOptions extends ValueOptionsSingleValueBase { +public abstract class StringValue extends ValueSingleValueBase { public static Builder builder() { - return ImmutableStringOptions.builder(); + return ImmutableStringValue.builder(); } /** @@ -25,7 +25,7 @@ public static Builder builder() { * * @return the lenient String options */ - public static StringOptions lenient() { + public static StringValue lenient() { return builder() .allowedTypes(JsonValueTypes.stringLike()) .build(); @@ -36,7 +36,7 @@ public static StringOptions lenient() { * * @return the standard String options */ - public static StringOptions standard() { + public static StringValue standard() { return builder().build(); } @@ -45,7 +45,7 @@ public static StringOptions standard() { * * @return the strict String options */ - public static StringOptions strict() { + public static StringValue strict() { return builder() .allowMissing(false) .allowedTypes(JsonValueTypes.string()) @@ -72,7 +72,7 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends BuilderSpecial { + public interface Builder extends BuilderSpecial { Builder onNull(String onNull); diff --git a/extensions/json/src/main/java/io/deephaven/json/TupleOptions.java b/extensions/json/src/main/java/io/deephaven/json/TupleValue.java similarity index 74% rename from extensions/json/src/main/java/io/deephaven/json/TupleOptions.java rename to extensions/json/src/main/java/io/deephaven/json/TupleValue.java index 53c02cd7866..2b394268a72 100644 --- a/extensions/json/src/main/java/io/deephaven/json/TupleOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/TupleValue.java @@ -21,10 +21,10 @@ */ @Immutable @BuildableStyle -public abstract class TupleOptions extends ValueOptionsRestrictedUniverseBase { +public abstract class TupleValue extends ValueRestrictedUniverseBase { public static Builder builder() { - return ImmutableTupleOptions.builder(); + return ImmutableTupleValue.builder(); } /** @@ -33,7 +33,7 @@ public static Builder builder() { * @param values the values * @return the tuple options */ - public static TupleOptions of(ValueOptions... values) { + public static TupleValue of(Value... values) { return of(Arrays.asList(values)); } @@ -43,9 +43,9 @@ public static TupleOptions of(ValueOptions... values) { * @param values the values * @return the tuple options */ - public static TupleOptions of(Iterable values) { + public static TupleValue of(Iterable values) { final Builder builder = builder(); - final Iterator it = values.iterator(); + final Iterator it = values.iterator(); for (int i = 0; it.hasNext(); ++i) { builder.putNamedValues(Integer.toString(i), it.next()); } @@ -55,7 +55,7 @@ public static TupleOptions of(Iterable values) { /** * The named, ordered values of the tuple. */ - public abstract Map namedValues(); + public abstract Map namedValues(); /** * {@inheritDoc} Must be a subset of {@link JsonValueTypes#arrayOrNull()}. By default is @@ -77,14 +77,14 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptions.Builder { + public interface Builder extends Value.Builder { - Builder putNamedValues(String key, ValueOptions value); + Builder putNamedValues(String key, Value value); - Builder putNamedValues(Map.Entry entry); + Builder putNamedValues(Map.Entry entry); - Builder putAllNamedValues(Map entries); + Builder putAllNamedValues(Map entries); - TupleOptions build(); + TupleValue build(); } } diff --git a/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java b/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java similarity index 74% rename from extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java rename to extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java index f3090760aae..4dc08f536c8 100644 --- a/extensions/json/src/main/java/io/deephaven/json/TypedObjectOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java @@ -40,28 +40,28 @@ */ @Immutable @BuildableStyle -public abstract class TypedObjectOptions extends ValueOptionsRestrictedUniverseBase { +public abstract class TypedObjectValue extends ValueRestrictedUniverseBase { public static Builder builder() { - return ImmutableTypedObjectOptions.builder(); + return ImmutableTypedObjectValue.builder(); } /** * Creates a new builder with the {@link #typeFieldName()} set to {@code typeFieldName}, {@link #sharedFields()} - * inferred from {@code objects} based on {@link ObjectFieldOptions} equality, and {@link #objects()} set to + * inferred from {@code objects} based on {@link ObjectField} equality, and {@link #objects()} set to * {@code objects} with the shared fields removed. * * @param typeFieldName the type field name * @param objects the objects * @return the builder */ - public static Builder builder(String typeFieldName, Map objects) { + public static Builder builder(String typeFieldName, Map objects) { final Builder builder = builder().typeFieldName(typeFieldName); - final Set sharedFields = new LinkedHashSet<>(); - final ObjectOptions first = objects.values().iterator().next(); - for (ObjectFieldOptions field : first.fields()) { + final Set sharedFields = new LinkedHashSet<>(); + final ObjectValue first = objects.values().iterator().next(); + for (ObjectField field : first.fields()) { boolean isShared = true; - for (ObjectOptions obj : objects.values()) { + for (ObjectValue obj : objects.values()) { if (!obj.fields().contains(field)) { isShared = false; break; @@ -71,7 +71,7 @@ public static Builder builder(String typeFieldName, Map o sharedFields.add(field); } } - for (Entry e : objects.entrySet()) { + for (Entry e : objects.entrySet()) { builder.putObjects(e.getKey(), without(e.getValue(), sharedFields)); } return builder.addAllSharedFields(sharedFields); @@ -85,7 +85,7 @@ public static Builder builder(String typeFieldName, Map o * @param objects the objects * @return the typed object */ - public static TypedObjectOptions standard(String typeFieldName, Map objects) { + public static TypedObjectValue standard(String typeFieldName, Map objects) { return builder(typeFieldName, objects).build(); } @@ -97,7 +97,7 @@ public static TypedObjectOptions standard(String typeFieldName, Map objects) { + public static TypedObjectValue strict(String typeFieldName, Map objects) { return builder(typeFieldName, objects) .allowUnknownTypes(false) .allowMissing(false) @@ -107,10 +107,10 @@ public static TypedObjectOptions strict(String typeFieldName, Map sharedFields(); + public abstract Set sharedFields(); // canonical name - public abstract Map objects(); + public abstract Map objects(); /** * If unknown fields are allowed. By default is {@code true}. @@ -140,27 +140,27 @@ public final T walk(Visitor visitor) { return visitor.visit(this); } - public interface Builder extends ValueOptions.Builder { + public interface Builder extends Value.Builder { Builder typeFieldName(String typeFieldName); - Builder addSharedFields(ObjectFieldOptions element); + Builder addSharedFields(ObjectField element); - Builder addSharedFields(ObjectFieldOptions... elements); + Builder addSharedFields(ObjectField... elements); - Builder addAllSharedFields(Iterable elements); + Builder addAllSharedFields(Iterable elements); - Builder putObjects(String key, ObjectOptions value); + Builder putObjects(String key, ObjectValue value); Builder allowUnknownTypes(boolean allowUnknownTypes); } - private static ObjectOptions without(ObjectOptions options, Set excludedFields) { - final ObjectOptions.Builder builder = ObjectOptions.builder() + private static ObjectValue without(ObjectValue options, Set excludedFields) { + final ObjectValue.Builder builder = ObjectValue.builder() .allowUnknownFields(options.allowUnknownFields()) .allowMissing(options.allowMissing()) .allowedTypes(options.allowedTypes()); - for (ObjectFieldOptions field : options.fields()) { + for (ObjectField field : options.fields()) { if (!excludedFields.contains(field)) { builder.addFields(field); } diff --git a/extensions/json/src/main/java/io/deephaven/json/ValueOptions.java b/extensions/json/src/main/java/io/deephaven/json/Value.java similarity index 50% rename from extensions/json/src/main/java/io/deephaven/json/ValueOptions.java rename to extensions/json/src/main/java/io/deephaven/json/Value.java index 44907e602ee..f47d373f38e 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ValueOptions.java +++ b/extensions/json/src/main/java/io/deephaven/json/Value.java @@ -14,7 +14,7 @@ /** * The base configuration for JSON values. */ -public abstract class ValueOptions { +public abstract class Value { /** * The allowed types. @@ -30,88 +30,88 @@ public boolean allowMissing() { } /** - * Wraps the allowed values of {@code this} as {@link SkipOptions}. Equivalent to - * {@code SkipOptions.builder().allowMissing(allowMissing()).allowedTypes(allowedTypes()).build()}. + * Wraps the allowed values of {@code this} as {@link SkipValue}. Equivalent to + * {@code SkipValue.builder().allowMissing(allowMissing()).allowedTypes(allowedTypes()).build()}. * * @return this allowed values of this as skip options */ - public final SkipOptions skip() { - return SkipOptions.builder() + public final SkipValue skip() { + return SkipValue.builder() .allowMissing(allowMissing()) .allowedTypes(allowedTypes()) .build(); } /** - * Wraps {@code this} as the value of an {@link ArrayOptions}. Equivalent to {@code ArrayOptions.standard(this)}. + * Wraps {@code this} as the value of an {@link ArrayValue}. Equivalent to {@code ArrayOptions.standard(this)}. * * @return this as the value of an array options - * @see ArrayOptions#standard(ValueOptions) + * @see ArrayValue#standard(Value) */ - public final ArrayOptions array() { - return ArrayOptions.standard(this); + public final ArrayValue array() { + return ArrayValue.standard(this); } /** - * Wraps {@code this} as a singular field of an {@link ObjectOptions}. Equivalent to + * Wraps {@code this} as a singular field of an {@link ObjectValue}. Equivalent to * {@code ObjectOptions.standard(Map.of(name, this))}. * * @param name the field name * @return this as the singular field of an object options - * @see ObjectOptions#standard(Map) + * @see ObjectValue#standard(Map) */ - public final ObjectOptions field(String name) { - return ObjectOptions.standard(Map.of(name, this)); + public final ObjectValue field(String name) { + return ObjectValue.standard(Map.of(name, this)); } public abstract T walk(Visitor visitor); public interface Visitor { - T visit(StringOptions _string); + T visit(StringValue _string); - T visit(BoolOptions _bool); + T visit(BoolValue _bool); - T visit(CharOptions _char); + T visit(CharValue _char); - T visit(ByteOptions _byte); + T visit(ByteValue _byte); - T visit(ShortOptions _short); + T visit(ShortValue _short); - T visit(IntOptions _int); + T visit(IntValue _int); - T visit(LongOptions _long); + T visit(LongValue _long); - T visit(FloatOptions _float); + T visit(FloatValue _float); - T visit(DoubleOptions _double); + T visit(DoubleValue _double); - T visit(ObjectOptions object); + T visit(ObjectValue object); - T visit(ObjectKvOptions objectKv); + T visit(ObjectKvValue objectKv); - T visit(InstantOptions instant); + T visit(InstantValue instant); - T visit(InstantNumberOptions instantNumber); + T visit(InstantNumberValue instantNumber); - T visit(BigIntegerOptions bigInteger); + T visit(BigIntegerValue bigInteger); - T visit(BigDecimalOptions bigDecimal); + T visit(BigDecimalValue bigDecimal); - T visit(SkipOptions skip); + T visit(SkipValue skip); - T visit(TupleOptions tuple); + T visit(TupleValue tuple); - T visit(TypedObjectOptions typedObject); + T visit(TypedObjectValue typedObject); - T visit(LocalDateOptions localDate); + T visit(LocalDateValue localDate); - T visit(ArrayOptions array); + T visit(ArrayValue array); - T visit(AnyOptions any); + T visit(AnyValue any); } - public interface Builder> { + public interface Builder> { B allowMissing(boolean allowMissing); diff --git a/extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java b/extensions/json/src/main/java/io/deephaven/json/ValueRestrictedUniverseBase.java similarity index 81% rename from extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java rename to extensions/json/src/main/java/io/deephaven/json/ValueRestrictedUniverseBase.java index 1af2a70f9ec..07336654ffb 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ValueOptionsRestrictedUniverseBase.java +++ b/extensions/json/src/main/java/io/deephaven/json/ValueRestrictedUniverseBase.java @@ -10,9 +10,9 @@ import java.util.stream.Collectors; /** - * A base {@link ValueOptions} where the implementation has a clearly defined universe. + * A base {@link Value} where the implementation has a clearly defined universe. */ -public abstract class ValueOptionsRestrictedUniverseBase extends ValueOptions { +public abstract class ValueRestrictedUniverseBase extends Value { abstract Set universe(); diff --git a/extensions/json/src/main/java/io/deephaven/json/ValueOptionsSingleValueBase.java b/extensions/json/src/main/java/io/deephaven/json/ValueSingleValueBase.java similarity index 74% rename from extensions/json/src/main/java/io/deephaven/json/ValueOptionsSingleValueBase.java rename to extensions/json/src/main/java/io/deephaven/json/ValueSingleValueBase.java index c85b193a91b..ed3cb0b57cd 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ValueOptionsSingleValueBase.java +++ b/extensions/json/src/main/java/io/deephaven/json/ValueSingleValueBase.java @@ -8,11 +8,11 @@ import java.util.Optional; /** - * A base {@link ValueOptions} where the JSON value represents a single value. + * A base {@link Value} where the JSON value represents a single value. * * @param the value type */ -public abstract class ValueOptionsSingleValueBase extends ValueOptionsRestrictedUniverseBase { +public abstract class ValueSingleValueBase extends ValueRestrictedUniverseBase { /** * The value to use when {@link JsonValueTypes#NULL} is encountered. {@link #allowedTypes()} must contain @@ -25,8 +25,8 @@ public abstract class ValueOptionsSingleValueBase extends ValueOptionsRestric */ public abstract Optional onMissing(); - public interface Builder, B extends Builder> - extends ValueOptions.Builder { + public interface Builder, B extends Builder> + extends Value.Builder { B onNull(T onNull); B onNull(Optional onNull); @@ -36,8 +36,8 @@ public interface Builder, B extends B onMissing(Optional onMissing); } - public interface BuilderSpecial, B extends BuilderSpecial> - extends ValueOptions.Builder { + public interface BuilderSpecial, B extends BuilderSpecial> + extends Value.Builder { // Immutables has special handling for primitive types and some "special" types like String. // This differs from the above Builder where the Optional generic is "? extends T". diff --git a/extensions/json/src/main/java/io/deephaven/json/package-info.java b/extensions/json/src/main/java/io/deephaven/json/package-info.java index 59b5347485c..8f5e6431c3b 100644 --- a/extensions/json/src/main/java/io/deephaven/json/package-info.java +++ b/extensions/json/src/main/java/io/deephaven/json/package-info.java @@ -18,6 +18,6 @@ * "builder" option allows the user fine-grained control over the behavior, and otherwise uses the "standard" options * when the user does not override. * - * @see io.deephaven.json.ValueOptions + * @see io.deephaven.json.Value */ package io.deephaven.json; diff --git a/extensions/json/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java b/extensions/json/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java deleted file mode 100644 index c1d90d8f349..00000000000 --- a/extensions/json/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json; - -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -public class TypedObjectOptionsTest { - - - public static final ObjectOptions TRADE = ObjectOptions.builder() - .putFields("symbol", StringOptions.strict()) - .putFields("price", DoubleOptions.strict()) - .putFields("size", LongOptions.strict()) - .build(); - - public static final ObjectOptions QUOTE = ObjectOptions.builder() - .putFields("symbol", StringOptions.strict()) - .putFields("bid", DoubleOptions.strict()) - .putFields("ask", DoubleOptions.strict()) - .build(); - - public static final TypedObjectOptions COMBINED = TypedObjectOptions.builder() - .typeFieldName("type") - .addSharedFields(ObjectFieldOptions.of("symbol", StringOptions.strict())) - .putObjects("trade", ObjectOptions.builder() - .putFields("price", DoubleOptions.strict()) - .putFields("size", LongOptions.strict()) - .build()) - .putObjects("quote", ObjectOptions.builder() - .putFields("bid", DoubleOptions.strict()) - .putFields("ask", DoubleOptions.strict()) - .build()) - .build(); - - @Test - void builderHelper() { - final TypedObjectOptions combined = - TypedObjectOptions.builder("type", Map.of("quote", QUOTE, "trade", TRADE)).build(); - assertThat(combined).isEqualTo(COMBINED); - } -} diff --git a/extensions/json/src/test/java/io/deephaven/json/TypedObjectValueTest.java b/extensions/json/src/test/java/io/deephaven/json/TypedObjectValueTest.java new file mode 100644 index 00000000000..bec21e4bc7f --- /dev/null +++ b/extensions/json/src/test/java/io/deephaven/json/TypedObjectValueTest.java @@ -0,0 +1,45 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TypedObjectValueTest { + + public static final ObjectValue TRADE = ObjectValue.builder() + .putFields("symbol", StringValue.strict()) + .putFields("price", DoubleValue.strict()) + .putFields("size", LongValue.strict()) + .build(); + + public static final ObjectValue QUOTE = ObjectValue.builder() + .putFields("symbol", StringValue.strict()) + .putFields("bid", DoubleValue.strict()) + .putFields("ask", DoubleValue.strict()) + .build(); + + public static final TypedObjectValue COMBINED = TypedObjectValue.builder() + .typeFieldName("type") + .addSharedFields(ObjectField.of("symbol", StringValue.strict())) + .putObjects("trade", ObjectValue.builder() + .putFields("price", DoubleValue.strict()) + .putFields("size", LongValue.strict()) + .build()) + .putObjects("quote", ObjectValue.builder() + .putFields("bid", DoubleValue.strict()) + .putFields("ask", DoubleValue.strict()) + .build()) + .build(); + + @Test + void builderHelper() { + final TypedObjectValue combined = + TypedObjectValue.builder("type", Map.of("quote", QUOTE, "trade", TRADE)).build(); + assertThat(combined).isEqualTo(COMBINED); + } +} From 5dc5aa8ec3dda7d5b120d0f58e9c434edcd19b36 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Tue, 16 Apr 2024 09:41:28 -0700 Subject: [PATCH 09/53] unmodifiable set --- extensions/json/src/main/java/io/deephaven/json/Value.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/json/src/main/java/io/deephaven/json/Value.java b/extensions/json/src/main/java/io/deephaven/json/Value.java index f47d373f38e..1d842eaaa96 100644 --- a/extensions/json/src/main/java/io/deephaven/json/Value.java +++ b/extensions/json/src/main/java/io/deephaven/json/Value.java @@ -7,6 +7,7 @@ import org.immutables.value.Value.Default; import java.util.Arrays; +import java.util.Collections; import java.util.EnumSet; import java.util.Map; import java.util.Set; @@ -118,7 +119,7 @@ public interface Builder> { B allowedTypes(Set allowedTypes); default B allowedTypes(JsonValueTypes... allowedTypes) { - return allowedTypes(EnumSet.copyOf(Arrays.asList(allowedTypes))); + return allowedTypes(Collections.unmodifiableSet(EnumSet.copyOf(Arrays.asList(allowedTypes)))); } V build(); From 26ff8176b0cdc98a4ceac62aecbaa5684a53fbde Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Tue, 16 Apr 2024 15:30:29 -0700 Subject: [PATCH 10/53] f --- .../io/deephaven/json/jackson/AnyMixin.java | 10 +- .../io/deephaven/json/jackson/ArrayMixin.java | 42 ++- .../json/jackson/BigDecimalMixin.java | 10 +- .../json/jackson/BigIntegerMixin.java | 10 +- .../io/deephaven/json/jackson/BoolMixin.java | 10 +- .../json/jackson/BoolValueProcessor.java | 38 -- .../io/deephaven/json/jackson/ByteMixin.java | 70 ++-- .../json/jackson/ByteValueProcessor.java | 22 +- .../io/deephaven/json/jackson/CharMixin.java | 68 ++-- .../json/jackson/CharValueProcessor.java | 22 +- .../deephaven/json/jackson/ContextAware.java | 13 + .../jackson/ContextAwareDelegateBase.java | 40 ++ .../deephaven/json/jackson/DoubleMixin.java | 68 ++-- .../json/jackson/DoubleValueProcessor.java | 22 +- .../io/deephaven/json/jackson/FloatMixin.java | 68 ++-- .../json/jackson/FloatValueProcessor.java | 22 +- .../deephaven/json/jackson/InstantMixin.java | 9 +- .../json/jackson/InstantNumberMixin.java | 9 +- .../io/deephaven/json/jackson/IntMixin.java | 70 ++-- .../json/jackson/IntValueProcessor.java | 22 +- .../json/jackson/LocalDateMixin.java | 10 +- .../io/deephaven/json/jackson/LongMixin.java | 9 +- .../json/jackson/LongRepeaterImpl.java | 61 ++-- .../json/jackson/LongValueProcessor.java | 30 +- .../java/io/deephaven/json/jackson/Mixin.java | 16 +- .../deephaven/json/jackson/ObjectKvMixin.java | 29 +- .../deephaven/json/jackson/ObjectMixin.java | 343 +++++++++--------- .../json/jackson/ObjectValueProcessor.java | 31 +- .../json/jackson/RepeaterGenericImpl.java | 57 ++- .../json/jackson/RepeaterProcessor.java | 8 +- .../json/jackson/RepeaterProcessorBase.java | 52 ++- .../io/deephaven/json/jackson/ShortMixin.java | 68 ++-- .../json/jackson/ShortValueProcessor.java | 22 +- .../io/deephaven/json/jackson/SkipMixin.java | 68 +++- .../deephaven/json/jackson/StringMixin.java | 10 +- .../io/deephaven/json/jackson/TupleMixin.java | 123 +++---- .../json/jackson/TypedObjectMixin.java | 74 ++-- .../json/jackson/ValueProcessor.java | 3 +- .../json/jackson/ValueProcessorArrayImpl.java | 20 +- .../json/jackson/ValueProcessorKvImpl.java | 26 +- 40 files changed, 910 insertions(+), 795 deletions(-) delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolValueProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAwareDelegateBase.java diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java index 5a31559c0ce..372b497e711 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.TreeNode; -import io.deephaven.chunk.WritableChunk; import io.deephaven.json.AnyValue; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; @@ -36,14 +35,13 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return ObjectValueProcessor.of(out.get(0).asWritableObjectChunk(), ToTreeNode.INSTANCE); + public ValueProcessor processor(String context) { + return new ObjectValueProcessor<>(ToTreeNode.INSTANCE); } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new RepeaterGenericImpl<>(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull, null, - null, ToTreeNode.INSTANCE, TreeNode.class, TreeNode[].class); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new RepeaterGenericImpl<>(ToTreeNode.INSTANCE, allowMissing, allowNull, null, null); } private enum ToTreeNode implements ToObject { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java index acb0ffb681f..338b1538463 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java @@ -14,7 +14,6 @@ import java.io.IOException; import java.lang.reflect.Array; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -46,46 +45,61 @@ public Stream> paths() { } @Override - public ValueProcessor processor(String context, List> out) { - return innerProcessor(out); + public ValueProcessor processor(String context) { + return innerProcessor(); } Stream> elementOutputTypes() { return element().outputTypesImpl(); } - RepeaterProcessor elementRepeater(List> out) { - return element().repeaterProcessor(allowMissing(), allowNull(), out); + RepeaterProcessor elementRepeater() { + return element().repeaterProcessor(allowMissing(), allowNull()); } - private ValueProcessorArrayImpl innerProcessor(List> out) { - return new ValueProcessorArrayImpl(elementRepeater(out)); + private ValueProcessorArrayImpl innerProcessor() { + return new ValueProcessorArrayImpl(elementRepeater()); } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { // For example: // double (element()) // double[] (processor()) // double[][] (arrayProcessor()) - return new ArrayOfArrayProcessor(out, allowMissing, allowNull); + return new ArrayOfArrayProcessor(allowMissing, allowNull); } final class ArrayOfArrayProcessor implements RepeaterProcessor { - private final List> out; private final List> outerTypes; private final boolean allowMissing; private final boolean allowNull; - public ArrayOfArrayProcessor(List> out, boolean allowMissing, boolean allowNull) { - this.out = Objects.requireNonNull(out); + private List> out; + + public ArrayOfArrayProcessor(boolean allowMissing, boolean allowNull) { this.outerTypes = outputTypesImpl().map(NativeArrayType::arrayType).collect(Collectors.toList()); this.allowMissing = allowMissing; this.allowNull = allowNull; } @Override - public Context start(JsonParser parser) throws IOException { + public void setContext(List> out) { + this.out = out; + } + + @Override + public void clearContext() { + out = null; + } + + @Override + public int numColumns() { + return 0; + } + + @Override + public Context context() { return new ArrayOfArrayProcessorContext(); } @@ -157,7 +171,7 @@ private void resize(int index) { innerChunk.close(); innerChunks.set(i, resized); } - innerProcessor = innerProcessor(Collections.unmodifiableList(innerChunks)); + innerProcessor = innerProcessor(); } } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java index 3f2a0c87b95..135d77b11d8 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; import io.deephaven.json.BigDecimalValue; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; @@ -37,8 +36,8 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return ObjectValueProcessor.of(out.get(0).asWritableObjectChunk(), this); + public ValueProcessor processor(String context) { + return new ObjectValueProcessor<>(this); } @Override @@ -62,9 +61,8 @@ public BigDecimal parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new RepeaterGenericImpl<>(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull, null, - null, this, BigDecimal.class, BigDecimal[].class); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null); } private BigDecimal parseFromNumber(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java index 1fc7af4d522..f609cdbac90 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; import io.deephaven.json.BigIntegerValue; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; @@ -37,8 +36,8 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return ObjectValueProcessor.of(out.get(0).asWritableObjectChunk(), this); + public ValueProcessor processor(String context) { + return new ObjectValueProcessor<>(this); } @Override @@ -63,9 +62,8 @@ public BigInteger parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new RepeaterGenericImpl<>(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull, null, - null, this, BigInteger.class, BigInteger[].class); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null); } private BigInteger parseFromInt(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java index 8d2ef9e9241..3bf945459ff 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; import io.deephaven.json.BoolValue; import io.deephaven.json.jackson.ByteValueProcessor.ToByte; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; @@ -37,8 +36,8 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return new ByteValueProcessor(out.get(0).asWritableByteChunk(), this); + public ValueProcessor processor(String context) { + return new ByteValueProcessor(this); } @Override @@ -63,9 +62,8 @@ public byte parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new RepeaterGenericImpl<>(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull, null, - null, new ToBoolean(), Boolean.class, Boolean[].class); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new RepeaterGenericImpl<>(new ToBoolean(), allowMissing, allowNull, null, null); } final class ToBoolean implements ToObject { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolValueProcessor.java deleted file mode 100644 index b30728c62e1..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolValueProcessor.java +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableByteChunk; - -import java.io.IOException; -import java.util.Objects; - -final class BoolValueProcessor implements ValueProcessor { - - private final WritableByteChunk out; - private final ToByte toChar; - - BoolValueProcessor(WritableByteChunk out, ToByte toByte) { - this.out = Objects.requireNonNull(out); - this.toChar = Objects.requireNonNull(toByte); - } - - @Override - public void processCurrentValue(JsonParser parser) throws IOException { - out.add(toChar.parseValue(parser)); - } - - @Override - public void processMissing(JsonParser parser) throws IOException { - out.add(toChar.parseMissing(parser)); - } - - interface ToByte { - - byte parseValue(JsonParser parser) throws IOException; - - byte parseMissing(JsonParser parser) throws IOException; - } -} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java index 1604c4618ef..c3d314885f6 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java @@ -5,8 +5,8 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.base.ArrayUtil; -import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableByteChunk; +import io.deephaven.chunk.sized.SizedByteChunk; import io.deephaven.json.ByteValue; import io.deephaven.json.jackson.ByteValueProcessor.ToByte; import io.deephaven.qst.type.Type; @@ -15,11 +15,8 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; -import java.util.function.Consumer; import java.util.stream.Stream; -import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_BYTE_ARRAY; - final class ByteMixin extends Mixin implements ToByte { public ByteMixin(ByteValue options, JsonFactory factory) { super(factory, options); @@ -41,8 +38,8 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return new ByteValueProcessor(out.get(0).asWritableByteChunk(), this); + public ValueProcessor processor(String context) { + return new ByteValueProcessor(this); } @Override @@ -67,50 +64,37 @@ public byte parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new ByteRepeaterProcessorImpl(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new ByteRepeaterImpl(allowMissing, allowNull); } - final class ByteRepeaterProcessorImpl extends RepeaterProcessorBase { + final class ByteRepeaterImpl extends RepeaterProcessorBase { + private final SizedByteChunk chunk = new SizedByteChunk<>(0); - public ByteRepeaterProcessorImpl(Consumer consumer, boolean allowMissing, boolean allowNull) { - super(consumer, allowMissing, allowNull, null, null); + public ByteRepeaterImpl(boolean allowMissing, boolean allowNull) { + super(allowMissing, allowNull, null, null); } @Override - public ByteArrayContext newContext() { - return new ByteArrayContext(); + public void processElement(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, ByteMixin.this.parseValue(parser)); + chunk.setSize(newSize); } - final class ByteArrayContext extends RepeaterContextBase { - private byte[] arr = EMPTY_BYTE_ARRAY; - private int len = 0; - - @Override - public void processElement(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, ByteMixin.this.parseValue(parser)); - ++len; - } - - @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, ByteMixin.this.parseMissing(parser)); - ++len; - } - - @Override - public byte[] onDone(int length) { - if (length != len) { - throw new IllegalStateException(); - } - return arr.length == len ? arr : Arrays.copyOf(arr, len); - } + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, ByteMixin.this.parseMissing(parser)); + chunk.setSize(newSize); + } + + @Override + public byte[] doneImpl(JsonParser parser, int length) { + final WritableByteChunk chunk = this.chunk.get(); + return Arrays.copyOfRange(chunk.array(), chunk.arrayOffset(), chunk.arrayOffset() + length); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java index b75624b0f75..a64b0c6b74c 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java @@ -5,20 +5,36 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableByteChunk; +import io.deephaven.chunk.WritableChunk; import java.io.IOException; +import java.util.List; import java.util.Objects; final class ByteValueProcessor implements ValueProcessor { - private final WritableByteChunk out; + private WritableByteChunk out; private final ToByte toByte; - ByteValueProcessor(WritableByteChunk out, ToByte toByte) { - this.out = Objects.requireNonNull(out); + ByteValueProcessor(ToByte toByte) { this.toByte = Objects.requireNonNull(toByte); } + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableByteChunk(); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + public int numColumns() { + return 1; + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toByte.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java index f37bd045141..3470cf1a3db 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java @@ -5,8 +5,8 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.base.ArrayUtil; -import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableCharChunk; +import io.deephaven.chunk.sized.SizedCharChunk; import io.deephaven.json.CharValue; import io.deephaven.json.jackson.CharValueProcessor.ToChar; import io.deephaven.qst.type.Type; @@ -15,11 +15,8 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; -import java.util.function.Consumer; import java.util.stream.Stream; -import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_CHAR_ARRAY; - final class CharMixin extends Mixin implements ToChar { public CharMixin(CharValue options, JsonFactory factory) { super(factory, options); @@ -41,8 +38,8 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return new CharValueProcessor(out.get(0).asWritableCharChunk(), this); + public ValueProcessor processor(String context) { + return new CharValueProcessor(this); } @Override @@ -63,50 +60,37 @@ public char parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new CharRepeaterImpl(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new CharRepeaterImpl(allowMissing, allowNull); } final class CharRepeaterImpl extends RepeaterProcessorBase { + private final SizedCharChunk chunk = new SizedCharChunk<>(0); - public CharRepeaterImpl(Consumer consumer, boolean allowMissing, boolean allowNull) { - super(consumer, allowMissing, allowNull, null, null); + public CharRepeaterImpl(boolean allowMissing, boolean allowNull) { + super(allowMissing, allowNull, null, null); } @Override - public CharArrayContext newContext() { - return new CharArrayContext(); + public void processElement(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, CharMixin.this.parseValue(parser)); + chunk.setSize(newSize); } - final class CharArrayContext extends RepeaterContextBase { - private char[] arr = EMPTY_CHAR_ARRAY; - private int len = 0; - - @Override - public void processElement(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, CharMixin.this.parseValue(parser)); - ++len; - } - - @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, CharMixin.this.parseMissing(parser)); - ++len; - } - - @Override - public char[] onDone(int length) { - if (length != len) { - throw new IllegalStateException(); - } - return arr.length == len ? arr : Arrays.copyOf(arr, len); - } + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, CharMixin.this.parseMissing(parser)); + chunk.setSize(newSize); + } + + @Override + public char[] doneImpl(JsonParser parser, int length) { + final WritableCharChunk chunk = this.chunk.get(); + return Arrays.copyOfRange(chunk.array(), chunk.arrayOffset(), chunk.arrayOffset() + length); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java index 83e5dba0855..7656722d3be 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java @@ -5,20 +5,36 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableCharChunk; +import io.deephaven.chunk.WritableChunk; import java.io.IOException; +import java.util.List; import java.util.Objects; final class CharValueProcessor implements ValueProcessor { - private final WritableCharChunk out; + private WritableCharChunk out; private final ToChar toChar; - CharValueProcessor(WritableCharChunk out, ToChar toChar) { - this.out = Objects.requireNonNull(out); + CharValueProcessor(ToChar toChar) { this.toChar = Objects.requireNonNull(toChar); } + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableCharChunk(); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + public int numColumns() { + return 1; + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toChar.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java new file mode 100644 index 00000000000..795ca3d5372 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java @@ -0,0 +1,13 @@ +package io.deephaven.json.jackson; + +import io.deephaven.chunk.WritableChunk; + +import java.util.List; + +interface ContextAware { + void setContext(List> out); + + void clearContext(); + + int numColumns(); +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAwareDelegateBase.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAwareDelegateBase.java new file mode 100644 index 00000000000..d1f4f25baae --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAwareDelegateBase.java @@ -0,0 +1,40 @@ +package io.deephaven.json.jackson; + +import io.deephaven.chunk.WritableChunk; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +abstract class ContextAwareDelegateBase implements ContextAware { + + private final Collection delegates; + private final int numColumns; + + public ContextAwareDelegateBase(Collection delegates) { + this.delegates = Objects.requireNonNull(delegates); + this.numColumns = delegates.stream().mapToInt(ContextAware::numColumns).sum(); + } + + @Override + public final void setContext(List> out) { + int ix = 0; + for (ContextAware delegate : delegates) { + final int numColumns = delegate.numColumns(); + delegate.setContext(out.subList(ix, ix + numColumns)); + ix += numColumns; + } + } + + @Override + public final void clearContext() { + for (ContextAware delegate : delegates) { + delegate.clearContext(); + } + } + + @Override + public final int numColumns() { + return numColumns; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java index 00ee2eebdcc..6d17a53460b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java @@ -5,8 +5,8 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.base.ArrayUtil; -import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableDoubleChunk; +import io.deephaven.chunk.sized.SizedDoubleChunk; import io.deephaven.json.DoubleValue; import io.deephaven.json.jackson.DoubleValueProcessor.ToDouble; import io.deephaven.qst.type.Type; @@ -15,11 +15,8 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; -import java.util.function.Consumer; import java.util.stream.Stream; -import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_DOUBLE_ARRAY; - final class DoubleMixin extends Mixin implements ToDouble { public DoubleMixin(DoubleValue options, JsonFactory factory) { @@ -42,8 +39,8 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return new DoubleValueProcessor(out.get(0).asWritableDoubleChunk(), this); + public ValueProcessor processor(String context) { + return new DoubleValueProcessor(this); } @Override @@ -67,50 +64,37 @@ public double parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new DoubleRepeaterImpl(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new DoubleRepeaterImpl(allowMissing, allowNull); } final class DoubleRepeaterImpl extends RepeaterProcessorBase { + private final SizedDoubleChunk chunk = new SizedDoubleChunk<>(0); - public DoubleRepeaterImpl(Consumer consumer, boolean allowMissing, boolean allowNull) { - super(consumer, allowMissing, allowNull, null, null); + public DoubleRepeaterImpl(boolean allowMissing, boolean allowNull) { + super(allowMissing, allowNull, null, null); } @Override - public DoubleArrayContext newContext() { - return new DoubleArrayContext(); + public void processElement(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, DoubleMixin.this.parseValue(parser)); + chunk.setSize(newSize); } - final class DoubleArrayContext extends RepeaterContextBase { - private double[] arr = EMPTY_DOUBLE_ARRAY; - private int len = 0; - - @Override - public void processElement(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, DoubleMixin.this.parseValue(parser)); - ++len; - } - - @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, DoubleMixin.this.parseMissing(parser)); - ++len; - } - - @Override - public double[] onDone(int length) { - if (length != len) { - throw new IllegalStateException(); - } - return arr.length == len ? arr : Arrays.copyOf(arr, len); - } + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, DoubleMixin.this.parseMissing(parser)); + chunk.setSize(newSize); + } + + @Override + public double[] doneImpl(JsonParser parser, int length) { + final WritableDoubleChunk chunk = this.chunk.get(); + return Arrays.copyOfRange(chunk.array(), chunk.arrayOffset(), chunk.arrayOffset() + length); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java index ecc36e2a957..6536a88241d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java @@ -4,21 +4,37 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableDoubleChunk; import java.io.IOException; +import java.util.List; import java.util.Objects; final class DoubleValueProcessor implements ValueProcessor { - private final WritableDoubleChunk out; + private WritableDoubleChunk out; private final ToDouble toDouble; - DoubleValueProcessor(WritableDoubleChunk out, ToDouble toDouble) { - this.out = Objects.requireNonNull(out); + DoubleValueProcessor(ToDouble toDouble) { this.toDouble = Objects.requireNonNull(toDouble); } + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableDoubleChunk(); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + public int numColumns() { + return 1; + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toDouble.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java index b2eaaf36bec..7ebcbca0981 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java @@ -5,8 +5,8 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.base.ArrayUtil; -import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableFloatChunk; +import io.deephaven.chunk.sized.SizedFloatChunk; import io.deephaven.json.FloatValue; import io.deephaven.json.jackson.FloatValueProcessor.ToFloat; import io.deephaven.qst.type.Type; @@ -15,11 +15,8 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; -import java.util.function.Consumer; import java.util.stream.Stream; -import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_FLOAT_ARRAY; - final class FloatMixin extends Mixin implements ToFloat { public FloatMixin(FloatValue options, JsonFactory factory) { @@ -42,8 +39,8 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return new FloatValueProcessor(out.get(0).asWritableFloatChunk(), this); + public ValueProcessor processor(String context) { + return new FloatValueProcessor(this); } @Override @@ -67,50 +64,37 @@ public float parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new FloatRepeaterImpl(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new FloatRepeaterImpl(allowMissing, allowNull); } final class FloatRepeaterImpl extends RepeaterProcessorBase { + private final SizedFloatChunk chunk = new SizedFloatChunk<>(0); - public FloatRepeaterImpl(Consumer consumer, boolean allowMissing, boolean allowNull) { - super(consumer, allowMissing, allowNull, null, null); + public FloatRepeaterImpl(boolean allowMissing, boolean allowNull) { + super(allowMissing, allowNull, null, null); } @Override - public FloatArrayContext newContext() { - return new FloatArrayContext(); + public void processElement(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, FloatMixin.this.parseValue(parser)); + chunk.setSize(newSize); } - final class FloatArrayContext extends RepeaterContextBase { - private float[] arr = EMPTY_FLOAT_ARRAY; - private int len = 0; - - @Override - public void processElement(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, FloatMixin.this.parseValue(parser)); - ++len; - } - - @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, FloatMixin.this.parseMissing(parser)); - ++len; - } - - @Override - public float[] onDone(int length) { - if (length != len) { - throw new IllegalStateException(); - } - return arr.length == len ? arr : Arrays.copyOf(arr, len); - } + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, FloatMixin.this.parseMissing(parser)); + chunk.setSize(newSize); + } + + @Override + public float[] doneImpl(JsonParser parser, int length) { + final WritableFloatChunk chunk = this.chunk.get(); + return Arrays.copyOfRange(chunk.array(), chunk.arrayOffset(), chunk.arrayOffset() + length); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java index 51f94e07c2d..b096a1087e1 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java @@ -4,21 +4,37 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableFloatChunk; import java.io.IOException; +import java.util.List; import java.util.Objects; final class FloatValueProcessor implements ValueProcessor { - private final WritableFloatChunk out; + private WritableFloatChunk out; private final ToFloat toFloat; - FloatValueProcessor(WritableFloatChunk out, ToFloat toFloat) { - this.out = Objects.requireNonNull(out); + FloatValueProcessor(ToFloat toFloat) { this.toFloat = Objects.requireNonNull(toFloat); } + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableFloatChunk(); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + public int numColumns() { + return 1; + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toFloat.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java index beb0cf0beff..de45f91f79f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; import io.deephaven.json.InstantValue; import io.deephaven.json.jackson.LongValueProcessor.ToLong; import io.deephaven.qst.type.Type; @@ -45,8 +44,8 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return LongValueProcessor.of(out.get(0).asWritableLongChunk(), this); + public ValueProcessor processor(String context) { + return new LongValueProcessor(this); } @Override @@ -67,8 +66,8 @@ public long parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new LongRepeaterImpl(this, allowMissing, allowNull, out.get(0).asWritableObjectChunk()::add); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new LongRepeaterImpl(this, allowMissing, allowNull); } private long parseFromString(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java index a4edc4e9ead..20397c8864e 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; import io.deephaven.json.InstantNumberValue; import io.deephaven.qst.type.Type; import io.deephaven.time.DateTimeUtils; @@ -42,13 +41,13 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return LongValueProcessor.of(out.get(0).asWritableLongChunk(), function()); + public ValueProcessor processor(String context) { + return new LongValueProcessor(function()); } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new LongRepeaterImpl(function(), allowMissing, allowNull, out.get(0).asWritableObjectChunk()::add); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new LongRepeaterImpl(function(), allowMissing, allowNull); } private LongValueProcessor.ToLong function() { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java index d4d15aaf59f..14b0a655f32 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -5,8 +5,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.base.ArrayUtil; -import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableIntChunk; import io.deephaven.chunk.sized.SizedIntChunk; import io.deephaven.json.IntValue; import io.deephaven.json.jackson.IntValueProcessor.ToInt; @@ -16,11 +15,8 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; -import java.util.function.Consumer; import java.util.stream.Stream; -import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_INT_ARRAY; - final class IntMixin extends Mixin implements ToInt { public IntMixin(IntValue options, JsonFactory factory) { @@ -43,8 +39,8 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return new IntValueProcessor(out.get(0).asWritableIntChunk(), this); + public ValueProcessor processor(String context) { + return new IntValueProcessor(this); } @Override @@ -69,53 +65,37 @@ public int parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new IntRepeaterImpl(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new IntRepeaterImpl(allowMissing, allowNull); } final class IntRepeaterImpl extends RepeaterProcessorBase { + private final SizedIntChunk chunk = new SizedIntChunk<>(0); - public IntRepeaterImpl(Consumer consumer, boolean allowMissing, boolean allowNull) { - super(consumer, allowMissing, allowNull, null, null); + public IntRepeaterImpl(boolean allowMissing, boolean allowNull) { + super(allowMissing, allowNull, null, null); } @Override - public IntArrayContext newContext() { - return new IntArrayContext(); + public void processElement(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, IntMixin.this.parseValue(parser)); + chunk.setSize(newSize); } - final class IntArrayContext extends RepeaterContextBase { - - private final SizedIntChunk chunk = new SizedIntChunk<>(); - - private int[] arr = EMPTY_INT_ARRAY; - private int len = 0; - - @Override - public void processElement(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, IntMixin.this.parseValue(parser)); - ++len; - } - - @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, IntMixin.this.parseMissing(parser)); - ++len; - } - - @Override - public int[] onDone(int length) { - if (length != len) { - throw new IllegalStateException(); - } - return arr.length == len ? arr : Arrays.copyOf(arr, len); - } + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, IntMixin.this.parseMissing(parser)); + chunk.setSize(newSize); + } + + @Override + public int[] doneImpl(JsonParser parser, int length) { + final WritableIntChunk chunk = this.chunk.get(); + return Arrays.copyOfRange(chunk.array(), chunk.arrayOffset(), chunk.arrayOffset() + length); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java index e3d1be94cd0..11709949b87 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java @@ -4,21 +4,37 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableIntChunk; import java.io.IOException; +import java.util.List; import java.util.Objects; final class IntValueProcessor implements ValueProcessor { - private final WritableIntChunk out; + private WritableIntChunk out; private final ToInt toInt; - IntValueProcessor(WritableIntChunk out, ToInt toInt) { - this.out = Objects.requireNonNull(out); + IntValueProcessor(ToInt toInt) { this.toInt = Objects.requireNonNull(toInt); } + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableIntChunk(); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + public int numColumns() { + return 1; + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toInt.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java index ed54ca9dc8f..39495e9ac67 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; import io.deephaven.json.LocalDateValue; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; @@ -38,8 +37,8 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return ObjectValueProcessor.of(out.get(0).asWritableObjectChunk(), this); + public ValueProcessor processor(String context) { + return new ObjectValueProcessor<>(this); } @Override @@ -60,9 +59,8 @@ public LocalDate parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new RepeaterGenericImpl<>(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull, null, - null, this, LocalDate.class, LocalDate[].class); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null); } private LocalDate parseFromString(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java index 702b54ada67..d1d6d9d91b3 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; import io.deephaven.json.LongValue; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -36,8 +35,8 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return LongValueProcessor.of(out.get(0).asWritableLongChunk(), this); + public ValueProcessor processor(String context) { + return new LongValueProcessor(this); } @Override @@ -62,8 +61,8 @@ public long parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new LongRepeaterImpl(this, allowMissing, allowNull, out.get(0).asWritableObjectChunk()::add); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new LongRepeaterImpl(this, allowMissing, allowNull); } private long parseFromInt(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java index 87af3d69e14..0e3620a5110 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java @@ -4,59 +4,44 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.base.ArrayUtil; +import io.deephaven.chunk.WritableLongChunk; +import io.deephaven.chunk.sized.SizedLongChunk; import io.deephaven.json.jackson.LongValueProcessor.ToLong; import java.io.IOException; import java.util.Arrays; import java.util.Objects; -import java.util.function.Consumer; - -import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_LONG_ARRAY; final class LongRepeaterImpl extends RepeaterProcessorBase { + private final SizedLongChunk chunk = new SizedLongChunk<>(0); + private final ToLong toLong; - public LongRepeaterImpl(ToLong toLong, boolean allowMissing, boolean allowNull, - Consumer consumer) { - super(consumer, allowMissing, allowNull, null, null); + public LongRepeaterImpl(ToLong toLong, boolean allowMissing, boolean allowNull) { + super(allowMissing, allowNull, null, null); this.toLong = Objects.requireNonNull(toLong); } @Override - public LongArrayContext newContext() { - return new LongArrayContext(); + public void processElement(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, toLong.parseValue(parser)); + chunk.setSize(newSize); + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, toLong.parseMissing(parser)); + chunk.setSize(newSize); } - final class LongArrayContext extends RepeaterContextBase { - private long[] arr = EMPTY_LONG_ARRAY; - private int len = 0; - - @Override - public void processElement(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, toLong.parseValue(parser)); - ++len; - } - - @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, toLong.parseMissing(parser)); - ++len; - } - - @Override - public long[] onDone(int length) { - if (length != len) { - throw new IllegalStateException(); - } - return arr.length == len ? arr : Arrays.copyOf(arr, len); - } + @Override + public long[] doneImpl(JsonParser parser, int length) { + final WritableLongChunk chunk = this.chunk.get(); + return Arrays.copyOfRange(chunk.array(), chunk.arrayOffset(), chunk.arrayOffset() + length); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java index 7e29e663f89..ef07a0d605f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java @@ -4,34 +4,44 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableLongChunk; import java.io.IOException; +import java.util.List; import java.util.Objects; -import java.util.function.LongConsumer; final class LongValueProcessor implements ValueProcessor { + private WritableLongChunk out; + private final ToLong toLong; - public static LongValueProcessor of(WritableLongChunk out, ToLong toLong) { - return new LongValueProcessor(out::add, toLong); + LongValueProcessor(ToLong toLong) { + this.toLong = Objects.requireNonNull(toLong); } - private final LongConsumer out; - private final ToLong toLong; + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableLongChunk(); + } - LongValueProcessor(LongConsumer out, ToLong toLong) { - this.out = Objects.requireNonNull(out); - this.toLong = Objects.requireNonNull(toLong); + @Override + public void clearContext() { + out = null; + } + + @Override + public int numColumns() { + return 1; } @Override public void processCurrentValue(JsonParser parser) throws IOException { - out.accept(toLong.parseValue(parser)); + out.add(toLong.parseValue(parser)); } @Override public void processMissing(JsonParser parser) throws IOException { - out.accept(toLong.parseMissing(parser)); + out.add(toLong.parseMissing(parser)); } interface ToLong { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index ee25c192ff3..3f2b4845276 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -157,9 +157,9 @@ final Mixin mixin(Value options) { return of(options, factory); } - abstract ValueProcessor processor(String context, List> out); + abstract ValueProcessor processor(String context); - abstract RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out); + abstract RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull); abstract int numColumns(); @@ -266,6 +266,13 @@ private abstract class ObjectProcessorJsonValue implements ObjectProcessor protected abstract JsonParser createParser(X in) throws IOException; + // TODO: need to document that this object processor is stateful; need shutdown? + private final ValueProcessor processor; + + public ObjectProcessorJsonValue() { + processor = processor(""); + } + @Override public final int size() { return Mixin.this.size(); @@ -278,15 +285,18 @@ public final List> outputTypes() { @Override public final void processAll(ObjectChunk in, List> out) { + processor.setContext(out); try { processAllImpl(in, out); } catch (IOException e) { throw new UncheckedIOException(e); + } finally { + processor.clearContext(); } } void processAllImpl(ObjectChunk in, List> out) throws IOException { - final ValueProcessor valueProcessor = processor("", out); + final ValueProcessor valueProcessor = processor(""); for (int i = 0; i < in.size(); ++i) { try (final JsonParser parser = createParser(in.get(i))) { ValueProcessor.processFullJson(parser, valueProcessor); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java index 86e108b8e56..5db30a4434b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java @@ -14,7 +14,6 @@ import java.io.IOException; import java.lang.reflect.Array; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -58,45 +57,43 @@ public Stream> paths() { } @Override - public ValueProcessorKvImpl processor(String context, List> out) { - return innerProcessor(out); + public ValueProcessorKvImpl processor(String context) { + return innerProcessor(); } Stream> keyValueOutputTypes() { return Stream.concat(keyMixin().outputTypesImpl(), valueMixin().outputTypesImpl()); } - private ValueProcessorKvImpl innerProcessor(List> out) { + private ValueProcessorKvImpl innerProcessor() { final Mixin key = keyMixin(); final Mixin value = valueMixin(); - final List> keyColumns = out.subList(0, key.numColumns()); - final List> valueColumns = - out.subList(key.numColumns(), key.numColumns() + value.numColumns()); - final RepeaterProcessor kp = key.repeaterProcessor(allowMissing(), allowNull(), keyColumns); - final RepeaterProcessor vp = value.repeaterProcessor(allowMissing(), allowNull(), valueColumns); + final RepeaterProcessor kp = key.repeaterProcessor(allowMissing(), allowNull()); + final RepeaterProcessor vp = value.repeaterProcessor(allowMissing(), allowNull()); return new ValueProcessorKvImpl(kp, vp); } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new RepeaterImpl(out, allowMissing, allowNull); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new RepeaterImpl(allowMissing, allowNull); } final class RepeaterImpl implements RepeaterProcessor { - private final List> out; private final List> outerTypes; private final boolean allowMissing; private final boolean allowNull; - public RepeaterImpl(List> out, boolean allowMissing, boolean allowNull) { - this.out = Objects.requireNonNull(out); + private List> out; + + + public RepeaterImpl(boolean allowMissing, boolean allowNull) { this.outerTypes = outputTypesImpl().map(NativeArrayType::arrayType).collect(Collectors.toList()); this.allowMissing = allowMissing; this.allowNull = allowNull; } @Override - public Context start(JsonParser parser) throws IOException { + public Context context() { return new ContextImpl(); } @@ -168,7 +165,7 @@ private void resize(int index) { innerChunk.close(); innerChunks.set(i, resized); } - innerProcessor = innerProcessor(Collections.unmodifiableList(innerChunks)); + innerProcessor = innerProcessor(); } } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java index 5a9307d172f..0bb28d2fa8b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -6,10 +6,10 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; -import io.deephaven.chunk.WritableChunk; import io.deephaven.json.ObjectField; import io.deephaven.json.ObjectField.RepeatedBehavior; import io.deephaven.json.ObjectValue; +import io.deephaven.json.jackson.RepeaterProcessor.Context; import io.deephaven.qst.type.Type; import java.io.IOException; @@ -56,44 +56,35 @@ public Stream> paths() { } @Override - public ValueProcessor processor(String context, List> out) { - if (out.size() != numColumns()) { - throw new IllegalArgumentException(); - } + public ValueProcessor processor(String context) { final Map processors = new LinkedHashMap<>(options.fields().size()); int ix = 0; for (ObjectField field : options.fields()) { final Mixin opts = mixin(field.options()); final int numTypes = opts.numColumns(); - final ValueProcessor fieldProcessor = - opts.processor(context + "/" + field.name(), out.subList(ix, ix + numTypes)); + final ValueProcessor fieldProcessor = opts.processor(context + "/" + field.name()); processors.put(field, fieldProcessor); ix += numTypes; } - if (ix != out.size()) { + if (ix != numColumns()) { throw new IllegalStateException(); } return processorImpl(processors); } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - if (out.size() != numColumns()) { - throw new IllegalArgumentException(); - } - final Map processors = - new LinkedHashMap<>(options.fields().size()); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + final Map processors = new LinkedHashMap<>(options.fields().size()); int ix = 0; - for (ObjectField field : options.fields()) { final Mixin opts = mixin(field.options()); final int numTypes = opts.numColumns(); final RepeaterProcessor fieldProcessor = - opts.repeaterProcessor(allowMissing, allowNull, out.subList(ix, ix + numTypes)); + opts.repeaterProcessor(allowMissing, allowNull); processors.put(field, fieldProcessor); ix += numTypes; } - if (ix != out.size()) { + if (ix != numColumns()) { throw new IllegalStateException(); } return new ObjectValueRepeaterProcessor(processors); @@ -107,11 +98,13 @@ ObjectValueFieldProcessor processorImpl(Map fields) return new ObjectValueFieldProcessor(fields); } - final class ObjectValueFieldProcessor implements ValueProcessor { + final class ObjectValueFieldProcessor extends ContextAwareDelegateBase implements ValueProcessor, FieldProcessor { private final Map fields; private final Map map; + private final Set visited; ObjectValueFieldProcessor(Map fields) { + super(fields.values()); this.fields = fields; this.map = allCaseSensitive() ? new HashMap<>() @@ -123,6 +116,7 @@ final class ObjectValueFieldProcessor implements ValueProcessor { map.put(alias, field); } } + this.visited = new HashSet<>(fields.size()); } private ObjectField lookupField(String fieldName) { @@ -197,57 +191,67 @@ private void processEmptyObject(JsonParser parser) throws IOException { } private void processObjectFields(JsonParser parser) throws IOException { - final State state = new State(); - FieldProcessor.processFields(parser, state); - state.processMissing(parser); + visited.clear(); + FieldProcessor.processFields(parser, this); + processMissingFields(parser); } - private class State implements FieldProcessor { - // Note: we could try to build a stricter implementation that doesn't use Set; if all of the fields disallow - // missing and the user knows that the data doesn't have any repeated fields, we could use a simple - // counter to ensure all field processors were invoked. - private final Set visited = new HashSet<>(fields.size()); - - @Override - public void process(String fieldName, JsonParser parser) throws IOException { - final ObjectField field = lookupField(fieldName); - if (field == null) { - if (!options.allowUnknownFields()) { - throw new IOException( - String.format("Unexpected field '%s' and allowUnknownFields == false", fieldName)); - } - parser.skipChildren(); - } else if (visited.add(field)) { - // First time seeing field - processor(field).processCurrentValue(parser); - } else if (field.repeatedBehavior() == RepeatedBehavior.USE_FIRST) { - parser.skipChildren(); - } else { + @Override + public void process(String fieldName, JsonParser parser) throws IOException { + final ObjectField field = lookupField(fieldName); + if (field == null) { + if (!options.allowUnknownFields()) { throw new IOException( - String.format("Field '%s' has already been visited and repeatedBehavior == %s", fieldName, - field.repeatedBehavior())); + String.format("Unexpected field '%s' and allowUnknownFields == false", fieldName)); } + parser.skipChildren(); + } else if (visited.add(field)) { + // First time seeing field + processor(field).processCurrentValue(parser); + } else if (field.repeatedBehavior() == RepeatedBehavior.USE_FIRST) { + parser.skipChildren(); + } else { + throw new IOException( + String.format("Field '%s' has already been visited and repeatedBehavior == %s", fieldName, + field.repeatedBehavior())); } + } - void processMissing(JsonParser parser) throws IOException { - if (visited.size() == fields.size()) { - // All fields visited, none missing - return; - } - for (Entry e : fields.entrySet()) { - if (!visited.contains(e.getKey())) { - e.getValue().processMissing(parser); - } + void processMissingFields(JsonParser parser) throws IOException { + if (visited.size() == fields.size()) { + // All fields visited, none missing + return; + } + for (Entry e : fields.entrySet()) { + if (!visited.contains(e.getKey())) { + e.getValue().processMissing(parser); } } } } - final class ObjectValueRepeaterProcessor implements RepeaterProcessor { + final class ObjectValueRepeaterProcessor extends ContextAwareDelegateBase implements RepeaterProcessor, Context { private final Map fields; + private final Map contexts; + private final Map map; public ObjectValueRepeaterProcessor(Map fields) { + super(fields.values()); this.fields = Objects.requireNonNull(fields); + contexts = new LinkedHashMap<>(fields.size()); + for (Entry e : fields.entrySet()) { + contexts.put(e.getKey(), e.getValue().context()); + } + this.map = allCaseSensitive() + ? new HashMap<>() + : new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + for (Entry e : fields.entrySet()) { + final ObjectField field = e.getKey(); + map.put(field.name(), field); + for (String alias : field.aliases()) { + map.put(alias, field); + } + } } private Collection processors() { @@ -255,12 +259,8 @@ private Collection processors() { } @Override - public Context start(JsonParser parser) throws IOException { - final Map contexts = new LinkedHashMap<>(fields.size()); - for (Entry e : fields.entrySet()) { - contexts.put(e.getKey(), e.getValue().start(parser)); - } - return new ObjectArrayContext(contexts); + public Context context() { + return this; } @Override @@ -277,148 +277,137 @@ public void processMissingRepeater(JsonParser parser) throws IOException { } } - final class ObjectArrayContext implements Context { - private final Map contexts; - private final Map map; - - public ObjectArrayContext(Map contexts) { - this.contexts = Objects.requireNonNull(contexts); - this.map = allCaseSensitive() - ? new HashMap<>() - : new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - for (Entry e : fields.entrySet()) { - final ObjectField field = e.getKey(); - map.put(field.name(), field); - for (String alias : field.aliases()) { - map.put(alias, field); - } - } - } - - private ObjectField lookupField(String fieldName) { - final ObjectField field = map.get(fieldName); - if (field == null) { - return null; - } - if (!field.caseSensitive()) { - return field; - } - // Need to handle the case where some fields are case-insensitive, but this one is _not_. - if (field.name().equals(fieldName) || field.aliases().contains(fieldName)) { - return field; - } + private ObjectField lookupField(String fieldName) { + final ObjectField field = map.get(fieldName); + if (field == null) { return null; } + if (!field.caseSensitive()) { + return field; + } + // Need to handle the case where some fields are case-insensitive, but this one is _not_. + if (field.name().equals(fieldName) || field.aliases().contains(fieldName)) { + return field; + } + return null; + } - private Context context(ObjectField o) { - return Objects.requireNonNull(contexts.get(o)); + private Context context(ObjectField o) { + return Objects.requireNonNull(contexts.get(o)); + } + + @Override + public void init(JsonParser parser) throws IOException { + for (Context value : contexts.values()) { + value.init(parser); } + } - @Override - public void processElement(JsonParser parser, int index) throws IOException { - // see - // com.fasterxml.jackson.databind.JsonDeserializer.deserialize(com.fasterxml.jackson.core.JsonParser, - // com.fasterxml.jackson.databind.DeserializationContext) - // for notes on FIELD_NAME - switch (parser.currentToken()) { - case START_OBJECT: - if (parser.nextToken() == JsonToken.END_OBJECT) { - processEmptyObject(parser, index); - return; - } - if (!parser.hasToken(JsonToken.FIELD_NAME)) { - throw new IllegalStateException(); - } - // fall-through - case FIELD_NAME: - processObjectFields(parser, index); - return; - case VALUE_NULL: - processNullObject(parser, index); + @Override + public void processElement(JsonParser parser, int index) throws IOException { + // see + // com.fasterxml.jackson.databind.JsonDeserializer.deserialize(com.fasterxml.jackson.core.JsonParser, + // com.fasterxml.jackson.databind.DeserializationContext) + // for notes on FIELD_NAME + switch (parser.currentToken()) { + case START_OBJECT: + if (parser.nextToken() == JsonToken.END_OBJECT) { + processEmptyObject(parser, index); return; - default: - throw Parsing.mismatch(parser, Object.class); - } + } + if (!parser.hasToken(JsonToken.FIELD_NAME)) { + throw new IllegalStateException(); + } + // fall-through + case FIELD_NAME: + processObjectFields(parser, index); + return; + case VALUE_NULL: + processNullObject(parser, index); + return; + default: + throw Parsing.mismatch(parser, Object.class); } + } - private void processNullObject(JsonParser parser, int ix) throws IOException { - // element is null - // pass-through JsonToken.VALUE_NULL - for (Context context : contexts.values()) { - context.processElement(parser, ix); - } + private void processNullObject(JsonParser parser, int ix) throws IOException { + // element is null + // pass-through JsonToken.VALUE_NULL + for (Context context : contexts.values()) { + context.processElement(parser, ix); } + } - private void processEmptyObject(JsonParser parser, int ix) throws IOException { - // This logic should be equivalent to processObjectFields, but where we know there are no fields - for (Context context : contexts.values()) { - context.processElementMissing(parser, ix); - } + private void processEmptyObject(JsonParser parser, int ix) throws IOException { + // This logic should be equivalent to processObjectFields, but where we know there are no fields + for (Context context : contexts.values()) { + context.processElementMissing(parser, ix); } + } - private void processObjectFields(JsonParser parser, int ix) throws IOException { - final State state = new State(ix); - FieldProcessor.processFields(parser, state); - state.processMissing(parser); - } + private void processObjectFields(JsonParser parser, int ix) throws IOException { + // todo: keep state + final State state = new State(ix); + FieldProcessor.processFields(parser, state); + state.processMissingFields(parser); + } - private class State implements FieldProcessor { - // Note: we could try to build a stricter implementation that doesn't use Set; if the user can guarantee - // that none of the fields will be missing and there won't be any repeated fields, we could use a simple - // counter to ensure all field processors were invoked. - private final Set visited = new HashSet<>(contexts.size()); - private final int ix; + private class State implements FieldProcessor { + // Note: we could try to build a stricter implementation that doesn't use Set; if the user can guarantee + // that none of the fields will be missing and there won't be any repeated fields, we could use a simple + // counter to ensure all field processors were invoked. + private final Set visited = new HashSet<>(contexts.size()); + private final int ix; - public State(int ix) { - this.ix = ix; - } + public State(int ix) { + this.ix = ix; + } - @Override - public void process(String fieldName, JsonParser parser) throws IOException { - final ObjectField field = lookupField(fieldName); - if (field == null) { - if (!options.allowUnknownFields()) { - throw new IOException( - String.format("Unexpected field '%s' and allowUnknownFields == false", fieldName)); - } - parser.skipChildren(); - } else if (visited.add(field)) { - // First time seeing field - context(field).processElement(parser, ix); - } else if (field.repeatedBehavior() == RepeatedBehavior.USE_FIRST) { - parser.skipChildren(); - } else { + @Override + public void process(String fieldName, JsonParser parser) throws IOException { + final ObjectField field = lookupField(fieldName); + if (field == null) { + if (!options.allowUnknownFields()) { throw new IOException( - String.format("Field '%s' has already been visited and repeatedBehavior == %s", - fieldName, field.repeatedBehavior())); + String.format("Unexpected field '%s' and allowUnknownFields == false", fieldName)); } + parser.skipChildren(); + } else if (visited.add(field)) { + // First time seeing field + context(field).processElement(parser, ix); + } else if (field.repeatedBehavior() == RepeatedBehavior.USE_FIRST) { + parser.skipChildren(); + } else { + throw new IOException( + String.format("Field '%s' has already been visited and repeatedBehavior == %s", + fieldName, field.repeatedBehavior())); } + } - void processMissing(JsonParser parser) throws IOException { - if (visited.size() == fields.size()) { - // All fields visited, none missing - return; - } - for (Entry e : contexts.entrySet()) { - if (!visited.contains(e.getKey())) { - e.getValue().processElementMissing(parser, ix); - } + void processMissingFields(JsonParser parser) throws IOException { + if (visited.size() == fields.size()) { + // All fields visited, none missing + return; + } + for (Entry e : contexts.entrySet()) { + if (!visited.contains(e.getKey())) { + e.getValue().processElementMissing(parser, ix); } } } + } - @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { - for (Context context : contexts.values()) { - context.processElementMissing(parser, index); - } + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + for (Context context : contexts.values()) { + context.processElementMissing(parser, index); } + } - @Override - public void done(JsonParser parser, int length) throws IOException { - for (Context context : contexts.values()) { - context.done(parser, length); - } + @Override + public void done(JsonParser parser, int length) throws IOException { + for (Context context : contexts.values()) { + context.done(parser, length); } } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java index 5f094750faa..34d3df6376b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java @@ -4,34 +4,45 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableObjectChunk; import java.io.IOException; +import java.util.List; import java.util.Objects; -import java.util.function.Consumer; final class ObjectValueProcessor implements ValueProcessor { - public static ObjectValueProcessor of(WritableObjectChunk chunk, ToObject toObj) { - return new ObjectValueProcessor<>(chunk::add, toObj); - } - - private final Consumer out; + private WritableObjectChunk out; private final ToObject toObj; - ObjectValueProcessor(Consumer out, ToObject toObj) { - this.out = Objects.requireNonNull(out); + ObjectValueProcessor(ToObject toObj) { this.toObj = Objects.requireNonNull(toObj); } + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableObjectChunk(); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + public int numColumns() { + return 1; + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { - out.accept(toObj.parseValue(parser)); + out.add(toObj.parseValue(parser)); } @Override public void processMissing(JsonParser parser) throws IOException { - out.accept(toObj.parseMissing(parser)); + out.add(toObj.parseMissing(parser)); } interface ToObject { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java index b7b0badb120..671b92dc214 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java @@ -4,56 +4,43 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.base.ArrayUtil; +import io.deephaven.chunk.WritableObjectChunk; +import io.deephaven.chunk.sized.SizedObjectChunk; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import java.io.IOException; -import java.lang.reflect.Array; import java.util.Arrays; import java.util.Objects; -import java.util.function.Consumer; final class RepeaterGenericImpl extends RepeaterProcessorBase { private final ToObject toObject; - private final Class componentClazz; - private final Class arrayClazz; + private final SizedObjectChunk chunk; - public RepeaterGenericImpl(Consumer consumer, boolean allowMissing, boolean allowNull, - T[] onMissing, T[] onNull, ToObject toObject, Class componentClazz, Class arrayClazz) { - super(consumer, allowMissing, allowNull, onMissing, onNull); + public RepeaterGenericImpl(ToObject toObject, boolean allowMissing, boolean allowNull, T[] onMissing, T[] onNull) { + super(allowMissing, allowNull, onMissing, onNull); this.toObject = Objects.requireNonNull(toObject); - this.componentClazz = Objects.requireNonNull(componentClazz); - this.arrayClazz = Objects.requireNonNull(arrayClazz); + chunk = new SizedObjectChunk<>(0); } @Override - public GenericRepeaterContext newContext() { - return new GenericRepeaterContext(); + public void processElement(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, toObject.parseValue(parser)); + chunk.setSize(newSize); } - final class GenericRepeaterContext extends RepeaterContextBase { - @SuppressWarnings("unchecked") - private T[] arr = (T[]) Array.newInstance(componentClazz, 0); - private int len; - - @Override - public void processElement(JsonParser parser, int index) throws IOException { - arr = ArrayUtil.put(arr, index, toObject.parseValue(parser), componentClazz); - ++len; - } - - @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { - arr = ArrayUtil.put(arr, index, toObject.parseMissing(parser), componentClazz); - ++len; - } + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, toObject.parseMissing(parser)); + chunk.setSize(newSize); + } - @Override - public T[] onDone(int length) { - if (length != len) { - throw new IllegalStateException(); - } - return len == arr.length ? arr : Arrays.copyOf(arr, len, arrayClazz); - } + @Override + public T[] doneImpl(JsonParser parser, int length) { + final WritableObjectChunk chunk = this.chunk.get(); + return Arrays.copyOfRange(chunk.array(), chunk.arrayOffset(), chunk.arrayOffset() + length); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java index a2d714120f0..080ac0ca9eb 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java @@ -8,16 +8,18 @@ import java.io.IOException; // generalized for array vs object-kv -interface RepeaterProcessor { - - Context start(JsonParser parser) throws IOException; +interface RepeaterProcessor extends ContextAware { void processNullRepeater(JsonParser parser) throws IOException; void processMissingRepeater(JsonParser parser) throws IOException; + Context context(); + interface Context { + void init(JsonParser parser) throws IOException; + void processElement(JsonParser parser, int index) throws IOException; // While a traditional arrays can't have missing elements, when an object is an array, a field may be missing: diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java index d437474b615..6186f99e376 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java @@ -4,33 +4,49 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableObjectChunk; +import io.deephaven.json.jackson.RepeaterProcessor.Context; import java.io.IOException; -import java.util.Objects; -import java.util.function.Consumer; +import java.util.List; -abstract class RepeaterProcessorBase implements RepeaterProcessor { +abstract class RepeaterProcessorBase implements RepeaterProcessor, Context { - private final Consumer consumer; private final boolean allowMissing; private final boolean allowNull; private final T onMissing; private final T onNull; - public RepeaterProcessorBase(Consumer consumer, boolean allowMissing, boolean allowNull, T onMissing, - T onNull) { - this.consumer = Objects.requireNonNull(consumer); + private WritableObjectChunk out; + + public RepeaterProcessorBase(boolean allowMissing, boolean allowNull, T onMissing, T onNull) { this.onMissing = onMissing; this.onNull = onNull; this.allowNull = allowNull; this.allowMissing = allowMissing; } - public abstract RepeaterContextBase newContext(); + public abstract T doneImpl(JsonParser parser, int length); + + @Override + public final void setContext(List> out) { + this.out = out.get(0).asWritableObjectChunk(); + } + + @Override + public final void clearContext() { + out = null; + } + + @Override + public final int numColumns() { + return 1; + } @Override - public final Context start(JsonParser parser) throws IOException { - return newContext(); + public final Context context() { + return this; } @Override @@ -38,7 +54,7 @@ public final void processMissingRepeater(JsonParser parser) throws IOException { if (!allowMissing) { throw Parsing.mismatchMissing(parser, void.class); } - consumer.accept(onMissing); + out.add(onMissing); } @Override @@ -46,16 +62,16 @@ public final void processNullRepeater(JsonParser parser) throws IOException { if (!allowNull) { throw Parsing.mismatch(parser, void.class); } - consumer.accept(onNull); + out.add(onNull); } - public abstract class RepeaterContextBase implements Context { + @Override + public final void init(JsonParser parser) throws IOException { - @Override - public final void done(JsonParser parser, int length) throws IOException { - consumer.accept(onDone(length)); - } + } - public abstract T onDone(int length); + @Override + public final void done(JsonParser parser, int length) throws IOException { + out.add(doneImpl(parser, length)); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java index 221a42ee0a2..6e039cedab0 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java @@ -5,8 +5,8 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.base.ArrayUtil; -import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableShortChunk; +import io.deephaven.chunk.sized.SizedShortChunk; import io.deephaven.json.ShortValue; import io.deephaven.json.jackson.ShortValueProcessor.ToShort; import io.deephaven.qst.type.Type; @@ -15,11 +15,8 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; -import java.util.function.Consumer; import java.util.stream.Stream; -import static io.deephaven.util.type.ArrayTypeUtils.EMPTY_SHORT_ARRAY; - final class ShortMixin extends Mixin implements ToShort { public ShortMixin(ShortValue options, JsonFactory factory) { super(factory, options); @@ -41,8 +38,8 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return new ShortValueProcessor(out.get(0).asWritableShortChunk(), this); + public ValueProcessor processor(String context) { + return new ShortValueProcessor(this); } @Override @@ -67,50 +64,37 @@ public short parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new ShortRepeaterImpl(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new ShortRepeaterImpl(allowMissing, allowNull); } final class ShortRepeaterImpl extends RepeaterProcessorBase { + private final SizedShortChunk chunk = new SizedShortChunk<>(0); - public ShortRepeaterImpl(Consumer consumer, boolean allowMissing, boolean allowNull) { - super(consumer, allowMissing, allowNull, null, null); + public ShortRepeaterImpl(boolean allowMissing, boolean allowNull) { + super(allowMissing, allowNull, null, null); } @Override - public ShortArrayContext newContext() { - return new ShortArrayContext(); + public void processElement(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, ShortMixin.this.parseValue(parser)); + chunk.setSize(newSize); } - final class ShortArrayContext extends RepeaterContextBase { - private short[] arr = EMPTY_SHORT_ARRAY; - private int len = 0; - - @Override - public void processElement(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, ShortMixin.this.parseValue(parser)); - ++len; - } - - @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { - if (index != len) { - throw new IllegalStateException(); - } - arr = ArrayUtil.put(arr, len, ShortMixin.this.parseMissing(parser)); - ++len; - } - - @Override - public short[] onDone(int length) { - if (length != len) { - throw new IllegalStateException(); - } - return arr.length == len ? arr : Arrays.copyOf(arr, len); - } + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + chunk.set(index, ShortMixin.this.parseMissing(parser)); + chunk.setSize(newSize); + } + + @Override + public short[] doneImpl(JsonParser parser, int length) { + final WritableShortChunk chunk = this.chunk.get(); + return Arrays.copyOfRange(chunk.array(), chunk.arrayOffset(), chunk.arrayOffset() + length); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java index 48287655f4d..b6ce4d611fd 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java @@ -4,21 +4,37 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableShortChunk; import java.io.IOException; +import java.util.List; import java.util.Objects; final class ShortValueProcessor implements ValueProcessor { - private final WritableShortChunk out; + private WritableShortChunk out; private final ToShort toShort; - ShortValueProcessor(WritableShortChunk out, ToShort toShort) { - this.out = Objects.requireNonNull(out); + ShortValueProcessor(ToShort toShort) { this.toShort = Objects.requireNonNull(toShort); } + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableShortChunk(); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + public int numColumns() { + return 1; + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toShort.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java index 421e1a2eec1..ee7376e7ef7 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java @@ -7,13 +7,14 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; import io.deephaven.json.SkipValue; +import io.deephaven.json.jackson.RepeaterProcessor.Context; import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; import java.util.stream.Stream; -final class SkipMixin extends Mixin implements ValueProcessor, RepeaterProcessor.Context { +final class SkipMixin extends Mixin implements ValueProcessor { public SkipMixin(SkipValue options, JsonFactory factory) { super(factory, options); @@ -35,12 +36,22 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { + public ValueProcessor processor(String context) { return this; } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + public void setContext(List> out) { + + } + + @Override + public void clearContext() { + + } + + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { return new SkipArray(allowMissing, allowNull); } @@ -98,7 +109,7 @@ public void processMissing(JsonParser parser) throws IOException { } } - private final class SkipArray implements RepeaterProcessor { + private final class SkipArray implements RepeaterProcessor, Context { private final boolean allowMissing; private final boolean allowNull; @@ -108,8 +119,8 @@ public SkipArray(boolean allowMissing, boolean allowNull) { } @Override - public Context start(JsonParser parser) throws IOException { - return SkipMixin.this; + public Context context() { + return this; } @Override @@ -125,20 +136,41 @@ public void processMissingRepeater(JsonParser parser) throws IOException { throw Parsing.mismatch(parser, void.class); } } - } - @Override - public void processElement(JsonParser parser, int index) throws IOException { - processCurrentValue(parser); - } + @Override + public void setContext(List> out) { - @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { - processMissing(parser); - } + } - @Override - public void done(JsonParser parser, int length) throws IOException { - // no-op + @Override + public void clearContext() { + + } + + @Override + public int numColumns() { + return 0; + } + + @Override + public void init(JsonParser parser) throws IOException { + + } + + @Override + public void processElement(JsonParser parser, int index) throws IOException { + processCurrentValue(parser); + + } + + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + processMissing(parser); + } + + @Override + public void done(JsonParser parser, int length) throws IOException { + + } } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java index ba1daa3e802..d0cd30e4034 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; import io.deephaven.json.StringValue; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; @@ -36,8 +35,8 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - return ObjectValueProcessor.of(out.get(0).asWritableObjectChunk(), this); + public ValueProcessor processor(String context) { + return new ObjectValueProcessor<>(this); } @Override @@ -65,9 +64,8 @@ public String parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - return new RepeaterGenericImpl<>(out.get(0).asWritableObjectChunk()::add, allowMissing, allowNull, null, - null, this, String.class, String[].class); + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null); } private String parseFromString(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java index 43b84ba2c0e..1093323bc94 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java @@ -6,9 +6,9 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; -import io.deephaven.chunk.WritableChunk; import io.deephaven.json.TupleValue; import io.deephaven.json.Value; +import io.deephaven.json.jackson.RepeaterProcessor.Context; import io.deephaven.qst.type.Type; import java.io.IOException; @@ -50,51 +50,45 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - if (out.size() != numColumns()) { - throw new IllegalArgumentException(); - } + public ValueProcessor processor(String context) { final List processors = new ArrayList<>(options.namedValues().size()); int ix = 0; for (Entry e : options.namedValues().entrySet()) { final Mixin mixin = mixin(e.getValue()); final int numTypes = mixin.numColumns(); - final ValueProcessor processor = - mixin.processor(context + "[" + e.getKey() + "]", out.subList(ix, ix + numTypes)); + final ValueProcessor processor = mixin.processor(context + "[" + e.getKey() + "]"); processors.add(processor); ix += numTypes; } - if (ix != out.size()) { + if (ix != numColumns()) { throw new IllegalStateException(); } return new TupleProcessor(processors); } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { - if (out.size() != numColumns()) { - throw new IllegalArgumentException(); - } + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { final List processors = new ArrayList<>(options.namedValues().size()); int ix = 0; for (Entry e : options.namedValues().entrySet()) { final Mixin mixin = mixin(e.getValue()); final int numTypes = mixin.numColumns(); final RepeaterProcessor processor = - mixin.repeaterProcessor(allowMissing, allowNull, out.subList(ix, ix + numTypes)); + mixin.repeaterProcessor(allowMissing, allowNull); processors.add(processor); ix += numTypes; } - if (ix != out.size()) { + if (ix != numColumns()) { throw new IllegalStateException(); } return new TupleArrayProcessor(processors); } - private class TupleProcessor implements ValueProcessor { + private class TupleProcessor extends ContextAwareDelegateBase implements ValueProcessor { private final List values; public TupleProcessor(List values) { + super(values); this.values = Objects.requireNonNull(values); } @@ -150,20 +144,22 @@ private void parseFromMissing(JsonParser parser) throws IOException { } } - final class TupleArrayProcessor implements RepeaterProcessor { + final class TupleArrayProcessor extends ContextAwareDelegateBase implements RepeaterProcessor, Context { private final List values; + private final List contexts; public TupleArrayProcessor(List values) { + super(values); this.values = Objects.requireNonNull(values); + this.contexts = new ArrayList<>(values.size()); + for (RepeaterProcessor value : values) { + contexts.add(value.context()); + } } @Override - public TupleArrayContext start(JsonParser parser) throws IOException { - final List contexts = new ArrayList<>(values.size()); - for (RepeaterProcessor value : values) { - contexts.add(value.start(parser)); - } - return new TupleArrayContext(contexts); + public Context context() { + return this; } @Override @@ -186,59 +182,58 @@ public void processMissingRepeater(JsonParser parser) throws IOException { } } - final class TupleArrayContext implements Context { - private final List contexts; - - public TupleArrayContext(List contexts) { - this.contexts = Objects.requireNonNull(contexts); + @Override + public void init(JsonParser parser) throws IOException { + for (Context context : contexts) { + context.init(parser); } + } - @Override - public void processElement(JsonParser parser, int index) throws IOException { - switch (parser.currentToken()) { - case START_ARRAY: - processTuple(parser, index); - return; - case VALUE_NULL: - processNullTuple(parser, index); - return; - default: - throw Parsing.mismatch(parser, Object.class); - } + @Override + public void processElement(JsonParser parser, int index) throws IOException { + switch (parser.currentToken()) { + case START_ARRAY: + processTuple(parser, index); + return; + case VALUE_NULL: + processNullTuple(parser, index); + return; + default: + throw Parsing.mismatch(parser, Object.class); } + } - private void processTuple(JsonParser parser, int index) throws IOException { - for (Context context : contexts) { - parser.nextToken(); - context.processElement(parser, index); - } + private void processTuple(JsonParser parser, int index) throws IOException { + for (Context context : contexts) { parser.nextToken(); - assertCurrentToken(parser, JsonToken.END_ARRAY); + context.processElement(parser, index); } + parser.nextToken(); + assertCurrentToken(parser, JsonToken.END_ARRAY); + } - private void processNullTuple(JsonParser parser, int index) throws IOException { - // Note: we are treating a null tuple the same as a tuple of null objects - // null ~= [null, ..., null] - for (Context context : contexts) { - context.processElement(parser, index); - } + private void processNullTuple(JsonParser parser, int index) throws IOException { + // Note: we are treating a null tuple the same as a tuple of null objects + // null ~= [null, ..., null] + for (Context context : contexts) { + context.processElement(parser, index); } + } - @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { - // Note: we are treating a missing tuple the same as a tuple of missing objects (which, is technically - // impossible w/ native json, but it's the semantics we are exposing). - // ~= [, ..., ] - for (Context context : contexts) { - context.processElementMissing(parser, index); - } + @Override + public void processElementMissing(JsonParser parser, int index) throws IOException { + // Note: we are treating a missing tuple the same as a tuple of missing objects (which, is technically + // impossible w/ native json, but it's the semantics we are exposing). + // ~= [, ..., ] + for (Context context : contexts) { + context.processElementMissing(parser, index); } + } - @Override - public void done(JsonParser parser, int length) throws IOException { - for (Context context : contexts) { - context.done(parser, length); - } + @Override + public void done(JsonParser parser, int length) throws IOException { + for (Context context : contexts) { + context.done(parser, length); } } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java index 23742ffb879..b726a88817d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java @@ -68,30 +68,20 @@ public Stream> outputTypesImpl() { } @Override - public ValueProcessor processor(String context, List> out) { - final WritableObjectChunk typeOut = out.get(0).asWritableObjectChunk(); - final List> sharedFields = out.subList(1, 1 + options.sharedFields().size()); + public ValueProcessor processor(String context) { final Map processors = new LinkedHashMap<>(options.objects().size()); - int outIx = 1 + sharedFields.size(); for (Entry e : options.objects().entrySet()) { final String type = e.getKey(); final ObjectValue specificOpts = e.getValue(); - final int numSpecificFields = mixin(specificOpts).numColumns(); - final List> specificChunks = out.subList(outIx, outIx + numSpecificFields); - final List> allChunks = concat(sharedFields, specificChunks); final ObjectValue combinedObject = combinedObject(specificOpts); - final ValueProcessor processor = mixin(combinedObject).processor(context + "[" + type + "]", allChunks); - processors.put(type, new Processor(processor, specificChunks)); - outIx += numSpecificFields; + final ValueProcessor processor = mixin(combinedObject).processor(context + "[" + type + "]"); + processors.put(type, new Processor(processor)); } - if (outIx != out.size()) { - throw new IllegalStateException(); - } - return new DiscriminatedProcessor(typeOut, processors); + return new DiscriminatedProcessor(processors); } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull, List> out) { + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { throw new UnsupportedOperationException(); } @@ -143,11 +133,20 @@ private String parseTypeField(JsonParser parser) throws IOException { private static class Processor { private final ValueProcessor valueProcessor; - private final List> specificChunks; + private List> specificOut; - public Processor(ValueProcessor valueProcessor, List> specificChunks) { + public Processor(ValueProcessor valueProcessor) { this.valueProcessor = Objects.requireNonNull(valueProcessor); - this.specificChunks = Objects.requireNonNull(specificChunks); + } + + void setContext(List> sharedOut, List> specifiedOut) { + this.specificOut = Objects.requireNonNull(specifiedOut); + valueProcessor.setContext(concat(sharedOut, specifiedOut)); + } + + void clearContext() { + valueProcessor.clearContext(); + specificOut = null; } ValueProcessor processor() { @@ -155,7 +154,8 @@ ValueProcessor processor() { } void notApplicable() { - for (WritableChunk wc : specificChunks) { + // only skip specific fields + for (WritableChunk wc : specificOut) { final int size = wc.size(); wc.fillWithNullValue(size, 1); wc.setSize(size + 1); @@ -165,12 +165,42 @@ void notApplicable() { private class DiscriminatedProcessor implements ValueProcessor { - private final WritableObjectChunk typeOut; + private WritableObjectChunk typeOut; + private List> sharedFields; private final Map processors; + private final int numColumns; - public DiscriminatedProcessor(WritableObjectChunk typeOut, Map processors) { - this.typeOut = Objects.requireNonNull(typeOut); + public DiscriminatedProcessor(Map processors) { this.processors = Objects.requireNonNull(processors); + this.numColumns = TypedObjectMixin.this.numColumns(); + } + + @Override + public void setContext(List> out) { + typeOut = out.get(0).asWritableObjectChunk(); + sharedFields = out.subList(1, 1 + options.sharedFields().size()); + int outIx = 1 + sharedFields.size(); + for (Processor value : processors.values()) { + final int numColumns = value.processor().numColumns(); + final int numSpecificFields = numColumns - options.sharedFields().size(); + final List> specificChunks = out.subList(outIx, outIx + numSpecificFields); + value.setContext(sharedFields, specificChunks); + outIx += numSpecificFields; + } + } + + @Override + public void clearContext() { + typeOut = null; + sharedFields = null; + for (Processor value : processors.values()) { + value.clearContext(); + } + } + + @Override + public int numColumns() { + return numColumns; } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessor.java index a292be173f4..6f5a37b2957 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessor.java @@ -11,7 +11,7 @@ import static io.deephaven.json.jackson.Parsing.assertNextToken; import static io.deephaven.json.jackson.Parsing.assertNoCurrentToken; -interface ValueProcessor { +interface ValueProcessor extends ContextAware { static void processFullJson(JsonParser parser, ValueProcessor processor) throws IOException { assertNoCurrentToken(parser); @@ -33,6 +33,7 @@ static void processFullJson(JsonParser parser, ValueProcessor processor) throws // com.fasterxml.jackson.databind.DeserializationContext), // but not functional (want to destructure efficiently) + /** * Called when the JSON value is present; the current token should be one of {@link JsonToken#START_OBJECT}, * {@link JsonToken#START_ARRAY}, {@link JsonToken#VALUE_STRING}, {@link JsonToken#VALUE_NUMBER_INT}, diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorArrayImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorArrayImpl.java index 7f7fe6a6a36..d2577e896bf 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorArrayImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorArrayImpl.java @@ -5,9 +5,11 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; +import io.deephaven.chunk.WritableChunk; import io.deephaven.json.jackson.RepeaterProcessor.Context; import java.io.IOException; +import java.util.List; import java.util.Objects; final class ValueProcessorArrayImpl implements ValueProcessor { @@ -17,7 +19,8 @@ static void processArray2( RepeaterProcessor elementProcessor, Runnable processElementCallback) throws IOException { Parsing.assertCurrentToken(parser, JsonToken.START_ARRAY); - final Context context = elementProcessor.start(parser); + final Context context = elementProcessor.context(); + context.init(parser); parser.nextToken(); int ix; for (ix = 0; !parser.hasToken(JsonToken.END_ARRAY); ++ix) { @@ -36,6 +39,21 @@ static void processArray2( this.elementProcessor = Objects.requireNonNull(elementProcessor); } + @Override + public void setContext(List> out) { + elementProcessor.setContext(out); + } + + @Override + public void clearContext() { + elementProcessor.clearContext(); + } + + @Override + public int numColumns() { + return elementProcessor.numColumns(); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { if (parser.hasToken(JsonToken.VALUE_NULL)) { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java index 34528d9f070..0a32eff4d9f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java @@ -5,9 +5,11 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; +import io.deephaven.chunk.WritableChunk; import io.deephaven.json.jackson.RepeaterProcessor.Context; import java.io.IOException; +import java.util.List; import java.util.Objects; final class ValueProcessorKvImpl implements ValueProcessor { @@ -18,8 +20,10 @@ public static void processKeyValues2( RepeaterProcessor valueProcessor, Runnable processElementCallback) throws IOException { Parsing.assertCurrentToken(parser, JsonToken.START_OBJECT); - final Context keyContext = keyProcessor.start(parser); - final Context valueContext = valueProcessor.start(parser); + final Context keyContext = keyProcessor.context(); + final Context valueContext = valueProcessor.context(); + keyContext.init(parser); + valueContext.init(parser); parser.nextToken(); int ix; for (ix = 0; !parser.hasToken(JsonToken.END_OBJECT); ++ix) { @@ -44,6 +48,24 @@ public ValueProcessorKvImpl(RepeaterProcessor keyProcessor, RepeaterProcessor va this.valueProcessor = Objects.requireNonNull(valueProcessor); } + @Override + public void setContext(List> out) { + final int keySize = keyProcessor.numColumns(); + keyProcessor.setContext(out.subList(0, keySize)); + valueProcessor.setContext(out.subList(keySize, keySize + valueProcessor.numColumns())); + } + + @Override + public void clearContext() { + keyProcessor.clearContext(); + valueProcessor.clearContext(); + } + + @Override + public int numColumns() { + return keyProcessor.numColumns() + valueProcessor.numColumns(); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { if (parser.hasToken(JsonToken.VALUE_NULL)) { From 14eadbac13c3d21fada65f3f3e7aaf84f7b6bccf Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Tue, 16 Apr 2024 15:57:08 -0700 Subject: [PATCH 11/53] f --- .../io/deephaven/json/jackson/ArrayMixin.java | 7 +- .../java/io/deephaven/json/jackson/Mixin.java | 8 +- .../deephaven/json/jackson/ObjectKvMixin.java | 185 +++++++++--------- .../io/deephaven/json/CharOptionsTest.java | 1 - 4 files changed, 102 insertions(+), 99 deletions(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java index 338b1538463..22c9527a968 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java @@ -95,7 +95,7 @@ public void clearContext() { @Override public int numColumns() { - return 0; + return ArrayMixin.this.numColumns(); } @Override @@ -136,6 +136,11 @@ public ArrayOfArrayProcessorContext() { .collect(Collectors.toList()); } + @Override + public void init(JsonParser parser) throws IOException { + + } + @Override public void processElement(JsonParser parser, int index) throws IOException { if (isPow2(index)) { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index 3f2b4845276..0dd87f5af0b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -266,7 +266,6 @@ private abstract class ObjectProcessorJsonValue implements ObjectProcessor protected abstract JsonParser createParser(X in) throws IOException; - // TODO: need to document that this object processor is stateful; need shutdown? private final ValueProcessor processor; public ObjectProcessorJsonValue() { @@ -287,7 +286,7 @@ public final List> outputTypes() { public final void processAll(ObjectChunk in, List> out) { processor.setContext(out); try { - processAllImpl(in, out); + processAllImpl(in); } catch (IOException e) { throw new UncheckedIOException(e); } finally { @@ -295,11 +294,10 @@ public final void processAll(ObjectChunk in, List in, List> out) throws IOException { - final ValueProcessor valueProcessor = processor(""); + void processAllImpl(ObjectChunk in) throws IOException { for (int i = 0; i < in.size(); ++i) { try (final JsonParser parser = createParser(in.get(i))) { - ValueProcessor.processFullJson(parser, valueProcessor); + ValueProcessor.processFullJson(parser, processor); } } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java index 5db30a4434b..90c3d143491 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java @@ -75,100 +75,101 @@ private ValueProcessorKvImpl innerProcessor() { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterImpl(allowMissing, allowNull); + throw new UnsupportedOperationException("todo"); + //return new RepeaterImpl(allowMissing, allowNull); } - final class RepeaterImpl implements RepeaterProcessor { - private final List> outerTypes; - private final boolean allowMissing; - private final boolean allowNull; - - private List> out; - - - public RepeaterImpl(boolean allowMissing, boolean allowNull) { - this.outerTypes = outputTypesImpl().map(NativeArrayType::arrayType).collect(Collectors.toList()); - this.allowMissing = allowMissing; - this.allowNull = allowNull; - } - - @Override - public Context context() { - return new ContextImpl(); - } - - @Override - public void processNullRepeater(JsonParser parser) throws IOException { - if (!allowNull) { - throw Parsing.mismatch(parser, Object.class); - } - for (WritableChunk writableChunk : out) { - writableChunk.asWritableObjectChunk().add(null); - } - } - - @Override - public void processMissingRepeater(JsonParser parser) throws IOException { - if (!allowMissing) { - throw Parsing.mismatchMissing(parser, Object.class); - } - for (WritableChunk writableChunk : out) { - writableChunk.asWritableObjectChunk().add(null); - } - } - - final class ContextImpl implements Context { - - private final List> innerChunks; - - private ValueProcessorKvImpl innerProcessor; - - public ContextImpl() { - innerChunks = outputTypesImpl() - .map(ObjectProcessor::chunkType) - .map(chunkType -> chunkType.makeWritableChunk(0)) - .collect(Collectors.toList()); - } - - @Override - public void processElement(JsonParser parser, int index) throws IOException { - if (isPow2(index)) { - resize(index); - } - innerProcessor.processCurrentValue(parser); - } - - @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { - if (isPow2(index)) { - resize(index); - } - innerProcessor.processMissing(parser); - } - - @Override - public void done(JsonParser parser, int length) throws IOException { - final int size = out.size(); - for (int i = 0; i < size; ++i) { - final WritableChunk innerChunk = innerChunks.get(i); - final Object nativeArray = copy(innerChunk, outerTypes.get(i).componentType().clazz(), length); - innerChunk.close(); - out.get(i).asWritableObjectChunk().add(nativeArray); - } - } - - private void resize(int index) { - final int size = out.size(); - for (int i = 0; i < size; i++) { - final WritableChunk innerChunk = innerChunks.get(i); - final WritableChunk resized = resizeCopy(innerChunk, index, Math.max(index * 2, index + 1)); - innerChunk.close(); - innerChunks.set(i, resized); - } - innerProcessor = innerProcessor(); - } - } - } +// final class RepeaterImpl implements RepeaterProcessor { +// private final List> outerTypes; +// private final boolean allowMissing; +// private final boolean allowNull; +// +// private List> out; +// +// +// public RepeaterImpl(boolean allowMissing, boolean allowNull) { +// this.outerTypes = outputTypesImpl().map(NativeArrayType::arrayType).collect(Collectors.toList()); +// this.allowMissing = allowMissing; +// this.allowNull = allowNull; +// } +// +// @Override +// public Context context() { +// return new ContextImpl(); +// } +// +// @Override +// public void processNullRepeater(JsonParser parser) throws IOException { +// if (!allowNull) { +// throw Parsing.mismatch(parser, Object.class); +// } +// for (WritableChunk writableChunk : out) { +// writableChunk.asWritableObjectChunk().add(null); +// } +// } +// +// @Override +// public void processMissingRepeater(JsonParser parser) throws IOException { +// if (!allowMissing) { +// throw Parsing.mismatchMissing(parser, Object.class); +// } +// for (WritableChunk writableChunk : out) { +// writableChunk.asWritableObjectChunk().add(null); +// } +// } +// +// final class ContextImpl implements Context { +// +// private final List> innerChunks; +// +// private ValueProcessorKvImpl innerProcessor; +// +// public ContextImpl() { +// innerChunks = outputTypesImpl() +// .map(ObjectProcessor::chunkType) +// .map(chunkType -> chunkType.makeWritableChunk(0)) +// .collect(Collectors.toList()); +// } +// +// @Override +// public void processElement(JsonParser parser, int index) throws IOException { +// if (isPow2(index)) { +// resize(index); +// } +// innerProcessor.processCurrentValue(parser); +// } +// +// @Override +// public void processElementMissing(JsonParser parser, int index) throws IOException { +// if (isPow2(index)) { +// resize(index); +// } +// innerProcessor.processMissing(parser); +// } +// +// @Override +// public void done(JsonParser parser, int length) throws IOException { +// final int size = out.size(); +// for (int i = 0; i < size; ++i) { +// final WritableChunk innerChunk = innerChunks.get(i); +// final Object nativeArray = copy(innerChunk, outerTypes.get(i).componentType().clazz(), length); +// innerChunk.close(); +// out.get(i).asWritableObjectChunk().add(nativeArray); +// } +// } +// +// private void resize(int index) { +// final int size = out.size(); +// for (int i = 0; i < size; i++) { +// final WritableChunk innerChunk = innerChunks.get(i); +// final WritableChunk resized = resizeCopy(innerChunk, index, Math.max(index * 2, index + 1)); +// innerChunk.close(); +// innerChunks.set(i, resized); +// } +// innerProcessor = innerProcessor(); +// } +// } +// } private static WritableChunk resizeCopy(WritableChunk in, int inSize, int outCapacity) { diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java index 9b640f08805..fe64a2e804f 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java @@ -30,7 +30,6 @@ void standardNull() throws IOException { parse(CharValue.standard(), "null", CharChunk.chunkWrap(new char[] {QueryConstants.NULL_CHAR})); } - @Test void customMissing() throws IOException { parse(CharValue.builder().onMissing('m').build(), "", CharChunk.chunkWrap(new char[] {'m'})); From 5c6585220154522964337a8e0cc7dd52e0d96c9c Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 17 Apr 2024 07:56:51 -0700 Subject: [PATCH 12/53] f --- .../io/deephaven/json/jackson/ArrayMixin.java | 15 ++++--- ...rrayImpl.java => ArrayValueProcessor.java} | 26 ++---------- .../io/deephaven/json/jackson/ByteMixin.java | 4 +- .../io/deephaven/json/jackson/CharMixin.java | 4 +- .../deephaven/json/jackson/DoubleMixin.java | 4 +- .../io/deephaven/json/jackson/FloatMixin.java | 4 +- .../io/deephaven/json/jackson/IntMixin.java | 4 +- .../json/jackson/LongRepeaterImpl.java | 4 +- .../deephaven/json/jackson/ObjectMixin.java | 22 +++++----- .../json/jackson/RepeaterGenericImpl.java | 4 +- .../json/jackson/RepeaterProcessor.java | 40 +++++++++++++++++-- .../json/jackson/RepeaterProcessorBase.java | 4 +- .../io/deephaven/json/jackson/ShortMixin.java | 4 +- .../io/deephaven/json/jackson/SkipMixin.java | 8 ++-- .../io/deephaven/json/jackson/TupleMixin.java | 18 ++++----- .../json/jackson/ValueProcessorKvImpl.java | 29 +------------- 16 files changed, 89 insertions(+), 105 deletions(-) rename extensions/json-jackson/src/main/java/io/deephaven/json/jackson/{ValueProcessorArrayImpl.java => ArrayValueProcessor.java} (55%) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java index 22c9527a968..ad3808538f3 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java @@ -15,7 +15,6 @@ import java.io.IOException; import java.lang.reflect.Array; import java.util.List; -import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -57,8 +56,8 @@ RepeaterProcessor elementRepeater() { return element().repeaterProcessor(allowMissing(), allowNull()); } - private ValueProcessorArrayImpl innerProcessor() { - return new ValueProcessorArrayImpl(elementRepeater()); + private ArrayValueProcessor innerProcessor() { + return new ArrayValueProcessor(elementRepeater()); } @Override @@ -127,7 +126,7 @@ final class ArrayOfArrayProcessorContext implements Context { private final List> innerChunks; - private ValueProcessorArrayImpl innerProcessor; + private ArrayValueProcessor innerProcessor; public ArrayOfArrayProcessorContext() { innerChunks = outputTypesImpl() @@ -137,12 +136,12 @@ public ArrayOfArrayProcessorContext() { } @Override - public void init(JsonParser parser) throws IOException { + public void start(JsonParser parser) throws IOException { } @Override - public void processElement(JsonParser parser, int index) throws IOException { + public void processElement(JsonParser parser) throws IOException { if (isPow2(index)) { resize(index); } @@ -150,7 +149,7 @@ public void processElement(JsonParser parser, int index) throws IOException { } @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { + public void processElementMissing(JsonParser parser) throws IOException { if (isPow2(index)) { resize(index); } @@ -158,7 +157,7 @@ public void processElementMissing(JsonParser parser, int index) throws IOExcepti } @Override - public void done(JsonParser parser, int length) throws IOException { + public void done(JsonParser parser) throws IOException { final int size = out.size(); for (int i = 0; i < size; ++i) { final WritableChunk innerChunk = innerChunks.get(i); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorArrayImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayValueProcessor.java similarity index 55% rename from extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorArrayImpl.java rename to extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayValueProcessor.java index d2577e896bf..8c5d4cb7fad 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorArrayImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayValueProcessor.java @@ -6,36 +6,16 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.jackson.RepeaterProcessor.Context; import java.io.IOException; import java.util.List; import java.util.Objects; -final class ValueProcessorArrayImpl implements ValueProcessor { - - static void processArray2( - JsonParser parser, - RepeaterProcessor elementProcessor, - Runnable processElementCallback) throws IOException { - Parsing.assertCurrentToken(parser, JsonToken.START_ARRAY); - final Context context = elementProcessor.context(); - context.init(parser); - parser.nextToken(); - int ix; - for (ix = 0; !parser.hasToken(JsonToken.END_ARRAY); ++ix) { - context.processElement(parser, ix); - parser.nextToken(); - if (processElementCallback != null) { - processElementCallback.run(); - } - } - context.done(parser, ix); - } +final class ArrayValueProcessor implements ValueProcessor { private final RepeaterProcessor elementProcessor; - ValueProcessorArrayImpl(RepeaterProcessor elementProcessor) { + ArrayValueProcessor(RepeaterProcessor elementProcessor) { this.elementProcessor = Objects.requireNonNull(elementProcessor); } @@ -60,7 +40,7 @@ public void processCurrentValue(JsonParser parser) throws IOException { elementProcessor.processNullRepeater(parser); return; } - processArray2(parser, elementProcessor, null); + RepeaterProcessor.processArray(parser, elementProcessor); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java index c3d314885f6..064c7b461d5 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java @@ -76,7 +76,7 @@ public ByteRepeaterImpl(boolean allowMissing, boolean allowNull) { } @Override - public void processElement(JsonParser parser, int index) throws IOException { + public void processElement(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, ByteMixin.this.parseValue(parser)); @@ -84,7 +84,7 @@ public void processElement(JsonParser parser, int index) throws IOException { } @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { + public void processElementMissing(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, ByteMixin.this.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java index 3470cf1a3db..0193d579f88 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java @@ -72,7 +72,7 @@ public CharRepeaterImpl(boolean allowMissing, boolean allowNull) { } @Override - public void processElement(JsonParser parser, int index) throws IOException { + public void processElement(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, CharMixin.this.parseValue(parser)); @@ -80,7 +80,7 @@ public void processElement(JsonParser parser, int index) throws IOException { } @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { + public void processElementMissing(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, CharMixin.this.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java index 6d17a53460b..5471509e1da 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java @@ -76,7 +76,7 @@ public DoubleRepeaterImpl(boolean allowMissing, boolean allowNull) { } @Override - public void processElement(JsonParser parser, int index) throws IOException { + public void processElement(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, DoubleMixin.this.parseValue(parser)); @@ -84,7 +84,7 @@ public void processElement(JsonParser parser, int index) throws IOException { } @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { + public void processElementMissing(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, DoubleMixin.this.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java index 7ebcbca0981..1506da89e02 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java @@ -76,7 +76,7 @@ public FloatRepeaterImpl(boolean allowMissing, boolean allowNull) { } @Override - public void processElement(JsonParser parser, int index) throws IOException { + public void processElement(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, FloatMixin.this.parseValue(parser)); @@ -84,7 +84,7 @@ public void processElement(JsonParser parser, int index) throws IOException { } @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { + public void processElementMissing(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, FloatMixin.this.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java index 14b0a655f32..ca0bb352e0d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -77,7 +77,7 @@ public IntRepeaterImpl(boolean allowMissing, boolean allowNull) { } @Override - public void processElement(JsonParser parser, int index) throws IOException { + public void processElement(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, IntMixin.this.parseValue(parser)); @@ -85,7 +85,7 @@ public void processElement(JsonParser parser, int index) throws IOException { } @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { + public void processElementMissing(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, IntMixin.this.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java index 0e3620a5110..c23c3e6dc5d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java @@ -24,7 +24,7 @@ public LongRepeaterImpl(ToLong toLong, boolean allowMissing, boolean allowNull) } @Override - public void processElement(JsonParser parser, int index) throws IOException { + public void processElement(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, toLong.parseValue(parser)); @@ -32,7 +32,7 @@ public void processElement(JsonParser parser, int index) throws IOException { } @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { + public void processElementMissing(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, toLong.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java index 0bb28d2fa8b..0a18f62d98d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -297,14 +297,14 @@ private Context context(ObjectField o) { } @Override - public void init(JsonParser parser) throws IOException { + public void start(JsonParser parser) throws IOException { for (Context value : contexts.values()) { - value.init(parser); + value.start(parser); } } @Override - public void processElement(JsonParser parser, int index) throws IOException { + public void processElement(JsonParser parser) throws IOException { // see // com.fasterxml.jackson.databind.JsonDeserializer.deserialize(com.fasterxml.jackson.core.JsonParser, // com.fasterxml.jackson.databind.DeserializationContext) @@ -334,14 +334,14 @@ private void processNullObject(JsonParser parser, int ix) throws IOException { // element is null // pass-through JsonToken.VALUE_NULL for (Context context : contexts.values()) { - context.processElement(parser, ix); + context.processElement(parser); } } private void processEmptyObject(JsonParser parser, int ix) throws IOException { // This logic should be equivalent to processObjectFields, but where we know there are no fields for (Context context : contexts.values()) { - context.processElementMissing(parser, ix); + context.processElementMissing(parser); } } @@ -374,7 +374,7 @@ public void process(String fieldName, JsonParser parser) throws IOException { parser.skipChildren(); } else if (visited.add(field)) { // First time seeing field - context(field).processElement(parser, ix); + context(field).processElement(parser); } else if (field.repeatedBehavior() == RepeatedBehavior.USE_FIRST) { parser.skipChildren(); } else { @@ -391,23 +391,23 @@ void processMissingFields(JsonParser parser) throws IOException { } for (Entry e : contexts.entrySet()) { if (!visited.contains(e.getKey())) { - e.getValue().processElementMissing(parser, ix); + e.getValue().processElementMissing(parser); } } } } @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { + public void processElementMissing(JsonParser parser) throws IOException { for (Context context : contexts.values()) { - context.processElementMissing(parser, index); + context.processElementMissing(parser); } } @Override - public void done(JsonParser parser, int length) throws IOException { + public void done(JsonParser parser) throws IOException { for (Context context : contexts.values()) { - context.done(parser, length); + context.done(parser); } } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java index 671b92dc214..ee46ef39b1f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java @@ -23,7 +23,7 @@ public RepeaterGenericImpl(ToObject toObject, boolean allowMissing, boolean a } @Override - public void processElement(JsonParser parser, int index) throws IOException { + public void processElement(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, toObject.parseValue(parser)); @@ -31,7 +31,7 @@ public void processElement(JsonParser parser, int index) throws IOException { } @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { + public void processElementMissing(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, toObject.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java index 080ac0ca9eb..ca9928111fc 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java @@ -4,12 +4,44 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import java.io.IOException; // generalized for array vs object-kv interface RepeaterProcessor extends ContextAware { + static void processArray( + JsonParser parser, + RepeaterProcessor processor) throws IOException { + Parsing.assertCurrentToken(parser, JsonToken.START_ARRAY); + final Context context = processor.context(); + context.start(parser); + while (parser.nextToken() != JsonToken.END_ARRAY) { + context.processElement(parser); + } + context.done(parser); + } + + static void processKeyValues( + JsonParser parser, + RepeaterProcessor keyProcessor, + RepeaterProcessor valueProcessor) throws IOException { + Parsing.assertCurrentToken(parser, JsonToken.START_OBJECT); + final Context keyContext = keyProcessor.context(); + final Context valueContext = valueProcessor.context(); + keyContext.start(parser); + valueContext.start(parser); + while (parser.nextToken() != JsonToken.END_OBJECT) { + Parsing.assertCurrentToken(parser, JsonToken.FIELD_NAME); + keyContext.processElement(parser); + parser.nextToken(); + valueContext.processElement(parser); + } + keyContext.done(parser); + valueContext.done(parser); + } + void processNullRepeater(JsonParser parser) throws IOException; void processMissingRepeater(JsonParser parser) throws IOException; @@ -18,14 +50,14 @@ interface RepeaterProcessor extends ContextAware { interface Context { - void init(JsonParser parser) throws IOException; + void start(JsonParser parser) throws IOException; - void processElement(JsonParser parser, int index) throws IOException; + void processElement(JsonParser parser) throws IOException; // While a traditional arrays can't have missing elements, when an object is an array, a field may be missing: // [ { "foo": 1, "bar": 2 }, {"bar": 3} ] - void processElementMissing(JsonParser parser, int index) throws IOException; + void processElementMissing(JsonParser parser) throws IOException; - void done(JsonParser parser, int length) throws IOException; + void done(JsonParser parser) throws IOException; } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java index 6186f99e376..fc10827fd68 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java @@ -66,12 +66,12 @@ public final void processNullRepeater(JsonParser parser) throws IOException { } @Override - public final void init(JsonParser parser) throws IOException { + public final void start(JsonParser parser) throws IOException { } @Override - public final void done(JsonParser parser, int length) throws IOException { + public final void done(JsonParser parser) throws IOException { out.add(doneImpl(parser, length)); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java index 6e039cedab0..ef40ea8d391 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java @@ -76,7 +76,7 @@ public ShortRepeaterImpl(boolean allowMissing, boolean allowNull) { } @Override - public void processElement(JsonParser parser, int index) throws IOException { + public void processElement(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, ShortMixin.this.parseValue(parser)); @@ -84,7 +84,7 @@ public void processElement(JsonParser parser, int index) throws IOException { } @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { + public void processElementMissing(JsonParser parser) throws IOException { final int newSize = index + 1; final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, ShortMixin.this.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java index ee7376e7ef7..24db14a6bee 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java @@ -153,23 +153,23 @@ public int numColumns() { } @Override - public void init(JsonParser parser) throws IOException { + public void start(JsonParser parser) throws IOException { } @Override - public void processElement(JsonParser parser, int index) throws IOException { + public void processElement(JsonParser parser) throws IOException { processCurrentValue(parser); } @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { + public void processElementMissing(JsonParser parser) throws IOException { processMissing(parser); } @Override - public void done(JsonParser parser, int length) throws IOException { + public void done(JsonParser parser) throws IOException { } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java index 1093323bc94..e3d23e1a42a 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java @@ -183,14 +183,14 @@ public void processMissingRepeater(JsonParser parser) throws IOException { } @Override - public void init(JsonParser parser) throws IOException { + public void start(JsonParser parser) throws IOException { for (Context context : contexts) { - context.init(parser); + context.start(parser); } } @Override - public void processElement(JsonParser parser, int index) throws IOException { + public void processElement(JsonParser parser) throws IOException { switch (parser.currentToken()) { case START_ARRAY: processTuple(parser, index); @@ -206,7 +206,7 @@ public void processElement(JsonParser parser, int index) throws IOException { private void processTuple(JsonParser parser, int index) throws IOException { for (Context context : contexts) { parser.nextToken(); - context.processElement(parser, index); + context.processElement(parser); } parser.nextToken(); assertCurrentToken(parser, JsonToken.END_ARRAY); @@ -216,24 +216,24 @@ private void processNullTuple(JsonParser parser, int index) throws IOException { // Note: we are treating a null tuple the same as a tuple of null objects // null ~= [null, ..., null] for (Context context : contexts) { - context.processElement(parser, index); + context.processElement(parser); } } @Override - public void processElementMissing(JsonParser parser, int index) throws IOException { + public void processElementMissing(JsonParser parser) throws IOException { // Note: we are treating a missing tuple the same as a tuple of missing objects (which, is technically // impossible w/ native json, but it's the semantics we are exposing). // ~= [, ..., ] for (Context context : contexts) { - context.processElementMissing(parser, index); + context.processElementMissing(parser); } } @Override - public void done(JsonParser parser, int length) throws IOException { + public void done(JsonParser parser) throws IOException { for (Context context : contexts) { - context.done(parser, length); + context.done(parser); } } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java index 0a32eff4d9f..14399394a22 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.jackson.RepeaterProcessor.Context; import java.io.IOException; import java.util.List; @@ -14,32 +13,6 @@ final class ValueProcessorKvImpl implements ValueProcessor { - public static void processKeyValues2( - JsonParser parser, - RepeaterProcessor keyProcessor, - RepeaterProcessor valueProcessor, - Runnable processElementCallback) throws IOException { - Parsing.assertCurrentToken(parser, JsonToken.START_OBJECT); - final Context keyContext = keyProcessor.context(); - final Context valueContext = valueProcessor.context(); - keyContext.init(parser); - valueContext.init(parser); - parser.nextToken(); - int ix; - for (ix = 0; !parser.hasToken(JsonToken.END_OBJECT); ++ix) { - Parsing.assertCurrentToken(parser, JsonToken.FIELD_NAME); - keyContext.processElement(parser, ix); - parser.nextToken(); - valueContext.processElement(parser, ix); - parser.nextToken(); - if (processElementCallback != null) { - processElementCallback.run(); - } - } - keyContext.done(parser, ix); - valueContext.done(parser, ix); - } - private final RepeaterProcessor keyProcessor; private final RepeaterProcessor valueProcessor; @@ -73,7 +46,7 @@ public void processCurrentValue(JsonParser parser) throws IOException { valueProcessor.processNullRepeater(parser); return; } - processKeyValues2(parser, keyProcessor, valueProcessor, null); + RepeaterProcessor.processKeyValues(parser, keyProcessor, valueProcessor); } @Override From 979bdfad213bd08a545d09663cf3c3af700dee5a Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 17 Apr 2024 12:31:49 -0700 Subject: [PATCH 13/53] ValueInnerRepeaterProcessor --- .../io/deephaven/json/jackson/ArrayMixin.java | 142 +---------------- .../io/deephaven/json/jackson/ByteMixin.java | 4 +- .../io/deephaven/json/jackson/CharMixin.java | 4 +- .../deephaven/json/jackson/ContextAware.java | 3 + .../jackson/ContextAwareDelegateBase.java | 3 + .../deephaven/json/jackson/DoubleMixin.java | 4 +- .../io/deephaven/json/jackson/FloatMixin.java | 4 +- .../io/deephaven/json/jackson/IntMixin.java | 4 +- .../json/jackson/LongRepeaterImpl.java | 4 +- .../java/io/deephaven/json/jackson/Mixin.java | 2 +- .../deephaven/json/jackson/ObjectKvMixin.java | 122 +-------------- .../deephaven/json/jackson/ObjectMixin.java | 93 ++++++------ .../json/jackson/RepeaterGenericImpl.java | 11 +- .../json/jackson/RepeaterProcessor.java | 2 +- .../json/jackson/RepeaterProcessorBase.java | 24 ++- .../io/deephaven/json/jackson/ShortMixin.java | 4 +- .../io/deephaven/json/jackson/TupleMixin.java | 16 +- .../jackson/ValueInnerRepeaterProcessor.java | 143 ++++++++++++++++++ .../json/jackson/ValueProcessorKvImpl.java | 2 +- 19 files changed, 255 insertions(+), 336 deletions(-) create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java index ad3808538f3..e9a7a549b1c 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java @@ -4,16 +4,10 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; -import io.deephaven.chunk.attributes.Any; import io.deephaven.json.ArrayValue; -import io.deephaven.processor.ObjectProcessor; import io.deephaven.qst.type.NativeArrayType; import io.deephaven.qst.type.Type; -import java.io.IOException; -import java.lang.reflect.Array; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -65,137 +59,9 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { // For example: // double (element()) // double[] (processor()) - // double[][] (arrayProcessor()) - return new ArrayOfArrayProcessor(allowMissing, allowNull); - } - - final class ArrayOfArrayProcessor implements RepeaterProcessor { - private final List> outerTypes; - private final boolean allowMissing; - private final boolean allowNull; - - private List> out; - - public ArrayOfArrayProcessor(boolean allowMissing, boolean allowNull) { - this.outerTypes = outputTypesImpl().map(NativeArrayType::arrayType).collect(Collectors.toList()); - this.allowMissing = allowMissing; - this.allowNull = allowNull; - } - - @Override - public void setContext(List> out) { - this.out = out; - } - - @Override - public void clearContext() { - out = null; - } - - @Override - public int numColumns() { - return ArrayMixin.this.numColumns(); - } - - @Override - public Context context() { - return new ArrayOfArrayProcessorContext(); - } - - @Override - public void processNullRepeater(JsonParser parser) throws IOException { - if (!allowNull) { - throw Parsing.mismatch(parser, Object.class); - } - for (WritableChunk writableChunk : out) { - writableChunk.asWritableObjectChunk().add(null); - } - } - - @Override - public void processMissingRepeater(JsonParser parser) throws IOException { - if (!allowMissing) { - throw Parsing.mismatchMissing(parser, Object.class); - } - for (WritableChunk writableChunk : out) { - writableChunk.asWritableObjectChunk().add(null); - } - } - - final class ArrayOfArrayProcessorContext implements Context { - - private final List> innerChunks; - - private ArrayValueProcessor innerProcessor; - - public ArrayOfArrayProcessorContext() { - innerChunks = outputTypesImpl() - .map(ObjectProcessor::chunkType) - .map(chunkType -> chunkType.makeWritableChunk(0)) - .collect(Collectors.toList()); - } - - @Override - public void start(JsonParser parser) throws IOException { - - } - - @Override - public void processElement(JsonParser parser) throws IOException { - if (isPow2(index)) { - resize(index); - } - innerProcessor.processCurrentValue(parser); - } - - @Override - public void processElementMissing(JsonParser parser) throws IOException { - if (isPow2(index)) { - resize(index); - } - innerProcessor.processMissing(parser); - } - - @Override - public void done(JsonParser parser) throws IOException { - final int size = out.size(); - for (int i = 0; i < size; ++i) { - final WritableChunk innerChunk = innerChunks.get(i); - final Object nativeArray = copy(innerChunk, outerTypes.get(i).componentType().clazz(), length); - innerChunk.close(); - out.get(i).asWritableObjectChunk().add(nativeArray); - } - } - - private void resize(int index) { - final int size = out.size(); - for (int i = 0; i < size; i++) { - final WritableChunk innerChunk = innerChunks.get(i); - final WritableChunk resized = resizeCopy(innerChunk, index, Math.max(index * 2, index + 1)); - innerChunk.close(); - innerChunks.set(i, resized); - } - innerProcessor = innerProcessor(); - } - } - } - - private static WritableChunk resizeCopy(WritableChunk in, int inSize, - int outCapacity) { - final WritableChunk out = in.getChunkType().makeWritableChunk(outCapacity); - out.copyFromChunk(in, 0, 0, inSize); - out.setSize(inSize); - return out; - } - - private static Object copy(WritableChunk innerChunk, Class componentClazz, int length) { - final Object dest = Array.newInstance(componentClazz, length); - innerChunk.copyToArray(0, dest, 0, length); - return dest; - } - - private static boolean isPow2(int x) { - // true for 0, 1, 2, 4, 8, 16, ... - return (x & (x - 1)) == 0; + // double[][] (repeater()) + // return new ArrayOfArrayRepeaterProcessor(allowMissing, allowNull); + return new ValueInnerRepeaterProcessor(allowMissing, allowNull, innerProcessor(), + elementOutputTypes().collect(Collectors.toList())); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java index 064c7b461d5..5960b30575a 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java @@ -76,7 +76,7 @@ public ByteRepeaterImpl(boolean allowMissing, boolean allowNull) { } @Override - public void processElement(JsonParser parser) throws IOException { + public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, ByteMixin.this.parseValue(parser)); @@ -84,7 +84,7 @@ public void processElement(JsonParser parser) throws IOException { } @Override - public void processElementMissing(JsonParser parser) throws IOException { + public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, ByteMixin.this.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java index 0193d579f88..0e27c697805 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java @@ -72,7 +72,7 @@ public CharRepeaterImpl(boolean allowMissing, boolean allowNull) { } @Override - public void processElement(JsonParser parser) throws IOException { + public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, CharMixin.this.parseValue(parser)); @@ -80,7 +80,7 @@ public void processElement(JsonParser parser) throws IOException { } @Override - public void processElementMissing(JsonParser parser) throws IOException { + public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, CharMixin.this.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java index 795ca3d5372..83152f1de59 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java @@ -1,3 +1,6 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// package io.deephaven.json.jackson; import io.deephaven.chunk.WritableChunk; diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAwareDelegateBase.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAwareDelegateBase.java index d1f4f25baae..8c0bd9f5ae4 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAwareDelegateBase.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAwareDelegateBase.java @@ -1,3 +1,6 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// package io.deephaven.json.jackson; import io.deephaven.chunk.WritableChunk; diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java index 5471509e1da..85282a27faa 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java @@ -76,7 +76,7 @@ public DoubleRepeaterImpl(boolean allowMissing, boolean allowNull) { } @Override - public void processElement(JsonParser parser) throws IOException { + public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, DoubleMixin.this.parseValue(parser)); @@ -84,7 +84,7 @@ public void processElement(JsonParser parser) throws IOException { } @Override - public void processElementMissing(JsonParser parser) throws IOException { + public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, DoubleMixin.this.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java index 1506da89e02..4b9de1e104b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java @@ -76,7 +76,7 @@ public FloatRepeaterImpl(boolean allowMissing, boolean allowNull) { } @Override - public void processElement(JsonParser parser) throws IOException { + public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, FloatMixin.this.parseValue(parser)); @@ -84,7 +84,7 @@ public void processElement(JsonParser parser) throws IOException { } @Override - public void processElementMissing(JsonParser parser) throws IOException { + public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, FloatMixin.this.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java index ca0bb352e0d..769ad7e1caf 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -77,7 +77,7 @@ public IntRepeaterImpl(boolean allowMissing, boolean allowNull) { } @Override - public void processElement(JsonParser parser) throws IOException { + public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, IntMixin.this.parseValue(parser)); @@ -85,7 +85,7 @@ public void processElement(JsonParser parser) throws IOException { } @Override - public void processElementMissing(JsonParser parser) throws IOException { + public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, IntMixin.this.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java index c23c3e6dc5d..1aa03de41ea 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java @@ -24,7 +24,7 @@ public LongRepeaterImpl(ToLong toLong, boolean allowMissing, boolean allowNull) } @Override - public void processElement(JsonParser parser) throws IOException { + public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, toLong.parseValue(parser)); @@ -32,7 +32,7 @@ public void processElement(JsonParser parser) throws IOException { } @Override - public void processElementMissing(JsonParser parser) throws IOException { + public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, toLong.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index 0dd87f5af0b..1e2e8aa86c4 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -267,7 +267,7 @@ private abstract class ObjectProcessorJsonValue implements ObjectProcessor protected abstract JsonParser createParser(X in) throws IOException; private final ValueProcessor processor; - + public ObjectProcessorJsonValue() { processor = processor(""); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java index 90c3d143491..06ce77d67f9 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java @@ -4,18 +4,11 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; -import io.deephaven.chunk.attributes.Any; import io.deephaven.json.ObjectKvValue; -import io.deephaven.processor.ObjectProcessor; import io.deephaven.qst.type.NativeArrayType; import io.deephaven.qst.type.Type; -import java.io.IOException; -import java.lang.reflect.Array; import java.util.List; -import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -75,118 +68,7 @@ private ValueProcessorKvImpl innerProcessor() { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - throw new UnsupportedOperationException("todo"); - //return new RepeaterImpl(allowMissing, allowNull); - } - -// final class RepeaterImpl implements RepeaterProcessor { -// private final List> outerTypes; -// private final boolean allowMissing; -// private final boolean allowNull; -// -// private List> out; -// -// -// public RepeaterImpl(boolean allowMissing, boolean allowNull) { -// this.outerTypes = outputTypesImpl().map(NativeArrayType::arrayType).collect(Collectors.toList()); -// this.allowMissing = allowMissing; -// this.allowNull = allowNull; -// } -// -// @Override -// public Context context() { -// return new ContextImpl(); -// } -// -// @Override -// public void processNullRepeater(JsonParser parser) throws IOException { -// if (!allowNull) { -// throw Parsing.mismatch(parser, Object.class); -// } -// for (WritableChunk writableChunk : out) { -// writableChunk.asWritableObjectChunk().add(null); -// } -// } -// -// @Override -// public void processMissingRepeater(JsonParser parser) throws IOException { -// if (!allowMissing) { -// throw Parsing.mismatchMissing(parser, Object.class); -// } -// for (WritableChunk writableChunk : out) { -// writableChunk.asWritableObjectChunk().add(null); -// } -// } -// -// final class ContextImpl implements Context { -// -// private final List> innerChunks; -// -// private ValueProcessorKvImpl innerProcessor; -// -// public ContextImpl() { -// innerChunks = outputTypesImpl() -// .map(ObjectProcessor::chunkType) -// .map(chunkType -> chunkType.makeWritableChunk(0)) -// .collect(Collectors.toList()); -// } -// -// @Override -// public void processElement(JsonParser parser, int index) throws IOException { -// if (isPow2(index)) { -// resize(index); -// } -// innerProcessor.processCurrentValue(parser); -// } -// -// @Override -// public void processElementMissing(JsonParser parser, int index) throws IOException { -// if (isPow2(index)) { -// resize(index); -// } -// innerProcessor.processMissing(parser); -// } -// -// @Override -// public void done(JsonParser parser, int length) throws IOException { -// final int size = out.size(); -// for (int i = 0; i < size; ++i) { -// final WritableChunk innerChunk = innerChunks.get(i); -// final Object nativeArray = copy(innerChunk, outerTypes.get(i).componentType().clazz(), length); -// innerChunk.close(); -// out.get(i).asWritableObjectChunk().add(nativeArray); -// } -// } -// -// private void resize(int index) { -// final int size = out.size(); -// for (int i = 0; i < size; i++) { -// final WritableChunk innerChunk = innerChunks.get(i); -// final WritableChunk resized = resizeCopy(innerChunk, index, Math.max(index * 2, index + 1)); -// innerChunk.close(); -// innerChunks.set(i, resized); -// } -// innerProcessor = innerProcessor(); -// } -// } -// } - - private static WritableChunk resizeCopy(WritableChunk in, int inSize, - int outCapacity) { - final WritableChunk out = in.getChunkType().makeWritableChunk(outCapacity); - out.copyFromChunk(in, 0, 0, inSize); - out.setSize(inSize); - return out; - } - - private static Object copy(WritableChunk innerChunk, Class componentClazz, int length) { - final Object dest = Array.newInstance(componentClazz, length); - innerChunk.copyToArray(0, dest, 0, length); - return dest; - } - - private static boolean isPow2(int x) { - // true for 0, 1, 2, 4, 8, 16, ... - return (x & (x - 1)) == 0; + return new ValueInnerRepeaterProcessor(allowMissing, allowNull, innerProcessor(), + keyValueOutputTypes().collect(Collectors.toList())); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java index 0a18f62d98d..7b8ab8f530d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -230,7 +230,8 @@ void processMissingFields(JsonParser parser) throws IOException { } } - final class ObjectValueRepeaterProcessor extends ContextAwareDelegateBase implements RepeaterProcessor, Context { + final class ObjectValueRepeaterProcessor extends ContextAwareDelegateBase + implements RepeaterProcessor, Context, FieldProcessor { private final Map fields; private final Map contexts; private final Map map; @@ -252,6 +253,7 @@ public ObjectValueRepeaterProcessor(Map fields) map.put(alias, field); } } + this.visited = new HashSet<>(fields.size()); } private Collection processors() { @@ -312,25 +314,25 @@ public void processElement(JsonParser parser) throws IOException { switch (parser.currentToken()) { case START_OBJECT: if (parser.nextToken() == JsonToken.END_OBJECT) { - processEmptyObject(parser, index); - return; + processEmptyObject(parser); + break; } if (!parser.hasToken(JsonToken.FIELD_NAME)) { throw new IllegalStateException(); } // fall-through case FIELD_NAME: - processObjectFields(parser, index); - return; + processObjectFields(parser); + break; case VALUE_NULL: - processNullObject(parser, index); - return; + processNullObject(parser); + break; default: throw Parsing.mismatch(parser, Object.class); } } - private void processNullObject(JsonParser parser, int ix) throws IOException { + private void processNullObject(JsonParser parser) throws IOException { // element is null // pass-through JsonToken.VALUE_NULL for (Context context : contexts.values()) { @@ -338,65 +340,58 @@ private void processNullObject(JsonParser parser, int ix) throws IOException { } } - private void processEmptyObject(JsonParser parser, int ix) throws IOException { + private void processEmptyObject(JsonParser parser) throws IOException { // This logic should be equivalent to processObjectFields, but where we know there are no fields for (Context context : contexts.values()) { context.processElementMissing(parser); } } - private void processObjectFields(JsonParser parser, int ix) throws IOException { - // todo: keep state - final State state = new State(ix); - FieldProcessor.processFields(parser, state); - state.processMissingFields(parser); + private void processObjectFields(JsonParser parser) throws IOException { + visited.clear(); + FieldProcessor.processFields(parser, this); + processMissingFields(parser); } - private class State implements FieldProcessor { - // Note: we could try to build a stricter implementation that doesn't use Set; if the user can guarantee - // that none of the fields will be missing and there won't be any repeated fields, we could use a simple - // counter to ensure all field processors were invoked. - private final Set visited = new HashSet<>(contexts.size()); - private final int ix; + // ----------------------------------------------------------------------------------------------------------- - public State(int ix) { - this.ix = ix; - } + private final Set visited; - @Override - public void process(String fieldName, JsonParser parser) throws IOException { - final ObjectField field = lookupField(fieldName); - if (field == null) { - if (!options.allowUnknownFields()) { - throw new IOException( - String.format("Unexpected field '%s' and allowUnknownFields == false", fieldName)); - } - parser.skipChildren(); - } else if (visited.add(field)) { - // First time seeing field - context(field).processElement(parser); - } else if (field.repeatedBehavior() == RepeatedBehavior.USE_FIRST) { - parser.skipChildren(); - } else { + @Override + public void process(String fieldName, JsonParser parser) throws IOException { + final ObjectField field = lookupField(fieldName); + if (field == null) { + if (!options.allowUnknownFields()) { throw new IOException( - String.format("Field '%s' has already been visited and repeatedBehavior == %s", - fieldName, field.repeatedBehavior())); + String.format("Unexpected field '%s' and allowUnknownFields == false", fieldName)); } + parser.skipChildren(); + } else if (visited.add(field)) { + // First time seeing field + context(field).processElement(parser); + } else if (field.repeatedBehavior() == RepeatedBehavior.USE_FIRST) { + parser.skipChildren(); + } else { + throw new IOException( + String.format("Field '%s' has already been visited and repeatedBehavior == %s", + fieldName, field.repeatedBehavior())); } + } - void processMissingFields(JsonParser parser) throws IOException { - if (visited.size() == fields.size()) { - // All fields visited, none missing - return; - } - for (Entry e : contexts.entrySet()) { - if (!visited.contains(e.getKey())) { - e.getValue().processElementMissing(parser); - } + void processMissingFields(JsonParser parser) throws IOException { + if (visited.size() == fields.size()) { + // All fields visited, none missing + return; + } + for (Entry e : contexts.entrySet()) { + if (!visited.contains(e.getKey())) { + e.getValue().processElementMissing(parser); } } } + // ----------------------------------------------------------------------------------------------------------- + @Override public void processElementMissing(JsonParser parser) throws IOException { for (Context context : contexts.values()) { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java index ee46ef39b1f..27181a3fbac 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java @@ -16,14 +16,15 @@ final class RepeaterGenericImpl extends RepeaterProcessorBase { private final ToObject toObject; private final SizedObjectChunk chunk; - public RepeaterGenericImpl(ToObject toObject, boolean allowMissing, boolean allowNull, T[] onMissing, T[] onNull) { + public RepeaterGenericImpl(ToObject toObject, boolean allowMissing, boolean allowNull, T[] onMissing, + T[] onNull) { super(allowMissing, allowNull, onMissing, onNull); this.toObject = Objects.requireNonNull(toObject); chunk = new SizedObjectChunk<>(0); } @Override - public void processElement(JsonParser parser) throws IOException { + public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, toObject.parseValue(parser)); @@ -31,7 +32,7 @@ public void processElement(JsonParser parser) throws IOException { } @Override - public void processElementMissing(JsonParser parser) throws IOException { + public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, toObject.parseMissing(parser)); @@ -41,6 +42,8 @@ public void processElementMissing(JsonParser parser) throws IOException { @Override public T[] doneImpl(JsonParser parser, int length) { final WritableObjectChunk chunk = this.chunk.get(); - return Arrays.copyOfRange(chunk.array(), chunk.arrayOffset(), chunk.arrayOffset() + length); + final T[] result = Arrays.copyOfRange(chunk.array(), chunk.arrayOffset(), chunk.arrayOffset() + length); + chunk.fillWithNullValue(0, length); + return result; } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java index ca9928111fc..2635ef4dbc6 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java @@ -23,7 +23,7 @@ static void processArray( context.done(parser); } - static void processKeyValues( + static void processObjectKeyValues( JsonParser parser, RepeaterProcessor keyProcessor, RepeaterProcessor valueProcessor) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java index fc10827fd68..840c47508ea 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java @@ -19,6 +19,7 @@ abstract class RepeaterProcessorBase implements RepeaterProcessor, Context { private final T onNull; private WritableObjectChunk out; + private int ix; public RepeaterProcessorBase(boolean allowMissing, boolean allowNull, T onMissing, T onNull) { this.onMissing = onMissing; @@ -27,7 +28,13 @@ public RepeaterProcessorBase(boolean allowMissing, boolean allowNull, T onMissin this.allowMissing = allowMissing; } - public abstract T doneImpl(JsonParser parser, int length); + public void startImpl(JsonParser parser) throws IOException {} + + public abstract void processElementImpl(JsonParser parser, int index) throws IOException; + + public abstract void processElementMissingImpl(JsonParser parser, int index) throws IOException; + + public abstract T doneImpl(JsonParser parser, int length) throws IOException; @Override public final void setContext(List> out) { @@ -67,11 +74,24 @@ public final void processNullRepeater(JsonParser parser) throws IOException { @Override public final void start(JsonParser parser) throws IOException { + startImpl(parser); + ix = 0; + } + @Override + public final void processElement(JsonParser parser) throws IOException { + processElementImpl(parser, ix); + ++ix; + } + + @Override + public final void processElementMissing(JsonParser parser) throws IOException { + processElementMissingImpl(parser, ix); + ++ix; } @Override public final void done(JsonParser parser) throws IOException { - out.add(doneImpl(parser, length)); + out.add(doneImpl(parser, ix)); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java index ef40ea8d391..59d3f01baea 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java @@ -76,7 +76,7 @@ public ShortRepeaterImpl(boolean allowMissing, boolean allowNull) { } @Override - public void processElement(JsonParser parser) throws IOException { + public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, ShortMixin.this.parseValue(parser)); @@ -84,7 +84,7 @@ public void processElement(JsonParser parser) throws IOException { } @Override - public void processElementMissing(JsonParser parser) throws IOException { + public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(newSize); chunk.set(index, ShortMixin.this.parseMissing(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java index e3d23e1a42a..10ba63bada6 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java @@ -147,6 +147,7 @@ private void parseFromMissing(JsonParser parser) throws IOException { final class TupleArrayProcessor extends ContextAwareDelegateBase implements RepeaterProcessor, Context { private final List values; private final List contexts; + private int index; public TupleArrayProcessor(List values) { super(values); @@ -187,23 +188,25 @@ public void start(JsonParser parser) throws IOException { for (Context context : contexts) { context.start(parser); } + index = 0; } @Override public void processElement(JsonParser parser) throws IOException { switch (parser.currentToken()) { case START_ARRAY: - processTuple(parser, index); - return; + processTuple(parser); + break; case VALUE_NULL: - processNullTuple(parser, index); - return; + processNullTuple(parser); + break; default: throw Parsing.mismatch(parser, Object.class); } + ++index; } - private void processTuple(JsonParser parser, int index) throws IOException { + private void processTuple(JsonParser parser) throws IOException { for (Context context : contexts) { parser.nextToken(); context.processElement(parser); @@ -212,7 +215,7 @@ private void processTuple(JsonParser parser, int index) throws IOException { assertCurrentToken(parser, JsonToken.END_ARRAY); } - private void processNullTuple(JsonParser parser, int index) throws IOException { + private void processNullTuple(JsonParser parser) throws IOException { // Note: we are treating a null tuple the same as a tuple of null objects // null ~= [null, ..., null] for (Context context : contexts) { @@ -228,6 +231,7 @@ public void processElementMissing(JsonParser parser) throws IOException { for (Context context : contexts) { context.processElementMissing(parser); } + ++index; } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java new file mode 100644 index 00000000000..ef93754d444 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java @@ -0,0 +1,143 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableObjectChunk; +import io.deephaven.chunk.sized.SizedObjectChunk; +import io.deephaven.json.jackson.RepeaterProcessor.Context; +import io.deephaven.processor.ObjectProcessor; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +final class ValueInnerRepeaterProcessor implements RepeaterProcessor, Context { + + private final boolean allowMissing; + private final boolean allowNull; + private final ValueProcessor innerProcessor; + private final List> innerChunks; + private final List> sizedObjectChunks; + + private int ix; + + private List> out; + + public ValueInnerRepeaterProcessor(boolean allowMissing, boolean allowNull, ValueProcessor innerProcessor, + List> innerProcessorTypes) { + this.allowMissing = allowMissing; + this.allowNull = allowNull; + final List> innerChunks = innerProcessorTypes.stream() + .map(Type::arrayType) + .map(ObjectProcessor::chunkType) + .map(o -> o.makeWritableChunk(1)) + .peek(wc -> wc.setSize(0)) + .collect(Collectors.toList()); + // we _know_ these are all object chunks, given NativeArrayType + // noinspection unchecked,rawtypes + this.innerChunks = (List>) (List) innerChunks; + this.innerProcessor = innerProcessor; + this.innerProcessor.setContext(innerChunks); + sizedObjectChunks = IntStream.range(0, innerProcessor.numColumns()) + .mapToObj(i -> new SizedObjectChunk<>(0)) + .collect(Collectors.toList()); + } + + @Override + public void setContext(List> out) { + // noinspection unchecked,rawtypes + this.out = (List>) (List) Objects.requireNonNull(out); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + public int numColumns() { + return sizedObjectChunks.size(); + } + + @Override + public void processNullRepeater(JsonParser parser) throws IOException { + if (!allowNull) { + throw Parsing.mismatch(parser, Object.class); + } + for (WritableObjectChunk wc : out) { + wc.add(null); + } + } + + @Override + public void processMissingRepeater(JsonParser parser) throws IOException { + if (!allowMissing) { + throw Parsing.mismatchMissing(parser, Object.class); + } + for (WritableObjectChunk wc : out) { + wc.add(null); + } + } + + @Override + public Context context() { + return this; + } + + @Override + public void start(JsonParser parser) throws IOException { + // Note: we are setting the context once up-front + // innerProcessor.setContext(innerChunks); + ix = 0; + } + + @Override + public void processElement(JsonParser parser) throws IOException { + innerProcessor.processCurrentValue(parser); + processImpl(); + } + + @Override + public void processElementMissing(JsonParser parser) throws IOException { + innerProcessor.processMissing(parser); + processImpl(); + } + + private void processImpl() { + final int newSize = ix + 1; + final int L = sizedObjectChunks.size(); + for (int i = 0; i < L; ++i) { + // noinspection unchecked + final WritableObjectChunk from = (WritableObjectChunk) innerChunks.get(i); + // noinspection unchecked + final SizedObjectChunk to = (SizedObjectChunk) sizedObjectChunks.get(i); + // we _could_ consider doing this in a chunked fashion. doing in simple fashion to initially test + to.ensureCapacityPreserve(newSize); + to.get().set(ix, from.get(0)); + to.get().setSize(newSize); + from.set(0, null); + from.setSize(0); + } + ix = newSize; + } + + @Override + public void done(JsonParser parser) throws IOException { + // Note: we are setting the context once up-front + // innerProcessor.clearContext(); + final int L = sizedObjectChunks.size(); + for (int i = 0; i < L; ++i) { + final WritableObjectChunk from = sizedObjectChunks.get(i).get(); + final Object[] objects = Arrays.copyOfRange(from.array(), from.arrayOffset(), from.arrayOffset() + ix); + from.fillWithNullValue(0, ix); + out.get(i).add(objects); + } + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java index 14399394a22..c4b2396d2c4 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java @@ -46,7 +46,7 @@ public void processCurrentValue(JsonParser parser) throws IOException { valueProcessor.processNullRepeater(parser); return; } - RepeaterProcessor.processKeyValues(parser, keyProcessor, valueProcessor); + RepeaterProcessor.processObjectKeyValues(parser, keyProcessor, valueProcessor); } @Override From fd3f6eda55d0ffde74273929421ddb4e0cb53466 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 17 Apr 2024 14:04:45 -0700 Subject: [PATCH 14/53] ValueProcessor knows types --- .../io/deephaven/json/jackson/AnyMixin.java | 5 +- .../io/deephaven/json/jackson/ArrayMixin.java | 4 +- .../json/jackson/ArrayValueProcessor.java | 7 ++ .../json/jackson/BigDecimalMixin.java | 5 +- .../json/jackson/BigIntegerMixin.java | 5 +- .../io/deephaven/json/jackson/BoolMixin.java | 3 +- .../io/deephaven/json/jackson/ByteMixin.java | 2 +- .../json/jackson/ByteValueProcessor.java | 7 ++ .../io/deephaven/json/jackson/CharMixin.java | 2 +- .../json/jackson/CharValueProcessor.java | 7 ++ .../deephaven/json/jackson/ContextAware.java | 8 ++- .../jackson/ContextAwareDelegateBase.java | 7 ++ .../deephaven/json/jackson/DoubleMixin.java | 2 +- .../json/jackson/DoubleValueProcessor.java | 7 ++ .../io/deephaven/json/jackson/FloatMixin.java | 2 +- .../json/jackson/FloatValueProcessor.java | 7 ++ .../deephaven/json/jackson/InstantMixin.java | 2 +- .../json/jackson/InstantNumberMixin.java | 2 +- .../io/deephaven/json/jackson/IntMixin.java | 2 +- .../json/jackson/IntValueProcessor.java | 7 ++ .../json/jackson/LocalDateMixin.java | 5 +- .../io/deephaven/json/jackson/LongMixin.java | 2 +- .../json/jackson/LongRepeaterImpl.java | 5 +- .../json/jackson/LongValueProcessor.java | 7 ++ .../java/io/deephaven/json/jackson/Mixin.java | 64 ++++--------------- .../deephaven/json/jackson/ObjectKvMixin.java | 4 +- .../jackson/ObjectProcessorJsonValue.java | 57 +++++++++++++++++ .../json/jackson/ObjectValueProcessor.java | 12 +++- .../json/jackson/RepeaterGenericImpl.java | 5 +- .../json/jackson/RepeaterProcessorBase.java | 15 ++++- .../io/deephaven/json/jackson/ShortMixin.java | 2 +- .../json/jackson/ShortValueProcessor.java | 7 ++ .../io/deephaven/json/jackson/SkipMixin.java | 10 +++ .../deephaven/json/jackson/StringMixin.java | 4 +- .../json/jackson/TypedObjectMixin.java | 5 ++ .../jackson/ValueInnerRepeaterProcessor.java | 11 +++- .../json/jackson/ValueProcessorKvImpl.java | 7 ++ 37 files changed, 228 insertions(+), 87 deletions(-) create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectProcessorJsonValue.java diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java index 372b497e711..b1b8cb1ce26 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java @@ -36,12 +36,13 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new ObjectValueProcessor<>(ToTreeNode.INSTANCE); + return new ObjectValueProcessor<>(ToTreeNode.INSTANCE, Type.ofCustom(TreeNode.class)); } @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(ToTreeNode.INSTANCE, allowMissing, allowNull, null, null); + return new RepeaterGenericImpl<>(ToTreeNode.INSTANCE, allowMissing, allowNull, null, null, + Type.ofCustom(TreeNode.class).arrayType()); } private enum ToTreeNode implements ToObject { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java index e9a7a549b1c..3c10ad68c12 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java @@ -9,7 +9,6 @@ import io.deephaven.qst.type.Type; import java.util.List; -import java.util.stream.Collectors; import java.util.stream.Stream; final class ArrayMixin extends Mixin { @@ -61,7 +60,6 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { // double[] (processor()) // double[][] (repeater()) // return new ArrayOfArrayRepeaterProcessor(allowMissing, allowNull); - return new ValueInnerRepeaterProcessor(allowMissing, allowNull, innerProcessor(), - elementOutputTypes().collect(Collectors.toList())); + return new ValueInnerRepeaterProcessor(allowMissing, allowNull, innerProcessor()); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayValueProcessor.java index 8c5d4cb7fad..3a1f7ae4b84 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayValueProcessor.java @@ -6,10 +6,12 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import io.deephaven.chunk.WritableChunk; +import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; final class ArrayValueProcessor implements ValueProcessor { @@ -34,6 +36,11 @@ public int numColumns() { return elementProcessor.numColumns(); } + @Override + public Stream> columnTypes() { + return elementProcessor.columnTypes(); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { if (parser.hasToken(JsonToken.VALUE_NULL)) { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java index 135d77b11d8..c4c1cba166d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java @@ -37,7 +37,7 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new ObjectValueProcessor<>(this); + return new ObjectValueProcessor<>(this, Type.ofCustom(BigDecimal.class)); } @Override @@ -62,7 +62,8 @@ public BigDecimal parseMissing(JsonParser parser) throws IOException { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null); + return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null, + Type.ofCustom(BigDecimal.class).arrayType()); } private BigDecimal parseFromNumber(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java index f609cdbac90..d71c1f096de 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java @@ -37,7 +37,7 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new ObjectValueProcessor<>(this); + return new ObjectValueProcessor<>(this, Type.ofCustom(BigInteger.class)); } @Override @@ -63,7 +63,8 @@ public BigInteger parseMissing(JsonParser parser) throws IOException { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null); + return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null, + Type.ofCustom(BigInteger.class).arrayType()); } private BigInteger parseFromInt(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java index 3bf945459ff..15df180cb66 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java @@ -63,7 +63,8 @@ public byte parseMissing(JsonParser parser) throws IOException { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(new ToBoolean(), allowMissing, allowNull, null, null); + return new RepeaterGenericImpl<>(new ToBoolean(), allowMissing, allowNull, null, null, + Type.booleanType().boxedType().arrayType()); } final class ToBoolean implements ToObject { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java index 5960b30575a..90205124389 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java @@ -72,7 +72,7 @@ final class ByteRepeaterImpl extends RepeaterProcessorBase { private final SizedByteChunk chunk = new SizedByteChunk<>(0); public ByteRepeaterImpl(boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null); + super(allowMissing, allowNull, null, null, Type.byteType().arrayType()); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java index a64b0c6b74c..aa5a7aa9d72 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java @@ -6,10 +6,12 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableByteChunk; import io.deephaven.chunk.WritableChunk; +import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; final class ByteValueProcessor implements ValueProcessor { @@ -35,6 +37,11 @@ public int numColumns() { return 1; } + @Override + public Stream> columnTypes() { + return Stream.of(Type.byteType()); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toByte.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java index 0e27c697805..2d814ed6b73 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java @@ -68,7 +68,7 @@ final class CharRepeaterImpl extends RepeaterProcessorBase { private final SizedCharChunk chunk = new SizedCharChunk<>(0); public CharRepeaterImpl(boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null); + super(allowMissing, allowNull, null, null, Type.charType().arrayType()); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java index 7656722d3be..471dfe74639 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java @@ -6,10 +6,12 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableCharChunk; import io.deephaven.chunk.WritableChunk; +import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; final class CharValueProcessor implements ValueProcessor { @@ -35,6 +37,11 @@ public int numColumns() { return 1; } + @Override + public Stream> columnTypes() { + return Stream.of(Type.charType()); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toChar.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java index 83152f1de59..60f72dfe8e9 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java @@ -4,13 +4,19 @@ package io.deephaven.json.jackson; import io.deephaven.chunk.WritableChunk; +import io.deephaven.qst.type.Type; import java.util.List; +import java.util.stream.Stream; interface ContextAware { void setContext(List> out); void clearContext(); - int numColumns(); + default int numColumns() { + return (int) columnTypes().count(); + } + + Stream> columnTypes(); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAwareDelegateBase.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAwareDelegateBase.java index 8c0bd9f5ae4..bfe6a248727 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAwareDelegateBase.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAwareDelegateBase.java @@ -4,10 +4,12 @@ package io.deephaven.json.jackson; import io.deephaven.chunk.WritableChunk; +import io.deephaven.qst.type.Type; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; abstract class ContextAwareDelegateBase implements ContextAware { @@ -40,4 +42,9 @@ public final void clearContext() { public final int numColumns() { return numColumns; } + + @Override + public final Stream> columnTypes() { + return delegates.stream().flatMap(ContextAware::columnTypes); + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java index 85282a27faa..5a48e355130 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java @@ -72,7 +72,7 @@ final class DoubleRepeaterImpl extends RepeaterProcessorBase { private final SizedDoubleChunk chunk = new SizedDoubleChunk<>(0); public DoubleRepeaterImpl(boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null); + super(allowMissing, allowNull, null, null, Type.doubleType().arrayType()); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java index 6536a88241d..ca47de9e754 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java @@ -6,10 +6,12 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableDoubleChunk; +import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; final class DoubleValueProcessor implements ValueProcessor { @@ -35,6 +37,11 @@ public int numColumns() { return 1; } + @Override + public Stream> columnTypes() { + return Stream.of(Type.doubleType()); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toDouble.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java index 4b9de1e104b..be67641e9ba 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java @@ -72,7 +72,7 @@ final class FloatRepeaterImpl extends RepeaterProcessorBase { private final SizedFloatChunk chunk = new SizedFloatChunk<>(0); public FloatRepeaterImpl(boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null); + super(allowMissing, allowNull, null, null, Type.floatType().arrayType()); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java index b096a1087e1..c5ade0e2fa2 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java @@ -6,10 +6,12 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableFloatChunk; +import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; final class FloatValueProcessor implements ValueProcessor { @@ -35,6 +37,11 @@ public int numColumns() { return 1; } + @Override + public Stream> columnTypes() { + return Stream.of(Type.floatType()); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toFloat.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java index de45f91f79f..a1935a9a657 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java @@ -67,7 +67,7 @@ public long parseMissing(JsonParser parser) throws IOException { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new LongRepeaterImpl(this, allowMissing, allowNull); + return new LongRepeaterImpl(this, allowMissing, allowNull, Type.instantType().arrayType()); } private long parseFromString(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java index 20397c8864e..2db837a65b9 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java @@ -47,7 +47,7 @@ public ValueProcessor processor(String context) { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new LongRepeaterImpl(function(), allowMissing, allowNull); + return new LongRepeaterImpl(function(), allowMissing, allowNull, Type.instantType().arrayType()); } private LongValueProcessor.ToLong function() { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java index 769ad7e1caf..9bd0c459162 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -73,7 +73,7 @@ final class IntRepeaterImpl extends RepeaterProcessorBase { private final SizedIntChunk chunk = new SizedIntChunk<>(0); public IntRepeaterImpl(boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null); + super(allowMissing, allowNull, null, null, Type.intType().arrayType()); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java index 11709949b87..7dcdba768db 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java @@ -6,10 +6,12 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableIntChunk; +import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; final class IntValueProcessor implements ValueProcessor { @@ -35,6 +37,11 @@ public int numColumns() { return 1; } + @Override + public Stream> columnTypes() { + return Stream.of(Type.intType()); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toInt.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java index 39495e9ac67..e94f33c286c 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java @@ -38,7 +38,7 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new ObjectValueProcessor<>(this); + return new ObjectValueProcessor<>(this, Type.ofCustom(LocalDate.class)); } @Override @@ -60,7 +60,8 @@ public LocalDate parseMissing(JsonParser parser) throws IOException { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null); + return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null, + Type.ofCustom(LocalDate.class).arrayType()); } private LocalDate parseFromString(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java index d1d6d9d91b3..a548b50495d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java @@ -62,7 +62,7 @@ public long parseMissing(JsonParser parser) throws IOException { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new LongRepeaterImpl(this, allowMissing, allowNull); + return new LongRepeaterImpl(this, allowMissing, allowNull, Type.longType().arrayType()); } private long parseFromInt(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java index 1aa03de41ea..cbae181a286 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java @@ -7,6 +7,7 @@ import io.deephaven.chunk.WritableLongChunk; import io.deephaven.chunk.sized.SizedLongChunk; import io.deephaven.json.jackson.LongValueProcessor.ToLong; +import io.deephaven.qst.type.GenericType; import java.io.IOException; import java.util.Arrays; @@ -18,8 +19,8 @@ final class LongRepeaterImpl extends RepeaterProcessorBase { private final ToLong toLong; - public LongRepeaterImpl(ToLong toLong, boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null); + public LongRepeaterImpl(ToLong toLong, boolean allowMissing, boolean allowNull, GenericType type) { + super(allowMissing, allowNull, null, null, type); this.toLong = Objects.requireNonNull(toLong); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java index ef07a0d605f..5e15f4c1342 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java @@ -6,10 +6,12 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableLongChunk; +import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; final class LongValueProcessor implements ValueProcessor { private WritableLongChunk out; @@ -34,6 +36,11 @@ public int numColumns() { return 1; } + @Override + public Stream> columnTypes() { + return Stream.of(Type.longType()); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toLong.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index 1e2e8aa86c4..f289500718a 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -6,8 +6,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.api.util.NameValidator; -import io.deephaven.chunk.ObjectChunk; -import io.deephaven.chunk.WritableChunk; import io.deephaven.json.AnyValue; import io.deephaven.json.ArrayValue; import io.deephaven.json.BigDecimalValue; @@ -37,7 +35,6 @@ import java.io.File; import java.io.IOException; -import java.io.UncheckedIOException; import java.net.URL; import java.nio.ByteBuffer; import java.nio.file.Path; @@ -213,96 +210,61 @@ Stream> prefixWithKeys(Collection fields) { return paths.stream().flatMap(Function.identity()); } - private class StringIn extends ObjectProcessorJsonValue { + private abstract class ObjectProcessorMixin extends ObjectProcessorJsonValue { + public ObjectProcessorMixin() { + super(Mixin.this.processor("")); + } + } + + private class StringIn extends ObjectProcessorMixin { @Override protected JsonParser createParser(String in) throws IOException { return JacksonSource.of(factory, in); } } - private class BytesIn extends ObjectProcessorJsonValue { + private class BytesIn extends ObjectProcessorMixin { @Override protected JsonParser createParser(byte[] in) throws IOException { return JacksonSource.of(factory, in, 0, in.length); } } - private class ByteBufferIn extends ObjectProcessorJsonValue { + private class ByteBufferIn extends ObjectProcessorMixin { @Override protected JsonParser createParser(ByteBuffer in) throws IOException { return JacksonSource.of(factory, in); } } - private class CharsIn extends ObjectProcessorJsonValue { + private class CharsIn extends ObjectProcessorMixin { @Override protected JsonParser createParser(char[] in) throws IOException { return JacksonSource.of(factory, in, 0, in.length); } } - private class FileIn extends ObjectProcessorJsonValue { + private class FileIn extends ObjectProcessorMixin { @Override protected JsonParser createParser(File in) throws IOException { return JacksonSource.of(factory, in); } } - private class PathIn extends ObjectProcessorJsonValue { + private class PathIn extends ObjectProcessorMixin { @Override protected JsonParser createParser(Path in) throws IOException { return JacksonSource.of(factory, in); } } - private class URLIn extends ObjectProcessorJsonValue { + private class URLIn extends ObjectProcessorMixin { @Override protected JsonParser createParser(URL in) throws IOException { return JacksonSource.of(factory, in); } } - private abstract class ObjectProcessorJsonValue implements ObjectProcessor { - - protected abstract JsonParser createParser(X in) throws IOException; - - private final ValueProcessor processor; - - public ObjectProcessorJsonValue() { - processor = processor(""); - } - - @Override - public final int size() { - return Mixin.this.size(); - } - - @Override - public final List> outputTypes() { - return Mixin.this.outputTypes(); - } - - @Override - public final void processAll(ObjectChunk in, List> out) { - processor.setContext(out); - try { - processAllImpl(in); - } catch (IOException e) { - throw new UncheckedIOException(e); - } finally { - processor.clearContext(); - } - } - - void processAllImpl(ObjectChunk in) throws IOException { - for (int i = 0; i < in.size(); ++i) { - try (final JsonParser parser = createParser(in.get(i))) { - ValueProcessor.processFullJson(parser, processor); - } - } - } - } - private static class MixinImpl implements Value.Visitor> { private final JsonFactory factory; diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java index 06ce77d67f9..7b839abedcf 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java @@ -9,7 +9,6 @@ import io.deephaven.qst.type.Type; import java.util.List; -import java.util.stream.Collectors; import java.util.stream.Stream; final class ObjectKvMixin extends Mixin { @@ -68,7 +67,6 @@ private ValueProcessorKvImpl innerProcessor() { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new ValueInnerRepeaterProcessor(allowMissing, allowNull, innerProcessor(), - keyValueOutputTypes().collect(Collectors.toList())); + return new ValueInnerRepeaterProcessor(allowMissing, allowNull, innerProcessor()); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectProcessorJsonValue.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectProcessorJsonValue.java new file mode 100644 index 00000000000..7d1b3f523d3 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectProcessorJsonValue.java @@ -0,0 +1,57 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.ObjectChunk; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.processor.ObjectProcessor; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +abstract class ObjectProcessorJsonValue implements ObjectProcessor { + + protected abstract JsonParser createParser(X in) throws IOException; + + private final ValueProcessor processor; + + ObjectProcessorJsonValue(ValueProcessor processor) { + this.processor = Objects.requireNonNull(processor); + } + + @Override + public final int size() { + return processor.numColumns(); + } + + @Override + public final List> outputTypes() { + return processor.columnTypes().collect(Collectors.toList()); + } + + @Override + public final void processAll(ObjectChunk in, List> out) { + processor.setContext(out); + try { + processAllImpl(in); + } catch (IOException e) { + throw new UncheckedIOException(e); + } finally { + processor.clearContext(); + } + } + + void processAllImpl(ObjectChunk in) throws IOException { + for (int i = 0; i < in.size(); ++i) { + try (final JsonParser parser = createParser(in.get(i))) { + ValueProcessor.processFullJson(parser, processor); + } + } + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java index 34d3df6376b..6228cebe366 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java @@ -6,18 +6,23 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableObjectChunk; +import io.deephaven.qst.type.GenericType; +import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; final class ObjectValueProcessor implements ValueProcessor { private WritableObjectChunk out; private final ToObject toObj; + private final GenericType type; - ObjectValueProcessor(ToObject toObj) { + ObjectValueProcessor(ToObject toObj, GenericType type) { this.toObj = Objects.requireNonNull(toObj); + this.type = Objects.requireNonNull(type); } @Override @@ -35,6 +40,11 @@ public int numColumns() { return 1; } + @Override + public Stream> columnTypes() { + return Stream.of(type); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toObj.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java index 27181a3fbac..d4dbc3cba2f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java @@ -7,6 +7,7 @@ import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.chunk.sized.SizedObjectChunk; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; +import io.deephaven.qst.type.NativeArrayType; import java.io.IOException; import java.util.Arrays; @@ -17,8 +18,8 @@ final class RepeaterGenericImpl extends RepeaterProcessorBase { private final SizedObjectChunk chunk; public RepeaterGenericImpl(ToObject toObject, boolean allowMissing, boolean allowNull, T[] onMissing, - T[] onNull) { - super(allowMissing, allowNull, onMissing, onNull); + T[] onNull, NativeArrayType arrayType) { + super(allowMissing, allowNull, onMissing, onNull, arrayType); this.toObject = Objects.requireNonNull(toObject); chunk = new SizedObjectChunk<>(0); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java index 840c47508ea..a9f2858e8ec 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java @@ -7,9 +7,13 @@ import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.json.jackson.RepeaterProcessor.Context; +import io.deephaven.qst.type.GenericType; +import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; abstract class RepeaterProcessorBase implements RepeaterProcessor, Context { @@ -18,14 +22,18 @@ abstract class RepeaterProcessorBase implements RepeaterProcessor, Context { private final T onMissing; private final T onNull; + // Does not need to be T; consider Type.instantType().arrayType() produces long[] + private final GenericType type; + private WritableObjectChunk out; private int ix; - public RepeaterProcessorBase(boolean allowMissing, boolean allowNull, T onMissing, T onNull) { + public RepeaterProcessorBase(boolean allowMissing, boolean allowNull, T onMissing, T onNull, GenericType type) { this.onMissing = onMissing; this.onNull = onNull; this.allowNull = allowNull; this.allowMissing = allowMissing; + this.type = Objects.requireNonNull(type); } public void startImpl(JsonParser parser) throws IOException {} @@ -51,6 +59,11 @@ public final int numColumns() { return 1; } + @Override + public Stream> columnTypes() { + return Stream.of(type); + } + @Override public final Context context() { return this; diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java index 59d3f01baea..c6ac7439b70 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java @@ -72,7 +72,7 @@ final class ShortRepeaterImpl extends RepeaterProcessorBase { private final SizedShortChunk chunk = new SizedShortChunk<>(0); public ShortRepeaterImpl(boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null); + super(allowMissing, allowNull, null, null, Type.shortType().arrayType()); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java index b6ce4d611fd..49aef7ac614 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java @@ -6,10 +6,12 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableShortChunk; +import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; final class ShortValueProcessor implements ValueProcessor { @@ -35,6 +37,11 @@ public int numColumns() { return 1; } + @Override + public Stream> columnTypes() { + return Stream.of(Type.shortType()); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { out.add(toShort.parseValue(parser)); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java index 24db14a6bee..55c82ed6875 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java @@ -55,6 +55,11 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { return new SkipArray(allowMissing, allowNull); } + @Override + public Stream> columnTypes() { + return Stream.empty(); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { @@ -152,6 +157,11 @@ public int numColumns() { return 0; } + @Override + public Stream> columnTypes() { + return Stream.empty(); + } + @Override public void start(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java index d0cd30e4034..593ff36f7ed 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java @@ -36,7 +36,7 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new ObjectValueProcessor<>(this); + return new ObjectValueProcessor<>(this, Type.stringType()); } @Override @@ -65,7 +65,7 @@ public String parseMissing(JsonParser parser) throws IOException { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null); + return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null, Type.stringType().arrayType()); } private String parseFromString(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java index b726a88817d..e1b7f9e4888 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java @@ -203,6 +203,11 @@ public int numColumns() { return numColumns; } + @Override + public Stream> columnTypes() { + return outputTypesImpl(); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java index ef93754d444..21fdd0cc79d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java @@ -17,6 +17,7 @@ import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; final class ValueInnerRepeaterProcessor implements RepeaterProcessor, Context { @@ -30,11 +31,10 @@ final class ValueInnerRepeaterProcessor implements RepeaterProcessor, Context { private List> out; - public ValueInnerRepeaterProcessor(boolean allowMissing, boolean allowNull, ValueProcessor innerProcessor, - List> innerProcessorTypes) { + public ValueInnerRepeaterProcessor(boolean allowMissing, boolean allowNull, ValueProcessor innerProcessor) { this.allowMissing = allowMissing; this.allowNull = allowNull; - final List> innerChunks = innerProcessorTypes.stream() + final List> innerChunks = innerProcessor.columnTypes() .map(Type::arrayType) .map(ObjectProcessor::chunkType) .map(o -> o.makeWritableChunk(1)) @@ -66,6 +66,11 @@ public int numColumns() { return sizedObjectChunks.size(); } + @Override + public Stream> columnTypes() { + return innerProcessor.columnTypes().map(Type::arrayType); + } + @Override public void processNullRepeater(JsonParser parser) throws IOException { if (!allowNull) { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java index c4b2396d2c4..317f45112f1 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java @@ -6,10 +6,12 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import io.deephaven.chunk.WritableChunk; +import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; final class ValueProcessorKvImpl implements ValueProcessor { @@ -39,6 +41,11 @@ public int numColumns() { return keyProcessor.numColumns() + valueProcessor.numColumns(); } + @Override + public Stream> columnTypes() { + return Stream.concat(keyProcessor.columnTypes(), valueProcessor.columnTypes()); + } + @Override public void processCurrentValue(JsonParser parser) throws IOException { if (parser.hasToken(JsonToken.VALUE_NULL)) { From a16b30f974ee3692a9b4cb0df73592e58ec207d0 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Thu, 18 Apr 2024 09:06:11 -0700 Subject: [PATCH 15/53] f --- py/server/deephaven/json/__init__.py | 46 ++++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/py/server/deephaven/json/__init__.py b/py/server/deephaven/json/__init__.py index ee442da63ce..35a81986fc6 100644 --- a/py/server/deephaven/json/__init__.py +++ b/py/server/deephaven/json/__init__.py @@ -51,34 +51,34 @@ # https://deephaven.atlassian.net/browse/DH-15061 # It is important that ValueOptions gets imported before the others. -_JValueOptions = jpy.get_type("io.deephaven.json.ValueOptions") -_JObjectOptions = jpy.get_type("io.deephaven.json.ObjectOptions") -_JArrayOptions = jpy.get_type("io.deephaven.json.ArrayOptions") -_JObjectKvOptions = jpy.get_type("io.deephaven.json.ObjectKvOptions") -_JTupleOptions = jpy.get_type("io.deephaven.json.TupleOptions") -_JObjectFieldOptions = jpy.get_type("io.deephaven.json.ObjectFieldOptions") +_JValueOptions = jpy.get_type("io.deephaven.json.Value") +_JObjectOptions = jpy.get_type("io.deephaven.json.ObjectValue") +_JArrayOptions = jpy.get_type("io.deephaven.json.ArrayValue") +_JObjectKvOptions = jpy.get_type("io.deephaven.json.ObjectKvValue") +_JTupleOptions = jpy.get_type("io.deephaven.json.TupleValue") +_JObjectFieldOptions = jpy.get_type("io.deephaven.json.ObjectField") _JRepeatedFieldBehavior = jpy.get_type( - "io.deephaven.json.ObjectFieldOptions$RepeatedBehavior" + "io.deephaven.json.ObjectField$RepeatedBehavior" ) _JJsonValueTypes = jpy.get_type("io.deephaven.json.JsonValueTypes") -_JBoolOptions = jpy.get_type("io.deephaven.json.BoolOptions") -_JCharOptions = jpy.get_type("io.deephaven.json.CharOptions") -_JByteOptions = jpy.get_type("io.deephaven.json.ByteOptions") -_JShortOptions = jpy.get_type("io.deephaven.json.ShortOptions") -_JIntOptions = jpy.get_type("io.deephaven.json.IntOptions") -_JLongOptions = jpy.get_type("io.deephaven.json.LongOptions") -_JFloatOptions = jpy.get_type("io.deephaven.json.FloatOptions") -_JDoubleOptions = jpy.get_type("io.deephaven.json.DoubleOptions") -_JStringOptions = jpy.get_type("io.deephaven.json.StringOptions") -_JSkipOptions = jpy.get_type("io.deephaven.json.SkipOptions") -_JInstantOptions = jpy.get_type("io.deephaven.json.InstantOptions") -_JInstantNumberOptions = jpy.get_type("io.deephaven.json.InstantNumberOptions") +_JBoolOptions = jpy.get_type("io.deephaven.json.BoolValue") +_JCharOptions = jpy.get_type("io.deephaven.json.CharValue") +_JByteOptions = jpy.get_type("io.deephaven.json.ByteValue") +_JShortOptions = jpy.get_type("io.deephaven.json.ShortValue") +_JIntOptions = jpy.get_type("io.deephaven.json.IntValue") +_JLongOptions = jpy.get_type("io.deephaven.json.LongValue") +_JFloatOptions = jpy.get_type("io.deephaven.json.FloatValue") +_JDoubleOptions = jpy.get_type("io.deephaven.json.DoubleValue") +_JStringOptions = jpy.get_type("io.deephaven.json.StringValue") +_JSkipOptions = jpy.get_type("io.deephaven.json.SkipValue") +_JInstantOptions = jpy.get_type("io.deephaven.json.InstantValue") +_JInstantNumberOptions = jpy.get_type("io.deephaven.json.InstantNumberValue") _JInstantNumberOptionsFormat = jpy.get_type( - "io.deephaven.json.InstantNumberOptions$Format" + "io.deephaven.json.InstantNumberValue$Format" ) -_JBigIntegerOptions = jpy.get_type("io.deephaven.json.BigIntegerOptions") -_JBigDecimalOptions = jpy.get_type("io.deephaven.json.BigDecimalOptions") -_JAnyOptions = jpy.get_type("io.deephaven.json.AnyOptions") +_JBigIntegerOptions = jpy.get_type("io.deephaven.json.BigIntegerValue") +_JBigDecimalOptions = jpy.get_type("io.deephaven.json.BigDecimalValue") +_JAnyOptions = jpy.get_type("io.deephaven.json.AnyValue") _VALUE_STRING = _JJsonValueTypes.STRING From f564bc453f5b0c97a1d34644e96af10a29c42a34 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Mon, 13 May 2024 14:49:46 -0700 Subject: [PATCH 16/53] f --- .../java/io/deephaven/json/jackson/ByteBufferInputStream.java | 2 +- .../main/java/io/deephaven/json/jackson/JacksonSource.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteBufferInputStream.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteBufferInputStream.java index f1705386a19..989a2586e07 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteBufferInputStream.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteBufferInputStream.java @@ -14,7 +14,7 @@ public static InputStream of(ByteBuffer buffer) { if (buffer.hasArray()) { return new ByteArrayInputStream(buffer.array(), buffer.position(), buffer.remaining()); } else { - return new ByteBufferInputStream(buffer.slice()); + return new ByteBufferInputStream(buffer.asReadOnlyBuffer()); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java index c26c5454ca7..0f31732de4a 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java @@ -67,11 +67,9 @@ public static JsonParser of(JsonFactory factory, char[] array, int pos, int len) public static JsonParser of(JsonFactory factory, CharBuffer buffer) throws IOException { // TODO: suggest jackson build this in if (buffer.hasArray()) { - return of(factory, buffer.array(), buffer.position(), buffer.remaining()); + return of(factory, buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); } // We could build CharBufferReader. Surprised it's not build into JDK. - - throw new RuntimeException("Only supports CharBuffer when backed by array"); } } From f4c674e6af90f15a3741676bf8d6e8057df17c18 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Tue, 21 May 2024 16:25:18 -0700 Subject: [PATCH 17/53] f --- .../java/io/deephaven/json/jackson/ByteBufferInputStream.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteBufferInputStream.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteBufferInputStream.java index 989a2586e07..17906ad0f84 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteBufferInputStream.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteBufferInputStream.java @@ -12,7 +12,8 @@ final class ByteBufferInputStream extends InputStream { public static InputStream of(ByteBuffer buffer) { if (buffer.hasArray()) { - return new ByteArrayInputStream(buffer.array(), buffer.position(), buffer.remaining()); + return new ByteArrayInputStream(buffer.array(), buffer.arrayOffset() + buffer.position(), + buffer.remaining()); } else { return new ByteBufferInputStream(buffer.asReadOnlyBuffer()); } From 6f45aa7cf9f400e2a853988a1f22832d5942f6fc Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 22 May 2024 12:41:45 -0700 Subject: [PATCH 18/53] some review responses --- .../chunk/util/hashing/ObjectChunkEquals.java | 2 +- .../processor/NamedObjectProcessor.java | 2 +- .../io/deephaven/bson/jackson/TestHelper.java | 2 + .../json/jackson/BigIntegerMixin.java | 4 +- .../io/deephaven/json/jackson/BoolMixin.java | 56 ++++--- .../json/jackson/InstantNumberMixin.java | 157 ++++-------------- .../io/deephaven/json/jackson/Parsing.java | 35 +++- .../io/deephaven/json/jackson/TupleMixin.java | 2 +- .../jackson/ValueInnerRepeaterProcessor.java | 8 + .../java/io/deephaven/chunk/ChunkEquals.java | 91 ---------- .../java/io/deephaven/json/TestHelper.java | 105 +----------- 11 files changed, 113 insertions(+), 351 deletions(-) delete mode 100644 extensions/json-jackson/src/test/java/io/deephaven/chunk/ChunkEquals.java diff --git a/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ObjectChunkEquals.java b/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ObjectChunkEquals.java index fe2ae7964ae..22cd5f99448 100644 --- a/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ObjectChunkEquals.java +++ b/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ObjectChunkEquals.java @@ -252,7 +252,7 @@ public void andEqualPairs(IntChunk chunkPositionsToCheckForEqual // region eq static private boolean eq(Object lhs, Object rhs) { - return Objects.equals(lhs, rhs); + return Objects.deepEquals(lhs, rhs); } // endregion eq diff --git a/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java b/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java index caf82ba980b..f47c8caa3b6 100644 --- a/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java +++ b/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java @@ -87,7 +87,7 @@ default NamedObjectProcessor named(Type inputType) { final void checkSizes() { if (names().size() != processor().size()) { throw new IllegalArgumentException( - String.format("Unmatched sizes; columnNames().size()=%d, processor().size()=%d", + String.format("Unmatched sizes; names().size()=%d, processor().size()=%d", names().size(), processor().size())); } } diff --git a/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java index fe0ae12e30f..39cff473bbb 100644 --- a/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java +++ b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java @@ -16,6 +16,7 @@ import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.chunk.attributes.Any; +import io.deephaven.chunk.util.hashing.ChunkEquals; import io.deephaven.processor.ObjectProcessor; import java.io.IOException; @@ -68,6 +69,7 @@ public static void parse(ObjectProcessor processor, List rows, static void check(Chunk actual, Chunk expected) { assertThat(actual.getChunkType()).isEqualTo(expected.getChunkType()); assertThat(actual.size()).isEqualTo(expected.size()); + //ChunkEquals.makeEqual(actual.getChunkType()).equalReduce() switch (actual.getChunkType()) { case Boolean: check(actual.asBooleanChunk(), expected.asBooleanChunk()); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java index d71c1f096de..7a42fe0f950 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java @@ -71,14 +71,14 @@ private BigInteger parseFromInt(JsonParser parser) throws IOException { if (!allowNumberInt()) { throw Parsing.mismatch(parser, BigInteger.class); } - return parser.getBigIntegerValue(); + return Parsing.parseIntAsBigInteger(parser); } private BigInteger parseFromDecimal(JsonParser parser) throws IOException { if (!allowDecimal()) { throw Parsing.mismatch(parser, BigInteger.class); } - return parser.getBigIntegerValue(); + return Parsing.parseDecimalAsBigInteger(parser); } private BigInteger parseFromString(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java index 15df180cb66..728c3bd9ac5 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java @@ -16,8 +16,18 @@ import java.util.stream.Stream; final class BoolMixin extends Mixin implements ToByte { + + private final Boolean onNull; + private final Boolean onMissing; + private final byte onNullByte; + private final byte onMissingByte; + public BoolMixin(BoolValue options, JsonFactory factory) { super(factory, options); + onNull = options.onNull().orElse(null); + onMissing = options.onMissing().orElse(null); + onNullByte = BooleanUtils.booleanAsByte(onNull); + onMissingByte = BooleanUtils.booleanAsByte(onMissing); } @Override @@ -53,7 +63,7 @@ public byte parseValue(JsonParser parser) throws IOException { case FIELD_NAME: return parseFromString(parser); } - throw Parsing.mismatch(parser, boolean.class); + throw Parsing.mismatch(parser, Boolean.class); } @Override @@ -81,7 +91,7 @@ public Boolean parseValue(JsonParser parser) throws IOException { case FIELD_NAME: return parseFromStringBoolean(parser); } - throw Parsing.mismatch(parser, boolean.class); + throw Parsing.mismatch(parser, Boolean.class); } @Override @@ -92,44 +102,30 @@ public Boolean parseMissing(JsonParser parser) throws IOException { private byte parseFromString(JsonParser parser) throws IOException { if (!allowString()) { - throw Parsing.mismatch(parser, boolean.class); + throw Parsing.mismatch(parser, Boolean.class); } if (!allowNull()) { final byte res = Parsing.parseStringAsByteBool(parser, BooleanUtils.NULL_BOOLEAN_AS_BYTE); if (res == BooleanUtils.NULL_BOOLEAN_AS_BYTE) { - throw Parsing.mismatch(parser, boolean.class); + throw Parsing.mismatch(parser, Boolean.class); } return res; } - return Parsing.parseStringAsByteBool(parser, BooleanUtils.booleanAsByte(options.onNull().orElse(null))); + return Parsing.parseStringAsByteBool(parser, onNullByte); } private byte parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, boolean.class); - } - return BooleanUtils.booleanAsByte(options.onNull().orElse(null)); - } - - private byte parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, boolean.class); - } - return BooleanUtils.booleanAsByte(options.onMissing().orElse(null)); - } - - private Boolean parseFromNullBoolean(JsonParser parser) throws IOException { if (!allowNull()) { throw Parsing.mismatch(parser, Boolean.class); } - return options.onNull().orElse(null); + return onNullByte; } - private Boolean parseFromMissingBoolean(JsonParser parser) throws IOException { + private byte parseFromMissing(JsonParser parser) throws IOException { if (!allowMissing()) { throw Parsing.mismatchMissing(parser, Boolean.class); } - return options.onMissing().orElse(null); + return onMissingByte; } private Boolean parseFromStringBoolean(JsonParser parser) throws IOException { @@ -143,6 +139,20 @@ private Boolean parseFromStringBoolean(JsonParser parser) throws IOException { } return result; } - return Parsing.parseStringAsBoolean(parser, options.onNull().orElse(null)); + return Parsing.parseStringAsBoolean(parser, onNull); + } + + private Boolean parseFromNullBoolean(JsonParser parser) throws IOException { + if (!allowNull()) { + throw Parsing.mismatch(parser, Boolean.class); + } + return onNull; + } + + private Boolean parseFromMissingBoolean(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw Parsing.mismatchMissing(parser, Boolean.class); + } + return onMissing; } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java index 2db837a65b9..967c08346eb 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java @@ -10,11 +10,12 @@ import io.deephaven.time.DateTimeUtils; import java.io.IOException; +import java.math.BigInteger; import java.time.Instant; import java.util.List; import java.util.stream.Stream; -final class InstantNumberMixin extends Mixin { +final class InstantNumberMixin extends Mixin { private final long onNull; private final long onMissing; @@ -53,27 +54,47 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { private LongValueProcessor.ToLong function() { switch (options.format()) { case EPOCH_SECONDS: - return new EpochSeconds(); + return new Impl(9); case EPOCH_MILLIS: - return new EpochMillis(); + return new Impl(6); case EPOCH_MICROS: - return new EpochMicros(); + return new Impl(3); case EPOCH_NANOS: - return new EpochNanos(); + return new Impl(0); default: throw new IllegalStateException(); } } - private abstract class Base implements LongValueProcessor.ToLong { + private class Impl implements LongValueProcessor.ToLong { - abstract long parseFromInt(JsonParser parser) throws IOException; + private final int scaled; + private final int mult; - abstract long parseFromDecimal(JsonParser parser) throws IOException; + Impl(int scaled) { + this.scaled = scaled; + this.mult = BigInteger.valueOf(10).pow(scaled).intValueExact(); + } - abstract long parseFromString(JsonParser parser) throws IOException; + private long parseFromInt(JsonParser parser) throws IOException { + return mult * Parsing.parseIntAsLong(parser); + } - abstract long parseFromDecimalString(JsonParser parser) throws IOException; + private long parseFromDecimal(JsonParser parser) throws IOException { + // We need to parse w/ BigDecimal in the case of VALUE_NUMBER_FLOAT, otherwise we might lose accuracy + // jshell> (long)(1703292532.123456789 * 1000000000) + // $4 ==> 1703292532123456768 + // See InstantNumberOptionsTest + return Parsing.parseDecimalAsScaledTruncatedLong(parser, scaled); + } + + private long parseFromString(JsonParser parser) throws IOException { + return mult * Parsing.parseStringAsLong(parser); + } + + private long parseFromDecimalString(JsonParser parser) throws IOException { + return Parsing.parseDecimalStringAsScaledTruncatedLong(parser, scaled); + } @Override public final long parseValue(JsonParser parser) throws IOException { @@ -113,120 +134,4 @@ public final long parseMissing(JsonParser parser) throws IOException { return onMissing; } } - - // We need to parse w/ BigDecimal in the case of VALUE_NUMBER_FLOAT, otherwise we might lose accuracy - // jshell> (long)(1703292532.123456789 * 1000000000) - // $4 ==> 1703292532123456768 - // See InstantNumberOptionsTest - - private class EpochSeconds extends Base { - - private static final int SCALED = 9; - private static final int MULT = 1_000_000_000; - - private long epochNanos(long epochSeconds) { - return MULT * epochSeconds; - } - - @Override - long parseFromInt(JsonParser parser) throws IOException { - return epochNanos(Parsing.parseIntAsLong(parser)); - } - - @Override - long parseFromDecimal(JsonParser parser) throws IOException { - return Parsing.parseDecimalAsScaledTruncatedLong(parser, SCALED); - } - - @Override - long parseFromString(JsonParser parser) throws IOException { - return epochNanos(Parsing.parseStringAsLong(parser)); - } - - @Override - long parseFromDecimalString(JsonParser parser) throws IOException { - return Parsing.parseDecimalStringAsScaledTruncatedLong(parser, SCALED); - } - } - - private class EpochMillis extends Base { - private static final int SCALED = 6; - private static final int MULT = 1_000_000; - - private long epochNanos(long epochMillis) { - return MULT * epochMillis; - } - - @Override - long parseFromInt(JsonParser parser) throws IOException { - return epochNanos(Parsing.parseIntAsLong(parser)); - } - - @Override - long parseFromDecimal(JsonParser parser) throws IOException { - return Parsing.parseDecimalAsScaledTruncatedLong(parser, SCALED); - } - - @Override - long parseFromString(JsonParser parser) throws IOException { - return epochNanos(Parsing.parseStringAsLong(parser)); - } - - @Override - long parseFromDecimalString(JsonParser parser) throws IOException { - return Parsing.parseDecimalStringAsScaledTruncatedLong(parser, SCALED); - } - } - - private class EpochMicros extends Base { - private static final int SCALED = 3; - private static final int MULT = 1_000; - - private long epochNanos(long epochMicros) { - return MULT * epochMicros; - } - - @Override - long parseFromInt(JsonParser parser) throws IOException { - return epochNanos(Parsing.parseIntAsLong(parser)); - } - - @Override - long parseFromDecimal(JsonParser parser) throws IOException { - return Parsing.parseDecimalAsScaledTruncatedLong(parser, SCALED); - } - - @Override - long parseFromString(JsonParser parser) throws IOException { - return epochNanos(Parsing.parseStringAsLong(parser)); - } - - @Override - long parseFromDecimalString(JsonParser parser) throws IOException { - return Parsing.parseDecimalStringAsScaledTruncatedLong(parser, SCALED); - } - } - - private class EpochNanos extends Base { - - @Override - long parseFromInt(JsonParser parser) throws IOException { - return Parsing.parseIntAsLong(parser); - } - - @Override - long parseFromDecimal(JsonParser parser) throws IOException { - return Parsing.parseDecimalAsTruncatedLong(parser); - } - - @Override - long parseFromString(JsonParser parser) throws IOException { - return Parsing.parseStringAsLong(parser); - } - - @Override - long parseFromDecimalString(JsonParser parser) throws IOException { - return Parsing.parseDecimalStringAsTruncatedLong(parser); - } - } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java index 8e48ef1de84..fb5b98616cf 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java @@ -111,6 +111,8 @@ static char parseStringAsChar(JsonParser parser) throws IOException { return text.charAt(0); } + // --------------------------------------------------------------------------- + static byte parseIntAsByte(JsonParser parser) throws IOException { return parser.getByteValue(); } @@ -128,6 +130,9 @@ static byte parseStringAsByte(JsonParser parser) throws IOException { return (byte) parseStringAsInt(parser); } + // --------------------------------------------------------------------------- + + static short parseIntAsShort(JsonParser parser) throws IOException { return parser.getShortValue(); } @@ -145,6 +150,8 @@ static short parseStringAsShort(JsonParser parser) throws IOException { return (short) parseStringAsInt(parser); } + // --------------------------------------------------------------------------- + static int parseIntAsInt(JsonParser parser) throws IOException { return parser.getIntValue(); } @@ -154,6 +161,8 @@ static int parseDecimalAsTruncatedInt(JsonParser parser) throws IOException { return parser.getIntValue(); } + // --------------------------------------------------------------------------- + static long parseIntAsLong(JsonParser parser) throws IOException { return parser.getLongValue(); } @@ -173,6 +182,8 @@ static long parseDecimalAsScaledTruncatedLong(JsonParser parser, int n) throws I return parser.getDecimalValue().scaleByPowerOfTen(n).longValue(); } + // --------------------------------------------------------------------------- + static String parseStringAsString(JsonParser parser) throws IOException { return parser.getText(); } @@ -189,9 +200,7 @@ static String parseBoolAsString(JsonParser parser) throws IOException { return parser.getText(); } - static BigDecimal parseDecimalAsBigDecimal(JsonParser parser) throws IOException { - return parser.getDecimalValue(); - } + // --------------------------------------------------------------------------- static float parseNumberAsFloat(JsonParser parser) throws IOException { // TODO: improve after https://github.com/FasterXML/jackson-core/issues/1229 @@ -203,6 +212,8 @@ static double parseNumberAsDouble(JsonParser parser) throws IOException { return parser.getDoubleValue(); } + // --------------------------------------------------------------------------- + static int parseStringAsInt(JsonParser parser) throws IOException { if (parser.hasTextCharacters()) { // TODO: potential to write parseInt optimized for char[] @@ -259,6 +270,16 @@ static double parseStringAsDouble(JsonParser parser) throws IOException { : Double.parseDouble(parser.getText()); } + // --------------------------------------------------------------------------- + + static BigInteger parseIntAsBigInteger(JsonParser parser) throws IOException { + return parser.getBigIntegerValue(); + } + + static BigInteger parseDecimalAsBigInteger(JsonParser parser) throws IOException { + return parser.getBigIntegerValue(); + } + static BigInteger parseStringAsBigInteger(JsonParser parser) throws IOException { // TODO: improve after https://github.com/FasterXML/jackson-core/issues/1229 return parser.isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER) @@ -270,6 +291,12 @@ static BigInteger parseStringAsTruncatedBigInteger(JsonParser parser) throws IOE return parseStringAsBigDecimal(parser).toBigInteger(); } + // --------------------------------------------------------------------------- + + static BigDecimal parseDecimalAsBigDecimal(JsonParser parser) throws IOException { + return parser.getDecimalValue(); + } + static BigDecimal parseStringAsBigDecimal(JsonParser parser) throws IOException { // TODO: improve after https://github.com/FasterXML/jackson-core/issues/1229 return parser.isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER) @@ -277,6 +304,8 @@ static BigDecimal parseStringAsBigDecimal(JsonParser parser) throws IOException : new BigDecimal(parser.getText()); } + // --------------------------------------------------------------------------- + private static float parseStringAsFloatFast(JsonParser p) throws IOException { return p.hasTextCharacters() ? JavaFloatParser.parseFloat(p.getTextCharacters(), p.getTextOffset(), p.getTextLength()) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java index 10ba63bada6..273d9e9043f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java @@ -165,7 +165,7 @@ public Context context() { @Override public void processNullRepeater(JsonParser parser) throws IOException { - if (!allowMissing()) { + if (!allowNull()) { throw Parsing.mismatch(parser, Object.class); } for (RepeaterProcessor value : values) { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java index 21fdd0cc79d..cee6111fd5f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java @@ -4,6 +4,7 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.ChunkType; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.chunk.sized.SizedObjectChunk; @@ -37,6 +38,7 @@ public ValueInnerRepeaterProcessor(boolean allowMissing, boolean allowNull, Valu final List> innerChunks = innerProcessor.columnTypes() .map(Type::arrayType) .map(ObjectProcessor::chunkType) + .peek(ValueInnerRepeaterProcessor::checkObjectChunk) .map(o -> o.makeWritableChunk(1)) .peek(wc -> wc.setSize(0)) .collect(Collectors.toList()); @@ -50,6 +52,12 @@ public ValueInnerRepeaterProcessor(boolean allowMissing, boolean allowNull, Valu .collect(Collectors.toList()); } + static void checkObjectChunk(ChunkType chunkType) { + if (chunkType != ChunkType.Object) { + throw new IllegalStateException(); + } + } + @Override public void setContext(List> out) { // noinspection unchecked,rawtypes diff --git a/extensions/json-jackson/src/test/java/io/deephaven/chunk/ChunkEquals.java b/extensions/json-jackson/src/test/java/io/deephaven/chunk/ChunkEquals.java deleted file mode 100644 index 4d68feab523..00000000000 --- a/extensions/json-jackson/src/test/java/io/deephaven/chunk/ChunkEquals.java +++ /dev/null @@ -1,91 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.chunk; - -import java.util.Arrays; -import java.util.Objects; - -// todo: current dupe w/ extensions-kafka-v2 -public class ChunkEquals { - - // todo: should probably be moved into respective classes - - - - public static boolean equals(Chunk x, Chunk y) { - if (!x.getChunkType().equals(y.getChunkType())) { - return false; - } - switch (x.getChunkType()) { - case Boolean: - return equals((BooleanChunk) x, (BooleanChunk) y); - case Char: - return equals((CharChunk) x, (CharChunk) y); - case Byte: - return equals((ByteChunk) x, (ByteChunk) y); - case Short: - return equals((ShortChunk) x, (ShortChunk) y); - case Int: - return equals((IntChunk) x, (IntChunk) y); - case Long: - return equals((LongChunk) x, (LongChunk) y); - case Float: - return equals((FloatChunk) x, (FloatChunk) y); - case Double: - return equals((DoubleChunk) x, (DoubleChunk) y); - case Object: - // Note: we can't be this precise b/c io.deephaven.chunk.ObjectChunk.makeArray doesn't actually use the - // typed - // class - /* - * if (!actualObjectChunk.data.getClass().equals(expectedObjectChunk.data.getClass())) { return false; } - */ - return equals((ObjectChunk) x, (ObjectChunk) y); - default: - throw new IllegalStateException(); - } - } - - public static boolean equals(BooleanChunk actual, BooleanChunk expected) { - return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); - } - - public static boolean equals(ByteChunk actual, ByteChunk expected) { - return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); - } - - public static boolean equals(CharChunk actual, CharChunk expected) { - return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); - } - - public static boolean equals(ShortChunk actual, ShortChunk expected) { - return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); - } - - public static boolean equals(IntChunk actual, IntChunk expected) { - return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); - } - - public static boolean equals(LongChunk actual, LongChunk expected) { - return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); - } - - public static boolean equals(FloatChunk actual, FloatChunk expected) { - return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); - } - - public static boolean equals(DoubleChunk actual, DoubleChunk expected) { - return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size); - } - - public static boolean equals(ObjectChunk actual, ObjectChunk expected) { - // this is probably only suitable for unit testing, not something we'd want in real code? - return Arrays.equals(actual.data, actual.offset, actual.size, expected.data, expected.offset, expected.size, - ChunkEquals::fakeDeepCompareForEquals); - } - - private static int fakeDeepCompareForEquals(Object a, Object b) { - return Objects.deepEquals(a, b) ? 0 : 1; - } -} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java index c608196092b..48d862f9a42 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java @@ -3,19 +3,11 @@ // package io.deephaven.json; -import io.deephaven.chunk.BooleanChunk; -import io.deephaven.chunk.ByteChunk; -import io.deephaven.chunk.CharChunk; import io.deephaven.chunk.Chunk; -import io.deephaven.chunk.DoubleChunk; -import io.deephaven.chunk.FloatChunk; -import io.deephaven.chunk.IntChunk; -import io.deephaven.chunk.LongChunk; -import io.deephaven.chunk.ObjectChunk; -import io.deephaven.chunk.ShortChunk; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.chunk.attributes.Any; +import io.deephaven.chunk.util.hashing.ChunkEquals; import io.deephaven.json.jackson.JacksonProvider; import io.deephaven.processor.ObjectProcessor; @@ -76,99 +68,6 @@ public static void parse(ObjectProcessor processor, List rows, static void check(Chunk actual, Chunk expected) { assertThat(actual.getChunkType()).isEqualTo(expected.getChunkType()); assertThat(actual.size()).isEqualTo(expected.size()); - switch (actual.getChunkType()) { - case Boolean: - check(actual.asBooleanChunk(), expected.asBooleanChunk()); - break; - case Char: - check(actual.asCharChunk(), expected.asCharChunk()); - break; - case Byte: - check(actual.asByteChunk(), expected.asByteChunk()); - break; - case Short: - check(actual.asShortChunk(), expected.asShortChunk()); - break; - case Int: - check(actual.asIntChunk(), expected.asIntChunk()); - break; - case Long: - check(actual.asLongChunk(), expected.asLongChunk()); - break; - case Float: - check(actual.asFloatChunk(), expected.asFloatChunk()); - break; - case Double: - check(actual.asDoubleChunk(), expected.asDoubleChunk()); - break; - case Object: - check(actual.asObjectChunk(), expected.asObjectChunk()); - break; - default: - throw new IllegalStateException(); - } - } - - private static void check(BooleanChunk actual, BooleanChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(CharChunk actual, CharChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(ByteChunk actual, ByteChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(ShortChunk actual, ShortChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(IntChunk actual, IntChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(LongChunk actual, LongChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(FloatChunk actual, FloatChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(DoubleChunk actual, DoubleChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(ObjectChunk actual, ObjectChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } + assertThat(ChunkEquals.makeEqual(actual.getChunkType()).equalReduce(actual, expected)).isTrue(); } } From 6d4ebef3f5b8f3cae342792489201a45beab6f1d Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Thu, 23 May 2024 14:33:58 -0700 Subject: [PATCH 19/53] f --- .../io/deephaven/bson/jackson/TestHelper.java | 105 +----------------- .../json/jackson/InstantNumberMixin.java | 2 +- .../replicators/ReplicateHashing.java | 3 +- 3 files changed, 4 insertions(+), 106 deletions(-) diff --git a/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java index 39cff473bbb..44be6ee528c 100644 --- a/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java +++ b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java @@ -3,16 +3,7 @@ // package io.deephaven.bson.jackson; -import io.deephaven.chunk.BooleanChunk; -import io.deephaven.chunk.ByteChunk; -import io.deephaven.chunk.CharChunk; import io.deephaven.chunk.Chunk; -import io.deephaven.chunk.DoubleChunk; -import io.deephaven.chunk.FloatChunk; -import io.deephaven.chunk.IntChunk; -import io.deephaven.chunk.LongChunk; -import io.deephaven.chunk.ObjectChunk; -import io.deephaven.chunk.ShortChunk; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.chunk.attributes.Any; @@ -69,100 +60,6 @@ public static void parse(ObjectProcessor processor, List rows, static void check(Chunk actual, Chunk expected) { assertThat(actual.getChunkType()).isEqualTo(expected.getChunkType()); assertThat(actual.size()).isEqualTo(expected.size()); - //ChunkEquals.makeEqual(actual.getChunkType()).equalReduce() - switch (actual.getChunkType()) { - case Boolean: - check(actual.asBooleanChunk(), expected.asBooleanChunk()); - break; - case Char: - check(actual.asCharChunk(), expected.asCharChunk()); - break; - case Byte: - check(actual.asByteChunk(), expected.asByteChunk()); - break; - case Short: - check(actual.asShortChunk(), expected.asShortChunk()); - break; - case Int: - check(actual.asIntChunk(), expected.asIntChunk()); - break; - case Long: - check(actual.asLongChunk(), expected.asLongChunk()); - break; - case Float: - check(actual.asFloatChunk(), expected.asFloatChunk()); - break; - case Double: - check(actual.asDoubleChunk(), expected.asDoubleChunk()); - break; - case Object: - check(actual.asObjectChunk(), expected.asObjectChunk()); - break; - default: - throw new IllegalStateException(); - } - } - - private static void check(BooleanChunk actual, BooleanChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(CharChunk actual, CharChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(ByteChunk actual, ByteChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(ShortChunk actual, ShortChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(IntChunk actual, IntChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(LongChunk actual, LongChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(FloatChunk actual, FloatChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(DoubleChunk actual, DoubleChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } - } - - private static void check(ObjectChunk actual, ObjectChunk expected) { - final int size = actual.size(); - for (int i = 0; i < size; ++i) { - assertThat(actual.get(i)).isEqualTo(expected.get(i)); - } + assertThat(ChunkEquals.makeEqual(actual.getChunkType()).equalReduce(actual, expected)).isTrue(); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java index 967c08346eb..2b04d513ecb 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java @@ -15,7 +15,7 @@ import java.util.List; import java.util.stream.Stream; -final class InstantNumberMixin extends Mixin { +final class InstantNumberMixin extends Mixin { private final long onNull; private final long onMissing; diff --git a/replication/static/src/main/java/io/deephaven/replicators/ReplicateHashing.java b/replication/static/src/main/java/io/deephaven/replicators/ReplicateHashing.java index 0d5e0a50835..7c52b3ef847 100644 --- a/replication/static/src/main/java/io/deephaven/replicators/ReplicateHashing.java +++ b/replication/static/src/main/java/io/deephaven/replicators/ReplicateHashing.java @@ -75,8 +75,9 @@ private static void fixupObjectChunkEquals(String objectPath) throws IOException final File objectFile = new File(objectPath); List lines = FileUtils.readLines(objectFile, Charset.defaultCharset()); lines = addImport(lines, Objects.class); + // TODO: verify this is safe FileUtils.writeLines(objectFile, simpleFixup(fixupChunkAttributes(lines), - "eq", "lhs == rhs", "Objects.equals(lhs, rhs)")); + "eq", "lhs == rhs", "Objects.deepEquals(lhs, rhs)")); } private static void fixupBooleanCompact(String booleanPath) throws IOException { From 32841a5e147f7dd8825feb0297e77c543323baf1 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Fri, 24 May 2024 09:52:43 -0700 Subject: [PATCH 20/53] f --- .../json/jackson/BigIntegerMixin.java | 2 +- .../io/deephaven/json/jackson/ByteMixin.java | 4 +- .../json/jackson/InstantNumberMixin.java | 4 +- .../io/deephaven/json/jackson/IntMixin.java | 4 +- .../io/deephaven/json/jackson/LongMixin.java | 7 +-- .../io/deephaven/json/jackson/Parsing.java | 47 +++++++------------ .../io/deephaven/json/jackson/ShortMixin.java | 4 +- 7 files changed, 29 insertions(+), 43 deletions(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java index 7a42fe0f950..0c6fb180667 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java @@ -86,7 +86,7 @@ private BigInteger parseFromString(JsonParser parser) throws IOException { throw Parsing.mismatch(parser, BigInteger.class); } return allowDecimal() - ? Parsing.parseStringAsTruncatedBigInteger(parser) + ? Parsing.parseDecimalStringAsBigInteger(parser) : Parsing.parseStringAsBigInteger(parser); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java index 90205124389..e16f20797bf 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java @@ -109,7 +109,7 @@ private byte parseFromDecimal(JsonParser parser) throws IOException { if (!allowDecimal()) { throw Parsing.mismatch(parser, byte.class); } - return Parsing.parseDecimalAsTruncatedByte(parser); + return Parsing.parseDecimalAsByte(parser); } private byte parseFromString(JsonParser parser) throws IOException { @@ -117,7 +117,7 @@ private byte parseFromString(JsonParser parser) throws IOException { throw Parsing.mismatch(parser, byte.class); } return allowDecimal() - ? Parsing.parseDecimalStringAsTruncatedByte(parser) + ? Parsing.parseDecimalStringAsByte(parser) : Parsing.parseStringAsByte(parser); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java index 2b04d513ecb..dc609d5d3d2 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java @@ -85,7 +85,7 @@ private long parseFromDecimal(JsonParser parser) throws IOException { // jshell> (long)(1703292532.123456789 * 1000000000) // $4 ==> 1703292532123456768 // See InstantNumberOptionsTest - return Parsing.parseDecimalAsScaledTruncatedLong(parser, scaled); + return Parsing.parseDecimalAsScaledLong(parser, scaled); } private long parseFromString(JsonParser parser) throws IOException { @@ -93,7 +93,7 @@ private long parseFromString(JsonParser parser) throws IOException { } private long parseFromDecimalString(JsonParser parser) throws IOException { - return Parsing.parseDecimalStringAsScaledTruncatedLong(parser, scaled); + return Parsing.parseDecimalStringAsScaledLong(parser, scaled); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java index 9bd0c459162..423a8dfe170 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -110,7 +110,7 @@ private int parseFromDecimal(JsonParser parser) throws IOException { if (!allowDecimal()) { throw Parsing.mismatch(parser, int.class); } - return Parsing.parseDecimalAsTruncatedInt(parser); + return Parsing.parseDecimalAsInt(parser); } private int parseFromString(JsonParser parser) throws IOException { @@ -118,7 +118,7 @@ private int parseFromString(JsonParser parser) throws IOException { throw Parsing.mismatch(parser, int.class); } return allowDecimal() - ? Parsing.parseDecimalStringAsTruncatedInt(parser) + ? Parsing.parseDecimalStringAsInt(parser) : Parsing.parseStringAsInt(parser); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java index a548b50495d..899fc5ca824 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java @@ -76,8 +76,7 @@ private long parseFromDecimal(JsonParser parser) throws IOException { if (!allowDecimal()) { throw Parsing.mismatch(parser, long.class); } - // TODO: allow caller to configure between lossy long and truncated long? - return Parsing.parseDecimalAsLossyLong(parser); + return Parsing.parseDecimalAsLong(parser); } private long parseFromString(JsonParser parser) throws IOException { @@ -85,9 +84,7 @@ private long parseFromString(JsonParser parser) throws IOException { throw Parsing.mismatch(parser, long.class); } return allowDecimal() - // TODO: allow caller to configure between lossy long and truncated long? - // ? Helpers.parseDecimalStringAsLossyLong(parser) - ? Parsing.parseDecimalStringAsTruncatedLong(parser) + ? Parsing.parseDecimalStringAsLong(parser) : Parsing.parseStringAsLong(parser); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java index fb5b98616cf..f4507b48ef7 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java @@ -117,13 +117,13 @@ static byte parseIntAsByte(JsonParser parser) throws IOException { return parser.getByteValue(); } - static byte parseDecimalAsTruncatedByte(JsonParser parser) throws IOException { + static byte parseDecimalAsByte(JsonParser parser) throws IOException { return parser.getByteValue(); } - static byte parseDecimalStringAsTruncatedByte(JsonParser parser) throws IOException { + static byte parseDecimalStringAsByte(JsonParser parser) throws IOException { // parse as float then cast to byte; no loss of whole number part (32 bit -> 8 bit) if in range - return (byte) Parsing.parseStringAsFloat(parser); + return (byte) parseStringAsFloat(parser); } static byte parseStringAsByte(JsonParser parser) throws IOException { @@ -137,13 +137,14 @@ static short parseIntAsShort(JsonParser parser) throws IOException { return parser.getShortValue(); } - static short parseDecimalAsTruncatedShort(JsonParser parser) throws IOException { + static short parseDecimalAsShort(JsonParser parser) throws IOException { + // parse as double then cast to short; no loss of whole number part (64 bit -> 16 bit) return parser.getShortValue(); } - static short parseDecimalStringAsTruncatedShort(JsonParser parser) throws IOException { + static short parseDecimalStringAsShort(JsonParser parser) throws IOException { // parse as float then cast to short; no loss of whole number part (32 bit -> 16 bit) if in range - return (short) Parsing.parseStringAsFloat(parser); + return (short) parseStringAsFloat(parser); } static short parseStringAsShort(JsonParser parser) throws IOException { @@ -156,8 +157,8 @@ static int parseIntAsInt(JsonParser parser) throws IOException { return parser.getIntValue(); } - static int parseDecimalAsTruncatedInt(JsonParser parser) throws IOException { - // parser as double then cast to int; no loss of whole number part (64 bit -> 32 bit) + static int parseDecimalAsInt(JsonParser parser) throws IOException { + // parse as double then cast to int; no loss of whole number part (64 bit -> 32 bit) return parser.getIntValue(); } @@ -167,18 +168,13 @@ static long parseIntAsLong(JsonParser parser) throws IOException { return parser.getLongValue(); } - static long parseDecimalAsLossyLong(JsonParser parser) throws IOException { - // parser as double then cast to long; loses info (64 bit -> 64 bit) - return parser.getLongValue(); - } - - static long parseDecimalAsTruncatedLong(JsonParser parser) throws IOException { - // io.deephaven.json.InstantNumberOptionsTest.epochNanosDecimal fails - // return parser.getLongValue(); + static long parseDecimalAsLong(JsonParser parser) throws IOException { + // Parsing as double then casting to long will lose precision. + // (long)9223372036854775806.0 == 9223372036854775807 return parser.getDecimalValue().longValue(); } - static long parseDecimalAsScaledTruncatedLong(JsonParser parser, int n) throws IOException { + static long parseDecimalAsScaledLong(JsonParser parser, int n) throws IOException { return parser.getDecimalValue().scaleByPowerOfTen(n).longValue(); } @@ -216,7 +212,6 @@ static double parseNumberAsDouble(JsonParser parser) throws IOException { static int parseStringAsInt(JsonParser parser) throws IOException { if (parser.hasTextCharacters()) { - // TODO: potential to write parseInt optimized for char[] final int len = parser.getTextLength(); final CharSequence cs = CharBuffer.wrap(parser.getTextCharacters(), parser.getTextOffset(), len); return Integer.parseInt(cs, 0, len, 10); @@ -225,14 +220,13 @@ static int parseStringAsInt(JsonParser parser) throws IOException { } } - static int parseDecimalStringAsTruncatedInt(JsonParser parser) throws IOException { + static int parseDecimalStringAsInt(JsonParser parser) throws IOException { // parse as double then cast to int; no loss of whole number part (64 bit -> 32 bit) - return (int) Parsing.parseStringAsDouble(parser); + return (int) parseStringAsDouble(parser); } static long parseStringAsLong(JsonParser parser) throws IOException { if (parser.hasTextCharacters()) { - // TODO: potential to write parseInt optimized for char[] final int len = parser.getTextLength(); final CharSequence cs = CharBuffer.wrap(parser.getTextCharacters(), parser.getTextOffset(), len); return Long.parseLong(cs, 0, len, 10); @@ -241,17 +235,12 @@ static long parseStringAsLong(JsonParser parser) throws IOException { } } - static long parseDecimalStringAsLossyLong(JsonParser parser) throws IOException { - // parser as double then cast to long; loses info (64 bit -> 64 bit) - return (long) parseStringAsDouble(parser); - } - - static long parseDecimalStringAsTruncatedLong(JsonParser parser) throws IOException { + static long parseDecimalStringAsLong(JsonParser parser) throws IOException { // To ensure 64-bit in cases where the string is a decimal, we need BigDecimal return parseStringAsBigDecimal(parser).longValue(); } - static long parseDecimalStringAsScaledTruncatedLong(JsonParser parser, int n) throws IOException { + static long parseDecimalStringAsScaledLong(JsonParser parser, int n) throws IOException { // To ensure 64-bit in cases where the string is a decimal, we need BigDecimal return parseStringAsBigDecimal(parser).scaleByPowerOfTen(n).longValue(); } @@ -287,7 +276,7 @@ static BigInteger parseStringAsBigInteger(JsonParser parser) throws IOException : new BigInteger(parser.getText()); } - static BigInteger parseStringAsTruncatedBigInteger(JsonParser parser) throws IOException { + static BigInteger parseDecimalStringAsBigInteger(JsonParser parser) throws IOException { return parseStringAsBigDecimal(parser).toBigInteger(); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java index c6ac7439b70..987f93a16cd 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java @@ -109,7 +109,7 @@ private short parseFromDecimal(JsonParser parser) throws IOException { if (!allowDecimal()) { throw Parsing.mismatch(parser, short.class); } - return Parsing.parseDecimalAsTruncatedShort(parser); + return Parsing.parseDecimalAsShort(parser); } private short parseFromString(JsonParser parser) throws IOException { @@ -117,7 +117,7 @@ private short parseFromString(JsonParser parser) throws IOException { throw Parsing.mismatch(parser, short.class); } return allowDecimal() - ? Parsing.parseDecimalStringAsTruncatedShort(parser) + ? Parsing.parseDecimalStringAsShort(parser) : Parsing.parseStringAsShort(parser); } From 2d7389598838078f2495f79393d50adbe46be72c Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Fri, 24 May 2024 14:26:52 -0700 Subject: [PATCH 21/53] exceptions --- .../json/jackson/BigDecimalMixin.java | 18 +--- .../json/jackson/BigIntegerMixin.java | 22 ++-- .../io/deephaven/json/jackson/BoolMixin.java | 32 ++---- .../io/deephaven/json/jackson/ByteMixin.java | 22 ++-- .../io/deephaven/json/jackson/CharMixin.java | 14 +-- .../deephaven/json/jackson/DoubleMixin.java | 18 +--- .../io/deephaven/json/jackson/Exceptions.java | 54 ++++++++++ .../io/deephaven/json/jackson/FloatMixin.java | 18 +--- .../deephaven/json/jackson/InstantMixin.java | 11 +- .../json/jackson/InstantNumberMixin.java | 23 ++-- .../io/deephaven/json/jackson/IntMixin.java | 22 ++-- .../json/jackson/LocalDateMixin.java | 10 +- .../io/deephaven/json/jackson/LongMixin.java | 22 ++-- .../java/io/deephaven/json/jackson/Mixin.java | 87 +++++++++++++++ .../deephaven/json/jackson/ObjectMixin.java | 100 +++++++++++------- .../io/deephaven/json/jackson/Parsing.java | 19 ---- .../json/jackson/RepeaterProcessorBase.java | 4 +- .../io/deephaven/json/jackson/ShortMixin.java | 22 ++-- .../io/deephaven/json/jackson/SkipMixin.java | 39 ++----- .../deephaven/json/jackson/StringMixin.java | 26 ++--- .../io/deephaven/json/jackson/TupleMixin.java | 40 ++++--- .../json/jackson/TypedObjectMixin.java | 16 +-- .../jackson/ValueInnerRepeaterProcessor.java | 4 +- .../json/jackson/ValueProcessor.java | 1 - .../io/deephaven/json/ByteOptionsTest.java | 16 +-- .../io/deephaven/json/CharOptionsTest.java | 16 +-- .../io/deephaven/json/DoubleOptionsTest.java | 14 +-- .../io/deephaven/json/FloatOptionsTest.java | 14 +-- .../io/deephaven/json/InstantOptionsTest.java | 4 +- .../io/deephaven/json/IntOptionsTest.java | 16 +-- .../deephaven/json/LocalDateOptionsTest.java | 4 +- .../io/deephaven/json/LongOptionsTest.java | 16 +-- .../io/deephaven/json/ShortOptionsTest.java | 16 +-- .../io/deephaven/json/StringOptionsTest.java | 16 +-- .../src/main/resources/logback-minimal.xml | 2 +- 35 files changed, 389 insertions(+), 389 deletions(-) create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Exceptions.java diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java index c4c1cba166d..bfb5855a363 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java @@ -52,7 +52,7 @@ public BigDecimal parseValue(JsonParser parser) throws IOException { case VALUE_NULL: return parseFromNull(parser); } - throw Parsing.mismatch(parser, BigDecimal.class); + throw unexpectedToken(parser); } @Override @@ -67,30 +67,22 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { } private BigDecimal parseFromNumber(JsonParser parser) throws IOException { - if (!allowNumberInt() && !allowDecimal()) { - throw Parsing.mismatch(parser, BigDecimal.class); - } + checkNumberAllowed(parser); return Parsing.parseDecimalAsBigDecimal(parser); } private BigDecimal parseFromString(JsonParser parser) throws IOException { - if (!allowString()) { - throw Parsing.mismatch(parser, BigDecimal.class); - } + checkStringAllowed(parser); return Parsing.parseStringAsBigDecimal(parser); } private BigDecimal parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, BigDecimal.class); - } + checkNullAllowed(parser); return options.onNull().orElse(null); } private BigDecimal parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, BigDecimal.class); - } + checkMissingAllowed(parser); return options.onMissing().orElse(null); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java index 0c6fb180667..89d55523249 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java @@ -53,7 +53,7 @@ public BigInteger parseValue(JsonParser parser) throws IOException { case VALUE_NULL: return parseFromNull(parser); } - throw Parsing.mismatch(parser, BigInteger.class); + throw unexpectedToken(parser); } @Override @@ -68,39 +68,29 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { } private BigInteger parseFromInt(JsonParser parser) throws IOException { - if (!allowNumberInt()) { - throw Parsing.mismatch(parser, BigInteger.class); - } + checkNumberIntAllowed(parser); return Parsing.parseIntAsBigInteger(parser); } private BigInteger parseFromDecimal(JsonParser parser) throws IOException { - if (!allowDecimal()) { - throw Parsing.mismatch(parser, BigInteger.class); - } + checkDecimalAllowed(parser); return Parsing.parseDecimalAsBigInteger(parser); } private BigInteger parseFromString(JsonParser parser) throws IOException { - if (!allowString()) { - throw Parsing.mismatch(parser, BigInteger.class); - } + checkStringAllowed(parser); return allowDecimal() ? Parsing.parseDecimalStringAsBigInteger(parser) : Parsing.parseStringAsBigInteger(parser); } private BigInteger parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, BigInteger.class); - } + checkNullAllowed(parser); return options.onNull().orElse(null); } private BigInteger parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, BigInteger.class); - } + checkMissingAllowed(parser); return options.onMissing().orElse(null); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java index 728c3bd9ac5..0d4fbf360aa 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java @@ -63,7 +63,7 @@ public byte parseValue(JsonParser parser) throws IOException { case FIELD_NAME: return parseFromString(parser); } - throw Parsing.mismatch(parser, Boolean.class); + throw unexpectedToken(parser); } @Override @@ -91,7 +91,7 @@ public Boolean parseValue(JsonParser parser) throws IOException { case FIELD_NAME: return parseFromStringBoolean(parser); } - throw Parsing.mismatch(parser, Boolean.class); + throw unexpectedToken(parser); } @Override @@ -101,13 +101,11 @@ public Boolean parseMissing(JsonParser parser) throws IOException { } private byte parseFromString(JsonParser parser) throws IOException { - if (!allowString()) { - throw Parsing.mismatch(parser, Boolean.class); - } + checkStringAllowed(parser); if (!allowNull()) { final byte res = Parsing.parseStringAsByteBool(parser, BooleanUtils.NULL_BOOLEAN_AS_BYTE); if (res == BooleanUtils.NULL_BOOLEAN_AS_BYTE) { - throw Parsing.mismatch(parser, Boolean.class); + throw Exceptions.notAllowed(parser, this); } return res; } @@ -115,27 +113,21 @@ private byte parseFromString(JsonParser parser) throws IOException { } private byte parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, Boolean.class); - } + checkNullAllowed(parser); return onNullByte; } private byte parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, Boolean.class); - } + checkMissingAllowed(parser); return onMissingByte; } private Boolean parseFromStringBoolean(JsonParser parser) throws IOException { - if (!allowString()) { - throw Parsing.mismatch(parser, Boolean.class); - } + checkStringAllowed(parser); if (!allowNull()) { final Boolean result = Parsing.parseStringAsBoolean(parser, null); if (result == null) { - throw Parsing.mismatch(parser, Boolean.class); + throw Exceptions.notAllowed(parser, this); } return result; } @@ -143,16 +135,12 @@ private Boolean parseFromStringBoolean(JsonParser parser) throws IOException { } private Boolean parseFromNullBoolean(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, Boolean.class); - } + checkNullAllowed(parser); return onNull; } private Boolean parseFromMissingBoolean(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, Boolean.class); - } + checkMissingAllowed(parser); return onMissing; } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java index e16f20797bf..aa16a7921f6 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java @@ -55,7 +55,7 @@ public byte parseValue(JsonParser parser) throws IOException { case VALUE_NULL: return parseFromNull(parser); } - throw Parsing.mismatch(parser, int.class); + throw unexpectedToken(parser); } @Override @@ -99,39 +99,29 @@ public byte[] doneImpl(JsonParser parser, int length) { } private byte parseFromInt(JsonParser parser) throws IOException { - if (!allowNumberInt()) { - throw Parsing.mismatch(parser, byte.class); - } + checkNumberIntAllowed(parser); return Parsing.parseIntAsByte(parser); } private byte parseFromDecimal(JsonParser parser) throws IOException { - if (!allowDecimal()) { - throw Parsing.mismatch(parser, byte.class); - } + checkDecimalAllowed(parser); return Parsing.parseDecimalAsByte(parser); } private byte parseFromString(JsonParser parser) throws IOException { - if (!allowString()) { - throw Parsing.mismatch(parser, byte.class); - } + checkStringAllowed(parser); return allowDecimal() ? Parsing.parseDecimalStringAsByte(parser) : Parsing.parseStringAsByte(parser); } private byte parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, byte.class); - } + checkNullAllowed(parser); return options.onNull().orElse(QueryConstants.NULL_BYTE); } private byte parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, byte.class); - } + checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_BYTE); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java index 2d814ed6b73..e8d5d9baab8 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java @@ -51,7 +51,7 @@ public char parseValue(JsonParser parser) throws IOException { case VALUE_NULL: return parseFromNull(parser); } - throw Parsing.mismatch(parser, int.class); + throw unexpectedToken(parser); } @Override @@ -95,23 +95,17 @@ public char[] doneImpl(JsonParser parser, int length) { } private char parseFromString(JsonParser parser) throws IOException { - if (!allowString()) { - throw Parsing.mismatch(parser, char.class); - } + checkStringAllowed(parser); return Parsing.parseStringAsChar(parser); } private char parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, char.class); - } + checkNullAllowed(parser); return options.onNull().orElse(QueryConstants.NULL_CHAR); } private char parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, char.class); - } + checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_CHAR); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java index 5a48e355130..ff443177296 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java @@ -55,7 +55,7 @@ public double parseValue(JsonParser parser) throws IOException { case VALUE_NULL: return parseFromNull(parser); } - throw Parsing.mismatch(parser, double.class); + throw unexpectedToken(parser); } @Override @@ -99,31 +99,23 @@ public double[] doneImpl(JsonParser parser, int length) { } private double parseFromNumber(JsonParser parser) throws IOException { - if (!allowDecimal() && !allowNumberInt()) { - throw Parsing.mismatch(parser, double.class); - } + checkNumberAllowed(parser); // TODO: improve after https://github.com/FasterXML/jackson-core/issues/1229 return Parsing.parseNumberAsDouble(parser); } private double parseFromString(JsonParser parser) throws IOException { - if (!allowString()) { - throw Parsing.mismatch(parser, double.class); - } + checkStringAllowed(parser); return Parsing.parseStringAsDouble(parser); } private double parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, double.class); - } + checkNullAllowed(parser); return options.onNull().orElse(QueryConstants.NULL_DOUBLE); } private double parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, double.class); - } + checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_DOUBLE); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Exceptions.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Exceptions.java new file mode 100644 index 00000000000..c48d00ba207 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Exceptions.java @@ -0,0 +1,54 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import io.deephaven.json.Value; + +import java.io.IOException; +import java.util.Objects; + +final class Exceptions { + + static IOException notAllowed(JsonParser parser) { + return new IOException(String.format("Token '%s' not allowed", parser.currentToken())); + } + + static IOException missingNotAllowed(JsonParser parser) { + return new IOException("Missing token not allowed"); + } + + static IOException notAllowed(JsonParser parser, Mixin mixin) { + final JsonLocation location = parser.currentLocation(); + return new ValueAwareException(String.format("Token '%s' not allowed", parser.currentToken()), location, + mixin.options); + } + + static IOException missingNotAllowed(JsonParser parser, Mixin mixin) { + final JsonLocation location = parser.currentLocation(); + return new ValueAwareException("Missing token not allowed", location, mixin.options); + } + + public static class ValueAwareException extends JsonProcessingException { + + private final Value value; + + public ValueAwareException(String msg, JsonLocation loc, Value valueContext) { + super(msg, loc); + this.value = Objects.requireNonNull(valueContext); + } + + public ValueAwareException(String msg, JsonLocation loc, Throwable cause, Value valueContext) { + super(msg, loc, cause); + this.value = Objects.requireNonNull(valueContext); + } + + @Override + protected String getMessageSuffix() { + return " for " + value; + } + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java index be67641e9ba..d4fb7e3b0ff 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java @@ -55,7 +55,7 @@ public float parseValue(JsonParser parser) throws IOException { case VALUE_NULL: return parseFromNull(parser); } - throw Parsing.mismatch(parser, float.class); + throw unexpectedToken(parser); } @Override @@ -99,30 +99,22 @@ public float[] doneImpl(JsonParser parser, int length) { } private float parseFromNumber(JsonParser parser) throws IOException { - if (!allowDecimal() && !allowNumberInt()) { - throw Parsing.mismatch(parser, float.class); - } + checkNumberAllowed(parser); return Parsing.parseNumberAsFloat(parser); } private float parseFromString(JsonParser parser) throws IOException { - if (!allowString()) { - throw Parsing.mismatch(parser, float.class); - } + checkStringAllowed(parser); return Parsing.parseStringAsFloat(parser); } private float parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, float.class); - } + checkNullAllowed(parser); return options.onNull().orElse(QueryConstants.NULL_FLOAT); } private float parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, float.class); - } + checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_FLOAT); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java index a1935a9a657..3f85ca5c3f8 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java @@ -11,7 +11,6 @@ import io.deephaven.time.DateTimeUtils; import java.io.IOException; -import java.time.Instant; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.util.List; @@ -57,7 +56,7 @@ public long parseValue(JsonParser parser) throws IOException { case VALUE_NULL: return parseFromNull(parser); } - throw Parsing.mismatch(parser, Instant.class); + throw unexpectedToken(parser); } @Override @@ -78,16 +77,12 @@ private long parseFromString(JsonParser parser) throws IOException { } private long parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, Instant.class); - } + checkNullAllowed(parser); return onNull; } private long parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, Instant.class); - } + checkMissingAllowed(parser); return onMissing; } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java index dc609d5d3d2..ab6296ed962 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java @@ -11,7 +11,6 @@ import java.io.IOException; import java.math.BigInteger; -import java.time.Instant; import java.util.List; import java.util.stream.Stream; @@ -100,37 +99,27 @@ private long parseFromDecimalString(JsonParser parser) throws IOException { public final long parseValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { case VALUE_NUMBER_INT: - if (!allowNumberInt()) { - throw Parsing.mismatch(parser, Instant.class); - } + checkNumberIntAllowed(parser); return parseFromInt(parser); case VALUE_NUMBER_FLOAT: - if (!allowDecimal()) { - throw Parsing.mismatch(parser, Instant.class); - } + checkDecimalAllowed(parser); return parseFromDecimal(parser); case VALUE_STRING: case FIELD_NAME: - if (!allowString()) { - throw Parsing.mismatch(parser, Instant.class); - } + checkStringAllowed(parser); return allowDecimal() ? parseFromDecimalString(parser) : parseFromString(parser); case VALUE_NULL: - if (!allowNull()) { - throw Parsing.mismatch(parser, Instant.class); - } + checkNullAllowed(parser); return onNull; } - throw Parsing.mismatch(parser, Instant.class); + throw unexpectedToken(parser); } @Override public final long parseMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, Instant.class); - } + checkMissingAllowed(parser); return onMissing; } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java index 423a8dfe170..1483281a9b3 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -56,7 +56,7 @@ public int parseValue(JsonParser parser) throws IOException { case VALUE_NULL: return parseFromNull(parser); } - throw Parsing.mismatch(parser, int.class); + throw unexpectedToken(parser); } @Override @@ -100,39 +100,29 @@ public int[] doneImpl(JsonParser parser, int length) { } private int parseFromInt(JsonParser parser) throws IOException { - if (!allowNumberInt()) { - throw Parsing.mismatch(parser, int.class); - } + checkNumberIntAllowed(parser); return Parsing.parseIntAsInt(parser); } private int parseFromDecimal(JsonParser parser) throws IOException { - if (!allowDecimal()) { - throw Parsing.mismatch(parser, int.class); - } + checkDecimalAllowed(parser); return Parsing.parseDecimalAsInt(parser); } private int parseFromString(JsonParser parser) throws IOException { - if (!allowString()) { - throw Parsing.mismatch(parser, int.class); - } + checkStringAllowed(parser); return allowDecimal() ? Parsing.parseDecimalStringAsInt(parser) : Parsing.parseStringAsInt(parser); } private int parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, int.class); - } + checkNullAllowed(parser); return options.onNull().orElse(QueryConstants.NULL_INT); } private int parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, int.class); - } + checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_INT); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java index e94f33c286c..5c8cd5c63d5 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java @@ -50,7 +50,7 @@ public LocalDate parseValue(JsonParser parser) throws IOException { case VALUE_NULL: return parseFromNull(parser); } - throw Parsing.mismatch(parser, LocalDateValue.class); + throw unexpectedToken(parser); } @Override @@ -70,16 +70,12 @@ private LocalDate parseFromString(JsonParser parser) throws IOException { } private LocalDate parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, LocalDate.class); - } + checkNullAllowed(parser); return options.onNull().orElse(null); } private LocalDate parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, LocalDate.class); - } + checkMissingAllowed(parser); return options.onMissing().orElse(null); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java index 899fc5ca824..cb166d531f5 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java @@ -52,7 +52,7 @@ public long parseValue(JsonParser parser) throws IOException { case VALUE_NULL: return parseFromNull(parser); } - throw Parsing.mismatch(parser, long.class); + throw unexpectedToken(parser); } @Override @@ -66,39 +66,29 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { } private long parseFromInt(JsonParser parser) throws IOException { - if (!allowNumberInt()) { - throw Parsing.mismatch(parser, long.class); - } + checkNumberIntAllowed(parser); return Parsing.parseIntAsLong(parser); } private long parseFromDecimal(JsonParser parser) throws IOException { - if (!allowDecimal()) { - throw Parsing.mismatch(parser, long.class); - } + checkDecimalAllowed(parser); return Parsing.parseDecimalAsLong(parser); } private long parseFromString(JsonParser parser) throws IOException { - if (!allowString()) { - throw Parsing.mismatch(parser, long.class); - } + checkStringAllowed(parser); return allowDecimal() ? Parsing.parseDecimalStringAsLong(parser) : Parsing.parseStringAsLong(parser); } private long parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, long.class); - } + checkNullAllowed(parser); return options.onNull().orElse(QueryConstants.NULL_LONG); } private long parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, long.class); - } + checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_LONG); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index f289500718a..80b34c07b27 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -30,6 +30,7 @@ import io.deephaven.json.TupleValue; import io.deephaven.json.TypedObjectValue; import io.deephaven.json.Value; +import io.deephaven.json.jackson.Exceptions.ValueAwareException; import io.deephaven.processor.ObjectProcessor; import io.deephaven.qst.type.Type; @@ -377,4 +378,90 @@ public AnyMixin visit(AnyValue any) { return new AnyMixin(any, factory); } } + + final void checkNumberAllowed(JsonParser parser) throws IOException { + if (!allowNumberInt() && !allowDecimal()) { + throw new ValueAwareException("Number not allowed", parser.currentLocation(), options); + } + } + + final void checkNumberIntAllowed(JsonParser parser) throws IOException { + if (!allowNumberInt()) { + throw new ValueAwareException("Number int not allowed", parser.currentLocation(), options); + } + } + + final void checkDecimalAllowed(JsonParser parser) throws IOException { + if (!allowDecimal()) { + throw new ValueAwareException("Decimal not allowed", parser.currentLocation(), options); + } + } + + final void checkBoolAllowed(JsonParser parser) throws IOException { + if (!allowBool()) { + throw new ValueAwareException("Bool not expected", parser.currentLocation(), options); + } + } + + final void checkStringAllowed(JsonParser parser) throws IOException { + if (!allowString()) { + throw new ValueAwareException("String not allowed", parser.currentLocation(), options); + } + } + + final void checkObjectAllowed(JsonParser parser) throws IOException { + if (!allowObject()) { + throw new ValueAwareException("Object not allowed", parser.currentLocation(), options); + } + } + + final void checkArrayAllowed(JsonParser parser) throws IOException { + if (!allowArray()) { + throw new ValueAwareException("Array not allowed", parser.currentLocation(), options); + } + } + + final void checkNullAllowed(JsonParser parser) throws IOException { + if (!allowNull()) { + throw new ValueAwareException("Null not allowed", parser.currentLocation(), options); + } + } + + final void checkMissingAllowed(JsonParser parser) throws IOException { + if (!allowMissing()) { + throw new ValueAwareException("Missing not allowed", parser.currentLocation(), options); + } + } + + final IOException unexpectedToken(JsonParser parser) throws ValueAwareException { + final String msg; + switch (parser.currentToken()) { + case VALUE_TRUE: + case VALUE_FALSE: + msg = "Bool not expected"; + break; + case START_OBJECT: + msg = "Object not expected"; + break; + case START_ARRAY: + msg = "Array not expected"; + break; + case VALUE_NUMBER_INT: + msg = "Number int not expected"; + break; + case VALUE_NUMBER_FLOAT: + msg = "Decimal not expected"; + break; + case FIELD_NAME: + case VALUE_STRING: + msg = "String not expected"; + break; + case VALUE_NULL: + msg = "Null not expected"; + break; + default: + msg = parser.currentToken() + " not expected"; + } + throw new ValueAwareException(msg, parser.currentLocation(), options); + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java index 7b8ab8f530d..c647a8b6513 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -9,11 +9,13 @@ import io.deephaven.json.ObjectField; import io.deephaven.json.ObjectField.RepeatedBehavior; import io.deephaven.json.ObjectValue; +import io.deephaven.json.jackson.Exceptions.ValueAwareException; import io.deephaven.json.jackson.RepeaterProcessor.Context; import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -27,27 +29,27 @@ final class ObjectMixin extends Mixin { + private final Map> mixins; + private final int numOutputs; + public ObjectMixin(ObjectValue options, JsonFactory factory) { super(factory, options); + final LinkedHashMap> map = new LinkedHashMap<>(options.fields().size()); + for (ObjectField field : options.fields()) { + map.put(field, mixin(field.options())); + } + mixins = Collections.unmodifiableMap(map); + numOutputs = mixins.values().stream().mapToInt(Mixin::numColumns).sum(); } @Override public Stream> outputTypesImpl() { - return options.fields() - .stream() - .map(ObjectField::options) - .map(this::mixin) - .flatMap(Mixin::outputTypesImpl); + return mixins.values().stream().flatMap(Mixin::outputTypesImpl); } @Override public int numColumns() { - return options.fields() - .stream() - .map(ObjectField::options) - .map(this::mixin) - .mapToInt(Mixin::numColumns) - .sum(); + return numOutputs; } @Override @@ -60,7 +62,7 @@ public ValueProcessor processor(String context) { final Map processors = new LinkedHashMap<>(options.fields().size()); int ix = 0; for (ObjectField field : options.fields()) { - final Mixin opts = mixin(field.options()); + final Mixin opts = mixins.get(field); final int numTypes = opts.numColumns(); final ValueProcessor fieldProcessor = opts.processor(context + "/" + field.name()); processors.put(field, fieldProcessor); @@ -77,10 +79,9 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { final Map processors = new LinkedHashMap<>(options.fields().size()); int ix = 0; for (ObjectField field : options.fields()) { - final Mixin opts = mixin(field.options()); + final Mixin opts = mixins.get(field); final int numTypes = opts.numColumns(); - final RepeaterProcessor fieldProcessor = - opts.repeaterProcessor(allowMissing, allowNull); + final RepeaterProcessor fieldProcessor = opts.repeaterProcessor(allowMissing, allowNull); processors.put(field, fieldProcessor); ix += numTypes; } @@ -160,33 +161,29 @@ public void processCurrentValue(JsonParser parser) throws IOException { processNullObject(parser); return; default: - throw Parsing.mismatch(parser, Object.class); + throw unexpectedToken(parser); } } @Override public void processMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, Object.class); - } - for (ValueProcessor value : fields.values()) { - value.processMissing(parser); + checkMissingAllowed(parser); + for (Entry entry : fields.entrySet()) { + processMissingField(entry.getKey(), entry.getValue(), parser); } } private void processNullObject(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, Object.class); - } - for (ValueProcessor value : fields.values()) { - value.processCurrentValue(parser); + checkNullAllowed(parser); + for (Entry entry : fields.entrySet()) { + processField(entry.getKey(), entry.getValue(), parser); } } private void processEmptyObject(JsonParser parser) throws IOException { // This logic should be equivalent to processObjectFields, but where we know there are no fields - for (ValueProcessor value : fields.values()) { - value.processMissing(parser); + for (Entry entry : fields.entrySet()) { + processMissingField(entry.getKey(), entry.getValue(), parser); } } @@ -201,19 +198,18 @@ public void process(String fieldName, JsonParser parser) throws IOException { final ObjectField field = lookupField(fieldName); if (field == null) { if (!options.allowUnknownFields()) { - throw new IOException( - String.format("Unexpected field '%s' and allowUnknownFields == false", fieldName)); + throw new ValueAwareException(String.format("Unknown field '%s' not allowed", fieldName), + parser.currentLocation(), options); } parser.skipChildren(); } else if (visited.add(field)) { // First time seeing field - processor(field).processCurrentValue(parser); + processField(field, processor(field), parser); } else if (field.repeatedBehavior() == RepeatedBehavior.USE_FIRST) { parser.skipChildren(); } else { - throw new IOException( - String.format("Field '%s' has already been visited and repeatedBehavior == %s", fieldName, - field.repeatedBehavior())); + throw new ValueAwareException(String.format("Field '%s' has already been visited", fieldName), + parser.currentLocation(), options); } } @@ -224,10 +220,30 @@ void processMissingFields(JsonParser parser) throws IOException { } for (Entry e : fields.entrySet()) { if (!visited.contains(e.getKey())) { - e.getValue().processMissing(parser); + processMissingField(e.getKey(), e.getValue(), parser); } } } + + private void processField(ObjectField field, ValueProcessor processor, JsonParser parser) + throws ValueAwareException { + try { + processor.processCurrentValue(parser); + } catch (IOException | RuntimeException e) { + throw new ValueAwareException(String.format("Unable to process field '%s'", field.name()), + parser.currentLocation(), e, options); + } + } + + private void processMissingField(ObjectField field, ValueProcessor processor, JsonParser parser) + throws ValueAwareException { + try { + processor.processMissing(parser); + } catch (IOException | RuntimeException e) { + throw new ValueAwareException(String.format("Unable to process field '%s'", field.name()), + parser.currentLocation(), e, options); + } + } } final class ObjectValueRepeaterProcessor extends ContextAwareDelegateBase @@ -328,7 +344,7 @@ public void processElement(JsonParser parser) throws IOException { processNullObject(parser); break; default: - throw Parsing.mismatch(parser, Object.class); + throw unexpectedToken(parser); } } @@ -362,8 +378,9 @@ public void process(String fieldName, JsonParser parser) throws IOException { final ObjectField field = lookupField(fieldName); if (field == null) { if (!options.allowUnknownFields()) { - throw new IOException( - String.format("Unexpected field '%s' and allowUnknownFields == false", fieldName)); + throw new ValueAwareException( + String.format("Unexpected field '%s' and allowUnknownFields == false", fieldName), + parser.currentLocation(), options); } parser.skipChildren(); } else if (visited.add(field)) { @@ -372,9 +389,10 @@ public void process(String fieldName, JsonParser parser) throws IOException { } else if (field.repeatedBehavior() == RepeatedBehavior.USE_FIRST) { parser.skipChildren(); } else { - throw new IOException( - String.format("Field '%s' has already been visited and repeatedBehavior == %s", - fieldName, field.repeatedBehavior())); + throw new ValueAwareException( + String.format("Field '%s' has already been visited and repeatedBehavior == %s", fieldName, + field.repeatedBehavior()), + parser.currentLocation(), options); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java index f4507b48ef7..b50e306ec89 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Parsing.java @@ -7,9 +7,7 @@ import ch.randelshofer.fastdoubleparser.JavaBigIntegerParser; import ch.randelshofer.fastdoubleparser.JavaDoubleParser; import ch.randelshofer.fastdoubleparser.JavaFloatParser; -import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.StreamReadFeature; import io.deephaven.util.BooleanUtils; @@ -49,23 +47,6 @@ static CharSequence textAsCharSequence(JsonParser parser) throws IOException { : parser.getText(); } - static class UnexpectedToken extends JsonProcessingException { - public UnexpectedToken(String msg, JsonLocation loc) { - super(msg, loc); - } - } - - static IOException mismatch(JsonParser parser, Class clazz) { - final JsonLocation location = parser.currentLocation(); - final String msg = String.format("Unexpected token '%s'", parser.currentToken()); - return new UnexpectedToken(msg, location); - } - - static IOException mismatchMissing(JsonParser parser, Class clazz) { - final JsonLocation location = parser.currentLocation(); - return new UnexpectedToken("Unexpected missing token", location); - } - static byte parseStringAsByteBool(JsonParser parser, byte onNull) throws IOException { final String text = parser.getText().trim(); if ("true".equalsIgnoreCase(text)) { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java index a9f2858e8ec..522040afffd 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java @@ -72,7 +72,7 @@ public final Context context() { @Override public final void processMissingRepeater(JsonParser parser) throws IOException { if (!allowMissing) { - throw Parsing.mismatchMissing(parser, void.class); + throw Exceptions.missingNotAllowed(parser); } out.add(onMissing); } @@ -80,7 +80,7 @@ public final void processMissingRepeater(JsonParser parser) throws IOException { @Override public final void processNullRepeater(JsonParser parser) throws IOException { if (!allowNull) { - throw Parsing.mismatch(parser, void.class); + throw Exceptions.notAllowed(parser); } out.add(onNull); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java index 987f93a16cd..0219c95db5b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java @@ -55,7 +55,7 @@ public short parseValue(JsonParser parser) throws IOException { case VALUE_NULL: return parseFromNull(parser); } - throw Parsing.mismatch(parser, int.class); + throw unexpectedToken(parser); } @Override @@ -99,39 +99,29 @@ public short[] doneImpl(JsonParser parser, int length) { } private short parseFromInt(JsonParser parser) throws IOException { - if (!allowNumberInt()) { - throw Parsing.mismatch(parser, short.class); - } + checkNumberIntAllowed(parser); return Parsing.parseIntAsShort(parser); } private short parseFromDecimal(JsonParser parser) throws IOException { - if (!allowDecimal()) { - throw Parsing.mismatch(parser, short.class); - } + checkDecimalAllowed(parser); return Parsing.parseDecimalAsShort(parser); } private short parseFromString(JsonParser parser) throws IOException { - if (!allowString()) { - throw Parsing.mismatch(parser, short.class); - } + checkStringAllowed(parser); return allowDecimal() ? Parsing.parseDecimalStringAsShort(parser) : Parsing.parseStringAsShort(parser); } private short parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, short.class); - } + checkNullAllowed(parser); return options.onNull().orElse(QueryConstants.NULL_SHORT); } private short parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, short.class); - } + checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_SHORT); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java index 55c82ed6875..b98c7d124b3 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java @@ -64,54 +64,38 @@ public Stream> columnTypes() { public void processCurrentValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { case START_OBJECT: - if (!allowObject()) { - throw Parsing.mismatch(parser, void.class); - } + checkObjectAllowed(parser); parser.skipChildren(); break; case START_ARRAY: - if (!allowArray()) { - throw Parsing.mismatch(parser, void.class); - } + checkArrayAllowed(parser); parser.skipChildren(); break; case VALUE_STRING: case FIELD_NAME: - if (!allowString()) { - throw Parsing.mismatch(parser, void.class); - } + checkStringAllowed(parser); break; case VALUE_NUMBER_INT: - if (!allowNumberInt()) { - throw Parsing.mismatch(parser, void.class); - } + checkNumberIntAllowed(parser); break; case VALUE_NUMBER_FLOAT: - if (!allowDecimal()) { - throw Parsing.mismatch(parser, void.class); - } + checkDecimalAllowed(parser); break; case VALUE_TRUE: case VALUE_FALSE: - if (!allowBool()) { - throw Parsing.mismatch(parser, void.class); - } + checkBoolAllowed(parser); break; case VALUE_NULL: - if (!allowNull()) { - throw Parsing.mismatch(parser, void.class); - } + checkNullAllowed(parser); break; default: - throw Parsing.mismatch(parser, void.class); + throw unexpectedToken(parser); } } @Override public void processMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, void.class); - } + checkMissingAllowed(parser); } private final class SkipArray implements RepeaterProcessor, Context { @@ -131,14 +115,14 @@ public Context context() { @Override public void processNullRepeater(JsonParser parser) throws IOException { if (!allowNull) { - throw Parsing.mismatch(parser, void.class); + throw Exceptions.notAllowed(parser); } } @Override public void processMissingRepeater(JsonParser parser) throws IOException { if (!allowMissing) { - throw Parsing.mismatch(parser, void.class); + throw Exceptions.notAllowed(parser); } } @@ -170,7 +154,6 @@ public void start(JsonParser parser) throws IOException { @Override public void processElement(JsonParser parser) throws IOException { processCurrentValue(parser); - } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java index 593ff36f7ed..e32e2f57cf7 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java @@ -55,7 +55,7 @@ public String parseValue(JsonParser parser) throws IOException { case VALUE_NULL: return parseFromNull(parser); } - throw Parsing.mismatch(parser, String.class); + throw unexpectedToken(parser); } @Override @@ -69,44 +69,32 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { } private String parseFromString(JsonParser parser) throws IOException { - if (!allowString()) { - throw Parsing.mismatch(parser, String.class); - } + checkStringAllowed(parser); return Parsing.parseStringAsString(parser); } private String parseFromInt(JsonParser parser) throws IOException { - if (!allowNumberInt()) { - throw Parsing.mismatch(parser, String.class); - } + checkNumberIntAllowed(parser); return Parsing.parseIntAsString(parser); } private String parseFromDecimal(JsonParser parser) throws IOException { - if (!allowDecimal()) { - throw Parsing.mismatch(parser, String.class); - } + checkDecimalAllowed(parser); return Parsing.parseDecimalAsString(parser); } private String parseFromBool(JsonParser parser) throws IOException { - if (!allowBool()) { - throw Parsing.mismatch(parser, String.class); - } + checkBoolAllowed(parser); return Parsing.parseBoolAsString(parser); } private String parseFromNull(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, String.class); - } + checkNullAllowed(parser); return options.onNull().orElse(null); } private String parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, String.class); - } + checkMissingAllowed(parser); return options.onMissing().orElse(null); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java index 273d9e9043f..80e72e9b3cf 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.core.JsonToken; import io.deephaven.json.TupleValue; import io.deephaven.json.Value; +import io.deephaven.json.jackson.Exceptions.ValueAwareException; import io.deephaven.json.jackson.RepeaterProcessor.Context; import io.deephaven.qst.type.Type; @@ -102,7 +103,7 @@ public void processCurrentValue(JsonParser parser) throws IOException { processNullTuple(parser); return; default: - throw Parsing.mismatch(parser, Object.class); + throw unexpectedToken(parser); } } @@ -112,29 +113,40 @@ public void processMissing(JsonParser parser) throws IOException { } private void processTuple(JsonParser parser) throws IOException { + int ix = 0; for (ValueProcessor value : values) { parser.nextToken(); - value.processCurrentValue(parser); + try { + value.processCurrentValue(parser); + } catch (IOException | RuntimeException e) { + throw new ValueAwareException(String.format("Unable to process tuple ix %d", ix), + parser.currentLocation(), e, options); + } + ++ix; } parser.nextToken(); assertCurrentToken(parser, JsonToken.END_ARRAY); } private void processNullTuple(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, Object.class); - } + checkNullAllowed(parser); + int ix = 0; // Note: we are treating a null tuple the same as a tuple of null objects // null ~= [null, ..., null] for (ValueProcessor value : values) { - value.processCurrentValue(parser); + // Note: _not_ incrementing to nextToken + try { + value.processCurrentValue(parser); + } catch (IOException | RuntimeException e) { + throw new ValueAwareException(String.format("Unable to process tuple ix %d", ix), + parser.currentLocation(), e, options); + } + ++ix; } } private void parseFromMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, Object.class); - } + checkMissingAllowed(parser); // Note: we are treating a missing tuple the same as a tuple of missing objects (which, is technically // impossible w/ native json, but it's the semantics we are exposing). // ~= [, ..., ] @@ -165,9 +177,7 @@ public Context context() { @Override public void processNullRepeater(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, Object.class); - } + checkNullAllowed(parser); for (RepeaterProcessor value : values) { value.processNullRepeater(parser); } @@ -175,9 +185,7 @@ public void processNullRepeater(JsonParser parser) throws IOException { @Override public void processMissingRepeater(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, Object.class); - } + checkMissingAllowed(parser); for (RepeaterProcessor value : values) { value.processMissingRepeater(parser); } @@ -201,7 +209,7 @@ public void processElement(JsonParser parser) throws IOException { processNullTuple(parser); break; default: - throw Parsing.mismatch(parser, Object.class); + throw Exceptions.notAllowed(parser); } ++index; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java index e1b7f9e4888..ad907abb888 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java @@ -122,12 +122,10 @@ private String parseTypeField(JsonParser parser) throws IOException { case FIELD_NAME: return parser.getText(); case VALUE_NULL: - if (!allowNull()) { - throw Parsing.mismatch(parser, String.class); - } + checkNullAllowed(parser); return null; default: - throw Parsing.mismatch(parser, String.class); + throw Exceptions.notAllowed(parser, this); } } @@ -227,15 +225,13 @@ public void processCurrentValue(JsonParser parser) throws IOException { processNullObject(parser); break; default: - throw Parsing.mismatch(parser, Object.class); + throw unexpectedToken(parser); } } @Override public void processMissing(JsonParser parser) throws IOException { - if (!allowMissing()) { - throw Parsing.mismatchMissing(parser, Object.class); - } + checkMissingAllowed(parser); // onMissingType()? typeOut.add(null); for (Processor processor : processors.values()) { @@ -244,9 +240,7 @@ public void processMissing(JsonParser parser) throws IOException { } private void processNullObject(JsonParser parser) throws IOException { - if (!allowNull()) { - throw Parsing.mismatch(parser, Object.class); - } + checkNullAllowed(parser); // onNullType()? typeOut.add(null); for (Processor processor : processors.values()) { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java index cee6111fd5f..a4c0471cffc 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java @@ -82,7 +82,7 @@ public Stream> columnTypes() { @Override public void processNullRepeater(JsonParser parser) throws IOException { if (!allowNull) { - throw Parsing.mismatch(parser, Object.class); + throw Exceptions.notAllowed(parser); } for (WritableObjectChunk wc : out) { wc.add(null); @@ -92,7 +92,7 @@ public void processNullRepeater(JsonParser parser) throws IOException { @Override public void processMissingRepeater(JsonParser parser) throws IOException { if (!allowMissing) { - throw Parsing.mismatchMissing(parser, Object.class); + throw Exceptions.missingNotAllowed(parser); } for (WritableObjectChunk wc : out) { wc.add(null); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessor.java index 6f5a37b2957..865f62160f2 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessor.java @@ -33,7 +33,6 @@ static void processFullJson(JsonParser parser, ValueProcessor processor) throws // com.fasterxml.jackson.databind.DeserializationContext), // but not functional (want to destructure efficiently) - /** * Called when the JSON value is present; the current token should be one of {@link JsonToken#START_OBJECT}, * {@link JsonToken#START_ARRAY}, {@link JsonToken#VALUE_STRING}, {@link JsonToken#VALUE_NUMBER_INT}, diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java index 2a6c757a52d..4249ee2d02f 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java @@ -53,7 +53,7 @@ void strictMissing() throws IOException { parse(ByteValue.strict(), "", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected missing token"); + assertThat(e).hasMessageContaining("Missing not allowed"); } } @@ -63,7 +63,7 @@ void strictNull() throws IOException { parse(ByteValue.strict(), "null", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + assertThat(e).hasMessageContaining("Null not allowed"); } } @@ -83,7 +83,7 @@ void standardString() throws IOException { parse(ByteValue.standard(), "\"42\"", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); + assertThat(e).hasMessageContaining("String not allowed"); } } @@ -93,7 +93,7 @@ void standardTrue() throws IOException { parse(ByteValue.standard(), "true", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -103,7 +103,7 @@ void standardFalse() throws IOException { parse(ByteValue.standard(), "false", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -113,7 +113,7 @@ void standardFloat() throws IOException { parse(ByteValue.standard(), "42.0", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); + assertThat(e).hasMessageContaining("Decimal not allowed"); } } @@ -123,7 +123,7 @@ void standardObject() throws IOException { parse(ByteValue.standard(), "{}", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + assertThat(e).hasMessageContaining("Object not expected"); } } @@ -133,7 +133,7 @@ void standardArray() throws IOException { parse(ByteValue.standard(), "[]", ByteChunk.chunkWrap(new byte[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + assertThat(e).hasMessageContaining("Array not expected"); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java index fe64a2e804f..49b6a2fb85f 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java @@ -51,7 +51,7 @@ void strictMissing() throws IOException { parse(CharValue.strict(), "", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected missing token"); + assertThat(e).hasMessageContaining("Missing not allowed"); } } @@ -61,7 +61,7 @@ void strictNull() throws IOException { parse(CharValue.strict(), "null", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + assertThat(e).hasMessageContaining("Null not allowed"); } } @@ -72,7 +72,7 @@ void standardInt() throws IOException { parse(CharValue.standard(), "42", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_INT'"); + assertThat(e).hasMessageContaining("Number int not expected"); } } @@ -82,7 +82,7 @@ void standardFloat() throws IOException { parse(CharValue.standard(), "42.42", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); + assertThat(e).hasMessageContaining("Decimal not expected"); } } @@ -93,7 +93,7 @@ void standardTrue() throws IOException { parse(CharValue.standard(), "true", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -103,7 +103,7 @@ void standardFalse() throws IOException { parse(CharValue.standard(), "false", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -113,7 +113,7 @@ void standardObject() throws IOException { parse(CharValue.standard(), "{}", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + assertThat(e).hasMessageContaining("Object not expected"); } } @@ -123,7 +123,7 @@ void standardArray() throws IOException { parse(CharValue.standard(), "[]", CharChunk.chunkWrap(new char[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + assertThat(e).hasMessageContaining("Array not expected"); } } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java index fbe0405709e..663ca37ebbe 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java @@ -47,7 +47,7 @@ void strictMissing() throws IOException { parse(DoubleValue.strict(), "", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected missing token"); + assertThat(e).hasMessageContaining("Missing not allowed"); } } @@ -57,7 +57,7 @@ void strictNull() throws IOException { parse(DoubleValue.strict(), "null", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + assertThat(e).hasMessageContaining("Null not allowed"); } } @@ -67,7 +67,7 @@ void standardString() throws IOException { parse(DoubleValue.standard(), "\"42\"", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); + assertThat(e).hasMessageContaining("String not allowed"); } } @@ -77,7 +77,7 @@ void standardTrue() throws IOException { parse(DoubleValue.standard(), "true", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -87,7 +87,7 @@ void standardFalse() throws IOException { parse(DoubleValue.standard(), "false", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -97,7 +97,7 @@ void standardObject() throws IOException { parse(DoubleValue.standard(), "{}", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + assertThat(e).hasMessageContaining("Object not expected"); } } @@ -107,7 +107,7 @@ void standardArray() throws IOException { parse(DoubleValue.standard(), "[]", DoubleChunk.chunkWrap(new double[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + assertThat(e).hasMessageContaining("Array not expected"); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java index 63ab9a90ffd..63f1a75c611 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java @@ -47,7 +47,7 @@ void strictMissing() throws IOException { parse(FloatValue.strict(), "", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected missing token"); + assertThat(e).hasMessageContaining("Missing not allowed"); } } @@ -57,7 +57,7 @@ void strictNull() throws IOException { parse(FloatValue.strict(), "null", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + assertThat(e).hasMessageContaining("Null not allowed"); } } @@ -67,7 +67,7 @@ void standardString() throws IOException { parse(FloatValue.standard(), "\"42\"", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); + assertThat(e).hasMessageContaining("String not allowed"); } } @@ -77,7 +77,7 @@ void standardTrue() throws IOException { parse(FloatValue.standard(), "true", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -87,7 +87,7 @@ void standardFalse() throws IOException { parse(FloatValue.standard(), "false", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -97,7 +97,7 @@ void standardObject() throws IOException { parse(FloatValue.standard(), "{}", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + assertThat(e).hasMessageContaining("Object not expected"); } } @@ -107,7 +107,7 @@ void standardArray() throws IOException { parse(FloatValue.standard(), "[]", FloatChunk.chunkWrap(new float[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + assertThat(e).hasMessageContaining("Array not expected"); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java index eda06779356..7dbeb34bf7d 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java @@ -45,7 +45,7 @@ void strictNull() throws IOException { parse(InstantValue.strict(), "null", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + assertThat(e).hasMessageContaining("Null not allowed"); } } @@ -55,7 +55,7 @@ void strictMissing() throws IOException { parse(InstantValue.strict(), "", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected missing token"); + assertThat(e).hasMessageContaining("Missing not allowed"); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java index e2b181e71b6..7e7a7d546c0 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java @@ -53,7 +53,7 @@ void strictMissing() throws IOException { parse(IntValue.strict(), "", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected missing token"); + assertThat(e).hasMessageContaining("Missing not allowed"); } } @@ -63,7 +63,7 @@ void strictNull() throws IOException { parse(IntValue.strict(), "null", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + assertThat(e).hasMessageContaining("Null not allowed"); } } @@ -83,7 +83,7 @@ void standardString() throws IOException { parse(IntValue.standard(), "\"42\"", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); + assertThat(e).hasMessageContaining("String not allowed"); } } @@ -93,7 +93,7 @@ void standardTrue() throws IOException { parse(IntValue.standard(), "true", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -103,7 +103,7 @@ void standardFalse() throws IOException { parse(IntValue.standard(), "false", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -113,7 +113,7 @@ void standardFloat() throws IOException { parse(IntValue.standard(), "42.0", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); + assertThat(e).hasMessageContaining("Decimal not allowed"); } } @@ -123,7 +123,7 @@ void standardObject() throws IOException { parse(IntValue.standard(), "{}", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + assertThat(e).hasMessageContaining("Object not expected"); } } @@ -133,7 +133,7 @@ void standardArray() throws IOException { parse(IntValue.standard(), "[]", IntChunk.chunkWrap(new int[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + assertThat(e).hasMessageContaining("Array not expected"); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java index f43b5191fba..85bf317c814 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java @@ -39,7 +39,7 @@ void strictNull() throws IOException { parse(LocalDateValue.strict(), "null", ObjectChunk.chunkWrap(new LocalDate[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + assertThat(e).hasMessageContaining("Null not allowed"); } } @@ -49,7 +49,7 @@ void strictMissing() throws IOException { parse(LocalDateValue.strict(), "", ObjectChunk.chunkWrap(new LocalDate[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected missing token"); + assertThat(e).hasMessageContaining("Missing not allowed"); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java index 8fd74d3b633..af1151e5c9f 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java @@ -48,7 +48,7 @@ void strictMissing() throws IOException { parse(LongValue.strict(), "", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected missing token"); + assertThat(e).hasMessageContaining("Missing not allowed"); } } @@ -58,7 +58,7 @@ void strictNull() throws IOException { parse(LongValue.strict(), "null", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + assertThat(e).hasMessageContaining("Null not allowed"); } } @@ -78,7 +78,7 @@ void standardString() throws IOException { parse(LongValue.standard(), "\"42\"", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); + assertThat(e).hasMessageContaining("String not allowed"); } } @@ -88,7 +88,7 @@ void standardTrue() throws IOException { parse(LongValue.standard(), "true", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -98,7 +98,7 @@ void standardFalse() throws IOException { parse(LongValue.standard(), "false", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -108,7 +108,7 @@ void standardFloat() throws IOException { parse(LongValue.standard(), "42.0", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); + assertThat(e).hasMessageContaining("Decimal not allowed"); } } @@ -118,7 +118,7 @@ void standardObject() throws IOException { parse(LongValue.standard(), "{}", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + assertThat(e).hasMessageContaining("Object not expected"); } } @@ -128,7 +128,7 @@ void standardArray() throws IOException { parse(LongValue.standard(), "[]", LongChunk.chunkWrap(new long[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + assertThat(e).hasMessageContaining("Array not expected"); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java index 1bebca975f5..f54c570a4e3 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java @@ -53,7 +53,7 @@ void strictMissing() throws IOException { parse(ShortValue.strict(), "", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected missing token"); + assertThat(e).hasMessageContaining("Missing not allowed"); } } @@ -63,7 +63,7 @@ void strictNull() throws IOException { parse(ShortValue.strict(), "null", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + assertThat(e).hasMessageContaining("Null not allowed"); } } @@ -83,7 +83,7 @@ void standardString() throws IOException { parse(ShortValue.standard(), "\"42\"", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_STRING'"); + assertThat(e).hasMessageContaining("String not allowed"); } } @@ -93,7 +93,7 @@ void standardTrue() throws IOException { parse(ShortValue.standard(), "true", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -103,7 +103,7 @@ void standardFalse() throws IOException { parse(ShortValue.standard(), "false", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -113,7 +113,7 @@ void standardFloat() throws IOException { parse(ShortValue.standard(), "42.0", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); + assertThat(e).hasMessageContaining("Decimal not allowed"); } } @@ -123,7 +123,7 @@ void standardObject() throws IOException { parse(ShortValue.standard(), "{}", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + assertThat(e).hasMessageContaining("Object not expected"); } } @@ -133,7 +133,7 @@ void standardArray() throws IOException { parse(ShortValue.standard(), "[]", ShortChunk.chunkWrap(new short[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + assertThat(e).hasMessageContaining("Array not expected"); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java index 867165bfbe1..3616b67bba7 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java @@ -53,7 +53,7 @@ void strictMissing() throws IOException { parse(StringValue.strict(), "", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected missing token"); + assertThat(e).hasMessageContaining("Missing not allowed"); } } @@ -63,7 +63,7 @@ void strictNull() throws IOException { parse(StringValue.strict(), "null", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NULL'"); + assertThat(e).hasMessageContaining("Null not allowed"); } } @@ -74,7 +74,7 @@ void standardInt() throws IOException { parse(StringValue.standard(), "42", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_INT'"); + assertThat(e).hasMessageContaining("Number int not allowed"); } } @@ -84,7 +84,7 @@ void standardFloat() throws IOException { parse(StringValue.standard(), "42.42", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_NUMBER_FLOAT'"); + assertThat(e).hasMessageContaining("Decimal not allowed"); } } @@ -95,7 +95,7 @@ void standardTrue() throws IOException { parse(StringValue.standard(), "true", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_TRUE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -105,7 +105,7 @@ void standardFalse() throws IOException { parse(StringValue.standard(), "false", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'VALUE_FALSE'"); + assertThat(e).hasMessageContaining("Bool not expected"); } } @@ -115,7 +115,7 @@ void standardObject() throws IOException { parse(StringValue.standard(), "{}", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_OBJECT'"); + assertThat(e).hasMessageContaining("Object not expected"); } } @@ -125,7 +125,7 @@ void standardArray() throws IOException { parse(StringValue.standard(), "[]", ObjectChunk.chunkWrap(new String[1])); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Unexpected token 'START_ARRAY'"); + assertThat(e).hasMessageContaining("Array not expected"); } } diff --git a/py/embedded-server/java-runtime/src/main/resources/logback-minimal.xml b/py/embedded-server/java-runtime/src/main/resources/logback-minimal.xml index 1922705b272..4b1fadb1e55 100644 --- a/py/embedded-server/java-runtime/src/main/resources/logback-minimal.xml +++ b/py/embedded-server/java-runtime/src/main/resources/logback-minimal.xml @@ -15,7 +15,7 @@ - + From c6f098b5c055e136aa03b3582cc597ba4a178de6 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Fri, 24 May 2024 15:12:03 -0700 Subject: [PATCH 22/53] tests --- .../json/jackson/JacksonConfiguration.java | 7 ++-- .../io/deephaven/json/ByteOptionsTest.java | 40 ++++++++++--------- .../io/deephaven/json/CharOptionsTest.java | 33 +++++++-------- .../io/deephaven/json/DoubleOptionsTest.java | 29 +++++++------- .../io/deephaven/json/FloatOptionsTest.java | 29 +++++++------- .../io/deephaven/json/InstantOptionsTest.java | 5 ++- .../io/deephaven/json/IntOptionsTest.java | 40 ++++++++++--------- .../deephaven/json/LocalDateOptionsTest.java | 9 +++-- .../io/deephaven/json/LongOptionsTest.java | 40 ++++++++++--------- .../io/deephaven/json/ObjectOptionsTest.java | 14 +++++++ .../io/deephaven/json/ShortOptionsTest.java | 40 ++++++++++--------- .../io/deephaven/json/StringOptionsTest.java | 33 +++++++-------- .../java/io/deephaven/json/TestHelper.java | 35 ++++++++++++---- .../io/deephaven/json/TupleOptionsTest.java | 16 ++++++++ 14 files changed, 218 insertions(+), 152 deletions(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonConfiguration.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonConfiguration.java index d507d0f8fcb..c1de008ec88 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonConfiguration.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonConfiguration.java @@ -29,15 +29,16 @@ public final class JacksonConfiguration { /** * Constructs a Deephaven-configured json factory builder. This currently includes - * {@link StreamReadFeature#USE_FAST_DOUBLE_PARSER} and {@link StreamReadFeature#USE_FAST_BIG_NUMBER_PARSER}. The - * specific configuration may change in the future. + * {@link StreamReadFeature#USE_FAST_DOUBLE_PARSER}, {@link StreamReadFeature#USE_FAST_BIG_NUMBER_PARSER}, and + * {@link StreamReadFeature#INCLUDE_SOURCE_IN_LOCATION}. The specific configuration may change in the future. * * @return the Deephaven-configured json factory builder */ public static JsonFactoryBuilder defaultFactoryBuilder() { return new JsonFactoryBuilder() .enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER) - .enable(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER); + .enable(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER) + .enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION); } // Not currently public, but javadoc still useful to ensure internal callers don't modify. diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java index 4249ee2d02f..3647e341de8 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java @@ -12,6 +12,7 @@ import java.util.List; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; @@ -48,9 +49,9 @@ void strict() throws IOException { } @Test - void strictMissing() throws IOException { + void strictMissing() { try { - parse(ByteValue.strict(), "", ByteChunk.chunkWrap(new byte[1])); + process(ByteValue.strict(), ""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Missing not allowed"); @@ -58,9 +59,9 @@ void strictMissing() throws IOException { } @Test - void strictNull() throws IOException { + void strictNull() { try { - parse(ByteValue.strict(), "null", ByteChunk.chunkWrap(new byte[1])); + process(ByteValue.strict(), "null"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Null not allowed"); @@ -68,19 +69,20 @@ void strictNull() throws IOException { } @Test - void standardOverflow() throws IOException { + void standardOverflow() { try { - parse(ByteValue.standard(), "2147483648", ByteChunk.chunkWrap(new byte[1])); - } catch (InputCoercionException e) { + process(ByteValue.standard(), "2147483648"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { assertThat(e).hasMessageContaining( "Numeric value (2147483648) out of range of int (-2147483648 - 2147483647)"); } } @Test - void standardString() throws IOException { + void standardString() { try { - parse(ByteValue.standard(), "\"42\"", ByteChunk.chunkWrap(new byte[1])); + process(ByteValue.standard(), "\"42\""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("String not allowed"); @@ -88,9 +90,9 @@ void standardString() throws IOException { } @Test - void standardTrue() throws IOException { + void standardTrue() { try { - parse(ByteValue.standard(), "true", ByteChunk.chunkWrap(new byte[1])); + process(ByteValue.standard(), "true"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -98,9 +100,9 @@ void standardTrue() throws IOException { } @Test - void standardFalse() throws IOException { + void standardFalse() { try { - parse(ByteValue.standard(), "false", ByteChunk.chunkWrap(new byte[1])); + process(ByteValue.standard(), "false"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -108,9 +110,9 @@ void standardFalse() throws IOException { } @Test - void standardFloat() throws IOException { + void standardFloat() { try { - parse(ByteValue.standard(), "42.0", ByteChunk.chunkWrap(new byte[1])); + process(ByteValue.standard(), "42.0"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Decimal not allowed"); @@ -118,9 +120,9 @@ void standardFloat() throws IOException { } @Test - void standardObject() throws IOException { + void standardObject() { try { - parse(ByteValue.standard(), "{}", ByteChunk.chunkWrap(new byte[1])); + process(ByteValue.standard(), "{}"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Object not expected"); @@ -128,9 +130,9 @@ void standardObject() throws IOException { } @Test - void standardArray() throws IOException { + void standardArray() { try { - parse(ByteValue.standard(), "[]", ByteChunk.chunkWrap(new byte[1])); + process(ByteValue.standard(), "[]"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Array not expected"); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java index 49b6a2fb85f..7e822436ea0 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java @@ -10,6 +10,7 @@ import java.io.IOException; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; @@ -46,9 +47,9 @@ void strict() throws IOException { } @Test - void strictMissing() throws IOException { + void strictMissing() { try { - parse(CharValue.strict(), "", CharChunk.chunkWrap(new char[1])); + process(CharValue.strict(), ""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Missing not allowed"); @@ -56,9 +57,9 @@ void strictMissing() throws IOException { } @Test - void strictNull() throws IOException { + void strictNull() { try { - parse(CharValue.strict(), "null", CharChunk.chunkWrap(new char[1])); + process(CharValue.strict(), "null"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Null not allowed"); @@ -67,9 +68,9 @@ void strictNull() throws IOException { @Test - void standardInt() throws IOException { + void standardInt() { try { - parse(CharValue.standard(), "42", CharChunk.chunkWrap(new char[1])); + process(CharValue.standard(), "42"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Number int not expected"); @@ -77,9 +78,9 @@ void standardInt() throws IOException { } @Test - void standardFloat() throws IOException { + void standardFloat() { try { - parse(CharValue.standard(), "42.42", CharChunk.chunkWrap(new char[1])); + process(CharValue.standard(), "42.42"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Decimal not expected"); @@ -88,9 +89,9 @@ void standardFloat() throws IOException { @Test - void standardTrue() throws IOException { + void standardTrue() { try { - parse(CharValue.standard(), "true", CharChunk.chunkWrap(new char[1])); + process(CharValue.standard(), "true"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -98,9 +99,9 @@ void standardTrue() throws IOException { } @Test - void standardFalse() throws IOException { + void standardFalse() { try { - parse(CharValue.standard(), "false", CharChunk.chunkWrap(new char[1])); + process(CharValue.standard(), "false"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -108,9 +109,9 @@ void standardFalse() throws IOException { } @Test - void standardObject() throws IOException { + void standardObject() { try { - parse(CharValue.standard(), "{}", CharChunk.chunkWrap(new char[1])); + process(CharValue.standard(), "{}"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Object not expected"); @@ -118,9 +119,9 @@ void standardObject() throws IOException { } @Test - void standardArray() throws IOException { + void standardArray() { try { - parse(CharValue.standard(), "[]", CharChunk.chunkWrap(new char[1])); + process(CharValue.standard(), "[]"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Array not expected"); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java index 663ca37ebbe..7a21c90c6aa 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java @@ -11,6 +11,7 @@ import java.util.List; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; @@ -42,9 +43,9 @@ void strict() throws IOException { } @Test - void strictMissing() throws IOException { + void strictMissing() { try { - parse(DoubleValue.strict(), "", DoubleChunk.chunkWrap(new double[1])); + process(DoubleValue.strict(), ""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Missing not allowed"); @@ -52,9 +53,9 @@ void strictMissing() throws IOException { } @Test - void strictNull() throws IOException { + void strictNull() { try { - parse(DoubleValue.strict(), "null", DoubleChunk.chunkWrap(new double[1])); + process(DoubleValue.strict(), "null"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Null not allowed"); @@ -62,9 +63,9 @@ void strictNull() throws IOException { } @Test - void standardString() throws IOException { + void standardString() { try { - parse(DoubleValue.standard(), "\"42\"", DoubleChunk.chunkWrap(new double[1])); + process(DoubleValue.standard(), "\"42\""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("String not allowed"); @@ -72,9 +73,9 @@ void standardString() throws IOException { } @Test - void standardTrue() throws IOException { + void standardTrue() { try { - parse(DoubleValue.standard(), "true", DoubleChunk.chunkWrap(new double[1])); + process(DoubleValue.standard(), "true"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -82,9 +83,9 @@ void standardTrue() throws IOException { } @Test - void standardFalse() throws IOException { + void standardFalse() { try { - parse(DoubleValue.standard(), "false", DoubleChunk.chunkWrap(new double[1])); + process(DoubleValue.standard(), "false"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -92,9 +93,9 @@ void standardFalse() throws IOException { } @Test - void standardObject() throws IOException { + void standardObject() { try { - parse(DoubleValue.standard(), "{}", DoubleChunk.chunkWrap(new double[1])); + process(DoubleValue.standard(), "{}"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Object not expected"); @@ -102,9 +103,9 @@ void standardObject() throws IOException { } @Test - void standardArray() throws IOException { + void standardArray() { try { - parse(DoubleValue.standard(), "[]", DoubleChunk.chunkWrap(new double[1])); + process(DoubleValue.standard(), "[]"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Array not expected"); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java index 63f1a75c611..7033075ebf4 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java @@ -11,6 +11,7 @@ import java.util.List; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; @@ -42,9 +43,9 @@ void strict() throws IOException { } @Test - void strictMissing() throws IOException { + void strictMissing() { try { - parse(FloatValue.strict(), "", FloatChunk.chunkWrap(new float[1])); + process(FloatValue.strict(), ""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Missing not allowed"); @@ -52,9 +53,9 @@ void strictMissing() throws IOException { } @Test - void strictNull() throws IOException { + void strictNull() { try { - parse(FloatValue.strict(), "null", FloatChunk.chunkWrap(new float[1])); + process(FloatValue.strict(), "null"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Null not allowed"); @@ -62,9 +63,9 @@ void strictNull() throws IOException { } @Test - void standardString() throws IOException { + void standardString() { try { - parse(FloatValue.standard(), "\"42\"", FloatChunk.chunkWrap(new float[1])); + process(FloatValue.standard(), "\"42\""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("String not allowed"); @@ -72,9 +73,9 @@ void standardString() throws IOException { } @Test - void standardTrue() throws IOException { + void standardTrue() { try { - parse(FloatValue.standard(), "true", FloatChunk.chunkWrap(new float[1])); + process(FloatValue.standard(), "true"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -82,9 +83,9 @@ void standardTrue() throws IOException { } @Test - void standardFalse() throws IOException { + void standardFalse() { try { - parse(FloatValue.standard(), "false", FloatChunk.chunkWrap(new float[1])); + process(FloatValue.standard(), "false"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -92,9 +93,9 @@ void standardFalse() throws IOException { } @Test - void standardObject() throws IOException { + void standardObject() { try { - parse(FloatValue.standard(), "{}", FloatChunk.chunkWrap(new float[1])); + process(FloatValue.standard(), "{}"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Object not expected"); @@ -102,9 +103,9 @@ void standardObject() throws IOException { } @Test - void standardArray() throws IOException { + void standardArray() { try { - parse(FloatValue.standard(), "[]", FloatChunk.chunkWrap(new float[1])); + process(FloatValue.standard(), "[]"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Array not expected"); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java index 7dbeb34bf7d..0602f18f325 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java @@ -11,6 +11,7 @@ import java.time.Instant; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; @@ -42,7 +43,7 @@ void standardMissing() throws IOException { @Test void strictNull() throws IOException { try { - parse(InstantValue.strict(), "null", LongChunk.chunkWrap(new long[1])); + process(InstantValue.strict(), "null"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Null not allowed"); @@ -52,7 +53,7 @@ void strictNull() throws IOException { @Test void strictMissing() throws IOException { try { - parse(InstantValue.strict(), "", LongChunk.chunkWrap(new long[1])); + process(InstantValue.strict(), ""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Missing not allowed"); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java index 7e7a7d546c0..b6e9dcca009 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java @@ -12,6 +12,7 @@ import java.util.List; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; @@ -48,9 +49,9 @@ void strict() throws IOException { } @Test - void strictMissing() throws IOException { + void strictMissing() { try { - parse(IntValue.strict(), "", IntChunk.chunkWrap(new int[1])); + process(IntValue.strict(), ""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Missing not allowed"); @@ -58,9 +59,9 @@ void strictMissing() throws IOException { } @Test - void strictNull() throws IOException { + void strictNull() { try { - parse(IntValue.strict(), "null", IntChunk.chunkWrap(new int[1])); + process(IntValue.strict(), "null"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Null not allowed"); @@ -68,19 +69,20 @@ void strictNull() throws IOException { } @Test - void standardOverflow() throws IOException { + void standardOverflow() { try { - parse(IntValue.standard(), "2147483648", IntChunk.chunkWrap(new int[1])); - } catch (InputCoercionException e) { + process(IntValue.standard(), "2147483648"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { assertThat(e).hasMessageContaining( "Numeric value (2147483648) out of range of int (-2147483648 - 2147483647)"); } } @Test - void standardString() throws IOException { + void standardString() { try { - parse(IntValue.standard(), "\"42\"", IntChunk.chunkWrap(new int[1])); + process(IntValue.standard(), "\"42\""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("String not allowed"); @@ -88,9 +90,9 @@ void standardString() throws IOException { } @Test - void standardTrue() throws IOException { + void standardTrue() { try { - parse(IntValue.standard(), "true", IntChunk.chunkWrap(new int[1])); + process(IntValue.standard(), "true"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -98,9 +100,9 @@ void standardTrue() throws IOException { } @Test - void standardFalse() throws IOException { + void standardFalse() { try { - parse(IntValue.standard(), "false", IntChunk.chunkWrap(new int[1])); + process(IntValue.standard(), "false"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -108,9 +110,9 @@ void standardFalse() throws IOException { } @Test - void standardFloat() throws IOException { + void standardFloat() { try { - parse(IntValue.standard(), "42.0", IntChunk.chunkWrap(new int[1])); + process(IntValue.standard(), "42.0"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Decimal not allowed"); @@ -118,9 +120,9 @@ void standardFloat() throws IOException { } @Test - void standardObject() throws IOException { + void standardObject() { try { - parse(IntValue.standard(), "{}", IntChunk.chunkWrap(new int[1])); + process(IntValue.standard(), "{}"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Object not expected"); @@ -128,9 +130,9 @@ void standardObject() throws IOException { } @Test - void standardArray() throws IOException { + void standardArray() { try { - parse(IntValue.standard(), "[]", IntChunk.chunkWrap(new int[1])); + process(IntValue.standard(), "[]"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Array not expected"); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java index 85bf317c814..2031af7f605 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java @@ -10,6 +10,7 @@ import java.time.LocalDate; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; @@ -34,9 +35,9 @@ void standardMissing() throws IOException { } @Test - void strictNull() throws IOException { + void strictNull() { try { - parse(LocalDateValue.strict(), "null", ObjectChunk.chunkWrap(new LocalDate[1])); + process(LocalDateValue.strict(), "null"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Null not allowed"); @@ -44,9 +45,9 @@ void strictNull() throws IOException { } @Test - void strictMissing() throws IOException { + void strictMissing() { try { - parse(LocalDateValue.strict(), "", ObjectChunk.chunkWrap(new LocalDate[1])); + process(LocalDateValue.strict(), ""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Missing not allowed"); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java index af1151e5c9f..05b2e7f2d75 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java @@ -12,6 +12,7 @@ import java.util.List; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; @@ -43,9 +44,9 @@ void strict() throws IOException { } @Test - void strictMissing() throws IOException { + void strictMissing() { try { - parse(LongValue.strict(), "", LongChunk.chunkWrap(new long[1])); + process(LongValue.strict(), ""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Missing not allowed"); @@ -53,9 +54,9 @@ void strictMissing() throws IOException { } @Test - void strictNull() throws IOException { + void strictNull() { try { - parse(LongValue.strict(), "null", LongChunk.chunkWrap(new long[1])); + process(LongValue.strict(), "null"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Null not allowed"); @@ -63,19 +64,20 @@ void strictNull() throws IOException { } @Test - void strictOverflow() throws IOException { + void strictOverflow() { try { - parse(LongValue.strict(), "9223372036854775808", LongChunk.chunkWrap(new long[1])); - } catch (InputCoercionException e) { + process(LongValue.strict(), "9223372036854775808"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { assertThat(e).hasMessageContaining( "Numeric value (9223372036854775808) out of range of long (-9223372036854775808 - 9223372036854775807)"); } } @Test - void standardString() throws IOException { + void standardString() { try { - parse(LongValue.standard(), "\"42\"", LongChunk.chunkWrap(new long[1])); + process(LongValue.standard(), "\"42\""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("String not allowed"); @@ -83,9 +85,9 @@ void standardString() throws IOException { } @Test - void standardTrue() throws IOException { + void standardTrue() { try { - parse(LongValue.standard(), "true", LongChunk.chunkWrap(new long[1])); + process(LongValue.standard(), "true"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -93,9 +95,9 @@ void standardTrue() throws IOException { } @Test - void standardFalse() throws IOException { + void standardFalse() { try { - parse(LongValue.standard(), "false", LongChunk.chunkWrap(new long[1])); + process(LongValue.standard(), "false"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -103,9 +105,9 @@ void standardFalse() throws IOException { } @Test - void standardFloat() throws IOException { + void standardFloat() { try { - parse(LongValue.standard(), "42.0", LongChunk.chunkWrap(new long[1])); + process(LongValue.standard(), "42.0"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Decimal not allowed"); @@ -113,9 +115,9 @@ void standardFloat() throws IOException { } @Test - void standardObject() throws IOException { + void standardObject() { try { - parse(LongValue.standard(), "{}", LongChunk.chunkWrap(new long[1])); + process(LongValue.standard(), "{}"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Object not expected"); @@ -123,9 +125,9 @@ void standardObject() throws IOException { } @Test - void standardArray() throws IOException { + void standardArray() { try { - parse(LongValue.standard(), "[]", LongChunk.chunkWrap(new long[1])); + process(LongValue.standard(), "[]"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Array not expected"); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java index 4620dccc83a..3a4e39bf5f8 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java @@ -14,6 +14,7 @@ import java.util.List; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; @@ -201,4 +202,17 @@ void columnNamesWithFieldThatIsNotColumnNameCompatible() { assertThat(JacksonProvider.of(objPlusOneMinusOneCount).named(Type.stringType()).names()) .containsExactly("column_1", "column_12"); } + + @Test + void fieldException() { + try { + process(OBJECT_NAME_AGE_FIELD, "{\"name\": \"Devin\", \"age\": 43.42}"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unable to process field 'age'"); + assertThat(e).hasCauseInstanceOf(IOException.class); + assertThat(e.getCause()).hasMessageContaining("Decimal not allowed"); + assertThat(e.getCause()).hasNoCause(); + } + } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java index f54c570a4e3..eb2aebd017b 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java @@ -12,6 +12,7 @@ import java.util.List; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; @@ -48,9 +49,9 @@ void strict() throws IOException { } @Test - void strictMissing() throws IOException { + void strictMissing() { try { - parse(ShortValue.strict(), "", ShortChunk.chunkWrap(new short[1])); + process(ShortValue.strict(), ""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Missing not allowed"); @@ -58,9 +59,9 @@ void strictMissing() throws IOException { } @Test - void strictNull() throws IOException { + void strictNull() { try { - parse(ShortValue.strict(), "null", ShortChunk.chunkWrap(new short[1])); + process(ShortValue.strict(), "null"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Null not allowed"); @@ -68,19 +69,20 @@ void strictNull() throws IOException { } @Test - void standardOverflow() throws IOException { + void standardOverflow() { try { - parse(ShortValue.standard(), "2147483648", ShortChunk.chunkWrap(new short[1])); - } catch (InputCoercionException e) { + process(ShortValue.standard(), "2147483648"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { assertThat(e).hasMessageContaining( "Numeric value (2147483648) out of range of int (-2147483648 - 2147483647)"); } } @Test - void standardString() throws IOException { + void standardString() { try { - parse(ShortValue.standard(), "\"42\"", ShortChunk.chunkWrap(new short[1])); + process(ShortValue.standard(), "\"42\""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("String not allowed"); @@ -88,9 +90,9 @@ void standardString() throws IOException { } @Test - void standardTrue() throws IOException { + void standardTrue() { try { - parse(ShortValue.standard(), "true", ShortChunk.chunkWrap(new short[1])); + process(ShortValue.standard(), "true"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -98,9 +100,9 @@ void standardTrue() throws IOException { } @Test - void standardFalse() throws IOException { + void standardFalse() { try { - parse(ShortValue.standard(), "false", ShortChunk.chunkWrap(new short[1])); + process(ShortValue.standard(), "false"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -108,9 +110,9 @@ void standardFalse() throws IOException { } @Test - void standardFloat() throws IOException { + void standardFloat() { try { - parse(ShortValue.standard(), "42.0", ShortChunk.chunkWrap(new short[1])); + process(ShortValue.standard(), "42.0"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Decimal not allowed"); @@ -118,9 +120,9 @@ void standardFloat() throws IOException { } @Test - void standardObject() throws IOException { + void standardObject() { try { - parse(ShortValue.standard(), "{}", ShortChunk.chunkWrap(new short[1])); + process(ShortValue.standard(), "{}"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Object not expected"); @@ -128,9 +130,9 @@ void standardObject() throws IOException { } @Test - void standardArray() throws IOException { + void standardArray() { try { - parse(ShortValue.standard(), "[]", ShortChunk.chunkWrap(new short[1])); + process(ShortValue.standard(), "[]"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Array not expected"); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java index 3616b67bba7..99daa50e2be 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java @@ -10,6 +10,7 @@ import java.util.List; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; @@ -48,9 +49,9 @@ void strict() throws IOException { } @Test - void strictMissing() throws IOException { + void strictMissing() { try { - parse(StringValue.strict(), "", ObjectChunk.chunkWrap(new String[1])); + process(StringValue.strict(), ""); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Missing not allowed"); @@ -58,9 +59,9 @@ void strictMissing() throws IOException { } @Test - void strictNull() throws IOException { + void strictNull() { try { - parse(StringValue.strict(), "null", ObjectChunk.chunkWrap(new String[1])); + process(StringValue.strict(), "null"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Null not allowed"); @@ -69,9 +70,9 @@ void strictNull() throws IOException { @Test - void standardInt() throws IOException { + void standardInt() { try { - parse(StringValue.standard(), "42", ObjectChunk.chunkWrap(new String[1])); + process(StringValue.standard(), "42"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Number int not allowed"); @@ -79,9 +80,9 @@ void standardInt() throws IOException { } @Test - void standardFloat() throws IOException { + void standardFloat() { try { - parse(StringValue.standard(), "42.42", ObjectChunk.chunkWrap(new String[1])); + process(StringValue.standard(), "42.42"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Decimal not allowed"); @@ -90,9 +91,9 @@ void standardFloat() throws IOException { @Test - void standardTrue() throws IOException { + void standardTrue() { try { - parse(StringValue.standard(), "true", ObjectChunk.chunkWrap(new String[1])); + process(StringValue.standard(), "true"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -100,9 +101,9 @@ void standardTrue() throws IOException { } @Test - void standardFalse() throws IOException { + void standardFalse() { try { - parse(StringValue.standard(), "false", ObjectChunk.chunkWrap(new String[1])); + process(StringValue.standard(), "false"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Bool not expected"); @@ -110,9 +111,9 @@ void standardFalse() throws IOException { } @Test - void standardObject() throws IOException { + void standardObject() { try { - parse(StringValue.standard(), "{}", ObjectChunk.chunkWrap(new String[1])); + process(StringValue.standard(), "{}"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Object not expected"); @@ -120,9 +121,9 @@ void standardObject() throws IOException { } @Test - void standardArray() throws IOException { + void standardArray() { try { - parse(StringValue.standard(), "[]", ObjectChunk.chunkWrap(new String[1])); + process(StringValue.standard(), "[]"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { assertThat(e).hasMessageContaining("Array not expected"); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java index 48d862f9a42..3b051617b2f 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java @@ -30,6 +30,31 @@ public static void parse(Value options, List jsonRows, Chunk... expec public static void parse(ObjectProcessor processor, List rows, Chunk... expectedCols) throws IOException { + final List> out = process(processor, rows); + try { + assertThat(out.size()).isEqualTo(expectedCols.length); + assertThat(out.stream().map(Chunk::getChunkType).collect(Collectors.toList())) + .isEqualTo(Stream.of(expectedCols).map(Chunk::getChunkType).collect(Collectors.toList())); + for (int i = 0; i < expectedCols.length; ++i) { + check(out.get(i), expectedCols[i]); + } + } finally { + for (WritableChunk wc : out) { + wc.close(); + } + } + } + + public static List> process(Value options, String jsonRows) throws IOException { + return process(JacksonProvider.of(options).stringProcessor(), List.of(jsonRows)); + } + + // public static List> parse(Value options, String jsonRows, Chunk chunk) throws IOException { + // return process(JacksonProvider.of(options).stringProcessor(), List.of(jsonRows)); + // } + + public static List> process(ObjectProcessor processor, List rows) + throws IOException { final List> out = processor .outputTypes() .stream() @@ -37,9 +62,6 @@ public static void parse(ObjectProcessor processor, List rows, .map(x -> x.makeWritableChunk(rows.size())) .collect(Collectors.toList()); try { - assertThat(out.size()).isEqualTo(expectedCols.length); - assertThat(out.stream().map(Chunk::getChunkType).collect(Collectors.toList())) - .isEqualTo(Stream.of(expectedCols).map(Chunk::getChunkType).collect(Collectors.toList())); for (WritableChunk wc : out) { wc.setSize(0); } @@ -54,14 +76,13 @@ public static void parse(ObjectProcessor processor, List rows, } catch (UncheckedIOException e) { throw e.getCause(); } + return out; } - for (int i = 0; i < expectedCols.length; ++i) { - check(out.get(i), expectedCols[i]); - } - } finally { + } catch (IOException | RuntimeException e) { for (WritableChunk wc : out) { wc.close(); } + throw e; } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java index bcd302eff24..dc977d53694 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java @@ -11,6 +11,9 @@ import java.util.List; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; public class TupleOptionsTest { @@ -37,4 +40,17 @@ void stringSkipIntTuple() throws IOException { "[\"bar\", 43]"), ObjectChunk.chunkWrap(new String[] {"foo", "bar"})); } + + @Test + void indexException() { + try { + process(STRING_INT_TUPLE, "[\"foo\", 43.43]"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unable to process tuple ix 1"); + assertThat(e).hasCauseInstanceOf(IOException.class); + assertThat(e.getCause()).hasMessageContaining("Decimal not allowed"); + assertThat(e.getCause()).hasNoCause(); + } + } } From cf817698f2387f6ac6c71392dd0d417f4bca17e6 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Tue, 28 May 2024 10:02:22 -0700 Subject: [PATCH 23/53] Rename tests --- ...rayOptionsTest.java => BoolArrayTest.java} | 2 +- ...yteOptionsTest.java => ByteValueTest.java} | 3 +-- ...harOptionsTest.java => CharValueTest.java} | 2 +- ...yOptionsTest.java => DoubleArrayTest.java} | 2 +- ...eOptionsTest.java => DoubleValueTest.java} | 2 +- ...atOptionsTest.java => FloatValueTest.java} | 2 +- ...ptionsTest.java => InstantNumberTest.java} | 2 +- ...OptionsTest.java => InstantValueTest.java} | 2 +- ...rrayOptionsTest.java => IntArrayTest.java} | 20 +++++++++---------- ...{IntOptionsTest.java => IntValueTest.java} | 3 +-- ...tionsTest.java => LocalDateValueTest.java} | 2 +- ...rayOptionsTest.java => LongArrayTest.java} | 2 +- ...ongOptionsTest.java => LongValueTest.java} | 3 +-- ...dOptionsTest.java => ObjectFieldTest.java} | 2 +- ...ptionsTest.java => ObjectKvValueTest.java} | 2 +- ...tOptionsTest.java => ObjectValueTest.java} | 2 +- ...rtOptionsTest.java => ShortValueTest.java} | 3 +-- ...gOptionsTest.java => StringValueTest.java} | 2 +- ...leOptionsTest.java => TupleValueTest.java} | 2 +- ...onsTest.java => TypedObjectValueTest.java} | 2 +- ...ionsTest.java => JacksonAnyValueTest.java} | 2 +- ....java => TypedObjectValueBuilderTest.java} | 2 +- 22 files changed, 31 insertions(+), 35 deletions(-) rename extensions/json-jackson/src/test/java/io/deephaven/json/{BoolArrayOptionsTest.java => BoolArrayTest.java} (95%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{ByteOptionsTest.java => ByteValueTest.java} (98%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{CharOptionsTest.java => CharValueTest.java} (99%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{DoubleArrayOptionsTest.java => DoubleArrayTest.java} (95%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{DoubleOptionsTest.java => DoubleValueTest.java} (99%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{FloatOptionsTest.java => FloatValueTest.java} (99%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{InstantNumberOptionsTest.java => InstantNumberTest.java} (98%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{InstantOptionsTest.java => InstantValueTest.java} (98%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{IntArrayOptionsTest.java => IntArrayTest.java} (53%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{IntOptionsTest.java => IntValueTest.java} (98%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{LocalDateOptionsTest.java => LocalDateValueTest.java} (98%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{LongArrayOptionsTest.java => LongArrayTest.java} (95%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{LongOptionsTest.java => LongValueTest.java} (98%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{ObjectFieldOptionsTest.java => ObjectFieldTest.java} (98%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{ObjectKvOptionsTest.java => ObjectKvValueTest.java} (98%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{ObjectOptionsTest.java => ObjectValueTest.java} (99%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{ShortOptionsTest.java => ShortValueTest.java} (98%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{StringOptionsTest.java => StringValueTest.java} (99%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{TupleOptionsTest.java => TupleValueTest.java} (98%) rename extensions/json-jackson/src/test/java/io/deephaven/json/{TypedObjectOptionsTest.java => TypedObjectValueTest.java} (98%) rename extensions/json-jackson/src/test/java/io/deephaven/json/jackson/{JacksonAnyOptionsTest.java => JacksonAnyValueTest.java} (99%) rename extensions/json/src/test/java/io/deephaven/json/{TypedObjectValueTest.java => TypedObjectValueBuilderTest.java} (97%) diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayTest.java similarity index 95% rename from extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayTest.java index 67a413d8718..e5efb746df9 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/BoolArrayTest.java @@ -10,7 +10,7 @@ import static io.deephaven.json.TestHelper.parse; -public class BoolArrayOptionsTest { +public class BoolArrayTest { @Test void standard() throws IOException { diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteValueTest.java similarity index 98% rename from extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/ByteValueTest.java index 3647e341de8..bf23952084e 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ByteOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteValueTest.java @@ -3,7 +3,6 @@ // package io.deephaven.json; -import com.fasterxml.jackson.core.exc.InputCoercionException; import io.deephaven.chunk.ByteChunk; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -16,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class ByteOptionsTest { +public class ByteValueTest { @Test void standard() throws IOException { diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/CharValueTest.java similarity index 99% rename from extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/CharValueTest.java index 7e822436ea0..250d1f587b0 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/CharOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/CharValueTest.java @@ -14,7 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class CharOptionsTest { +public class CharValueTest { @Test void standard() throws IOException { diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayTest.java similarity index 95% rename from extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayTest.java index 9ec4e7253cb..060b1504261 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleArrayTest.java @@ -11,7 +11,7 @@ import static io.deephaven.json.TestHelper.parse; -public class DoubleArrayOptionsTest { +public class DoubleArrayTest { @Test void standard() throws IOException { diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleValueTest.java similarity index 99% rename from extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/DoubleValueTest.java index 7a21c90c6aa..7be9ef541f2 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleValueTest.java @@ -15,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class DoubleOptionsTest { +public class DoubleValueTest { @Test void standard() throws IOException { diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/FloatValueTest.java similarity index 99% rename from extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/FloatValueTest.java index 7033075ebf4..6050d2149f1 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/FloatOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/FloatValueTest.java @@ -15,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class FloatOptionsTest { +public class FloatValueTest { @Test void standard() throws IOException { diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberTest.java similarity index 98% rename from extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberTest.java index 776d2bba1a1..0e67dc20aaa 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberTest.java @@ -11,7 +11,7 @@ import static io.deephaven.json.TestHelper.parse; -public class InstantNumberOptionsTest { +public class InstantNumberTest { private static final long WITH_SECONDS = 1703292532000000000L; private static final long WITH_MILLIS = 1703292532123000000L; private static final long WITH_MICROS = 1703292532123456000L; diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantValueTest.java similarity index 98% rename from extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/InstantValueTest.java index 0602f18f325..be84ae96bb1 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantValueTest.java @@ -15,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class InstantOptionsTest { +public class InstantValueTest { private static final String XYZ_STR = "2009-02-13T23:31:30.123456789"; private static final long XYZ_NANOS = 1234567890L * 1_000_000_000 + 123456789; diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java similarity index 53% rename from extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java index f621b120292..d6c11257f15 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java @@ -10,20 +10,20 @@ import static io.deephaven.json.TestHelper.parse; -public class IntArrayOptionsTest { +public class IntArrayTest { @Test void standard() throws IOException { parse(IntValue.standard().array(), "[42, 43]", ObjectChunk.chunkWrap(new Object[] {new int[] {42, 43}})); } - // @Test - // void standardMissing() throws IOException { - // parse(IntOptions.standard().array(), "", ObjectChunk.chunkWrap(new Object[] { null })); - // } - // - // @Test - // void standardNull() throws IOException { - // parse(IntOptions.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] { null })); - // } + @Test + void standardMissing() throws IOException { + parse(IntValue.standard().array(), "", ObjectChunk.chunkWrap(new Object[] {null})); + } + + @Test + void standardNull() throws IOException { + parse(IntValue.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] {null})); + } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/IntValueTest.java similarity index 98% rename from extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/IntValueTest.java index b6e9dcca009..85af66137ad 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/IntOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/IntValueTest.java @@ -3,7 +3,6 @@ // package io.deephaven.json; -import com.fasterxml.jackson.core.exc.InputCoercionException; import io.deephaven.chunk.IntChunk; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -16,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class IntOptionsTest { +public class IntValueTest { @Test void standard() throws IOException { diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateValueTest.java similarity index 98% rename from extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateValueTest.java index 2031af7f605..bc4e293d5d7 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateValueTest.java @@ -14,7 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class LocalDateOptionsTest { +public class LocalDateValueTest { private static final String XYZ_STR = "2009-02-13"; diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayTest.java similarity index 95% rename from extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayTest.java index fcd31d0942f..6a6bf0075b2 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LongArrayTest.java @@ -10,7 +10,7 @@ import static io.deephaven.json.TestHelper.parse; -public class LongArrayOptionsTest { +public class LongArrayTest { @Test void standard() throws IOException { diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LongValueTest.java similarity index 98% rename from extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/LongValueTest.java index 05b2e7f2d75..c54820e41e0 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LongOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LongValueTest.java @@ -3,7 +3,6 @@ // package io.deephaven.json; -import com.fasterxml.jackson.core.exc.InputCoercionException; import io.deephaven.chunk.LongChunk; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -16,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class LongOptionsTest { +public class LongValueTest { @Test void standard() throws IOException { diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldTest.java similarity index 98% rename from extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldTest.java index 4f4fac27dfc..b5b1e93fcfe 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectFieldTest.java @@ -9,7 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class ObjectFieldOptionsTest { +public class ObjectFieldTest { @Test void basic() { final ObjectField field = ObjectField.of("Foo", IntValue.standard()); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvValueTest.java similarity index 98% rename from extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvValueTest.java index cd24f67f6fd..ea0c51f7b72 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvValueTest.java @@ -15,7 +15,7 @@ import static io.deephaven.json.TestHelper.parse; import static org.assertj.core.api.Assertions.assertThat; -public class ObjectKvOptionsTest { +public class ObjectKvValueTest { private static final ObjectKvValue STRING_INT_KV = ObjectKvValue.standard(IntValue.standard()); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectValueTest.java similarity index 99% rename from extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/ObjectValueTest.java index 3a4e39bf5f8..cb37d52fd65 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectValueTest.java @@ -18,7 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class ObjectOptionsTest { +public class ObjectValueTest { public static final ObjectValue OBJECT_AGE_FIELD = ObjectValue.builder() .putFields("age", IntValue.standard()) diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortValueTest.java similarity index 98% rename from extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/ShortValueTest.java index eb2aebd017b..141e125cfd5 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ShortOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortValueTest.java @@ -3,7 +3,6 @@ // package io.deephaven.json; -import com.fasterxml.jackson.core.exc.InputCoercionException; import io.deephaven.chunk.ShortChunk; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -16,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class ShortOptionsTest { +public class ShortValueTest { @Test void standard() throws IOException { diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java similarity index 99% rename from extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java index 99daa50e2be..95609eed2f2 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/StringOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java @@ -14,7 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class StringOptionsTest { +public class StringValueTest { @Test void standard() throws IOException { diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TupleValueTest.java similarity index 98% rename from extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/TupleValueTest.java index dc977d53694..18238a835ef 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TupleOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TupleValueTest.java @@ -15,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -public class TupleOptionsTest { +public class TupleValueTest { private static final TupleValue STRING_INT_TUPLE = TupleValue.of(StringValue.standard(), IntValue.standard()); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java similarity index 98% rename from extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java index 72f229dc30f..cc18204edb8 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java @@ -14,7 +14,7 @@ import static io.deephaven.json.TestHelper.parse; -public class TypedObjectOptionsTest { +public class TypedObjectValueTest { private static final ObjectValue QUOTE_OBJECT = ObjectValue.builder() .putFields("symbol", StringValue.strict()) .putFields("bid", DoubleValue.standard()) diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyOptionsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyValueTest.java similarity index 99% rename from extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyOptionsTest.java rename to extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyValueTest.java index 62517be351c..308e7331a0a 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyOptionsTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyValueTest.java @@ -28,7 +28,7 @@ import java.util.List; import java.util.Map; -public class JacksonAnyOptionsTest { +public class JacksonAnyValueTest { @Test void anyMissing() throws IOException { diff --git a/extensions/json/src/test/java/io/deephaven/json/TypedObjectValueTest.java b/extensions/json/src/test/java/io/deephaven/json/TypedObjectValueBuilderTest.java similarity index 97% rename from extensions/json/src/test/java/io/deephaven/json/TypedObjectValueTest.java rename to extensions/json/src/test/java/io/deephaven/json/TypedObjectValueBuilderTest.java index bec21e4bc7f..4a24b9807ac 100644 --- a/extensions/json/src/test/java/io/deephaven/json/TypedObjectValueTest.java +++ b/extensions/json/src/test/java/io/deephaven/json/TypedObjectValueBuilderTest.java @@ -9,7 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat; -public class TypedObjectValueTest { +public class TypedObjectValueBuilderTest { public static final ObjectValue TRADE = ObjectValue.builder() .putFields("symbol", StringValue.strict()) From 8d1e377ea229651ba3187e589e6a8566c78251b8 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Tue, 28 May 2024 10:05:19 -0700 Subject: [PATCH 24/53] f --- .../deephaven/json/jackson/JacksonProvider.java | 8 ++++++++ .../io/deephaven/json/jackson/JacksonSource.java | 15 ++++++--------- .../java/io/deephaven/json/jackson/Mixin.java | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java index 0fa604c5511..906e7f9e224 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java @@ -12,6 +12,7 @@ import java.io.File; import java.net.URL; import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.file.Path; import java.util.List; import java.util.Set; @@ -145,4 +146,11 @@ default Set> inputTypes() { * @return the object processor */ ObjectProcessor byteBufferProcessor(); + + /** + * Creates a {@link CharBuffer} json object processor. + * + * @return the object processor + */ + ObjectProcessor charBufferProcessor(); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java index 0f31732de4a..adb9b278ce0 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonSource.java @@ -28,7 +28,6 @@ public static JsonParser of(JsonFactory factory, File file) throws IOException { } public static JsonParser of(JsonFactory factory, Path path) throws IOException { - // TODO: suggest jackson build this in if (FileSystems.getDefault() == path.getFileSystem()) { return of(factory, path.toFile()); } @@ -48,28 +47,26 @@ public static JsonParser of(JsonFactory factory, URL url) throws IOException { return factory.createParser(url); } - public static JsonParser of(JsonFactory factory, byte[] array, int pos, int len) throws IOException { - return factory.createParser(array, pos, len); + public static JsonParser of(JsonFactory factory, byte[] array, int offset, int len) throws IOException { + return factory.createParser(array, offset, len); } public static JsonParser of(JsonFactory factory, ByteBuffer buffer) throws IOException { - // TODO: suggest jackson build this in if (buffer.hasArray()) { return of(factory, buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); } return of(factory, ByteBufferInputStream.of(buffer)); } - public static JsonParser of(JsonFactory factory, char[] array, int pos, int len) throws IOException { - return factory.createParser(array, pos, len); + public static JsonParser of(JsonFactory factory, char[] array, int offset, int len) throws IOException { + return factory.createParser(array, offset, len); } public static JsonParser of(JsonFactory factory, CharBuffer buffer) throws IOException { - // TODO: suggest jackson build this in if (buffer.hasArray()) { return of(factory, buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); } - // We could build CharBufferReader. Surprised it's not build into JDK. - throw new RuntimeException("Only supports CharBuffer when backed by array"); + // We could be more efficient here with CharBufferReader. Surprised it's not build into JDK. + return of(factory, buffer.toString()); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index 80b34c07b27..a6f07787f05 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -38,6 +38,7 @@ import java.io.IOException; import java.net.URL; import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; @@ -113,6 +114,9 @@ public final ObjectProcessor processor(Type inputType) { if (ByteBuffer.class.isAssignableFrom(clazz)) { return (ObjectProcessor) byteBufferProcessor(); } + if (CharBuffer.class.isAssignableFrom(clazz)) { + return (ObjectProcessor) charBufferProcessor(); + } throw new IllegalArgumentException("Unable to create JSON processor from type " + inputType); } @@ -151,6 +155,11 @@ public final ObjectProcessor byteBufferProcessor() { return new ByteBufferIn(); } + @Override + public final ObjectProcessor charBufferProcessor() { + return new CharBufferIn(); + } + final Mixin mixin(Value options) { return of(options, factory); } @@ -238,6 +247,13 @@ protected JsonParser createParser(ByteBuffer in) throws IOException { } } + private class CharBufferIn extends ObjectProcessorMixin { + @Override + protected JsonParser createParser(CharBuffer in) throws IOException { + return JacksonSource.of(factory, in); + } + } + private class CharsIn extends ObjectProcessorMixin { @Override protected JsonParser createParser(char[] in) throws IOException { From 5828f1a82600ff83350b013cab8d832ae7ddc987 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Tue, 28 May 2024 10:59:19 -0700 Subject: [PATCH 25/53] stuff --- .../deephaven/processor/ObjectProcessor.java | 4 +- .../deephaven/json/jackson/ObjectMixin.java | 69 ++++++++++--------- .../json/jackson/RepeaterProcessor.java | 2 +- 3 files changed, 39 insertions(+), 36 deletions(-) diff --git a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java index 06c7d7e16f0..47ac0d40963 100644 --- a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java +++ b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java @@ -185,9 +185,9 @@ default int size() { interface Provider { /** - * The base input types for {@link #processor(Type)}. + * The supported input types for {@link #processor(Type)}. * - * @return the input types + * @return the supported input types */ Set> inputTypes(); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java index c647a8b6513..9390440883f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -102,7 +102,6 @@ ObjectValueFieldProcessor processorImpl(Map fields) final class ObjectValueFieldProcessor extends ContextAwareDelegateBase implements ValueProcessor, FieldProcessor { private final Map fields; private final Map map; - private final Set visited; ObjectValueFieldProcessor(Map fields) { super(fields.values()); @@ -187,10 +186,25 @@ private void processEmptyObject(JsonParser parser) throws IOException { } } + // ----------------------------------------------------------------------------------------------------------- + + private final Set visited; + private void processObjectFields(JsonParser parser) throws IOException { - visited.clear(); - FieldProcessor.processFields(parser, this); - processMissingFields(parser); + try { + FieldProcessor.processFields(parser, this); + if (visited.size() == fields.size()) { + // All fields visited, none missing + return; + } + for (Entry e : fields.entrySet()) { + if (!visited.contains(e.getKey())) { + processMissingField(e.getKey(), e.getValue(), parser); + } + } + } finally { + visited.clear(); + } } @Override @@ -213,17 +227,7 @@ public void process(String fieldName, JsonParser parser) throws IOException { } } - void processMissingFields(JsonParser parser) throws IOException { - if (visited.size() == fields.size()) { - // All fields visited, none missing - return; - } - for (Entry e : fields.entrySet()) { - if (!visited.contains(e.getKey())) { - processMissingField(e.getKey(), e.getValue(), parser); - } - } - } + // ----------------------------------------------------------------------------------------------------------- private void processField(ObjectField field, ValueProcessor processor, JsonParser parser) throws ValueAwareException { @@ -363,16 +367,27 @@ private void processEmptyObject(JsonParser parser) throws IOException { } } - private void processObjectFields(JsonParser parser) throws IOException { - visited.clear(); - FieldProcessor.processFields(parser, this); - processMissingFields(parser); - } - // ----------------------------------------------------------------------------------------------------------- private final Set visited; + private void processObjectFields(JsonParser parser) throws IOException { + try { + FieldProcessor.processFields(parser, this); + if (visited.size() == fields.size()) { + // All fields visited, none missing + return; + } + for (Entry e : contexts.entrySet()) { + if (!visited.contains(e.getKey())) { + e.getValue().processElementMissing(parser); + } + } + } finally { + visited.clear(); + } + } + @Override public void process(String fieldName, JsonParser parser) throws IOException { final ObjectField field = lookupField(fieldName); @@ -396,18 +411,6 @@ public void process(String fieldName, JsonParser parser) throws IOException { } } - void processMissingFields(JsonParser parser) throws IOException { - if (visited.size() == fields.size()) { - // All fields visited, none missing - return; - } - for (Entry e : contexts.entrySet()) { - if (!visited.contains(e.getKey())) { - e.getValue().processElementMissing(parser); - } - } - } - // ----------------------------------------------------------------------------------------------------------- @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java index 2635ef4dbc6..2118ba40bb4 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessor.java @@ -54,7 +54,7 @@ interface Context { void processElement(JsonParser parser) throws IOException; - // While a traditional arrays can't have missing elements, when an object is an array, a field may be missing: + // While traditional arrays can't have missing elements, when an object is an array, a field may be missing: // [ { "foo": 1, "bar": 2 }, {"bar": 3} ] void processElementMissing(JsonParser parser) throws IOException; From 5022c2d257117d374f8eb280c0600df85e8738fb Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Tue, 28 May 2024 12:08:17 -0700 Subject: [PATCH 26/53] Tests and stuff --- .../processor/NamedObjectProcessor.java | 6 +- .../deephaven/processor/ObjectProcessor.java | 5 +- .../processor/ObjectProcessorRowLimited.java | 4 +- .../processor/ObjectProcessorStrict.java | 12 +- .../processor/ObjectProcessorStrictTest.java | 11 +- .../io/deephaven/json/jackson/Exceptions.java | 5 - .../jackson/ObjectProcessorJsonValue.java | 2 +- .../deephaven/json/BigDecimalValueTest.java | 127 +++++++++++++++ .../deephaven/json/BigIntegerValueTest.java | 149 ++++++++++++++++++ .../java/io/deephaven/json/BoolValueTest.java | 135 ++++++++++++++++ .../java/io/deephaven/json/ByteValueTest.java | 2 +- .../java/io/deephaven/json/CharValueTest.java | 2 +- .../java/io/deephaven/json/IntValueTest.java | 2 +- .../java/io/deephaven/json/LongValueTest.java | 2 +- .../io/deephaven/json/ShortValueTest.java | 2 +- .../io/deephaven/json/StringValueTest.java | 2 +- .../java/io/deephaven/json/TestHelper.java | 3 - 17 files changed, 438 insertions(+), 33 deletions(-) create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/BigDecimalValueTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/BigIntegerValueTest.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/BoolValueTest.java diff --git a/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java b/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java index f47c8caa3b6..4da3d25b526 100644 --- a/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java +++ b/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java @@ -29,7 +29,7 @@ public static NamedObjectProcessor of(ObjectProcessor processo } public static NamedObjectProcessor prefix(ObjectProcessor processor, String prefix) { - final int size = processor.size(); + final int size = processor.numOutputs(); if (size == 1) { return of(processor, prefix); } @@ -85,10 +85,10 @@ default NamedObjectProcessor named(Type inputType) { @Check final void checkSizes() { - if (names().size() != processor().size()) { + if (names().size() != processor().numOutputs()) { throw new IllegalArgumentException( String.format("Unmatched sizes; names().size()=%d, processor().size()=%d", - names().size(), processor().size())); + names().size(), processor().numOutputs())); } } } diff --git a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java index 47ac0d40963..066a844e9ac 100644 --- a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java +++ b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java @@ -146,7 +146,7 @@ static ChunkType chunkType(Type type) { * * @return the number of outputs */ - default int size() { + default int numOutputs() { return outputTypes().size(); } @@ -199,7 +199,8 @@ interface Provider { List> outputTypes(); /** - * The number of output types for the processors. Equivalent to the processors' {@link ObjectProcessor#size()}. + * The number of output types for the processors. Equivalent to the processors' + * {@link ObjectProcessor#numOutputs()}. * * @return the number of output types */ diff --git a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorRowLimited.java b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorRowLimited.java index 6bf446b8415..4fe68a7cf75 100644 --- a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorRowLimited.java +++ b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorRowLimited.java @@ -49,8 +49,8 @@ int rowLimit() { } @Override - public int size() { - return delegate.size(); + public int numOutputs() { + return delegate.numOutputs(); } @Override diff --git a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorStrict.java b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorStrict.java index a62083d390d..691f8c6c36c 100644 --- a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorStrict.java +++ b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorStrict.java @@ -27,16 +27,16 @@ static ObjectProcessor create(ObjectProcessor delegate) { ObjectProcessorStrict(ObjectProcessor delegate) { this.delegate = Objects.requireNonNull(delegate); this.outputTypes = List.copyOf(delegate.outputTypes()); - if (delegate.size() != outputTypes.size()) { + if (delegate.numOutputs() != outputTypes.size()) { throw new IllegalArgumentException( - String.format("Inconsistent size. delegate.size()=%d, delegate.outputTypes().size()=%d", - delegate.size(), outputTypes.size())); + String.format("Inconsistent size. delegate.numOutputs()=%d, delegate.outputTypes().size()=%d", + delegate.numOutputs(), outputTypes.size())); } } @Override - public int size() { - return delegate.size(); + public int numOutputs() { + return delegate.numOutputs(); } @Override @@ -50,7 +50,7 @@ public List> outputTypes() { @Override public void processAll(ObjectChunk in, List> out) { - final int numColumns = delegate.size(); + final int numColumns = delegate.numOutputs(); if (numColumns != out.size()) { throw new IllegalArgumentException(String.format( "Improper number of out chunks. Expected delegate.outputTypes().size() == out.size(). delegate.outputTypes().size()=%d, out.size()=%d", diff --git a/engine/processor/src/test/java/io/deephaven/processor/ObjectProcessorStrictTest.java b/engine/processor/src/test/java/io/deephaven/processor/ObjectProcessorStrictTest.java index 667b077bc50..14bea583f09 100644 --- a/engine/processor/src/test/java/io/deephaven/processor/ObjectProcessorStrictTest.java +++ b/engine/processor/src/test/java/io/deephaven/processor/ObjectProcessorStrictTest.java @@ -103,7 +103,7 @@ public void testIncorrectChunkType() { } @Test - public void testNotEnoughOutputSize() { + public void testNotEnoughOutputNumOutputs() { ObjectProcessor delegate = ObjectProcessor.noop(List.of(Type.intType()), false); ObjectProcessor strict = ObjectProcessor.strict(delegate); try ( @@ -173,7 +173,7 @@ public void testBadDelegateOutputTypes() { private final List> outputTypes = new ArrayList<>(List.of(Type.intType())); @Override - public int size() { + public int numOutputs() { return 1; } @@ -227,11 +227,11 @@ public void processAll(ObjectChunk in, List> out) { } @Test - public void testBadDelegateSize() { + public void testBadDelegateNumOutputs() { try { ObjectProcessor.strict(new ObjectProcessor<>() { @Override - public int size() { + public int numOutputs() { return 2; } @@ -247,7 +247,8 @@ public void processAll(ObjectChunk in, List> out) { }); failBecauseExceptionWasNotThrown(IllegalAccessException.class); } catch (IllegalArgumentException e) { - assertThat(e).hasMessageContaining("Inconsistent size. delegate.size()=2, delegate.outputTypes().size()=1"); + assertThat(e).hasMessageContaining( + "Inconsistent size. delegate.numOutputs()=2, delegate.outputTypes().size()=1"); } } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Exceptions.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Exceptions.java index c48d00ba207..36702d59dc1 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Exceptions.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Exceptions.java @@ -27,11 +27,6 @@ static IOException notAllowed(JsonParser parser, Mixin mixin) { mixin.options); } - static IOException missingNotAllowed(JsonParser parser, Mixin mixin) { - final JsonLocation location = parser.currentLocation(); - return new ValueAwareException("Missing token not allowed", location, mixin.options); - } - public static class ValueAwareException extends JsonProcessingException { private final Value value; diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectProcessorJsonValue.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectProcessorJsonValue.java index 7d1b3f523d3..3d85f4112a0 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectProcessorJsonValue.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectProcessorJsonValue.java @@ -26,7 +26,7 @@ abstract class ObjectProcessorJsonValue implements ObjectProcessor { } @Override - public final int size() { + public final int numOutputs() { return processor.numColumns(); } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/BigDecimalValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/BigDecimalValueTest.java new file mode 100644 index 00000000000..d14a311b13a --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/BigDecimalValueTest.java @@ -0,0 +1,127 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.ObjectChunk; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class BigDecimalValueTest { + + @Test + void standard() throws IOException { + parse(BigDecimalValue.standard(), "42.42", ObjectChunk.chunkWrap(new BigDecimal[] {new BigDecimal("42.42")})); + } + + @Test + void standardMissing() throws IOException { + parse(BigDecimalValue.standard(), "", ObjectChunk.chunkWrap(new BigDecimal[] {null})); + } + + @Test + void standardNull() throws IOException { + parse(BigDecimalValue.standard(), "null", ObjectChunk.chunkWrap(new BigDecimal[] {null})); + } + + @Test + void customMissing() throws IOException { + parse(BigDecimalValue.builder().onMissing(BigDecimal.valueOf(-1)).build(), "", + ObjectChunk.chunkWrap(new BigDecimal[] {BigDecimal.valueOf(-1)})); + } + + @Test + void customNull() throws IOException { + parse(BigDecimalValue.builder().onNull(BigDecimal.valueOf(-2)).build(), "null", + ObjectChunk.chunkWrap(new BigDecimal[] {BigDecimal.valueOf(-2)})); + } + + @Test + void strict() throws IOException { + parse(BigDecimalValue.strict(), "42.42", ObjectChunk.chunkWrap(new BigDecimal[] {new BigDecimal("42.42")})); + } + + @Test + void strictMissing() { + try { + process(BigDecimalValue.strict(), ""); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Missing not allowed"); + } + } + + @Test + void strictNull() { + try { + process(BigDecimalValue.strict(), "null"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Null not allowed"); + } + } + + @Test + void standardString() { + try { + process(BigDecimalValue.standard(), "\"42\""); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("String not allowed"); + } + } + + @Test + void standardTrue() { + try { + process(BigDecimalValue.standard(), "true"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Bool not expected"); + } + } + + @Test + void standardFalse() { + try { + process(BigDecimalValue.standard(), "false"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Bool not expected"); + } + } + + @Test + void standardObject() { + try { + process(BigDecimalValue.standard(), "{}"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Object not expected"); + } + } + + @Test + void standardArray() { + try { + process(BigDecimalValue.standard(), "[]"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Array not expected"); + } + } + + @Test + void lenientString() throws IOException { + parse(BigDecimalValue.lenient(), List.of("\"42.42\"", "\"43.999\""), + ObjectChunk.chunkWrap(new BigDecimal[] {new BigDecimal("42.42"), new BigDecimal("43.999")})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/BigIntegerValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/BigIntegerValueTest.java new file mode 100644 index 00000000000..c04347b822a --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/BigIntegerValueTest.java @@ -0,0 +1,149 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.ObjectChunk; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class BigIntegerValueTest { + + @Test + void standard() throws IOException { + parse(BigIntegerValue.standard(false), "42", ObjectChunk.chunkWrap(new BigInteger[] {BigInteger.valueOf(42)})); + } + + @Test + void standardMissing() throws IOException { + parse(BigIntegerValue.standard(false), "", ObjectChunk.chunkWrap(new BigInteger[] {null})); + } + + @Test + void standardNull() throws IOException { + parse(BigIntegerValue.standard(false), "null", ObjectChunk.chunkWrap(new BigInteger[] {null})); + } + + @Test + void customMissing() throws IOException { + parse(BigIntegerValue.builder().onMissing(BigInteger.valueOf(-1)).build(), "", + ObjectChunk.chunkWrap(new BigInteger[] {BigInteger.valueOf(-1)})); + } + + @Test + void customNull() throws IOException { + parse(BigIntegerValue.builder().onNull(BigInteger.valueOf(-2)).build(), "null", + ObjectChunk.chunkWrap(new BigInteger[] {BigInteger.valueOf(-2)})); + } + + @Test + void strict() throws IOException { + parse(BigIntegerValue.strict(false), "42", ObjectChunk.chunkWrap(new BigInteger[] {BigInteger.valueOf(42)})); + } + + @Test + void strictMissing() { + try { + process(BigIntegerValue.strict(false), ""); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Missing not allowed"); + } + } + + @Test + void strictNull() { + try { + process(BigIntegerValue.strict(false), "null"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Null not allowed"); + } + } + + @Test + void standardString() { + try { + process(BigIntegerValue.standard(false), "\"42\""); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("String not allowed"); + } + } + + @Test + void standardTrue() { + try { + process(BigIntegerValue.standard(false), "true"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Bool not expected"); + } + } + + @Test + void standardFalse() { + try { + process(BigIntegerValue.standard(false), "false"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Bool not expected"); + } + } + + @Test + void standardDecimal() { + try { + process(BigIntegerValue.standard(false), "42.42"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Decimal not allowed"); + } + } + + @Test + void standardObject() { + try { + process(BigIntegerValue.standard(false), "{}"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Object not expected"); + } + } + + @Test + void standardArray() { + try { + process(BigIntegerValue.standard(false), "[]"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Array not expected"); + } + } + + @Test + void lenientString() throws IOException { + parse(BigIntegerValue.lenient(false), List.of("\"42\"", "\"43\""), + ObjectChunk.chunkWrap(new BigInteger[] {BigInteger.valueOf(42), BigInteger.valueOf(43)})); + } + + @Test + void allowDecimal() throws IOException { + parse(BigIntegerValue.standard(true), List.of("42.42", "43.999"), + ObjectChunk.chunkWrap(new BigInteger[] {BigInteger.valueOf(42), BigInteger.valueOf(43)})); + } + + @Test + void allowDecimalString() throws IOException { + parse(BigIntegerValue.lenient(true), List.of("\"42.42\"", "\"43.999\""), + ObjectChunk.chunkWrap(new BigInteger[] {BigInteger.valueOf(42), BigInteger.valueOf(43)})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/BoolValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/BoolValueTest.java new file mode 100644 index 00000000000..e7a2ef4f98a --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/BoolValueTest.java @@ -0,0 +1,135 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json; + +import io.deephaven.chunk.ByteChunk; +import io.deephaven.util.BooleanUtils; +import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class BoolValueTest { + + @Test + void standard() throws IOException { + parse(BoolValue.standard(), List.of("true", "false"), ByteChunk + .chunkWrap(new byte[] {BooleanUtils.TRUE_BOOLEAN_AS_BYTE, BooleanUtils.FALSE_BOOLEAN_AS_BYTE})); + } + + @Test + void standardMissing() throws IOException { + parse(BoolValue.standard(), "", ByteChunk.chunkWrap(new byte[] {BooleanUtils.NULL_BOOLEAN_AS_BYTE})); + } + + @Test + void standardNull() throws IOException { + parse(BoolValue.standard(), "null", ByteChunk.chunkWrap(new byte[] {BooleanUtils.NULL_BOOLEAN_AS_BYTE})); + } + + @Test + void customMissing() throws IOException { + parse(BoolValue.builder().onMissing(true).build(), "", + ByteChunk.chunkWrap(new byte[] {BooleanUtils.TRUE_BOOLEAN_AS_BYTE})); + parse(BoolValue.builder().onMissing(false).build(), "", + ByteChunk.chunkWrap(new byte[] {BooleanUtils.FALSE_BOOLEAN_AS_BYTE})); + } + + @Test + void customNull() throws IOException { + parse(BoolValue.builder().onNull(true).build(), "null", + ByteChunk.chunkWrap(new byte[] {BooleanUtils.TRUE_BOOLEAN_AS_BYTE})); + parse(BoolValue.builder().onNull(false).build(), "null", + ByteChunk.chunkWrap(new byte[] {BooleanUtils.FALSE_BOOLEAN_AS_BYTE})); + } + + @Test + void strict() throws IOException { + parse(BoolValue.strict(), List.of("true", "false"), ByteChunk + .chunkWrap(new byte[] {BooleanUtils.TRUE_BOOLEAN_AS_BYTE, BooleanUtils.FALSE_BOOLEAN_AS_BYTE})); + } + + @Test + void strictMissing() { + try { + process(BoolValue.strict(), ""); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Missing not allowed"); + } + } + + @Test + void strictNull() { + try { + process(BoolValue.strict(), "null"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Null not allowed"); + } + } + + @Test + void standardString() { + try { + process(BoolValue.standard(), "\"42\""); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("String not allowed"); + } + } + + @Test + void standardInt() { + try { + process(BoolValue.standard(), "42"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Number int not expected"); + } + } + + @Test + void standardDecimal() { + try { + process(BoolValue.standard(), "42.0"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Decimal not expected"); + } + } + + @Test + void standardObject() { + try { + process(BoolValue.standard(), "{}"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Object not expected"); + } + } + + @Test + void standardArray() { + try { + process(BoolValue.standard(), "[]"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Array not expected"); + } + } + + @Test + void lenientString() throws IOException { + parse(BoolValue.lenient(), List.of("\"true\"", "\"false\"", "\"null\""), + ByteChunk.chunkWrap(new byte[] {BooleanUtils.TRUE_BOOLEAN_AS_BYTE, BooleanUtils.FALSE_BOOLEAN_AS_BYTE, + BooleanUtils.NULL_BOOLEAN_AS_BYTE})); + } +} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ByteValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteValueTest.java index bf23952084e..f1316613f2b 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ByteValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteValueTest.java @@ -109,7 +109,7 @@ void standardFalse() { } @Test - void standardFloat() { + void standardDecimal() { try { process(ByteValue.standard(), "42.0"); failBecauseExceptionWasNotThrown(IOException.class); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/CharValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/CharValueTest.java index 250d1f587b0..eda160965bc 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/CharValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/CharValueTest.java @@ -78,7 +78,7 @@ void standardInt() { } @Test - void standardFloat() { + void standardDecimal() { try { process(CharValue.standard(), "42.42"); failBecauseExceptionWasNotThrown(IOException.class); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/IntValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/IntValueTest.java index 85af66137ad..1ad7768a5d6 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/IntValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/IntValueTest.java @@ -109,7 +109,7 @@ void standardFalse() { } @Test - void standardFloat() { + void standardDecimal() { try { process(IntValue.standard(), "42.0"); failBecauseExceptionWasNotThrown(IOException.class); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LongValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LongValueTest.java index c54820e41e0..50adefcc62c 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LongValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LongValueTest.java @@ -104,7 +104,7 @@ void standardFalse() { } @Test - void standardFloat() { + void standardDecimal() { try { process(LongValue.standard(), "42.0"); failBecauseExceptionWasNotThrown(IOException.class); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ShortValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortValueTest.java index 141e125cfd5..d09c9ae80ac 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ShortValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortValueTest.java @@ -109,7 +109,7 @@ void standardFalse() { } @Test - void standardFloat() { + void standardDecimal() { try { process(ShortValue.standard(), "42.0"); failBecauseExceptionWasNotThrown(IOException.class); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java index 95609eed2f2..b3ea9b3a852 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java @@ -80,7 +80,7 @@ void standardInt() { } @Test - void standardFloat() { + void standardDecimal() { try { process(StringValue.standard(), "42.42"); failBecauseExceptionWasNotThrown(IOException.class); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java index 3b051617b2f..28454c636c6 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java @@ -49,9 +49,6 @@ public static List> process(Value options, String jsonRows) thr return process(JacksonProvider.of(options).stringProcessor(), List.of(jsonRows)); } - // public static List> parse(Value options, String jsonRows, Chunk chunk) throws IOException { - // return process(JacksonProvider.of(options).stringProcessor(), List.of(jsonRows)); - // } public static List> process(ObjectProcessor processor, List rows) throws IOException { From 88db86d8d9176fa2a7c04e4ba0e819b2e5cbfbb5 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Tue, 28 May 2024 16:29:30 -0700 Subject: [PATCH 27/53] Typed object improvements --- .../json/jackson/FieldProcessor.java | 9 + .../java/io/deephaven/json/jackson/Mixin.java | 18 +- .../deephaven/json/jackson/ObjectMixin.java | 39 +++- .../json/jackson/TypedObjectMixin.java | 211 +++++++++--------- .../deephaven/json/TypedObjectValueTest.java | 173 ++++++++++++-- .../io/deephaven/json/TypedObjectValue.java | 32 +++ 6 files changed, 346 insertions(+), 136 deletions(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FieldProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FieldProcessor.java index f11354212f0..22f47e8d3a4 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FieldProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FieldProcessor.java @@ -27,5 +27,14 @@ static void processFields(JsonParser parser, FieldProcessor fieldProcess) throws assertCurrentToken(parser, JsonToken.END_OBJECT); } + static void skipFields(JsonParser parser) throws IOException { + while (parser.hasToken(JsonToken.FIELD_NAME)) { + parser.nextToken(); + parser.skipChildren(); + parser.nextToken(); + } + assertCurrentToken(parser, JsonToken.END_OBJECT); + } + void process(String fieldName, JsonParser parser) throws IOException; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index a6f07787f05..aa9f47eb75e 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -42,8 +42,9 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; @@ -210,11 +211,20 @@ static List prefixWith(String prefix, List path) { return Stream.concat(Stream.of(prefix), path.stream()).collect(Collectors.toList()); } - Stream> prefixWithKeys(Collection fields) { + static Stream> prefixWithKeys(Map> fields) { final List>> paths = new ArrayList<>(fields.size()); - for (ObjectField field : fields) { + for (Entry> e : fields.entrySet()) { + final Stream> prefixedPaths = e.getValue().paths().map(x -> prefixWith(e.getKey().name(), x)); + paths.add(prefixedPaths); + } + return paths.stream().flatMap(Function.identity()); + } + + static Stream> prefixWithKeysAndSkip(Map> fields, int skip) { + final List>> paths = new ArrayList<>(fields.size()); + for (Entry> e : fields.entrySet()) { final Stream> prefixedPaths = - mixin(field.options()).paths().map(x -> prefixWith(field.name(), x)); + e.getValue().paths().map(x -> prefixWith(e.getKey(), x)).skip(skip); paths.add(prefixedPaths); } return paths.stream().flatMap(Function.identity()); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java index 9390440883f..030baa4e78a 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -54,11 +54,15 @@ public int numColumns() { @Override public Stream> paths() { - return prefixWithKeys(options.fields()); + return prefixWithKeys(mixins); } @Override public ValueProcessor processor(String context) { + return processor(context, false); + } + + public ValueProcessor processor(String context, boolean isDiscriminatedObject) { final Map processors = new LinkedHashMap<>(options.fields().size()); int ix = 0; for (ObjectField field : options.fields()) { @@ -71,7 +75,7 @@ public ValueProcessor processor(String context) { if (ix != numColumns()) { throw new IllegalStateException(); } - return processorImpl(processors); + return processorImpl(processors, isDiscriminatedObject); } @Override @@ -95,20 +99,22 @@ private boolean allCaseSensitive() { return options.fields().stream().allMatch(ObjectField::caseSensitive); } - ObjectValueFieldProcessor processorImpl(Map fields) { - return new ObjectValueFieldProcessor(fields); + ObjectValueFieldProcessor processorImpl(Map fields, boolean isDiscriminatedObject) { + return new ObjectValueFieldProcessor(fields, isDiscriminatedObject); } final class ObjectValueFieldProcessor extends ContextAwareDelegateBase implements ValueProcessor, FieldProcessor { private final Map fields; private final Map map; + private final boolean isDiscriminatedObject; - ObjectValueFieldProcessor(Map fields) { + ObjectValueFieldProcessor(Map fields, boolean isDiscriminatedObject) { super(fields.values()); this.fields = fields; this.map = allCaseSensitive() ? new HashMap<>() : new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + this.isDiscriminatedObject = isDiscriminatedObject; for (Entry e : fields.entrySet()) { final ObjectField field = e.getKey(); map.put(field.name(), field); @@ -140,11 +146,11 @@ private ValueProcessor processor(ObjectField options) { @Override public void processCurrentValue(JsonParser parser) throws IOException { - // see com.fasterxml.jackson.databind.JsonDeserializer.deserialize(com.fasterxml.jackson.core.JsonParser, - // com.fasterxml.jackson.databind.DeserializationContext) - // for notes on FIELD_NAME switch (parser.currentToken()) { case START_OBJECT: + if (isDiscriminatedObject) { + throw unexpectedToken(parser); + } if (parser.nextToken() == JsonToken.END_OBJECT) { processEmptyObject(parser); return; @@ -152,13 +158,26 @@ public void processCurrentValue(JsonParser parser) throws IOException { if (!parser.hasToken(JsonToken.FIELD_NAME)) { throw new IllegalStateException(); } - // fall-through - case FIELD_NAME: processObjectFields(parser); return; case VALUE_NULL: + if (isDiscriminatedObject) { + throw unexpectedToken(parser); + } processNullObject(parser); return; + case FIELD_NAME: + if (!isDiscriminatedObject) { + throw unexpectedToken(parser); + } + processObjectFields(parser); + return; + case END_OBJECT: + if (!isDiscriminatedObject) { + throw unexpectedToken(parser); + } + processEmptyObject(parser); + return; default: throw unexpectedToken(parser); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java index ad907abb888..226e224daa4 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java @@ -11,11 +11,13 @@ import io.deephaven.json.ObjectField; import io.deephaven.json.ObjectValue; import io.deephaven.json.TypedObjectValue; +import io.deephaven.json.jackson.Exceptions.ValueAwareException; import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -26,25 +28,36 @@ import java.util.stream.Stream; final class TypedObjectMixin extends Mixin { + + private final Map> sharedFields; + private final Map combinedFields; + private final int numSharedColumns; + private final int numSpecificColumns; + public TypedObjectMixin(TypedObjectValue options, JsonFactory factory) { super(factory, options); + { + final LinkedHashMap> map = new LinkedHashMap<>(options.sharedFields().size()); + for (ObjectField sharedField : options.sharedFields()) { + map.put(sharedField, mixin(sharedField.options())); + } + sharedFields = Collections.unmodifiableMap(map); + } + { + final LinkedHashMap map = new LinkedHashMap<>(options.objects().size()); + for (Entry e : options.objects().entrySet()) { + map.put(e.getKey(), new ObjectMixin(combinedObject(e.getValue()), factory)); + } + combinedFields = Collections.unmodifiableMap(map); + } + numSharedColumns = sharedFields.values().stream().mapToInt(Mixin::numColumns).sum(); + numSpecificColumns = + combinedFields.values().stream().mapToInt(ObjectMixin::numColumns).map(x -> x - numSharedColumns).sum(); } @Override public int numColumns() { - return 1 - + options.sharedFields() - .stream() - .map(ObjectField::options) - .map(this::mixin) - .mapToInt(Mixin::numColumns) - .sum() - + options.objects() - .values() - .stream() - .map(this::mixin) - .mapToInt(Mixin::numColumns) - .sum(); + return 1 + numSharedColumns + numSpecificColumns; } @Override @@ -52,9 +65,8 @@ public Stream> paths() { return Stream.concat( Stream.of(List.of(options.typeFieldName())), Stream.concat( - prefixWithKeys(options.sharedFields()), - prefixWithKeys(options.objects().values().stream().map(ObjectValue::fields) - .flatMap(Collection::stream).collect(Collectors.toList())))); + prefixWithKeys(sharedFields), + prefixWithKeysAndSkip(combinedFields, numSharedColumns))); } @Override @@ -62,19 +74,17 @@ public Stream> outputTypesImpl() { return Stream.concat( Stream.of(Type.stringType()), Stream.concat( - options.sharedFields().stream().map(ObjectField::options).map(this::mixin) - .flatMap(Mixin::outputTypesImpl), - options.objects().values().stream().map(this::mixin).flatMap(Mixin::outputTypesImpl))); + sharedFields.values().stream().flatMap(Mixin::outputTypesImpl), + combinedFields.values().stream().map(Mixin::outputTypesImpl) + .flatMap(x -> x.skip(numSharedColumns)))); } @Override public ValueProcessor processor(String context) { - final Map processors = new LinkedHashMap<>(options.objects().size()); - for (Entry e : options.objects().entrySet()) { + final Map processors = new LinkedHashMap<>(combinedFields.size()); + for (Entry e : combinedFields.entrySet()) { final String type = e.getKey(); - final ObjectValue specificOpts = e.getValue(); - final ObjectValue combinedObject = combinedObject(specificOpts); - final ValueProcessor processor = mixin(combinedObject).processor(context + "[" + type + "]"); + final ValueProcessor processor = e.getValue().processor(context + "[" + type + "]", true); processors.put(type, new Processor(processor)); } return new DiscriminatedProcessor(processors); @@ -85,19 +95,6 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { throw new UnsupportedOperationException(); } - private static List concat(List x, List y) { - if (x.isEmpty()) { - return y; - } - if (y.isEmpty()) { - return x; - } - final List out = new ArrayList<>(x.size() + y.size()); - out.addAll(x); - out.addAll(y); - return out; - } - private ObjectValue combinedObject(ObjectValue objectOpts) { final Set sharedFields = options.sharedFields(); if (sharedFields.isEmpty()) { @@ -113,92 +110,95 @@ private ObjectValue combinedObject(ObjectValue objectOpts) { } private String parseTypeField(JsonParser parser) throws IOException { - final String currentFieldName = parser.currentName(); - if (!options.typeFieldName().equals(currentFieldName)) { - throw new IOException("Can only process when first field in object is the type"); + final String actualFieldName = parser.currentName(); + if (!options.typeFieldName().equals(actualFieldName)) { + throw new ValueAwareException(String.format("Expected the first field to be '%s', is '%s'", + options.typeFieldName(), actualFieldName), parser.currentLocation(), options); } switch (parser.nextToken()) { case VALUE_STRING: case FIELD_NAME: return parser.getText(); case VALUE_NULL: - checkNullAllowed(parser); return null; default: - throw Exceptions.notAllowed(parser, this); + throw unexpectedToken(parser); } } private static class Processor { - private final ValueProcessor valueProcessor; + private final ValueProcessor combinedProcessor; + private final List> buffer; private List> specificOut; - public Processor(ValueProcessor valueProcessor) { - this.valueProcessor = Objects.requireNonNull(valueProcessor); + Processor(ValueProcessor combinedProcessor) { + this.combinedProcessor = Objects.requireNonNull(combinedProcessor); + this.buffer = new ArrayList<>(combinedProcessor.numColumns()); } void setContext(List> sharedOut, List> specifiedOut) { this.specificOut = Objects.requireNonNull(specifiedOut); - valueProcessor.setContext(concat(sharedOut, specifiedOut)); + buffer.clear(); + buffer.addAll(sharedOut); + buffer.addAll(specifiedOut); + combinedProcessor.setContext(buffer); } void clearContext() { - valueProcessor.clearContext(); + combinedProcessor.clearContext(); + buffer.clear(); specificOut = null; } - ValueProcessor processor() { - return valueProcessor; + ValueProcessor combinedProcessor() { + return combinedProcessor; } void notApplicable() { // only skip specific fields for (WritableChunk wc : specificOut) { - final int size = wc.size(); - wc.fillWithNullValue(size, 1); - wc.setSize(size + 1); + addNullValue(wc); } } } private class DiscriminatedProcessor implements ValueProcessor { - private WritableObjectChunk typeOut; - private List> sharedFields; - private final Map processors; - private final int numColumns; + private final Map combinedProcessors; + + private WritableObjectChunk typeChunk; + private List> sharedChunks; - public DiscriminatedProcessor(Map processors) { - this.processors = Objects.requireNonNull(processors); - this.numColumns = TypedObjectMixin.this.numColumns(); + public DiscriminatedProcessor(Map combinedProcessors) { + this.combinedProcessors = Objects.requireNonNull(combinedProcessors); } @Override public void setContext(List> out) { - typeOut = out.get(0).asWritableObjectChunk(); - sharedFields = out.subList(1, 1 + options.sharedFields().size()); - int outIx = 1 + sharedFields.size(); - for (Processor value : processors.values()) { - final int numColumns = value.processor().numColumns(); - final int numSpecificFields = numColumns - options.sharedFields().size(); - final List> specificChunks = out.subList(outIx, outIx + numSpecificFields); - value.setContext(sharedFields, specificChunks); - outIx += numSpecificFields; + typeChunk = out.get(0).asWritableObjectChunk(); + sharedChunks = out.subList(1, 1 + numSharedColumns); + int outIx = 1 + sharedChunks.size(); + for (Processor combinedProcessor : combinedProcessors.values()) { + final int numColumns = combinedProcessor.combinedProcessor().numColumns(); + final int numSpecificColumns = numColumns - numSharedColumns; + final List> specificChunks = out.subList(outIx, outIx + numSpecificColumns); + combinedProcessor.setContext(sharedChunks, specificChunks); + outIx += numSpecificColumns; } } @Override public void clearContext() { - typeOut = null; - sharedFields = null; - for (Processor value : processors.values()) { - value.clearContext(); + typeChunk = null; + sharedChunks = null; + for (Processor combinedProcessor : combinedProcessors.values()) { + combinedProcessor.clearContext(); } } @Override public int numColumns() { - return numColumns; + return TypedObjectMixin.this.numColumns(); } @Override @@ -217,13 +217,11 @@ public void processCurrentValue(JsonParser parser) throws IOException { if (!parser.hasToken(JsonToken.FIELD_NAME)) { throw new IllegalStateException(); } - // fall-through - case FIELD_NAME: processObjectFields(parser); - break; + return; case VALUE_NULL: processNullObject(parser); - break; + return; default: throw unexpectedToken(parser); } @@ -232,54 +230,61 @@ public void processCurrentValue(JsonParser parser) throws IOException { @Override public void processMissing(JsonParser parser) throws IOException { checkMissingAllowed(parser); - // onMissingType()? - typeOut.add(null); - for (Processor processor : processors.values()) { + typeChunk.add(options.onMissing().orElse(null)); + for (WritableChunk sharedChunk : sharedChunks) { + addNullValue(sharedChunk); + } + for (Processor processor : combinedProcessors.values()) { processor.notApplicable(); } } private void processNullObject(JsonParser parser) throws IOException { checkNullAllowed(parser); - // onNullType()? - typeOut.add(null); - for (Processor processor : processors.values()) { + typeChunk.add(options.onNull().orElse(null)); + for (WritableChunk sharedChunk : sharedChunks) { + addNullValue(sharedChunk); + } + for (Processor processor : combinedProcessors.values()) { processor.notApplicable(); } } private void processEmptyObject(JsonParser parser) throws IOException { - // this logic should be equivalent to processObjectFields w/ no fields - // suggests that maybe this branch should be an error b/c we _need_ type field? - throw new IOException("no field"); + throw new ValueAwareException("Expected a non-empty object", parser.currentLocation(), options); } private void processObjectFields(JsonParser parser) throws IOException { final String typeFieldValue = parseTypeField(parser); - if (!options.allowUnknownTypes()) { - if (!processors.containsKey(typeFieldValue)) { - throw new IOException(String.format("Unmapped type '%s'", typeFieldValue)); - } - } - typeOut.add(typeFieldValue); - if (parser.nextToken() == JsonToken.END_OBJECT) { - for (Processor processor : processors.values()) { - processor.notApplicable(); - } - return; - } - if (!parser.hasToken(JsonToken.FIELD_NAME)) { - throw new IllegalStateException(); - } - for (Entry e : processors.entrySet()) { + typeChunk.add(typeFieldValue); + parser.nextToken(); + boolean foundProcessor = false; + for (Entry e : combinedProcessors.entrySet()) { final String processorType = e.getKey(); final Processor processor = e.getValue(); if (processorType.equals(typeFieldValue)) { - processor.processor().processCurrentValue(parser); + processor.combinedProcessor().processCurrentValue(parser); + foundProcessor = true; } else { processor.notApplicable(); } } + if (!foundProcessor) { + if (!options.allowUnknownTypes()) { + throw new ValueAwareException(String.format("Unknown type '%s' not allowed", typeFieldValue), + parser.currentLocation(), options); + } + for (WritableChunk sharedChunk : sharedChunks) { + addNullValue(sharedChunk); + } + FieldProcessor.skipFields(parser); + } } } + + private static void addNullValue(WritableChunk writableChunk) { + final int size = writableChunk.size(); + writableChunk.fillWithNullValue(size, 1); + writableChunk.setSize(size + 1); + } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java index cc18204edb8..c8092ca2364 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java @@ -4,7 +4,10 @@ package io.deephaven.json; import io.deephaven.chunk.DoubleChunk; +import io.deephaven.chunk.LongChunk; import io.deephaven.chunk.ObjectChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -13,43 +16,175 @@ import java.util.List; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; public class TypedObjectValueTest { + + private static final ObjectValue SYMBOL_INFO = ObjectValue.builder() + .putFields("name", StringValue.strict()) + .putFields("id", LongValue.strict()) + .build(); + private static final ObjectValue QUOTE_OBJECT = ObjectValue.builder() - .putFields("symbol", StringValue.strict()) - .putFields("bid", DoubleValue.standard()) - .putFields("ask", DoubleValue.standard()) + .putFields("symbol", SYMBOL_INFO) + .putFields("quote", ObjectValue.builder() + .putFields("bid", DoubleValue.standard()) + .putFields("ask", DoubleValue.standard()) + .build()) .build(); private static final ObjectValue TRADE_OBJECT = ObjectValue.builder() - .putFields("symbol", StringValue.strict()) + .putFields("symbol", SYMBOL_INFO) .putFields("price", DoubleValue.standard()) .putFields("size", DoubleValue.standard()) .build(); private static final TypedObjectValue QUOTE_OR_TRADE_OBJECT = - TypedObjectValue.strict("type", new LinkedHashMap<>() { + TypedObjectValue.builder("type", new LinkedHashMap<>() { { put("quote", QUOTE_OBJECT); put("trade", TRADE_OBJECT); } - }); + }) + .onNull("") + .onMissing("") + .build(); @Test void typeDiscriminationQuoteTrade() throws IOException { parse(QUOTE_OR_TRADE_OBJECT, List.of( - // "", - // "null", - // "{}", - // "{\"type\": null}", - // "{\"type\": \"other\"}", - "{\"type\": \"quote\", \"symbol\": \"foo\", \"bid\": 1.01, \"ask\": 1.05}", - "{\"type\": \"trade\", \"symbol\": \"bar\", \"price\": 42.42, \"size\": 123}"), - ObjectChunk.chunkWrap(new String[] {"quote", "trade"}), // type - ObjectChunk.chunkWrap(new String[] {"foo", "bar"}), // symbol - DoubleChunk.chunkWrap(new double[] {1.01, QueryConstants.NULL_DOUBLE}), // quote/bid - DoubleChunk.chunkWrap(new double[] {1.05, QueryConstants.NULL_DOUBLE}), // quote/ask - DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 42.42}), // trade/price - DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 123})); // trade/size + "{\"type\": \"quote\", \"symbol\": {\"name\": \"foo\", \"id\": 42}, \"quote\":{\"bid\": 1.01, \"ask\": 1.05}}", + "{\"type\": \"trade\", \"symbol\": {\"name\": \"bar\", \"id\": 43}, \"price\": 42.42, \"size\": 123}", + "{\"type\": \"other\"}", + "{\"type\": \"other_mimic_trade\", \"symbol\": {\"name\": \"bar\", \"id\": 43}, \"price\": 42.42, \"size\": 123}"), + ObjectChunk.chunkWrap(new String[] {"quote", "trade", "other", "other_mimic_trade"}), // type + ObjectChunk.chunkWrap(new String[] {"foo", "bar", null, null}), // symbol/symbol + LongChunk.chunkWrap(new long[] {42, 43, QueryConstants.NULL_LONG, QueryConstants.NULL_LONG}), // symbol/symbol_id + DoubleChunk.chunkWrap(new double[] {1.01, QueryConstants.NULL_DOUBLE, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE}), // quote: quote/bid + DoubleChunk.chunkWrap(new double[] {1.05, QueryConstants.NULL_DOUBLE, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE}), // quote: quote/ask + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 42.42, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE}), // trade: price + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 123, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE})); // trade: size + } + + @Test + void standardNull() throws IOException { + parse(QUOTE_OR_TRADE_OBJECT, "null", + ObjectChunk.chunkWrap(new String[] {""}), // type + ObjectChunk.chunkWrap(new String[] {null}), // symbol/symbol + LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG}), // symbol/symbol_id + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE}), // quote: quote/bid + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE}), // quote: quote/ask + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE}), // trade: price + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE})); // trade: size + } + + @Test + void standardMissing() throws IOException { + parse(QUOTE_OR_TRADE_OBJECT, "", + ObjectChunk.chunkWrap(new String[] {""}), // type + ObjectChunk.chunkWrap(new String[] {null}), // symbol/symbol + LongChunk.chunkWrap(new long[] {QueryConstants.NULL_LONG}), // symbol/symbol_id + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE}), // quote: quote/bid + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE}), // quote: quote/ask + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE}), // trade: price + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE})); // trade: size + } + + @Test + void unexpectedFirstField() { + try { + process(QUOTE_OR_TRADE_OBJECT, "{\"foo\": 42}"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Expected the first field to be 'type', is 'foo'"); + } + } + + @Test + void standardEmptyObject() { + try { + process(QUOTE_OR_TRADE_OBJECT, "{}"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Expected a non-empty object"); + } + } + + @Test + void standardInt() { + try { + process(QUOTE_OR_TRADE_OBJECT, "42"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Number int not expected"); + } + } + + @Test + void standardDecimal() { + try { + process(QUOTE_OR_TRADE_OBJECT, "42.42"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Decimal not expected"); + } + } + + @Test + void standardString() { + try { + process(QUOTE_OR_TRADE_OBJECT, "\"hello\""); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("String not expected"); + } + } + + @Test + void standardTrue() { + try { + process(QUOTE_OR_TRADE_OBJECT, "true"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Bool not expected"); + } + } + + @Test + void standardFalse() { + try { + process(QUOTE_OR_TRADE_OBJECT, "false"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Bool not expected"); + } + } + + @Test + void standardArray() { + try { + process(QUOTE_OR_TRADE_OBJECT, "[]"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Array not expected"); + } + } + + @Test + void columnNames() { + assertThat(JacksonProvider.of(QUOTE_OR_TRADE_OBJECT).named(Type.stringType()).names()).containsExactly( + "type", + "symbol_name", + "symbol_id", + "quote_quote_bid", + "quote_quote_ask", + "trade_price", + "trade_size"); } } diff --git a/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java b/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java index 4dc08f536c8..2ac02128ef7 100644 --- a/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java +++ b/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java @@ -4,12 +4,15 @@ package io.deephaven.json; import io.deephaven.annotations.BuildableStyle; +import io.deephaven.json.ImmutableTypedObjectValue.Builder; +import org.immutables.value.Value.Check; import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; /** @@ -130,6 +133,17 @@ public Set allowedTypes() { return JsonValueTypes.objectOrNull(); } + /** + * The output type value to use when {@link JsonValueTypes#NULL} is encountered. {@link #allowedTypes()} must + * contain {@link JsonValueTypes#NULL}. + */ + public abstract Optional onNull(); + + /** + * The output type value to use when a value is missing. {@link #allowMissing()} must be {@code true}. + */ + public abstract Optional onMissing(); + @Override final Set universe() { return JsonValueTypes.objectOrNull(); @@ -153,6 +167,10 @@ public interface Builder extends Value.Builder { Builder putObjects(String key, ObjectValue value); Builder allowUnknownTypes(boolean allowUnknownTypes); + + Builder onNull(String onNull); + + Builder onMissing(String onMissing); } private static ObjectValue without(ObjectValue options, Set excludedFields) { @@ -167,4 +185,18 @@ private static ObjectValue without(ObjectValue options, Set exclude } return builder.build(); } + + @Check + final void checkOnNull() { + if (!allowedTypes().contains(JsonValueTypes.NULL) && onNull().isPresent()) { + throw new IllegalArgumentException("onNull set, but NULL is not allowed"); + } + } + + @Check + final void checkOnMissing() { + if (!allowMissing() && onMissing().isPresent()) { + throw new IllegalArgumentException("onMissing set, but allowMissing is false"); + } + } } From f4b60c2b882ad0ebbc149531a9b11fdb2aaa7b9d Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 29 May 2024 06:55:30 -0700 Subject: [PATCH 28/53] Array maths --- .../io/deephaven/json/jackson/ByteMixin.java | 4 +- .../io/deephaven/json/jackson/CharMixin.java | 4 +- .../deephaven/json/jackson/DoubleMixin.java | 4 +- .../io/deephaven/json/jackson/FloatMixin.java | 4 +- .../io/deephaven/json/jackson/IntMixin.java | 4 +- .../json/jackson/LongRepeaterImpl.java | 4 +- .../java/io/deephaven/json/jackson/Maths.java | 21 ++++++++ .../json/jackson/RepeaterGenericImpl.java | 4 +- .../io/deephaven/json/jackson/ShortMixin.java | 4 +- .../jackson/ValueInnerRepeaterProcessor.java | 2 +- .../io/deephaven/json/jackson/MathsTest.java | 52 +++++++++++++++++++ 11 files changed, 90 insertions(+), 17 deletions(-) create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Maths.java create mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/jackson/MathsTest.java diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java index aa16a7921f6..9acfa10556a 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java @@ -78,7 +78,7 @@ public ByteRepeaterImpl(boolean allowMissing, boolean allowNull) { @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, ByteMixin.this.parseValue(parser)); chunk.setSize(newSize); } @@ -86,7 +86,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, ByteMixin.this.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java index e8d5d9baab8..83abbfa6607 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java @@ -74,7 +74,7 @@ public CharRepeaterImpl(boolean allowMissing, boolean allowNull) { @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, CharMixin.this.parseValue(parser)); chunk.setSize(newSize); } @@ -82,7 +82,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, CharMixin.this.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java index ff443177296..1def7cbdc27 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java @@ -78,7 +78,7 @@ public DoubleRepeaterImpl(boolean allowMissing, boolean allowNull) { @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, DoubleMixin.this.parseValue(parser)); chunk.setSize(newSize); } @@ -86,7 +86,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, DoubleMixin.this.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java index d4fb7e3b0ff..f83c3905732 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java @@ -78,7 +78,7 @@ public FloatRepeaterImpl(boolean allowMissing, boolean allowNull) { @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, FloatMixin.this.parseValue(parser)); chunk.setSize(newSize); } @@ -86,7 +86,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, FloatMixin.this.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java index 1483281a9b3..20627dd1887 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -79,7 +79,7 @@ public IntRepeaterImpl(boolean allowMissing, boolean allowNull) { @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, IntMixin.this.parseValue(parser)); chunk.setSize(newSize); } @@ -87,7 +87,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, IntMixin.this.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java index cbae181a286..195a1cd201f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java @@ -27,7 +27,7 @@ public LongRepeaterImpl(ToLong toLong, boolean allowMissing, boolean allowNull, @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, toLong.parseValue(parser)); chunk.setSize(newSize); } @@ -35,7 +35,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, toLong.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Maths.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Maths.java new file mode 100644 index 00000000000..fd62bd59beb --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Maths.java @@ -0,0 +1,21 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import io.deephaven.base.ArrayUtil; + +final class Maths { + + static final int MAX_POWER_OF_2 = 1 << 30; + + static int nextPowerOf2(int newSize) { + return Math.max(Integer.highestOneBit(newSize - 1) << 1, 1); + } + + static int nextArrayCapacity(int newSize) { + return newSize <= MAX_POWER_OF_2 + ? nextPowerOf2(newSize) + : ArrayUtil.MAX_ARRAY_SIZE; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java index d4dbc3cba2f..de3b219086f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java @@ -27,7 +27,7 @@ public RepeaterGenericImpl(ToObject toObject, boolean allowMissing, boolean a @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, toObject.parseValue(parser)); chunk.setSize(newSize); } @@ -35,7 +35,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, toObject.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java index 0219c95db5b..79d78e6cd88 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java @@ -78,7 +78,7 @@ public ShortRepeaterImpl(boolean allowMissing, boolean allowNull) { @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, ShortMixin.this.parseValue(parser)); chunk.setSize(newSize); } @@ -86,7 +86,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(newSize); + final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); chunk.set(index, ShortMixin.this.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java index a4c0471cffc..483aa6b6194 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java @@ -132,7 +132,7 @@ private void processImpl() { // noinspection unchecked final SizedObjectChunk to = (SizedObjectChunk) sizedObjectChunks.get(i); // we _could_ consider doing this in a chunked fashion. doing in simple fashion to initially test - to.ensureCapacityPreserve(newSize); + to.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); to.get().set(ix, from.get(0)); to.get().setSize(newSize); from.set(0, null); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/MathsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/MathsTest.java new file mode 100644 index 00000000000..a4b0c2adf8b --- /dev/null +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/MathsTest.java @@ -0,0 +1,52 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import io.deephaven.base.ArrayUtil; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MathsTest { + + @Test + void nextPowerOf2() { + pow2(0, 1); + pow2(1, 1); + pow2(2, 2); + for (int i = 2; i < 31; ++i) { + final int pow2 = 1 << i; + pow2(pow2, pow2); + pow2(pow2 - 1, pow2); + if (i < 30) { + pow2(pow2 + 1, pow2 * 2); + } + } + } + + @Test + void nextArrayCapacity() { + arrayCapacity(0, 1); + arrayCapacity(1, 1); + arrayCapacity(2, 2); + for (int i = 2; i < 31; ++i) { + final int pow2 = 1 << i; + arrayCapacity(pow2, pow2); + arrayCapacity(pow2 - 1, pow2); + if (i < 30) { + arrayCapacity(pow2 + 1, pow2 * 2); + } else { + arrayCapacity(pow2 + 1, ArrayUtil.MAX_ARRAY_SIZE); + } + } + } + + public static void pow2(int newSize, int expectedSize) { + assertThat(Maths.nextPowerOf2(newSize)).isEqualTo(expectedSize); + } + + public static void arrayCapacity(int newSize, int expectedSize) { + assertThat(Maths.nextArrayCapacity(newSize)).isEqualTo(expectedSize); + } +} From 52e59323f758cbba6df647c04a3ba2982daf3666 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 29 May 2024 07:12:17 -0700 Subject: [PATCH 29/53] deeply nested array test --- .../java/io/deephaven/json/IntArrayTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java index d6c11257f15..1c564ee60e3 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java @@ -26,4 +26,42 @@ void standardMissing() throws IOException { void standardNull() throws IOException { parse(IntValue.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] {null})); } + + @Test + void doubleNestedArray() throws IOException { + parse(IntValue.standard().array().array(), "[null, [], [42, 43]]", + ObjectChunk.chunkWrap(new Object[] {new int[][] { + null, + new int[] {}, + new int[] {42, 43}}})); + } + + @Test + void tripleNestedArray() throws IOException { + parse(IntValue.standard().array().array().array(), "[null, [], [null, [], [42, 43]]]", + ObjectChunk.chunkWrap(new Object[] {new int[][][] { + null, + new int[][] {}, + new int[][] { + null, + new int[] {}, + new int[] {42, 43}} + }})); + } + + @Test + void quadNestedArray() throws IOException { + parse(IntValue.standard().array().array().array().array(), "[null, [], [null, [], [null, [], [42, 43]]]]", + ObjectChunk.chunkWrap(new Object[] {new int[][][][] { + null, + new int[][][] {}, + new int[][][] { + null, + new int[][] {}, + new int[][] { + null, + new int[] {}, + new int[] {42, 43}} + }}})); + } } From dbb66dee2f4982fd71d99e1b21201a2894da1e36 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 29 May 2024 07:57:34 -0700 Subject: [PATCH 30/53] array stuff --- .../io/deephaven/json/jackson/ArrayMixin.java | 80 ++++++++++++++---- .../json/jackson/ArrayValueProcessor.java | 57 ------------- .../java/io/deephaven/json/IntArrayTest.java | 83 +++++++++++++++++++ 3 files changed, 147 insertions(+), 73 deletions(-) delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayValueProcessor.java diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java index 3c10ad68c12..2fc48285cb9 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java @@ -4,31 +4,33 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; import io.deephaven.json.ArrayValue; import io.deephaven.qst.type.NativeArrayType; import io.deephaven.qst.type.Type; +import java.io.IOException; import java.util.List; import java.util.stream.Stream; final class ArrayMixin extends Mixin { + private final Mixin element; + public ArrayMixin(ArrayValue options, JsonFactory factory) { super(factory, options); - } - - Mixin element() { - return mixin(options.element()); + element = mixin(options.element()); } @Override public int numColumns() { - return element().numColumns(); + return element.numColumns(); } @Override public Stream> paths() { - return element().paths(); + return element.paths(); } @Override @@ -38,19 +40,15 @@ public Stream> paths() { @Override public ValueProcessor processor(String context) { - return innerProcessor(); - } - - Stream> elementOutputTypes() { - return element().outputTypesImpl(); + return new ArrayValueProcessor(); } - RepeaterProcessor elementRepeater() { - return element().repeaterProcessor(allowMissing(), allowNull()); + private Stream> elementOutputTypes() { + return element.outputTypesImpl(); } - private ArrayValueProcessor innerProcessor() { - return new ArrayValueProcessor(elementRepeater()); + private RepeaterProcessor elementRepeater() { + return element.repeaterProcessor(allowMissing(), allowNull()); } @Override @@ -60,6 +58,56 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { // double[] (processor()) // double[][] (repeater()) // return new ArrayOfArrayRepeaterProcessor(allowMissing, allowNull); - return new ValueInnerRepeaterProcessor(allowMissing, allowNull, innerProcessor()); + return new ValueInnerRepeaterProcessor(allowMissing, allowNull, new ArrayValueProcessor()); + } + + private class ArrayValueProcessor implements ValueProcessor { + + private final RepeaterProcessor elementProcessor; + + ArrayValueProcessor() { + this.elementProcessor = elementRepeater(); + } + + @Override + public void setContext(List> out) { + elementProcessor.setContext(out); + } + + @Override + public void clearContext() { + elementProcessor.clearContext(); + } + + @Override + public int numColumns() { + return elementProcessor.numColumns(); + } + + @Override + public Stream> columnTypes() { + return elementProcessor.columnTypes(); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case START_ARRAY: + RepeaterProcessor.processArray(parser, elementProcessor); + return; + case VALUE_NULL: + checkNullAllowed(parser); + elementProcessor.processNullRepeater(parser); + return; + default: + throw unexpectedToken(parser); + } + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + checkMissingAllowed(parser); + elementProcessor.processMissingRepeater(parser); + } } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayValueProcessor.java deleted file mode 100644 index 3a1f7ae4b84..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayValueProcessor.java +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import io.deephaven.chunk.WritableChunk; -import io.deephaven.qst.type.Type; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -final class ArrayValueProcessor implements ValueProcessor { - - private final RepeaterProcessor elementProcessor; - - ArrayValueProcessor(RepeaterProcessor elementProcessor) { - this.elementProcessor = Objects.requireNonNull(elementProcessor); - } - - @Override - public void setContext(List> out) { - elementProcessor.setContext(out); - } - - @Override - public void clearContext() { - elementProcessor.clearContext(); - } - - @Override - public int numColumns() { - return elementProcessor.numColumns(); - } - - @Override - public Stream> columnTypes() { - return elementProcessor.columnTypes(); - } - - @Override - public void processCurrentValue(JsonParser parser) throws IOException { - if (parser.hasToken(JsonToken.VALUE_NULL)) { - elementProcessor.processNullRepeater(parser); - return; - } - RepeaterProcessor.processArray(parser, elementProcessor); - } - - @Override - public void processMissing(JsonParser parser) throws IOException { - elementProcessor.processMissingRepeater(parser); - } -} diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java index 1c564ee60e3..3123539cefa 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java @@ -9,6 +9,9 @@ import java.io.IOException; import static io.deephaven.json.TestHelper.parse; +import static io.deephaven.json.TestHelper.process; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; public class IntArrayTest { @@ -27,6 +30,86 @@ void standardNull() throws IOException { parse(IntValue.standard().array(), "null", ObjectChunk.chunkWrap(new Object[] {null})); } + @Test + void strictMissing() { + try { + process(ArrayValue.strict(IntValue.standard()), ""); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Missing not allowed"); + } + } + + @Test + void strictNull() { + try { + process(ArrayValue.strict(IntValue.standard()), "null"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Null not allowed"); + } + } + + @Test + void standardInt() { + try { + process(IntValue.standard().array(), "42"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Number int not expected"); + } + } + + @Test + void standardDecimal() { + try { + process(IntValue.standard().array(), "42.42"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Decimal not expected"); + } + } + + @Test + void standardString() { + try { + process(IntValue.standard().array(), "\"hello\""); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("String not expected"); + } + } + + @Test + void standardTrue() { + try { + process(IntValue.standard().array(), "true"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Bool not expected"); + } + } + + @Test + void standardFalse() { + try { + process(IntValue.standard().array(), "false"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Bool not expected"); + } + } + + @Test + void standardObject() { + try { + process(IntValue.standard().array(), "{}"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Object not expected"); + } + } + @Test void doubleNestedArray() throws IOException { parse(IntValue.standard().array().array(), "[null, [], [42, 43]]", From 0c0760eab3e492f64ce766cd9366ce0f54ba7a5e Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 29 May 2024 08:13:15 -0700 Subject: [PATCH 31/53] kv mixin --- .../deephaven/json/jackson/ObjectKvMixin.java | 100 +++++++++++++----- .../io/deephaven/json/jackson/TupleMixin.java | 37 ++++--- .../json/jackson/ValueProcessorKvImpl.java | 64 ----------- 3 files changed, 96 insertions(+), 105 deletions(-) delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java index 7b839abedcf..5d89d265a62 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java @@ -4,69 +4,113 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; import io.deephaven.json.ObjectKvValue; import io.deephaven.qst.type.NativeArrayType; import io.deephaven.qst.type.Type; +import java.io.IOException; import java.util.List; import java.util.stream.Stream; final class ObjectKvMixin extends Mixin { + private final Mixin key; + private final Mixin value; public ObjectKvMixin(ObjectKvValue options, JsonFactory factory) { super(factory, options); - } - - public Mixin keyMixin() { - return mixin(options.key()); - } - - public Mixin valueMixin() { - return mixin(options.value()); + key = mixin(options.key()); + value = mixin(options.value()); } @Override public Stream> outputTypesImpl() { - return keyValueOutputTypes().map(Type::arrayType); + return Stream.concat(key.outputTypesImpl(), value.outputTypesImpl()).map(Type::arrayType); } @Override public int numColumns() { - return keyMixin().numColumns() + valueMixin().numColumns(); + return key.numColumns() + value.numColumns(); } @Override public Stream> paths() { final Stream> keyPath = - keyMixin().numColumns() == 1 && keyMixin().paths().findFirst().orElseThrow().isEmpty() + key.numColumns() == 1 && key.paths().findFirst().orElseThrow().isEmpty() ? Stream.of(List.of("Key")) - : keyMixin().paths(); + : key.paths(); final Stream> valuePath = - valueMixin().numColumns() == 1 && valueMixin().paths().findFirst().orElseThrow().isEmpty() + value.numColumns() == 1 && value.paths().findFirst().orElseThrow().isEmpty() ? Stream.of(List.of("Value")) - : valueMixin().paths(); + : value.paths(); return Stream.concat(keyPath, valuePath); } @Override - public ValueProcessorKvImpl processor(String context) { - return innerProcessor(); + public ValueProcessor processor(String context) { + return new ValueProcessorKvImpl(); } - Stream> keyValueOutputTypes() { - return Stream.concat(keyMixin().outputTypesImpl(), valueMixin().outputTypesImpl()); + @Override + RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + return new ValueInnerRepeaterProcessor(allowMissing, allowNull, new ValueProcessorKvImpl()); } - private ValueProcessorKvImpl innerProcessor() { - final Mixin key = keyMixin(); - final Mixin value = valueMixin(); - final RepeaterProcessor kp = key.repeaterProcessor(allowMissing(), allowNull()); - final RepeaterProcessor vp = value.repeaterProcessor(allowMissing(), allowNull()); - return new ValueProcessorKvImpl(kp, vp); - } + private class ValueProcessorKvImpl implements ValueProcessor { - @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new ValueInnerRepeaterProcessor(allowMissing, allowNull, innerProcessor()); + private final RepeaterProcessor keyProcessor; + private final RepeaterProcessor valueProcessor; + + ValueProcessorKvImpl() { + this.keyProcessor = key.repeaterProcessor(allowMissing(), allowNull()); + this.valueProcessor = value.repeaterProcessor(allowMissing(), allowNull()); + } + + @Override + public void setContext(List> out) { + final int keySize = keyProcessor.numColumns(); + keyProcessor.setContext(out.subList(0, keySize)); + valueProcessor.setContext(out.subList(keySize, keySize + valueProcessor.numColumns())); + } + + @Override + public void clearContext() { + keyProcessor.clearContext(); + valueProcessor.clearContext(); + } + + @Override + public int numColumns() { + return keyProcessor.numColumns() + valueProcessor.numColumns(); + } + + @Override + public Stream> columnTypes() { + return Stream.concat(keyProcessor.columnTypes(), valueProcessor.columnTypes()); + } + + @Override + public void processCurrentValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case START_OBJECT: + RepeaterProcessor.processObjectKeyValues(parser, keyProcessor, valueProcessor); + return; + case VALUE_NULL: + checkNullAllowed(parser); + keyProcessor.processNullRepeater(parser); + valueProcessor.processNullRepeater(parser); + return; + default: + throw unexpectedToken(parser); + } + } + + @Override + public void processMissing(JsonParser parser) throws IOException { + checkMissingAllowed(parser); + keyProcessor.processMissingRepeater(parser); + valueProcessor.processMissingRepeater(parser); + } } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java index 80e72e9b3cf..7378c50a340 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java @@ -14,7 +14,10 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.function.Function; @@ -24,38 +27,47 @@ final class TupleMixin extends Mixin { + private final Map> mixins; + private final int numColumns; + public TupleMixin(TupleValue options, JsonFactory factory) { super(factory, options); + final LinkedHashMap> map = new LinkedHashMap<>(options.namedValues().size()); + for (Entry e : options.namedValues().entrySet()) { + map.put(e.getKey(), mixin(e.getValue())); + } + mixins = Collections.unmodifiableMap(map); + numColumns = mixins.values().stream().mapToInt(Mixin::numColumns).sum(); } @Override public int numColumns() { - return options.namedValues().values().stream().map(this::mixin).mapToInt(Mixin::numColumns).sum(); + return numColumns; } @Override public Stream> paths() { - if (options.namedValues().size() == 1) { - return mixin(options.namedValues().get(0)).paths(); + if (mixins.size() == 1) { + return mixins.values().iterator().next().paths(); } final List>> prefixed = new ArrayList<>(); - for (Entry e : options.namedValues().entrySet()) { - prefixed.add(mixin(e.getValue()).paths().map(x -> prefixWith(e.getKey(), x))); + for (Entry> e : mixins.entrySet()) { + prefixed.add(e.getValue().paths().map(x -> prefixWith(e.getKey(), x))); } return prefixed.stream().flatMap(Function.identity()); } @Override public Stream> outputTypesImpl() { - return options.namedValues().values().stream().map(this::mixin).flatMap(Mixin::outputTypesImpl); + return mixins.values().stream().flatMap(Mixin::outputTypesImpl); } @Override public ValueProcessor processor(String context) { final List processors = new ArrayList<>(options.namedValues().size()); int ix = 0; - for (Entry e : options.namedValues().entrySet()) { - final Mixin mixin = mixin(e.getValue()); + for (Entry> e : mixins.entrySet()) { + final Mixin mixin = e.getValue(); final int numTypes = mixin.numColumns(); final ValueProcessor processor = mixin.processor(context + "[" + e.getKey() + "]"); processors.add(processor); @@ -69,13 +81,12 @@ public ValueProcessor processor(String context) { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - final List processors = new ArrayList<>(options.namedValues().size()); + final List processors = new ArrayList<>(mixins.size()); int ix = 0; - for (Entry e : options.namedValues().entrySet()) { - final Mixin mixin = mixin(e.getValue()); + for (Entry> e : mixins.entrySet()) { + final Mixin mixin = e.getValue(); final int numTypes = mixin.numColumns(); - final RepeaterProcessor processor = - mixin.repeaterProcessor(allowMissing, allowNull); + final RepeaterProcessor processor = mixin.repeaterProcessor(allowMissing, allowNull); processors.add(processor); ix += numTypes; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java deleted file mode 100644 index 317f45112f1..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueProcessorKvImpl.java +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import io.deephaven.chunk.WritableChunk; -import io.deephaven.qst.type.Type; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -final class ValueProcessorKvImpl implements ValueProcessor { - - private final RepeaterProcessor keyProcessor; - private final RepeaterProcessor valueProcessor; - - public ValueProcessorKvImpl(RepeaterProcessor keyProcessor, RepeaterProcessor valueProcessor) { - this.keyProcessor = Objects.requireNonNull(keyProcessor); - this.valueProcessor = Objects.requireNonNull(valueProcessor); - } - - @Override - public void setContext(List> out) { - final int keySize = keyProcessor.numColumns(); - keyProcessor.setContext(out.subList(0, keySize)); - valueProcessor.setContext(out.subList(keySize, keySize + valueProcessor.numColumns())); - } - - @Override - public void clearContext() { - keyProcessor.clearContext(); - valueProcessor.clearContext(); - } - - @Override - public int numColumns() { - return keyProcessor.numColumns() + valueProcessor.numColumns(); - } - - @Override - public Stream> columnTypes() { - return Stream.concat(keyProcessor.columnTypes(), valueProcessor.columnTypes()); - } - - @Override - public void processCurrentValue(JsonParser parser) throws IOException { - if (parser.hasToken(JsonToken.VALUE_NULL)) { - keyProcessor.processNullRepeater(parser); - valueProcessor.processNullRepeater(parser); - return; - } - RepeaterProcessor.processObjectKeyValues(parser, keyProcessor, valueProcessor); - } - - @Override - public void processMissing(JsonParser parser) throws IOException { - keyProcessor.processMissingRepeater(parser); - valueProcessor.processMissingRepeater(parser); - } -} From c9c20e0de6877ea7e7515b21f9e7da771e32c92f Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 29 May 2024 08:16:57 -0700 Subject: [PATCH 32/53] mixin consolidation --- .../src/main/java/io/deephaven/json/jackson/ArrayMixin.java | 2 +- .../src/main/java/io/deephaven/json/jackson/Mixin.java | 4 ---- .../main/java/io/deephaven/json/jackson/ObjectKvMixin.java | 4 ++-- .../src/main/java/io/deephaven/json/jackson/ObjectMixin.java | 2 +- .../src/main/java/io/deephaven/json/jackson/TupleMixin.java | 2 +- .../main/java/io/deephaven/json/jackson/TypedObjectMixin.java | 2 +- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java index 2fc48285cb9..24d7ea323e8 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java @@ -20,7 +20,7 @@ final class ArrayMixin extends Mixin { public ArrayMixin(ArrayValue options, JsonFactory factory) { super(factory, options); - element = mixin(options.element()); + element = Mixin.of(options.element(), factory); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index aa9f47eb75e..37725c5558f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -161,10 +161,6 @@ public final ObjectProcessor charBufferProcessor() { return new CharBufferIn(); } - final Mixin mixin(Value options) { - return of(options, factory); - } - abstract ValueProcessor processor(String context); abstract RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java index 5d89d265a62..c192cfd993c 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java @@ -20,8 +20,8 @@ final class ObjectKvMixin extends Mixin { public ObjectKvMixin(ObjectKvValue options, JsonFactory factory) { super(factory, options); - key = mixin(options.key()); - value = mixin(options.value()); + key = Mixin.of(options.key(), factory); + value = Mixin.of(options.value(), factory); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java index 030baa4e78a..43fb7041811 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -36,7 +36,7 @@ public ObjectMixin(ObjectValue options, JsonFactory factory) { super(factory, options); final LinkedHashMap> map = new LinkedHashMap<>(options.fields().size()); for (ObjectField field : options.fields()) { - map.put(field, mixin(field.options())); + map.put(field, Mixin.of(field.options(), factory)); } mixins = Collections.unmodifiableMap(map); numOutputs = mixins.values().stream().mapToInt(Mixin::numColumns).sum(); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java index 7378c50a340..192a73f3c57 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java @@ -34,7 +34,7 @@ public TupleMixin(TupleValue options, JsonFactory factory) { super(factory, options); final LinkedHashMap> map = new LinkedHashMap<>(options.namedValues().size()); for (Entry e : options.namedValues().entrySet()) { - map.put(e.getKey(), mixin(e.getValue())); + map.put(e.getKey(), Mixin.of(e.getValue(), factory)); } mixins = Collections.unmodifiableMap(map); numColumns = mixins.values().stream().mapToInt(Mixin::numColumns).sum(); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java index 226e224daa4..33a895a6e86 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java @@ -39,7 +39,7 @@ public TypedObjectMixin(TypedObjectValue options, JsonFactory factory) { { final LinkedHashMap> map = new LinkedHashMap<>(options.sharedFields().size()); for (ObjectField sharedField : options.sharedFields()) { - map.put(sharedField, mixin(sharedField.options())); + map.put(sharedField, Mixin.of(sharedField.options(), factory)); } sharedFields = Collections.unmodifiableMap(map); } From f979b56809c1d7cba9b99e09bd7429d99b042e3c Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 29 May 2024 08:42:00 -0700 Subject: [PATCH 33/53] discriminated object --- .../json/jackson/FieldProcessor.java | 5 ----- .../java/io/deephaven/json/jackson/Mixin.java | 2 ++ .../deephaven/json/jackson/ObjectMixin.java | 20 ++++++++++--------- .../json/jackson/TypedObjectMixin.java | 2 ++ 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FieldProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FieldProcessor.java index 22f47e8d3a4..6a0b75e4a77 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FieldProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FieldProcessor.java @@ -11,11 +11,6 @@ import static io.deephaven.json.jackson.Parsing.assertCurrentToken; interface FieldProcessor { - static void processObject(JsonParser parser, FieldProcessor fieldProcess) throws IOException { - Parsing.assertCurrentToken(parser, JsonToken.START_OBJECT); - parser.nextToken(); - processFields(parser, fieldProcess); - } static void processFields(JsonParser parser, FieldProcessor fieldProcess) throws IOException { while (parser.hasToken(JsonToken.FIELD_NAME)) { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index 37725c5558f..1aca2a39abb 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -475,6 +475,8 @@ final IOException unexpectedToken(JsonParser parser) throws ValueAwareException msg = "Decimal not expected"; break; case FIELD_NAME: + msg = "Field name not expected"; + break; case VALUE_STRING: msg = "String not expected"; break; diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java index 43fb7041811..de46beaa69d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -62,7 +62,7 @@ public ValueProcessor processor(String context) { return processor(context, false); } - public ValueProcessor processor(String context, boolean isDiscriminatedObject) { + public ValueProcessor processor(String context, boolean isDiscriminated) { final Map processors = new LinkedHashMap<>(options.fields().size()); int ix = 0; for (ObjectField field : options.fields()) { @@ -75,7 +75,7 @@ public ValueProcessor processor(String context, boolean isDiscriminatedObject) { if (ix != numColumns()) { throw new IllegalStateException(); } - return processorImpl(processors, isDiscriminatedObject); + return processorImpl(processors, isDiscriminated); } @Override @@ -106,15 +106,15 @@ ObjectValueFieldProcessor processorImpl(Map fields, final class ObjectValueFieldProcessor extends ContextAwareDelegateBase implements ValueProcessor, FieldProcessor { private final Map fields; private final Map map; - private final boolean isDiscriminatedObject; + private final boolean isDiscriminated; - ObjectValueFieldProcessor(Map fields, boolean isDiscriminatedObject) { + ObjectValueFieldProcessor(Map fields, boolean isDiscriminated) { super(fields.values()); this.fields = fields; this.map = allCaseSensitive() ? new HashMap<>() : new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - this.isDiscriminatedObject = isDiscriminatedObject; + this.isDiscriminated = isDiscriminated; for (Entry e : fields.entrySet()) { final ObjectField field = e.getKey(); map.put(field.name(), field); @@ -146,9 +146,11 @@ private ValueProcessor processor(ObjectField options) { @Override public void processCurrentValue(JsonParser parser) throws IOException { + // In the normal case, we expect to be at START_OBJECT or VALUE_NULL. + // In the discriminated case, we expect to already be inside an object (FIELD_NAME or END_OBJECT). switch (parser.currentToken()) { case START_OBJECT: - if (isDiscriminatedObject) { + if (isDiscriminated) { throw unexpectedToken(parser); } if (parser.nextToken() == JsonToken.END_OBJECT) { @@ -161,19 +163,19 @@ public void processCurrentValue(JsonParser parser) throws IOException { processObjectFields(parser); return; case VALUE_NULL: - if (isDiscriminatedObject) { + if (isDiscriminated) { throw unexpectedToken(parser); } processNullObject(parser); return; case FIELD_NAME: - if (!isDiscriminatedObject) { + if (!isDiscriminated) { throw unexpectedToken(parser); } processObjectFields(parser); return; case END_OBJECT: - if (!isDiscriminatedObject) { + if (!isDiscriminated) { throw unexpectedToken(parser); } processEmptyObject(parser); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java index 33a895a6e86..d36959c3833 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java @@ -277,6 +277,8 @@ private void processObjectFields(JsonParser parser) throws IOException { for (WritableChunk sharedChunk : sharedChunks) { addNullValue(sharedChunk); } + // We need to skip all the fields. parser.skipChildren() is not applicable here because we are already + // inside the object (as opposed to at START_OBJECT). FieldProcessor.skipFields(parser); } } From 2338fa05ccb0148e62df1c6355fbb524dae48cff Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 29 May 2024 09:39:33 -0700 Subject: [PATCH 34/53] outputSize --- .../processor/NamedObjectProcessor.java | 14 ++---- .../deephaven/processor/ObjectProcessor.java | 6 +-- .../processor/ObjectProcessorRowLimited.java | 4 +- .../processor/ObjectProcessorStrict.java | 14 +++--- .../processor/ObjectProcessorStrictTest.java | 10 ++--- .../io/deephaven/json/jackson/AnyMixin.java | 2 +- .../io/deephaven/json/jackson/ArrayMixin.java | 4 +- .../json/jackson/BigDecimalMixin.java | 2 +- .../json/jackson/BigIntegerMixin.java | 2 +- .../io/deephaven/json/jackson/BoolMixin.java | 2 +- .../io/deephaven/json/jackson/ByteMixin.java | 2 +- .../io/deephaven/json/jackson/CharMixin.java | 2 +- .../deephaven/json/jackson/DoubleMixin.java | 2 +- .../io/deephaven/json/jackson/FloatMixin.java | 2 +- .../deephaven/json/jackson/InstantMixin.java | 2 +- .../json/jackson/InstantNumberMixin.java | 2 +- .../io/deephaven/json/jackson/IntMixin.java | 2 +- .../json/jackson/LocalDateMixin.java | 2 +- .../io/deephaven/json/jackson/LongMixin.java | 2 +- .../java/io/deephaven/json/jackson/Mixin.java | 7 --- .../deephaven/json/jackson/ObjectKvMixin.java | 8 ++-- .../deephaven/json/jackson/ObjectMixin.java | 12 ++--- .../jackson/ObjectProcessorJsonValue.java | 2 +- .../io/deephaven/json/jackson/ShortMixin.java | 2 +- .../io/deephaven/json/jackson/SkipMixin.java | 2 +- .../deephaven/json/jackson/StringMixin.java | 2 +- .../io/deephaven/json/jackson/TupleMixin.java | 25 ++++++----- .../json/jackson/TypedObjectMixin.java | 39 +++++++++++----- .../deephaven/json/TypedObjectValueTest.java | 36 ++++++++++++++- .../io/deephaven/json/TypedObjectValue.java | 44 ++++++++++++------- .../json/TypedObjectValueBuilderTest.java | 6 ++- 31 files changed, 156 insertions(+), 107 deletions(-) diff --git a/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java b/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java index 4da3d25b526..96703818349 100644 --- a/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java +++ b/engine/processor/src/main/java/io/deephaven/processor/NamedObjectProcessor.java @@ -28,14 +28,6 @@ public static NamedObjectProcessor of(ObjectProcessor processo return NamedObjectProcessor.builder().processor(processor).addAllNames(names).build(); } - public static NamedObjectProcessor prefix(ObjectProcessor processor, String prefix) { - final int size = processor.numOutputs(); - if (size == 1) { - return of(processor, prefix); - } - return of(processor, IntStream.range(0, size).mapToObj(ix -> prefix + "_" + ix).collect(Collectors.toList())); - } - /** * The name for each output of {@link #processor()}. */ @@ -85,10 +77,10 @@ default NamedObjectProcessor named(Type inputType) { @Check final void checkSizes() { - if (names().size() != processor().numOutputs()) { + if (names().size() != processor().outputSize()) { throw new IllegalArgumentException( - String.format("Unmatched sizes; names().size()=%d, processor().size()=%d", - names().size(), processor().numOutputs())); + String.format("Unmatched sizes; names().size()=%d, processor().outputSize()=%d", + names().size(), processor().outputSize())); } } } diff --git a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java index 066a844e9ac..70e42e14f05 100644 --- a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java +++ b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessor.java @@ -146,7 +146,7 @@ static ChunkType chunkType(Type type) { * * @return the number of outputs */ - default int numOutputs() { + default int outputSize() { return outputTypes().size(); } @@ -200,11 +200,11 @@ interface Provider { /** * The number of output types for the processors. Equivalent to the processors' - * {@link ObjectProcessor#numOutputs()}. + * {@link ObjectProcessor#outputSize()}. * * @return the number of output types */ - int size(); + int outputSize(); /** * Creates an object processor that can process the {@code inputType}. This will successfully create a processor diff --git a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorRowLimited.java b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorRowLimited.java index 4fe68a7cf75..53d2192e517 100644 --- a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorRowLimited.java +++ b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorRowLimited.java @@ -49,8 +49,8 @@ int rowLimit() { } @Override - public int numOutputs() { - return delegate.numOutputs(); + public int outputSize() { + return delegate.outputSize(); } @Override diff --git a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorStrict.java b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorStrict.java index 691f8c6c36c..30c84f38aea 100644 --- a/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorStrict.java +++ b/engine/processor/src/main/java/io/deephaven/processor/ObjectProcessorStrict.java @@ -27,16 +27,16 @@ static ObjectProcessor create(ObjectProcessor delegate) { ObjectProcessorStrict(ObjectProcessor delegate) { this.delegate = Objects.requireNonNull(delegate); this.outputTypes = List.copyOf(delegate.outputTypes()); - if (delegate.numOutputs() != outputTypes.size()) { + if (delegate.outputSize() != outputTypes.size()) { throw new IllegalArgumentException( - String.format("Inconsistent size. delegate.numOutputs()=%d, delegate.outputTypes().size()=%d", - delegate.numOutputs(), outputTypes.size())); + String.format("Inconsistent size. delegate.outputSize()=%d, delegate.outputTypes().size()=%d", + delegate.outputSize(), outputTypes.size())); } } @Override - public int numOutputs() { - return delegate.numOutputs(); + public int outputSize() { + return delegate.outputSize(); } @Override @@ -50,10 +50,10 @@ public List> outputTypes() { @Override public void processAll(ObjectChunk in, List> out) { - final int numColumns = delegate.numOutputs(); + final int numColumns = delegate.outputSize(); if (numColumns != out.size()) { throw new IllegalArgumentException(String.format( - "Improper number of out chunks. Expected delegate.outputTypes().size() == out.size(). delegate.outputTypes().size()=%d, out.size()=%d", + "Improper number of out chunks. Expected delegate.outputSize() == out.size(). delegate.outputSize()=%d, out.size()=%d", numColumns, out.size())); } final List> delegateOutputTypes = delegate.outputTypes(); diff --git a/engine/processor/src/test/java/io/deephaven/processor/ObjectProcessorStrictTest.java b/engine/processor/src/test/java/io/deephaven/processor/ObjectProcessorStrictTest.java index 14bea583f09..562aeb368dc 100644 --- a/engine/processor/src/test/java/io/deephaven/processor/ObjectProcessorStrictTest.java +++ b/engine/processor/src/test/java/io/deephaven/processor/ObjectProcessorStrictTest.java @@ -103,7 +103,7 @@ public void testIncorrectChunkType() { } @Test - public void testNotEnoughOutputNumOutputs() { + public void testNotEnoughOutputOutputSize() { ObjectProcessor delegate = ObjectProcessor.noop(List.of(Type.intType()), false); ObjectProcessor strict = ObjectProcessor.strict(delegate); try ( @@ -173,7 +173,7 @@ public void testBadDelegateOutputTypes() { private final List> outputTypes = new ArrayList<>(List.of(Type.intType())); @Override - public int numOutputs() { + public int outputSize() { return 1; } @@ -227,11 +227,11 @@ public void processAll(ObjectChunk in, List> out) { } @Test - public void testBadDelegateNumOutputs() { + public void testBadDelegateOutputSize() { try { ObjectProcessor.strict(new ObjectProcessor<>() { @Override - public int numOutputs() { + public int outputSize() { return 2; } @@ -248,7 +248,7 @@ public void processAll(ObjectChunk in, List> out) { failBecauseExceptionWasNotThrown(IllegalAccessException.class); } catch (IllegalArgumentException e) { assertThat(e).hasMessageContaining( - "Inconsistent size. delegate.numOutputs()=2, delegate.outputTypes().size()=1"); + "Inconsistent size. delegate.outputSize()=2, delegate.outputTypes().size()=1"); } } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java index b1b8cb1ce26..f1020c55426 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java @@ -20,7 +20,7 @@ public AnyMixin(AnyValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java index 24d7ea323e8..35279330fdc 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java @@ -24,8 +24,8 @@ public ArrayMixin(ArrayValue options, JsonFactory factory) { } @Override - public int numColumns() { - return element.numColumns(); + public int outputSize() { + return element.outputSize(); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java index bfb5855a363..2685ac47d1c 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java @@ -21,7 +21,7 @@ public BigDecimalMixin(BigDecimalValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java index 89d55523249..944179b3e55 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java @@ -21,7 +21,7 @@ public BigIntegerMixin(BigIntegerValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java index 0d4fbf360aa..6a1eccad2bf 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java @@ -31,7 +31,7 @@ public BoolMixin(BoolValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java index 9acfa10556a..6ea27d109a9 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java @@ -23,7 +23,7 @@ public ByteMixin(ByteValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java index 83abbfa6607..fe29969a5d2 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java @@ -23,7 +23,7 @@ public CharMixin(CharValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java index 1def7cbdc27..4c608d976ff 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java @@ -24,7 +24,7 @@ public DoubleMixin(DoubleValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java index f83c3905732..6ef6a177f30 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java @@ -24,7 +24,7 @@ public FloatMixin(FloatValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java index 3f85ca5c3f8..ebd4ed9e023 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java @@ -28,7 +28,7 @@ public InstantMixin(InstantValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java index ab6296ed962..a3b1dcf1eca 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java @@ -26,7 +26,7 @@ public InstantNumberMixin(InstantNumberValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java index 20627dd1887..cbddd935a74 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -24,7 +24,7 @@ public IntMixin(IntValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java index 5c8cd5c63d5..04eeec05597 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java @@ -22,7 +22,7 @@ public LocalDateMixin(LocalDateValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java index cb166d531f5..a9125f1abad 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java @@ -20,7 +20,7 @@ public LongMixin(LongValue options, JsonFactory config) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index 1aca2a39abb..f963288914f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -70,11 +70,6 @@ static Mixin of(Value options, JsonFactory factory) { this.options = Objects.requireNonNull(options); } - @Override - public final int size() { - return numColumns(); - } - @Override public final List> outputTypes() { return outputTypesImpl().collect(Collectors.toList()); @@ -165,8 +160,6 @@ public final ObjectProcessor charBufferProcessor() { abstract RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull); - abstract int numColumns(); - abstract Stream> paths(); abstract Stream> outputTypesImpl(); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java index c192cfd993c..299033b9567 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java @@ -30,18 +30,18 @@ public ObjectKvMixin(ObjectKvValue options, JsonFactory factory) { } @Override - public int numColumns() { - return key.numColumns() + value.numColumns(); + public int outputSize() { + return key.outputSize() + value.outputSize(); } @Override public Stream> paths() { final Stream> keyPath = - key.numColumns() == 1 && key.paths().findFirst().orElseThrow().isEmpty() + key.outputSize() == 1 && key.paths().findFirst().orElseThrow().isEmpty() ? Stream.of(List.of("Key")) : key.paths(); final Stream> valuePath = - value.numColumns() == 1 && value.paths().findFirst().orElseThrow().isEmpty() + value.outputSize() == 1 && value.paths().findFirst().orElseThrow().isEmpty() ? Stream.of(List.of("Value")) : value.paths(); return Stream.concat(keyPath, valuePath); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java index de46beaa69d..7cd6ef0eee7 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -39,7 +39,7 @@ public ObjectMixin(ObjectValue options, JsonFactory factory) { map.put(field, Mixin.of(field.options(), factory)); } mixins = Collections.unmodifiableMap(map); - numOutputs = mixins.values().stream().mapToInt(Mixin::numColumns).sum(); + numOutputs = mixins.values().stream().mapToInt(Mixin::outputSize).sum(); } @Override @@ -48,7 +48,7 @@ public Stream> outputTypesImpl() { } @Override - public int numColumns() { + public int outputSize() { return numOutputs; } @@ -67,12 +67,12 @@ public ValueProcessor processor(String context, boolean isDiscriminated) { int ix = 0; for (ObjectField field : options.fields()) { final Mixin opts = mixins.get(field); - final int numTypes = opts.numColumns(); + final int numTypes = opts.outputSize(); final ValueProcessor fieldProcessor = opts.processor(context + "/" + field.name()); processors.put(field, fieldProcessor); ix += numTypes; } - if (ix != numColumns()) { + if (ix != outputSize()) { throw new IllegalStateException(); } return processorImpl(processors, isDiscriminated); @@ -84,12 +84,12 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { int ix = 0; for (ObjectField field : options.fields()) { final Mixin opts = mixins.get(field); - final int numTypes = opts.numColumns(); + final int numTypes = opts.outputSize(); final RepeaterProcessor fieldProcessor = opts.repeaterProcessor(allowMissing, allowNull); processors.put(field, fieldProcessor); ix += numTypes; } - if (ix != numColumns()) { + if (ix != outputSize()) { throw new IllegalStateException(); } return new ObjectValueRepeaterProcessor(processors); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectProcessorJsonValue.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectProcessorJsonValue.java index 3d85f4112a0..8268ab3b348 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectProcessorJsonValue.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectProcessorJsonValue.java @@ -26,7 +26,7 @@ abstract class ObjectProcessorJsonValue implements ObjectProcessor { } @Override - public final int numOutputs() { + public final int outputSize() { return processor.numColumns(); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java index 79d78e6cd88..9112b4e0f53 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java @@ -23,7 +23,7 @@ public ShortMixin(ShortValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java index b98c7d124b3..43e0ae03f02 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java @@ -21,7 +21,7 @@ public SkipMixin(SkipValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 0; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java index e32e2f57cf7..abbfabe5064 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java @@ -20,7 +20,7 @@ public StringMixin(StringValue options, JsonFactory factory) { } @Override - public int numColumns() { + public int outputSize() { return 1; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java index 192a73f3c57..c2cd6d8c11e 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java @@ -37,11 +37,11 @@ public TupleMixin(TupleValue options, JsonFactory factory) { map.put(e.getKey(), Mixin.of(e.getValue(), factory)); } mixins = Collections.unmodifiableMap(map); - numColumns = mixins.values().stream().mapToInt(Mixin::numColumns).sum(); + numColumns = mixins.values().stream().mapToInt(Mixin::outputSize).sum(); } @Override - public int numColumns() { + public int outputSize() { return numColumns; } @@ -68,12 +68,12 @@ public ValueProcessor processor(String context) { int ix = 0; for (Entry> e : mixins.entrySet()) { final Mixin mixin = e.getValue(); - final int numTypes = mixin.numColumns(); + final int numTypes = mixin.outputSize(); final ValueProcessor processor = mixin.processor(context + "[" + e.getKey() + "]"); processors.add(processor); ix += numTypes; } - if (ix != numColumns()) { + if (ix != outputSize()) { throw new IllegalStateException(); } return new TupleProcessor(processors); @@ -85,12 +85,12 @@ RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { int ix = 0; for (Entry> e : mixins.entrySet()) { final Mixin mixin = e.getValue(); - final int numTypes = mixin.numColumns(); + final int numTypes = mixin.outputSize(); final RepeaterProcessor processor = mixin.repeaterProcessor(allowMissing, allowNull); processors.add(processor); ix += numTypes; } - if (ix != numColumns()) { + if (ix != outputSize()) { throw new IllegalStateException(); } return new TupleArrayProcessor(processors); @@ -158,11 +158,18 @@ private void processNullTuple(JsonParser parser) throws IOException { private void parseFromMissing(JsonParser parser) throws IOException { checkMissingAllowed(parser); + int ix = 0; // Note: we are treating a missing tuple the same as a tuple of missing objects (which, is technically // impossible w/ native json, but it's the semantics we are exposing). // ~= [, ..., ] for (ValueProcessor value : values) { - value.processMissing(parser); + try { + value.processMissing(parser); + } catch (IOException | RuntimeException e) { + throw new ValueAwareException(String.format("Unable to process tuple ix %d", ix), + parser.currentLocation(), e, options); + } + ++ix; } } } @@ -170,7 +177,6 @@ private void parseFromMissing(JsonParser parser) throws IOException { final class TupleArrayProcessor extends ContextAwareDelegateBase implements RepeaterProcessor, Context { private final List values; private final List contexts; - private int index; public TupleArrayProcessor(List values) { super(values); @@ -207,7 +213,6 @@ public void start(JsonParser parser) throws IOException { for (Context context : contexts) { context.start(parser); } - index = 0; } @Override @@ -222,7 +227,6 @@ public void processElement(JsonParser parser) throws IOException { default: throw Exceptions.notAllowed(parser); } - ++index; } private void processTuple(JsonParser parser) throws IOException { @@ -250,7 +254,6 @@ public void processElementMissing(JsonParser parser) throws IOException { for (Context context : contexts) { context.processElementMissing(parser); } - ++index; } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java index d36959c3833..2893174976b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java @@ -10,13 +10,13 @@ import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.json.ObjectField; import io.deephaven.json.ObjectValue; +import io.deephaven.json.StringValue; import io.deephaven.json.TypedObjectValue; import io.deephaven.json.jackson.Exceptions.ValueAwareException; import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -24,11 +24,12 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; +import java.util.TreeSet; import java.util.stream.Stream; final class TypedObjectMixin extends Mixin { + private final TreeSet typeFieldAliases; private final Map> sharedFields; private final Map combinedFields; private final int numSharedColumns; @@ -36,6 +37,10 @@ final class TypedObjectMixin extends Mixin { public TypedObjectMixin(TypedObjectValue options, JsonFactory factory) { super(factory, options); + if (!(options.typeField().options() instanceof StringValue)) { + throw new IllegalArgumentException("Only string-valued type fields are currently supported"); + } + typeFieldAliases = options.typeField().caseSensitive() ? null : new TreeSet<>(String.CASE_INSENSITIVE_ORDER); { final LinkedHashMap> map = new LinkedHashMap<>(options.sharedFields().size()); for (ObjectField sharedField : options.sharedFields()) { @@ -45,25 +50,25 @@ public TypedObjectMixin(TypedObjectValue options, JsonFactory factory) { } { final LinkedHashMap map = new LinkedHashMap<>(options.objects().size()); - for (Entry e : options.objects().entrySet()) { - map.put(e.getKey(), new ObjectMixin(combinedObject(e.getValue()), factory)); + for (Entry e : options.objects().entrySet()) { + map.put((String) e.getKey(), new ObjectMixin(combinedObject(e.getValue()), factory)); } combinedFields = Collections.unmodifiableMap(map); } - numSharedColumns = sharedFields.values().stream().mapToInt(Mixin::numColumns).sum(); + numSharedColumns = sharedFields.values().stream().mapToInt(Mixin::outputSize).sum(); numSpecificColumns = - combinedFields.values().stream().mapToInt(ObjectMixin::numColumns).map(x -> x - numSharedColumns).sum(); + combinedFields.values().stream().mapToInt(ObjectMixin::outputSize).map(x -> x - numSharedColumns).sum(); } @Override - public int numColumns() { + public int outputSize() { return 1 + numSharedColumns + numSpecificColumns; } @Override public Stream> paths() { return Stream.concat( - Stream.of(List.of(options.typeFieldName())), + Stream.of(List.of(options.typeField().name())), Stream.concat( prefixWithKeys(sharedFields), prefixWithKeysAndSkip(combinedFields, numSharedColumns))); @@ -109,16 +114,26 @@ private ObjectValue combinedObject(ObjectValue objectOpts) { .build(); } + private boolean matchesTypeField(String name) { + if (options.typeField().caseSensitive()) { + return options.typeField().name().equals(name) || options.typeField().aliases().contains(name); + } else { + return options.typeField().name().equalsIgnoreCase(name) || typeFieldAliases.contains(name); + } + } + private String parseTypeField(JsonParser parser) throws IOException { - final String actualFieldName = parser.currentName(); - if (!options.typeFieldName().equals(actualFieldName)) { + final String firstFieldName = parser.currentName(); + if (!matchesTypeField(firstFieldName)) { throw new ValueAwareException(String.format("Expected the first field to be '%s', is '%s'", - options.typeFieldName(), actualFieldName), parser.currentLocation(), options); + options.typeField().name(), firstFieldName), parser.currentLocation(), options); } switch (parser.nextToken()) { case VALUE_STRING: case FIELD_NAME: return parser.getText(); + case VALUE_NUMBER_INT: + return parser.getText(); case VALUE_NULL: return null; default: @@ -198,7 +213,7 @@ public void clearContext() { @Override public int numColumns() { - return TypedObjectMixin.this.numColumns(); + return TypedObjectMixin.this.outputSize(); } @Override diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java index c8092ca2364..e4e4016b3df 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java @@ -4,11 +4,13 @@ package io.deephaven.json; import io.deephaven.chunk.DoubleChunk; +import io.deephaven.chunk.IntChunk; import io.deephaven.chunk.LongChunk; import io.deephaven.chunk.ObjectChunk; import io.deephaven.json.jackson.JacksonProvider; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -42,12 +44,13 @@ public class TypedObjectValueTest { .build(); private static final TypedObjectValue QUOTE_OR_TRADE_OBJECT = - TypedObjectValue.builder("type", new LinkedHashMap<>() { + TypedObjectValue.builder(new LinkedHashMap<>() { { put("quote", QUOTE_OBJECT); put("trade", TRADE_OBJECT); } }) + .typeFieldName("type") .onNull("") .onMissing("") .build(); @@ -176,6 +179,37 @@ void standardArray() { } } + // Disabled; this may be a feature we want to spec out in the future + @Test + @Disabled + void intAsDiscriminator() throws IOException { + // Note: need to pass + final TypedObjectValue tov = TypedObjectValue.builder(new LinkedHashMap<>() { + { + put(1, QUOTE_OBJECT); + put(2, TRADE_OBJECT); + } + }).typeField(ObjectField.of("id", IntValue.standard())).build(); + + parse(tov, List.of( + "{\"id\": 1, \"symbol\": {\"name\": \"foo\", \"id\": 42}, \"quote\":{\"bid\": 1.01, \"ask\": 1.05}}", + "{\"id\": 2, \"symbol\": {\"name\": \"bar\", \"id\": 43}, \"price\": 42.42, \"size\": 123}", + "{\"id\": 3}", + "{\"id\": 4, \"symbol\": {\"name\": \"bar\", \"id\": 43}, \"price\": 42.42, \"size\": 123}"), + IntChunk.chunkWrap(new int[] {1, 2, 3, 4}), // id + ObjectChunk.chunkWrap(new String[] {"foo", "bar", null, null}), // symbol/symbol + LongChunk.chunkWrap(new long[] {42, 43, QueryConstants.NULL_LONG, QueryConstants.NULL_LONG}), // symbol/symbol_id + DoubleChunk.chunkWrap(new double[] {1.01, QueryConstants.NULL_DOUBLE, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE}), // quote: quote/bid + DoubleChunk.chunkWrap(new double[] {1.05, QueryConstants.NULL_DOUBLE, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE}), // quote: quote/ask + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 42.42, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE}), // trade: price + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 123, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE})); // trade: size + + } + @Test void columnNames() { assertThat(JacksonProvider.of(QUOTE_OR_TRADE_OBJECT).named(Type.stringType()).names()).containsExactly( diff --git a/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java b/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java index 2ac02128ef7..1d92e6426fd 100644 --- a/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java +++ b/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java @@ -4,7 +4,6 @@ package io.deephaven.json; import io.deephaven.annotations.BuildableStyle; -import io.deephaven.json.ImmutableTypedObjectValue.Builder; import org.immutables.value.Value.Check; import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; @@ -50,16 +49,14 @@ public static Builder builder() { } /** - * Creates a new builder with the {@link #typeFieldName()} set to {@code typeFieldName}, {@link #sharedFields()} - * inferred from {@code objects} based on {@link ObjectField} equality, and {@link #objects()} set to - * {@code objects} with the shared fields removed. + * Creates a new builder with {@link #sharedFields()} inferred from {@code objects} based on {@link ObjectField} + * equality, and {@link #objects()} set to {@code objects} with the shared fields removed. * - * @param typeFieldName the type field name * @param objects the objects * @return the builder */ - public static Builder builder(String typeFieldName, Map objects) { - final Builder builder = builder().typeFieldName(typeFieldName); + public static Builder builder(Map objects) { + final Builder builder = builder(); final Set sharedFields = new LinkedHashSet<>(); final ObjectValue first = objects.values().iterator().next(); for (ObjectField field : first.fields()) { @@ -74,7 +71,7 @@ public static Builder builder(String typeFieldName, Map obj sharedFields.add(field); } } - for (Entry e : objects.entrySet()) { + for (Entry e : objects.entrySet()) { builder.putObjects(e.getKey(), without(e.getValue(), sharedFields)); } return builder.addAllSharedFields(sharedFields); @@ -88,8 +85,8 @@ public static Builder builder(String typeFieldName, Map obj * @param objects the objects * @return the typed object */ - public static TypedObjectValue standard(String typeFieldName, Map objects) { - return builder(typeFieldName, objects).build(); + public static TypedObjectValue standard(String typeFieldName, Map objects) { + return builder(objects).typeFieldName(typeFieldName).build(); } /** @@ -100,20 +97,29 @@ public static TypedObjectValue standard(String typeFieldName, Map objects) { - return builder(typeFieldName, objects) + public static TypedObjectValue strict(String typeFieldName, Map objects) { + return builder(objects) + .typeFieldName(typeFieldName) .allowUnknownTypes(false) .allowMissing(false) .allowedTypes(JsonValueTypes.object()) .build(); } - public abstract String typeFieldName(); + /** + * The type field. + */ + public abstract ObjectField typeField(); + /** + * The shared fields. + */ public abstract Set sharedFields(); - // canonical name - public abstract Map objects(); + /** + * The discriminated objects. + */ + public abstract Map objects(); /** * If unknown fields are allowed. By default is {@code true}. @@ -156,7 +162,11 @@ public final T walk(Visitor visitor) { public interface Builder extends Value.Builder { - Builder typeFieldName(String typeFieldName); + default Builder typeFieldName(String typeFieldName) { + return typeField(ObjectField.of(typeFieldName, StringValue.standard())); + } + + Builder typeField(ObjectField typeField); Builder addSharedFields(ObjectField element); @@ -164,7 +174,7 @@ public interface Builder extends Value.Builder { Builder addAllSharedFields(Iterable elements); - Builder putObjects(String key, ObjectValue value); + Builder putObjects(Object key, ObjectValue value); Builder allowUnknownTypes(boolean allowUnknownTypes); diff --git a/extensions/json/src/test/java/io/deephaven/json/TypedObjectValueBuilderTest.java b/extensions/json/src/test/java/io/deephaven/json/TypedObjectValueBuilderTest.java index 4a24b9807ac..6abe077d3cc 100644 --- a/extensions/json/src/test/java/io/deephaven/json/TypedObjectValueBuilderTest.java +++ b/extensions/json/src/test/java/io/deephaven/json/TypedObjectValueBuilderTest.java @@ -38,8 +38,10 @@ public class TypedObjectValueBuilderTest { @Test void builderHelper() { - final TypedObjectValue combined = - TypedObjectValue.builder("type", Map.of("quote", QUOTE, "trade", TRADE)).build(); + final TypedObjectValue combined = TypedObjectValue + .builder(Map.of("quote", QUOTE, "trade", TRADE)) + .typeFieldName("type") + .build(); assertThat(combined).isEqualTo(COMBINED); } } From 34dda0c01b3fd68ade0331d45a12c32c881cc552 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 29 May 2024 09:45:43 -0700 Subject: [PATCH 35/53] cleanup --- .../json/jackson/JacksonProvider.java | 6 +- .../java/io/deephaven/json/jackson/Mixin.java | 56 +++++++------------ 2 files changed, 24 insertions(+), 38 deletions(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java index 906e7f9e224..d2b10cdeb5a 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java @@ -49,7 +49,7 @@ static JacksonProvider of(Value options, JsonFactory factory) { /** * The supported types. Includes {@link String}, {@code byte[]}, {@code char[]}, {@link File}, {@link Path}, - * {@link URL}, and {@link ByteBuffer}. + * {@link URL}, {@link ByteBuffer}, and {@link CharBuffer}. * * @return the supported types */ @@ -61,7 +61,8 @@ static Set> getInputTypes() { Type.ofCustom(File.class), Type.ofCustom(Path.class), Type.ofCustom(URL.class), - Type.ofCustom(ByteBuffer.class)); + Type.ofCustom(ByteBuffer.class), + Type.ofCustom(CharBuffer.class)); } /** @@ -87,6 +88,7 @@ default Set> inputTypes() { * @see #pathProcessor() * @see #urlProcessor() * @see #byteBufferProcessor() + * @see #charBufferProcessor() */ @Override ObjectProcessor processor(Type inputType); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index f963288914f..bd6318b621b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -164,38 +164,6 @@ public final ObjectProcessor charBufferProcessor() { abstract Stream> outputTypesImpl(); - final boolean allowNull() { - return options.allowedTypes().contains(JsonValueTypes.NULL); - } - - final boolean allowMissing() { - return options.allowMissing(); - } - - final boolean allowString() { - return options.allowedTypes().contains(JsonValueTypes.STRING); - } - - final boolean allowNumberInt() { - return options.allowedTypes().contains(JsonValueTypes.INT); - } - - final boolean allowDecimal() { - return options.allowedTypes().contains(JsonValueTypes.DECIMAL); - } - - final boolean allowBool() { - return options.allowedTypes().contains(JsonValueTypes.BOOL); - } - - final boolean allowObject() { - return options.allowedTypes().contains(JsonValueTypes.OBJECT); - } - - final boolean allowArray() { - return options.allowedTypes().contains(JsonValueTypes.ARRAY); - } - static List prefixWith(String prefix, List path) { return Stream.concat(Stream.of(prefix), path.stream()).collect(Collectors.toList()); } @@ -394,6 +362,22 @@ public AnyMixin visit(AnyValue any) { } } + final boolean allowNull() { + return options.allowedTypes().contains(JsonValueTypes.NULL); + } + + final boolean allowMissing() { + return options.allowMissing(); + } + + final boolean allowNumberInt() { + return options.allowedTypes().contains(JsonValueTypes.INT); + } + + final boolean allowDecimal() { + return options.allowedTypes().contains(JsonValueTypes.DECIMAL); + } + final void checkNumberAllowed(JsonParser parser) throws IOException { if (!allowNumberInt() && !allowDecimal()) { throw new ValueAwareException("Number not allowed", parser.currentLocation(), options); @@ -413,25 +397,25 @@ final void checkDecimalAllowed(JsonParser parser) throws IOException { } final void checkBoolAllowed(JsonParser parser) throws IOException { - if (!allowBool()) { + if (!options.allowedTypes().contains(JsonValueTypes.BOOL)) { throw new ValueAwareException("Bool not expected", parser.currentLocation(), options); } } final void checkStringAllowed(JsonParser parser) throws IOException { - if (!allowString()) { + if (!options.allowedTypes().contains(JsonValueTypes.STRING)) { throw new ValueAwareException("String not allowed", parser.currentLocation(), options); } } final void checkObjectAllowed(JsonParser parser) throws IOException { - if (!allowObject()) { + if (!options.allowedTypes().contains(JsonValueTypes.OBJECT)) { throw new ValueAwareException("Object not allowed", parser.currentLocation(), options); } } final void checkArrayAllowed(JsonParser parser) throws IOException { - if (!allowArray()) { + if (!options.allowedTypes().contains(JsonValueTypes.ARRAY)) { throw new ValueAwareException("Array not allowed", parser.currentLocation(), options); } } From 9fccd9df6f6061519836a24d45ea4c3417f78f9e Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 29 May 2024 10:18:04 -0700 Subject: [PATCH 36/53] Instant array types --- .../deephaven/json/jackson/InstantMixin.java | 38 ++++++++++++++++- .../json/jackson/InstantNumberMixin.java | 41 +++++++++++++++---- .../java/io/deephaven/json/ArrayTest.java | 13 +++++- .../io/deephaven/json/ObjectKvValueTest.java | 5 ++- 4 files changed, 85 insertions(+), 12 deletions(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java index ebd4ed9e023..f5825f0aad7 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java @@ -7,10 +7,12 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.json.InstantValue; import io.deephaven.json.jackson.LongValueProcessor.ToLong; +import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; import io.deephaven.time.DateTimeUtils; import java.io.IOException; +import java.time.Instant; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.util.List; @@ -66,7 +68,27 @@ public long parseMissing(JsonParser parser) throws IOException { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new LongRepeaterImpl(this, allowMissing, allowNull, Type.instantType().arrayType()); + return new RepeaterGenericImpl<>(new ToObjectImpl(), allowMissing, allowNull, null, null, + Type.instantType().arrayType()); + } + + class ToObjectImpl implements ToObject { + @Override + public Instant parseValue(JsonParser parser) throws IOException { + switch (parser.currentToken()) { + case VALUE_STRING: + case FIELD_NAME: + return parseFromStringToInstant(parser); + case VALUE_NULL: + return parseFromNullToInstant(parser); + } + throw unexpectedToken(parser); + } + + @Override + public Instant parseMissing(JsonParser parser) throws IOException { + return parseFromMissingToInstant(parser); + } } private long parseFromString(JsonParser parser) throws IOException { @@ -85,4 +107,18 @@ private long parseFromMissing(JsonParser parser) throws IOException { checkMissingAllowed(parser); return onMissing; } + + private Instant parseFromStringToInstant(JsonParser parser) throws IOException { + return Instant.from(options.dateTimeFormatter().parse(Parsing.textAsCharSequence(parser))); + } + + private Instant parseFromNullToInstant(JsonParser parser) throws IOException { + checkNullAllowed(parser); + return options.onNull().orElse(null); + } + + private Instant parseFromMissingToInstant(JsonParser parser) throws IOException { + checkMissingAllowed(parser); + return options.onMissing().orElse(null); + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java index a3b1dcf1eca..4a1a541fec2 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java @@ -6,11 +6,14 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.json.InstantNumberValue; +import io.deephaven.json.jackson.LongValueProcessor.ToLong; +import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; import io.deephaven.time.DateTimeUtils; import java.io.IOException; import java.math.BigInteger; +import java.time.Instant; import java.util.List; import java.util.stream.Stream; @@ -42,35 +45,36 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new LongValueProcessor(function()); + return new LongValueProcessor(longFunction()); } @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new LongRepeaterImpl(function(), allowMissing, allowNull, Type.instantType().arrayType()); + return new RepeaterGenericImpl<>(new ObjectImpl(), allowMissing, allowNull, null, null, + Type.instantType().arrayType()); } - private LongValueProcessor.ToLong function() { + private LongValueProcessor.ToLong longFunction() { switch (options.format()) { case EPOCH_SECONDS: - return new Impl(9); + return new LongImpl(9); case EPOCH_MILLIS: - return new Impl(6); + return new LongImpl(6); case EPOCH_MICROS: - return new Impl(3); + return new LongImpl(3); case EPOCH_NANOS: - return new Impl(0); + return new LongImpl(0); default: throw new IllegalStateException(); } } - private class Impl implements LongValueProcessor.ToLong { + private class LongImpl implements LongValueProcessor.ToLong { private final int scaled; private final int mult; - Impl(int scaled) { + LongImpl(int scaled) { this.scaled = scaled; this.mult = BigInteger.valueOf(10).pow(scaled).intValueExact(); } @@ -123,4 +127,23 @@ public final long parseMissing(JsonParser parser) throws IOException { return onMissing; } } + + private class ObjectImpl implements ToObject { + + private final ToLong longImpl; + + public ObjectImpl() { + this.longImpl = longFunction(); + } + + @Override + public Instant parseValue(JsonParser parser) throws IOException { + return DateTimeUtils.epochNanosToInstant(longImpl.parseValue(parser)); + } + + @Override + public Instant parseMissing(JsonParser parser) throws IOException { + return DateTimeUtils.epochNanosToInstant(longImpl.parseValue(parser)); + } + } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ArrayTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ArrayTest.java index 8ec596966be..b350a70b32c 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ArrayTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ArrayTest.java @@ -4,11 +4,13 @@ package io.deephaven.json; import io.deephaven.chunk.ObjectChunk; +import io.deephaven.json.InstantNumberValue.Format; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.time.Instant; import java.util.Map; import static io.deephaven.json.TestHelper.parse; @@ -78,7 +80,16 @@ void instant() throws IOException { // ["2009-02-13T23:31:30.123456789Z", null] parse(InstantValue.standard().array(), "[\"2009-02-13T23:31:30.123456789Z\", null]", - ObjectChunk.chunkWrap(new Object[] {new long[] {1234567890123456789L, QueryConstants.NULL_LONG}})); + ObjectChunk.chunkWrap( + new Object[] {new Instant[] {Instant.parse("2009-02-13T23:31:30.123456789Z"), null}})); + } + + @Test + void instantNumber() throws IOException { + // [1234567, null] + parse(Format.EPOCH_SECONDS.standard(false).array(), + "[1234567, null]", + ObjectChunk.chunkWrap(new Object[] {new Instant[] {Instant.ofEpochSecond(1234567), null}})); } @Test diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvValueTest.java index ea0c51f7b72..ab734620e7c 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvValueTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import java.io.IOException; +import java.time.Instant; import java.util.List; import static io.deephaven.json.TestHelper.parse; @@ -55,7 +56,9 @@ void kvPrimitiveKey() throws IOException { void kvObjectKey() throws IOException { parse(ObjectKvValue.builder().key(InstantValue.standard()).value(SkipValue.lenient()).build(), List.of( "{\"2009-02-13T23:31:30.123456788Z\": null, \"2009-02-13T23:31:30.123456789Z\": null}"), - ObjectChunk.chunkWrap(new Object[] {new long[] {1234567890123456788L, 1234567890123456789L}})); + ObjectChunk.chunkWrap(new Object[] {new Instant[] { + Instant.parse("2009-02-13T23:31:30.123456788Z"), + Instant.parse("2009-02-13T23:31:30.123456789Z")}})); } @Test From a389e88dfd39f712c835f43efd371d827908a61b Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 29 May 2024 17:03:09 -0700 Subject: [PATCH 37/53] Introduce ObjectChunkDeepEquals --- .../chunk/util/hashing/ChunkEquals.java | 17 +- .../util/hashing/ObjectChunkDeepEquals.java | 264 ++++++++++++++++++ .../chunk/util/hashing/ObjectChunkEquals.java | 2 +- .../io/deephaven/bson/jackson/TestHelper.java | 4 +- .../java/io/deephaven/json/TestHelper.java | 4 +- .../replicators/ReplicateHashing.java | 28 +- 6 files changed, 313 insertions(+), 6 deletions(-) create mode 100644 engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ObjectChunkDeepEquals.java diff --git a/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ChunkEquals.java b/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ChunkEquals.java index ae1d31e2ab9..5fc83d1fda8 100644 --- a/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ChunkEquals.java +++ b/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ChunkEquals.java @@ -150,6 +150,14 @@ void andEqualPairs(IntChunk chunkPositionsToCheckForEquality, Ch WritableBooleanChunk destinations); static ChunkEquals makeEqual(ChunkType chunkType) { + return makeEqual(chunkType, ObjectComparison.EQUALS); + } + + enum ObjectComparison { + IDENTITY, EQUALS, DEEP_EQUALS + } + + static ChunkEquals makeEqual(ChunkType chunkType, ObjectComparison objectComparison) { switch (chunkType) { case Boolean: return BooleanChunkEquals.INSTANCE; @@ -168,7 +176,14 @@ static ChunkEquals makeEqual(ChunkType chunkType) { case Double: return DoubleChunkEquals.INSTANCE; case Object: - return ObjectChunkEquals.INSTANCE; + switch (objectComparison) { + case IDENTITY: + return ObjectChunkIdentityEquals.INSTANCE; + case EQUALS: + return ObjectChunkEquals.INSTANCE; + case DEEP_EQUALS: + return ObjectChunkDeepEquals.INSTANCE; + } } throw new IllegalStateException(); } diff --git a/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ObjectChunkDeepEquals.java b/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ObjectChunkDeepEquals.java new file mode 100644 index 00000000000..985aca636f1 --- /dev/null +++ b/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ObjectChunkDeepEquals.java @@ -0,0 +1,264 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +// ****** AUTO-GENERATED CLASS - DO NOT EDIT MANUALLY +// ****** Edit CharChunkEquals and run "./gradlew replicateHashing" to regenerate +// +// @formatter:off +package io.deephaven.chunk.util.hashing; + +import java.util.Objects; + +import io.deephaven.chunk.*; +import io.deephaven.chunk.attributes.Any; +import io.deephaven.chunk.attributes.ChunkPositions; + +// region name +public class ObjectChunkDeepEquals implements ChunkEquals { + public static ObjectChunkDeepEquals INSTANCE = new ObjectChunkDeepEquals(); + // endregion name + + public static boolean equalReduce(ObjectChunk lhs, ObjectChunk rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + for (int ii = 0; ii < lhs.size(); ++ii) { + if (!eq(lhs.get(ii), rhs.get(ii))) { + return false; + } + } + return true; + } + + public static int firstDifference(ObjectChunk lhs, ObjectChunk rhs) { + int ii = 0; + for (ii = 0; ii < lhs.size() && ii < rhs.size(); ++ii) { + if (!eq(lhs.get(ii), rhs.get(ii))) { + return ii; + } + } + return ii; + } + + private static void equal(ObjectChunk lhs, ObjectChunk rhs, + WritableBooleanChunk destination) { + for (int ii = 0; ii < lhs.size(); ++ii) { + destination.set(ii, eq(lhs.get(ii), rhs.get(ii))); + } + destination.setSize(lhs.size()); + } + + private static void equalNext(ObjectChunk chunk, WritableBooleanChunk destination) { + for (int ii = 0; ii < chunk.size() - 1; ++ii) { + destination.set(ii, eq(chunk.get(ii), chunk.get(ii + 1))); + } + destination.setSize(chunk.size() - 1); + } + + private static void equal(ObjectChunk lhs, Object rhs, WritableBooleanChunk destination) { + for (int ii = 0; ii < lhs.size(); ++ii) { + destination.set(ii, eq(lhs.get(ii), rhs)); + } + destination.setSize(lhs.size()); + } + + public static void notEqual(ObjectChunk lhs, ObjectChunk rhs, + WritableBooleanChunk destination) { + for (int ii = 0; ii < lhs.size(); ++ii) { + destination.set(ii, neq(lhs.get(ii), rhs.get(ii))); + } + destination.setSize(lhs.size()); + } + + public static void notEqual(ObjectChunk lhs, Object rhs, WritableBooleanChunk destination) { + for (int ii = 0; ii < lhs.size(); ++ii) { + destination.set(ii, neq(lhs.get(ii), rhs)); + } + destination.setSize(lhs.size()); + } + + private static void andEqual(ObjectChunk lhs, ObjectChunk rhs, + WritableBooleanChunk destination) { + for (int ii = 0; ii < lhs.size(); ++ii) { + destination.set(ii, destination.get(ii) && eq(lhs.get(ii), rhs.get(ii))); + } + destination.setSize(lhs.size()); + } + + private static void andNotEqual(ObjectChunk lhs, ObjectChunk rhs, + WritableBooleanChunk destination) { + for (int ii = 0; ii < lhs.size(); ++ii) { + destination.set(ii, destination.get(ii) && neq(lhs.get(ii), rhs.get(ii))); + } + destination.setSize(lhs.size()); + } + + private static void andEqualNext(ObjectChunk chunk, WritableBooleanChunk destination) { + for (int ii = 0; ii < chunk.size() - 1; ++ii) { + destination.set(ii, destination.get(ii) && eq(chunk.get(ii), chunk.get(ii + 1))); + } + destination.setSize(chunk.size() - 1); + } + + private static void equalPairs(IntChunk chunkPositionsToCheckForEquality, + ObjectChunk valuesChunk, WritableBooleanChunk destinations) { + final int pairCount = chunkPositionsToCheckForEquality.size() / 2; + for (int ii = 0; ii < pairCount; ++ii) { + final int firstPosition = chunkPositionsToCheckForEquality.get(ii * 2); + final int secondPosition = chunkPositionsToCheckForEquality.get(ii * 2 + 1); + final boolean equals = eq(valuesChunk.get(firstPosition), valuesChunk.get(secondPosition)); + destinations.set(ii, equals); + } + destinations.setSize(pairCount); + } + + private static void andEqualPairs(IntChunk chunkPositionsToCheckForEquality, + ObjectChunk valuesChunk, WritableBooleanChunk destinations) { + final int pairCount = chunkPositionsToCheckForEquality.size() / 2; + for (int ii = 0; ii < pairCount; ++ii) { + if (destinations.get(ii)) { + final int firstPosition = chunkPositionsToCheckForEquality.get(ii * 2); + final int secondPosition = chunkPositionsToCheckForEquality.get(ii * 2 + 1); + final boolean equals = eq(valuesChunk.get(firstPosition), valuesChunk.get(secondPosition)); + destinations.set(ii, equals); + } + } + } + + private static void equalPermuted(IntChunk lhsPositions, IntChunk rhsPositions, + ObjectChunk lhs, ObjectChunk rhs, WritableBooleanChunk destinations) { + for (int ii = 0; ii < lhsPositions.size(); ++ii) { + final int lhsPosition = lhsPositions.get(ii); + final int rhsPosition = rhsPositions.get(ii); + final boolean equals = eq(lhs.get(lhsPosition), rhs.get(rhsPosition)); + destinations.set(ii, equals); + } + destinations.setSize(lhsPositions.size()); + } + + private static void andEqualPermuted(IntChunk lhsPositions, IntChunk rhsPositions, + ObjectChunk lhs, ObjectChunk rhs, WritableBooleanChunk destinations) { + for (int ii = 0; ii < lhsPositions.size(); ++ii) { + if (destinations.get(ii)) { + final int lhsPosition = lhsPositions.get(ii); + final int rhsPosition = rhsPositions.get(ii); + final boolean equals = eq(lhs.get(lhsPosition), rhs.get(rhsPosition)); + destinations.set(ii, equals); + } + } + destinations.setSize(lhsPositions.size()); + } + + private static void equalLhsPermuted(IntChunk lhsPositions, ObjectChunk lhs, + ObjectChunk rhs, WritableBooleanChunk destinations) { + for (int ii = 0; ii < lhsPositions.size(); ++ii) { + final int lhsPosition = lhsPositions.get(ii); + final boolean equals = eq(lhs.get(lhsPosition), rhs.get(ii)); + destinations.set(ii, equals); + } + destinations.setSize(lhsPositions.size()); + } + + private static void andEqualLhsPermuted(IntChunk lhsPositions, ObjectChunk lhs, + ObjectChunk rhs, WritableBooleanChunk destinations) { + for (int ii = 0; ii < lhsPositions.size(); ++ii) { + if (destinations.get(ii)) { + final int lhsPosition = lhsPositions.get(ii); + final boolean equals = eq(lhs.get(lhsPosition), rhs.get(ii)); + destinations.set(ii, equals); + } + } + destinations.setSize(lhsPositions.size()); + } + + @Override + public boolean equalReduce(Chunk lhs, Chunk rhs) { + return equalReduce(lhs.asObjectChunk(), rhs.asObjectChunk()); + } + + @Override + public void equal(Chunk lhs, Chunk rhs, WritableBooleanChunk destination) { + equal(lhs.asObjectChunk(), rhs.asObjectChunk(), destination); + } + + public static void equal(Chunk lhs, Object rhs, WritableBooleanChunk destination) { + equal(lhs.asObjectChunk(), rhs, destination); + } + + @Override + public void equalNext(Chunk chunk, WritableBooleanChunk destination) { + equalNext(chunk.asObjectChunk(), destination); + } + + @Override + public void andEqual(Chunk lhs, Chunk rhs, WritableBooleanChunk destination) { + andEqual(lhs.asObjectChunk(), rhs.asObjectChunk(), destination); + } + + @Override + public void andEqualNext(Chunk chunk, WritableBooleanChunk destination) { + andEqualNext(chunk.asObjectChunk(), destination); + } + + @Override + public void equalPermuted(IntChunk lhsPositions, IntChunk rhsPositions, + Chunk lhs, Chunk rhs, WritableBooleanChunk destination) { + equalPermuted(lhsPositions, rhsPositions, lhs.asObjectChunk(), rhs.asObjectChunk(), destination); + } + + @Override + public void equalLhsPermuted(IntChunk lhsPositions, Chunk lhs, + Chunk rhs, WritableBooleanChunk destination) { + equalLhsPermuted(lhsPositions, lhs.asObjectChunk(), rhs.asObjectChunk(), destination); + } + + @Override + public void andEqualPermuted(IntChunk lhsPositions, IntChunk rhsPositions, + Chunk lhs, Chunk rhs, WritableBooleanChunk destination) { + andEqualPermuted(lhsPositions, rhsPositions, lhs.asObjectChunk(), rhs.asObjectChunk(), destination); + } + + @Override + public void andEqualLhsPermuted(IntChunk lhsPositions, Chunk lhs, + Chunk rhs, WritableBooleanChunk destination) { + andEqualLhsPermuted(lhsPositions, lhs.asObjectChunk(), rhs.asObjectChunk(), destination); + } + + @Override + public void notEqual(Chunk lhs, Chunk rhs, WritableBooleanChunk destination) { + notEqual(lhs.asObjectChunk(), rhs.asObjectChunk(), destination); + } + + public static void notEqual(Chunk lhs, Object rhs, WritableBooleanChunk destination) { + notEqual(lhs.asObjectChunk(), rhs, destination); + } + + @Override + public void andNotEqual(Chunk lhs, Chunk rhs, WritableBooleanChunk destination) { + andNotEqual(lhs.asObjectChunk(), rhs.asObjectChunk(), destination); + } + + @Override + public void equalPairs(IntChunk chunkPositionsToCheckForEquality, Chunk valuesChunk, + WritableBooleanChunk destinations) { + equalPairs(chunkPositionsToCheckForEquality, valuesChunk.asObjectChunk(), destinations); + } + + @Override + public void andEqualPairs(IntChunk chunkPositionsToCheckForEquality, + Chunk valuesChunk, WritableBooleanChunk destinations) { + andEqualPairs(chunkPositionsToCheckForEquality, valuesChunk.asObjectChunk(), destinations); + } + + // region eq + static private boolean eq(Object lhs, Object rhs) { + return Objects.deepEquals(lhs, rhs); + } + // endregion eq + + // region neq + static private boolean neq(Object lhs, Object rhs) { + return !eq(lhs, rhs); + } + // endregion neq +} diff --git a/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ObjectChunkEquals.java b/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ObjectChunkEquals.java index 22cd5f99448..fe2ae7964ae 100644 --- a/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ObjectChunkEquals.java +++ b/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ObjectChunkEquals.java @@ -252,7 +252,7 @@ public void andEqualPairs(IntChunk chunkPositionsToCheckForEqual // region eq static private boolean eq(Object lhs, Object rhs) { - return Objects.deepEquals(lhs, rhs); + return Objects.equals(lhs, rhs); } // endregion eq diff --git a/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java index 44be6ee528c..9f73a79247a 100644 --- a/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java +++ b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java @@ -8,6 +8,7 @@ import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.chunk.attributes.Any; import io.deephaven.chunk.util.hashing.ChunkEquals; +import io.deephaven.chunk.util.hashing.ChunkEquals.ObjectComparison; import io.deephaven.processor.ObjectProcessor; import java.io.IOException; @@ -60,6 +61,7 @@ public static void parse(ObjectProcessor processor, List rows, static void check(Chunk actual, Chunk expected) { assertThat(actual.getChunkType()).isEqualTo(expected.getChunkType()); assertThat(actual.size()).isEqualTo(expected.size()); - assertThat(ChunkEquals.makeEqual(actual.getChunkType()).equalReduce(actual, expected)).isTrue(); + assertThat(ChunkEquals.makeEqual(actual.getChunkType(), ObjectComparison.DEEP_EQUALS).equalReduce(actual, + expected)).isTrue(); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java index 28454c636c6..1ba6fe15d8d 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java @@ -8,6 +8,7 @@ import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.chunk.attributes.Any; import io.deephaven.chunk.util.hashing.ChunkEquals; +import io.deephaven.chunk.util.hashing.ChunkEquals.ObjectComparison; import io.deephaven.json.jackson.JacksonProvider; import io.deephaven.processor.ObjectProcessor; @@ -86,6 +87,7 @@ public static List> process(ObjectProcessor proc static void check(Chunk actual, Chunk expected) { assertThat(actual.getChunkType()).isEqualTo(expected.getChunkType()); assertThat(actual.size()).isEqualTo(expected.size()); - assertThat(ChunkEquals.makeEqual(actual.getChunkType()).equalReduce(actual, expected)).isTrue(); + assertThat(ChunkEquals.makeEqual(actual.getChunkType(), ObjectComparison.DEEP_EQUALS).equalReduce(actual, + expected)).isTrue(); } } diff --git a/replication/static/src/main/java/io/deephaven/replicators/ReplicateHashing.java b/replication/static/src/main/java/io/deephaven/replicators/ReplicateHashing.java index 7c52b3ef847..5268c3c6697 100644 --- a/replication/static/src/main/java/io/deephaven/replicators/ReplicateHashing.java +++ b/replication/static/src/main/java/io/deephaven/replicators/ReplicateHashing.java @@ -48,6 +48,10 @@ public static void main(String[] args) throws IOException { charToObject(TASK, "engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/CharChunkEquals.java"); fixupObjectChunkIdentityEquals(objectIdentityEquals); + final String objectDeepEquals = + charToObject(TASK, "engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/CharChunkEquals.java"); + fixupObjectChunkDeepEquals(objectDeepEquals); + final String objectEquals = charToObject(TASK, "engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/CharChunkEquals.java"); fixupObjectChunkEquals(objectEquals); @@ -75,9 +79,8 @@ private static void fixupObjectChunkEquals(String objectPath) throws IOException final File objectFile = new File(objectPath); List lines = FileUtils.readLines(objectFile, Charset.defaultCharset()); lines = addImport(lines, Objects.class); - // TODO: verify this is safe FileUtils.writeLines(objectFile, simpleFixup(fixupChunkAttributes(lines), - "eq", "lhs == rhs", "Objects.deepEquals(lhs, rhs)")); + "eq", "lhs == rhs", "Objects.equals(lhs, rhs)")); } private static void fixupBooleanCompact(String booleanPath) throws IOException { @@ -147,6 +150,27 @@ private static void fixupObjectChunkIdentityEquals(String objectPath) throws IOE "name", "ObjectChunkEquals", "ObjectChunkIdentityEquals")); } + private static void fixupObjectChunkDeepEquals(String objectPath) throws IOException { + final File objectChunkEqualsFileName = new File(objectPath); + final File objectChunkIdentifyEqualsFileName = + new File(objectChunkEqualsFileName.getParent(), "ObjectChunkDeepEquals.java"); + Assert.eqTrue(objectChunkEqualsFileName.renameTo(objectChunkIdentifyEqualsFileName), + "objectChunkEqualsFileName.renameTo(objectChunkIdentifyEqualsFileName)"); + + { + List lines = FileUtils.readLines(objectChunkIdentifyEqualsFileName, Charset.defaultCharset()); + lines = addImport(lines, Objects.class); + FileUtils.writeLines(objectChunkIdentifyEqualsFileName, simpleFixup(fixupChunkAttributes(lines), + "name", "ObjectChunkEquals", "ObjectChunkDeepEquals")); + } + { + final List lines = FileUtils.readLines(objectChunkIdentifyEqualsFileName, Charset.defaultCharset()); + FileUtils.writeLines(objectChunkIdentifyEqualsFileName, simpleFixup(lines, + "eq", "lhs == rhs", "Objects.deepEquals(lhs, rhs)")); + } + + } + private static void fixupDoubleChunkEquals(String doublePath) throws IOException { final File objectFile = new File(doublePath); final List lines = FileUtils.readLines(objectFile, Charset.defaultCharset()); From 91c29903a9936f5cced47cb921606b5707acee1d Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 29 May 2024 17:15:37 -0700 Subject: [PATCH 38/53] Move LongRepeaterImpl to LongMixin --- .../io/deephaven/json/jackson/LongMixin.java | 35 +++++++++++++- .../json/jackson/LongRepeaterImpl.java | 48 ------------------- .../json/jackson/RepeaterProcessorBase.java | 12 ++--- 3 files changed, 40 insertions(+), 55 deletions(-) delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java index a9125f1abad..d408438deea 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java @@ -5,11 +5,14 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableLongChunk; +import io.deephaven.chunk.sized.SizedLongChunk; import io.deephaven.json.LongValue; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; import java.io.IOException; +import java.util.Arrays; import java.util.List; import java.util.stream.Stream; @@ -62,7 +65,37 @@ public long parseMissing(JsonParser parser) throws IOException { @Override RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new LongRepeaterImpl(this, allowMissing, allowNull, Type.longType().arrayType()); + return new LongRepeaterImpl(allowMissing, allowNull); + } + + final class LongRepeaterImpl extends RepeaterProcessorBase { + private final SizedLongChunk chunk = new SizedLongChunk<>(0); + + public LongRepeaterImpl(boolean allowMissing, boolean allowNull) { + super(allowMissing, allowNull, null, null, Type.longType().arrayType()); + } + + @Override + public void processElementImpl(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + chunk.set(index, LongMixin.this.parseValue(parser)); + chunk.setSize(newSize); + } + + @Override + public void processElementMissingImpl(JsonParser parser, int index) throws IOException { + final int newSize = index + 1; + final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + chunk.set(index, LongMixin.this.parseMissing(parser)); + chunk.setSize(newSize); + } + + @Override + public long[] doneImpl(JsonParser parser, int length) { + final WritableLongChunk chunk = this.chunk.get(); + return Arrays.copyOfRange(chunk.array(), chunk.arrayOffset(), chunk.arrayOffset() + length); + } } private long parseFromInt(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java deleted file mode 100644 index 195a1cd201f..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongRepeaterImpl.java +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableLongChunk; -import io.deephaven.chunk.sized.SizedLongChunk; -import io.deephaven.json.jackson.LongValueProcessor.ToLong; -import io.deephaven.qst.type.GenericType; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Objects; - -final class LongRepeaterImpl extends RepeaterProcessorBase { - - private final SizedLongChunk chunk = new SizedLongChunk<>(0); - - private final ToLong toLong; - - public LongRepeaterImpl(ToLong toLong, boolean allowMissing, boolean allowNull, GenericType type) { - super(allowMissing, allowNull, null, null, type); - this.toLong = Objects.requireNonNull(toLong); - } - - @Override - public void processElementImpl(JsonParser parser, int index) throws IOException { - final int newSize = index + 1; - final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); - chunk.set(index, toLong.parseValue(parser)); - chunk.setSize(newSize); - } - - @Override - public void processElementMissingImpl(JsonParser parser, int index) throws IOException { - final int newSize = index + 1; - final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); - chunk.set(index, toLong.parseMissing(parser)); - chunk.setSize(newSize); - } - - @Override - public long[] doneImpl(JsonParser parser, int length) { - final WritableLongChunk chunk = this.chunk.get(); - return Arrays.copyOfRange(chunk.array(), chunk.arrayOffset(), chunk.arrayOffset() + length); - } -} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java index 522040afffd..4859b97155e 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java @@ -7,7 +7,7 @@ import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.json.jackson.RepeaterProcessor.Context; -import io.deephaven.qst.type.GenericType; +import io.deephaven.qst.type.NativeArrayType; import io.deephaven.qst.type.Type; import java.io.IOException; @@ -22,18 +22,18 @@ abstract class RepeaterProcessorBase implements RepeaterProcessor, Context { private final T onMissing; private final T onNull; - // Does not need to be T; consider Type.instantType().arrayType() produces long[] - private final GenericType type; + private final NativeArrayType arrayType; private WritableObjectChunk out; private int ix; - public RepeaterProcessorBase(boolean allowMissing, boolean allowNull, T onMissing, T onNull, GenericType type) { + public RepeaterProcessorBase(boolean allowMissing, boolean allowNull, T onMissing, T onNull, + NativeArrayType arrayType) { this.onMissing = onMissing; this.onNull = onNull; this.allowNull = allowNull; this.allowMissing = allowMissing; - this.type = Objects.requireNonNull(type); + this.arrayType = Objects.requireNonNull(arrayType); } public void startImpl(JsonParser parser) throws IOException {} @@ -61,7 +61,7 @@ public final int numColumns() { @Override public Stream> columnTypes() { - return Stream.of(type); + return Stream.of(arrayType); } @Override From 1526eb4a7fb7dff3e788e4c4ca60c646b9d4b720 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 29 May 2024 18:21:15 -0700 Subject: [PATCH 39/53] Remove some exceptions --- .../io/deephaven/json/jackson/AnyMixin.java | 4 +- .../io/deephaven/json/jackson/ArrayMixin.java | 6 +- .../json/jackson/BigDecimalMixin.java | 4 +- .../json/jackson/BigIntegerMixin.java | 4 +- .../io/deephaven/json/jackson/BoolMixin.java | 8 +-- .../io/deephaven/json/jackson/ByteMixin.java | 8 +-- .../io/deephaven/json/jackson/CharMixin.java | 8 +-- .../deephaven/json/jackson/DoubleMixin.java | 8 +-- .../io/deephaven/json/jackson/Exceptions.java | 49 --------------- .../io/deephaven/json/jackson/FloatMixin.java | 8 +-- .../deephaven/json/jackson/InstantMixin.java | 4 +- .../json/jackson/InstantNumberMixin.java | 4 +- .../io/deephaven/json/jackson/IntMixin.java | 8 +-- .../json/jackson/LocalDateMixin.java | 4 +- .../io/deephaven/json/jackson/LongMixin.java | 8 +-- .../java/io/deephaven/json/jackson/Mixin.java | 11 ++-- .../deephaven/json/jackson/ObjectKvMixin.java | 8 +-- .../deephaven/json/jackson/ObjectMixin.java | 5 +- .../json/jackson/RepeaterGenericImpl.java | 5 +- .../json/jackson/RepeaterProcessorBase.java | 13 +--- .../io/deephaven/json/jackson/ShortMixin.java | 8 +-- .../io/deephaven/json/jackson/SkipMixin.java | 23 ++----- .../deephaven/json/jackson/StringMixin.java | 4 +- .../io/deephaven/json/jackson/TupleMixin.java | 11 ++-- .../json/jackson/TypedObjectMixin.java | 3 +- .../json/jackson/ValueAwareException.java | 30 +++++++++ .../jackson/ValueInnerRepeaterProcessor.java | 12 +--- .../java/io/deephaven/json/IntArrayTest.java | 63 +++++++++++++++++++ .../io/deephaven/json/StringValueTest.java | 4 +- 29 files changed, 171 insertions(+), 164 deletions(-) delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Exceptions.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueAwareException.java diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java index f1020c55426..21632a4d66f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java @@ -40,8 +40,8 @@ public ValueProcessor processor(String context) { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(ToTreeNode.INSTANCE, allowMissing, allowNull, null, null, + RepeaterProcessor repeaterProcessor() { + return new RepeaterGenericImpl<>(ToTreeNode.INSTANCE, null, null, Type.ofCustom(TreeNode.class).arrayType()); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java index 35279330fdc..1e8d271173b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java @@ -48,17 +48,17 @@ private Stream> elementOutputTypes() { } private RepeaterProcessor elementRepeater() { - return element.repeaterProcessor(allowMissing(), allowNull()); + return element.repeaterProcessor(); } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + RepeaterProcessor repeaterProcessor() { // For example: // double (element()) // double[] (processor()) // double[][] (repeater()) // return new ArrayOfArrayRepeaterProcessor(allowMissing, allowNull); - return new ValueInnerRepeaterProcessor(allowMissing, allowNull, new ArrayValueProcessor()); + return new ValueInnerRepeaterProcessor(new ArrayValueProcessor()); } private class ArrayValueProcessor implements ValueProcessor { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java index 2685ac47d1c..d8372167b42 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java @@ -61,8 +61,8 @@ public BigDecimal parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null, + RepeaterProcessor repeaterProcessor() { + return new RepeaterGenericImpl<>(this, null, null, Type.ofCustom(BigDecimal.class).arrayType()); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java index 944179b3e55..545fd15fdab 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java @@ -62,8 +62,8 @@ public BigInteger parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null, + RepeaterProcessor repeaterProcessor() { + return new RepeaterGenericImpl<>(this, null, null, Type.ofCustom(BigInteger.class).arrayType()); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java index 6a1eccad2bf..fcedb1ca5c8 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java @@ -72,8 +72,8 @@ public byte parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(new ToBoolean(), allowMissing, allowNull, null, null, + RepeaterProcessor repeaterProcessor() { + return new RepeaterGenericImpl<>(new ToBoolean(), null, null, Type.booleanType().boxedType().arrayType()); } @@ -105,7 +105,7 @@ private byte parseFromString(JsonParser parser) throws IOException { if (!allowNull()) { final byte res = Parsing.parseStringAsByteBool(parser, BooleanUtils.NULL_BOOLEAN_AS_BYTE); if (res == BooleanUtils.NULL_BOOLEAN_AS_BYTE) { - throw Exceptions.notAllowed(parser, this); + throw nullNotAllowed(parser); } return res; } @@ -127,7 +127,7 @@ private Boolean parseFromStringBoolean(JsonParser parser) throws IOException { if (!allowNull()) { final Boolean result = Parsing.parseStringAsBoolean(parser, null); if (result == null) { - throw Exceptions.notAllowed(parser, this); + throw nullNotAllowed(parser); } return result; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java index 6ea27d109a9..a6a3fd75f06 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java @@ -64,15 +64,15 @@ public byte parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new ByteRepeaterImpl(allowMissing, allowNull); + RepeaterProcessor repeaterProcessor() { + return new ByteRepeaterImpl(); } final class ByteRepeaterImpl extends RepeaterProcessorBase { private final SizedByteChunk chunk = new SizedByteChunk<>(0); - public ByteRepeaterImpl(boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null, Type.byteType().arrayType()); + public ByteRepeaterImpl() { + super(null, null, Type.byteType().arrayType()); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java index fe29969a5d2..951b89e503a 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java @@ -60,15 +60,15 @@ public char parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new CharRepeaterImpl(allowMissing, allowNull); + RepeaterProcessor repeaterProcessor() { + return new CharRepeaterImpl(); } final class CharRepeaterImpl extends RepeaterProcessorBase { private final SizedCharChunk chunk = new SizedCharChunk<>(0); - public CharRepeaterImpl(boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null, Type.charType().arrayType()); + public CharRepeaterImpl() { + super(null, null, Type.charType().arrayType()); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java index 4c608d976ff..34643c57759 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java @@ -64,15 +64,15 @@ public double parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new DoubleRepeaterImpl(allowMissing, allowNull); + RepeaterProcessor repeaterProcessor() { + return new DoubleRepeaterImpl(); } final class DoubleRepeaterImpl extends RepeaterProcessorBase { private final SizedDoubleChunk chunk = new SizedDoubleChunk<>(0); - public DoubleRepeaterImpl(boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null, Type.doubleType().arrayType()); + public DoubleRepeaterImpl() { + super(null, null, Type.doubleType().arrayType()); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Exceptions.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Exceptions.java deleted file mode 100644 index 36702d59dc1..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Exceptions.java +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import com.fasterxml.jackson.core.JsonLocation; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import io.deephaven.json.Value; - -import java.io.IOException; -import java.util.Objects; - -final class Exceptions { - - static IOException notAllowed(JsonParser parser) { - return new IOException(String.format("Token '%s' not allowed", parser.currentToken())); - } - - static IOException missingNotAllowed(JsonParser parser) { - return new IOException("Missing token not allowed"); - } - - static IOException notAllowed(JsonParser parser, Mixin mixin) { - final JsonLocation location = parser.currentLocation(); - return new ValueAwareException(String.format("Token '%s' not allowed", parser.currentToken()), location, - mixin.options); - } - - public static class ValueAwareException extends JsonProcessingException { - - private final Value value; - - public ValueAwareException(String msg, JsonLocation loc, Value valueContext) { - super(msg, loc); - this.value = Objects.requireNonNull(valueContext); - } - - public ValueAwareException(String msg, JsonLocation loc, Throwable cause, Value valueContext) { - super(msg, loc, cause); - this.value = Objects.requireNonNull(valueContext); - } - - @Override - protected String getMessageSuffix() { - return " for " + value; - } - } -} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java index 6ef6a177f30..870df83b784 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java @@ -64,15 +64,15 @@ public float parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new FloatRepeaterImpl(allowMissing, allowNull); + RepeaterProcessor repeaterProcessor() { + return new FloatRepeaterImpl(); } final class FloatRepeaterImpl extends RepeaterProcessorBase { private final SizedFloatChunk chunk = new SizedFloatChunk<>(0); - public FloatRepeaterImpl(boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null, Type.floatType().arrayType()); + public FloatRepeaterImpl() { + super(null, null, Type.floatType().arrayType()); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java index f5825f0aad7..24c87db3f29 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java @@ -67,8 +67,8 @@ public long parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(new ToObjectImpl(), allowMissing, allowNull, null, null, + RepeaterProcessor repeaterProcessor() { + return new RepeaterGenericImpl<>(new ToObjectImpl(), null, null, Type.instantType().arrayType()); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java index 4a1a541fec2..be0fbe2195b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java @@ -49,8 +49,8 @@ public ValueProcessor processor(String context) { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(new ObjectImpl(), allowMissing, allowNull, null, null, + RepeaterProcessor repeaterProcessor() { + return new RepeaterGenericImpl<>(new ObjectImpl(), null, null, Type.instantType().arrayType()); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java index cbddd935a74..47651bbc8f3 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -65,15 +65,15 @@ public int parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new IntRepeaterImpl(allowMissing, allowNull); + RepeaterProcessor repeaterProcessor() { + return new IntRepeaterImpl(); } final class IntRepeaterImpl extends RepeaterProcessorBase { private final SizedIntChunk chunk = new SizedIntChunk<>(0); - public IntRepeaterImpl(boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null, Type.intType().arrayType()); + public IntRepeaterImpl() { + super(null, null, Type.intType().arrayType()); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java index 04eeec05597..8de65f18a0f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java @@ -59,8 +59,8 @@ public LocalDate parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null, + RepeaterProcessor repeaterProcessor() { + return new RepeaterGenericImpl<>(this, null, null, Type.ofCustom(LocalDate.class).arrayType()); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java index d408438deea..0f6d6009efc 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java @@ -64,15 +64,15 @@ public long parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new LongRepeaterImpl(allowMissing, allowNull); + RepeaterProcessor repeaterProcessor() { + return new LongRepeaterImpl(); } final class LongRepeaterImpl extends RepeaterProcessorBase { private final SizedLongChunk chunk = new SizedLongChunk<>(0); - public LongRepeaterImpl(boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null, Type.longType().arrayType()); + public LongRepeaterImpl() { + super(null, null, Type.longType().arrayType()); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index bd6318b621b..c3d5776f2e3 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -30,7 +30,6 @@ import io.deephaven.json.TupleValue; import io.deephaven.json.TypedObjectValue; import io.deephaven.json.Value; -import io.deephaven.json.jackson.Exceptions.ValueAwareException; import io.deephaven.processor.ObjectProcessor; import io.deephaven.qst.type.Type; @@ -158,7 +157,7 @@ public final ObjectProcessor charBufferProcessor() { abstract ValueProcessor processor(String context); - abstract RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull); + abstract RepeaterProcessor repeaterProcessor(); abstract Stream> paths(); @@ -398,7 +397,7 @@ final void checkDecimalAllowed(JsonParser parser) throws IOException { final void checkBoolAllowed(JsonParser parser) throws IOException { if (!options.allowedTypes().contains(JsonValueTypes.BOOL)) { - throw new ValueAwareException("Bool not expected", parser.currentLocation(), options); + throw new ValueAwareException("Bool not allowed", parser.currentLocation(), options); } } @@ -422,10 +421,14 @@ final void checkArrayAllowed(JsonParser parser) throws IOException { final void checkNullAllowed(JsonParser parser) throws IOException { if (!allowNull()) { - throw new ValueAwareException("Null not allowed", parser.currentLocation(), options); + throw nullNotAllowed(parser); } } + final ValueAwareException nullNotAllowed(JsonParser parser) { + return new ValueAwareException("Null not allowed", parser.currentLocation(), options); + } + final void checkMissingAllowed(JsonParser parser) throws IOException { if (!allowMissing()) { throw new ValueAwareException("Missing not allowed", parser.currentLocation(), options); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java index 299033b9567..ecbe9ec1c9f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java @@ -53,8 +53,8 @@ public ValueProcessor processor(String context) { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new ValueInnerRepeaterProcessor(allowMissing, allowNull, new ValueProcessorKvImpl()); + RepeaterProcessor repeaterProcessor() { + return new ValueInnerRepeaterProcessor(new ValueProcessorKvImpl()); } private class ValueProcessorKvImpl implements ValueProcessor { @@ -63,8 +63,8 @@ private class ValueProcessorKvImpl implements ValueProcessor { private final RepeaterProcessor valueProcessor; ValueProcessorKvImpl() { - this.keyProcessor = key.repeaterProcessor(allowMissing(), allowNull()); - this.valueProcessor = value.repeaterProcessor(allowMissing(), allowNull()); + this.keyProcessor = key.repeaterProcessor(); + this.valueProcessor = value.repeaterProcessor(); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java index 7cd6ef0eee7..17a50a5e9a7 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -9,7 +9,6 @@ import io.deephaven.json.ObjectField; import io.deephaven.json.ObjectField.RepeatedBehavior; import io.deephaven.json.ObjectValue; -import io.deephaven.json.jackson.Exceptions.ValueAwareException; import io.deephaven.json.jackson.RepeaterProcessor.Context; import io.deephaven.qst.type.Type; @@ -79,13 +78,13 @@ public ValueProcessor processor(String context, boolean isDiscriminated) { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + RepeaterProcessor repeaterProcessor() { final Map processors = new LinkedHashMap<>(options.fields().size()); int ix = 0; for (ObjectField field : options.fields()) { final Mixin opts = mixins.get(field); final int numTypes = opts.outputSize(); - final RepeaterProcessor fieldProcessor = opts.repeaterProcessor(allowMissing, allowNull); + final RepeaterProcessor fieldProcessor = opts.repeaterProcessor(); processors.put(field, fieldProcessor); ix += numTypes; } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java index de3b219086f..eb76b4e64b2 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java @@ -17,9 +17,8 @@ final class RepeaterGenericImpl extends RepeaterProcessorBase { private final ToObject toObject; private final SizedObjectChunk chunk; - public RepeaterGenericImpl(ToObject toObject, boolean allowMissing, boolean allowNull, T[] onMissing, - T[] onNull, NativeArrayType arrayType) { - super(allowMissing, allowNull, onMissing, onNull, arrayType); + public RepeaterGenericImpl(ToObject toObject, T[] onMissing, T[] onNull, NativeArrayType arrayType) { + super(onMissing, onNull, arrayType); this.toObject = Objects.requireNonNull(toObject); chunk = new SizedObjectChunk<>(0); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java index 4859b97155e..f9ecc19d752 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterProcessorBase.java @@ -17,8 +17,6 @@ abstract class RepeaterProcessorBase implements RepeaterProcessor, Context { - private final boolean allowMissing; - private final boolean allowNull; private final T onMissing; private final T onNull; @@ -27,12 +25,9 @@ abstract class RepeaterProcessorBase implements RepeaterProcessor, Context { private WritableObjectChunk out; private int ix; - public RepeaterProcessorBase(boolean allowMissing, boolean allowNull, T onMissing, T onNull, - NativeArrayType arrayType) { + public RepeaterProcessorBase(T onMissing, T onNull, NativeArrayType arrayType) { this.onMissing = onMissing; this.onNull = onNull; - this.allowNull = allowNull; - this.allowMissing = allowMissing; this.arrayType = Objects.requireNonNull(arrayType); } @@ -71,17 +66,11 @@ public final Context context() { @Override public final void processMissingRepeater(JsonParser parser) throws IOException { - if (!allowMissing) { - throw Exceptions.missingNotAllowed(parser); - } out.add(onMissing); } @Override public final void processNullRepeater(JsonParser parser) throws IOException { - if (!allowNull) { - throw Exceptions.notAllowed(parser); - } out.add(onNull); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java index 9112b4e0f53..172fbbc8894 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java @@ -64,15 +64,15 @@ public short parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new ShortRepeaterImpl(allowMissing, allowNull); + RepeaterProcessor repeaterProcessor() { + return new ShortRepeaterImpl(); } final class ShortRepeaterImpl extends RepeaterProcessorBase { private final SizedShortChunk chunk = new SizedShortChunk<>(0); - public ShortRepeaterImpl(boolean allowMissing, boolean allowNull) { - super(allowMissing, allowNull, null, null, Type.shortType().arrayType()); + public ShortRepeaterImpl() { + super(null, null, Type.shortType().arrayType()); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java index 43e0ae03f02..ff042224cc8 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java @@ -51,8 +51,8 @@ public void clearContext() { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new SkipArray(allowMissing, allowNull); + RepeaterProcessor repeaterProcessor() { + return new SkipArray(); } @Override @@ -99,13 +99,6 @@ public void processMissing(JsonParser parser) throws IOException { } private final class SkipArray implements RepeaterProcessor, Context { - private final boolean allowMissing; - private final boolean allowNull; - - public SkipArray(boolean allowMissing, boolean allowNull) { - this.allowMissing = allowMissing; - this.allowNull = allowNull; - } @Override public Context context() { @@ -113,18 +106,10 @@ public Context context() { } @Override - public void processNullRepeater(JsonParser parser) throws IOException { - if (!allowNull) { - throw Exceptions.notAllowed(parser); - } - } + public void processNullRepeater(JsonParser parser) throws IOException {} @Override - public void processMissingRepeater(JsonParser parser) throws IOException { - if (!allowMissing) { - throw Exceptions.notAllowed(parser); - } - } + public void processMissingRepeater(JsonParser parser) throws IOException {} @Override public void setContext(List> out) { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java index abbfabe5064..a5b53f7b82b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java @@ -64,8 +64,8 @@ public String parseMissing(JsonParser parser) throws IOException { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { - return new RepeaterGenericImpl<>(this, allowMissing, allowNull, null, null, Type.stringType().arrayType()); + RepeaterProcessor repeaterProcessor() { + return new RepeaterGenericImpl<>(this, null, null, Type.stringType().arrayType()); } private String parseFromString(JsonParser parser) throws IOException { diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java index c2cd6d8c11e..a520ea12703 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TupleMixin.java @@ -8,7 +8,6 @@ import com.fasterxml.jackson.core.JsonToken; import io.deephaven.json.TupleValue; import io.deephaven.json.Value; -import io.deephaven.json.jackson.Exceptions.ValueAwareException; import io.deephaven.json.jackson.RepeaterProcessor.Context; import io.deephaven.qst.type.Type; @@ -80,13 +79,13 @@ public ValueProcessor processor(String context) { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + RepeaterProcessor repeaterProcessor() { final List processors = new ArrayList<>(mixins.size()); int ix = 0; for (Entry> e : mixins.entrySet()) { final Mixin mixin = e.getValue(); final int numTypes = mixin.outputSize(); - final RepeaterProcessor processor = mixin.repeaterProcessor(allowMissing, allowNull); + final RepeaterProcessor processor = mixin.repeaterProcessor(); processors.add(processor); ix += numTypes; } @@ -194,7 +193,6 @@ public Context context() { @Override public void processNullRepeater(JsonParser parser) throws IOException { - checkNullAllowed(parser); for (RepeaterProcessor value : values) { value.processNullRepeater(parser); } @@ -202,7 +200,6 @@ public void processNullRepeater(JsonParser parser) throws IOException { @Override public void processMissingRepeater(JsonParser parser) throws IOException { - checkMissingAllowed(parser); for (RepeaterProcessor value : values) { value.processMissingRepeater(parser); } @@ -225,7 +222,7 @@ public void processElement(JsonParser parser) throws IOException { processNullTuple(parser); break; default: - throw Exceptions.notAllowed(parser); + throw unexpectedToken(parser); } } @@ -239,6 +236,7 @@ private void processTuple(JsonParser parser) throws IOException { } private void processNullTuple(JsonParser parser) throws IOException { + checkNullAllowed(parser); // Note: we are treating a null tuple the same as a tuple of null objects // null ~= [null, ..., null] for (Context context : contexts) { @@ -248,6 +246,7 @@ private void processNullTuple(JsonParser parser) throws IOException { @Override public void processElementMissing(JsonParser parser) throws IOException { + checkMissingAllowed(parser); // Note: we are treating a missing tuple the same as a tuple of missing objects (which, is technically // impossible w/ native json, but it's the semantics we are exposing). // ~= [, ..., ] diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java index 2893174976b..7cc2b748b95 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java @@ -12,7 +12,6 @@ import io.deephaven.json.ObjectValue; import io.deephaven.json.StringValue; import io.deephaven.json.TypedObjectValue; -import io.deephaven.json.jackson.Exceptions.ValueAwareException; import io.deephaven.qst.type.Type; import java.io.IOException; @@ -96,7 +95,7 @@ public ValueProcessor processor(String context) { } @Override - RepeaterProcessor repeaterProcessor(boolean allowMissing, boolean allowNull) { + RepeaterProcessor repeaterProcessor() { throw new UnsupportedOperationException(); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueAwareException.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueAwareException.java new file mode 100644 index 00000000000..aa7673adb78 --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueAwareException.java @@ -0,0 +1,30 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonProcessingException; +import io.deephaven.json.Value; + +import java.util.Objects; + +public class ValueAwareException extends JsonProcessingException { + + private final Value value; + + public ValueAwareException(String msg, JsonLocation loc, Value valueContext) { + super(msg, loc); + this.value = Objects.requireNonNull(valueContext); + } + + public ValueAwareException(String msg, JsonLocation loc, Throwable cause, Value valueContext) { + super(msg, loc, cause); + this.value = Objects.requireNonNull(valueContext); + } + + @Override + protected String getMessageSuffix() { + return " for " + value; + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java index 483aa6b6194..ad3af831ec0 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java @@ -22,8 +22,6 @@ final class ValueInnerRepeaterProcessor implements RepeaterProcessor, Context { - private final boolean allowMissing; - private final boolean allowNull; private final ValueProcessor innerProcessor; private final List> innerChunks; private final List> sizedObjectChunks; @@ -32,9 +30,7 @@ final class ValueInnerRepeaterProcessor implements RepeaterProcessor, Context { private List> out; - public ValueInnerRepeaterProcessor(boolean allowMissing, boolean allowNull, ValueProcessor innerProcessor) { - this.allowMissing = allowMissing; - this.allowNull = allowNull; + public ValueInnerRepeaterProcessor(ValueProcessor innerProcessor) { final List> innerChunks = innerProcessor.columnTypes() .map(Type::arrayType) .map(ObjectProcessor::chunkType) @@ -81,9 +77,6 @@ public Stream> columnTypes() { @Override public void processNullRepeater(JsonParser parser) throws IOException { - if (!allowNull) { - throw Exceptions.notAllowed(parser); - } for (WritableObjectChunk wc : out) { wc.add(null); } @@ -91,9 +84,6 @@ public void processNullRepeater(JsonParser parser) throws IOException { @Override public void processMissingRepeater(JsonParser parser) throws IOException { - if (!allowMissing) { - throw Exceptions.missingNotAllowed(parser); - } for (WritableObjectChunk wc : out) { wc.add(null); } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java index 3123539cefa..58c4b957a32 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/IntArrayTest.java @@ -4,9 +4,11 @@ package io.deephaven.json; import io.deephaven.chunk.ObjectChunk; +import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.util.List; import static io.deephaven.json.TestHelper.parse; import static io.deephaven.json.TestHelper.process; @@ -147,4 +149,65 @@ void quadNestedArray() throws IOException { new int[] {42, 43}} }}})); } + + @Test + void innerStrict() throws IOException { + parse(ArrayValue.standard(IntValue.strict()), List.of("", "null", "[42, 43]"), + ObjectChunk.chunkWrap(new Object[] {null, null, new int[] {42, 43}})); + try { + process(ArrayValue.standard(IntValue.strict()), "[42, null]"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Null not allowed for IntValue"); + } + } + + @Test + void arrayStrict() throws IOException { + parse(ArrayValue.strict(IntValue.standard()), "[42, null]", + ObjectChunk.chunkWrap(new Object[] {new int[] {42, QueryConstants.NULL_INT}})); + try { + process(ArrayValue.strict(IntValue.standard()), ""); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Missing not allowed for ArrayValue"); + } + try { + process(ArrayValue.strict(IntValue.standard()), "null"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Null not allowed for ArrayValue"); + } + } + + @Test + void doubleNestedArrayStrict() throws IOException { + parse(ArrayValue.strict(ArrayValue.standard(IntValue.standard())), "[null, [], [42, null]]", ObjectChunk + .chunkWrap(new Object[] {new int[][] {null, new int[] {}, new int[] {42, QueryConstants.NULL_INT}}})); + try { + process(ArrayValue.strict(ArrayValue.standard(IntValue.standard())), ""); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Missing not allowed for ArrayValue"); + } + try { + process(ArrayValue.strict(ArrayValue.standard(IntValue.standard())), "null"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Null not allowed for ArrayValue"); + } + } + + @Test + void doubleNestedInnerArrayStrict() throws IOException { + parse(ArrayValue.standard(ArrayValue.strict(IntValue.standard())), List.of("", "null", "[[], [42, null]]"), + ObjectChunk.chunkWrap(new Object[] {null, null, + new int[][] {new int[] {}, new int[] {42, QueryConstants.NULL_INT}}})); + try { + process(ArrayValue.standard(ArrayValue.strict(IntValue.standard())), "[[], [42, null], null]"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Null not allowed for ArrayValue"); + } + } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java index b3ea9b3a852..529f8c003c5 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java @@ -96,7 +96,7 @@ void standardTrue() { process(StringValue.standard(), "true"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Bool not expected"); + assertThat(e).hasMessageContaining("Bool not allowed"); } } @@ -106,7 +106,7 @@ void standardFalse() { process(StringValue.standard(), "false"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining("Bool not expected"); + assertThat(e).hasMessageContaining("Bool not allowed"); } } From 30ef9524fc74bf9d86e1e86fd4f90ac2ef709e05 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Thu, 30 May 2024 08:41:35 -0700 Subject: [PATCH 40/53] math util --- .../main/java/io/deephaven/base/MathUtil.java | 37 +++++++++++++ .../base/ringbuffer/ByteRingBuffer.java | 20 ++----- .../base/ringbuffer/CharRingBuffer.java | 20 ++----- .../base/ringbuffer/DoubleRingBuffer.java | 20 ++----- .../base/ringbuffer/FloatRingBuffer.java | 20 ++----- .../base/ringbuffer/IntRingBuffer.java | 20 ++----- .../base/ringbuffer/LongRingBuffer.java | 20 ++----- .../base/ringbuffer/ObjectRingBuffer.java | 20 ++----- .../base/ringbuffer/ShortRingBuffer.java | 20 ++----- .../java/io/deephaven/base/MathUtilTest.java | 39 ++++++++++++++ .../impl/sources/ring/RingTableTools.java | 11 ++-- .../io/deephaven/json/jackson/ByteMixin.java | 5 +- .../io/deephaven/json/jackson/CharMixin.java | 5 +- .../deephaven/json/jackson/DoubleMixin.java | 5 +- .../io/deephaven/json/jackson/FloatMixin.java | 5 +- .../io/deephaven/json/jackson/IntMixin.java | 5 +- .../io/deephaven/json/jackson/LongMixin.java | 5 +- .../java/io/deephaven/json/jackson/Maths.java | 21 -------- .../deephaven/json/jackson/ObjectMixin.java | 28 +++++----- .../json/jackson/RepeaterGenericImpl.java | 5 +- .../io/deephaven/json/jackson/ShortMixin.java | 5 +- .../json/jackson/TypedObjectMixin.java | 17 ++++-- .../jackson/ValueInnerRepeaterProcessor.java | 3 +- .../deephaven/json/TypedObjectValueTest.java | 53 +++++++++++++++++++ .../io/deephaven/json/jackson/MathsTest.java | 52 ------------------ 25 files changed, 230 insertions(+), 231 deletions(-) delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Maths.java delete mode 100644 extensions/json-jackson/src/test/java/io/deephaven/json/jackson/MathsTest.java diff --git a/Base/src/main/java/io/deephaven/base/MathUtil.java b/Base/src/main/java/io/deephaven/base/MathUtil.java index 1f07a5923ef..555409a709b 100644 --- a/Base/src/main/java/io/deephaven/base/MathUtil.java +++ b/Base/src/main/java/io/deephaven/base/MathUtil.java @@ -8,6 +8,11 @@ */ public class MathUtil { + /** + * The maximum power of 2. + */ + public static final int MAX_POWER_OF_2 = 1 << 30; + /** * Compute ceil(log2(x)). See {@link Integer#numberOfLeadingZeros(int)}. * @@ -108,4 +113,36 @@ public static int base10digits(int n) { } return base10guess; } + + /** + * Rounds up to the next power of 2 for {@code x}; if {@code x} is already a power of 2, {@code x} will be returned. + * Values outside the range {@code 1 <= x <= MAX_POWER_OF_2} will return {@code 1}. + * + *

+ * Equivalent to {@code Math.max(Integer.highestOneBit(x - 1) << 1, 1)}. + * + * @param x the value + * @return the next power of 2 for {@code x} + * @see #MAX_POWER_OF_2 + */ + public static int roundUpPowerOf2(int x) { + return Math.max(Integer.highestOneBit(x - 1) << 1, 1); + } + + /** + * Rounds up to the next power of 2 for {@code size <= MAX_POWER_OF_2}, otherwise returns + * {@link ArrayUtil#MAX_ARRAY_SIZE}. + * + *

+ * Equivalent to {@code size <= MAX_POWER_OF_2 ? roundUpPowerOf2(size) : ArrayUtil.MAX_ARRAY_SIZE}. + * + * @param size the size + * @return the + * @see #MAX_POWER_OF_2 + * @see #roundUpPowerOf2(int) + * @see ArrayUtil#MAX_ARRAY_SIZE + */ + public static int roundUpArraySize(int size) { + return size <= MAX_POWER_OF_2 ? roundUpPowerOf2(size) : ArrayUtil.MAX_ARRAY_SIZE; + } } diff --git a/Base/src/main/java/io/deephaven/base/ringbuffer/ByteRingBuffer.java b/Base/src/main/java/io/deephaven/base/ringbuffer/ByteRingBuffer.java index ee8e47041cd..aede7521309 100644 --- a/Base/src/main/java/io/deephaven/base/ringbuffer/ByteRingBuffer.java +++ b/Base/src/main/java/io/deephaven/base/ringbuffer/ByteRingBuffer.java @@ -7,7 +7,7 @@ // @formatter:off package io.deephaven.base.ringbuffer; -import io.deephaven.base.ArrayUtil; +import io.deephaven.base.MathUtil; import io.deephaven.base.verify.Assert; import java.io.Serializable; @@ -20,8 +20,6 @@ * determination of storage indices through a mask operation. */ public class ByteRingBuffer implements Serializable { - /** Maximum capacity is the highest power of two that can be allocated (i.e. <= than ArrayUtil.MAX_ARRAY_SIZE). */ - static final int RING_BUFFER_MAX_CAPACITY = Integer.highestOneBit(ArrayUtil.MAX_ARRAY_SIZE); static final long FIXUP_THRESHOLD = 1L << 62; final boolean growable; byte[] storage; @@ -45,21 +43,13 @@ public ByteRingBuffer(int capacity) { * @param growable whether to allow growth when the buffer is full. */ public ByteRingBuffer(int capacity, boolean growable) { - Assert.leq(capacity, "ByteRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(capacity, "ByteRingBuffer capacity", MathUtil.MAX_POWER_OF_2); this.growable = growable; // use next larger power of 2 for our storage - final int newCapacity; - if (capacity < 2) { - // sensibly handle the size=0 and size=1 cases - newCapacity = 1; - } else { - newCapacity = Integer.highestOneBit(capacity - 1) << 1; - } - // reset the data structure members - storage = new byte[newCapacity]; + storage = new byte[MathUtil.roundUpPowerOf2(capacity)]; mask = storage.length - 1; tail = head = 0; } @@ -73,9 +63,9 @@ protected void grow(int increase) { final int size = size(); final long newCapacity = (long) storage.length + increase; // assert that we are not asking for the impossible - Assert.leq(newCapacity, "ByteRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(newCapacity, "ByteRingBuffer capacity", MathUtil.MAX_POWER_OF_2); - final byte[] newStorage = new byte[Integer.highestOneBit((int) newCapacity - 1) << 1]; + final byte[] newStorage = new byte[MathUtil.roundUpPowerOf2((int) newCapacity)]; // move the current data to the new buffer copyRingBufferToArray(newStorage); diff --git a/Base/src/main/java/io/deephaven/base/ringbuffer/CharRingBuffer.java b/Base/src/main/java/io/deephaven/base/ringbuffer/CharRingBuffer.java index a2d934e9537..68c58a866e7 100644 --- a/Base/src/main/java/io/deephaven/base/ringbuffer/CharRingBuffer.java +++ b/Base/src/main/java/io/deephaven/base/ringbuffer/CharRingBuffer.java @@ -3,7 +3,7 @@ // package io.deephaven.base.ringbuffer; -import io.deephaven.base.ArrayUtil; +import io.deephaven.base.MathUtil; import io.deephaven.base.verify.Assert; import java.io.Serializable; @@ -16,8 +16,6 @@ * determination of storage indices through a mask operation. */ public class CharRingBuffer implements Serializable { - /** Maximum capacity is the highest power of two that can be allocated (i.e. <= than ArrayUtil.MAX_ARRAY_SIZE). */ - static final int RING_BUFFER_MAX_CAPACITY = Integer.highestOneBit(ArrayUtil.MAX_ARRAY_SIZE); static final long FIXUP_THRESHOLD = 1L << 62; final boolean growable; char[] storage; @@ -41,21 +39,13 @@ public CharRingBuffer(int capacity) { * @param growable whether to allow growth when the buffer is full. */ public CharRingBuffer(int capacity, boolean growable) { - Assert.leq(capacity, "CharRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(capacity, "CharRingBuffer capacity", MathUtil.MAX_POWER_OF_2); this.growable = growable; // use next larger power of 2 for our storage - final int newCapacity; - if (capacity < 2) { - // sensibly handle the size=0 and size=1 cases - newCapacity = 1; - } else { - newCapacity = Integer.highestOneBit(capacity - 1) << 1; - } - // reset the data structure members - storage = new char[newCapacity]; + storage = new char[MathUtil.roundUpPowerOf2(capacity)]; mask = storage.length - 1; tail = head = 0; } @@ -69,9 +59,9 @@ protected void grow(int increase) { final int size = size(); final long newCapacity = (long) storage.length + increase; // assert that we are not asking for the impossible - Assert.leq(newCapacity, "CharRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(newCapacity, "CharRingBuffer capacity", MathUtil.MAX_POWER_OF_2); - final char[] newStorage = new char[Integer.highestOneBit((int) newCapacity - 1) << 1]; + final char[] newStorage = new char[MathUtil.roundUpPowerOf2((int) newCapacity)]; // move the current data to the new buffer copyRingBufferToArray(newStorage); diff --git a/Base/src/main/java/io/deephaven/base/ringbuffer/DoubleRingBuffer.java b/Base/src/main/java/io/deephaven/base/ringbuffer/DoubleRingBuffer.java index 3f0f5cfbe35..8e942f8937a 100644 --- a/Base/src/main/java/io/deephaven/base/ringbuffer/DoubleRingBuffer.java +++ b/Base/src/main/java/io/deephaven/base/ringbuffer/DoubleRingBuffer.java @@ -7,7 +7,7 @@ // @formatter:off package io.deephaven.base.ringbuffer; -import io.deephaven.base.ArrayUtil; +import io.deephaven.base.MathUtil; import io.deephaven.base.verify.Assert; import java.io.Serializable; @@ -20,8 +20,6 @@ * determination of storage indices through a mask operation. */ public class DoubleRingBuffer implements Serializable { - /** Maximum capacity is the highest power of two that can be allocated (i.e. <= than ArrayUtil.MAX_ARRAY_SIZE). */ - static final int RING_BUFFER_MAX_CAPACITY = Integer.highestOneBit(ArrayUtil.MAX_ARRAY_SIZE); static final long FIXUP_THRESHOLD = 1L << 62; final boolean growable; double[] storage; @@ -45,21 +43,13 @@ public DoubleRingBuffer(int capacity) { * @param growable whether to allow growth when the buffer is full. */ public DoubleRingBuffer(int capacity, boolean growable) { - Assert.leq(capacity, "DoubleRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(capacity, "DoubleRingBuffer capacity", MathUtil.MAX_POWER_OF_2); this.growable = growable; // use next larger power of 2 for our storage - final int newCapacity; - if (capacity < 2) { - // sensibly handle the size=0 and size=1 cases - newCapacity = 1; - } else { - newCapacity = Integer.highestOneBit(capacity - 1) << 1; - } - // reset the data structure members - storage = new double[newCapacity]; + storage = new double[MathUtil.roundUpPowerOf2(capacity)]; mask = storage.length - 1; tail = head = 0; } @@ -73,9 +63,9 @@ protected void grow(int increase) { final int size = size(); final long newCapacity = (long) storage.length + increase; // assert that we are not asking for the impossible - Assert.leq(newCapacity, "DoubleRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(newCapacity, "DoubleRingBuffer capacity", MathUtil.MAX_POWER_OF_2); - final double[] newStorage = new double[Integer.highestOneBit((int) newCapacity - 1) << 1]; + final double[] newStorage = new double[MathUtil.roundUpPowerOf2((int) newCapacity)]; // move the current data to the new buffer copyRingBufferToArray(newStorage); diff --git a/Base/src/main/java/io/deephaven/base/ringbuffer/FloatRingBuffer.java b/Base/src/main/java/io/deephaven/base/ringbuffer/FloatRingBuffer.java index 70cbded55ac..16c8751c447 100644 --- a/Base/src/main/java/io/deephaven/base/ringbuffer/FloatRingBuffer.java +++ b/Base/src/main/java/io/deephaven/base/ringbuffer/FloatRingBuffer.java @@ -7,7 +7,7 @@ // @formatter:off package io.deephaven.base.ringbuffer; -import io.deephaven.base.ArrayUtil; +import io.deephaven.base.MathUtil; import io.deephaven.base.verify.Assert; import java.io.Serializable; @@ -20,8 +20,6 @@ * determination of storage indices through a mask operation. */ public class FloatRingBuffer implements Serializable { - /** Maximum capacity is the highest power of two that can be allocated (i.e. <= than ArrayUtil.MAX_ARRAY_SIZE). */ - static final int RING_BUFFER_MAX_CAPACITY = Integer.highestOneBit(ArrayUtil.MAX_ARRAY_SIZE); static final long FIXUP_THRESHOLD = 1L << 62; final boolean growable; float[] storage; @@ -45,21 +43,13 @@ public FloatRingBuffer(int capacity) { * @param growable whether to allow growth when the buffer is full. */ public FloatRingBuffer(int capacity, boolean growable) { - Assert.leq(capacity, "FloatRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(capacity, "FloatRingBuffer capacity", MathUtil.MAX_POWER_OF_2); this.growable = growable; // use next larger power of 2 for our storage - final int newCapacity; - if (capacity < 2) { - // sensibly handle the size=0 and size=1 cases - newCapacity = 1; - } else { - newCapacity = Integer.highestOneBit(capacity - 1) << 1; - } - // reset the data structure members - storage = new float[newCapacity]; + storage = new float[MathUtil.roundUpPowerOf2(capacity)]; mask = storage.length - 1; tail = head = 0; } @@ -73,9 +63,9 @@ protected void grow(int increase) { final int size = size(); final long newCapacity = (long) storage.length + increase; // assert that we are not asking for the impossible - Assert.leq(newCapacity, "FloatRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(newCapacity, "FloatRingBuffer capacity", MathUtil.MAX_POWER_OF_2); - final float[] newStorage = new float[Integer.highestOneBit((int) newCapacity - 1) << 1]; + final float[] newStorage = new float[MathUtil.roundUpPowerOf2((int) newCapacity)]; // move the current data to the new buffer copyRingBufferToArray(newStorage); diff --git a/Base/src/main/java/io/deephaven/base/ringbuffer/IntRingBuffer.java b/Base/src/main/java/io/deephaven/base/ringbuffer/IntRingBuffer.java index 590014fa926..80a47f0a389 100644 --- a/Base/src/main/java/io/deephaven/base/ringbuffer/IntRingBuffer.java +++ b/Base/src/main/java/io/deephaven/base/ringbuffer/IntRingBuffer.java @@ -7,7 +7,7 @@ // @formatter:off package io.deephaven.base.ringbuffer; -import io.deephaven.base.ArrayUtil; +import io.deephaven.base.MathUtil; import io.deephaven.base.verify.Assert; import java.io.Serializable; @@ -20,8 +20,6 @@ * determination of storage indices through a mask operation. */ public class IntRingBuffer implements Serializable { - /** Maximum capacity is the highest power of two that can be allocated (i.e. <= than ArrayUtil.MAX_ARRAY_SIZE). */ - static final int RING_BUFFER_MAX_CAPACITY = Integer.highestOneBit(ArrayUtil.MAX_ARRAY_SIZE); static final long FIXUP_THRESHOLD = 1L << 62; final boolean growable; int[] storage; @@ -45,21 +43,13 @@ public IntRingBuffer(int capacity) { * @param growable whether to allow growth when the buffer is full. */ public IntRingBuffer(int capacity, boolean growable) { - Assert.leq(capacity, "IntRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(capacity, "IntRingBuffer capacity", MathUtil.MAX_POWER_OF_2); this.growable = growable; // use next larger power of 2 for our storage - final int newCapacity; - if (capacity < 2) { - // sensibly handle the size=0 and size=1 cases - newCapacity = 1; - } else { - newCapacity = Integer.highestOneBit(capacity - 1) << 1; - } - // reset the data structure members - storage = new int[newCapacity]; + storage = new int[MathUtil.roundUpPowerOf2(capacity)]; mask = storage.length - 1; tail = head = 0; } @@ -73,9 +63,9 @@ protected void grow(int increase) { final int size = size(); final long newCapacity = (long) storage.length + increase; // assert that we are not asking for the impossible - Assert.leq(newCapacity, "IntRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(newCapacity, "IntRingBuffer capacity", MathUtil.MAX_POWER_OF_2); - final int[] newStorage = new int[Integer.highestOneBit((int) newCapacity - 1) << 1]; + final int[] newStorage = new int[MathUtil.roundUpPowerOf2((int) newCapacity)]; // move the current data to the new buffer copyRingBufferToArray(newStorage); diff --git a/Base/src/main/java/io/deephaven/base/ringbuffer/LongRingBuffer.java b/Base/src/main/java/io/deephaven/base/ringbuffer/LongRingBuffer.java index 49a3203860a..de5f8df9c90 100644 --- a/Base/src/main/java/io/deephaven/base/ringbuffer/LongRingBuffer.java +++ b/Base/src/main/java/io/deephaven/base/ringbuffer/LongRingBuffer.java @@ -7,7 +7,7 @@ // @formatter:off package io.deephaven.base.ringbuffer; -import io.deephaven.base.ArrayUtil; +import io.deephaven.base.MathUtil; import io.deephaven.base.verify.Assert; import java.io.Serializable; @@ -20,8 +20,6 @@ * determination of storage indices through a mask operation. */ public class LongRingBuffer implements Serializable { - /** Maximum capacity is the highest power of two that can be allocated (i.e. <= than ArrayUtil.MAX_ARRAY_SIZE). */ - static final int RING_BUFFER_MAX_CAPACITY = Integer.highestOneBit(ArrayUtil.MAX_ARRAY_SIZE); static final long FIXUP_THRESHOLD = 1L << 62; final boolean growable; long[] storage; @@ -45,21 +43,13 @@ public LongRingBuffer(int capacity) { * @param growable whether to allow growth when the buffer is full. */ public LongRingBuffer(int capacity, boolean growable) { - Assert.leq(capacity, "LongRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(capacity, "LongRingBuffer capacity", MathUtil.MAX_POWER_OF_2); this.growable = growable; // use next larger power of 2 for our storage - final int newCapacity; - if (capacity < 2) { - // sensibly handle the size=0 and size=1 cases - newCapacity = 1; - } else { - newCapacity = Integer.highestOneBit(capacity - 1) << 1; - } - // reset the data structure members - storage = new long[newCapacity]; + storage = new long[MathUtil.roundUpPowerOf2(capacity)]; mask = storage.length - 1; tail = head = 0; } @@ -73,9 +63,9 @@ protected void grow(int increase) { final int size = size(); final long newCapacity = (long) storage.length + increase; // assert that we are not asking for the impossible - Assert.leq(newCapacity, "LongRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(newCapacity, "LongRingBuffer capacity", MathUtil.MAX_POWER_OF_2); - final long[] newStorage = new long[Integer.highestOneBit((int) newCapacity - 1) << 1]; + final long[] newStorage = new long[MathUtil.roundUpPowerOf2((int) newCapacity)]; // move the current data to the new buffer copyRingBufferToArray(newStorage); diff --git a/Base/src/main/java/io/deephaven/base/ringbuffer/ObjectRingBuffer.java b/Base/src/main/java/io/deephaven/base/ringbuffer/ObjectRingBuffer.java index e65291e23a6..ad93ddd1f43 100644 --- a/Base/src/main/java/io/deephaven/base/ringbuffer/ObjectRingBuffer.java +++ b/Base/src/main/java/io/deephaven/base/ringbuffer/ObjectRingBuffer.java @@ -9,7 +9,7 @@ import java.util.Arrays; -import io.deephaven.base.ArrayUtil; +import io.deephaven.base.MathUtil; import io.deephaven.base.verify.Assert; import java.io.Serializable; @@ -22,8 +22,6 @@ * determination of storage indices through a mask operation. */ public class ObjectRingBuffer implements Serializable { - /** Maximum capacity is the highest power of two that can be allocated (i.e. <= than ArrayUtil.MAX_ARRAY_SIZE). */ - static final int RING_BUFFER_MAX_CAPACITY = Integer.highestOneBit(ArrayUtil.MAX_ARRAY_SIZE); static final long FIXUP_THRESHOLD = 1L << 62; final boolean growable; T[] storage; @@ -47,21 +45,13 @@ public ObjectRingBuffer(int capacity) { * @param growable whether to allow growth when the buffer is full. */ public ObjectRingBuffer(int capacity, boolean growable) { - Assert.leq(capacity, "ObjectRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(capacity, "ObjectRingBuffer capacity", MathUtil.MAX_POWER_OF_2); this.growable = growable; // use next larger power of 2 for our storage - final int newCapacity; - if (capacity < 2) { - // sensibly handle the size=0 and size=1 cases - newCapacity = 1; - } else { - newCapacity = Integer.highestOneBit(capacity - 1) << 1; - } - // reset the data structure members - storage = (T[]) new Object[newCapacity]; + storage = (T[]) new Object[MathUtil.roundUpPowerOf2(capacity)]; mask = storage.length - 1; tail = head = 0; } @@ -75,9 +65,9 @@ protected void grow(int increase) { final int size = size(); final long newCapacity = (long) storage.length + increase; // assert that we are not asking for the impossible - Assert.leq(newCapacity, "ObjectRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(newCapacity, "ObjectRingBuffer capacity", MathUtil.MAX_POWER_OF_2); - final T[] newStorage = (T[]) new Object[Integer.highestOneBit((int) newCapacity - 1) << 1]; + final T[] newStorage = (T[]) new Object[MathUtil.roundUpPowerOf2((int) newCapacity)]; // move the current data to the new buffer copyRingBufferToArray(newStorage); diff --git a/Base/src/main/java/io/deephaven/base/ringbuffer/ShortRingBuffer.java b/Base/src/main/java/io/deephaven/base/ringbuffer/ShortRingBuffer.java index d074fab3431..89619fb839c 100644 --- a/Base/src/main/java/io/deephaven/base/ringbuffer/ShortRingBuffer.java +++ b/Base/src/main/java/io/deephaven/base/ringbuffer/ShortRingBuffer.java @@ -7,7 +7,7 @@ // @formatter:off package io.deephaven.base.ringbuffer; -import io.deephaven.base.ArrayUtil; +import io.deephaven.base.MathUtil; import io.deephaven.base.verify.Assert; import java.io.Serializable; @@ -20,8 +20,6 @@ * determination of storage indices through a mask operation. */ public class ShortRingBuffer implements Serializable { - /** Maximum capacity is the highest power of two that can be allocated (i.e. <= than ArrayUtil.MAX_ARRAY_SIZE). */ - static final int RING_BUFFER_MAX_CAPACITY = Integer.highestOneBit(ArrayUtil.MAX_ARRAY_SIZE); static final long FIXUP_THRESHOLD = 1L << 62; final boolean growable; short[] storage; @@ -45,21 +43,13 @@ public ShortRingBuffer(int capacity) { * @param growable whether to allow growth when the buffer is full. */ public ShortRingBuffer(int capacity, boolean growable) { - Assert.leq(capacity, "ShortRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(capacity, "ShortRingBuffer capacity", MathUtil.MAX_POWER_OF_2); this.growable = growable; // use next larger power of 2 for our storage - final int newCapacity; - if (capacity < 2) { - // sensibly handle the size=0 and size=1 cases - newCapacity = 1; - } else { - newCapacity = Integer.highestOneBit(capacity - 1) << 1; - } - // reset the data structure members - storage = new short[newCapacity]; + storage = new short[MathUtil.roundUpPowerOf2(capacity)]; mask = storage.length - 1; tail = head = 0; } @@ -73,9 +63,9 @@ protected void grow(int increase) { final int size = size(); final long newCapacity = (long) storage.length + increase; // assert that we are not asking for the impossible - Assert.leq(newCapacity, "ShortRingBuffer capacity", RING_BUFFER_MAX_CAPACITY); + Assert.leq(newCapacity, "ShortRingBuffer capacity", MathUtil.MAX_POWER_OF_2); - final short[] newStorage = new short[Integer.highestOneBit((int) newCapacity - 1) << 1]; + final short[] newStorage = new short[MathUtil.roundUpPowerOf2((int) newCapacity)]; // move the current data to the new buffer copyRingBufferToArray(newStorage); diff --git a/Base/src/test/java/io/deephaven/base/MathUtilTest.java b/Base/src/test/java/io/deephaven/base/MathUtilTest.java index 2f9833d96a1..b29cc79d3ca 100644 --- a/Base/src/test/java/io/deephaven/base/MathUtilTest.java +++ b/Base/src/test/java/io/deephaven/base/MathUtilTest.java @@ -24,4 +24,43 @@ public void check(int a, int b, int expect) { assertEquals(expect, MathUtil.gcd(-a, -b)); assertEquals(expect, MathUtil.gcd(-b, -a)); } + + public void testRoundUpPowerOf2() { + pow2(0, 1); + pow2(1, 1); + pow2(2, 2); + for (int i = 2; i < 31; ++i) { + final int pow2 = 1 << i; + pow2(pow2, pow2); + pow2(pow2 - 1, pow2); + if (i < 30) { + pow2(pow2 + 1, pow2 * 2); + } + } + } + + public void testRoundUpArraySize() { + arraySize(0, 1); + arraySize(1, 1); + arraySize(2, 2); + for (int i = 2; i < 31; ++i) { + final int pow2 = 1 << i; + arraySize(pow2, pow2); + arraySize(pow2 - 1, pow2); + if (i < 30) { + arraySize(pow2 + 1, pow2 * 2); + } else { + arraySize(pow2 + 1, ArrayUtil.MAX_ARRAY_SIZE); + } + } + arraySize(Integer.MAX_VALUE, ArrayUtil.MAX_ARRAY_SIZE); + } + + public static void pow2(int newSize, int expectedSize) { + assertEquals(MathUtil.roundUpPowerOf2(newSize), expectedSize); + } + + public static void arraySize(int newSize, int expectedSize) { + assertEquals(MathUtil.roundUpArraySize(newSize), expectedSize); + } } diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/sources/ring/RingTableTools.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/sources/ring/RingTableTools.java index 80bd1be94d7..45495357d15 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/sources/ring/RingTableTools.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/sources/ring/RingTableTools.java @@ -3,6 +3,9 @@ // package io.deephaven.engine.table.impl.sources.ring; +import io.deephaven.base.ArrayUtil; +import io.deephaven.base.MathUtil; +import io.deephaven.base.verify.Require; import io.deephaven.engine.context.ExecutionContext; import io.deephaven.engine.table.Table; import io.deephaven.engine.table.TableUpdate; @@ -43,6 +46,7 @@ public static Table of(Table parent, int capacity) { * @return the ring table */ public static Table of(Table parent, int capacity, boolean initialize) { + Require.leq(capacity, "capacity", ArrayUtil.MAX_ARRAY_SIZE); return QueryPerformanceRecorder.withNugget("RingTableTools.of", () -> { final BaseTable baseTable = (BaseTable) parent.coalesce(); final OperationSnapshotControl snapshotControl = @@ -56,7 +60,7 @@ public static Table of(Table parent, int capacity, boolean initialize) { * re-indexed, with an additional {@link Table#tail(long)} to restructure for {@code capacity}. * *

- * Logically equivalent to {@code of(parent, Integer.highestOneBit(capacity - 1) << 1, initialize).tail(capacity)}. + * Logically equivalent to {@code of(parent, MathUtil.roundUpPowerOf2(capacity), initialize).tail(capacity)}. * *

* This setup may be useful when consumers need to maximize random access fill speed from a ring table. @@ -66,11 +70,12 @@ public static Table of(Table parent, int capacity, boolean initialize) { * @param initialize if the resulting table should source initial data from the snapshot of {@code parent} * @return the ring table * @see #of(Table, int, boolean) + * @see MathUtil#roundUpPowerOf2(int) */ public static Table of2(Table parent, int capacity, boolean initialize) { + Require.leq(capacity, "capacity", MathUtil.MAX_POWER_OF_2); return QueryPerformanceRecorder.withNugget("RingTableTools.of2", () -> { - // todo: there is probably a better way to do this - final int capacityPowerOf2 = capacity == 1 ? 1 : Integer.highestOneBit(capacity - 1) << 1; + final int capacityPowerOf2 = MathUtil.roundUpPowerOf2(capacity); final BaseTable baseTable = (BaseTable) parent.coalesce(); final OperationSnapshotControl snapshotControl = baseTable.createSnapshotControlIfRefreshing(OperationSnapshotControl::new); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java index a6a3fd75f06..81158a8b839 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.MathUtil; import io.deephaven.chunk.WritableByteChunk; import io.deephaven.chunk.sized.SizedByteChunk; import io.deephaven.json.ByteValue; @@ -78,7 +79,7 @@ public ByteRepeaterImpl() { @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, ByteMixin.this.parseValue(parser)); chunk.setSize(newSize); } @@ -86,7 +87,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableByteChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, ByteMixin.this.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java index 951b89e503a..de30afa9969 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.MathUtil; import io.deephaven.chunk.WritableCharChunk; import io.deephaven.chunk.sized.SizedCharChunk; import io.deephaven.json.CharValue; @@ -74,7 +75,7 @@ public CharRepeaterImpl() { @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, CharMixin.this.parseValue(parser)); chunk.setSize(newSize); } @@ -82,7 +83,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableCharChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, CharMixin.this.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java index 34643c57759..a4c49a53108 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.MathUtil; import io.deephaven.chunk.WritableDoubleChunk; import io.deephaven.chunk.sized.SizedDoubleChunk; import io.deephaven.json.DoubleValue; @@ -78,7 +79,7 @@ public DoubleRepeaterImpl() { @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, DoubleMixin.this.parseValue(parser)); chunk.setSize(newSize); } @@ -86,7 +87,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableDoubleChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, DoubleMixin.this.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java index 870df83b784..76de0bdae35 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.MathUtil; import io.deephaven.chunk.WritableFloatChunk; import io.deephaven.chunk.sized.SizedFloatChunk; import io.deephaven.json.FloatValue; @@ -78,7 +79,7 @@ public FloatRepeaterImpl() { @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, FloatMixin.this.parseValue(parser)); chunk.setSize(newSize); } @@ -86,7 +87,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableFloatChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, FloatMixin.this.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java index 47651bbc8f3..7bae0c7532d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.MathUtil; import io.deephaven.chunk.WritableIntChunk; import io.deephaven.chunk.sized.SizedIntChunk; import io.deephaven.json.IntValue; @@ -79,7 +80,7 @@ public IntRepeaterImpl() { @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, IntMixin.this.parseValue(parser)); chunk.setSize(newSize); } @@ -87,7 +88,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableIntChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, IntMixin.this.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java index 0f6d6009efc..97d6ccb68ad 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.MathUtil; import io.deephaven.chunk.WritableLongChunk; import io.deephaven.chunk.sized.SizedLongChunk; import io.deephaven.json.LongValue; @@ -78,7 +79,7 @@ public LongRepeaterImpl() { @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, LongMixin.this.parseValue(parser)); chunk.setSize(newSize); } @@ -86,7 +87,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableLongChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, LongMixin.this.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Maths.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Maths.java deleted file mode 100644 index fd62bd59beb..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Maths.java +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import io.deephaven.base.ArrayUtil; - -final class Maths { - - static final int MAX_POWER_OF_2 = 1 << 30; - - static int nextPowerOf2(int newSize) { - return Math.max(Integer.highestOneBit(newSize - 1) << 1, 1); - } - - static int nextArrayCapacity(int newSize) { - return newSize <= MAX_POWER_OF_2 - ? nextPowerOf2(newSize) - : ArrayUtil.MAX_ARRAY_SIZE; - } -} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java index 17a50a5e9a7..ea14baf40bf 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectMixin.java @@ -62,10 +62,11 @@ public ValueProcessor processor(String context) { } public ValueProcessor processor(String context, boolean isDiscriminated) { - final Map processors = new LinkedHashMap<>(options.fields().size()); + final Map processors = new LinkedHashMap<>(mixins.size()); int ix = 0; - for (ObjectField field : options.fields()) { - final Mixin opts = mixins.get(field); + for (Entry> e : mixins.entrySet()) { + final ObjectField field = e.getKey(); + final Mixin opts = e.getValue(); final int numTypes = opts.outputSize(); final ValueProcessor fieldProcessor = opts.processor(context + "/" + field.name()); processors.put(field, fieldProcessor); @@ -79,10 +80,11 @@ public ValueProcessor processor(String context, boolean isDiscriminated) { @Override RepeaterProcessor repeaterProcessor() { - final Map processors = new LinkedHashMap<>(options.fields().size()); + final Map processors = new LinkedHashMap<>(mixins.size()); int ix = 0; - for (ObjectField field : options.fields()) { - final Mixin opts = mixins.get(field); + for (Entry> e : mixins.entrySet()) { + final ObjectField field = e.getKey(); + final Mixin opts = e.getValue(); final int numTypes = opts.outputSize(); final RepeaterProcessor fieldProcessor = opts.repeaterProcessor(); processors.put(field, fieldProcessor); @@ -347,26 +349,22 @@ public void start(JsonParser parser) throws IOException { @Override public void processElement(JsonParser parser) throws IOException { - // see - // com.fasterxml.jackson.databind.JsonDeserializer.deserialize(com.fasterxml.jackson.core.JsonParser, - // com.fasterxml.jackson.databind.DeserializationContext) - // for notes on FIELD_NAME + // Not supporting TypedObjectValue array (TypedObjectMixin#repeaterProcessor) yet, so don't need + // discrimination support here. switch (parser.currentToken()) { case START_OBJECT: if (parser.nextToken() == JsonToken.END_OBJECT) { processEmptyObject(parser); - break; + return; } if (!parser.hasToken(JsonToken.FIELD_NAME)) { throw new IllegalStateException(); } - // fall-through - case FIELD_NAME: processObjectFields(parser); - break; + return; case VALUE_NULL: processNullObject(parser); - break; + return; default: throw unexpectedToken(parser); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java index eb76b4e64b2..aeb018b40b0 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java @@ -4,6 +4,7 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.MathUtil; import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.chunk.sized.SizedObjectChunk; import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; @@ -26,7 +27,7 @@ public RepeaterGenericImpl(ToObject toObject, T[] onMissing, T[] onNull, Nati @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, toObject.parseValue(parser)); chunk.setSize(newSize); } @@ -34,7 +35,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableObjectChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, toObject.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java index 172fbbc8894..9ef5624bbab 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.MathUtil; import io.deephaven.chunk.WritableShortChunk; import io.deephaven.chunk.sized.SizedShortChunk; import io.deephaven.json.ShortValue; @@ -78,7 +79,7 @@ public ShortRepeaterImpl() { @Override public void processElementImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, ShortMixin.this.parseValue(parser)); chunk.setSize(newSize); } @@ -86,7 +87,7 @@ public void processElementImpl(JsonParser parser, int index) throws IOException @Override public void processElementMissingImpl(JsonParser parser, int index) throws IOException { final int newSize = index + 1; - final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + final WritableShortChunk chunk = this.chunk.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); chunk.set(index, ShortMixin.this.parseMissing(parser)); chunk.setSize(newSize); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java index 7cc2b748b95..b2dcfed813b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java @@ -150,11 +150,11 @@ private static class Processor { this.buffer = new ArrayList<>(combinedProcessor.numColumns()); } - void setContext(List> sharedOut, List> specifiedOut) { - this.specificOut = Objects.requireNonNull(specifiedOut); + void setContext(List> sharedOut, List> specificOut) { + this.specificOut = Objects.requireNonNull(specificOut); buffer.clear(); buffer.addAll(sharedOut); - buffer.addAll(specifiedOut); + buffer.addAll(specificOut); combinedProcessor.setContext(buffer); } @@ -245,6 +245,10 @@ public void processCurrentValue(JsonParser parser) throws IOException { public void processMissing(JsonParser parser) throws IOException { checkMissingAllowed(parser); typeChunk.add(options.onMissing().orElse(null)); + // We are _not_ trying to pass along the potential "on missing" value for each individual chunk; the + // individual columns may have "allowMissing = false", but as a higher level of control with + // TypedObjectValue, we have already verified that we want to allow missing. As such, the discriminating + // factor will be the typeChunk. for (WritableChunk sharedChunk : sharedChunks) { addNullValue(sharedChunk); } @@ -256,6 +260,10 @@ public void processMissing(JsonParser parser) throws IOException { private void processNullObject(JsonParser parser) throws IOException { checkNullAllowed(parser); typeChunk.add(options.onNull().orElse(null)); + // We are _not_ trying to pass along the potential "on null" value for each individual chunk; the + // individual columns may have "allowNull = false", but as a higher level of control with + // TypedObjectValue, we have already verified that we want to allow null. As such, the discriminating + // factor will be the typeChunk. for (WritableChunk sharedChunk : sharedChunks) { addNullValue(sharedChunk); } @@ -276,6 +284,9 @@ private void processObjectFields(JsonParser parser) throws IOException { for (Entry e : combinedProcessors.entrySet()) { final String processorType = e.getKey(); final Processor processor = e.getValue(); + // Note: we are not supporting case-insensitive _value_ matching at this point in time. We do allow the + // field _names_ to be case insensitive (see ObjectField#caseSensitive). + // See io.deephaven.json.TypedObjectValueTest#caseSensitiveDiscriminator if (processorType.equals(typeFieldValue)) { processor.combinedProcessor().processCurrentValue(parser); foundProcessor = true; diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java index ad3af831ec0..589909e80da 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueInnerRepeaterProcessor.java @@ -4,6 +4,7 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.base.MathUtil; import io.deephaven.chunk.ChunkType; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableObjectChunk; @@ -122,7 +123,7 @@ private void processImpl() { // noinspection unchecked final SizedObjectChunk to = (SizedObjectChunk) sizedObjectChunks.get(i); // we _could_ consider doing this in a chunked fashion. doing in simple fashion to initially test - to.ensureCapacityPreserve(Maths.nextArrayCapacity(newSize)); + to.ensureCapacityPreserve(MathUtil.roundUpArraySize(newSize)); to.get().set(ix, from.get(0)); to.get().setSize(newSize); from.set(0, null); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java index e4e4016b3df..42d13d6ee9b 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java @@ -210,6 +210,59 @@ void intAsDiscriminator() throws IOException { } + @Test + void intStringAsDiscriminator() throws IOException { + final TypedObjectValue tov = TypedObjectValue.builder(new LinkedHashMap<>() { + { + put("1", QUOTE_OBJECT); + put("2", TRADE_OBJECT); + } + }).typeFieldName("id").build(); + + parse(tov, List.of( + "{\"id\": 1, \"symbol\": {\"name\": \"foo\", \"id\": 42}, \"quote\":{\"bid\": 1.01, \"ask\": 1.05}}", + "{\"id\": 2, \"symbol\": {\"name\": \"bar\", \"id\": 43}, \"price\": 42.42, \"size\": 123}", + "{\"id\": 3}", + "{\"id\": 4, \"symbol\": {\"name\": \"bar\", \"id\": 43}, \"price\": 42.42, \"size\": 123}"), + ObjectChunk.chunkWrap(new String[] {"1", "2", "3", "4"}), // id + ObjectChunk.chunkWrap(new String[] {"foo", "bar", null, null}), // symbol/symbol + LongChunk.chunkWrap(new long[] {42, 43, QueryConstants.NULL_LONG, QueryConstants.NULL_LONG}), // symbol/symbol_id + DoubleChunk.chunkWrap(new double[] {1.01, QueryConstants.NULL_DOUBLE, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE}), // quote: quote/bid + DoubleChunk.chunkWrap(new double[] {1.05, QueryConstants.NULL_DOUBLE, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE}), // quote: quote/ask + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 42.42, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE}), // trade: price + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 123, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE})); // trade: size + } + + @Test + void caseSensitiveDiscriminator() throws IOException { + final TypedObjectValue tov = TypedObjectValue.builder(new LinkedHashMap<>() { + { + put("q", QUOTE_OBJECT); + put("Q", TRADE_OBJECT); + } + }).typeFieldName("type").build(); + parse(tov, List.of( + "{\"type\": \"q\", \"symbol\": {\"name\": \"foo\", \"id\": 42}, \"quote\":{\"bid\": 1.01, \"ask\": 1.05}}", + "{\"type\": \"Q\", \"symbol\": {\"name\": \"bar\", \"id\": 43}, \"price\": 42.42, \"size\": 123}", + "{\"type\": \"other\"}", + "{\"type\": \"other_mimic_trade\", \"symbol\": {\"name\": \"bar\", \"id\": 43}, \"price\": 42.42, \"size\": 123}"), + ObjectChunk.chunkWrap(new String[] {"q", "Q", "other", "other_mimic_trade"}), // type + ObjectChunk.chunkWrap(new String[] {"foo", "bar", null, null}), // symbol/symbol + LongChunk.chunkWrap(new long[] {42, 43, QueryConstants.NULL_LONG, QueryConstants.NULL_LONG}), // symbol/symbol_id + DoubleChunk.chunkWrap(new double[] {1.01, QueryConstants.NULL_DOUBLE, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE}), // quote: quote/bid + DoubleChunk.chunkWrap(new double[] {1.05, QueryConstants.NULL_DOUBLE, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE}), // quote: quote/ask + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 42.42, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE}), // trade: price + DoubleChunk.chunkWrap(new double[] {QueryConstants.NULL_DOUBLE, 123, QueryConstants.NULL_DOUBLE, + QueryConstants.NULL_DOUBLE})); // trade: size + } + @Test void columnNames() { assertThat(JacksonProvider.of(QUOTE_OR_TRADE_OBJECT).named(Type.stringType()).names()).containsExactly( diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/MathsTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/MathsTest.java deleted file mode 100644 index a4b0c2adf8b..00000000000 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/MathsTest.java +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import io.deephaven.base.ArrayUtil; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class MathsTest { - - @Test - void nextPowerOf2() { - pow2(0, 1); - pow2(1, 1); - pow2(2, 2); - for (int i = 2; i < 31; ++i) { - final int pow2 = 1 << i; - pow2(pow2, pow2); - pow2(pow2 - 1, pow2); - if (i < 30) { - pow2(pow2 + 1, pow2 * 2); - } - } - } - - @Test - void nextArrayCapacity() { - arrayCapacity(0, 1); - arrayCapacity(1, 1); - arrayCapacity(2, 2); - for (int i = 2; i < 31; ++i) { - final int pow2 = 1 << i; - arrayCapacity(pow2, pow2); - arrayCapacity(pow2 - 1, pow2); - if (i < 30) { - arrayCapacity(pow2 + 1, pow2 * 2); - } else { - arrayCapacity(pow2 + 1, ArrayUtil.MAX_ARRAY_SIZE); - } - } - } - - public static void pow2(int newSize, int expectedSize) { - assertThat(Maths.nextPowerOf2(newSize)).isEqualTo(expectedSize); - } - - public static void arrayCapacity(int newSize, int expectedSize) { - assertThat(Maths.nextArrayCapacity(newSize)).isEqualTo(expectedSize); - } -} From c0ab02a8369e0c095d40301bf517d0a3a3c22aa3 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Thu, 30 May 2024 10:39:59 -0700 Subject: [PATCH 41/53] Update python --- .../json/jackson/TypedObjectMixin.java | 7 +- .../io/deephaven/json/TypedObjectValue.java | 8 +- py/server/deephaven/json/__init__.py | 184 +++++++++++++----- 3 files changed, 141 insertions(+), 58 deletions(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java index b2dcfed813b..865e3a5351b 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java @@ -38,6 +38,9 @@ public TypedObjectMixin(TypedObjectValue options, JsonFactory factory) { super(factory, options); if (!(options.typeField().options() instanceof StringValue)) { throw new IllegalArgumentException("Only string-valued type fields are currently supported"); + } + if (!(options.onNull().orElse(null) instanceof String)) { + } typeFieldAliases = options.typeField().caseSensitive() ? null : new TreeSet<>(String.CASE_INSENSITIVE_ORDER); { @@ -244,7 +247,7 @@ public void processCurrentValue(JsonParser parser) throws IOException { @Override public void processMissing(JsonParser parser) throws IOException { checkMissingAllowed(parser); - typeChunk.add(options.onMissing().orElse(null)); + typeChunk.add((String) options.onMissing().orElse(null)); // We are _not_ trying to pass along the potential "on missing" value for each individual chunk; the // individual columns may have "allowMissing = false", but as a higher level of control with // TypedObjectValue, we have already verified that we want to allow missing. As such, the discriminating @@ -259,7 +262,7 @@ public void processMissing(JsonParser parser) throws IOException { private void processNullObject(JsonParser parser) throws IOException { checkNullAllowed(parser); - typeChunk.add(options.onNull().orElse(null)); + typeChunk.add((String) options.onNull().orElse(null)); // We are _not_ trying to pass along the potential "on null" value for each individual chunk; the // individual columns may have "allowNull = false", but as a higher level of control with // TypedObjectValue, we have already verified that we want to allow null. As such, the discriminating diff --git a/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java b/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java index 1d92e6426fd..ad1b6562377 100644 --- a/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java +++ b/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java @@ -143,12 +143,12 @@ public Set allowedTypes() { * The output type value to use when {@link JsonValueTypes#NULL} is encountered. {@link #allowedTypes()} must * contain {@link JsonValueTypes#NULL}. */ - public abstract Optional onNull(); + public abstract Optional onNull(); /** * The output type value to use when a value is missing. {@link #allowMissing()} must be {@code true}. */ - public abstract Optional onMissing(); + public abstract Optional onMissing(); @Override final Set universe() { @@ -178,9 +178,9 @@ default Builder typeFieldName(String typeFieldName) { Builder allowUnknownTypes(boolean allowUnknownTypes); - Builder onNull(String onNull); + Builder onNull(Object onNull); - Builder onMissing(String onMissing); + Builder onMissing(Object onMissing); } private static ObjectValue without(ObjectValue options, Set excludedFields) { diff --git a/py/server/deephaven/json/__init__.py b/py/server/deephaven/json/__init__.py index 35a81986fc6..4b65ea75902 100644 --- a/py/server/deephaven/json/__init__.py +++ b/py/server/deephaven/json/__init__.py @@ -22,6 +22,8 @@ from deephaven import dtypes from deephaven._wrapper import JObjectWrapper from deephaven.time import to_j_instant +from deephaven._jpy import strict_cast + __all__ = [ "string_", @@ -51,34 +53,31 @@ # https://deephaven.atlassian.net/browse/DH-15061 # It is important that ValueOptions gets imported before the others. -_JValueOptions = jpy.get_type("io.deephaven.json.Value") -_JObjectOptions = jpy.get_type("io.deephaven.json.ObjectValue") -_JArrayOptions = jpy.get_type("io.deephaven.json.ArrayValue") -_JObjectKvOptions = jpy.get_type("io.deephaven.json.ObjectKvValue") -_JTupleOptions = jpy.get_type("io.deephaven.json.TupleValue") -_JObjectFieldOptions = jpy.get_type("io.deephaven.json.ObjectField") -_JRepeatedFieldBehavior = jpy.get_type( - "io.deephaven.json.ObjectField$RepeatedBehavior" -) +_JValue = jpy.get_type("io.deephaven.json.Value") +_JObjectValue = jpy.get_type("io.deephaven.json.ObjectValue") +_JTypedObjectValue = jpy.get_type("io.deephaven.json.TypedObjectValue") +_JArrayValue = jpy.get_type("io.deephaven.json.ArrayValue") +_JObjectKvValue = jpy.get_type("io.deephaven.json.ObjectKvValue") +_JTupleValue = jpy.get_type("io.deephaven.json.TupleValue") +_JObjectField = jpy.get_type("io.deephaven.json.ObjectField") +_JRepeatedFieldBehavior = jpy.get_type("io.deephaven.json.ObjectField$RepeatedBehavior") _JJsonValueTypes = jpy.get_type("io.deephaven.json.JsonValueTypes") -_JBoolOptions = jpy.get_type("io.deephaven.json.BoolValue") -_JCharOptions = jpy.get_type("io.deephaven.json.CharValue") -_JByteOptions = jpy.get_type("io.deephaven.json.ByteValue") -_JShortOptions = jpy.get_type("io.deephaven.json.ShortValue") -_JIntOptions = jpy.get_type("io.deephaven.json.IntValue") -_JLongOptions = jpy.get_type("io.deephaven.json.LongValue") -_JFloatOptions = jpy.get_type("io.deephaven.json.FloatValue") -_JDoubleOptions = jpy.get_type("io.deephaven.json.DoubleValue") -_JStringOptions = jpy.get_type("io.deephaven.json.StringValue") -_JSkipOptions = jpy.get_type("io.deephaven.json.SkipValue") -_JInstantOptions = jpy.get_type("io.deephaven.json.InstantValue") -_JInstantNumberOptions = jpy.get_type("io.deephaven.json.InstantNumberValue") -_JInstantNumberOptionsFormat = jpy.get_type( - "io.deephaven.json.InstantNumberValue$Format" -) -_JBigIntegerOptions = jpy.get_type("io.deephaven.json.BigIntegerValue") -_JBigDecimalOptions = jpy.get_type("io.deephaven.json.BigDecimalValue") -_JAnyOptions = jpy.get_type("io.deephaven.json.AnyValue") +_JBoolValue = jpy.get_type("io.deephaven.json.BoolValue") +_JCharValue = jpy.get_type("io.deephaven.json.CharValue") +_JByteValue = jpy.get_type("io.deephaven.json.ByteValue") +_JShortValue = jpy.get_type("io.deephaven.json.ShortValue") +_JIntValue = jpy.get_type("io.deephaven.json.IntValue") +_JLongValue = jpy.get_type("io.deephaven.json.LongValue") +_JFloatValue = jpy.get_type("io.deephaven.json.FloatValue") +_JDoubleValue = jpy.get_type("io.deephaven.json.DoubleValue") +_JStringValue = jpy.get_type("io.deephaven.json.StringValue") +_JSkipValue = jpy.get_type("io.deephaven.json.SkipValue") +_JInstantValue = jpy.get_type("io.deephaven.json.InstantValue") +_JInstantNumberValue = jpy.get_type("io.deephaven.json.InstantNumberValue") +_JInstantNumberValueFormat = jpy.get_type("io.deephaven.json.InstantNumberValue$Format") +_JBigIntegerValue = jpy.get_type("io.deephaven.json.BigIntegerValue") +_JBigDecimalValue = jpy.get_type("io.deephaven.json.BigDecimalValue") +_JAnyValue = jpy.get_type("io.deephaven.json.AnyValue") _VALUE_STRING = _JJsonValueTypes.STRING @@ -89,16 +88,16 @@ _VALUE_OBJECT = _JJsonValueTypes.OBJECT _VALUE_ARRAY = _JJsonValueTypes.ARRAY -_EPOCH_SECONDS = _JInstantNumberOptionsFormat.EPOCH_SECONDS -_EPOCH_MILLIS = _JInstantNumberOptionsFormat.EPOCH_MILLIS -_EPOCH_MICROS = _JInstantNumberOptionsFormat.EPOCH_MICROS -_EPOCH_NANOS = _JInstantNumberOptionsFormat.EPOCH_NANOS +_EPOCH_SECONDS = _JInstantNumberValueFormat.EPOCH_SECONDS +_EPOCH_MILLIS = _JInstantNumberValueFormat.EPOCH_MILLIS +_EPOCH_MICROS = _JInstantNumberValueFormat.EPOCH_MICROS +_EPOCH_NANOS = _JInstantNumberValueFormat.EPOCH_NANOS class JsonOptions(JObjectWrapper): """The JSON options object. Provides a named object processor provider.""" - j_object_type = _JValueOptions + j_object_type = _JValue def __init__(self, j_options: jpy.JType): self.j_options = j_options @@ -170,7 +169,7 @@ class FieldOptions: def _j_field_options(self, name: str) -> jpy.JType: builder = ( - _JObjectFieldOptions.builder() + _JObjectField.builder() .name(name) .options(json(self.value).j_options) .repeatedBehavior(self.repeated_behavior.value) @@ -248,7 +247,7 @@ def object_( Returns: the object options """ - builder = _JObjectOptions.builder() + builder = _JObjectValue.builder() _build(builder, allow_missing, allow_null, allow_object=True) builder.allowUnknownFields(allow_unknown_fields) for field_name, field_opts in fields.items(): @@ -266,6 +265,87 @@ def object_( return JsonOptions(builder.build()) +def typed_object_( + type_field: str, + shared_fields: Dict[str, Union[JsonValueType, FieldOptions]], + objects: Dict[str, JsonValueType], + allow_unknown_types: bool = True, + allow_missing: bool = True, + allow_null: bool = True, + on_missing: Optional[str] = None, + on_null: Optional[str] = None, +) -> JsonOptions: + """Creates a type-discriminated object options. For example, the JSON objects + + .. code-block:: json + { "type": "trade", "symbol": "FOO", "price": 70.03, "size": 42 } + + .. code-block:: json + { "type": "quote", "symbol": "BAR", "bid": 10.01, "ask": 10.05 } + + might be modelled as a type-discriminated object with "type" as the type field, "symbol" as a shared field, with a + "trade" object containing a "bid" and an "ask" field, and with a "quote" object containing a "price" and a "size" + field: + + .. code-block:: python + typed_object_( + "type", + {"symbol": str}, + { + "quote": { + "price": float, + "size": int + }, + "trade": { + "bid": float, + "ask": float + } + } + ) + + Args: + type_field (str): the type-discriminating field + shared_fields (Dict[str, Union[JsonValueType, FieldOptions]]): the shared fields + objects (Dict[str, Union[JsonValueType, FieldOptions]]): the individual objects, keyed by their + type-discriminated value. The values must be object options. + allow_unknown_types (bool): if unknown types are allow, by default is True + allow_missing (bool): if the object is allowed to be missing, by default is True + allow_null (bool): if the object is allowed to be a JSON null type, by default is True + on_missing (Optional[str]): the type value to use when the JSON value is missing and allow_missing is True, + default is None + on_null (Optional[str]): the type value to use when the JSON value is null and allow_null is True, default is + None + + Returns: + the object options + """ + builder = _JTypedObjectValue.builder() + _build(builder, allow_missing, allow_null, allow_object=True) + builder.typeFieldName(type_field) + builder.allowUnknownTypes(allow_unknown_types) + if on_missing: + builder.onMissing(on_missing) + if on_null: + builder.onNull(on_null) + for shared_field_name, shared_field_opts in shared_fields.items(): + shared_field_opts = ( + shared_field_opts + if isinstance(shared_field_opts, FieldOptions) + else FieldOptions( + shared_field_opts, + repeated_behavior=RepeatedFieldBehavior.ERROR, + case_sensitive=True, + ) + ) + # noinspection PyProtectedMember + builder.addSharedFields(shared_field_opts._j_field_options(shared_field_name)) + for object_name, object_type in objects.items(): + builder.putObjects( + object_name, strict_cast(json(object_type).j_options, _JObjectValue) + ) + return JsonOptions(builder.build()) + + def array_( element: JsonValueType, allow_missing: bool = True, @@ -300,7 +380,7 @@ def array_( Returns: the array options """ - builder = _JArrayOptions.builder() + builder = _JArrayValue.builder() builder.element(json(element).j_options) _build(builder, allow_missing, allow_null, allow_array=True) return JsonOptions(builder.build()) @@ -338,7 +418,7 @@ def object_kv_( Returns: the object kv options """ - builder = _JObjectKvOptions.builder() + builder = _JObjectKvValue.builder() builder.key(json(key_type).j_options) builder.value(json(value_type).j_options) _build(builder, allow_missing, allow_null, allow_object=True) @@ -389,7 +469,7 @@ def tuple_( kvs = values.items() else: raise TypeError(f"Invalid tuple type: {type(values)}") - builder = _JTupleOptions.builder() + builder = _JTupleValue.builder() _build( builder, allow_missing, @@ -439,7 +519,7 @@ def bool_( Returns: the bool options """ - builder = _JBoolOptions.builder() + builder = _JBoolValue.builder() _build( builder, allow_missing, @@ -479,7 +559,7 @@ def char_( Returns: the char options """ - builder = _JCharOptions.builder() + builder = _JCharValue.builder() _build( builder, allow_missing, @@ -522,7 +602,7 @@ def byte_( Returns: the byte options """ - builder = _JByteOptions.builder() + builder = _JByteValue.builder() _build( builder, allow_missing, @@ -567,7 +647,7 @@ def short_( Returns: the short options """ - builder = _JShortOptions.builder() + builder = _JShortValue.builder() _build( builder, allow_missing, @@ -612,7 +692,7 @@ def int_( Returns: the int options """ - builder = _JIntOptions.builder() + builder = _JIntValue.builder() _build( builder, allow_missing, @@ -668,7 +748,7 @@ def long_( Returns: the long options """ - builder = _JLongOptions.builder() + builder = _JLongValue.builder() _build( builder, allow_missing, @@ -711,7 +791,7 @@ def float_( Returns: the float options """ - builder = _JFloatOptions.builder() + builder = _JFloatValue.builder() _build( builder, allow_missing, @@ -765,7 +845,7 @@ def double_( Returns: the double options """ - builder = _JDoubleOptions.builder() + builder = _JDoubleValue.builder() _build( builder, allow_missing, @@ -823,7 +903,7 @@ def string_( Returns: the double options """ - builder = _JStringOptions.builder() + builder = _JStringValue.builder() _build( builder, allow_missing, @@ -894,7 +974,7 @@ def instant_( the Instant options """ if number_format: - builder = _JInstantNumberOptions.builder() + builder = _JInstantNumberValue.builder() if on_missing: builder.onMull(to_j_instant(on_missing)) if on_null: @@ -920,7 +1000,7 @@ def instant_( else: if allow_decimal: raise TypeError("allow_decimal is only valid when using number_format") - builder = _JInstantOptions.builder() + builder = _JInstantValue.builder() if on_missing: builder.onMull(to_j_instant(on_missing)) if on_null: @@ -960,7 +1040,7 @@ def big_integer_( Returns: the BigInteger options """ - builder = _JBigIntegerOptions.builder() + builder = _JBigIntegerValue.builder() _build( builder, allow_missing, @@ -996,7 +1076,7 @@ def big_decimal_( Returns: the BigDecimal options """ - builder = _JBigDecimalOptions.builder() + builder = _JBigDecimalValue.builder() _build( builder, allow_missing, @@ -1014,7 +1094,7 @@ def any_() -> JsonOptions: Returns: the "any" options """ - return JsonOptions(_JAnyOptions.of()) + return JsonOptions(_JAnyValue.of()) def skip_( @@ -1057,7 +1137,7 @@ def skip_( def _allow(x: Optional[bool]) -> bool: return x if x is not None else allow_by_default - builder = _JSkipOptions.builder() + builder = _JSkipValue.builder() _build( builder, allow_missing=_allow(allow_missing), From 08e44de7ff9e8b0a4b8d9eb5ca432ff1b60aff67 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Thu, 30 May 2024 16:43:28 -0700 Subject: [PATCH 42/53] Review responses --- .../io/deephaven/json/jackson/AnyMixin.java | 45 ++--------- .../io/deephaven/json/jackson/ArrayMixin.java | 11 ++- .../json/jackson/BigDecimalMixin.java | 33 +------- .../json/jackson/BigIntegerMixin.java | 33 +------- .../io/deephaven/json/jackson/BoolMixin.java | 38 ++++++++-- .../io/deephaven/json/jackson/ByteMixin.java | 37 +++++++-- .../json/jackson/ByteValueProcessor.java | 61 --------------- .../io/deephaven/json/jackson/CharMixin.java | 36 +++++++-- .../json/jackson/CharValueProcessor.java | 61 --------------- .../deephaven/json/jackson/ContextAware.java | 4 +- .../deephaven/json/jackson/DoubleMixin.java | 37 +++++++-- .../json/jackson/DoubleValueProcessor.java | 61 --------------- .../io/deephaven/json/jackson/FloatMixin.java | 37 +++++++-- .../json/jackson/FloatValueProcessor.java | 61 --------------- .../json/jackson/GenericObjectMixin.java | 76 +++++++++++++++++++ .../deephaven/json/jackson/InstantMixin.java | 38 ++++++++-- .../json/jackson/InstantNumberMixin.java | 45 +++++++++-- .../io/deephaven/json/jackson/IntMixin.java | 37 +++++++-- .../json/jackson/IntValueProcessor.java | 61 --------------- .../json/jackson/LocalDateMixin.java | 33 +------- .../io/deephaven/json/jackson/LongMixin.java | 35 +++++++-- .../json/jackson/LongValueProcessor.java | 60 --------------- .../java/io/deephaven/json/jackson/Mixin.java | 52 ++++++++++++- .../deephaven/json/jackson/ObjectKvMixin.java | 25 ++---- .../json/jackson/ObjectValueProcessor.java | 64 ---------------- .../json/jackson/RepeaterGenericImpl.java | 1 - .../io/deephaven/json/jackson/ShortMixin.java | 37 +++++++-- .../json/jackson/ShortValueProcessor.java | 61 --------------- .../io/deephaven/json/jackson/SkipMixin.java | 5 ++ .../deephaven/json/jackson/StringMixin.java | 32 +------- .../io/deephaven/json/jackson/ToObject.java | 15 ++++ .../json/jackson/TypedObjectMixin.java | 25 +++--- .../json/jackson/ValueAwareException.java | 4 + .../deephaven/json/BigDecimalValueTest.java | 17 +++++ .../deephaven/json/BigIntegerValueTest.java | 18 +++++ .../java/io/deephaven/json/BoolValueTest.java | 17 +++++ .../java/io/deephaven/json/ByteValueTest.java | 38 +++++++++- .../java/io/deephaven/json/CharValueTest.java | 28 +++++++ .../io/deephaven/json/DoubleValueTest.java | 16 ++++ .../io/deephaven/json/FloatValueTest.java | 16 ++++ .../io/deephaven/json/InstantNumberTest.java | 17 +++++ .../io/deephaven/json/InstantValueTest.java | 17 +++++ .../java/io/deephaven/json/IntValueTest.java | 35 ++++++++- .../io/deephaven/json/LocalDateValueTest.java | 17 +++++ .../java/io/deephaven/json/LongValueTest.java | 38 +++++++++- .../io/deephaven/json/ObjectValueTest.java | 16 ++++ .../io/deephaven/json/ShortValueTest.java | 37 ++++++++- .../io/deephaven/json/StringValueTest.java | 16 ++++ .../io/deephaven/json/TupleValueTest.java | 17 +++++ .../deephaven/json/TypedObjectValueTest.java | 10 +++ .../json/jackson/JacksonAnyValueTest.java | 18 +++++ py/server/deephaven/json/__init__.py | 9 +-- py/server/deephaven/json/jackson.py | 35 +++------ py/server/deephaven/stream/kafka/consumer.py | 2 +- 54 files changed, 881 insertions(+), 814 deletions(-) delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/GenericObjectMixin.java delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java delete mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java create mode 100644 extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ToObject.java diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java index 21632a4d66f..acaa2cc9fe2 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/AnyMixin.java @@ -7,55 +7,22 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.TreeNode; import io.deephaven.json.AnyValue; -import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; import java.io.IOException; -import java.util.List; -import java.util.stream.Stream; -final class AnyMixin extends Mixin { +final class AnyMixin extends GenericObjectMixin { public AnyMixin(AnyValue options, JsonFactory factory) { - super(factory, options); + super(factory, options, Type.ofCustom(TreeNode.class)); } @Override - public int outputSize() { - return 1; + public TreeNode parseValue(JsonParser parser) throws IOException { + return parser.readValueAsTree(); } @Override - public Stream> paths() { - return Stream.of(List.of()); - } - - @Override - public Stream> outputTypesImpl() { - return Stream.of(Type.ofCustom(TreeNode.class)); - } - - @Override - public ValueProcessor processor(String context) { - return new ObjectValueProcessor<>(ToTreeNode.INSTANCE, Type.ofCustom(TreeNode.class)); - } - - @Override - RepeaterProcessor repeaterProcessor() { - return new RepeaterGenericImpl<>(ToTreeNode.INSTANCE, null, null, - Type.ofCustom(TreeNode.class).arrayType()); - } - - private enum ToTreeNode implements ToObject { - INSTANCE; - - @Override - public TreeNode parseValue(JsonParser parser) throws IOException { - return parser.readValueAsTree(); - } - - @Override - public TreeNode parseMissing(JsonParser parser) { - return parser.getCodec().missingNode(); - } + public TreeNode parseMissing(JsonParser parser) throws IOException { + return parser.getCodec().missingNode(); } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java index 1e8d271173b..9f7ecd147e8 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ArrayMixin.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; import io.deephaven.json.ArrayValue; -import io.deephaven.qst.type.NativeArrayType; import io.deephaven.qst.type.Type; import java.io.IOException; @@ -34,13 +33,13 @@ public Stream> paths() { } @Override - public Stream> outputTypesImpl() { + public Stream> outputTypesImpl() { return elementOutputTypes().map(Type::arrayType); } @Override public ValueProcessor processor(String context) { - return new ArrayValueProcessor(); + return new ArrayMixinProcessor(); } private Stream> elementOutputTypes() { @@ -58,14 +57,14 @@ RepeaterProcessor repeaterProcessor() { // double[] (processor()) // double[][] (repeater()) // return new ArrayOfArrayRepeaterProcessor(allowMissing, allowNull); - return new ValueInnerRepeaterProcessor(new ArrayValueProcessor()); + return new ValueInnerRepeaterProcessor(new ArrayMixinProcessor()); } - private class ArrayValueProcessor implements ValueProcessor { + private class ArrayMixinProcessor implements ValueProcessor { private final RepeaterProcessor elementProcessor; - ArrayValueProcessor() { + ArrayMixinProcessor() { this.elementProcessor = elementRepeater(); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java index d8372167b42..6d2d1fd3a8c 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigDecimalMixin.java @@ -6,38 +6,15 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.json.BigDecimalValue; -import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; import java.io.IOException; import java.math.BigDecimal; -import java.util.List; -import java.util.stream.Stream; -final class BigDecimalMixin extends Mixin implements ToObject { +final class BigDecimalMixin extends GenericObjectMixin { public BigDecimalMixin(BigDecimalValue options, JsonFactory factory) { - super(factory, options); - } - - @Override - public int outputSize() { - return 1; - } - - @Override - public Stream> paths() { - return Stream.of(List.of()); - } - - @Override - public Stream> outputTypesImpl() { - return Stream.of(Type.ofCustom(BigDecimal.class)); - } - - @Override - public ValueProcessor processor(String context) { - return new ObjectValueProcessor<>(this, Type.ofCustom(BigDecimal.class)); + super(factory, options, Type.ofCustom(BigDecimal.class)); } @Override @@ -60,12 +37,6 @@ public BigDecimal parseMissing(JsonParser parser) throws IOException { return parseFromMissing(parser); } - @Override - RepeaterProcessor repeaterProcessor() { - return new RepeaterGenericImpl<>(this, null, null, - Type.ofCustom(BigDecimal.class).arrayType()); - } - private BigDecimal parseFromNumber(JsonParser parser) throws IOException { checkNumberAllowed(parser); return Parsing.parseDecimalAsBigDecimal(parser); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java index 545fd15fdab..f2585108fd2 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BigIntegerMixin.java @@ -6,38 +6,15 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.json.BigIntegerValue; -import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; import java.io.IOException; import java.math.BigInteger; -import java.util.List; -import java.util.stream.Stream; -final class BigIntegerMixin extends Mixin implements ToObject { +final class BigIntegerMixin extends GenericObjectMixin { public BigIntegerMixin(BigIntegerValue options, JsonFactory factory) { - super(factory, options); - } - - @Override - public int outputSize() { - return 1; - } - - @Override - public Stream> paths() { - return Stream.of(List.of()); - } - - @Override - public Stream> outputTypesImpl() { - return Stream.of(Type.ofCustom(BigInteger.class)); - } - - @Override - public ValueProcessor processor(String context) { - return new ObjectValueProcessor<>(this, Type.ofCustom(BigInteger.class)); + super(factory, options, Type.ofCustom(BigInteger.class)); } @Override @@ -61,12 +38,6 @@ public BigInteger parseMissing(JsonParser parser) throws IOException { return parseFromMissing(parser); } - @Override - RepeaterProcessor repeaterProcessor() { - return new RepeaterGenericImpl<>(this, null, null, - Type.ofCustom(BigInteger.class).arrayType()); - } - private BigInteger parseFromInt(JsonParser parser) throws IOException { checkNumberIntAllowed(parser); return Parsing.parseIntAsBigInteger(parser); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java index fcedb1ca5c8..70a5ba39d17 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/BoolMixin.java @@ -5,9 +5,9 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableByteChunk; +import io.deephaven.chunk.WritableChunk; import io.deephaven.json.BoolValue; -import io.deephaven.json.jackson.ByteValueProcessor.ToByte; -import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; import io.deephaven.util.BooleanUtils; @@ -15,7 +15,7 @@ import java.util.List; import java.util.stream.Stream; -final class BoolMixin extends Mixin implements ToByte { +final class BoolMixin extends Mixin { private final Boolean onNull; private final Boolean onMissing; @@ -47,11 +47,10 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new ByteValueProcessor(this); + return new BoolMixinProcessor(); } - @Override - public byte parseValue(JsonParser parser) throws IOException { + private byte parseValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { case VALUE_TRUE: return BooleanUtils.TRUE_BOOLEAN_AS_BYTE; @@ -66,8 +65,7 @@ public byte parseValue(JsonParser parser) throws IOException { throw unexpectedToken(parser); } - @Override - public byte parseMissing(JsonParser parser) throws IOException { + private byte parseMissing(JsonParser parser) throws IOException { return parseFromMissing(parser); } @@ -143,4 +141,28 @@ private Boolean parseFromMissingBoolean(JsonParser parser) throws IOException { checkMissingAllowed(parser); return onMissing; } + + private class BoolMixinProcessor extends ValueProcessorMixinBase { + private WritableByteChunk out; + + @Override + public final void setContext(List> out) { + this.out = out.get(0).asWritableByteChunk(); + } + + @Override + public final void clearContext() { + out = null; + } + + @Override + protected void processCurrentValueImpl(JsonParser parser) throws IOException { + out.add(parseValue(parser)); + } + + @Override + protected void processMissingImpl(JsonParser parser) throws IOException { + out.add(parseMissing(parser)); + } + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java index 81158a8b839..856491ab8cb 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteMixin.java @@ -7,9 +7,10 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.base.MathUtil; import io.deephaven.chunk.WritableByteChunk; +import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.sized.SizedByteChunk; import io.deephaven.json.ByteValue; -import io.deephaven.json.jackson.ByteValueProcessor.ToByte; +import io.deephaven.json.Value; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -18,7 +19,7 @@ import java.util.List; import java.util.stream.Stream; -final class ByteMixin extends Mixin implements ToByte { +final class ByteMixin extends Mixin { public ByteMixin(ByteValue options, JsonFactory factory) { super(factory, options); } @@ -40,11 +41,10 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new ByteValueProcessor(this); + return new ByteMixinProcessor(); } - @Override - public byte parseValue(JsonParser parser) throws IOException { + private byte parseValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { case VALUE_NUMBER_INT: return parseFromInt(parser); @@ -59,8 +59,7 @@ public byte parseValue(JsonParser parser) throws IOException { throw unexpectedToken(parser); } - @Override - public byte parseMissing(JsonParser parser) throws IOException { + private byte parseMissing(JsonParser parser) throws IOException { return parseFromMissing(parser); } @@ -125,4 +124,28 @@ private byte parseFromMissing(JsonParser parser) throws IOException { checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_BYTE); } + + private class ByteMixinProcessor extends ValueProcessorMixinBase { + private WritableByteChunk out; + + @Override + public final void setContext(List> out) { + this.out = out.get(0).asWritableByteChunk(); + } + + @Override + public final void clearContext() { + out = null; + } + + @Override + protected void processCurrentValueImpl(JsonParser parser) throws IOException { + out.add(parseValue(parser)); + } + + @Override + protected void processMissingImpl(JsonParser parser) throws IOException { + out.add(parseMissing(parser)); + } + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java deleted file mode 100644 index aa5a7aa9d72..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ByteValueProcessor.java +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableByteChunk; -import io.deephaven.chunk.WritableChunk; -import io.deephaven.qst.type.Type; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -final class ByteValueProcessor implements ValueProcessor { - - private WritableByteChunk out; - private final ToByte toByte; - - ByteValueProcessor(ToByte toByte) { - this.toByte = Objects.requireNonNull(toByte); - } - - @Override - public void setContext(List> out) { - this.out = out.get(0).asWritableByteChunk(); - } - - @Override - public void clearContext() { - out = null; - } - - @Override - public int numColumns() { - return 1; - } - - @Override - public Stream> columnTypes() { - return Stream.of(Type.byteType()); - } - - @Override - public void processCurrentValue(JsonParser parser) throws IOException { - out.add(toByte.parseValue(parser)); - } - - @Override - public void processMissing(JsonParser parser) throws IOException { - out.add(toByte.parseMissing(parser)); - } - - interface ToByte { - - byte parseValue(JsonParser parser) throws IOException; - - byte parseMissing(JsonParser parser) throws IOException; - } -} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java index de30afa9969..f14b81583f1 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharMixin.java @@ -7,9 +7,9 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.base.MathUtil; import io.deephaven.chunk.WritableCharChunk; +import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.sized.SizedCharChunk; import io.deephaven.json.CharValue; -import io.deephaven.json.jackson.CharValueProcessor.ToChar; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -18,7 +18,7 @@ import java.util.List; import java.util.stream.Stream; -final class CharMixin extends Mixin implements ToChar { +final class CharMixin extends Mixin { public CharMixin(CharValue options, JsonFactory factory) { super(factory, options); } @@ -40,11 +40,10 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new CharValueProcessor(this); + return new CharMixinProcessor(); } - @Override - public char parseValue(JsonParser parser) throws IOException { + private char parseValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { case VALUE_STRING: case FIELD_NAME: @@ -55,8 +54,7 @@ public char parseValue(JsonParser parser) throws IOException { throw unexpectedToken(parser); } - @Override - public char parseMissing(JsonParser parser) throws IOException { + private char parseMissing(JsonParser parser) throws IOException { return parseFromMissing(parser); } @@ -109,4 +107,28 @@ private char parseFromMissing(JsonParser parser) throws IOException { checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_CHAR); } + + private class CharMixinProcessor extends ValueProcessorMixinBase { + private WritableCharChunk out; + + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableCharChunk(); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + protected void processCurrentValueImpl(JsonParser parser) throws IOException { + out.add(parseValue(parser)); + } + + @Override + protected void processMissingImpl(JsonParser parser) throws IOException { + out.add(parseMissing(parser)); + } + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java deleted file mode 100644 index 471dfe74639..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/CharValueProcessor.java +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableCharChunk; -import io.deephaven.chunk.WritableChunk; -import io.deephaven.qst.type.Type; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -final class CharValueProcessor implements ValueProcessor { - - private WritableCharChunk out; - private final ToChar toChar; - - CharValueProcessor(ToChar toChar) { - this.toChar = Objects.requireNonNull(toChar); - } - - @Override - public void setContext(List> out) { - this.out = out.get(0).asWritableCharChunk(); - } - - @Override - public void clearContext() { - out = null; - } - - @Override - public int numColumns() { - return 1; - } - - @Override - public Stream> columnTypes() { - return Stream.of(Type.charType()); - } - - @Override - public void processCurrentValue(JsonParser parser) throws IOException { - out.add(toChar.parseValue(parser)); - } - - @Override - public void processMissing(JsonParser parser) throws IOException { - out.add(toChar.parseMissing(parser)); - } - - interface ToChar { - - char parseValue(JsonParser parser) throws IOException; - - char parseMissing(JsonParser parser) throws IOException; - } -} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java index 60f72dfe8e9..7e28d086bb4 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ContextAware.java @@ -14,9 +14,7 @@ interface ContextAware { void clearContext(); - default int numColumns() { - return (int) columnTypes().count(); - } + int numColumns(); Stream> columnTypes(); } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java index a4c49a53108..bd1425a3784 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleMixin.java @@ -6,10 +6,10 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.base.MathUtil; +import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableDoubleChunk; import io.deephaven.chunk.sized.SizedDoubleChunk; import io.deephaven.json.DoubleValue; -import io.deephaven.json.jackson.DoubleValueProcessor.ToDouble; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -18,7 +18,7 @@ import java.util.List; import java.util.stream.Stream; -final class DoubleMixin extends Mixin implements ToDouble { +final class DoubleMixin extends Mixin { public DoubleMixin(DoubleValue options, JsonFactory factory) { super(factory, options); @@ -41,11 +41,10 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new DoubleValueProcessor(this); + return new DoubleMixinProcessor(); } - @Override - public double parseValue(JsonParser parser) throws IOException { + private double parseValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { case VALUE_NUMBER_INT: case VALUE_NUMBER_FLOAT: @@ -59,8 +58,7 @@ public double parseValue(JsonParser parser) throws IOException { throw unexpectedToken(parser); } - @Override - public double parseMissing(JsonParser parser) throws IOException { + private double parseMissing(JsonParser parser) throws IOException { return parseFromMissing(parser); } @@ -119,4 +117,29 @@ private double parseFromMissing(JsonParser parser) throws IOException { checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_DOUBLE); } + + final class DoubleMixinProcessor extends ValueProcessorMixinBase { + + private WritableDoubleChunk out; + + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableDoubleChunk(); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + protected void processCurrentValueImpl(JsonParser parser) throws IOException { + out.add(parseValue(parser)); + } + + @Override + protected void processMissingImpl(JsonParser parser) throws IOException { + out.add(parseMissing(parser)); + } + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java deleted file mode 100644 index ca47de9e754..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/DoubleValueProcessor.java +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; -import io.deephaven.chunk.WritableDoubleChunk; -import io.deephaven.qst.type.Type; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -final class DoubleValueProcessor implements ValueProcessor { - - private WritableDoubleChunk out; - private final ToDouble toDouble; - - DoubleValueProcessor(ToDouble toDouble) { - this.toDouble = Objects.requireNonNull(toDouble); - } - - @Override - public void setContext(List> out) { - this.out = out.get(0).asWritableDoubleChunk(); - } - - @Override - public void clearContext() { - out = null; - } - - @Override - public int numColumns() { - return 1; - } - - @Override - public Stream> columnTypes() { - return Stream.of(Type.doubleType()); - } - - @Override - public void processCurrentValue(JsonParser parser) throws IOException { - out.add(toDouble.parseValue(parser)); - } - - @Override - public void processMissing(JsonParser parser) throws IOException { - out.add(toDouble.parseMissing(parser)); - } - - interface ToDouble { - - double parseValue(JsonParser parser) throws IOException; - - double parseMissing(JsonParser parser) throws IOException; - } -} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java index 76de0bdae35..18ec8cde73a 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatMixin.java @@ -6,10 +6,10 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.base.MathUtil; +import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableFloatChunk; import io.deephaven.chunk.sized.SizedFloatChunk; import io.deephaven.json.FloatValue; -import io.deephaven.json.jackson.FloatValueProcessor.ToFloat; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -18,7 +18,7 @@ import java.util.List; import java.util.stream.Stream; -final class FloatMixin extends Mixin implements ToFloat { +final class FloatMixin extends Mixin { public FloatMixin(FloatValue options, JsonFactory factory) { super(factory, options); @@ -41,11 +41,10 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new FloatValueProcessor(this); + return new FloatMixinProcessor(); } - @Override - public float parseValue(JsonParser parser) throws IOException { + private float parseValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { case VALUE_NUMBER_INT: case VALUE_NUMBER_FLOAT: @@ -59,8 +58,7 @@ public float parseValue(JsonParser parser) throws IOException { throw unexpectedToken(parser); } - @Override - public float parseMissing(JsonParser parser) throws IOException { + private float parseMissing(JsonParser parser) throws IOException { return parseFromMissing(parser); } @@ -118,4 +116,29 @@ private float parseFromMissing(JsonParser parser) throws IOException { checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_FLOAT); } + + final class FloatMixinProcessor extends ValueProcessorMixinBase { + + private WritableFloatChunk out; + + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableFloatChunk(); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + protected void processCurrentValueImpl(JsonParser parser) throws IOException { + out.add(parseValue(parser)); + } + + @Override + protected void processMissingImpl(JsonParser parser) throws IOException { + out.add(parseMissing(parser)); + } + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java deleted file mode 100644 index c5ade0e2fa2..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/FloatValueProcessor.java +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; -import io.deephaven.chunk.WritableFloatChunk; -import io.deephaven.qst.type.Type; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -final class FloatValueProcessor implements ValueProcessor { - - private WritableFloatChunk out; - private final ToFloat toFloat; - - FloatValueProcessor(ToFloat toFloat) { - this.toFloat = Objects.requireNonNull(toFloat); - } - - @Override - public void setContext(List> out) { - this.out = out.get(0).asWritableFloatChunk(); - } - - @Override - public void clearContext() { - out = null; - } - - @Override - public int numColumns() { - return 1; - } - - @Override - public Stream> columnTypes() { - return Stream.of(Type.floatType()); - } - - @Override - public void processCurrentValue(JsonParser parser) throws IOException { - out.add(toFloat.parseValue(parser)); - } - - @Override - public void processMissing(JsonParser parser) throws IOException { - out.add(toFloat.parseMissing(parser)); - } - - interface ToFloat { - - float parseValue(JsonParser parser) throws IOException; - - float parseMissing(JsonParser parser) throws IOException; - } -} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/GenericObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/GenericObjectMixin.java new file mode 100644 index 00000000000..0a87f2f566a --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/GenericObjectMixin.java @@ -0,0 +1,76 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableObjectChunk; +import io.deephaven.json.Value; +import io.deephaven.qst.type.GenericType; +import io.deephaven.qst.type.Type; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +abstract class GenericObjectMixin extends Mixin implements ToObject { + private final GenericType type; + + public GenericObjectMixin(JsonFactory factory, T options, GenericType type) { + super(factory, options); + this.type = Objects.requireNonNull(type); + } + + @Override + public final int outputSize() { + return 1; + } + + @Override + final Stream> paths() { + return Stream.of(List.of()); + } + + @Override + final Stream> outputTypesImpl() { + return Stream.of(type); + } + + @Override + final ValueProcessor processor(String context) { + return new GenericObjectMixinProcessor(); + } + + @Override + final RepeaterProcessor repeaterProcessor() { + return new RepeaterGenericImpl<>(this, null, null, type.arrayType()); + } + + private class GenericObjectMixinProcessor extends ValueProcessorMixinBase { + + private WritableObjectChunk out; + + @Override + public final void setContext(List> out) { + this.out = out.get(0).asWritableObjectChunk(); + } + + @Override + public final void clearContext() { + out = null; + } + + @Override + protected void processCurrentValueImpl(JsonParser parser) throws IOException { + out.add(parseValue(parser)); + } + + @Override + protected void processMissingImpl(JsonParser parser) throws IOException { + out.add(parseMissing(parser)); + } + } +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java index 24c87db3f29..7084213bbcf 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantMixin.java @@ -5,9 +5,9 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableLongChunk; import io.deephaven.json.InstantValue; -import io.deephaven.json.jackson.LongValueProcessor.ToLong; -import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; import io.deephaven.time.DateTimeUtils; @@ -18,7 +18,7 @@ import java.util.List; import java.util.stream.Stream; -final class InstantMixin extends Mixin implements ToLong { +final class InstantMixin extends Mixin { private final long onNull; private final long onMissing; @@ -46,11 +46,10 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new LongValueProcessor(this); + return new InstantMixinProcessor(); } - @Override - public long parseValue(JsonParser parser) throws IOException { + private long parseValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { case VALUE_STRING: case FIELD_NAME: @@ -61,8 +60,7 @@ public long parseValue(JsonParser parser) throws IOException { throw unexpectedToken(parser); } - @Override - public long parseMissing(JsonParser parser) throws IOException { + private long parseMissing(JsonParser parser) throws IOException { return parseFromMissing(parser); } @@ -121,4 +119,28 @@ private Instant parseFromMissingToInstant(JsonParser parser) throws IOException checkMissingAllowed(parser); return options.onMissing().orElse(null); } + + private class InstantMixinProcessor extends ValueProcessorMixinBase { + private WritableLongChunk out; + + @Override + public final void setContext(List> out) { + this.out = out.get(0).asWritableLongChunk(); + } + + @Override + public final void clearContext() { + out = null; + } + + @Override + protected void processCurrentValueImpl(JsonParser parser) throws IOException { + out.add(parseValue(parser)); + } + + @Override + protected void processMissingImpl(JsonParser parser) throws IOException { + out.add(parseMissing(parser)); + } + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java index be0fbe2195b..f8bd7befdb7 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/InstantNumberMixin.java @@ -5,9 +5,9 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.chunk.WritableLongChunk; import io.deephaven.json.InstantNumberValue; -import io.deephaven.json.jackson.LongValueProcessor.ToLong; -import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; import io.deephaven.time.DateTimeUtils; @@ -15,6 +15,7 @@ import java.math.BigInteger; import java.time.Instant; import java.util.List; +import java.util.Objects; import java.util.stream.Stream; final class InstantNumberMixin extends Mixin { @@ -45,7 +46,7 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new LongValueProcessor(longFunction()); + return new InstantNumberMixinProcessor(longFunction()); } @Override @@ -54,7 +55,7 @@ RepeaterProcessor repeaterProcessor() { Type.instantType().arrayType()); } - private LongValueProcessor.ToLong longFunction() { + private LongImpl longFunction() { switch (options.format()) { case EPOCH_SECONDS: return new LongImpl(9); @@ -69,7 +70,7 @@ private LongValueProcessor.ToLong longFunction() { } } - private class LongImpl implements LongValueProcessor.ToLong { + private class LongImpl { private final int scaled; private final int mult; @@ -99,7 +100,6 @@ private long parseFromDecimalString(JsonParser parser) throws IOException { return Parsing.parseDecimalStringAsScaledLong(parser, scaled); } - @Override public final long parseValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { case VALUE_NUMBER_INT: @@ -121,7 +121,6 @@ public final long parseValue(JsonParser parser) throws IOException { throw unexpectedToken(parser); } - @Override public final long parseMissing(JsonParser parser) throws IOException { checkMissingAllowed(parser); return onMissing; @@ -130,7 +129,7 @@ public final long parseMissing(JsonParser parser) throws IOException { private class ObjectImpl implements ToObject { - private final ToLong longImpl; + private final LongImpl longImpl; public ObjectImpl() { this.longImpl = longFunction(); @@ -146,4 +145,34 @@ public Instant parseMissing(JsonParser parser) throws IOException { return DateTimeUtils.epochNanosToInstant(longImpl.parseValue(parser)); } } + + private class InstantNumberMixinProcessor extends ValueProcessorMixinBase { + private final LongImpl impl; + + private WritableLongChunk out; + + public InstantNumberMixinProcessor(LongImpl impl) { + this.impl = Objects.requireNonNull(impl); + } + + @Override + public final void setContext(List> out) { + this.out = out.get(0).asWritableLongChunk(); + } + + @Override + public final void clearContext() { + out = null; + } + + @Override + protected void processCurrentValueImpl(JsonParser parser) throws IOException { + out.add(impl.parseValue(parser)); + } + + @Override + protected void processMissingImpl(JsonParser parser) throws IOException { + out.add(impl.parseMissing(parser)); + } + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java index 7bae0c7532d..40cd67fcfc5 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntMixin.java @@ -6,10 +6,10 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.base.MathUtil; +import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableIntChunk; import io.deephaven.chunk.sized.SizedIntChunk; import io.deephaven.json.IntValue; -import io.deephaven.json.jackson.IntValueProcessor.ToInt; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -18,7 +18,7 @@ import java.util.List; import java.util.stream.Stream; -final class IntMixin extends Mixin implements ToInt { +final class IntMixin extends Mixin { public IntMixin(IntValue options, JsonFactory factory) { super(factory, options); @@ -41,11 +41,10 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new IntValueProcessor(this); + return new IntMixinProcessor(); } - @Override - public int parseValue(JsonParser parser) throws IOException { + private int parseValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { case VALUE_NUMBER_INT: return parseFromInt(parser); @@ -60,8 +59,7 @@ public int parseValue(JsonParser parser) throws IOException { throw unexpectedToken(parser); } - @Override - public int parseMissing(JsonParser parser) throws IOException { + private int parseMissing(JsonParser parser) throws IOException { return parseFromMissing(parser); } @@ -126,4 +124,29 @@ private int parseFromMissing(JsonParser parser) throws IOException { checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_INT); } + + final class IntMixinProcessor extends ValueProcessorMixinBase { + + private WritableIntChunk out; + + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableIntChunk(); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + protected void processCurrentValueImpl(JsonParser parser) throws IOException { + out.add(parseValue(parser)); + } + + @Override + protected void processMissingImpl(JsonParser parser) throws IOException { + out.add(parseMissing(parser)); + } + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java deleted file mode 100644 index 7dcdba768db..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/IntValueProcessor.java +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; -import io.deephaven.chunk.WritableIntChunk; -import io.deephaven.qst.type.Type; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -final class IntValueProcessor implements ValueProcessor { - - private WritableIntChunk out; - private final ToInt toInt; - - IntValueProcessor(ToInt toInt) { - this.toInt = Objects.requireNonNull(toInt); - } - - @Override - public void setContext(List> out) { - this.out = out.get(0).asWritableIntChunk(); - } - - @Override - public void clearContext() { - out = null; - } - - @Override - public int numColumns() { - return 1; - } - - @Override - public Stream> columnTypes() { - return Stream.of(Type.intType()); - } - - @Override - public void processCurrentValue(JsonParser parser) throws IOException { - out.add(toInt.parseValue(parser)); - } - - @Override - public void processMissing(JsonParser parser) throws IOException { - out.add(toInt.parseMissing(parser)); - } - - interface ToInt { - - int parseValue(JsonParser parser) throws IOException; - - int parseMissing(JsonParser parser) throws IOException; - } -} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java index 8de65f18a0f..2ad69c30932 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LocalDateMixin.java @@ -6,39 +6,16 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.json.LocalDateValue; -import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; import java.io.IOException; import java.time.LocalDate; import java.time.temporal.TemporalAccessor; -import java.util.List; -import java.util.stream.Stream; -final class LocalDateMixin extends Mixin implements ToObject { +final class LocalDateMixin extends GenericObjectMixin { public LocalDateMixin(LocalDateValue options, JsonFactory factory) { - super(factory, options); - } - - @Override - public int outputSize() { - return 1; - } - - @Override - public Stream> paths() { - return Stream.of(List.of()); - } - - @Override - public Stream> outputTypesImpl() { - return Stream.of(Type.ofCustom(LocalDate.class)); - } - - @Override - public ValueProcessor processor(String context) { - return new ObjectValueProcessor<>(this, Type.ofCustom(LocalDate.class)); + super(factory, options, Type.ofCustom(LocalDate.class)); } @Override @@ -58,12 +35,6 @@ public LocalDate parseMissing(JsonParser parser) throws IOException { return parseFromMissing(parser); } - @Override - RepeaterProcessor repeaterProcessor() { - return new RepeaterGenericImpl<>(this, null, null, - Type.ofCustom(LocalDate.class).arrayType()); - } - private LocalDate parseFromString(JsonParser parser) throws IOException { final TemporalAccessor accessor = options.dateTimeFormatter().parse(Parsing.textAsCharSequence(parser)); return LocalDate.from(accessor); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java index 97d6ccb68ad..c7a69e7f47f 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongMixin.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.base.MathUtil; +import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableLongChunk; import io.deephaven.chunk.sized.SizedLongChunk; import io.deephaven.json.LongValue; @@ -17,7 +18,7 @@ import java.util.List; import java.util.stream.Stream; -final class LongMixin extends Mixin implements LongValueProcessor.ToLong { +final class LongMixin extends Mixin { public LongMixin(LongValue options, JsonFactory config) { super(config, options); @@ -40,11 +41,10 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new LongValueProcessor(this); + return new LongMixinProcessor(); } - @Override - public long parseValue(JsonParser parser) throws IOException { + private long parseValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { case VALUE_NUMBER_INT: return parseFromInt(parser); @@ -59,8 +59,7 @@ public long parseValue(JsonParser parser) throws IOException { throw unexpectedToken(parser); } - @Override - public long parseMissing(JsonParser parser) throws IOException { + private long parseMissing(JsonParser parser) throws IOException { return parseFromMissing(parser); } @@ -125,4 +124,28 @@ private long parseFromMissing(JsonParser parser) throws IOException { checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_LONG); } + + private class LongMixinProcessor extends ValueProcessorMixinBase { + private WritableLongChunk out; + + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableLongChunk(); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + protected void processCurrentValueImpl(JsonParser parser) throws IOException { + out.add(parseValue(parser)); + } + + @Override + protected void processMissingImpl(JsonParser parser) throws IOException { + out.add(parseMissing(parser)); + } + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java deleted file mode 100644 index 5e15f4c1342..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/LongValueProcessor.java +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; -import io.deephaven.chunk.WritableLongChunk; -import io.deephaven.qst.type.Type; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -final class LongValueProcessor implements ValueProcessor { - private WritableLongChunk out; - private final ToLong toLong; - - LongValueProcessor(ToLong toLong) { - this.toLong = Objects.requireNonNull(toLong); - } - - @Override - public void setContext(List> out) { - this.out = out.get(0).asWritableLongChunk(); - } - - @Override - public void clearContext() { - out = null; - } - - @Override - public int numColumns() { - return 1; - } - - @Override - public Stream> columnTypes() { - return Stream.of(Type.longType()); - } - - @Override - public void processCurrentValue(JsonParser parser) throws IOException { - out.add(toLong.parseValue(parser)); - } - - @Override - public void processMissing(JsonParser parser) throws IOException { - out.add(toLong.parseMissing(parser)); - } - - interface ToLong { - - long parseValue(JsonParser parser) throws IOException; - - long parseMissing(JsonParser parser) throws IOException; - } -} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index c3d5776f2e3..75a70e0c05c 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -161,7 +161,7 @@ public final ObjectProcessor charBufferProcessor() { abstract Stream> paths(); - abstract Stream> outputTypesImpl(); + abstract Stream> outputTypesImpl(); static List prefixWith(String prefix, List path) { return Stream.concat(Stream.of(prefix), path.stream()).collect(Collectors.toList()); @@ -468,4 +468,54 @@ final IOException unexpectedToken(JsonParser parser) throws ValueAwareException } throw new ValueAwareException(msg, parser.currentLocation(), options); } + + abstract class ValueProcessorMixinBase implements ValueProcessor { + @Override + public final int numColumns() { + return Mixin.this.outputSize(); + } + + @Override + public final Stream> columnTypes() { + return Mixin.this.outputTypesImpl(); + } + + @Override + public final void processCurrentValue(JsonParser parser) throws IOException { + try { + processCurrentValueImpl(parser); + } catch (ValueAwareException e) { + if (options.equals(e.value())) { + throw e; + } else { + throw wrap(parser, e, "Unable to process current value"); + } + } catch (IOException e) { + throw wrap(parser, e, "Unable to process current value"); + } + } + + @Override + public final void processMissing(JsonParser parser) throws IOException { + try { + processMissingImpl(parser); + } catch (ValueAwareException e) { + if (options.equals(e.value())) { + throw e; + } else { + throw wrap(parser, e, "Unable to process missing value"); + } + } catch (IOException e) { + throw wrap(parser, e, "Unable to process missing value"); + } + } + + protected abstract void processCurrentValueImpl(JsonParser parser) throws IOException; + + protected abstract void processMissingImpl(JsonParser parser) throws IOException; + + private ValueAwareException wrap(JsonParser parser, IOException e, String msg) { + return new ValueAwareException(msg, parser.currentLocation(), e, options); + } + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java index ecbe9ec1c9f..c3d55c62300 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; import io.deephaven.json.ObjectKvValue; -import io.deephaven.qst.type.NativeArrayType; import io.deephaven.qst.type.Type; import java.io.IOException; @@ -25,7 +24,7 @@ public ObjectKvMixin(ObjectKvValue options, JsonFactory factory) { } @Override - public Stream> outputTypesImpl() { + public Stream> outputTypesImpl() { return Stream.concat(key.outputTypesImpl(), value.outputTypesImpl()).map(Type::arrayType); } @@ -49,20 +48,20 @@ public Stream> paths() { @Override public ValueProcessor processor(String context) { - return new ValueProcessorKvImpl(); + return new ObjectKvMixinProcessor(); } @Override RepeaterProcessor repeaterProcessor() { - return new ValueInnerRepeaterProcessor(new ValueProcessorKvImpl()); + return new ValueInnerRepeaterProcessor(new ObjectKvMixinProcessor()); } - private class ValueProcessorKvImpl implements ValueProcessor { + private class ObjectKvMixinProcessor extends ValueProcessorMixinBase { private final RepeaterProcessor keyProcessor; private final RepeaterProcessor valueProcessor; - ValueProcessorKvImpl() { + ObjectKvMixinProcessor() { this.keyProcessor = key.repeaterProcessor(); this.valueProcessor = value.repeaterProcessor(); } @@ -81,17 +80,7 @@ public void clearContext() { } @Override - public int numColumns() { - return keyProcessor.numColumns() + valueProcessor.numColumns(); - } - - @Override - public Stream> columnTypes() { - return Stream.concat(keyProcessor.columnTypes(), valueProcessor.columnTypes()); - } - - @Override - public void processCurrentValue(JsonParser parser) throws IOException { + protected void processCurrentValueImpl(JsonParser parser) throws IOException { switch (parser.currentToken()) { case START_OBJECT: RepeaterProcessor.processObjectKeyValues(parser, keyProcessor, valueProcessor); @@ -107,7 +96,7 @@ public void processCurrentValue(JsonParser parser) throws IOException { } @Override - public void processMissing(JsonParser parser) throws IOException { + protected void processMissingImpl(JsonParser parser) throws IOException { checkMissingAllowed(parser); keyProcessor.processMissingRepeater(parser); valueProcessor.processMissingRepeater(parser); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java deleted file mode 100644 index 6228cebe366..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectValueProcessor.java +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; -import io.deephaven.chunk.WritableObjectChunk; -import io.deephaven.qst.type.GenericType; -import io.deephaven.qst.type.Type; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -final class ObjectValueProcessor implements ValueProcessor { - - private WritableObjectChunk out; - private final ToObject toObj; - private final GenericType type; - - ObjectValueProcessor(ToObject toObj, GenericType type) { - this.toObj = Objects.requireNonNull(toObj); - this.type = Objects.requireNonNull(type); - } - - @Override - public void setContext(List> out) { - this.out = out.get(0).asWritableObjectChunk(); - } - - @Override - public void clearContext() { - out = null; - } - - @Override - public int numColumns() { - return 1; - } - - @Override - public Stream> columnTypes() { - return Stream.of(type); - } - - @Override - public void processCurrentValue(JsonParser parser) throws IOException { - out.add(toObj.parseValue(parser)); - } - - @Override - public void processMissing(JsonParser parser) throws IOException { - out.add(toObj.parseMissing(parser)); - } - - interface ToObject { - - T parseValue(JsonParser parser) throws IOException; - - T parseMissing(JsonParser parser) throws IOException; - } -} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java index aeb018b40b0..0f2ba69774e 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/RepeaterGenericImpl.java @@ -7,7 +7,6 @@ import io.deephaven.base.MathUtil; import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.chunk.sized.SizedObjectChunk; -import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.NativeArrayType; import java.io.IOException; diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java index 9ef5624bbab..786491b1863 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortMixin.java @@ -6,10 +6,10 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.base.MathUtil; +import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableShortChunk; import io.deephaven.chunk.sized.SizedShortChunk; import io.deephaven.json.ShortValue; -import io.deephaven.json.jackson.ShortValueProcessor.ToShort; import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; @@ -18,7 +18,7 @@ import java.util.List; import java.util.stream.Stream; -final class ShortMixin extends Mixin implements ToShort { +final class ShortMixin extends Mixin { public ShortMixin(ShortValue options, JsonFactory factory) { super(factory, options); } @@ -40,11 +40,10 @@ public Stream> outputTypesImpl() { @Override public ValueProcessor processor(String context) { - return new ShortValueProcessor(this); + return new ShortMixinProcessor(); } - @Override - public short parseValue(JsonParser parser) throws IOException { + private short parseValue(JsonParser parser) throws IOException { switch (parser.currentToken()) { case VALUE_NUMBER_INT: return parseFromInt(parser); @@ -59,8 +58,7 @@ public short parseValue(JsonParser parser) throws IOException { throw unexpectedToken(parser); } - @Override - public short parseMissing(JsonParser parser) throws IOException { + private short parseMissing(JsonParser parser) throws IOException { return parseFromMissing(parser); } @@ -125,4 +123,29 @@ private short parseFromMissing(JsonParser parser) throws IOException { checkMissingAllowed(parser); return options.onMissing().orElse(QueryConstants.NULL_SHORT); } + + final class ShortMixinProcessor extends ValueProcessorMixinBase { + + private WritableShortChunk out; + + @Override + public void setContext(List> out) { + this.out = out.get(0).asWritableShortChunk(); + } + + @Override + public void clearContext() { + out = null; + } + + @Override + protected void processCurrentValueImpl(JsonParser parser) throws IOException { + out.add(parseValue(parser)); + } + + @Override + protected void processMissingImpl(JsonParser parser) throws IOException { + out.add(parseMissing(parser)); + } + } } diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java deleted file mode 100644 index 49aef7ac614..00000000000 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ShortValueProcessor.java +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.json.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import io.deephaven.chunk.WritableChunk; -import io.deephaven.chunk.WritableShortChunk; -import io.deephaven.qst.type.Type; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -final class ShortValueProcessor implements ValueProcessor { - - private WritableShortChunk out; - private final ToShort toShort; - - ShortValueProcessor(ToShort toShort) { - this.toShort = Objects.requireNonNull(toShort); - } - - @Override - public void setContext(List> out) { - this.out = out.get(0).asWritableShortChunk(); - } - - @Override - public void clearContext() { - out = null; - } - - @Override - public int numColumns() { - return 1; - } - - @Override - public Stream> columnTypes() { - return Stream.of(Type.shortType()); - } - - @Override - public void processCurrentValue(JsonParser parser) throws IOException { - out.add(toShort.parseValue(parser)); - } - - @Override - public void processMissing(JsonParser parser) throws IOException { - out.add(toShort.parseMissing(parser)); - } - - interface ToShort { - - short parseValue(JsonParser parser) throws IOException; - - short parseMissing(JsonParser parser) throws IOException; - } -} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java index ff042224cc8..b291a0000d9 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/SkipMixin.java @@ -25,6 +25,11 @@ public int outputSize() { return 0; } + @Override + public int numColumns() { + return 0; + } + @Override public Stream> paths() { return Stream.empty(); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java index a5b53f7b82b..19c64504430 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/StringMixin.java @@ -6,37 +6,14 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.json.StringValue; -import io.deephaven.json.jackson.ObjectValueProcessor.ToObject; import io.deephaven.qst.type.Type; import java.io.IOException; -import java.util.List; -import java.util.stream.Stream; -final class StringMixin extends Mixin implements ToObject { +final class StringMixin extends GenericObjectMixin { public StringMixin(StringValue options, JsonFactory factory) { - super(factory, options); - } - - @Override - public int outputSize() { - return 1; - } - - @Override - public Stream> paths() { - return Stream.of(List.of()); - } - - @Override - public Stream> outputTypesImpl() { - return Stream.of(Type.stringType()); - } - - @Override - public ValueProcessor processor(String context) { - return new ObjectValueProcessor<>(this, Type.stringType()); + super(factory, options, Type.stringType()); } @Override @@ -63,11 +40,6 @@ public String parseMissing(JsonParser parser) throws IOException { return parseFromMissing(parser); } - @Override - RepeaterProcessor repeaterProcessor() { - return new RepeaterGenericImpl<>(this, null, null, Type.stringType().arrayType()); - } - private String parseFromString(JsonParser parser) throws IOException { checkStringAllowed(parser); return Parsing.parseStringAsString(parser); diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ToObject.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ToObject.java new file mode 100644 index 00000000000..8f91cc089ce --- /dev/null +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ToObject.java @@ -0,0 +1,15 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.json.jackson; + +import com.fasterxml.jackson.core.JsonParser; + +import java.io.IOException; + +interface ToObject { + + T parseValue(JsonParser parser) throws IOException; + + T parseMissing(JsonParser parser) throws IOException; +} diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java index 865e3a5351b..62ea03ecc8c 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/TypedObjectMixin.java @@ -39,8 +39,13 @@ public TypedObjectMixin(TypedObjectValue options, JsonFactory factory) { if (!(options.typeField().options() instanceof StringValue)) { throw new IllegalArgumentException("Only string-valued type fields are currently supported"); } - if (!(options.onNull().orElse(null) instanceof String)) { - + final Object onNull = options.onNull().orElse(null); + if (onNull != null && !(onNull instanceof String)) { + throw new IllegalArgumentException("Only String onNull values are currently supported"); + } + final Object onMissing = options.onMissing().orElse(null); + if (onMissing != null && !(onMissing instanceof String)) { + throw new IllegalArgumentException("Only String onMissing values are currently supported"); } typeFieldAliases = options.typeField().caseSensitive() ? null : new TreeSet<>(String.CASE_INSENSITIVE_ORDER); { @@ -179,7 +184,7 @@ void notApplicable() { } } - private class DiscriminatedProcessor implements ValueProcessor { + private class DiscriminatedProcessor extends ValueProcessorMixinBase { private final Map combinedProcessors; @@ -214,17 +219,7 @@ public void clearContext() { } @Override - public int numColumns() { - return TypedObjectMixin.this.outputSize(); - } - - @Override - public Stream> columnTypes() { - return outputTypesImpl(); - } - - @Override - public void processCurrentValue(JsonParser parser) throws IOException { + protected void processCurrentValueImpl(JsonParser parser) throws IOException { switch (parser.currentToken()) { case START_OBJECT: if (parser.nextToken() == JsonToken.END_OBJECT) { @@ -245,7 +240,7 @@ public void processCurrentValue(JsonParser parser) throws IOException { } @Override - public void processMissing(JsonParser parser) throws IOException { + protected void processMissingImpl(JsonParser parser) throws IOException { checkMissingAllowed(parser); typeChunk.add((String) options.onMissing().orElse(null)); // We are _not_ trying to pass along the potential "on missing" value for each individual chunk; the diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueAwareException.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueAwareException.java index aa7673adb78..90027d1e783 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueAwareException.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ValueAwareException.java @@ -23,6 +23,10 @@ public ValueAwareException(String msg, JsonLocation loc, Throwable cause, Value this.value = Objects.requireNonNull(valueContext); } + public Value value() { + return value; + } + @Override protected String getMessageSuffix() { return " for " + value; diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/BigDecimalValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/BigDecimalValueTest.java index d14a311b13a..05db216d2b5 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/BigDecimalValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/BigDecimalValueTest.java @@ -4,6 +4,8 @@ package io.deephaven.json; import io.deephaven.chunk.ObjectChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -17,6 +19,21 @@ public class BigDecimalValueTest { + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(BigDecimalValue.standard()); + assertThat(provider.outputTypes()).containsExactly(Type.ofCustom(BigDecimal.class)); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.ofCustom(BigDecimal.class)); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(BigDecimalValue.standard().array()); + assertThat(provider.outputTypes()).containsExactly(Type.ofCustom(BigDecimal.class).arrayType()); + assertThat(provider.stringProcessor().outputTypes()) + .containsExactly(Type.ofCustom(BigDecimal.class).arrayType()); + } + @Test void standard() throws IOException { parse(BigDecimalValue.standard(), "42.42", ObjectChunk.chunkWrap(new BigDecimal[] {new BigDecimal("42.42")})); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/BigIntegerValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/BigIntegerValueTest.java index c04347b822a..f57d1d217b0 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/BigIntegerValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/BigIntegerValueTest.java @@ -4,9 +4,12 @@ package io.deephaven.json; import io.deephaven.chunk.ObjectChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.math.BigDecimal; import java.math.BigInteger; import java.util.List; @@ -17,6 +20,21 @@ public class BigIntegerValueTest { + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(BigIntegerValue.standard(false)); + assertThat(provider.outputTypes()).containsExactly(Type.ofCustom(BigInteger.class)); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.ofCustom(BigInteger.class)); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(BigIntegerValue.standard(false).array()); + assertThat(provider.outputTypes()).containsExactly(Type.ofCustom(BigInteger.class).arrayType()); + assertThat(provider.stringProcessor().outputTypes()) + .containsExactly(Type.ofCustom(BigInteger.class).arrayType()); + } + @Test void standard() throws IOException { parse(BigIntegerValue.standard(false), "42", ObjectChunk.chunkWrap(new BigInteger[] {BigInteger.valueOf(42)})); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/BoolValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/BoolValueTest.java index e7a2ef4f98a..29adc524f5b 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/BoolValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/BoolValueTest.java @@ -4,6 +4,8 @@ package io.deephaven.json; import io.deephaven.chunk.ByteChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import io.deephaven.util.BooleanUtils; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -18,6 +20,21 @@ public class BoolValueTest { + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(BoolValue.standard()); + assertThat(provider.outputTypes()).containsExactly(Type.booleanType().boxedType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.booleanType().boxedType()); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(BoolValue.standard().array()); + assertThat(provider.outputTypes()).containsExactly(Type.booleanType().boxedType().arrayType()); + assertThat(provider.stringProcessor().outputTypes()) + .containsExactly(Type.booleanType().boxedType().arrayType()); + } + @Test void standard() throws IOException { parse(BoolValue.standard(), List.of("true", "false"), ByteChunk diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ByteValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteValueTest.java index f1316613f2b..244e4ac2324 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ByteValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ByteValueTest.java @@ -4,6 +4,8 @@ package io.deephaven.json; import io.deephaven.chunk.ByteChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -17,6 +19,20 @@ public class ByteValueTest { + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(ByteValue.standard()); + assertThat(provider.outputTypes()).containsExactly(Type.byteType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.byteType()); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(ByteValue.standard().array()); + assertThat(provider.outputTypes()).containsExactly(Type.byteType().arrayType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.byteType().arrayType()); + } + @Test void standard() throws IOException { parse(ByteValue.standard(), "42", ByteChunk.chunkWrap(new byte[] {42})); @@ -67,14 +83,30 @@ void strictNull() { } } + @Test + void standardUnderflow() { + try { + process(ByteValue.standard(), "-129"); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unable to process current value for ByteValue"); + assertThat(e).hasCauseInstanceOf(IOException.class); + assertThat(e.getCause()).hasMessageContaining( + "Numeric value (-129) out of range of Java byte"); + } + } + @Test void standardOverflow() { + // Jackson has non-standard byte processing try { - process(ByteValue.standard(), "2147483648"); + process(ByteValue.standard(), "256"); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining( - "Numeric value (2147483648) out of range of int (-2147483648 - 2147483647)"); + assertThat(e).hasMessageContaining("Unable to process current value for ByteValue"); + assertThat(e).hasCauseInstanceOf(IOException.class); + assertThat(e.getCause()).hasMessageContaining( + "Numeric value (256) out of range of Java byte"); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/CharValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/CharValueTest.java index eda160965bc..bd0fea9eda3 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/CharValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/CharValueTest.java @@ -4,6 +4,8 @@ package io.deephaven.json; import io.deephaven.chunk.CharChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -16,6 +18,20 @@ public class CharValueTest { + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(CharValue.standard()); + assertThat(provider.outputTypes()).containsExactly(Type.charType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.charType()); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(CharValue.standard().array()); + assertThat(provider.outputTypes()).containsExactly(Type.charType().arrayType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.charType().arrayType()); + } + @Test void standard() throws IOException { parse(CharValue.standard(), "\"c\"", CharChunk.chunkWrap(new char[] {'c'})); @@ -127,4 +143,16 @@ void standardArray() { assertThat(e).hasMessageContaining("Array not expected"); } } + + @Test + void stringTooBig() { + try { + process(CharValue.standard(), "\"ABC\""); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unable to process current value for CharValue"); + assertThat(e).hasCauseInstanceOf(IOException.class); + assertThat(e.getCause()).hasMessageContaining("Expected char to be string of length 1"); + } + } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleValueTest.java index 7be9ef541f2..53092c41f7f 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/DoubleValueTest.java @@ -4,6 +4,8 @@ package io.deephaven.json; import io.deephaven.chunk.DoubleChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -17,6 +19,20 @@ public class DoubleValueTest { + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(DoubleValue.standard()); + assertThat(provider.outputTypes()).containsExactly(Type.doubleType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.doubleType()); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(DoubleValue.standard().array()); + assertThat(provider.outputTypes()).containsExactly(Type.doubleType().arrayType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.doubleType().arrayType()); + } + @Test void standard() throws IOException { parse(DoubleValue.standard(), List.of("42", "42.42"), DoubleChunk.chunkWrap(new double[] {42, 42.42})); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/FloatValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/FloatValueTest.java index 6050d2149f1..f5465dcb2ce 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/FloatValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/FloatValueTest.java @@ -4,6 +4,8 @@ package io.deephaven.json; import io.deephaven.chunk.FloatChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -17,6 +19,20 @@ public class FloatValueTest { + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(FloatValue.standard()); + assertThat(provider.outputTypes()).containsExactly(Type.floatType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.floatType()); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(FloatValue.standard().array()); + assertThat(provider.outputTypes()).containsExactly(Type.floatType().arrayType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.floatType().arrayType()); + } + @Test void standard() throws IOException { parse(FloatValue.standard(), List.of("42", "42.42"), FloatChunk.chunkWrap(new float[] {42, 42.42f})); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberTest.java index 0e67dc20aaa..73fa3a3128d 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantNumberTest.java @@ -5,11 +5,14 @@ import io.deephaven.chunk.LongChunk; import io.deephaven.json.InstantNumberValue.Format; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import org.junit.jupiter.api.Test; import java.io.IOException; import static io.deephaven.json.TestHelper.parse; +import static org.assertj.core.api.Assertions.assertThat; public class InstantNumberTest { private static final long WITH_SECONDS = 1703292532000000000L; @@ -17,6 +20,20 @@ public class InstantNumberTest { private static final long WITH_MICROS = 1703292532123456000L; private static final long WITH_NANOS = 1703292532123456789L; + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(Format.EPOCH_SECONDS.standard(false)); + assertThat(provider.outputTypes()).containsExactly(Type.instantType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.instantType()); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(Format.EPOCH_SECONDS.standard(false).array()); + assertThat(provider.outputTypes()).containsExactly(Type.instantType().arrayType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.instantType().arrayType()); + } + @Test void epochSeconds() throws IOException { parse(Format.EPOCH_SECONDS.standard(false), "1703292532", LongChunk.chunkWrap(new long[] {WITH_SECONDS})); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantValueTest.java index be84ae96bb1..7b85dcf7cdb 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/InstantValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/InstantValueTest.java @@ -4,6 +4,9 @@ package io.deephaven.json; import io.deephaven.chunk.LongChunk; +import io.deephaven.json.InstantNumberValue.Format; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -20,6 +23,20 @@ public class InstantValueTest { private static final String XYZ_STR = "2009-02-13T23:31:30.123456789"; private static final long XYZ_NANOS = 1234567890L * 1_000_000_000 + 123456789; + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(InstantValue.standard()); + assertThat(provider.outputTypes()).containsExactly(Type.instantType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.instantType()); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(InstantValue.standard().array()); + assertThat(provider.outputTypes()).containsExactly(Type.instantType().arrayType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.instantType().arrayType()); + } + @Test void iso8601() throws IOException { parse(InstantValue.standard(), "\"" + XYZ_STR + "Z\"", LongChunk.chunkWrap(new long[] {XYZ_NANOS})); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/IntValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/IntValueTest.java index 1ad7768a5d6..ea915d13ddc 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/IntValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/IntValueTest.java @@ -4,6 +4,8 @@ package io.deephaven.json; import io.deephaven.chunk.IntChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -17,6 +19,20 @@ public class IntValueTest { + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(IntValue.standard()); + assertThat(provider.outputTypes()).containsExactly(Type.intType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.intType()); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(IntValue.standard().array()); + assertThat(provider.outputTypes()).containsExactly(Type.intType().arrayType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.intType().arrayType()); + } + @Test void standard() throws IOException { parse(IntValue.standard(), "42", IntChunk.chunkWrap(new int[] {42})); @@ -67,13 +83,28 @@ void strictNull() { } } + @Test + void standardUnderflow() { + try { + process(IntValue.standard(), Long.toString(Integer.MIN_VALUE - 1L)); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unable to process current value for IntValue"); + assertThat(e).hasCauseInstanceOf(IOException.class); + assertThat(e.getCause()).hasMessageContaining( + "Numeric value (-2147483649) out of range of int (-2147483648 - 2147483647)"); + } + } + @Test void standardOverflow() { try { - process(IntValue.standard(), "2147483648"); + process(IntValue.standard(), Long.toString(Integer.MAX_VALUE + 1L)); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining( + assertThat(e).hasMessageContaining("Unable to process current value for IntValue"); + assertThat(e).hasCauseInstanceOf(IOException.class); + assertThat(e.getCause()).hasMessageContaining( "Numeric value (2147483648) out of range of int (-2147483648 - 2147483647)"); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateValueTest.java index bc4e293d5d7..76a401e039b 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LocalDateValueTest.java @@ -4,6 +4,8 @@ package io.deephaven.json; import io.deephaven.chunk.ObjectChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -18,6 +20,21 @@ public class LocalDateValueTest { private static final String XYZ_STR = "2009-02-13"; + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(LocalDateValue.standard()); + assertThat(provider.outputTypes()).containsExactly(Type.ofCustom(LocalDate.class)); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.ofCustom(LocalDate.class)); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(LocalDateValue.standard().array()); + assertThat(provider.outputTypes()).containsExactly(Type.ofCustom(LocalDate.class).arrayType()); + assertThat(provider.stringProcessor().outputTypes()) + .containsExactly(Type.ofCustom(LocalDate.class).arrayType()); + } + @Test void iso8601() throws IOException { parse(LocalDateValue.standard(), "\"" + XYZ_STR + "\"", diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/LongValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/LongValueTest.java index 50adefcc62c..b59456d913f 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/LongValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/LongValueTest.java @@ -4,10 +4,13 @@ package io.deephaven.json; import io.deephaven.chunk.LongChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.math.BigInteger; import java.util.List; import static io.deephaven.json.TestHelper.parse; @@ -17,6 +20,20 @@ public class LongValueTest { + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(LongValue.standard()); + assertThat(provider.outputTypes()).containsExactly(Type.longType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.longType()); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(LongValue.standard().array()); + assertThat(provider.outputTypes()).containsExactly(Type.longType().arrayType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.longType().arrayType()); + } + @Test void standard() throws IOException { parse(LongValue.standard(), List.of("42", "43"), LongChunk.chunkWrap(new long[] {42, 43})); @@ -63,12 +80,27 @@ void strictNull() { } @Test - void strictOverflow() { + void standardUnderflow() { + try { + process(LongValue.standard(), BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE).toString()); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unable to process current value for LongValue"); + assertThat(e).hasCauseInstanceOf(IOException.class); + assertThat(e.getCause()).hasMessageContaining( + "Numeric value (-9223372036854775809) out of range of long (-9223372036854775808 - 9223372036854775807)"); + } + } + + @Test + void standardOverflow() { try { - process(LongValue.strict(), "9223372036854775808"); + process(LongValue.standard(), BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE).toString()); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining( + assertThat(e).hasMessageContaining("Unable to process current value for LongValue"); + assertThat(e).hasCauseInstanceOf(IOException.class); + assertThat(e.getCause()).hasMessageContaining( "Numeric value (9223372036854775808) out of range of long (-9223372036854775808 - 9223372036854775807)"); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectValueTest.java index cb37d52fd65..0cdb6a3ddd5 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectValueTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test; import java.io.IOException; +import java.math.BigDecimal; import java.util.List; import static io.deephaven.json.TestHelper.parse; @@ -29,6 +30,21 @@ public class ObjectValueTest { .putFields("age", IntValue.standard()) .build(); + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(OBJECT_NAME_AGE_FIELD); + assertThat(provider.outputTypes()).containsExactly(Type.stringType(), Type.intType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.stringType(), Type.intType()); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(OBJECT_NAME_AGE_FIELD.array()); + assertThat(provider.outputTypes()).containsExactly(Type.stringType().arrayType(), Type.intType().arrayType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.stringType().arrayType(), + Type.intType().arrayType()); + } + @Test void ofAge() throws IOException { parse(OBJECT_AGE_FIELD, List.of( diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ShortValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortValueTest.java index d09c9ae80ac..85c21576905 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ShortValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ShortValueTest.java @@ -4,6 +4,8 @@ package io.deephaven.json; import io.deephaven.chunk.ShortChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -17,6 +19,20 @@ public class ShortValueTest { + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(ShortValue.standard()); + assertThat(provider.outputTypes()).containsExactly(Type.shortType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.shortType()); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(ShortValue.standard().array()); + assertThat(provider.outputTypes()).containsExactly(Type.shortType().arrayType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.shortType().arrayType()); + } + @Test void standard() throws IOException { parse(ShortValue.standard(), "42", ShortChunk.chunkWrap(new short[] {42})); @@ -67,14 +83,29 @@ void strictNull() { } } + @Test + void standardUnderflow() { + try { + process(ShortValue.standard(), Integer.toString(Short.MIN_VALUE - 1)); + failBecauseExceptionWasNotThrown(IOException.class); + } catch (IOException e) { + assertThat(e).hasMessageContaining("Unable to process current value for ShortValue"); + assertThat(e).hasCauseInstanceOf(IOException.class); + assertThat(e.getCause()).hasMessageContaining( + "Numeric value (-32769) out of range of Java short"); + } + } + @Test void standardOverflow() { try { - process(ShortValue.standard(), "2147483648"); + process(ShortValue.standard(), Integer.toString(Short.MAX_VALUE + 1)); failBecauseExceptionWasNotThrown(IOException.class); } catch (IOException e) { - assertThat(e).hasMessageContaining( - "Numeric value (2147483648) out of range of int (-2147483648 - 2147483647)"); + assertThat(e).hasMessageContaining("Unable to process current value for ShortValue"); + assertThat(e).hasCauseInstanceOf(IOException.class); + assertThat(e.getCause()).hasMessageContaining( + "Numeric value (32768) out of range of Java short"); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java index 529f8c003c5..5344622fce2 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/StringValueTest.java @@ -4,6 +4,8 @@ package io.deephaven.json; import io.deephaven.chunk.ObjectChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -16,6 +18,20 @@ public class StringValueTest { + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(StringValue.standard()); + assertThat(provider.outputTypes()).containsExactly(Type.stringType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.stringType()); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(StringValue.standard().array()); + assertThat(provider.outputTypes()).containsExactly(Type.stringType().arrayType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.stringType().arrayType()); + } + @Test void standard() throws IOException { parse(StringValue.standard(), "\"foo\"", ObjectChunk.chunkWrap(new String[] {"foo"})); diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TupleValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TupleValueTest.java index 18238a835ef..36bb667a63c 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TupleValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TupleValueTest.java @@ -5,6 +5,8 @@ import io.deephaven.chunk.IntChunk; import io.deephaven.chunk.ObjectChunk; +import io.deephaven.json.jackson.JacksonProvider; +import io.deephaven.qst.type.Type; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -23,6 +25,21 @@ public class TupleValueTest { private static final TupleValue STRING_SKIPINT_TUPLE = TupleValue.of(StringValue.standard(), IntValue.standard().skip()); + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(STRING_INT_TUPLE); + assertThat(provider.outputTypes()).containsExactly(Type.stringType(), Type.intType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.stringType(), Type.intType()); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(STRING_INT_TUPLE.array()); + assertThat(provider.outputTypes()).containsExactly(Type.stringType().arrayType(), Type.intType().arrayType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.stringType().arrayType(), + Type.intType().arrayType()); + } + @Test void stringIntTuple() throws IOException { parse(STRING_INT_TUPLE, List.of( diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java index 42d13d6ee9b..c0fc482f452 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TypedObjectValueTest.java @@ -55,6 +55,16 @@ public class TypedObjectValueTest { .onMissing("") .build(); + + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(QUOTE_OR_TRADE_OBJECT); + assertThat(provider.outputTypes()).containsExactly(Type.stringType(), Type.stringType(), Type.longType(), + Type.doubleType(), Type.doubleType(), Type.doubleType(), Type.doubleType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.stringType(), Type.stringType(), + Type.longType(), Type.doubleType(), Type.doubleType(), Type.doubleType(), Type.doubleType()); + } + @Test void typeDiscriminationQuoteTrade() throws IOException { parse(QUOTE_OR_TRADE_OBJECT, List.of( diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyValueTest.java index 308e7331a0a..27aa2859fa3 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/jackson/JacksonAnyValueTest.java @@ -16,11 +16,13 @@ import io.deephaven.chunk.IntChunk; import io.deephaven.chunk.ObjectChunk; import io.deephaven.json.AnyValue; +import io.deephaven.json.CharValue; import io.deephaven.json.DoubleValue; import io.deephaven.json.IntValue; import io.deephaven.json.ObjectValue; import io.deephaven.json.TestHelper; import io.deephaven.json.TupleValue; +import io.deephaven.qst.type.Type; import io.deephaven.util.QueryConstants; import org.junit.jupiter.api.Test; @@ -28,8 +30,24 @@ import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; + public class JacksonAnyValueTest { + @Test + void provider() { + final JacksonProvider provider = JacksonProvider.of(AnyValue.of()); + assertThat(provider.outputTypes()).containsExactly(Type.ofCustom(TreeNode.class)); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.ofCustom(TreeNode.class)); + } + + @Test + void arrayProvider() { + final JacksonProvider provider = JacksonProvider.of(AnyValue.of().array()); + assertThat(provider.outputTypes()).containsExactly(Type.ofCustom(TreeNode.class).arrayType()); + assertThat(provider.stringProcessor().outputTypes()).containsExactly(Type.ofCustom(TreeNode.class).arrayType()); + } + @Test void anyMissing() throws IOException { checkAny("", MissingNode.getInstance()); diff --git a/py/server/deephaven/json/__init__.py b/py/server/deephaven/json/__init__.py index 4b65ea75902..e6f3b438d18 100644 --- a/py/server/deephaven/json/__init__.py +++ b/py/server/deephaven/json/__init__.py @@ -10,9 +10,6 @@ JSON value from a Deephaven structure). """ -# todo: should be on the classpath by default, but doesn't have to be -# todo: would be nice if this code could live in the JSON jar - import jpy from dataclasses import dataclass, field from datetime import datetime @@ -51,8 +48,6 @@ "FieldOptions", ] -# https://deephaven.atlassian.net/browse/DH-15061 -# It is important that ValueOptions gets imported before the others. _JValue = jpy.get_type("io.deephaven.json.Value") _JObjectValue = jpy.get_type("io.deephaven.json.ObjectValue") _JTypedObjectValue = jpy.get_type("io.deephaven.json.TypedObjectValue") @@ -95,7 +90,7 @@ class JsonOptions(JObjectWrapper): - """The JSON options object. Provides a named object processor provider.""" + """The JSON options object.""" j_object_type = _JValue @@ -107,8 +102,6 @@ def j_object(self) -> jpy.JType: return self.j_options -# todo use type alias instead of Any in the future -# todo named tuple JsonValueType = Union[ JsonOptions, dtypes.DType, diff --git a/py/server/deephaven/json/jackson.py b/py/server/deephaven/json/jackson.py index 1449b1c0d3a..83c4860b826 100644 --- a/py/server/deephaven/json/jackson.py +++ b/py/server/deephaven/json/jackson.py @@ -1,14 +1,19 @@ # # Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending # + +"""A JSON processor provider implementation using Jackson.""" + import jpy from typing import Optional -from . import JsonValueType, json as json_ +from . import JsonValueType, json -def json(json_value: JsonValueType, factory: Optional[jpy.JType] = None) -> jpy.JType: +def provider( + json_value: JsonValueType, factory: Optional[jpy.JType] = None +) -> jpy.JType: """Creates a jackson JSON named object processor provider. Args: @@ -18,31 +23,9 @@ def json(json_value: JsonValueType, factory: Optional[jpy.JType] = None) -> jpy. Returns: the jackson JSON named object processor provider """ - # todo: should be on the classpath by default, but doesn't have to be - # todo: would be nice if this code could live in the JSON jar _JProvider = jpy.get_type("io.deephaven.json.jackson.JacksonProvider") return ( - _JProvider.of(json_(json_value).j_options, factory) - if factory - else _JProvider.of(json_(json_value).j_options) - ) - - -def bson(json_value: JsonValueType, factory: Optional[jpy.JType] = None) -> jpy.JType: - """Creates a jackson BSON named object processor provider. - - Args: - json_value(JsonValueType): the JSON value - factory(Optional[jpy.JType]): the factory (java type "de.undercouch.bson4jackson.BsonFactory"), by default is None - - Returns: - the jackson BSON named object processor provider - """ - # todo: not on the classpath by default - # todo: would be nice if this code could live in the BSON jar - _JProvider = jpy.get_type("io.deephaven.bson.jackson.JacksonBsonProvider") - return ( - _JProvider.of(json_(json_value).j_options, factory) + _JProvider.of(json(json_value).j_options, factory) if factory - else _JProvider.of(json_(json_value).j_options) + else _JProvider.of(json(json_value).j_options) ) diff --git a/py/server/deephaven/stream/kafka/consumer.py b/py/server/deephaven/stream/kafka/consumer.py index 8da2a3ec23b..01d33e7463b 100644 --- a/py/server/deephaven/stream/kafka/consumer.py +++ b/py/server/deephaven/stream/kafka/consumer.py @@ -486,7 +486,7 @@ def simple_spec(col_name: str, data_type: DType = None) -> KeyValueSpec: def object_processor_spec(provider: jpy.JType) -> KeyValueSpec: - """Creates a kafka key or value spec implementation from a named object processor provider. It must be capable of + """Creates a kafka key-value spec implementation from a named object processor provider. It must be capable of supporting a byte array. Args: From 6a6f3830edea170c670d84b8420159a631d8151a Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 12 Jun 2024 10:21:35 -0700 Subject: [PATCH 43/53] Add python json tests --- py/server/deephaven/json/__init__.py | 2 +- py/server/tests/test_json.py | 120 +++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 py/server/tests/test_json.py diff --git a/py/server/deephaven/json/__init__.py b/py/server/deephaven/json/__init__.py index e6f3b438d18..35bad2e96fc 100644 --- a/py/server/deephaven/json/__init__.py +++ b/py/server/deephaven/json/__init__.py @@ -400,7 +400,7 @@ def object_kv_( might be modelled as the object kv type .. code-block:: python - object_kv_(value_element=int) + object_kv_(value_type=int) Args: key_type (JsonValueType): the key element, by defaults is type str diff --git a/py/server/tests/test_json.py b/py/server/tests/test_json.py new file mode 100644 index 00000000000..e4fdcd4fd41 --- /dev/null +++ b/py/server/tests/test_json.py @@ -0,0 +1,120 @@ +# +# Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +# + +import unittest + +from datetime import datetime +from tests.testbase import BaseTestCase + +from deephaven import dtypes +from deephaven.json import * + + +def all_equals(items) -> bool: + return len(set(items)) <= 1 + + +class JsonTestCase(BaseTestCase): + def all_same_json_internal(self, items): + self.assertTrue(all_equals([json(x) for x in items])) + + def all_same_json(self, items): + self.all_same_json_internal(items) + # The following might be seen as redundant special cases, but worthwhile for a bit of extra coverage + self.all_same_json_internal([array_(x) for x in items] + [[x] for x in items]) + self.all_same_json_internal([object_kv_(value_type=x) for x in items]) + self.all_same_json_internal([object_({"Foo": x}) for x in items]) + self.all_same_json_internal([tuple_((x,)) for x in items] + [(x,) for x in items]) + self.all_same_json_internal([tuple_({"Bar": x}) for x in items]) + self.all_same_json_internal( + [array_(array_(x)) for x in items] + [[[x]] for x in items] + ) + self.all_same_json_internal([object_kv_(value_type=array_(x)) for x in items]) + + def test_bool(self): + self.all_same_json([bool_(), dtypes.bool_, bool]) + + def test_char(self): + self.all_same_json([char_(), dtypes.char]) + + def test_byte(self): + self.all_same_json([byte_(), dtypes.byte]) + + def test_short(self): + self.all_same_json([short_(), dtypes.short]) + + def test_int(self): + self.all_same_json([int_(), dtypes.int32]) + + def test_long(self): + self.all_same_json([long_(), dtypes.long, int]) + + def test_float(self): + self.all_same_json([float_(), dtypes.float32]) + + def test_double(self): + self.all_same_json([double_(), dtypes.double, float]) + + def test_string(self): + self.all_same_json([string_(), dtypes.string, str]) + + def test_instant(self): + self.all_same_json([instant_(), dtypes.Instant, datetime]) + + def test_any(self): + self.all_same_json([any_(), dtypes.JObject, object]) + + def test_big_integer(self): + self.all_same_json([big_integer_(), dtypes.BigInteger]) + + def test_big_decimal(self): + self.all_same_json([big_decimal_(), dtypes.BigDecimal]) + + def test_object(self): + e1 = [ + {"name": str, "age": int}, + {"name": string_(), "age": long_()}, + {"name": FieldOptions(str), "age": FieldOptions(int)}, + {"name": FieldOptions(string_()), "age": FieldOptions(long_())}, + ] + e2 = [object_(x) for x in e1] + self.all_same_json(e1 + e2) + + def test_array(self): + self.all_same_json( + [ + array_(int), + array_(long_()), + [int], + [long_()], + ] + ) + + def test_tuple(self): + e1 = [(str, int), (string_(), long_())] + e2 = [tuple_(x) for x in e1] + self.all_same_json(e1 + e2) + + with self.subTest("named tuple"): + self.all_same_json( + [ + tuple_({"name": str, "age": int}), + tuple_({"name": string_(), "age": long_()}), + ] + ) + + def test_object_kv(self): + self.all_same_json( + [ + object_kv_(int), + object_kv_(long_()), + ] + ) + + def test_skip(self): + self.all_same_json([skip_()]) + + +if __name__ == "__main__": + unittest.main() From c7fc14b71ab9a339360253362c99f8e869d3ec5f Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 12 Jun 2024 15:06:21 -0700 Subject: [PATCH 44/53] Fix test --- py/server/tests/test_json.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/py/server/tests/test_json.py b/py/server/tests/test_json.py index e4fdcd4fd41..74c82c9b299 100644 --- a/py/server/tests/test_json.py +++ b/py/server/tests/test_json.py @@ -25,7 +25,9 @@ def all_same_json(self, items): self.all_same_json_internal([array_(x) for x in items] + [[x] for x in items]) self.all_same_json_internal([object_kv_(value_type=x) for x in items]) self.all_same_json_internal([object_({"Foo": x}) for x in items]) - self.all_same_json_internal([tuple_((x,)) for x in items] + [(x,) for x in items]) + self.all_same_json_internal( + [tuple_((x,)) for x in items] + [(x,) for x in items] + ) self.all_same_json_internal([tuple_({"Bar": x}) for x in items]) self.all_same_json_internal( [array_(array_(x)) for x in items] + [[[x]] for x in items] @@ -107,8 +109,8 @@ def test_tuple(self): def test_object_kv(self): self.all_same_json( [ - object_kv_(int), - object_kv_(long_()), + object_kv_(value_type=int), + object_kv_(value_type=long_()), ] ) From 10b7794c202709fee9504e32b7606654fa759ee5 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Wed, 12 Jun 2024 16:08:21 -0700 Subject: [PATCH 45/53] Undo ChunkEquals interface changes in light of #5605 --- .../chunk/util/hashing/ChunkEquals.java | 17 +---------------- .../io/deephaven/bson/jackson/TestHelper.java | 12 +++++++++--- .../test/java/io/deephaven/json/TestHelper.java | 12 +++++++++--- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ChunkEquals.java b/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ChunkEquals.java index 5fc83d1fda8..ae1d31e2ab9 100644 --- a/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ChunkEquals.java +++ b/engine/chunk/src/main/java/io/deephaven/chunk/util/hashing/ChunkEquals.java @@ -150,14 +150,6 @@ void andEqualPairs(IntChunk chunkPositionsToCheckForEquality, Ch WritableBooleanChunk destinations); static ChunkEquals makeEqual(ChunkType chunkType) { - return makeEqual(chunkType, ObjectComparison.EQUALS); - } - - enum ObjectComparison { - IDENTITY, EQUALS, DEEP_EQUALS - } - - static ChunkEquals makeEqual(ChunkType chunkType, ObjectComparison objectComparison) { switch (chunkType) { case Boolean: return BooleanChunkEquals.INSTANCE; @@ -176,14 +168,7 @@ static ChunkEquals makeEqual(ChunkType chunkType, ObjectComparison objectCompari case Double: return DoubleChunkEquals.INSTANCE; case Object: - switch (objectComparison) { - case IDENTITY: - return ObjectChunkIdentityEquals.INSTANCE; - case EQUALS: - return ObjectChunkEquals.INSTANCE; - case DEEP_EQUALS: - return ObjectChunkDeepEquals.INSTANCE; - } + return ObjectChunkEquals.INSTANCE; } throw new IllegalStateException(); } diff --git a/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java index 9f73a79247a..70564ee2e5c 100644 --- a/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java +++ b/extensions/bson-jackson/src/test/java/io/deephaven/bson/jackson/TestHelper.java @@ -4,11 +4,12 @@ package io.deephaven.bson.jackson; import io.deephaven.chunk.Chunk; +import io.deephaven.chunk.ChunkType; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.chunk.attributes.Any; import io.deephaven.chunk.util.hashing.ChunkEquals; -import io.deephaven.chunk.util.hashing.ChunkEquals.ObjectComparison; +import io.deephaven.chunk.util.hashing.ObjectChunkDeepEquals; import io.deephaven.processor.ObjectProcessor; import java.io.IOException; @@ -61,7 +62,12 @@ public static void parse(ObjectProcessor processor, List rows, static void check(Chunk actual, Chunk expected) { assertThat(actual.getChunkType()).isEqualTo(expected.getChunkType()); assertThat(actual.size()).isEqualTo(expected.size()); - assertThat(ChunkEquals.makeEqual(actual.getChunkType(), ObjectComparison.DEEP_EQUALS).equalReduce(actual, - expected)).isTrue(); + assertThat(getChunkEquals(actual).equalReduce(actual, expected)).isTrue(); + } + + private static ChunkEquals getChunkEquals(Chunk actual) { + return actual.getChunkType() == ChunkType.Object + ? ObjectChunkDeepEquals.INSTANCE + : ChunkEquals.makeEqual(actual.getChunkType()); } } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java index 1ba6fe15d8d..8c750018fef 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/TestHelper.java @@ -4,11 +4,12 @@ package io.deephaven.json; import io.deephaven.chunk.Chunk; +import io.deephaven.chunk.ChunkType; import io.deephaven.chunk.WritableChunk; import io.deephaven.chunk.WritableObjectChunk; import io.deephaven.chunk.attributes.Any; import io.deephaven.chunk.util.hashing.ChunkEquals; -import io.deephaven.chunk.util.hashing.ChunkEquals.ObjectComparison; +import io.deephaven.chunk.util.hashing.ObjectChunkDeepEquals; import io.deephaven.json.jackson.JacksonProvider; import io.deephaven.processor.ObjectProcessor; @@ -87,7 +88,12 @@ public static List> process(ObjectProcessor proc static void check(Chunk actual, Chunk expected) { assertThat(actual.getChunkType()).isEqualTo(expected.getChunkType()); assertThat(actual.size()).isEqualTo(expected.size()); - assertThat(ChunkEquals.makeEqual(actual.getChunkType(), ObjectComparison.DEEP_EQUALS).equalReduce(actual, - expected)).isTrue(); + assertThat(getChunkEquals(actual).equalReduce(actual, expected)).isTrue(); + } + + private static ChunkEquals getChunkEquals(Chunk actual) { + return actual.getChunkType() == ChunkType.Object + ? ObjectChunkDeepEquals.INSTANCE + : ChunkEquals.makeEqual(actual.getChunkType()); } } From c26304ee1fa84893fbfbde7a1a62c63bcd41e603 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Thu, 13 Jun 2024 09:09:03 -0700 Subject: [PATCH 46/53] Fix on_null, on_missing, add tests --- py/server/deephaven/json/__init__.py | 46 ++++++++++++++++---------- py/server/tests/test_dtypes.py | 2 ++ py/server/tests/test_json.py | 48 ++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 16 deletions(-) diff --git a/py/server/deephaven/json/__init__.py b/py/server/deephaven/json/__init__.py index 35bad2e96fc..068fcc188c7 100644 --- a/py/server/deephaven/json/__init__.py +++ b/py/server/deephaven/json/__init__.py @@ -521,7 +521,7 @@ def bool_( allow_string=allow_string, ) if on_null: - builder.onNull(onNull) + builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) return JsonOptions(builder.build()) @@ -560,9 +560,9 @@ def char_( allow_string=True, ) if on_null: - builder.onNull(onNull) + builder.onNull(ord(on_null)) if on_missing: - builder.onMissing(on_missing) + builder.onMissing(ord(on_missing)) return JsonOptions(builder.build()) @@ -605,7 +605,7 @@ def byte_( allow_string=allow_string, ) if on_null: - builder.onNull(onNull) + builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) return JsonOptions(builder.build()) @@ -650,7 +650,7 @@ def short_( allow_string=allow_string, ) if on_null: - builder.onNull(onNull) + builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) return JsonOptions(builder.build()) @@ -695,7 +695,7 @@ def int_( allow_string=allow_string, ) if on_null: - builder.onNull(onNull) + builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) return JsonOptions(builder.build()) @@ -751,7 +751,7 @@ def long_( allow_string=allow_string, ) if on_null: - builder.onNull(onNull) + builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) return JsonOptions(builder.build()) @@ -794,7 +794,7 @@ def float_( allow_string=allow_string, ) if on_null: - builder.onNull(onNull) + builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) return JsonOptions(builder.build()) @@ -848,7 +848,7 @@ def double_( allow_string=allow_string, ) if on_null: - builder.onNull(onNull) + builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) return JsonOptions(builder.build()) @@ -890,8 +890,8 @@ def string_( allow_bool (bool): if the string value is allowed to be a JSON boolean type, default is False allow_missing (bool): if the double value is allowed to be missing, default is True allow_null (bool): if the double value is allowed to be a JSON null type, default is True - on_missing (Optional[int]): the value to use when the JSON value is missing and allow_missing is True, default is None. - on_null (Optional[int]): the value to use when the JSON value is null and allow_null is True, default is None. + on_missing (Optional[str]): the value to use when the JSON value is missing and allow_missing is True, default is None. + on_null (Optional[str]): the value to use when the JSON value is null and allow_null is True, default is None. Returns: the double options @@ -907,7 +907,7 @@ def string_( allow_bool=allow_bool, ) if on_null: - builder.onNull(onNull) + builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) return JsonOptions(builder.build()) @@ -969,7 +969,7 @@ def instant_( if number_format: builder = _JInstantNumberValue.builder() if on_missing: - builder.onMull(to_j_instant(on_missing)) + builder.onMissing(to_j_instant(on_missing)) if on_null: builder.onNull(to_j_instant(on_null)) _build( @@ -995,7 +995,7 @@ def instant_( raise TypeError("allow_decimal is only valid when using number_format") builder = _JInstantValue.builder() if on_missing: - builder.onMull(to_j_instant(on_missing)) + builder.onMissing(to_j_instant(on_missing)) if on_null: builder.onNull(to_j_instant(on_null)) _build( @@ -1012,7 +1012,8 @@ def big_integer_( allow_decimal: bool = False, allow_missing: bool = True, allow_null: bool = True, - # todo on_null, on_missing + on_missing: Optional[Union[int, str]] = None, + on_null: Optional[Union[int, str]] = None, ) -> JsonOptions: """Creates a BigInteger options. For example, the JSON integer @@ -1029,6 +1030,8 @@ def big_integer_( allow_decimal (bool): if the BigInteger value is allowed to be a JSON decimal type, default is False. allow_missing (bool): if the BigInteger value is allowed to be missing, default is True allow_null (bool): if the BigInteger value is allowed to be a JSON null type, default is True + on_missing (Optional[Union[int, str]]): the value to use when the JSON value is missing and allow_missing is True, default is None. + on_null (Optional[Union[int, str]]): the value to use when the JSON value is null and allow_null is True, default is None. Returns: the BigInteger options @@ -1042,6 +1045,10 @@ def big_integer_( allow_decimal=allow_decimal, allow_string=allow_string, ) + if on_missing: + builder.onMissing(dtypes.BigInteger(str(on_missing))) + if on_null: + builder.onNull(dtypes.BigInteger(str(on_null))) return JsonOptions(builder.build()) @@ -1049,7 +1056,8 @@ def big_decimal_( allow_string: bool = False, allow_missing: bool = True, allow_null: bool = True, - # todo on_null, on_missing + on_missing: Optional[Union[float, str]] = None, + on_null: Optional[Union[float, str]] = None, ) -> JsonOptions: """Creates a BigDecimal options. For example, the JSON decimal @@ -1065,6 +1073,8 @@ def big_decimal_( allow_string (bool): if the BigDecimal value is allowed to be a JSON string type, default is False. allow_missing (bool): if the BigDecimal value is allowed to be missing, default is True allow_null (bool): if the BigDecimal value is allowed to be a JSON null type, default is True + on_missing (Optional[Union[float, str]]): the value to use when the JSON value is missing and allow_missing is True, default is None. + on_null (Optional[Union[float, str]]): the value to use when the JSON value is null and allow_null is True, default is None. Returns: the BigDecimal options @@ -1078,6 +1088,10 @@ def big_decimal_( allow_decimal=True, allow_string=allow_string, ) + if on_missing: + builder.onMissing(dtypes.BigDecimal(str(on_missing))) + if on_null: + builder.onNull(dtypes.BigDecimal(str(on_null))) return JsonOptions(builder.build()) diff --git a/py/server/tests/test_dtypes.py b/py/server/tests/test_dtypes.py index 5058dda1143..0dfbaf3bc2f 100644 --- a/py/server/tests/test_dtypes.py +++ b/py/server/tests/test_dtypes.py @@ -46,6 +46,7 @@ def test_j_type(self): self.assertEqual(dtypes.float64.j_type, jpy.get_type("double")) self.assertEqual(dtypes.double.j_type, jpy.get_type("double")) self.assertEqual(dtypes.string.j_type, jpy.get_type("java.lang.String")) + self.assertEqual(dtypes.BigInteger.j_type, jpy.get_type("java.math.BigInteger")) self.assertEqual(dtypes.BigDecimal.j_type, jpy.get_type("java.math.BigDecimal")) self.assertEqual(dtypes.StringSet.j_type, jpy.get_type("io.deephaven.stringset.StringSet")) self.assertEqual(dtypes.Instant.j_type, jpy.get_type("java.time.Instant")) @@ -70,6 +71,7 @@ def test_np_type(self): self.assertEqual(dtypes.float64.np_type, np.float64) self.assertEqual(dtypes.double.np_type, np.float64) self.assertEqual(dtypes.string.np_type, np.str_) + self.assertEqual(dtypes.BigInteger.np_type, np.object_) self.assertEqual(dtypes.BigDecimal.np_type, np.object_) self.assertEqual(dtypes.StringSet.np_type, np.object_) self.assertEqual(dtypes.Instant.np_type, np.dtype("datetime64[ns]")) diff --git a/py/server/tests/test_json.py b/py/server/tests/test_json.py index 74c82c9b299..91b861f9c68 100644 --- a/py/server/tests/test_json.py +++ b/py/server/tests/test_json.py @@ -36,42 +36,90 @@ def all_same_json(self, items): def test_bool(self): self.all_same_json([bool_(), dtypes.bool_, bool]) + with self.subTest("on_missing"): + bool_(on_missing=False) + with self.subTest("on_null"): + bool_(on_null=False) def test_char(self): self.all_same_json([char_(), dtypes.char]) + with self.subTest("on_missing"): + char_(on_missing="m") + with self.subTest("on_null"): + char_(on_null="n") def test_byte(self): self.all_same_json([byte_(), dtypes.byte]) + with self.subTest("on_missing"): + byte_(on_missing=-1) + with self.subTest("on_null"): + byte_(on_null=-1) def test_short(self): self.all_same_json([short_(), dtypes.short]) + with self.subTest("on_missing"): + short_(on_missing=-1) + with self.subTest("on_null"): + short_(on_null=-1) def test_int(self): self.all_same_json([int_(), dtypes.int32]) + with self.subTest("on_missing"): + int_(on_missing=-1) + with self.subTest("on_null"): + int_(on_null=-1) def test_long(self): self.all_same_json([long_(), dtypes.long, int]) + with self.subTest("on_missing"): + long_(on_missing=-1) + with self.subTest("on_null"): + long_(on_null=-1) def test_float(self): self.all_same_json([float_(), dtypes.float32]) + with self.subTest("on_missing"): + float_(on_missing=-1.0) + with self.subTest("on_null"): + float_(on_null=-1.0) def test_double(self): self.all_same_json([double_(), dtypes.double, float]) + with self.subTest("on_missing"): + double_(on_missing=-1.0) + with self.subTest("on_null"): + double_(on_null=-1.0) def test_string(self): self.all_same_json([string_(), dtypes.string, str]) + with self.subTest("on_missing"): + string_(on_missing="(missing)") + with self.subTest("on_null"): + string_(on_null="(null)") def test_instant(self): self.all_same_json([instant_(), dtypes.Instant, datetime]) + with self.subTest("on_missing"): + instant_(on_missing=datetime.fromtimestamp(0)) + with self.subTest("on_null"): + instant_(on_null=datetime.fromtimestamp(0)) def test_any(self): self.all_same_json([any_(), dtypes.JObject, object]) def test_big_integer(self): self.all_same_json([big_integer_(), dtypes.BigInteger]) + with self.subTest("on_missing"): + big_integer_(on_missing=123456789012345678901234567890) + with self.subTest("on_null"): + big_integer_(on_null=123456789012345678901234567890) def test_big_decimal(self): self.all_same_json([big_decimal_(), dtypes.BigDecimal]) + with self.subTest("on_missing"): + big_decimal_(on_missing="123456789012345678901234567890.999999999999") + with self.subTest("on_null"): + big_decimal_(on_null="123456789012345678901234567890.999999999999") def test_object(self): e1 = [ From fb9d759e1bf14ec4934a7e329ff81199e6f1b8af Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Thu, 13 Jun 2024 09:28:27 -0700 Subject: [PATCH 47/53] object_processor_spec, jackson provider python construction test --- py/server/tests/test_kafka_consumer.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/py/server/tests/test_kafka_consumer.py b/py/server/tests/test_kafka_consumer.py index 91c2990f4e7..35a32fc1cc0 100644 --- a/py/server/tests/test_kafka_consumer.py +++ b/py/server/tests/test_kafka_consumer.py @@ -4,12 +4,13 @@ import os import unittest +from datetime import datetime from deephaven import kafka_consumer as ck from deephaven.stream.kafka.consumer import TableType, KeyValueSpec from tests.testbase import BaseTestCase from deephaven import dtypes - +from deephaven.json.jackson import provider as jackson_provider class KafkaConsumerTestCase(BaseTestCase): @@ -80,7 +81,13 @@ def test_json_spec(self): 'jqty': 'Qty', 'jts': 'Tstamp' } - )] + ), ck.object_processor_spec(jackson_provider({ + 'Symbol': str, + 'Side': str, + 'Price': float, + 'Qty': int, + 'Tstamp': datetime + }))] for value_spec in value_specs: t = ck.consume( From 258a8b8a42f32219accd2f5d062a770b9974804c Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Thu, 13 Jun 2024 08:47:00 -0700 Subject: [PATCH 48/53] Add JacksonProvider documentation --- .../json/jackson/JacksonProvider.java | 133 +++++++++++++++++- .../io/deephaven/json/TypedObjectValue.java | 17 +-- 2 files changed, 139 insertions(+), 11 deletions(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java index d2b10cdeb5a..507003ec7ed 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java @@ -4,10 +4,24 @@ package io.deephaven.json.jackson; import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.TreeNode; +import io.deephaven.chunk.WritableChunk; +import io.deephaven.json.AnyValue; +import io.deephaven.json.ArrayValue; +import io.deephaven.json.DoubleValue; +import io.deephaven.json.IntValue; +import io.deephaven.json.LongValue; +import io.deephaven.json.ObjectKvValue; +import io.deephaven.json.ObjectValue; +import io.deephaven.json.StringValue; +import io.deephaven.json.TupleValue; +import io.deephaven.json.TypedObjectValue; import io.deephaven.json.Value; import io.deephaven.processor.NamedObjectProcessor; import io.deephaven.processor.ObjectProcessor; import io.deephaven.qst.type.Type; +import io.deephaven.util.annotations.FinalDefault; import java.io.File; import java.net.URL; @@ -19,7 +33,123 @@ import java.util.function.Function; /** - * A specific JSON processor implementation using Jackson. + * A {@link Value JSON value} {@link ObjectProcessor processor} implementation using Jackson. + * + *

+ * This implementation allows users to efficiently parse / destructure a + * JSON value (from a supported {@link JacksonProvider#getInputTypes() + * input type}) into {@link WritableChunk writable chunks} according to the type(s) as specified by its {@link Value + * Value type}. This is done using the Jackson streaming + * API {@link JsonParser} (as opposed to the databind / object mapping API, which must first create intermediate + * objects). + * + *

+ * The "simple" types are self-explanatory. For example, the {@link StringValue} represents a {@link String} output + * type, and (by default) expects a JSON string as input; the {@link IntValue} represents an {@code int} output type, + * and (by default) expects a JSON number as input. The allowed JSON input types can be specified via + * {@link Value#allowedTypes() allowed types}; users are encouraged to use the strictest type they can according to how + * their JSON data is serialized. + * + *

+ * The most common "complex" type is {@link ObjectValue}, which expects to parse a JSON object known fields. The object + * contains {@link ObjectValue#fields()}, which represent other {@link Value values}. The fields are recursively + * resolved and flattened into the {@link ObjectProcessor#outputTypes()}. For example, a JSON object, which itself + * contains another JSON object + * + *

+ * {
+ *   "city": "Plymouth",
+ *   "point": {
+ *       "latitude": 45.018269,
+ *       "longitude": -93.473892
+ *   }
+ * }
+ * 
+ * + * when represented with structuring as one might expect ({@link ObjectValue}({@link StringValue}, + * {@link ObjectValue}({@link DoubleValue}, {@link DoubleValue}))), will produce {@link ObjectProcessor#outputTypes() + * output types} representing {@code [String, double, double]}. Furthermore, the field names and delimiter "_" will be + * used by default to provide the {@link NamedObjectProcessor#names() names} + * {@code ["city", "point_latitude", "point_longitude"]}. + * + *

+ * The {@link ArrayValue} represents a variable-length array, which expects to parse a JSON array where each element is + * expected to have the same {@link ArrayValue#element() element type}. (This is in contrast to JSON arrays more + * generally, where each element of the array can be a different JSON value type.) The output type will be the output + * type(s) of the element type as the component type of a native array, {@link Type#arrayType()}. For example, if we + * used the previous example as the {@link ArrayValue#element() array component type}, it will produce + * {@link ObjectProcessor#outputTypes() output types} representing {@code [String[], double[], double[]]} (the + * {@link NamedObjectProcessor#names() names} will remain unchanged). + * + *

+ * The {@link TupleValue} represents a fixed number of {@link TupleValue#namedValues() value types}, which expects to + * parse a fixed-length JSON array where each element is has the corresponding value type. The values are recursively + * resolved and flattened into the {@link ObjectProcessor#outputTypes()}; for example, the earlier example's data could + * be re-represented as the JSON array + * + *

+ * ["Plymouth", 45.018269, -93.473892]
+ * 
+ * + * and structured as one might expect ({@link TupleValue}({@link StringValue}, {@link DoubleValue}, + * {@link DoubleValue})), and will produce {@link ObjectProcessor#outputTypes() output types} representing + * {@code [String, double, double]}. Even though no field names are present in the JSON value, users may set names for + * each element {@link TupleValue#namedValues()} (and will otherwise inherit integer-indexed default names). + * + *

+ * The {@link TypedObjectValue} represents a union of {@link ObjectValue object values} where the first field is + * type-discriminating. For example, the following might be modelled as a type-discriminated object with + * type-discriminating field "type", shared "symbol" {@link StringValue}, "quote" object of "bid" {@link DoubleValue} + * and an "ask" {@link DoubleValue}, and "trade" object containing a "price" {@link DoubleValue} and a "size" + * {@link LongValue}. + * + *

+ * {
+ *   "type": "quote",
+ *   "symbol": "BAR",
+ *   "bid": 10.01,
+ *   "ask": 10.05
+ * }
+ * {
+ *   "type": "trade",
+ *   "symbol": "FOO",
+ *   "price": 70.03,
+ *   "size": 42
+ * }
+ * 
+ * + * The {@link ObjectProcessor#outputTypes() output types} are first the type-discriminating field, then the shared + * fields (if any), followed by the individual {@link ObjectValue object value} fields; with the above example, that + * would result in {@link ObjectProcessor#outputTypes() output types} + * {@code [String, String, double, double, double long]} and {@link NamedObjectProcessor#names() names} + * {@code ["type", "symbol", "quote_bid", "quote_ask", "trade_price", "trade_size"]}. + * + *

+ * The {@link ObjectKvValue} represents a variable-length object, which expects to parse a JSON object where each + * key-value pair has a common {@link ObjectKvValue#value() value type}. The output type will be the output type of the + * key and value element types as components types of native arrays ({@link Type#arrayType()}. For example, a JSON + * object, whose values are also JSON objects + * + *

+ * {
+ *   "Plymouth": {
+ *       "latitude": 45.018269,
+ *       "longitude": -93.473892
+ *   },
+ *   "New York": {
+ *       "latitude": 40.730610,
+ *       "longitude": -73.935242
+ *   }
+ * }
+ * 
+ * + * when represented with structuring as one might expect ({@link ObjectKvValue}({@link StringValue}, + * {@link ObjectValue}({@link DoubleValue}, {@link DoubleValue}))), will produce {@link ObjectProcessor#outputTypes() + * output types} representing {@code [String[], double[], double[]]}. + * + *

+ * The {@link AnyValue} type represents a {@link TreeNode} output; this requires that the Jackson databinding API be + * available on the classpath. This is useful for initial modelling and debugging purposes. */ public interface JacksonProvider extends NamedObjectProcessor.Provider { @@ -71,6 +201,7 @@ static Set> getInputTypes() { * @return the supported types */ @Override + @FinalDefault default Set> inputTypes() { return getInputTypes(); } diff --git a/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java b/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java index ad1b6562377..e975b1636c8 100644 --- a/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java +++ b/extensions/json/src/main/java/io/deephaven/json/TypedObjectValue.java @@ -19,25 +19,22 @@ * *

* For example, the following might be modelled as a type-discriminated object with "type" as the type field, "symbol" - * as a shared field, with a "trade" object containing a "bid" and an "ask" field, and with a "quote" object containing + * as a shared field, with a "quote" object containing a "bid" and an "ask" field, and with a "trade" object containing * a "price" and a "size" field: * *

  * {
- *   "type": "trade",
- *   "symbol": "FOO",
- *   "price": 70.03,
- *   "size": 42
- * }
- * 
- * - *
- * {
  *   "type": "quote",
  *   "symbol": "BAR",
  *   "bid": 10.01,
  *   "ask": 10.05
  * }
+ * {
+ *   "type": "trade",
+ *   "symbol": "FOO",
+ *   "price": 70.03,
+ *   "size": 42
+ * }
  * 
*/ @Immutable From 1683d9e0d7e8e78f3d5a6d955a9b06c54de490b7 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Thu, 13 Jun 2024 13:40:18 -0700 Subject: [PATCH 49/53] Review points --- .../json/jackson/JacksonProvider.java | 26 +-- py/server/deephaven/json/__init__.py | 184 +++++++++--------- py/server/deephaven/json/jackson.py | 4 +- 3 files changed, 108 insertions(+), 106 deletions(-) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java index 507003ec7ed..2ab5c1208a9 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java @@ -33,7 +33,8 @@ import java.util.function.Function; /** - * A {@link Value JSON value} {@link ObjectProcessor processor} implementation using Jackson. + * A {@link Value JSON value} {@link ObjectProcessor processor} implementation using + * Jackson>. * *

* This implementation allows users to efficiently parse / destructure a @@ -51,8 +52,8 @@ * their JSON data is serialized. * *

- * The most common "complex" type is {@link ObjectValue}, which expects to parse a JSON object known fields. The object - * contains {@link ObjectValue#fields()}, which represent other {@link Value values}. The fields are recursively + * The most common "complex" type is {@link ObjectValue}, which expects to parse a JSON object of known fields. The + * object contains {@link ObjectValue#fields()}, which represent other {@link Value values}. The fields are recursively * resolved and flattened into the {@link ObjectProcessor#outputTypes()}. For example, a JSON object, which itself * contains another JSON object * @@ -83,9 +84,9 @@ * *

* The {@link TupleValue} represents a fixed number of {@link TupleValue#namedValues() value types}, which expects to - * parse a fixed-length JSON array where each element is has the corresponding value type. The values are recursively - * resolved and flattened into the {@link ObjectProcessor#outputTypes()}; for example, the earlier example's data could - * be re-represented as the JSON array + * parse a fixed-length JSON array where each element corresponds to the same-indexed value type. The values are + * recursively resolved and flattened into the {@link ObjectProcessor#outputTypes()}; for example, the earlier example's + * data could be re-represented as the JSON array * *

  * ["Plymouth", 45.018269, -93.473892]
@@ -93,8 +94,8 @@
  * 
  * and structured as one might expect ({@link TupleValue}({@link StringValue}, {@link DoubleValue},
  * {@link DoubleValue})), and will produce {@link ObjectProcessor#outputTypes() output types} representing
- * {@code [String, double, double]}. Even though no field names are present in the JSON value, users may set names for
- * each element {@link TupleValue#namedValues()} (and will otherwise inherit integer-indexed default names).
+ * {@code [String, double, double]}. Even though no field names are present in the JSON value, users may set
+ * {@link TupleValue#namedValues() names} for each element (and will otherwise inherit integer-indexed default names).
  *
  * 

* The {@link TypedObjectValue} represents a union of {@link ObjectValue object values} where the first field is @@ -126,9 +127,9 @@ * *

* The {@link ObjectKvValue} represents a variable-length object, which expects to parse a JSON object where each - * key-value pair has a common {@link ObjectKvValue#value() value type}. The output type will be the output type of the - * key and value element types as components types of native arrays ({@link Type#arrayType()}. For example, a JSON - * object, whose values are also JSON objects + * key-value pair has a common {@link ObjectKvValue#value() value type}. The output type will be the key and value + * element types as component of native arrays ({@link Type#arrayType()}). For example, a JSON object, whose values are + * also JSON objects * *

  * {
@@ -145,7 +146,8 @@
  *
  * when represented with structuring as one might expect ({@link ObjectKvValue}({@link StringValue},
  * {@link ObjectValue}({@link DoubleValue}, {@link DoubleValue}))), will produce {@link ObjectProcessor#outputTypes()
- * output types} representing {@code [String[], double[], double[]]}.
+ * output types} representing {@code [String[], double[], double[]]}, and {@link NamedObjectProcessor#names() names}
+ * {@code ["Key", "latitude", "longitude"]}.
  *
  * 

* The {@link AnyValue} type represents a {@link TreeNode} output; this requires that the Jackson databinding API be diff --git a/py/server/deephaven/json/__init__.py b/py/server/deephaven/json/__init__.py index 068fcc188c7..c0ac38dc487 100644 --- a/py/server/deephaven/json/__init__.py +++ b/py/server/deephaven/json/__init__.py @@ -42,7 +42,7 @@ "any_", "skip_", "json", - "JsonOptions", + "JsonValue", "JsonValueType", "RepeatedFieldBehavior", "FieldOptions", @@ -89,21 +89,21 @@ _EPOCH_NANOS = _JInstantNumberValueFormat.EPOCH_NANOS -class JsonOptions(JObjectWrapper): - """The JSON options object.""" +class JsonValue(JObjectWrapper): + """The JSON Value type.""" j_object_type = _JValue - def __init__(self, j_options: jpy.JType): - self.j_options = j_options + def __init__(self, j_value: jpy.JType): + self.j_value = j_value @property def j_object(self) -> jpy.JType: - return self.j_options + return self.j_value JsonValueType = Union[ - JsonOptions, + JsonValue, dtypes.DType, type, Dict[str, Union["JsonValueType", "FieldOptions"]], @@ -164,7 +164,7 @@ def _j_field_options(self, name: str) -> jpy.JType: builder = ( _JObjectField.builder() .name(name) - .options(json(self.value).j_options) + .options(json(self.value).j_value) .repeatedBehavior(self.repeated_behavior.value) .caseSensitive(self.case_sensitive) ) @@ -205,8 +205,8 @@ def object_( allow_null: bool = True, repeated_field_behavior: RepeatedFieldBehavior = RepeatedFieldBehavior.ERROR, case_sensitive: bool = True, -) -> JsonOptions: - """Creates an object options. For example, the JSON object +) -> JsonValue: + """Creates an object value. For example, the JSON object .. code-block:: json { "name": "foo", "age": 42 } @@ -238,7 +238,7 @@ def object_( using JsonValueType, by default is True Returns: - the object options + the object value """ builder = _JObjectValue.builder() _build(builder, allow_missing, allow_null, allow_object=True) @@ -255,7 +255,7 @@ def object_( ) # noinspection PyProtectedMember builder.addFields(field_opts._j_field_options(field_name)) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def typed_object_( @@ -267,8 +267,8 @@ def typed_object_( allow_null: bool = True, on_missing: Optional[str] = None, on_null: Optional[str] = None, -) -> JsonOptions: - """Creates a type-discriminated object options. For example, the JSON objects +) -> JsonValue: + """Creates a type-discriminated object value. For example, the JSON objects .. code-block:: json { "type": "trade", "symbol": "FOO", "price": 70.03, "size": 42 } @@ -310,7 +310,7 @@ def typed_object_( None Returns: - the object options + the typed object value """ builder = _JTypedObjectValue.builder() _build(builder, allow_missing, allow_null, allow_object=True) @@ -334,16 +334,16 @@ def typed_object_( builder.addSharedFields(shared_field_opts._j_field_options(shared_field_name)) for object_name, object_type in objects.items(): builder.putObjects( - object_name, strict_cast(json(object_type).j_options, _JObjectValue) + object_name, strict_cast(json(object_type).j_value, _JObjectValue) ) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def array_( element: JsonValueType, allow_missing: bool = True, allow_null: bool = True, -) -> JsonOptions: +) -> JsonValue: """Creates a "typed array", where all elements of the array have the same element type. For example, the JSON array .. code-block:: json @@ -371,12 +371,12 @@ def array_( allow_null (bool): if the array is allowed to be a JSON null type, by default is True Returns: - the array options + the array value """ builder = _JArrayValue.builder() - builder.element(json(element).j_options) + builder.element(json(element).j_value) _build(builder, allow_missing, allow_null, allow_array=True) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def object_kv_( @@ -384,7 +384,7 @@ def object_kv_( value_type: Optional[JsonValueType] = None, allow_missing: bool = True, allow_null: bool = True, -) -> JsonOptions: +) -> JsonValue: """Creates an object key-value options. This is used in situations where the number of fields in an object is variable and all the values types are the same. For example, the JSON object @@ -409,21 +409,21 @@ def object_kv_( allow_null (bool): if the object is allowed to be a JSON null type, by default is True Returns: - the object kv options + the object kv value """ builder = _JObjectKvValue.builder() - builder.key(json(key_type).j_options) - builder.value(json(value_type).j_options) + builder.key(json(key_type).j_value) + builder.value(json(value_type).j_value) _build(builder, allow_missing, allow_null, allow_object=True) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def tuple_( values: Union[Tuple[JsonValueType, ...], Dict[str, JsonValueType]], allow_missing: bool = True, allow_null: bool = True, -) -> JsonOptions: - """Creates a tuple options. For example, the JSON array +) -> JsonValue: + """Creates a tuple value. For example, the JSON array .. code-block:: json ["foo", 42, 5.72] @@ -454,7 +454,7 @@ def tuple_( allow_missing (bool): if the array is allowed to be missing, by default is True allow_null (bool): if the array is allowed to be a JSON null type, by default is True Returns: - the tuple options + the tuple value """ if isinstance(values, Tuple): kvs = enumerate(values) @@ -470,8 +470,8 @@ def tuple_( allow_array=True, ) for name, json_value_type in kvs: - builder.putNamedValues(str(name), json(json_value_type).j_options) - return JsonOptions(builder.build()) + builder.putNamedValues(str(name), json(json_value_type).j_value) + return JsonValue(builder.build()) def bool_( @@ -480,8 +480,8 @@ def bool_( allow_null: bool = True, on_missing: Optional[bool] = None, on_null: Optional[bool] = None, -) -> JsonOptions: - """Creates a bool options. For example, the JSON boolean +) -> JsonValue: + """Creates a bool value. For example, the JSON boolean .. code-block:: json True @@ -510,7 +510,7 @@ def bool_( on_null (Optional[bool]): the value to use when the JSON value is null and allow_null is True, default is None Returns: - the bool options + the bool value """ builder = _JBoolValue.builder() _build( @@ -524,7 +524,7 @@ def bool_( builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def char_( @@ -532,8 +532,8 @@ def char_( allow_null: bool = True, on_missing: Optional[str] = None, on_null: Optional[str] = None, -) -> JsonOptions: - """Creates a char options. For example, the JSON string +) -> JsonValue: + """Creates a char value. For example, the JSON string .. code-block:: json "F" @@ -550,7 +550,7 @@ def char_( on_null (Optional[str]): the value to use when the JSON value is null and allow_null is True, default is None. If specified, must be a single character. Returns: - the char options + the char value """ builder = _JCharValue.builder() _build( @@ -563,7 +563,7 @@ def char_( builder.onNull(ord(on_null)) if on_missing: builder.onMissing(ord(on_missing)) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def byte_( @@ -573,8 +573,8 @@ def byte_( allow_null: bool = True, on_missing: Optional[int] = None, on_null: Optional[int] = None, -) -> JsonOptions: - """Creates a byte (signed 8-bit) options. For example, the JSON integer +) -> JsonValue: + """Creates a byte (signed 8-bit) value. For example, the JSON integer .. code-block:: json 42 @@ -593,7 +593,7 @@ def byte_( on_null (Optional[int]): the value to use when the JSON value is null and allow_null is True, default is None. Returns: - the byte options + the byte value """ builder = _JByteValue.builder() _build( @@ -608,7 +608,7 @@ def byte_( builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def short_( @@ -618,8 +618,8 @@ def short_( allow_null: bool = True, on_missing: Optional[int] = None, on_null: Optional[int] = None, -) -> JsonOptions: - """Creates a short (signed 16-bit) options. For example, the JSON integer +) -> JsonValue: + """Creates a short (signed 16-bit) value. For example, the JSON integer .. code-block:: json 30000 @@ -638,7 +638,7 @@ def short_( on_null (Optional[int]): the value to use when the JSON value is null and allow_null is True, default is None. Returns: - the short options + the short value """ builder = _JShortValue.builder() _build( @@ -653,7 +653,7 @@ def short_( builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def int_( @@ -663,8 +663,8 @@ def int_( allow_null: bool = True, on_missing: Optional[int] = None, on_null: Optional[int] = None, -) -> JsonOptions: - """Creates an int (signed 32-bit) options. For example, the JSON integer +) -> JsonValue: + """Creates an int (signed 32-bit) value. For example, the JSON integer .. code-block:: json 100000 @@ -683,7 +683,7 @@ def int_( on_null (Optional[int]): the value to use when the JSON value is null and allow_null is True, default is None. Returns: - the int options + the int value """ builder = _JIntValue.builder() _build( @@ -698,7 +698,7 @@ def int_( builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def long_( @@ -708,8 +708,8 @@ def long_( allow_null: bool = True, on_missing: Optional[int] = None, on_null: Optional[int] = None, -) -> JsonOptions: - """Creates a long (signed 64-bit) options. For example, the JSON integer +) -> JsonValue: + """Creates a long (signed 64-bit) value. For example, the JSON integer .. code-block:: json 8000000000 @@ -739,7 +739,7 @@ def long_( on_null (Optional[int]): the value to use when the JSON value is null and allow_null is True, default is None. Returns: - the long options + the long value """ builder = _JLongValue.builder() _build( @@ -754,7 +754,7 @@ def long_( builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def float_( @@ -763,8 +763,8 @@ def float_( allow_null: bool = True, on_missing: Optional[float] = None, on_null: Optional[float] = None, -) -> JsonOptions: - """Creates a float (signed 32-bit) options. For example, the JSON decimal +) -> JsonValue: + """Creates a float (signed 32-bit) value. For example, the JSON decimal .. code-block:: json 42.42 @@ -782,7 +782,7 @@ def float_( on_null (Optional[float]): the value to use when the JSON value is null and allow_null is True, default is None. Returns: - the float options + the float value """ builder = _JFloatValue.builder() _build( @@ -797,7 +797,7 @@ def float_( builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def double_( @@ -806,8 +806,8 @@ def double_( allow_null: bool = True, on_missing: Optional[float] = None, on_null: Optional[float] = None, -) -> JsonOptions: - """Creates a double (signed 64-bit) options. For example, the JSON decimal +) -> JsonValue: + """Creates a double (signed 64-bit) value. For example, the JSON decimal .. code-block:: json 42.42424242 @@ -836,7 +836,7 @@ def double_( on_null (Optional[int]): the value to use when the JSON value is null and allow_null is True, default is None. Returns: - the double options + the double value """ builder = _JDoubleValue.builder() _build( @@ -851,7 +851,7 @@ def double_( builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def string_( @@ -862,8 +862,8 @@ def string_( allow_null: bool = True, on_missing: Optional[str] = None, on_null: Optional[str] = None, -) -> JsonOptions: - """Creates a String options. For example, the JSON string +) -> JsonValue: + """Creates a String value. For example, the JSON string .. code-block:: json "Hello, world!" @@ -894,7 +894,7 @@ def string_( on_null (Optional[str]): the value to use when the JSON value is null and allow_null is True, default is None. Returns: - the double options + the String value """ builder = _JStringValue.builder() _build( @@ -910,7 +910,7 @@ def string_( builder.onNull(on_null) if on_missing: builder.onMissing(on_missing) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) # TODO(deephaven-core#5269): Create deephaven.time time-type aliases @@ -921,8 +921,8 @@ def instant_( allow_decimal: bool = False, on_missing: Optional[Any] = None, on_null: Optional[Any] = None, -) -> JsonOptions: - """Creates an Instant options. For example, the JSON string +) -> JsonValue: + """Creates an Instant value. For example, the JSON string .. code-block:: json "2009-02-13T23:31:30.123456789Z" @@ -964,7 +964,7 @@ def instant_( on_null (Optional[Any]): the value to use when the JSON value is null and allow_null is True, default is None. Returns: - the Instant options + the Instant value """ if number_format: builder = _JInstantNumberValue.builder() @@ -989,7 +989,7 @@ def instant_( builder.format(_EPOCH_NANOS) else: raise TypeError(f"Invalid number format: {number_format}") - return JsonOptions(builder.build()) + return JsonValue(builder.build()) else: if allow_decimal: raise TypeError("allow_decimal is only valid when using number_format") @@ -1004,7 +1004,7 @@ def instant_( allow_null, allow_string=True, ) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def big_integer_( @@ -1014,8 +1014,8 @@ def big_integer_( allow_null: bool = True, on_missing: Optional[Union[int, str]] = None, on_null: Optional[Union[int, str]] = None, -) -> JsonOptions: - """Creates a BigInteger options. For example, the JSON integer +) -> JsonValue: + """Creates a BigInteger value. For example, the JSON integer .. code-block:: json 123456789012345678901 @@ -1034,7 +1034,7 @@ def big_integer_( on_null (Optional[Union[int, str]]): the value to use when the JSON value is null and allow_null is True, default is None. Returns: - the BigInteger options + the BigInteger value """ builder = _JBigIntegerValue.builder() _build( @@ -1049,7 +1049,7 @@ def big_integer_( builder.onMissing(dtypes.BigInteger(str(on_missing))) if on_null: builder.onNull(dtypes.BigInteger(str(on_null))) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) def big_decimal_( @@ -1058,8 +1058,8 @@ def big_decimal_( allow_null: bool = True, on_missing: Optional[Union[float, str]] = None, on_null: Optional[Union[float, str]] = None, -) -> JsonOptions: - """Creates a BigDecimal options. For example, the JSON decimal +) -> JsonValue: + """Creates a BigDecimal value. For example, the JSON decimal .. code-block:: json 123456789012345678901.42 @@ -1077,7 +1077,7 @@ def big_decimal_( on_null (Optional[Union[float, str]]): the value to use when the JSON value is null and allow_null is True, default is None. Returns: - the BigDecimal options + the BigDecimal value """ builder = _JBigDecimalValue.builder() _build( @@ -1092,16 +1092,16 @@ def big_decimal_( builder.onMissing(dtypes.BigDecimal(str(on_missing))) if on_null: builder.onNull(dtypes.BigDecimal(str(on_null))) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) -def any_() -> JsonOptions: - """Creates an "any" options. The resulting type is implementation dependant. +def any_() -> JsonValue: + """Creates an "any" value. The resulting type is implementation dependant. Returns: - the "any" options + the "any" value """ - return JsonOptions(_JAnyValue.of()) + return JsonValue(_JAnyValue.of()) def skip_( @@ -1114,8 +1114,8 @@ def skip_( allow_object: Optional[bool] = None, allow_array: Optional[bool] = None, allow_by_default: bool = True, -) -> JsonOptions: - """Creates a "skip" type. No resulting type will be returned, but the JSON types will be validated as configured. +) -> JsonValue: + """Creates a "skip" value. No resulting type will be returned, but the JSON types will be validated as configured. This may be useful in combination with an object type where allow_unknown_fields=False. For example, the JSON object .. code-block:: json @@ -1138,7 +1138,7 @@ def skip_( allow_by_default (bool): the default behavior for the other arguments when they are set to None, by default is True Returns: - the "skip" options + the "skip" value """ def _allow(x: Optional[bool]) -> bool: @@ -1156,19 +1156,19 @@ def _allow(x: Optional[bool]) -> bool: allow_object=_allow(allow_object), allow_array=_allow(allow_array), ) - return JsonOptions(builder.build()) + return JsonValue(builder.build()) -def json(json_value_type: JsonValueType) -> JsonOptions: - """Creates a JsonOptions from a JsonValueType. +def json(json_value_type: JsonValueType) -> JsonValue: + """Creates a JsonValue from a JsonValueType. Args: json_value_type (JsonValueType): the JSON value type Returns: - the JSON options + the JSON value """ - if isinstance(json_value_type, JsonOptions): + if isinstance(json_value_type, JsonValue): return json_value_type if isinstance(json_value_type, dtypes.DType): return _dtype_dict[json_value_type] diff --git a/py/server/deephaven/json/jackson.py b/py/server/deephaven/json/jackson.py index 83c4860b826..55c057b3347 100644 --- a/py/server/deephaven/json/jackson.py +++ b/py/server/deephaven/json/jackson.py @@ -25,7 +25,7 @@ def provider( """ _JProvider = jpy.get_type("io.deephaven.json.jackson.JacksonProvider") return ( - _JProvider.of(json(json_value).j_options, factory) + _JProvider.of(json(json_value).j_value, factory) if factory - else _JProvider.of(json(json_value).j_options) + else _JProvider.of(json(json_value).j_value) ) From c53262c9f37a2be9dea00f78e3a92083190d3a34 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Thu, 13 Jun 2024 14:24:52 -0700 Subject: [PATCH 50/53] Python _val --- py/server/deephaven/json/__init__.py | 210 +++++++++++++-------------- py/server/deephaven/json/jackson.py | 6 +- py/server/tests/test_json.py | 120 +++++++-------- 3 files changed, 170 insertions(+), 166 deletions(-) diff --git a/py/server/deephaven/json/__init__.py b/py/server/deephaven/json/__init__.py index c0ac38dc487..8ce22106142 100644 --- a/py/server/deephaven/json/__init__.py +++ b/py/server/deephaven/json/__init__.py @@ -23,25 +23,25 @@ __all__ = [ - "string_", - "bool_", - "char_", - "byte_", - "short_", - "int_", - "long_", - "float_", - "double_", - "instant_", - "big_integer_", - "big_decimal_", - "array_", - "object_", - "object_kv_", - "tuple_", - "any_", - "skip_", - "json", + "string_val", + "bool_val", + "char_val", + "byte_val", + "short_val", + "int_val", + "long_val", + "float_val", + "double_val", + "instant_val", + "big_integer_val", + "big_decimal_val", + "array_val", + "object_val", + "object_kv_val", + "tuple_val", + "any_val", + "skip_val", + "json_val", "JsonValue", "JsonValueType", "RepeatedFieldBehavior", @@ -164,7 +164,7 @@ def _j_field_options(self, name: str) -> jpy.JType: builder = ( _JObjectField.builder() .name(name) - .options(json(self.value).j_value) + .options(json_val(self.value).j_value) .repeatedBehavior(self.repeated_behavior.value) .caseSensitive(self.case_sensitive) ) @@ -198,7 +198,7 @@ def _build( ) -def object_( +def object_val( fields: Dict[str, Union[JsonValueType, FieldOptions]], allow_unknown_fields: bool = True, allow_missing: bool = True, @@ -214,13 +214,13 @@ def object_( might be modelled as the object type .. code-block:: python - object_({ "name": str, "age": int }) + object_val({ "name": str, "age": int }) In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can simplify by using a Dict[str, Union[JsonValueType, FieldOptions]]. For example, .. code-block:: python - some_method(object_({ "name": str, "age": int })) + some_method(object_val({ "name": str, "age": int })) could be simplified to @@ -258,7 +258,7 @@ def object_( return JsonValue(builder.build()) -def typed_object_( +def typed_object_val( type_field: str, shared_fields: Dict[str, Union[JsonValueType, FieldOptions]], objects: Dict[str, JsonValueType], @@ -281,7 +281,7 @@ def typed_object_( field: .. code-block:: python - typed_object_( + typed_object_val( "type", {"symbol": str}, { @@ -334,12 +334,12 @@ def typed_object_( builder.addSharedFields(shared_field_opts._j_field_options(shared_field_name)) for object_name, object_type in objects.items(): builder.putObjects( - object_name, strict_cast(json(object_type).j_value, _JObjectValue) + object_name, strict_cast(json_val(object_type).j_value, _JObjectValue) ) return JsonValue(builder.build()) -def array_( +def array_val( element: JsonValueType, allow_missing: bool = True, allow_null: bool = True, @@ -352,13 +352,13 @@ def array_( might be modelled as an array of ints .. code-block:: python - array_(int) + array_val(int) In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can simplify by using a list with a single element type. For example, .. code-block:: python - some_method(array_(element)) + some_method(array_val(element)) could be simplified to @@ -374,12 +374,12 @@ def array_( the array value """ builder = _JArrayValue.builder() - builder.element(json(element).j_value) + builder.element(json_val(element).j_value) _build(builder, allow_missing, allow_null, allow_array=True) return JsonValue(builder.build()) -def object_kv_( +def object_kv_val( key_type: JsonValueType = str, value_type: Optional[JsonValueType] = None, allow_missing: bool = True, @@ -400,7 +400,7 @@ def object_kv_( might be modelled as the object kv type .. code-block:: python - object_kv_(value_type=int) + object_kv_val(value_type=int) Args: key_type (JsonValueType): the key element, by defaults is type str @@ -412,13 +412,13 @@ def object_kv_( the object kv value """ builder = _JObjectKvValue.builder() - builder.key(json(key_type).j_value) - builder.value(json(value_type).j_value) + builder.key(json_val(key_type).j_value) + builder.value(json_val(value_type).j_value) _build(builder, allow_missing, allow_null, allow_object=True) return JsonValue(builder.build()) -def tuple_( +def tuple_val( values: Union[Tuple[JsonValueType, ...], Dict[str, JsonValueType]], allow_missing: bool = True, allow_null: bool = True, @@ -431,18 +431,18 @@ def tuple_( might be modelled as the tuple type .. code-block:: python - tuple_((str, int, float)) + tuple_val((str, int, float)) To provide meaningful names, a dictionary can be used: .. code-block:: python - tuple_({"name": str, "age": int, "height": float}) + tuple_val({"name": str, "age": int, "height": float}) In contexts where the user needs to create a JsonValueType and isn't changing any default values nor is setting names, the user can simplify passing through a python tuple type. For example, .. code-block:: python - some_method(tuple_((tuple_type_1, tuple_type_2))) + some_method(tuple_val((tuple_type_1, tuple_type_2))) could be simplified to @@ -470,11 +470,11 @@ def tuple_( allow_array=True, ) for name, json_value_type in kvs: - builder.putNamedValues(str(name), json(json_value_type).j_value) + builder.putNamedValues(str(name), json_val(json_value_type).j_value) return JsonValue(builder.build()) -def bool_( +def bool_val( allow_string: bool = False, allow_missing: bool = True, allow_null: bool = True, @@ -489,13 +489,13 @@ def bool_( might be modelled as the bool type .. code-block:: python - bool_() + bool_val() In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can simplify by using the python built-in bool type. For example, .. code-block:: python - some_method(bool_()) + some_method(bool_val()) could be simplified to @@ -527,7 +527,7 @@ def bool_( return JsonValue(builder.build()) -def char_( +def char_val( allow_missing: bool = True, allow_null: bool = True, on_missing: Optional[str] = None, @@ -541,7 +541,7 @@ def char_( might be modelled as the char type .. code-block:: python - char_() + char_val() Args: allow_missing (bool): if the char value is allowed to be missing, default is True @@ -566,7 +566,7 @@ def char_( return JsonValue(builder.build()) -def byte_( +def byte_val( allow_decimal: bool = False, allow_string: bool = False, allow_missing: bool = True, @@ -582,7 +582,7 @@ def byte_( might be modelled as the byte type .. code-block:: python - byte_() + byte_val() Args: allow_decimal (bool): if the byte value is allowed to be a JSON decimal type, default is False @@ -611,7 +611,7 @@ def byte_( return JsonValue(builder.build()) -def short_( +def short_val( allow_decimal: bool = False, allow_string: bool = False, allow_missing: bool = True, @@ -627,7 +627,7 @@ def short_( might be modelled as the short type .. code-block:: python - short_() + short_val() Args: allow_decimal (bool): if the short value is allowed to be a JSON decimal type, default is False @@ -656,7 +656,7 @@ def short_( return JsonValue(builder.build()) -def int_( +def int_val( allow_decimal: bool = False, allow_string: bool = False, allow_missing: bool = True, @@ -672,7 +672,7 @@ def int_( might be modelled as the int type .. code-block:: python - int_() + int_val() Args: allow_decimal (bool): if the int value is allowed to be a JSON decimal type, default is False @@ -701,7 +701,7 @@ def int_( return JsonValue(builder.build()) -def long_( +def long_val( allow_decimal: bool = False, allow_string: bool = False, allow_missing: bool = True, @@ -717,13 +717,13 @@ def long_( might be modelled as the long type .. code-block:: python - long_() + long_val() In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can simplify by using the python built-in long type. For example, .. code-block:: python - some_method(long_()) + some_method(long_val()) could be simplified to @@ -757,7 +757,7 @@ def long_( return JsonValue(builder.build()) -def float_( +def float_val( allow_string: bool = False, allow_missing: bool = True, allow_null: bool = True, @@ -772,7 +772,7 @@ def float_( might be modelled as the float type .. code-block:: python - float_() + float_val() Args: allow_string (bool): if the float value is allowed to be a JSON string type, default is False @@ -800,7 +800,7 @@ def float_( return JsonValue(builder.build()) -def double_( +def double_val( allow_string: bool = False, allow_missing: bool = True, allow_null: bool = True, @@ -815,13 +815,13 @@ def double_( might be modelled as the double type .. code-block:: python - double_() + double_val() In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can simplify by using the python built-in float type. For example, .. code-block:: python - some_method(double_()) + some_method(double_val()) could be simplified to @@ -854,7 +854,7 @@ def double_( return JsonValue(builder.build()) -def string_( +def string_val( allow_int: bool = False, allow_decimal: bool = False, allow_bool: bool = False, @@ -871,13 +871,13 @@ def string_( might be modelled as the string type .. code-block:: python - string_() + string_val() In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can simplify by using the python built-in str type. For example, .. code-block:: python - some_method(string_()) + some_method(string_val()) could be simplified to @@ -914,7 +914,7 @@ def string_( # TODO(deephaven-core#5269): Create deephaven.time time-type aliases -def instant_( +def instant_val( allow_missing: bool = True, allow_null: bool = True, number_format: Literal[None, "s", "ms", "us", "ns"] = None, @@ -930,7 +930,7 @@ def instant_( might be modelled as the Instant type .. code-block:: python - instant_() + instant_val() In another example, the JSON decimal @@ -940,13 +940,13 @@ def instant_( might be modelled as the Instant type .. code-block:: python - instant_(number_format="s", allow_decimal=True) + instant_val(number_format="s", allow_decimal=True) In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can simplify by using the python datetime type. For example, .. code-block:: python - some_method(instant_()) + some_method(instant_val()) could be simplified to @@ -1007,7 +1007,7 @@ def instant_( return JsonValue(builder.build()) -def big_integer_( +def big_integer_val( allow_string: bool = False, allow_decimal: bool = False, allow_missing: bool = True, @@ -1023,7 +1023,7 @@ def big_integer_( might be modelled as the BigInteger type .. code-block:: python - big_integer_() + big_integer_val() Args: allow_string (bool): if the BigInteger value is allowed to be a JSON string type, default is False. @@ -1052,7 +1052,7 @@ def big_integer_( return JsonValue(builder.build()) -def big_decimal_( +def big_decimal_val( allow_string: bool = False, allow_missing: bool = True, allow_null: bool = True, @@ -1067,7 +1067,7 @@ def big_decimal_( might be modelled as the BigDecimal type .. code-block:: python - big_decimal_() + big_decimal_val() Args: allow_string (bool): if the BigDecimal value is allowed to be a JSON string type, default is False. @@ -1095,7 +1095,7 @@ def big_decimal_( return JsonValue(builder.build()) -def any_() -> JsonValue: +def any_val() -> JsonValue: """Creates an "any" value. The resulting type is implementation dependant. Returns: @@ -1104,7 +1104,7 @@ def any_() -> JsonValue: return JsonValue(_JAnyValue.of()) -def skip_( +def skip_val( allow_missing: Optional[bool] = None, allow_null: Optional[bool] = None, allow_int: Optional[bool] = None, @@ -1124,7 +1124,7 @@ def skip_( might be modelled as the object type .. code-block:: python - object_({ "name": str, "age": skip_() }, allow_unknown_fields=False) + object_val({ "name": str, "age": skip_val() }, allow_unknown_fields=False) Args: allow_missing (Optional[bool]): if a missing JSON value is allowed, by default is None @@ -1159,7 +1159,7 @@ def _allow(x: Optional[bool]) -> bool: return JsonValue(builder.build()) -def json(json_value_type: JsonValueType) -> JsonValue: +def json_val(json_value_type: JsonValueType) -> JsonValue: """Creates a JsonValue from a JsonValueType. Args: @@ -1175,47 +1175,47 @@ def json(json_value_type: JsonValueType) -> JsonValue: if isinstance(json_value_type, type): return _type_dict[json_value_type] if isinstance(json_value_type, Dict): - return object_(json_value_type) + return object_val(json_value_type) if isinstance(json_value_type, List): if len(json_value_type) is not 1: raise TypeError("Expected List as json type to have exactly one element") - return array_(json_value_type[0]) + return array_val(json_value_type[0]) if isinstance(json_value_type, Tuple): - return tuple_(json_value_type) + return tuple_val(json_value_type) raise TypeError(f"Unsupported JSON value type {type(json_value_type)}") _dtype_dict = { - dtypes.bool_: bool_(), - dtypes.char: char_(), - dtypes.int8: byte_(), - dtypes.int16: short_(), - dtypes.int32: int_(), - dtypes.int64: long_(), - dtypes.float32: float_(), - dtypes.float64: double_(), - dtypes.string: string_(), - dtypes.Instant: instant_(), - dtypes.BigInteger: big_integer_(), - dtypes.BigDecimal: big_decimal_(), - dtypes.JObject: any_(), - dtypes.bool_array: array_(bool_()), - dtypes.char_array: array_(char_()), - dtypes.int8_array: array_(byte_()), - dtypes.int16_array: array_(short_()), - dtypes.int32_array: array_(int_()), - dtypes.int64_array: array_(long_()), - dtypes.float32_array: array_(float_()), - dtypes.float64_array: array_(double_()), - dtypes.string_array: array_(string_()), - dtypes.instant_array: array_(instant_()), + dtypes.bool_: bool_val(), + dtypes.char: char_val(), + dtypes.int8: byte_val(), + dtypes.int16: short_val(), + dtypes.int32: int_val(), + dtypes.int64: long_val(), + dtypes.float32: float_val(), + dtypes.float64: double_val(), + dtypes.string: string_val(), + dtypes.Instant: instant_val(), + dtypes.BigInteger: big_integer_val(), + dtypes.BigDecimal: big_decimal_val(), + dtypes.JObject: any_val(), + dtypes.bool_array: array_val(bool_val()), + dtypes.char_array: array_val(char_val()), + dtypes.int8_array: array_val(byte_val()), + dtypes.int16_array: array_val(short_val()), + dtypes.int32_array: array_val(int_val()), + dtypes.int64_array: array_val(long_val()), + dtypes.float32_array: array_val(float_val()), + dtypes.float64_array: array_val(double_val()), + dtypes.string_array: array_val(string_val()), + dtypes.instant_array: array_val(instant_val()), } _type_dict = { - bool: bool_(), - int: long_(), - float: double_(), - str: string_(), - datetime: instant_(), - object: any_(), + bool: bool_val(), + int: long_val(), + float: double_val(), + str: string_val(), + datetime: instant_val(), + object: any_val(), } diff --git a/py/server/deephaven/json/jackson.py b/py/server/deephaven/json/jackson.py index 55c057b3347..22e4549377b 100644 --- a/py/server/deephaven/json/jackson.py +++ b/py/server/deephaven/json/jackson.py @@ -8,7 +8,7 @@ from typing import Optional -from . import JsonValueType, json +from . import JsonValueType, json_val def provider( @@ -25,7 +25,7 @@ def provider( """ _JProvider = jpy.get_type("io.deephaven.json.jackson.JacksonProvider") return ( - _JProvider.of(json(json_value).j_value, factory) + _JProvider.of(json_val(json_value).j_value, factory) if factory - else _JProvider.of(json(json_value).j_value) + else _JProvider.of(json_val(json_value).j_value) ) diff --git a/py/server/tests/test_json.py b/py/server/tests/test_json.py index 91b861f9c68..b9916a48d7f 100644 --- a/py/server/tests/test_json.py +++ b/py/server/tests/test_json.py @@ -17,153 +17,157 @@ def all_equals(items) -> bool: class JsonTestCase(BaseTestCase): def all_same_json_internal(self, items): - self.assertTrue(all_equals([json(x) for x in items])) + self.assertTrue(all_equals([json_val(x) for x in items])) def all_same_json(self, items): self.all_same_json_internal(items) # The following might be seen as redundant special cases, but worthwhile for a bit of extra coverage - self.all_same_json_internal([array_(x) for x in items] + [[x] for x in items]) - self.all_same_json_internal([object_kv_(value_type=x) for x in items]) - self.all_same_json_internal([object_({"Foo": x}) for x in items]) self.all_same_json_internal( - [tuple_((x,)) for x in items] + [(x,) for x in items] + [array_val(x) for x in items] + [[x] for x in items] ) - self.all_same_json_internal([tuple_({"Bar": x}) for x in items]) + self.all_same_json_internal([object_kv_val(value_type=x) for x in items]) + self.all_same_json_internal([object_val({"Foo": x}) for x in items]) self.all_same_json_internal( - [array_(array_(x)) for x in items] + [[[x]] for x in items] + [tuple_val((x,)) for x in items] + [(x,) for x in items] + ) + self.all_same_json_internal([tuple_val({"Bar": x}) for x in items]) + self.all_same_json_internal( + [array_val(array_val(x)) for x in items] + [[[x]] for x in items] + ) + self.all_same_json_internal( + [object_kv_val(value_type=array_val(x)) for x in items] ) - self.all_same_json_internal([object_kv_(value_type=array_(x)) for x in items]) def test_bool(self): - self.all_same_json([bool_(), dtypes.bool_, bool]) + self.all_same_json([bool_val(), dtypes.bool_, bool]) with self.subTest("on_missing"): - bool_(on_missing=False) + bool_val(on_missing=False) with self.subTest("on_null"): - bool_(on_null=False) + bool_val(on_null=False) def test_char(self): - self.all_same_json([char_(), dtypes.char]) + self.all_same_json([char_val(), dtypes.char]) with self.subTest("on_missing"): - char_(on_missing="m") + char_val(on_missing="m") with self.subTest("on_null"): - char_(on_null="n") + char_val(on_null="n") def test_byte(self): - self.all_same_json([byte_(), dtypes.byte]) + self.all_same_json([byte_val(), dtypes.byte]) with self.subTest("on_missing"): - byte_(on_missing=-1) + byte_val(on_missing=-1) with self.subTest("on_null"): - byte_(on_null=-1) + byte_val(on_null=-1) def test_short(self): - self.all_same_json([short_(), dtypes.short]) + self.all_same_json([short_val(), dtypes.short]) with self.subTest("on_missing"): - short_(on_missing=-1) + short_val(on_missing=-1) with self.subTest("on_null"): - short_(on_null=-1) + short_val(on_null=-1) def test_int(self): - self.all_same_json([int_(), dtypes.int32]) + self.all_same_json([int_val(), dtypes.int32]) with self.subTest("on_missing"): - int_(on_missing=-1) + int_val(on_missing=-1) with self.subTest("on_null"): - int_(on_null=-1) + int_val(on_null=-1) def test_long(self): - self.all_same_json([long_(), dtypes.long, int]) + self.all_same_json([long_val(), dtypes.long, int]) with self.subTest("on_missing"): - long_(on_missing=-1) + long_val(on_missing=-1) with self.subTest("on_null"): - long_(on_null=-1) + long_val(on_null=-1) def test_float(self): - self.all_same_json([float_(), dtypes.float32]) + self.all_same_json([float_val(), dtypes.float32]) with self.subTest("on_missing"): - float_(on_missing=-1.0) + float_val(on_missing=-1.0) with self.subTest("on_null"): - float_(on_null=-1.0) + float_val(on_null=-1.0) def test_double(self): - self.all_same_json([double_(), dtypes.double, float]) + self.all_same_json([double_val(), dtypes.double, float]) with self.subTest("on_missing"): - double_(on_missing=-1.0) + double_val(on_missing=-1.0) with self.subTest("on_null"): - double_(on_null=-1.0) + double_val(on_null=-1.0) def test_string(self): - self.all_same_json([string_(), dtypes.string, str]) + self.all_same_json([string_val(), dtypes.string, str]) with self.subTest("on_missing"): - string_(on_missing="(missing)") + string_val(on_missing="(missing)") with self.subTest("on_null"): - string_(on_null="(null)") + string_val(on_null="(null)") def test_instant(self): - self.all_same_json([instant_(), dtypes.Instant, datetime]) + self.all_same_json([instant_val(), dtypes.Instant, datetime]) with self.subTest("on_missing"): - instant_(on_missing=datetime.fromtimestamp(0)) + instant_val(on_missing=datetime.fromtimestamp(0)) with self.subTest("on_null"): - instant_(on_null=datetime.fromtimestamp(0)) + instant_val(on_null=datetime.fromtimestamp(0)) def test_any(self): - self.all_same_json([any_(), dtypes.JObject, object]) + self.all_same_json([any_val(), dtypes.JObject, object]) def test_big_integer(self): - self.all_same_json([big_integer_(), dtypes.BigInteger]) + self.all_same_json([big_integer_val(), dtypes.BigInteger]) with self.subTest("on_missing"): - big_integer_(on_missing=123456789012345678901234567890) + big_integer_val(on_missing=123456789012345678901234567890) with self.subTest("on_null"): - big_integer_(on_null=123456789012345678901234567890) + big_integer_val(on_null=123456789012345678901234567890) def test_big_decimal(self): - self.all_same_json([big_decimal_(), dtypes.BigDecimal]) + self.all_same_json([big_decimal_val(), dtypes.BigDecimal]) with self.subTest("on_missing"): - big_decimal_(on_missing="123456789012345678901234567890.999999999999") + big_decimal_val(on_missing="123456789012345678901234567890.999999999999") with self.subTest("on_null"): - big_decimal_(on_null="123456789012345678901234567890.999999999999") + big_decimal_val(on_null="123456789012345678901234567890.999999999999") def test_object(self): e1 = [ {"name": str, "age": int}, - {"name": string_(), "age": long_()}, + {"name": string_val(), "age": long_val()}, {"name": FieldOptions(str), "age": FieldOptions(int)}, - {"name": FieldOptions(string_()), "age": FieldOptions(long_())}, + {"name": FieldOptions(string_val()), "age": FieldOptions(long_val())}, ] - e2 = [object_(x) for x in e1] + e2 = [object_val(x) for x in e1] self.all_same_json(e1 + e2) def test_array(self): self.all_same_json( [ - array_(int), - array_(long_()), + array_val(int), + array_val(long_val()), [int], - [long_()], + [long_val()], ] ) def test_tuple(self): - e1 = [(str, int), (string_(), long_())] - e2 = [tuple_(x) for x in e1] + e1 = [(str, int), (string_val(), long_val())] + e2 = [tuple_val(x) for x in e1] self.all_same_json(e1 + e2) with self.subTest("named tuple"): self.all_same_json( [ - tuple_({"name": str, "age": int}), - tuple_({"name": string_(), "age": long_()}), + tuple_val({"name": str, "age": int}), + tuple_val({"name": string_val(), "age": long_val()}), ] ) def test_object_kv(self): self.all_same_json( [ - object_kv_(value_type=int), - object_kv_(value_type=long_()), + object_kv_val(value_type=int), + object_kv_val(value_type=long_val()), ] ) def test_skip(self): - self.all_same_json([skip_()]) + self.all_same_json([skip_val()]) if __name__ == "__main__": From 3cf7e9f2b1495f73e8bc8992a5cf173347707f04 Mon Sep 17 00:00:00 2001 From: Devin Smith Date: Thu, 13 Jun 2024 14:34:43 -0700 Subject: [PATCH 51/53] rename ObjectKvValue to ObjectEntriesValue --- .../io/deephaven/json/jackson/JacksonProvider.java | 12 ++++++------ .../main/java/io/deephaven/json/jackson/Mixin.java | 6 +++--- ...{ObjectKvMixin.java => ObjectEntriesMixin.java} | 14 +++++++------- .../java/io/deephaven/json/ObjectKvValueTest.java | 10 +++++----- .../io/deephaven/json/RepeatedProcessorTests.java | 4 ++-- ...{ObjectKvValue.java => ObjectEntriesValue.java} | 14 +++++++------- .../src/main/java/io/deephaven/json/Value.java | 2 +- py/server/deephaven/json/__init__.py | 14 +++++++------- py/server/tests/test_json.py | 10 +++++----- 9 files changed, 43 insertions(+), 43 deletions(-) rename extensions/json-jackson/src/main/java/io/deephaven/json/jackson/{ObjectKvMixin.java => ObjectEntriesMixin.java} (87%) rename extensions/json/src/main/java/io/deephaven/json/{ObjectKvValue.java => ObjectEntriesValue.java} (76%) diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java index 2ab5c1208a9..e70cd0578ef 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/JacksonProvider.java @@ -12,7 +12,7 @@ import io.deephaven.json.DoubleValue; import io.deephaven.json.IntValue; import io.deephaven.json.LongValue; -import io.deephaven.json.ObjectKvValue; +import io.deephaven.json.ObjectEntriesValue; import io.deephaven.json.ObjectValue; import io.deephaven.json.StringValue; import io.deephaven.json.TupleValue; @@ -126,10 +126,10 @@ * {@code ["type", "symbol", "quote_bid", "quote_ask", "trade_price", "trade_size"]}. * *

- * The {@link ObjectKvValue} represents a variable-length object, which expects to parse a JSON object where each - * key-value pair has a common {@link ObjectKvValue#value() value type}. The output type will be the key and value - * element types as component of native arrays ({@link Type#arrayType()}). For example, a JSON object, whose values are - * also JSON objects + * The {@link ObjectEntriesValue} represents a variable-length object, which expects to parse a JSON object where each + * key-value entry has a common {@link ObjectEntriesValue#value() value type}. The output type will be the key and value + * element types as a component of native arrays ({@link Type#arrayType()}). For example, a JSON object, whose values + * are also JSON objects * *

  * {
@@ -144,7 +144,7 @@
  * }
  * 
* - * when represented with structuring as one might expect ({@link ObjectKvValue}({@link StringValue}, + * when represented with structuring as one might expect ({@link ObjectEntriesValue}({@link StringValue}, * {@link ObjectValue}({@link DoubleValue}, {@link DoubleValue}))), will produce {@link ObjectProcessor#outputTypes() * output types} representing {@code [String[], double[], double[]]}, and {@link NamedObjectProcessor#names() names} * {@code ["Key", "latitude", "longitude"]}. diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java index 75a70e0c05c..e0b6c89f640 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/Mixin.java @@ -22,7 +22,7 @@ import io.deephaven.json.LocalDateValue; import io.deephaven.json.LongValue; import io.deephaven.json.ObjectField; -import io.deephaven.json.ObjectKvValue; +import io.deephaven.json.ObjectEntriesValue; import io.deephaven.json.ObjectValue; import io.deephaven.json.ShortValue; import io.deephaven.json.SkipValue; @@ -306,8 +306,8 @@ public ObjectMixin visit(ObjectValue object) { } @Override - public Mixin visit(ObjectKvValue objectKv) { - return new ObjectKvMixin(objectKv, factory); + public Mixin visit(ObjectEntriesValue objectKv) { + return new ObjectEntriesMixin(objectKv, factory); } @Override diff --git a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectEntriesMixin.java similarity index 87% rename from extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java rename to extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectEntriesMixin.java index c3d55c62300..75c3267168d 100644 --- a/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectKvMixin.java +++ b/extensions/json-jackson/src/main/java/io/deephaven/json/jackson/ObjectEntriesMixin.java @@ -6,18 +6,18 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import io.deephaven.chunk.WritableChunk; -import io.deephaven.json.ObjectKvValue; +import io.deephaven.json.ObjectEntriesValue; import io.deephaven.qst.type.Type; import java.io.IOException; import java.util.List; import java.util.stream.Stream; -final class ObjectKvMixin extends Mixin { +final class ObjectEntriesMixin extends Mixin { private final Mixin key; private final Mixin value; - public ObjectKvMixin(ObjectKvValue options, JsonFactory factory) { + public ObjectEntriesMixin(ObjectEntriesValue options, JsonFactory factory) { super(factory, options); key = Mixin.of(options.key(), factory); value = Mixin.of(options.value(), factory); @@ -48,20 +48,20 @@ public Stream> paths() { @Override public ValueProcessor processor(String context) { - return new ObjectKvMixinProcessor(); + return new ObjectEntriesMixinProcessor(); } @Override RepeaterProcessor repeaterProcessor() { - return new ValueInnerRepeaterProcessor(new ObjectKvMixinProcessor()); + return new ValueInnerRepeaterProcessor(new ObjectEntriesMixinProcessor()); } - private class ObjectKvMixinProcessor extends ValueProcessorMixinBase { + private class ObjectEntriesMixinProcessor extends ValueProcessorMixinBase { private final RepeaterProcessor keyProcessor; private final RepeaterProcessor valueProcessor; - ObjectKvMixinProcessor() { + ObjectEntriesMixinProcessor() { this.keyProcessor = key.repeaterProcessor(); this.valueProcessor = value.repeaterProcessor(); } diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvValueTest.java b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvValueTest.java index ab734620e7c..0a7574b66a8 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvValueTest.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/ObjectKvValueTest.java @@ -18,15 +18,15 @@ public class ObjectKvValueTest { - private static final ObjectKvValue STRING_INT_KV = - ObjectKvValue.standard(IntValue.standard()); + private static final ObjectEntriesValue STRING_INT_KV = + ObjectEntriesValue.standard(IntValue.standard()); private static final ObjectValue NAME_AGE_OBJ = ObjectValue.builder() .putFields("name", StringValue.standard()) .putFields("age", IntValue.standard()) .build(); - private static final ObjectKvValue STRING_OBJ_KV = ObjectKvValue.standard(NAME_AGE_OBJ); + private static final ObjectEntriesValue STRING_OBJ_KV = ObjectEntriesValue.standard(NAME_AGE_OBJ); @Test void kvPrimitiveValue() throws IOException { @@ -47,14 +47,14 @@ void kvObjectValue() throws IOException { @Test void kvPrimitiveKey() throws IOException { - parse(ObjectKvValue.builder().key(IntValue.lenient()).value(SkipValue.lenient()).build(), List.of( + parse(ObjectEntriesValue.builder().key(IntValue.lenient()).value(SkipValue.lenient()).build(), List.of( "{\"42\": null, \"43\": null}"), ObjectChunk.chunkWrap(new Object[] {new int[] {42, 43}})); } @Test void kvObjectKey() throws IOException { - parse(ObjectKvValue.builder().key(InstantValue.standard()).value(SkipValue.lenient()).build(), List.of( + parse(ObjectEntriesValue.builder().key(InstantValue.standard()).value(SkipValue.lenient()).build(), List.of( "{\"2009-02-13T23:31:30.123456788Z\": null, \"2009-02-13T23:31:30.123456789Z\": null}"), ObjectChunk.chunkWrap(new Object[] {new Instant[] { Instant.parse("2009-02-13T23:31:30.123456788Z"), diff --git a/extensions/json-jackson/src/test/java/io/deephaven/json/RepeatedProcessorTests.java b/extensions/json-jackson/src/test/java/io/deephaven/json/RepeatedProcessorTests.java index f7c0a087875..42e640144c9 100644 --- a/extensions/json-jackson/src/test/java/io/deephaven/json/RepeatedProcessorTests.java +++ b/extensions/json-jackson/src/test/java/io/deephaven/json/RepeatedProcessorTests.java @@ -25,7 +25,7 @@ void arrayArrayPrimitive() throws IOException { @Test void arrayKvPrimitive() throws IOException { // [{"a": 1.1}, null, {}, {"b": 2.2, "c": 3.3}] - parse(ObjectKvValue.builder().key(SkipValue.lenient()).value(DoubleValue.standard()).build().array(), + parse(ObjectEntriesValue.builder().key(SkipValue.lenient()).value(DoubleValue.standard()).build().array(), "[{\"a\": 1.1}, null, {}, {\"b\": 2.2, \"c\": 3.3}]", ObjectChunk.chunkWrap(new Object[] { new double[][] {new double[] {1.1}, null, new double[0], new double[] {2.2, 3.3}}})); @@ -34,7 +34,7 @@ void arrayKvPrimitive() throws IOException { @Test void kvArrayPrimitive() throws IOException { // {"a": [1.1], "b": null, "c": [], "d": [2.2, 3.3]} - parse(ObjectKvValue.standard(DoubleValue.standard().array()), + parse(ObjectEntriesValue.standard(DoubleValue.standard().array()), "{\"a\": [1.1], \"b\": null, \"c\": [], \"d\": [2.2, 3.3]}", ObjectChunk.chunkWrap(new Object[] { new String[] {"a", "b", "c", "d"}}), diff --git a/extensions/json/src/main/java/io/deephaven/json/ObjectKvValue.java b/extensions/json/src/main/java/io/deephaven/json/ObjectEntriesValue.java similarity index 76% rename from extensions/json/src/main/java/io/deephaven/json/ObjectKvValue.java rename to extensions/json/src/main/java/io/deephaven/json/ObjectEntriesValue.java index 2c24def5cf3..34ac3687d03 100644 --- a/extensions/json/src/main/java/io/deephaven/json/ObjectKvValue.java +++ b/extensions/json/src/main/java/io/deephaven/json/ObjectEntriesValue.java @@ -11,8 +11,8 @@ import java.util.Set; /** - * Processes a JSON object of variable size with a given key and value options. For example, when a JSON object - * structure represents a typed map-like structure (as opposed to a set of typed fields): + * Represents a JSON object of variable size with a given key and value type. For example, when a JSON object structure + * represents a list of key-value entries (as opposed to a set of separately typed fields): * *
  * {
@@ -26,17 +26,17 @@
  */
 @Immutable
 @BuildableStyle
-public abstract class ObjectKvValue extends ValueRestrictedUniverseBase {
+public abstract class ObjectEntriesValue extends ValueRestrictedUniverseBase {
 
     public static Builder builder() {
-        return ImmutableObjectKvValue.builder();
+        return ImmutableObjectEntriesValue.builder();
     }
 
-    public static ObjectKvValue standard(Value value) {
+    public static ObjectEntriesValue standard(Value value) {
         return builder().value(value).build();
     }
 
-    public static ObjectKvValue strict(Value value) {
+    public static ObjectEntriesValue strict(Value value) {
         return builder()
                 .allowMissing(false)
                 .allowedTypes(JsonValueTypes.object())
@@ -78,7 +78,7 @@ public final  T walk(Visitor visitor) {
         return visitor.visit(this);
     }
 
-    public interface Builder extends Value.Builder {
+    public interface Builder extends Value.Builder {
 
         Builder key(Value key);
 
diff --git a/extensions/json/src/main/java/io/deephaven/json/Value.java b/extensions/json/src/main/java/io/deephaven/json/Value.java
index 1d842eaaa96..cbb1451c770 100644
--- a/extensions/json/src/main/java/io/deephaven/json/Value.java
+++ b/extensions/json/src/main/java/io/deephaven/json/Value.java
@@ -89,7 +89,7 @@ public interface Visitor {
 
         T visit(ObjectValue object);
 
-        T visit(ObjectKvValue objectKv);
+        T visit(ObjectEntriesValue objectKv);
 
         T visit(InstantValue instant);
 
diff --git a/py/server/deephaven/json/__init__.py b/py/server/deephaven/json/__init__.py
index 8ce22106142..9ca58e3ae8f 100644
--- a/py/server/deephaven/json/__init__.py
+++ b/py/server/deephaven/json/__init__.py
@@ -37,7 +37,7 @@
     "big_decimal_val",
     "array_val",
     "object_val",
-    "object_kv_val",
+    "object_entries_val",
     "tuple_val",
     "any_val",
     "skip_val",
@@ -52,7 +52,7 @@
 _JObjectValue = jpy.get_type("io.deephaven.json.ObjectValue")
 _JTypedObjectValue = jpy.get_type("io.deephaven.json.TypedObjectValue")
 _JArrayValue = jpy.get_type("io.deephaven.json.ArrayValue")
-_JObjectKvValue = jpy.get_type("io.deephaven.json.ObjectKvValue")
+_JObjectEntriesValue = jpy.get_type("io.deephaven.json.ObjectEntriesValue")
 _JTupleValue = jpy.get_type("io.deephaven.json.TupleValue")
 _JObjectField = jpy.get_type("io.deephaven.json.ObjectField")
 _JRepeatedFieldBehavior = jpy.get_type("io.deephaven.json.ObjectField$RepeatedBehavior")
@@ -379,13 +379,13 @@ def array_val(
     return JsonValue(builder.build())
 
 
-def object_kv_val(
+def object_entries_val(
     key_type: JsonValueType = str,
     value_type: Optional[JsonValueType] = None,
     allow_missing: bool = True,
     allow_null: bool = True,
 ) -> JsonValue:
-    """Creates an object key-value options. This is used in situations where the number of fields in an object is
+    """Creates an object entries value. This is used in situations where the number of fields in an object is
     variable and all the values types are the same. For example, the JSON object
 
     .. code-block:: json
@@ -400,7 +400,7 @@ def object_kv_val(
     might be modelled as the object kv type
 
     .. code-block:: python
-        object_kv_val(value_type=int)
+        object_entries_val(value_type=int)
 
     Args:
         key_type (JsonValueType): the key element, by defaults is type str
@@ -409,9 +409,9 @@ def object_kv_val(
         allow_null (bool): if the object is allowed to be a JSON null type, by default is True
 
     Returns:
-        the object kv value
+        the object entries value
     """
-    builder = _JObjectKvValue.builder()
+    builder = _JObjectEntriesValue.builder()
     builder.key(json_val(key_type).j_value)
     builder.value(json_val(value_type).j_value)
     _build(builder, allow_missing, allow_null, allow_object=True)
diff --git a/py/server/tests/test_json.py b/py/server/tests/test_json.py
index b9916a48d7f..ff785844ffe 100644
--- a/py/server/tests/test_json.py
+++ b/py/server/tests/test_json.py
@@ -25,7 +25,7 @@ def all_same_json(self, items):
         self.all_same_json_internal(
             [array_val(x) for x in items] + [[x] for x in items]
         )
-        self.all_same_json_internal([object_kv_val(value_type=x) for x in items])
+        self.all_same_json_internal([object_entries_val(value_type=x) for x in items])
         self.all_same_json_internal([object_val({"Foo": x}) for x in items])
         self.all_same_json_internal(
             [tuple_val((x,)) for x in items] + [(x,) for x in items]
@@ -35,7 +35,7 @@ def all_same_json(self, items):
             [array_val(array_val(x)) for x in items] + [[[x]] for x in items]
         )
         self.all_same_json_internal(
-            [object_kv_val(value_type=array_val(x)) for x in items]
+            [object_entries_val(value_type=array_val(x)) for x in items]
         )
 
     def test_bool(self):
@@ -158,11 +158,11 @@ def test_tuple(self):
                 ]
             )
 
-    def test_object_kv(self):
+    def test_object_entries(self):
         self.all_same_json(
             [
-                object_kv_val(value_type=int),
-                object_kv_val(value_type=long_val()),
+                object_entries_val(value_type=int),
+                object_entries_val(value_type=long_val()),
             ]
         )
 

From 7e81e4199c9c2f6394bbfc67b3910dbd5096ae28 Mon Sep 17 00:00:00 2001
From: Devin Smith 
Date: Fri, 14 Jun 2024 16:28:14 -0700
Subject: [PATCH 52/53] Review responses

---
 .../src/main/resources/logback-minimal.xml    |   2 +-
 py/server/deephaven/json/__init__.py          | 105 +++++++++---------
 py/server/deephaven/json/jackson.py           |  15 +--
 py/server/tests/test_json.py                  |  14 +--
 4 files changed, 70 insertions(+), 66 deletions(-)

diff --git a/py/embedded-server/java-runtime/src/main/resources/logback-minimal.xml b/py/embedded-server/java-runtime/src/main/resources/logback-minimal.xml
index 4b1fadb1e55..1922705b272 100644
--- a/py/embedded-server/java-runtime/src/main/resources/logback-minimal.xml
+++ b/py/embedded-server/java-runtime/src/main/resources/logback-minimal.xml
@@ -15,7 +15,7 @@
   
   
 
-  
+  
     
   
 
diff --git a/py/server/deephaven/json/__init__.py b/py/server/deephaven/json/__init__.py
index 9ca58e3ae8f..3187d061131 100644
--- a/py/server/deephaven/json/__init__.py
+++ b/py/server/deephaven/json/__init__.py
@@ -37,6 +37,7 @@
     "big_decimal_val",
     "array_val",
     "object_val",
+    "typed_object_val",
     "object_entries_val",
     "tuple_val",
     "any_val",
@@ -45,7 +46,7 @@
     "JsonValue",
     "JsonValueType",
     "RepeatedFieldBehavior",
-    "FieldOptions",
+    "ObjectField",
 ]
 
 _JValue = jpy.get_type("io.deephaven.json.Value")
@@ -106,10 +107,37 @@ def j_object(self) -> jpy.JType:
     JsonValue,
     dtypes.DType,
     type,
-    Dict[str, Union["JsonValueType", "FieldOptions"]],
+    Dict[str, Union["JsonValueType", "ObjectField"]],
     List["JsonValueType"],
     Tuple["JsonValueType", ...],
 ]
+"""The JSON value alias"""
+
+
+def json_val(json_value_type: JsonValueType) -> JsonValue:
+    """Creates a JsonValue from a JsonValueType.
+
+    Args:
+        json_value_type (JsonValueType): the JSON value type
+
+    Returns:
+        the JSON value
+    """
+    if isinstance(json_value_type, JsonValue):
+        return json_value_type
+    if isinstance(json_value_type, dtypes.DType):
+        return _dtype_dict[json_value_type]
+    if isinstance(json_value_type, type):
+        return _type_dict[json_value_type]
+    if isinstance(json_value_type, Dict):
+        return object_val(json_value_type)
+    if isinstance(json_value_type, List):
+        if len(json_value_type) is not 1:
+            raise TypeError("Expected List as json type to have exactly one element")
+        return array_val(json_value_type[0])
+    if isinstance(json_value_type, Tuple):
+        return tuple_val(json_value_type)
+    raise TypeError(f"Unsupported JSON value type {type(json_value_type)}")
 
 
 class RepeatedFieldBehavior(Enum):
@@ -130,7 +158,7 @@ class RepeatedFieldBehavior(Enum):
 
 
 @dataclass
-class FieldOptions:
+class ObjectField:
     """The object field options.
 
     In contexts where the user needs to create an object field value and isn't changing any default values, the user can
@@ -138,8 +166,8 @@ class FieldOptions:
 
     .. code-block:: python
         {
-            "name": FieldOptions(str),
-            "age": FieldOptions(int),
+            "name": ObjectField(str),
+            "age": ObjectField(int),
         }
 
     could be simplified to
@@ -151,7 +179,7 @@ class FieldOptions:
         }
     """
 
-    value: JsonValueType
+    value_type: JsonValueType
     """The json value type"""
     aliases: Union[str, List[str]] = field(default_factory=list)
     """The field name aliases. By default, is an empty list."""
@@ -164,7 +192,7 @@ def _j_field_options(self, name: str) -> jpy.JType:
         builder = (
             _JObjectField.builder()
             .name(name)
-            .options(json_val(self.value).j_value)
+            .options(json_val(self.value_type).j_value)
             .repeatedBehavior(self.repeated_behavior.value)
             .caseSensitive(self.case_sensitive)
         )
@@ -199,7 +227,7 @@ def _build(
 
 
 def object_val(
-    fields: Dict[str, Union[JsonValueType, FieldOptions]],
+    fields: Dict[str, Union[JsonValueType, ObjectField]],
     allow_unknown_fields: bool = True,
     allow_missing: bool = True,
     allow_null: bool = True,
@@ -217,7 +245,7 @@ def object_val(
         object_val({ "name": str, "age": int })
 
     In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can
-    simplify by using a Dict[str, Union[JsonValueType, FieldOptions]]. For example,
+    simplify by using a Dict[str, Union[JsonValueType, ObjectField]]. For example,
 
     .. code-block:: python
         some_method(object_val({ "name": str, "age": int }))
@@ -228,14 +256,14 @@ def object_val(
         some_method({ "name": str, "age": int })
 
     Args:
-        fields (Dict[str, Union[JsonValueType, FieldOptions]]): the fields
+        fields (Dict[str, Union[JsonValueType, ObjectField]]): the fields
         allow_unknown_fields (bool): if unknown fields are allow, by default is True
         allow_missing (bool): if the object is allowed to be missing, by default is True
         allow_null (bool): if the object is allowed to be a JSON null type, by default is True
         repeated_field_behavior (RepeatedFieldBehavior): the default repeated field behavior, only used for fields that
             are specified using JsonValueType, by default is RepeatedFieldBehavior.ERROR
-        case_sensitive (bool): if default to use for field case-sensitivity. Only used for fields that are specified
-            using JsonValueType, by default is True
+        case_sensitive (bool): if the field name and aliases should be compared using case-sensitive equality, only used
+            for fields that are specified using JsonValueType, by default is True
 
     Returns:
         the object value
@@ -246,8 +274,8 @@ def object_val(
     for field_name, field_opts in fields.items():
         field_opts = (
             field_opts
-            if isinstance(field_opts, FieldOptions)
-            else FieldOptions(
+            if isinstance(field_opts, ObjectField)
+            else ObjectField(
                 field_opts,
                 repeated_behavior=repeated_field_behavior,
                 case_sensitive=case_sensitive,
@@ -260,7 +288,7 @@ def object_val(
 
 def typed_object_val(
     type_field: str,
-    shared_fields: Dict[str, Union[JsonValueType, FieldOptions]],
+    shared_fields: Dict[str, Union[JsonValueType, ObjectField]],
     objects: Dict[str, JsonValueType],
     allow_unknown_types: bool = True,
     allow_missing: bool = True,
@@ -298,8 +326,8 @@ def typed_object_val(
 
     Args:
         type_field (str): the type-discriminating field
-        shared_fields (Dict[str, Union[JsonValueType, FieldOptions]]): the shared fields
-        objects (Dict[str, Union[JsonValueType, FieldOptions]]): the individual objects, keyed by their
+        shared_fields (Dict[str, Union[JsonValueType, ObjectField]]): the shared fields
+        objects (Dict[str, Union[JsonValueType, ObjectField]]): the individual objects, keyed by their
             type-discriminated value. The values must be object options.
         allow_unknown_types (bool): if unknown types are allow, by default is True
         allow_missing (bool): if the object is allowed to be missing, by default is True
@@ -323,8 +351,8 @@ def typed_object_val(
     for shared_field_name, shared_field_opts in shared_fields.items():
         shared_field_opts = (
             shared_field_opts
-            if isinstance(shared_field_opts, FieldOptions)
-            else FieldOptions(
+            if isinstance(shared_field_opts, ObjectField)
+            else ObjectField(
                 shared_field_opts,
                 repeated_behavior=RepeatedFieldBehavior.ERROR,
                 case_sensitive=True,
@@ -380,8 +408,8 @@ def array_val(
 
 
 def object_entries_val(
+    value_type: JsonValueType,
     key_type: JsonValueType = str,
-    value_type: Optional[JsonValueType] = None,
     allow_missing: bool = True,
     allow_null: bool = True,
 ) -> JsonValue:
@@ -400,11 +428,11 @@ def object_entries_val(
     might be modelled as the object kv type
 
     .. code-block:: python
-        object_entries_val(value_type=int)
+        object_entries_val(int)
 
     Args:
-        key_type (JsonValueType): the key element, by defaults is type str
-        value_type (Optional[JsonValueType]): the value element, required
+        value_type (JsonValueType): the value type element, required
+        key_type (JsonValueType): the key type element, by default is type str
         allow_missing (bool): if the object is allowed to be missing, by default is True
         allow_null (bool): if the object is allowed to be a JSON null type, by default is True
 
@@ -438,6 +466,8 @@ def tuple_val(
     .. code-block:: python
         tuple_val({"name": str, "age": int, "height": float})
 
+    otherwise, default names based on the indexes of the values will be used.
+
     In contexts where the user needs to create a JsonValueType and isn't changing any default values nor is setting
     names, the user can simplify passing through a python tuple type. For example,
 
@@ -957,7 +987,8 @@ def instant_val(
         allow_missing (bool): if the Instant value is allowed to be missing, default is True
         allow_null (bool): if the Instant value is allowed to be a JSON null type, default is True
         number_format (Literal[None, "s", "ms", "us", "ns"]): when set, signifies that a JSON numeric type is expected.
-            "s" is for seconds, "ms" is for milliseconds, "us" is for microseconds, and "ns" is for nanoseconds.
+            "s" is for seconds, "ms" is for milliseconds, "us" is for microseconds, and "ns" is for nanoseconds since
+            the epoch. When not set, a JSON string in the ISO-8601 format is expected.
         allow_decimal (bool): if the Instant value is allowed to be a JSON decimal type, default is False. Only valid
             when number_format is specified.
         on_missing (Optional[Any]): the value to use when the JSON value is missing and allow_missing is True, default is None.
@@ -1159,32 +1190,6 @@ def _allow(x: Optional[bool]) -> bool:
     return JsonValue(builder.build())
 
 
-def json_val(json_value_type: JsonValueType) -> JsonValue:
-    """Creates a JsonValue from a JsonValueType.
-
-    Args:
-        json_value_type (JsonValueType): the JSON value type
-
-    Returns:
-        the JSON value
-    """
-    if isinstance(json_value_type, JsonValue):
-        return json_value_type
-    if isinstance(json_value_type, dtypes.DType):
-        return _dtype_dict[json_value_type]
-    if isinstance(json_value_type, type):
-        return _type_dict[json_value_type]
-    if isinstance(json_value_type, Dict):
-        return object_val(json_value_type)
-    if isinstance(json_value_type, List):
-        if len(json_value_type) is not 1:
-            raise TypeError("Expected List as json type to have exactly one element")
-        return array_val(json_value_type[0])
-    if isinstance(json_value_type, Tuple):
-        return tuple_val(json_value_type)
-    raise TypeError(f"Unsupported JSON value type {type(json_value_type)}")
-
-
 _dtype_dict = {
     dtypes.bool_: bool_val(),
     dtypes.char: char_val(),
diff --git a/py/server/deephaven/json/jackson.py b/py/server/deephaven/json/jackson.py
index 22e4549377b..4b206df6900 100644
--- a/py/server/deephaven/json/jackson.py
+++ b/py/server/deephaven/json/jackson.py
@@ -2,7 +2,7 @@
 # Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending
 #
 
-"""A JSON processor provider implementation using Jackson."""
+"""A JSON processor provider implementation using Jackson (https://github.com/FasterXML/jackson)."""
 
 import jpy
 
@@ -12,20 +12,21 @@
 
 
 def provider(
-    json_value: JsonValueType, factory: Optional[jpy.JType] = None
+    json_value_type: JsonValueType, factory: Optional[jpy.JType] = None
 ) -> jpy.JType:
-    """Creates a jackson JSON named object processor provider.
+    """Creates a Jackson JSON named object processor provider.
 
     Args:
-        json_value(JsonValueType): the JSON value
-        factory(Optional[jpy.JType]): the factory (java type "com.fasterxml.jackson.core.JsonFactory"), by default is None
+        json_value_type (JsonValueType): the JSON value
+        factory (Optional[jpy.JType]): the factory (java type "com.fasterxml.jackson.core.JsonFactory"), by default is
+            None which will use a default factory
 
     Returns:
         the jackson JSON named object processor provider
     """
     _JProvider = jpy.get_type("io.deephaven.json.jackson.JacksonProvider")
     return (
-        _JProvider.of(json_val(json_value).j_value, factory)
+        _JProvider.of(json_val(json_value_type).j_value, factory)
         if factory
-        else _JProvider.of(json_val(json_value).j_value)
+        else _JProvider.of(json_val(json_value_type).j_value)
     )
diff --git a/py/server/tests/test_json.py b/py/server/tests/test_json.py
index ff785844ffe..cc10c586a20 100644
--- a/py/server/tests/test_json.py
+++ b/py/server/tests/test_json.py
@@ -25,7 +25,7 @@ def all_same_json(self, items):
         self.all_same_json_internal(
             [array_val(x) for x in items] + [[x] for x in items]
         )
-        self.all_same_json_internal([object_entries_val(value_type=x) for x in items])
+        self.all_same_json_internal([object_entries_val(x) for x in items])
         self.all_same_json_internal([object_val({"Foo": x}) for x in items])
         self.all_same_json_internal(
             [tuple_val((x,)) for x in items] + [(x,) for x in items]
@@ -34,9 +34,7 @@ def all_same_json(self, items):
         self.all_same_json_internal(
             [array_val(array_val(x)) for x in items] + [[[x]] for x in items]
         )
-        self.all_same_json_internal(
-            [object_entries_val(value_type=array_val(x)) for x in items]
-        )
+        self.all_same_json_internal([object_entries_val(array_val(x)) for x in items])
 
     def test_bool(self):
         self.all_same_json([bool_val(), dtypes.bool_, bool])
@@ -129,8 +127,8 @@ def test_object(self):
         e1 = [
             {"name": str, "age": int},
             {"name": string_val(), "age": long_val()},
-            {"name": FieldOptions(str), "age": FieldOptions(int)},
-            {"name": FieldOptions(string_val()), "age": FieldOptions(long_val())},
+            {"name": ObjectField(str), "age": ObjectField(int)},
+            {"name": ObjectField(string_val()), "age": ObjectField(long_val())},
         ]
         e2 = [object_val(x) for x in e1]
         self.all_same_json(e1 + e2)
@@ -161,8 +159,8 @@ def test_tuple(self):
     def test_object_entries(self):
         self.all_same_json(
             [
-                object_entries_val(value_type=int),
-                object_entries_val(value_type=long_val()),
+                object_entries_val(int),
+                object_entries_val(long_val()),
             ]
         )
 

From b3195121322121434d3ff10a519330333915949a Mon Sep 17 00:00:00 2001
From: Devin Smith 
Date: Mon, 17 Jun 2024 14:02:32 -0700
Subject: [PATCH 53/53] Add high-level example documentation is python json
 module

---
 py/server/deephaven/json/__init__.py | 71 +++++++++++++++++++++++++---
 1 file changed, 64 insertions(+), 7 deletions(-)

diff --git a/py/server/deephaven/json/__init__.py b/py/server/deephaven/json/__init__.py
index 3187d061131..77957733df8 100644
--- a/py/server/deephaven/json/__init__.py
+++ b/py/server/deephaven/json/__init__.py
@@ -3,11 +3,57 @@
 #
 
 """The deephaven JSON module presents a declarative and composable configuration layer for describing the structure of a
-JSON value. It is meant to have sane defaults while also providing finer-grained configuration options for typical
-scenarios. The primary purpose of this module is to provide a common layer that various consumers can use to parse JSON
-values into appropriate Deephaven structures. As such (and by the very nature of JSON), these types represent a superset
-of JSON. This module can also service other use cases where the JSON structuring is necessary (for example, producing a
-JSON value from a Deephaven structure).
+JSON (https://www.json.org) value. Most commonly, this will be used to model the structure for a JSON object. For
+example, the JSON object
+
+.. code-block:: json
+    { "name": "Foo", "age": 42, "location": { "lat": 45.018269, "lon": -93.473892 } }
+
+can be modelled with the dictionary
+
+.. code-block:: python
+    { "name": str, "age": int, "location": { "lat": float, "lon": float } }
+
+Notice that this allows for the nested modelling of JSON values. Other common constructions involve the modelling of
+JSON arrays. For example, a variable-length JSON array where the elements are the same type
+
+.. code-block:: json
+    [42, 31, ..., 12345]
+
+can be modelled with a single-element list containing the element type
+
+.. code-block:: python
+    [ int ]
+
+If the JSON array is a fixed size and each elements' type is known, for example
+
+.. code-block:: json
+    ["Foo", 42, [45.018269, -93.473892]]
+
+can be modelled with a tuple containing each type
+
+.. code-block:: python
+    (str, int, (float, float))
+
+Notice again that this allows for the nested modelling of JSON values. Of course, these constructions can be all be used
+together. For example, the JSON object
+
+.. code-block:: json
+    {
+      "name": "Foo",
+      "locations": [
+        [45.018269, -93.473892],
+        ...,
+        [40.730610, -73.935242]
+      ]
+    }
+
+can be modelled as
+
+.. code-block:: python
+    {"name": str, "locations": [(float, float)]}
+
+See the methods in this module more more details on modelling JSON values.
 """
 
 import jpy
@@ -117,6 +163,17 @@ def j_object(self) -> jpy.JType:
 def json_val(json_value_type: JsonValueType) -> JsonValue:
     """Creates a JsonValue from a JsonValueType.
 
+     - JsonValue is returned unchanged
+     - bool returns bool_val()
+     - int returns long_val()
+     - float returns double_val()
+     - str returns string_val()
+     - datetime.datetime returns instant_val()
+     - object returns any_val()
+     - Dictionaries returns object_val(json_value_type)
+     - Lists of length 1 returns array_val(json_value_type[0]) (Lists of other sizes are not supported)
+     - Tuples returns tuple_val(json_value_type)
+
     Args:
         json_value_type (JsonValueType): the JSON value type
 
@@ -973,7 +1030,7 @@ def instant_val(
         instant_val(number_format="s", allow_decimal=True)
 
     In contexts where the user needs to create a JsonValueType and isn't changing any default values, the user can
-    simplify by using the python datetime type. For example,
+    simplify by using the python datetime.datetime type. For example,
 
     .. code-block:: python
         some_method(instant_val())
@@ -981,7 +1038,7 @@ def instant_val(
     could be simplified to
 
     .. code-block:: python
-        some_method(datetime)
+        some_method(datetime.datetime)
 
     Args:
         allow_missing (bool): if the Instant value is allowed to be missing, default is True