This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new e2f62c7ae feat(java): add async codegen for Fory JSON (#3825)
e2f62c7ae is described below
commit e2f62c7ae439ef4e2fa1492bd6556a4bf81eda4a
Author: Shawn Yang <[email protected]>
AuthorDate: Wed Jul 8 22:23:23 2026 +0530
feat(java): add async codegen for Fory JSON (#3825)
## What changed
- Added `ForyJsonBuilder.withAsyncCompilation(boolean)` with async
compilation enabled by default for Fory JSON.
- Added `JsonJITContext` to publish generated JSON object codecs using
the core JIT callback model.
- Removed the `ObjectCodecs` carrier and made generated codecs own their
writer/reader delegates directly.
- Updated resolver-owned object codec publication so async compilation
replaces the single current codec without wrapper/factory/state
abstractions.
- Parameterized Fory JSON tests over codegen on/off with async disabled,
and added dedicated async compilation coverage.
## Validation
- `ENABLE_FORY_DEBUG_OUTPUT=1 mvn -pl fory-json -DskipTests compile`
- `ENABLE_FORY_DEBUG_OUTPUT=1 mvn -pl fory-json -DskipTests
test-compile`
- `ENABLE_FORY_DEBUG_OUTPUT=1 mvn -pl fory-json
-Dtest=JsonAsyncCompilationTest,JsonGeneratedCodecTest test`
- `ENABLE_FORY_DEBUG_OUTPUT=1 mvn -pl fory-json test`
- `mvn -pl fory-json spotless:apply`
- `git diff --cached --check`
---
.../java/org/apache/fory/json/ForyJsonBuilder.java | 16 +-
.../main/java/org/apache/fory/json/JsonConfig.java | 64 +++-
.../org/apache/fory/json/codec/ArrayCodec.java | 9 +-
.../apache/fory/json/codec/CollectionCodec.java | 9 +-
.../fory/json/codec/GeneratedObjectCodec.java | 21 +-
.../java/org/apache/fory/json/codec/MapCodec.java | 3 +-
.../org/apache/fory/json/codec/ObjectCodec.java | 17 +-
.../org/apache/fory/json/codec/ObjectCodecs.java | 75 -----
.../org/apache/fory/json/codec/ScalarCodecs.java | 9 +-
.../org/apache/fory/json/codegen/JsonCodegen.java | 320 ++++++++++++------
.../apache/fory/json/codegen/JsonJITContext.java | 205 ++++++++++++
.../fory/json/resolver/JsonSharedRegistry.java | 54 +++-
.../apache/fory/json/resolver/JsonTypeInfo.java | 8 +-
.../fory/json/resolver/JsonTypeResolver.java | 199 +++++++++++-
.../org/apache/fory/json/ForyJsonTestModels.java | 27 +-
.../apache/fory/json/JsonAsyncCompilationTest.java | 360 +++++++++++++++++++++
.../org/apache/fory/json/JsonContainerTest.java | 64 ++--
.../java/org/apache/fory/json/JsonDepthTest.java | 20 +-
.../apache/fory/json/JsonGeneratedCodecTest.java | 51 ++-
.../fory/json/JsonGuavaOptionalDependencyTest.java | 18 +-
.../java/org/apache/fory/json/JsonObjectTest.java | 38 ++-
.../org/apache/fory/json/JsonPropertyTest.java | 28 +-
.../java/org/apache/fory/json/JsonRecordTest.java | 8 +-
.../java/org/apache/fory/json/JsonScalarTest.java | 88 ++---
.../java/org/apache/fory/json/JsonStringTest.java | 40 ++-
25 files changed, 1394 insertions(+), 357 deletions(-)
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/ForyJsonBuilder.java
b/java/fory-json/src/main/java/org/apache/fory/json/ForyJsonBuilder.java
index 148c24edd..a3795048a 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/ForyJsonBuilder.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/ForyJsonBuilder.java
@@ -26,6 +26,7 @@ import org.apache.fory.json.resolver.CodecRegistry;
public final class ForyJsonBuilder {
private boolean writeNullFields;
private boolean codegenEnabled = true;
+ private boolean asyncCompilationEnabled = true;
private boolean propertyDiscoveryEnabled = true;
private int maxDepth = ForyJson.DEFAULT_MAX_DEPTH;
private final CodecRegistry codecRegistry = new CodecRegistry();
@@ -38,12 +39,18 @@ public final class ForyJsonBuilder {
return this;
}
- /** Enables runtime-generated writers for supported public-field classes. */
+ /** Enables generated object codecs for supported classes. Enabled by
default. */
public ForyJsonBuilder withCodegen(boolean codegenEnabled) {
this.codegenEnabled = codegenEnabled;
return this;
}
+ /** Enables asynchronous runtime compilation for generated object codecs.
Enabled by default. */
+ public ForyJsonBuilder withAsyncCompilation(boolean asyncCompilationEnabled)
{
+ this.asyncCompilationEnabled = asyncCompilationEnabled;
+ return this;
+ }
+
/**
* Enables field mode, where JSON object members are discovered from Java
fields only. When
* disabled, Fory JSON uses the default JavaBean property model: public
getters, public setters,
@@ -72,6 +79,11 @@ public final class ForyJsonBuilder {
public ForyJson build() {
return new ForyJson(
new JsonConfig(
- writeNullFields, codegenEnabled, propertyDiscoveryEnabled,
maxDepth, codecRegistry));
+ writeNullFields,
+ codegenEnabled,
+ asyncCompilationEnabled,
+ propertyDiscoveryEnabled,
+ maxDepth,
+ codecRegistry));
}
}
diff --git a/java/fory-json/src/main/java/org/apache/fory/json/JsonConfig.java
b/java/fory-json/src/main/java/org/apache/fory/json/JsonConfig.java
index 3cda718a4..a725db01f 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/JsonConfig.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/JsonConfig.java
@@ -29,24 +29,29 @@ import org.apache.fory.json.resolver.CodecRegistry;
public final class JsonConfig {
private final boolean writeNullFields;
private final boolean codegenEnabled;
+ private final boolean asyncCompilationEnabled;
private final boolean propertyDiscoveryEnabled;
private final int maxDepth;
private final CodecRegistry codecRegistry;
private final String codecRegistryKey;
- private transient int configHash;
+ private final CodegenKey codegenKey;
+ private transient int codegenHash;
JsonConfig(
boolean writeNullFields,
boolean codegenEnabled,
+ boolean asyncCompilationEnabled,
boolean propertyDiscoveryEnabled,
int maxDepth,
CodecRegistry codecRegistry) {
this.writeNullFields = writeNullFields;
this.codegenEnabled = codegenEnabled;
+ this.asyncCompilationEnabled = asyncCompilationEnabled;
this.propertyDiscoveryEnabled = propertyDiscoveryEnabled;
this.maxDepth = maxDepth;
this.codecRegistry = codecRegistry;
codecRegistryKey = codecRegistry.codegenKey();
+ codegenKey = new CodegenKey(writeNullFields, propertyDiscoveryEnabled,
codecRegistryKey);
}
public boolean writeNullFields() {
@@ -57,6 +62,10 @@ public final class JsonConfig {
return codegenEnabled;
}
+ public boolean asyncCompilationEnabled() {
+ return asyncCompilationEnabled;
+ }
+
public boolean propertyDiscoveryEnabled() {
return propertyDiscoveryEnabled;
}
@@ -80,6 +89,7 @@ public final class JsonConfig {
JsonConfig that = (JsonConfig) other;
return writeNullFields == that.writeNullFields
&& codegenEnabled == that.codegenEnabled
+ && asyncCompilationEnabled == that.asyncCompilationEnabled
&& propertyDiscoveryEnabled == that.propertyDiscoveryEnabled
&& maxDepth == that.maxDepth
&& Objects.equals(codecRegistryKey, that.codecRegistryKey);
@@ -88,18 +98,56 @@ public final class JsonConfig {
@Override
public int hashCode() {
return Objects.hash(
- writeNullFields, codegenEnabled, propertyDiscoveryEnabled, maxDepth,
codecRegistryKey);
+ writeNullFields,
+ codegenEnabled,
+ asyncCompilationEnabled,
+ propertyDiscoveryEnabled,
+ maxDepth,
+ codecRegistryKey);
}
private static final AtomicInteger COUNTER = new AtomicInteger(0);
- // Equal configs share one map entry, following core Config's generated-code
naming model.
- private static final ConcurrentMap<JsonConfig, Integer> CONFIG_ID_MAP = new
ConcurrentHashMap<>();
+ // Equal generated source inputs share one map entry, following core
generated-code naming model.
+ private static final ConcurrentMap<CodegenKey, Integer> CODEGEN_ID_MAP =
+ new ConcurrentHashMap<>();
+
+ public int getCodegenHash() {
+ if (codegenHash == 0) {
+ codegenHash = CODEGEN_ID_MAP.computeIfAbsent(codegenKey, key ->
COUNTER.incrementAndGet());
+ }
+ return codegenHash;
+ }
+
+ private static final class CodegenKey {
+ private final boolean writeNullFields;
+ private final boolean propertyDiscoveryEnabled;
+ private final String codecRegistryKey;
+
+ private CodegenKey(
+ boolean writeNullFields, boolean propertyDiscoveryEnabled, String
codecRegistryKey) {
+ this.writeNullFields = writeNullFields;
+ this.propertyDiscoveryEnabled = propertyDiscoveryEnabled;
+ this.codecRegistryKey = codecRegistryKey;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+ CodegenKey that = (CodegenKey) other;
+ return writeNullFields == that.writeNullFields
+ && propertyDiscoveryEnabled == that.propertyDiscoveryEnabled
+ && Objects.equals(codecRegistryKey, that.codecRegistryKey);
+ }
- public int getConfigHash() {
- if (configHash == 0) {
- configHash = CONFIG_ID_MAP.computeIfAbsent(this, key ->
COUNTER.incrementAndGet());
+ @Override
+ public int hashCode() {
+ return Objects.hash(writeNullFields, propertyDiscoveryEnabled,
codecRegistryKey);
}
- return configHash;
}
}
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/codec/ArrayCodec.java
b/java/fory-json/src/main/java/org/apache/fory/json/codec/ArrayCodec.java
index d45e014d2..563e62c61 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/codec/ArrayCodec.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/codec/ArrayCodec.java
@@ -75,7 +75,8 @@ public abstract class ArrayCodec extends AbstractJsonCodec {
} else if (componentType == String.class) {
return StringArrayCodec.INSTANCE;
}
- return new ObjectArrayCodec(componentType,
resolver.getTypeInfo(componentType, componentType));
+ return new ObjectArrayCodec(
+ componentType, resolver.getTypeInfo(componentType, componentType),
resolver);
}
@Override
@@ -1231,15 +1232,17 @@ public abstract class ArrayCodec extends
AbstractJsonCodec {
private static final int MAX_CACHED_VALUES_SIZE = 1024;
private final JsonTypeInfo elementTypeInfo;
- private final JsonCodec elementCodec;
+ private JsonCodec elementCodec;
// Recursive object-array reads borrow one scratch slot per active depth.
private final Object[][] valuesCache = new Object[VALUES_CACHE_DEPTH][];
private int valuesDepth;
- private ObjectArrayCodec(Class<?> componentType, JsonTypeInfo
elementTypeInfo) {
+ private ObjectArrayCodec(
+ Class<?> componentType, JsonTypeInfo elementTypeInfo, JsonTypeResolver
resolver) {
super(componentType);
this.elementTypeInfo = elementTypeInfo;
elementCodec = elementTypeInfo.codec();
+ resolver.registerJITNotifyCallback(elementCodec, codec -> elementCodec =
codec);
}
@Override
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/codec/CollectionCodec.java
b/java/fory-json/src/main/java/org/apache/fory/json/codec/CollectionCodec.java
index c1a68c244..ace05e65c 100644
---
a/java/fory-json/src/main/java/org/apache/fory/json/codec/CollectionCodec.java
+++
b/java/fory-json/src/main/java/org/apache/fory/json/codec/CollectionCodec.java
@@ -109,7 +109,7 @@ public abstract class CollectionCodec extends
AbstractJsonCodec {
JsonCodec elementCodec = elementTypeInfo.codec();
if (elementCodec instanceof BaseObjectCodec) {
return new ObjectCollectionCodec(
- typeRef, factory, elementTypeInfo, (BaseObjectCodec) elementCodec);
+ typeRef, factory, elementTypeInfo, (BaseObjectCodec) elementCodec,
resolver);
}
return new GenericCollectionCodec(typeRef, factory, elementTypeInfo,
elementCodec);
}
@@ -877,16 +877,19 @@ public abstract class CollectionCodec extends
AbstractJsonCodec {
public static final class ObjectCollectionCodec extends CollectionCodec {
private final JsonTypeInfo elementTypeInfo;
- private final BaseObjectCodec elementCodec;
+ private BaseObjectCodec elementCodec;
private ObjectCollectionCodec(
TypeRef<?> typeRef,
CollectionFactory factory,
JsonTypeInfo elementTypeInfo,
- BaseObjectCodec elementCodec) {
+ BaseObjectCodec elementCodec,
+ JsonTypeResolver resolver) {
super(typeRef, factory);
this.elementTypeInfo = elementTypeInfo;
this.elementCodec = elementCodec;
+ resolver.registerJITNotifyCallback(
+ elementCodec, codec -> this.elementCodec = (BaseObjectCodec) codec);
}
@Override
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/codec/GeneratedObjectCodec.java
b/java/fory-json/src/main/java/org/apache/fory/json/codec/GeneratedObjectCodec.java
index fa4dc59b3..dd5c1a984 100644
---
a/java/fory-json/src/main/java/org/apache/fory/json/codec/GeneratedObjectCodec.java
+++
b/java/fory-json/src/main/java/org/apache/fory/json/codec/GeneratedObjectCodec.java
@@ -42,14 +42,21 @@ public final class GeneratedObjectCodec extends
BaseObjectCodec {
private final Utf16ObjectReader utf16Reader;
private final Utf8ObjectReader utf8Reader;
- GeneratedObjectCodec(ObjectCodec base, ObjectCodecs codecs) {
+ GeneratedObjectCodec(
+ ObjectCodec base,
+ StringObjectWriter stringWriter,
+ Utf8ObjectWriter utf8Writer,
+ ObjectReader reader,
+ Latin1ObjectReader latin1Reader,
+ Utf16ObjectReader utf16Reader,
+ Utf8ObjectReader utf8Reader) {
super(base.type, base.writeFields, base.readFields, base.instantiator);
- stringWriter = codecs.stringWriter();
- utf8Writer = codecs.utf8Writer();
- reader = codecs.reader();
- latin1Reader = codecs.latin1Reader();
- utf16Reader = codecs.utf16Reader();
- utf8Reader = codecs.utf8Reader();
+ this.stringWriter = stringWriter;
+ this.utf8Writer = utf8Writer;
+ this.reader = reader;
+ this.latin1Reader = latin1Reader;
+ this.utf16Reader = utf16Reader;
+ this.utf8Reader = utf8Reader;
}
@Override
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/codec/MapCodec.java
b/java/fory-json/src/main/java/org/apache/fory/json/codec/MapCodec.java
index c7899db14..680fc12e0 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/codec/MapCodec.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/codec/MapCodec.java
@@ -231,7 +231,7 @@ public abstract class MapCodec extends AbstractJsonCodec {
public static final class GenericMapCodec extends MapCodec {
private final MapKeyCodec keyCodec;
private final JsonTypeInfo valueTypeInfo;
- private final JsonCodec valueCodec;
+ private JsonCodec valueCodec;
private GenericMapCodec(
TypeRef<?> typeRef,
@@ -244,6 +244,7 @@ public abstract class MapCodec extends AbstractJsonCodec {
this.keyCodec = keyCodec;
valueTypeInfo = resolver.getTypeInfo(valueType, valueRawType);
valueCodec = valueTypeInfo.codec();
+ resolver.registerJITNotifyCallback(valueCodec, codec -> valueCodec =
codec);
}
@Override
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodec.java
b/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodec.java
index 225bb2929..7fcae9be5 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodec.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodec.java
@@ -20,6 +20,12 @@
package org.apache.fory.json.codec;
import org.apache.fory.json.meta.JsonFieldInfo;
+import org.apache.fory.json.reader.Latin1ObjectReader;
+import org.apache.fory.json.reader.ObjectReader;
+import org.apache.fory.json.reader.Utf16ObjectReader;
+import org.apache.fory.json.reader.Utf8ObjectReader;
+import org.apache.fory.json.writer.StringObjectWriter;
+import org.apache.fory.json.writer.Utf8ObjectWriter;
import org.apache.fory.reflect.ObjectInstantiator;
public final class ObjectCodec extends BaseObjectCodec {
@@ -31,7 +37,14 @@ public final class ObjectCodec extends BaseObjectCodec {
super(type, writeFields, readFields, instantiator);
}
- public GeneratedObjectCodec withCodecs(ObjectCodecs codecs) {
- return new GeneratedObjectCodec(this, codecs);
+ public GeneratedObjectCodec withGenerated(
+ StringObjectWriter stringWriter,
+ Utf8ObjectWriter utf8Writer,
+ ObjectReader reader,
+ Latin1ObjectReader latin1Reader,
+ Utf16ObjectReader utf16Reader,
+ Utf8ObjectReader utf8Reader) {
+ return new GeneratedObjectCodec(
+ this, stringWriter, utf8Writer, reader, latin1Reader, utf16Reader,
utf8Reader);
}
}
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodecs.java
b/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodecs.java
deleted file mode 100644
index c18719f4f..000000000
--- a/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodecs.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.fory.json.codec;
-
-import org.apache.fory.json.reader.Latin1ObjectReader;
-import org.apache.fory.json.reader.ObjectReader;
-import org.apache.fory.json.reader.Utf16ObjectReader;
-import org.apache.fory.json.reader.Utf8ObjectReader;
-import org.apache.fory.json.writer.StringObjectWriter;
-import org.apache.fory.json.writer.Utf8ObjectWriter;
-
-public final class ObjectCodecs {
- private final StringObjectWriter stringWriter;
- private final Utf8ObjectWriter utf8Writer;
- private final ObjectReader reader;
- private final Latin1ObjectReader latin1Reader;
- private final Utf16ObjectReader utf16Reader;
- private final Utf8ObjectReader utf8Reader;
-
- public ObjectCodecs(
- StringObjectWriter stringWriter,
- Utf8ObjectWriter utf8Writer,
- ObjectReader reader,
- Latin1ObjectReader latin1Reader,
- Utf16ObjectReader utf16Reader,
- Utf8ObjectReader utf8Reader) {
- this.stringWriter = stringWriter;
- this.utf8Writer = utf8Writer;
- this.reader = reader;
- this.latin1Reader = latin1Reader;
- this.utf16Reader = utf16Reader;
- this.utf8Reader = utf8Reader;
- }
-
- public StringObjectWriter stringWriter() {
- return stringWriter;
- }
-
- public Utf8ObjectWriter utf8Writer() {
- return utf8Writer;
- }
-
- public ObjectReader reader() {
- return reader;
- }
-
- public Latin1ObjectReader latin1Reader() {
- return latin1Reader;
- }
-
- public Utf16ObjectReader utf16Reader() {
- return utf16Reader;
- }
-
- public Utf8ObjectReader utf8Reader() {
- return utf8Reader;
- }
-}
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/codec/ScalarCodecs.java
b/java/fory-json/src/main/java/org/apache/fory/json/codec/ScalarCodecs.java
index 389e538db..e5d51c497 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/codec/ScalarCodecs.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/codec/ScalarCodecs.java
@@ -2054,12 +2054,13 @@ public final class ScalarCodecs {
public static final class AtomicReferenceCodec extends AbstractJsonCodec {
private final JsonTypeInfo valueTypeInfo;
- private final JsonCodec valueCodec;
+ private JsonCodec valueCodec;
public AtomicReferenceCodec(java.lang.reflect.Type valueType,
JsonTypeResolver resolver) {
Class<?> valueRawType = CodecUtils.rawType(valueType, Object.class);
valueTypeInfo = resolver.getTypeInfo(valueType, valueRawType);
valueCodec = valueTypeInfo.codec();
+ resolver.registerJITNotifyCallback(valueCodec, codec -> valueCodec =
codec);
}
@Override
@@ -2189,12 +2190,13 @@ public final class ScalarCodecs {
public static final class AtomicReferenceArrayCodec extends
AbstractJsonCodec {
private final JsonTypeInfo valueTypeInfo;
- private final JsonCodec valueCodec;
+ private JsonCodec valueCodec;
public AtomicReferenceArrayCodec(java.lang.reflect.Type valueType,
JsonTypeResolver resolver) {
Class<?> valueRawType = CodecUtils.rawType(valueType, Object.class);
valueTypeInfo = resolver.getTypeInfo(valueType, valueRawType);
valueCodec = valueTypeInfo.codec();
+ resolver.registerJITNotifyCallback(valueCodec, codec -> valueCodec =
codec);
}
@Override
@@ -2243,12 +2245,13 @@ public final class ScalarCodecs {
public static final class OptionalCodec extends AbstractJsonCodec {
private final JsonTypeInfo valueTypeInfo;
- private final JsonCodec valueCodec;
+ private JsonCodec valueCodec;
public OptionalCodec(java.lang.reflect.Type valueType, JsonTypeResolver
resolver) {
Class<?> valueRawType = CodecUtils.rawType(valueType, Object.class);
valueTypeInfo = resolver.getTypeInfo(valueType, valueRawType);
valueCodec = valueTypeInfo.codec();
+ resolver.registerJITNotifyCallback(valueCodec, codec -> valueCodec =
codec);
}
@Override
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonCodegen.java
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonCodegen.java
index 5480f5cdf..28a72a1ca 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonCodegen.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonCodegen.java
@@ -28,12 +28,14 @@ import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import org.apache.fory.annotation.Internal;
import org.apache.fory.codegen.CodeGenerator;
import org.apache.fory.codegen.CompileUnit;
import org.apache.fory.json.ForyJsonException;
import org.apache.fory.json.codec.BaseObjectCodec;
+import org.apache.fory.json.codec.GeneratedObjectCodec;
import org.apache.fory.json.codec.JsonCodec;
-import org.apache.fory.json.codec.ObjectCodecs;
+import org.apache.fory.json.codec.ObjectCodec;
import org.apache.fory.json.meta.JsonFieldInfo;
import org.apache.fory.json.meta.JsonFieldKind;
import org.apache.fory.json.reader.Latin1ObjectReader;
@@ -45,6 +47,7 @@ import org.apache.fory.json.writer.StringObjectWriter;
import org.apache.fory.json.writer.Utf8ObjectWriter;
import org.apache.fory.platform.AndroidSupport;
import org.apache.fory.platform.internal._JDKAccess;
+import org.apache.fory.reflect.ReflectionUtils;
import org.apache.fory.util.record.RecordUtils;
public final class JsonCodegen {
@@ -56,77 +59,54 @@ public final class JsonCodegen {
private static final Map<String, Map<String, Integer>> ID_GENERATOR = new
ConcurrentHashMap<>();
final boolean writeNullFields;
- private final int configHash;
+ private final int codegenHash;
private final CodeGenerator codeGenerator;
private final ClassLoader jsonLoader;
+ private final ConcurrentHashMap<Class<?>, GeneratedObjectCodecClasses>
generatedClasses;
- public JsonCodegen(boolean writeNullFields, int configHash) {
+ public JsonCodegen(boolean writeNullFields, int codegenHash) {
this.writeNullFields = writeNullFields;
- this.configHash = configHash;
+ this.codegenHash = codegenHash;
jsonLoader = JsonCodegen.class.getClassLoader();
codeGenerator = new CodeGenerator(jsonLoader);
+ generatedClasses = new ConcurrentHashMap<>();
}
- public ObjectCodecs compile(BaseObjectCodec objectCodec, JsonTypeResolver
typeResolver) {
+ public GeneratedObjectCodecClasses generatedClasses(Class<?> type) {
+ return generatedClasses.get(type);
+ }
+
+ public GeneratedObjectCodecClasses compileClasses(ObjectCodec objectCodec) {
Class<?> type = objectCodec.type();
- if (!canCompile(type)) {
+ if (!canCompile(objectCodec)) {
return null;
}
- boolean record = objectCodec.isRecord();
- JsonFieldInfo[] writeProperties = objectCodec.writeFields();
- for (int i = 0; i < writeProperties.length; i++) {
- if (!canCompileWrite(writeProperties[i])) {
- return null;
- }
- }
- JsonFieldInfo[] readProperties = objectCodec.readFields();
- for (int i = 0; i < readProperties.length; i++) {
- if (!canCompileRead(readProperties[i], record)) {
- return null;
- }
+ GeneratedObjectCodecClasses classes = generatedClasses.get(type);
+ if (classes != null) {
+ return classes;
}
- String generatedPackage = CodeGenerator.getPackage(type);
- JsonCodec[] writeCodecs = writeCodecs(writeProperties);
- Utf8ObjectWriter utf8Writer =
- (Utf8ObjectWriter)
- compileWriter(
- generatedPackage,
- className(type, "Utf8"),
- type,
- writeProperties,
- writeCodecs,
- true);
- if (utf8Writer == null) {
- return null;
- }
- StringObjectWriter stringWriter =
- (StringObjectWriter)
- compileWriter(
- generatedPackage,
- className(type, "String"),
- type,
- writeProperties,
- writeCodecs,
- false);
- if (stringWriter == null) {
+ return generatedClasses.computeIfAbsent(type, ignored ->
buildClasses(objectCodec));
+ }
+
+ public GeneratedObjectCodec newCodec(
+ ObjectCodec objectCodec, JsonTypeResolver typeResolver,
GeneratedObjectCodecClasses classes) {
+ if (classes == null) {
return null;
}
+ Class<?> type = objectCodec.type();
+ JsonFieldInfo[] writeProperties = objectCodec.writeFields();
+ JsonCodec[] writeCodecs = writeCodecs(writeProperties);
+ Utf8ObjectWriter utf8Writer = classes.newUtf8Writer(writeProperties,
writeCodecs);
+ StringObjectWriter stringWriter = classes.newStringWriter(writeProperties,
writeCodecs);
+ JsonFieldInfo[] readProperties = objectCodec.readFields();
JsonCodec[] readCodecs = readCodecs(readProperties);
BaseObjectCodec[] readObjectCodecs = readObjectCodecs(objectCodec,
typeResolver);
- ObjectReader reader =
- (ObjectReader)
- compileReader(
- generatedPackage,
- className(type, "Reader"),
- type,
- readProperties,
- readCodecs,
- readObjectCodecs,
- record);
- if (reader == null) {
- return null;
- }
- return new ObjectCodecs(
+ ObjectReader reader = classes.newReader(readProperties, readCodecs,
readObjectCodecs);
+ registerWriterCallbacks(typeResolver, stringWriter, writeProperties,
writeCodecs);
+ registerWriterCallbacks(typeResolver, utf8Writer, writeProperties,
writeCodecs);
+ registerReaderCallbacks(
+ typeResolver, reader, type, readProperties, readCodecs,
readObjectCodecs);
+ return objectCodec.withGenerated(
stringWriter,
utf8Writer,
reader,
@@ -135,67 +115,73 @@ public final class JsonCodegen {
(Utf8ObjectReader) reader);
}
- private Object compileWriter(
+ private GeneratedObjectCodecClasses buildClasses(ObjectCodec objectCodec) {
+ Class<?> type = objectCodec.type();
+ boolean record = objectCodec.isRecord();
+ JsonFieldInfo[] writeProperties = objectCodec.writeFields();
+ JsonFieldInfo[] readProperties = objectCodec.readFields();
+ String generatedPackage = CodeGenerator.getPackage(type);
+ Class<?> utf8WriterClass =
+ compileWriterClass(generatedPackage, className(type, "Utf8"), type,
writeProperties, true);
+ Class<?> stringWriterClass =
+ compileWriterClass(
+ generatedPackage, className(type, "String"), type,
writeProperties, false);
+ Class<?> readerClass =
+ compileReaderClass(
+ generatedPackage, className(type, "Reader"), type, readProperties,
record);
+ return new GeneratedObjectCodecClasses(stringWriterClass, utf8WriterClass,
readerClass);
+ }
+
+ public boolean canCompile(BaseObjectCodec objectCodec) {
+ Class<?> type = objectCodec.type();
+ if (!canCompileType(type)) {
+ return false;
+ }
+ boolean record = objectCodec.isRecord();
+ JsonFieldInfo[] writeProperties = objectCodec.writeFields();
+ for (int i = 0; i < writeProperties.length; i++) {
+ if (!canCompileWrite(writeProperties[i])) {
+ return false;
+ }
+ }
+ JsonFieldInfo[] readProperties = objectCodec.readFields();
+ for (int i = 0; i < readProperties.length; i++) {
+ if (!canCompileRead(readProperties[i], record)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private Class<?> compileWriterClass(
String generatedPackage,
String className,
Class<?> type,
JsonFieldInfo[] properties,
- JsonCodec[] nestedCodecs,
boolean utf8) {
String code =
new JsonGeneratedCodecBuilder(
this, generatedPackage, className, type, properties, utf8,
true, false)
.genCode();
try {
- Class<?> writerClass = compileClass(generatedPackage, className, code);
- if (AndroidSupport.IS_ANDROID) {
- Constructor<?> constructor =
- writerClass.getDeclaredConstructor(JsonFieldInfo[].class,
JsonCodec[].class);
- constructor.setAccessible(true);
- return constructor.newInstance(properties, nestedCodecs);
- }
- MethodHandle constructor =
- _JDKAccess._trustedLookup(writerClass)
- .findConstructor(
- writerClass,
- MethodType.methodType(void.class, JsonFieldInfo[].class,
JsonCodec[].class));
- return constructor.invoke(properties, nestedCodecs);
+ return compileClass(generatedPackage, className, code);
} catch (Throwable e) {
throw new ForyJsonException("Cannot compile generated JSON writer " +
className, e);
}
}
- private Object compileReader(
+ private Class<?> compileReaderClass(
String generatedPackage,
String className,
Class<?> type,
JsonFieldInfo[] properties,
- JsonCodec[] readCodecs,
- BaseObjectCodec[] nestedCodecs,
boolean record) {
String code =
new JsonGeneratedCodecBuilder(
this, generatedPackage, className, type, properties, false,
false, record)
.genCode();
try {
- Class<?> readerClass = compileClass(generatedPackage, className, code);
- if (AndroidSupport.IS_ANDROID) {
- Constructor<?> constructor =
- readerClass.getDeclaredConstructor(
- JsonFieldInfo[].class, JsonCodec[].class,
BaseObjectCodec[].class);
- constructor.setAccessible(true);
- return constructor.newInstance(properties, readCodecs, nestedCodecs);
- }
- MethodHandle constructor =
- _JDKAccess._trustedLookup(readerClass)
- .findConstructor(
- readerClass,
- MethodType.methodType(
- void.class,
- JsonFieldInfo[].class,
- JsonCodec[].class,
- BaseObjectCodec[].class));
- return constructor.invoke(properties, readCodecs, nestedCodecs);
+ return compileClass(generatedPackage, className, code);
} catch (Throwable e) {
throw new ForyJsonException("Cannot compile generated JSON reader " +
className, e);
}
@@ -287,7 +273,7 @@ public final class JsonCodegen {
return !isPojo(elementType) || isVisible(elementType);
}
- private boolean canCompile(Class<?> type) {
+ private boolean canCompileType(Class<?> type) {
return CodeGenerator.sourcePublicAccessible(type) && isVisible(type);
}
@@ -315,6 +301,9 @@ public final class JsonCodegen {
}
Class<?> codecFieldType(JsonCodec codec) {
+ if (codec instanceof BaseObjectCodec) {
+ return BaseObjectCodec.class;
+ }
Class<?> type = codec.getClass();
if (isPublicSourceType(type) && isVisible(type)) {
return type;
@@ -322,6 +311,43 @@ public final class JsonCodegen {
return JsonCodec.class;
}
+ private static void registerWriterCallbacks(
+ JsonTypeResolver resolver, Object writer, JsonFieldInfo[] properties,
JsonCodec[] codecs) {
+ for (int i = 0; i < properties.length; i++) {
+ JsonFieldInfo property = properties[i];
+ JsonCodec codec = codecs[i];
+ if (usesWriteCodec(property) && codec instanceof BaseObjectCodec) {
+ registerFieldCallback(resolver, writer, "c" + i, codec);
+ }
+ }
+ }
+
+ private static void registerReaderCallbacks(
+ JsonTypeResolver resolver,
+ Object reader,
+ Class<?> type,
+ JsonFieldInfo[] properties,
+ JsonCodec[] codecs,
+ BaseObjectCodec[] objectCodecs) {
+ for (int i = 0; i < properties.length; i++) {
+ JsonFieldInfo property = properties[i];
+ JsonCodec codec = codecs[i];
+ if (usesReadCodec(property) && codec instanceof BaseObjectCodec) {
+ registerFieldCallback(resolver, reader, "r" + i, codec);
+ }
+ if (storesReadObjectCodec(type, property)) {
+ registerFieldCallback(resolver, reader, "c" + i, objectCodecs[i]);
+ }
+ }
+ }
+
+ private static void registerFieldCallback(
+ JsonTypeResolver resolver, Object owner, String fieldName, JsonCodec
currentCodec) {
+ Field field = ReflectionUtils.getField(owner.getClass(), fieldName);
+ resolver.registerJITNotifyCallback(
+ currentCodec, codec -> ReflectionUtils.setObjectFieldValue(owner,
field, codec));
+ }
+
private static boolean isPublicSourceType(Class<?> type) {
if (!CodeGenerator.sourcePublicAccessible(type)) {
return false;
@@ -389,7 +415,7 @@ public final class JsonCodegen {
String name = simpleClassName(type) + role + "ForyJsonCodec";
Map<String, Integer> subGenerator =
ID_GENERATOR.computeIfAbsent(name, key -> new ConcurrentHashMap<>());
- String key = configHash + "_" + CodeGenerator.getClassUniqueId(type);
+ String key = codegenHash + "_" + CodeGenerator.getClassUniqueId(type);
Integer id = subGenerator.get(key);
if (id == null) {
synchronized (subGenerator) {
@@ -443,4 +469,110 @@ public final class JsonCodegen {
Field field = property.writeField();
return field != null && RecordUtils.isRecord(field.getDeclaringClass());
}
+
+ @Internal
+ public static final class GeneratedObjectCodecClasses {
+ private final MethodHandle stringWriterConstructor;
+ private final MethodHandle utf8WriterConstructor;
+ private final MethodHandle readerConstructor;
+ private final Constructor<?> androidStringWriterConstructor;
+ private final Constructor<?> androidUtf8WriterConstructor;
+ private final Constructor<?> androidReaderConstructor;
+
+ private GeneratedObjectCodecClasses(
+ Class<?> stringWriterClass, Class<?> utf8WriterClass, Class<?>
readerClass) {
+ try {
+ if (AndroidSupport.IS_ANDROID) {
+ androidStringWriterConstructor =
writerConstructor(stringWriterClass);
+ androidUtf8WriterConstructor = writerConstructor(utf8WriterClass);
+ androidReaderConstructor = readerConstructor(readerClass);
+ stringWriterConstructor = null;
+ utf8WriterConstructor = null;
+ readerConstructor = null;
+ } else {
+ stringWriterConstructor = writerHandle(stringWriterClass);
+ utf8WriterConstructor = writerHandle(utf8WriterClass);
+ readerConstructor = readerHandle(readerClass);
+ androidStringWriterConstructor = null;
+ androidUtf8WriterConstructor = null;
+ androidReaderConstructor = null;
+ }
+ } catch (Throwable e) {
+ throw new ForyJsonException(
+ "Cannot resolve generated JSON codec constructors for " +
readerClass.getName(), e);
+ }
+ }
+
+ private StringObjectWriter newStringWriter(JsonFieldInfo[] properties,
JsonCodec[] codecs) {
+ return (StringObjectWriter)
+ newWriter(androidStringWriterConstructor, stringWriterConstructor,
properties, codecs);
+ }
+
+ private Utf8ObjectWriter newUtf8Writer(JsonFieldInfo[] properties,
JsonCodec[] codecs) {
+ return (Utf8ObjectWriter)
+ newWriter(androidUtf8WriterConstructor, utf8WriterConstructor,
properties, codecs);
+ }
+
+ private ObjectReader newReader(
+ JsonFieldInfo[] properties, JsonCodec[] codecs, BaseObjectCodec[]
objectCodecs) {
+ try {
+ if (AndroidSupport.IS_ANDROID) {
+ return (ObjectReader)
+ androidReaderConstructor.newInstance(properties, codecs,
objectCodecs);
+ }
+ return (ObjectReader) readerConstructor.invoke(properties, codecs,
objectCodecs);
+ } catch (Throwable e) {
+ throw new ForyJsonException("Cannot instantiate generated JSON
reader", e);
+ }
+ }
+
+ private static Object newWriter(
+ Constructor<?> androidConstructor,
+ MethodHandle constructor,
+ JsonFieldInfo[] properties,
+ JsonCodec[] codecs) {
+ try {
+ if (AndroidSupport.IS_ANDROID) {
+ return androidConstructor.newInstance(properties, codecs);
+ }
+ return constructor.invoke(properties, codecs);
+ } catch (Throwable e) {
+ throw new ForyJsonException("Cannot instantiate generated JSON
writer", e);
+ }
+ }
+
+ private static Constructor<?> writerConstructor(Class<?> writerClass)
+ throws NoSuchMethodException {
+ Constructor<?> constructor =
+ writerClass.getDeclaredConstructor(JsonFieldInfo[].class,
JsonCodec[].class);
+ constructor.setAccessible(true);
+ return constructor;
+ }
+
+ private static Constructor<?> readerConstructor(Class<?> readerClass)
+ throws NoSuchMethodException {
+ Constructor<?> constructor =
+ readerClass.getDeclaredConstructor(
+ JsonFieldInfo[].class, JsonCodec[].class,
BaseObjectCodec[].class);
+ constructor.setAccessible(true);
+ return constructor;
+ }
+
+ private static MethodHandle writerHandle(Class<?> writerClass)
+ throws NoSuchMethodException, IllegalAccessException {
+ return _JDKAccess._trustedLookup(writerClass)
+ .findConstructor(
+ writerClass,
+ MethodType.methodType(void.class, JsonFieldInfo[].class,
JsonCodec[].class));
+ }
+
+ private static MethodHandle readerHandle(Class<?> readerClass)
+ throws NoSuchMethodException, IllegalAccessException {
+ return _JDKAccess._trustedLookup(readerClass)
+ .findConstructor(
+ readerClass,
+ MethodType.methodType(
+ void.class, JsonFieldInfo[].class, JsonCodec[].class,
BaseObjectCodec[].class));
+ }
+ }
}
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonJITContext.java
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonJITContext.java
new file mode 100644
index 000000000..f651183ab
--- /dev/null
+++
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonJITContext.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.json.codegen;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
+import org.apache.fory.annotation.Internal;
+import org.apache.fory.codegen.CodeGenerator;
+import org.apache.fory.json.resolver.JsonSharedRegistry;
+import org.apache.fory.util.ExceptionUtils;
+import org.apache.fory.util.Preconditions;
+
+/** Coordinates asynchronous JSON object-codec compilation and
generated-result publication. */
+public final class JsonJITContext {
+ private final boolean asyncCompilationEnabled;
+ private final ReentrantLock jitLock;
+ private int jsonVisitState;
+ private final Map<Object, List<NotifyCallback>> hasJITResult;
+
+ public JsonJITContext(boolean asyncCompilationEnabled) {
+ this.asyncCompilationEnabled = asyncCompilationEnabled;
+ jitLock = new ReentrantLock(true);
+ hasJITResult = new HashMap<>();
+ }
+
+ public boolean asyncCompilationEnabled() {
+ return asyncCompilationEnabled;
+ }
+
+ @Internal
+ public <T> T registerObjectJITCallback(
+ Callable<T> interpreterModeAction, Callable<T> jitAction,
ObjectJITCallback<T> callback) {
+ try {
+ lock();
+ Object id = callback.id();
+ if (asyncCompilationEnabled && !isAsyncVisitingJson()) {
+ List<NotifyCallback> callbacks = hasJITResult.get(id);
+ if (callbacks != null) {
+ callbacks.add(
+ new NotifyCallback() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public void onNotifyResult(Object result) {
+ callback.onSuccess((T) result);
+ }
+
+ @Override
+ public void onNotifyMissed() {}
+ });
+ return interpreterModeAction.call();
+ }
+ hasJITResult.put(id, new ArrayList<>());
+ ExecutorService compilationService =
CodeGenerator.getCompilationService();
+ compilationService.execute(
+ () -> {
+ try {
+ T result = jitAction.call();
+ try {
+ lock();
+ // Keep this entry visible while the owner callback installs
generated codecs.
+ // Recursive codec construction can subscribe nested fields
for the same type.
+ List<NotifyCallback> notifyCallbacks = hasJITResult.get(id);
+ callback.onSuccess(result);
+ if (notifyCallbacks != null) {
+ for (int i = 0; i < notifyCallbacks.size(); i++) {
+ notifyCallbacks.get(i).onNotifyResult(result);
+ }
+ }
+ hasJITResult.remove(id);
+ } finally {
+ unlock();
+ }
+ } catch (Throwable t) {
+ try {
+ lock();
+ hasJITResult.remove(id);
+ callback.onFailure(t);
+ } finally {
+ unlock();
+ }
+ }
+ });
+ return interpreterModeAction.call();
+ }
+ return jitAction.call();
+ } catch (Exception e) {
+ ExceptionUtils.throwException(e);
+ throw new IllegalStateException("unreachable");
+ } finally {
+ unlock();
+ }
+ }
+
+ public void registerJITNotifyCallback(Object id, NotifyCallback
notifyCallback) {
+ Preconditions.checkNotNull(id);
+ try {
+ lock();
+ List<NotifyCallback> notifyCallbacks = hasJITResult.get(id);
+ if (notifyCallbacks == null) {
+ notifyCallback.onNotifyMissed();
+ } else {
+ notifyCallbacks.add(notifyCallback);
+ }
+ } finally {
+ unlock();
+ }
+ }
+
+ @Internal
+ public <T> T asyncVisitJson(
+ JsonSharedRegistry sharedRegistry, Function<JsonSharedRegistry, T>
function) {
+ try {
+ lock();
+ jsonVisitState++;
+ return function.apply(sharedRegistry);
+ } finally {
+ jsonVisitState--;
+ unlock();
+ }
+ }
+
+ private boolean isAsyncVisitingJson() {
+ if (asyncCompilationEnabled) {
+ try {
+ lock();
+ return jsonVisitState != 0;
+ } finally {
+ unlock();
+ }
+ }
+ return false;
+ }
+
+ public boolean hasJITResult(Object key) {
+ try {
+ lock();
+ return hasJITResult.get(key) != null;
+ } finally {
+ unlock();
+ }
+ }
+
+ @Internal
+ public void lock() {
+ if (asyncCompilationEnabled) {
+ jitLock.lock();
+ }
+ }
+
+ @Internal
+ public boolean lockedByCurrentThread() {
+ return !asyncCompilationEnabled || jitLock.isHeldByCurrentThread();
+ }
+
+ @Internal
+ public void unlock() {
+ if (asyncCompilationEnabled) {
+ jitLock.unlock();
+ }
+ }
+
+ @Internal
+ public interface ObjectJITCallback<T> {
+ void onSuccess(T result);
+
+ default void onFailure(Throwable e) {
+ e.printStackTrace();
+ ExceptionUtils.throwException(e);
+ }
+
+ Object id();
+ }
+
+ @Internal
+ public interface NotifyCallback {
+ default void onNotifyResult(Object result) {
+ onNotifyMissed();
+ }
+
+ void onNotifyMissed();
+ }
+}
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java
index 282cbde9d..40556e962 100644
---
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java
+++
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java
@@ -78,10 +78,12 @@ import org.apache.fory.json.codec.CollectionCodec;
import org.apache.fory.json.codec.GuavaCodecs;
import org.apache.fory.json.codec.JsonCodec;
import org.apache.fory.json.codec.MapCodec;
-import org.apache.fory.json.codec.ObjectCodecs;
+import org.apache.fory.json.codec.ObjectCodec;
import org.apache.fory.json.codec.ScalarCodecs;
import org.apache.fory.json.codec.SqlJsonCodecs;
import org.apache.fory.json.codegen.JsonCodegen;
+import org.apache.fory.json.codegen.JsonCodegen.GeneratedObjectCodecClasses;
+import org.apache.fory.json.codegen.JsonJITContext;
import org.apache.fory.json.meta.JsonFieldKind;
import org.apache.fory.reflect.TypeRef;
import org.apache.fory.type.BFloat16;
@@ -92,16 +94,17 @@ public final class JsonSharedRegistry {
private final CodecRegistry customCodecs;
private final IdentityHashMap<Class<?>, JsonCodec> exactCodecs;
private final JsonCodegen codegen;
+ private final JsonJITContext jitContext;
private final boolean propertyDiscoveryEnabled;
public JsonSharedRegistry(JsonConfig config) {
this.customCodecs = config.codecRegistry().copy();
this.propertyDiscoveryEnabled = config.propertyDiscoveryEnabled();
exactCodecs = new IdentityHashMap<>();
+ boolean codegenEnabled = config.codegenEnabled();
+ jitContext = new JsonJITContext(codegenEnabled &&
config.asyncCompilationEnabled());
codegen =
- config.codegenEnabled()
- ? new JsonCodegen(config.writeNullFields(), config.getConfigHash())
- : null;
+ codegenEnabled ? new JsonCodegen(config.writeNullFields(),
config.getCodegenHash()) : null;
registerExactCodecs();
}
@@ -214,8 +217,47 @@ public final class JsonSharedRegistry {
return JsonFieldKind.OBJECT;
}
- public ObjectCodecs compileObject(BaseObjectCodec codec, JsonTypeResolver
localResolver) {
- return codegen == null ? null : codegen.compile(codec, localResolver);
+ public BaseObjectCodec compileObject(
+ ObjectCodec codec,
+ JsonTypeResolver localResolver,
+ JsonJITContext.ObjectJITCallback<GeneratedObjectCodecClasses> callback) {
+ if (codegen == null || !codegen.canCompile(codec)) {
+ return null;
+ }
+ GeneratedObjectCodecClasses classes =
codegen.generatedClasses(codec.type());
+ if (classes == null) {
+ classes =
+ jitContext.registerObjectJITCallback(
+ () -> null,
+ () -> jitContext.asyncVisitJson(this, ignored ->
codegen.compileClasses(codec)),
+ callback);
+ }
+ return classes == null ? null : codegen.newCodec(codec, localResolver,
classes);
+ }
+
+ BaseObjectCodec newGeneratedCodec(
+ ObjectCodec codec, JsonTypeResolver resolver,
GeneratedObjectCodecClasses classes) {
+ return codegen.newCodec(codec, resolver, classes);
+ }
+
+ GeneratedObjectCodecClasses generatedClasses(Class<?> type) {
+ return codegen == null ? null : codegen.generatedClasses(type);
+ }
+
+ JsonJITContext jitContext() {
+ return jitContext;
+ }
+
+ boolean hasJITResult(Object id) {
+ return jitContext.hasJITResult(id);
+ }
+
+ boolean asyncCompilationEnabled() {
+ return jitContext.asyncCompilationEnabled();
+ }
+
+ void registerJITNotifyCallback(Object id, JsonJITContext.NotifyCallback
callback) {
+ jitContext.registerJITNotifyCallback(id, callback);
}
boolean propertyDiscoveryEnabled() {
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeInfo.java
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeInfo.java
index dd61f6922..05dc24f21 100644
---
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeInfo.java
+++
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeInfo.java
@@ -24,13 +24,13 @@ import org.apache.fory.json.codec.JsonCodec;
import org.apache.fory.json.meta.JsonFieldKind;
import org.apache.fory.reflect.TypeRef;
-/** Immutable JSON type binding resolved and owned by {@link
JsonTypeResolver}. */
+/** JSON type binding resolved and owned by {@link JsonTypeResolver}. */
public final class JsonTypeInfo {
private final Type type;
private final TypeRef<?> typeRef;
private final Class<?> rawType;
private final JsonFieldKind kind;
- private final JsonCodec codec;
+ private JsonCodec codec;
private final boolean primitive;
JsonTypeInfo(
@@ -63,6 +63,10 @@ public final class JsonTypeInfo {
return codec;
}
+ void setCodec(JsonCodec codec) {
+ this.codec = codec;
+ }
+
public boolean primitive() {
return primitive;
}
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java
index 8b0e43e74..3b363fffc 100644
---
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java
+++
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java
@@ -20,14 +20,19 @@
package org.apache.fory.json.resolver;
import java.lang.reflect.Type;
+import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
import org.apache.fory.json.codec.BaseObjectCodec;
import org.apache.fory.json.codec.CodecUtils;
import org.apache.fory.json.codec.JsonCodec;
import org.apache.fory.json.codec.ObjectCodec;
-import org.apache.fory.json.codec.ObjectCodecs;
+import org.apache.fory.json.codegen.JsonCodegen.GeneratedObjectCodecClasses;
+import org.apache.fory.json.codegen.JsonJITContext;
import org.apache.fory.reflect.TypeRef;
/**
@@ -37,9 +42,10 @@ import org.apache.fory.reflect.TypeRef;
* Runtime JSON values, including non-enumerated string or number
values/tokens, must stay uncached.
*/
public final class JsonTypeResolver {
- private final IdentityHashMap<Class<?>, BaseObjectCodec> objectCodecs = new
IdentityHashMap<>();
- private final Map<Object, JsonTypeInfo> typeInfos = new HashMap<>();
+ private final Map<Class<?>, BaseObjectCodec> objectCodecs;
+ private final Map<Object, JsonTypeInfo> typeInfos;
private final JsonSharedRegistry sharedRegistry;
+ private final Set<Class<?>> installingCodecs;
private enum RuntimeObjectKey {
INSTANCE
@@ -47,14 +53,31 @@ public final class JsonTypeResolver {
public JsonTypeResolver(JsonSharedRegistry sharedRegistry) {
this.sharedRegistry = sharedRegistry;
+ if (sharedRegistry.asyncCompilationEnabled()) {
+ objectCodecs = new ConcurrentHashMap<>();
+ typeInfos = new ConcurrentHashMap<>();
+ } else {
+ objectCodecs = new IdentityHashMap<>();
+ typeInfos = new HashMap<>();
+ }
+ installingCodecs = Collections.newSetFromMap(new
ConcurrentHashMap<Class<?>, Boolean>());
}
public BaseObjectCodec getObjectCodec(Class<?> type) {
BaseObjectCodec codec = objectCodecs.get(type);
if (codec != null) {
- return codec;
+ return generatedCodecIfReady(type, codec);
+ }
+ try {
+ sharedRegistry.jitContext().lock();
+ codec = objectCodecs.get(type);
+ if (codec != null) {
+ return generatedCodecIfReady(type, codec);
+ }
+ return buildObjectCodec(type);
+ } finally {
+ sharedRegistry.jitContext().unlock();
}
- return buildObjectCodec(type);
}
public JsonTypeInfo getTypeInfo(Type declaredType, Class<?> fallback) {
@@ -64,7 +87,16 @@ public final class JsonTypeResolver {
if (typeInfo != null) {
return typeInfo;
}
- return buildTypeInfo(key, rawType, declaredType);
+ try {
+ sharedRegistry.jitContext().lock();
+ typeInfo = typeInfos.get(key);
+ if (typeInfo != null) {
+ return typeInfo;
+ }
+ return buildTypeInfo(key, rawType, declaredType);
+ } finally {
+ sharedRegistry.jitContext().unlock();
+ }
}
public JsonTypeInfo getRuntimeTypeInfo(Class<?> runtimeType) {
@@ -73,7 +105,16 @@ public final class JsonTypeResolver {
if (typeInfo != null) {
return typeInfo;
}
- return buildRuntimeTypeInfo(key, runtimeType);
+ try {
+ sharedRegistry.jitContext().lock();
+ typeInfo = typeInfos.get(key);
+ if (typeInfo != null) {
+ return typeInfo;
+ }
+ return buildRuntimeTypeInfo(key, runtimeType);
+ } finally {
+ sharedRegistry.jitContext().unlock();
+ }
}
private BaseObjectCodec buildObjectCodec(Class<?> type) {
@@ -87,11 +128,27 @@ public final class JsonTypeResolver {
objectCodecs.put(type, codec);
try {
codec.resolveTypes(this);
- ObjectCodecs codecs = sharedRegistry.compileObject(codec, this);
- if (codecs != null) {
- BaseObjectCodec generated = codec.withCodecs(codecs);
- objectCodecs.put(type, generated);
- return generated;
+ BaseObjectCodec compiled =
+ sharedRegistry.compileObject(
+ codec,
+ this,
+ new
JsonJITContext.ObjectJITCallback<GeneratedObjectCodecClasses>() {
+ @Override
+ public void onSuccess(GeneratedObjectCodecClasses result) {
+ BaseObjectCodec generated = newGeneratedCodec(codec, result);
+ if (generated != null) {
+ setObjectCodec(type, generated);
+ }
+ }
+
+ @Override
+ public Object id() {
+ return type;
+ }
+ });
+ if (compiled != null && compiled != codec) {
+ setObjectCodec(type, compiled);
+ return compiled;
}
return codec;
} catch (RuntimeException | Error e) {
@@ -116,7 +173,10 @@ public final class JsonTypeResolver {
if (codec == null) {
codec = getObjectCodec(rawType);
}
- return new JsonTypeInfo(declaredType, typeRef, rawType,
sharedRegistry.kind(rawType), codec);
+ JsonTypeInfo typeInfo =
+ new JsonTypeInfo(declaredType, typeRef, rawType,
sharedRegistry.kind(rawType), codec);
+ registerCodecUpdate(typeInfo, codec);
+ return typeInfo;
}
private JsonTypeInfo buildRuntimeTypeInfo(Object key, Class<?> rawType) {
@@ -134,10 +194,123 @@ public final class JsonTypeResolver {
}
JsonTypeInfo typeInfo =
new JsonTypeInfo(rawType, typeRef, rawType,
sharedRegistry.kind(rawType), codec);
+ registerCodecUpdate(typeInfo, codec);
typeInfos.put(key, typeInfo);
return typeInfo;
}
+ public void registerJITNotifyCallback(JsonCodec currentCodec,
Consumer<JsonCodec> updater) {
+ if (!(currentCodec instanceof BaseObjectCodec)) {
+ return;
+ }
+ BaseObjectCodec objectCodec = (BaseObjectCodec) currentCodec;
+ Class<?> type = objectCodec.type();
+ if (!sharedRegistry.hasJITResult(type)) {
+ BaseObjectCodec latest = generatedCodecIfReady(type, objectCodec);
+ if (latest != objectCodec) {
+ updater.accept(latest);
+ }
+ return;
+ }
+ sharedRegistry.registerJITNotifyCallback(
+ type,
+ new JsonJITContext.NotifyCallback() {
+ @Override
+ public void onNotifyResult(Object result) {
+ BaseObjectCodec latest = generatedCodecIfReady(type, objectCodec);
+ if (latest != objectCodec) {
+ updater.accept(latest);
+ return;
+ }
+ if (result instanceof GeneratedObjectCodecClasses
+ && objectCodec instanceof ObjectCodec) {
+ BaseObjectCodec generated =
+ newGeneratedCodec(
+ (ObjectCodec) objectCodec, (GeneratedObjectCodecClasses)
result);
+ if (generated != null) {
+ setObjectCodec(type, generated);
+ updater.accept(generated);
+ return;
+ }
+ }
+ onNotifyMissed();
+ }
+
+ @Override
+ public void onNotifyMissed() {
+ BaseObjectCodec latest = getObjectCodec(type);
+ if (latest != objectCodec) {
+ updater.accept(latest);
+ }
+ }
+ });
+ }
+
+ private void registerCodecUpdate(JsonTypeInfo typeInfo, JsonCodec codec) {
+ registerJITNotifyCallback(codec, typeInfo::setCodec);
+ }
+
+ private void setObjectCodec(Class<?> type, BaseObjectCodec codec) {
+ objectCodecs.put(type, codec);
+ if (type == Object.class) {
+ JsonTypeInfo runtimeTypeInfo = typeInfos.get(RuntimeObjectKey.INSTANCE);
+ if (runtimeTypeInfo != null) {
+ runtimeTypeInfo.setCodec(codec);
+ }
+ return;
+ }
+ JsonTypeInfo typeInfo = typeInfos.get(type);
+ if (typeInfo != null) {
+ typeInfo.setCodec(codec);
+ }
+ }
+
+ private BaseObjectCodec newGeneratedCodec(
+ ObjectCodec codec, GeneratedObjectCodecClasses classes) {
+ Class<?> type = codec.type();
+ // Generated codec construction may request a recursive object codec. Keep
the current
+ // interpreter until the outer generated codec is installed and its
pending callbacks run.
+ if (!installingCodecs.add(type)) {
+ return codec;
+ }
+ try {
+ return sharedRegistry.newGeneratedCodec(codec, this, classes);
+ } finally {
+ installingCodecs.remove(type);
+ }
+ }
+
+ private BaseObjectCodec generatedCodecIfReady(Class<?> type, BaseObjectCodec
codec) {
+ if (!(codec instanceof ObjectCodec)) {
+ return codec;
+ }
+ if (installingCodecs.contains(type)) {
+ return codec;
+ }
+ GeneratedObjectCodecClasses classes =
sharedRegistry.generatedClasses(type);
+ if (classes == null) {
+ return codec;
+ }
+ if (!sharedRegistry.jitContext().lockedByCurrentThread()) {
+ try {
+ sharedRegistry.jitContext().lock();
+ return generatedCodecIfReady(type, codec);
+ } finally {
+ sharedRegistry.jitContext().unlock();
+ }
+ }
+ BaseObjectCodec latest = objectCodecs.get(type);
+ if (latest != null && latest != codec) {
+ return latest;
+ }
+ BaseObjectCodec generated = newGeneratedCodec((ObjectCodec) codec,
classes);
+ if (generated != null) {
+ setObjectCodec(type, generated);
+ return generated;
+ }
+ return codec;
+ }
+
private static Object typeInfoKey(Type declaredType, Class<?> rawType) {
if (declaredType instanceof Class) {
return rawType;
diff --git
a/java/fory-json/src/test/java/org/apache/fory/json/ForyJsonTestModels.java
b/java/fory-json/src/test/java/org/apache/fory/json/ForyJsonTestModels.java
index 390eca491..8f4304336 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/ForyJsonTestModels.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/ForyJsonTestModels.java
@@ -52,6 +52,7 @@ import org.apache.fory.json.data.TokenValues;
import org.apache.fory.json.data.UnicodeMatrix;
import org.apache.fory.reflect.FieldAccessor;
import org.testng.SkipException;
+import org.testng.annotations.DataProvider;
public abstract class ForyJsonTestModels {
protected static final String TWO_BYTE_TEXT = JsonTestData.TWO_BYTE_TEXT;
@@ -61,6 +62,28 @@ public abstract class ForyJsonTestModels {
protected static final String COMBINING_TEXT = JsonTestData.COMBINING_TEXT;
protected static final String ZH_TEXT = JsonTestData.ZH_TEXT;
protected static final String EU_TEXT = JsonTestData.EU_TEXT;
+ private final boolean codegen;
+
+ protected ForyJsonTestModels(boolean codegen) {
+ this.codegen = codegen;
+ }
+
+ @DataProvider(name = "codegen")
+ public static Object[][] codegen() {
+ return new Object[][] {{false}, {true}};
+ }
+
+ protected final ForyJsonBuilder newJsonBuilder() {
+ return ForyJson.builder().withCodegen(codegen).withAsyncCompilation(false);
+ }
+
+ protected final ForyJson newJson() {
+ return newJsonBuilder().build();
+ }
+
+ protected final boolean codegenEnabled() {
+ return codegen;
+ }
protected static TokenValues tokenValue(int count, String name, List<String>
tags, long total) {
TokenValues value = new TokenValues();
@@ -268,8 +291,8 @@ public abstract class ForyJsonTestModels {
json.fromJson(objectJson.getBytes(StandardCharsets.UTF_8),
PublicFields.class).name, text);
}
- protected static void assertGeneratedWhenSupported(ForyJson json, Class<?>
type) {
- assertTrue(json.hasGeneratedWriter(type));
+ protected final void assertGeneratedWhenSupported(ForyJson json, Class<?>
type) {
+ assertEquals(json.hasGeneratedWriter(type), codegen);
}
protected static String repeat(char ch, int length) {
diff --git
a/java/fory-json/src/test/java/org/apache/fory/json/JsonAsyncCompilationTest.java
b/java/fory-json/src/test/java/org/apache/fory/json/JsonAsyncCompilationTest.java
new file mode 100644
index 000000000..0221ab1de
--- /dev/null
+++
b/java/fory-json/src/test/java/org/apache/fory/json/JsonAsyncCompilationTest.java
@@ -0,0 +1,360 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.json;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.fory.json.codec.BaseObjectCodec;
+import org.apache.fory.json.codec.GeneratedObjectCodec;
+import org.apache.fory.json.codec.JsonCodec;
+import org.apache.fory.json.data.RecursiveParent;
+import org.apache.fory.json.resolver.JsonSharedRegistry;
+import org.apache.fory.json.resolver.JsonTypeResolver;
+import org.testng.annotations.Test;
+
+public class JsonAsyncCompilationTest {
+ @Test
+ public void defaultBuilderEnablesAsync() throws Exception {
+ assertTrue(asyncCompilationEnabled(ForyJson.builder().build()));
+
assertFalse(asyncCompilationEnabled(ForyJson.builder().withAsyncCompilation(false).build()));
+
assertFalse(asyncCompilationEnabled(ForyJson.builder().withCodegen(false).build()));
+ }
+
+ @Test
+ public void asyncCompilationPublishesGeneratedCodec() throws Exception {
+ ForyJson json = ForyJson.builder().build();
+ AsyncChild value = child("root", 1);
+ assertEquals(json.toJson(value), "{\"id\":1,\"name\":\"root\"}");
+ awaitGenerated(json, AsyncChild.class);
+ assertEquals(json.toJson(value), "{\"id\":1,\"name\":\"root\"}");
+ assertEquals(json.fromJson("{\"id\":2,\"name\":\"read\"}",
AsyncChild.class).name, "read");
+ }
+
+ @Test
+ public void asyncUpdatesCachedNestedCodecs() throws Exception {
+ ForyJson json = ForyJson.builder().build();
+ AsyncParent value = parent();
+ String expected =
+
"{\"array\":[{\"id\":2,\"name\":\"array\"}],\"atomic\":{\"id\":6,\"name\":\"atomic\"},"
+ +
"\"child\":{\"id\":1,\"name\":\"child\"},\"list\":[{\"id\":3,\"name\":\"list\"}],"
+ + "\"map\":{\"entry\":{\"id\":4,\"name\":\"map\"}},"
+ + "\"optional\":{\"id\":5,\"name\":\"optional\"}}";
+ assertEquals(json.toJson(value), expected);
+ awaitGenerated(json, AsyncParent.class);
+ awaitGenerated(json, AsyncChild.class);
+ assertEquals(json.toJson(value), expected);
+ AsyncParent read = json.fromJson(expected, AsyncParent.class);
+ assertEquals(read.child.name, "child");
+ assertEquals(read.array[0].name, "array");
+ assertEquals(read.list.get(0).name, "list");
+ assertEquals(read.map.get("entry").name, "map");
+ assertEquals(read.optional.get().name, "optional");
+ assertEquals(read.atomic.get().name, "atomic");
+ assertCachedGeneratedChild(json, AsyncParent.class, AsyncChild.class);
+ }
+
+ @Test
+ public void objectDeclaredCodecsKeepNaturalSemantics() throws Exception {
+ ForyJson json = ForyJson.builder().build();
+ ObjectHolder holder = new ObjectHolder();
+ holder.array = new Object[] {"array", 7, child("array-child", 8)};
+ holder.atomic = new AtomicReference<Object>("atomic");
+ holder.map = new LinkedHashMap<>();
+ holder.map.put("string", "value");
+ holder.map.put("number", 9);
+ holder.optional = Optional.of("optional");
+ String expected =
+ "{\"array\":[\"array\",7,{\"id\":8,\"name\":\"array-child\"}],"
+ +
"\"atomic\":\"atomic\",\"map\":{\"string\":\"value\",\"number\":9},"
+ + "\"optional\":\"optional\"}";
+ withJitLocked(
+ json,
+ () -> {
+ json.hasGeneratedWriter(Object.class);
+ assertEquals(json.toJson(holder), expected);
+ });
+ awaitGenerated(json, Object.class);
+ assertEquals(json.toJson(holder), expected);
+ }
+
+ @Test
+ public void objectClassJitKeepsNaturalObjectBinding() throws Exception {
+ ForyJson json = ForyJson.builder().build();
+ assertTrue(json.fromJson("[1]", Object.class) instanceof JSONArray);
+ JSONObject before =
+ (JSONObject) json.fromJson("{\"items\":[1],\"name\":\"fory\"}",
Object.class);
+ assertTrue(before.get("items") instanceof JSONArray);
+ json.hasGeneratedWriter(Object.class);
+ awaitGenerated(json, Object.class);
+ assertEquals(json.fromJson("7", Object.class), Long.valueOf(7));
+ assertTrue(json.fromJson("[1]", Object.class) instanceof JSONArray);
+ JSONObject after =
+ (JSONObject) json.fromJson("{\"items\":[1],\"name\":\"fory\"}",
Object.class);
+ assertTrue(after.get("items") instanceof JSONArray);
+ assertEquals(after.get("name"), "fory");
+ }
+
+ @Test
+ public void asyncCompilationKeepsResolverLocalCodecs() throws Exception {
+ ForyJson json = ForyJson.builder().build();
+ JsonSharedRegistry sharedRegistry = (JsonSharedRegistry) field(json,
"sharedRegistry");
+ JsonTypeResolver first = new JsonTypeResolver(sharedRegistry);
+ JsonTypeResolver second = new JsonTypeResolver(sharedRegistry);
+ withJitLocked(
+ json,
+ () -> {
+ assertFalse(first.getObjectCodec(AsyncChild.class) instanceof
GeneratedObjectCodec);
+ assertFalse(second.getObjectCodec(AsyncChild.class) instanceof
GeneratedObjectCodec);
+ assertEquals(pendingJitCount(json), 1);
+ });
+ BaseObjectCodec firstCodec = awaitGenerated(first, AsyncChild.class);
+ BaseObjectCodec secondCodec = awaitGenerated(second, AsyncChild.class);
+ assertTrue(firstCodec instanceof GeneratedObjectCodec,
firstCodec.getClass().getName());
+ assertTrue(secondCodec instanceof GeneratedObjectCodec,
secondCodec.getClass().getName());
+ assertTrue(firstCodec != secondCodec);
+ assertEquals(generatedUtf8WriterClass(firstCodec),
generatedUtf8WriterClass(secondCodec));
+ }
+
+ @Test
+ public void asyncReusesGeneratedClassesForNewResolver() throws Exception {
+ ForyJson json = ForyJson.builder().build();
+ JsonSharedRegistry sharedRegistry = (JsonSharedRegistry) field(json,
"sharedRegistry");
+ JsonTypeResolver first = new JsonTypeResolver(sharedRegistry);
+ assertFalse(first.getObjectCodec(AsyncChild.class) instanceof
GeneratedObjectCodec);
+ BaseObjectCodec firstCodec = awaitGenerated(first, AsyncChild.class);
+ JsonTypeResolver second = new JsonTypeResolver(sharedRegistry);
+ BaseObjectCodec secondCodec = second.getObjectCodec(AsyncChild.class);
+ assertTrue(secondCodec instanceof GeneratedObjectCodec,
secondCodec.getClass().getName());
+ assertTrue(firstCodec != secondCodec);
+ assertEquals(generatedUtf8WriterClass(firstCodec),
generatedUtf8WriterClass(secondCodec));
+ }
+
+ @Test
+ public void asyncRecursiveTypes() throws Exception {
+ ForyJson json = ForyJson.builder().build();
+ RecursiveParent value = new RecursiveParent();
+ assertEquals(json.toJson(value),
"{\"child\":{\"name\":\"child\"},\"name\":\"parent\"}");
+ awaitGenerated(json, RecursiveParent.class);
+ assertEquals(json.toJson(value),
"{\"child\":{\"name\":\"child\"},\"name\":\"parent\"}");
+ }
+
+ @Test
+ public void disabledCodegenIgnoresAsync() throws Exception {
+ ForyJson json = ForyJson.builder().withCodegen(false).build();
+ assertEquals(json.toJson(child("root", 1)),
"{\"id\":1,\"name\":\"root\"}");
+ Thread.sleep(50);
+ assertFalse(json.hasGeneratedWriter(AsyncChild.class));
+ }
+
+ private static AsyncParent parent() {
+ AsyncParent parent = new AsyncParent();
+ parent.child = child("child", 1);
+ parent.array = new AsyncChild[] {child("array", 2)};
+ parent.list = Collections.singletonList(child("list", 3));
+ parent.map = new LinkedHashMap<>();
+ parent.map.put("entry", child("map", 4));
+ parent.optional = Optional.of(child("optional", 5));
+ parent.atomic = new AtomicReference<>(child("atomic", 6));
+ return parent;
+ }
+
+ private static AsyncChild child(String name, int id) {
+ AsyncChild child = new AsyncChild();
+ child.name = name;
+ child.id = id;
+ return child;
+ }
+
+ private static void awaitGenerated(ForyJson json, Class<?> type) throws
InterruptedException {
+ for (int i = 0; i < 200; i++) {
+ if (json.hasGeneratedWriter(type)) {
+ return;
+ }
+ Thread.sleep(10);
+ }
+ fail("Timed out waiting for generated JSON codec for " + type);
+ }
+
+ private static BaseObjectCodec awaitGenerated(JsonTypeResolver resolver,
Class<?> type)
+ throws InterruptedException {
+ for (int i = 0; i < 200; i++) {
+ BaseObjectCodec codec = resolver.getObjectCodec(type);
+ if (codec instanceof GeneratedObjectCodec) {
+ return codec;
+ }
+ Thread.sleep(10);
+ }
+ fail("Timed out waiting for generated JSON codec for " + type);
+ throw new IllegalStateException("unreachable");
+ }
+
+ private static boolean asyncCompilationEnabled(ForyJson json) throws
Exception {
+ Object sharedRegistry = field(json, "sharedRegistry");
+ Object jitContext = field(sharedRegistry, "jitContext");
+ return (boolean) field(jitContext, "asyncCompilationEnabled");
+ }
+
+ private static int pendingJitCount(ForyJson json) throws Exception {
+ Object sharedRegistry = field(json, "sharedRegistry");
+ Object jitContext = field(sharedRegistry, "jitContext");
+ return ((Map<?, ?>) field(jitContext, "hasJITResult")).size();
+ }
+
+ private static Class<?> generatedUtf8WriterClass(BaseObjectCodec codec)
throws Exception {
+ Field field = GeneratedObjectCodec.class.getDeclaredField("utf8Writer");
+ field.setAccessible(true);
+ return field.get(codec).getClass();
+ }
+
+ private static void assertCachedGeneratedChild(
+ ForyJson json, Class<?> parentType, Class<?> childType) throws Exception
{
+ GeneratedObjectCodec codec = generatedObjectCodec(json, parentType);
+ AtomicInteger cachedChildFields = new AtomicInteger();
+ scanCachedCodecs(
+ codec, childType, cachedChildFields, Collections.newSetFromMap(new
IdentityHashMap<>()));
+ assertTrue(
+ cachedChildFields.get() >= 6,
+ "Expected cached generated child codecs, got " +
cachedChildFields.get());
+ }
+
+ private static GeneratedObjectCodec generatedObjectCodec(ForyJson json,
Class<?> type)
+ throws Exception {
+ Object primarySlot = ((AtomicReference<?>) field(json,
"primarySlot")).get();
+ Object state = field(primarySlot, "state");
+ Object resolver = field(state, "typeResolver");
+ BaseObjectCodec codec =
+ (BaseObjectCodec)
+ resolver.getClass().getMethod("getObjectCodec",
Class.class).invoke(resolver, type);
+ assertTrue(codec instanceof GeneratedObjectCodec);
+ return (GeneratedObjectCodec) codec;
+ }
+
+ private static void scanCachedCodecs(
+ Object owner, Class<?> childType, AtomicInteger cachedChildFields,
java.util.Set<Object> seen)
+ throws Exception {
+ if (owner == null || !seen.add(owner)) {
+ return;
+ }
+ if (owner instanceof BaseObjectCodec) {
+ BaseObjectCodec codec = (BaseObjectCodec) owner;
+ if (codec.type() == childType) {
+ assertTrue(codec instanceof GeneratedObjectCodec,
codec.getClass().getName());
+ cachedChildFields.incrementAndGet();
+ }
+ if (!(codec instanceof GeneratedObjectCodec)) {
+ return;
+ }
+ }
+ Class<?> type = owner.getClass();
+ if (!isCodecOwner(type)) {
+ return;
+ }
+ for (Field field : allFields(type)) {
+ if (Modifier.isStatic(field.getModifiers()) ||
field.getType().isPrimitive()) {
+ continue;
+ }
+ field.setAccessible(true);
+ Object value = field.get(owner);
+ if (value instanceof BaseObjectCodec && ((BaseObjectCodec) value).type()
== childType) {
+ assertTrue(value instanceof GeneratedObjectCodec,
value.getClass().getName());
+ cachedChildFields.incrementAndGet();
+ continue;
+ }
+ if (value instanceof BaseObjectCodec
+ || value instanceof JsonCodec
+ || isCodecOwner(value == null ? null : value.getClass())) {
+ scanCachedCodecs(value, childType, cachedChildFields, seen);
+ }
+ }
+ }
+
+ private static boolean isCodecOwner(Class<?> type) {
+ if (type == null) {
+ return false;
+ }
+ Package pkg = type.getPackage();
+ String packageName = pkg == null ? "" : pkg.getName();
+ return packageName.startsWith("org.apache.fory.json.codec")
+ || type.getSimpleName().contains("ForyJsonCodec");
+ }
+
+ private static java.util.List<Field> allFields(Class<?> type) {
+ java.util.ArrayList<Field> fields = new java.util.ArrayList<>();
+ for (Class<?> current = type; current != null; current =
current.getSuperclass()) {
+ fields.addAll(Arrays.asList(current.getDeclaredFields()));
+ }
+ return fields;
+ }
+
+ private static Object field(Object owner, String name) throws Exception {
+ Field field = owner.getClass().getDeclaredField(name);
+ field.setAccessible(true);
+ return field.get(owner);
+ }
+
+ private static void withJitLocked(ForyJson json, ThrowingRunnable action)
throws Exception {
+ Object sharedRegistry = field(json, "sharedRegistry");
+ Object jitContext = field(sharedRegistry, "jitContext");
+ jitContext.getClass().getMethod("lock").invoke(jitContext);
+ try {
+ action.run();
+ } finally {
+ jitContext.getClass().getMethod("unlock").invoke(jitContext);
+ }
+ }
+
+ private interface ThrowingRunnable {
+ void run() throws Exception;
+ }
+
+ public static final class AsyncParent {
+ public AsyncChild[] array;
+ public AtomicReference<AsyncChild> atomic;
+ public AsyncChild child;
+ public java.util.List<AsyncChild> list;
+ public Map<String, AsyncChild> map;
+ public Optional<AsyncChild> optional;
+ }
+
+ public static final class AsyncChild {
+ public int id;
+ public String name;
+ }
+
+ public static final class ObjectHolder {
+ public Object[] array;
+ public AtomicReference<Object> atomic;
+ public Map<String, Object> map;
+ public Optional<Object> optional;
+ }
+}
diff --git
a/java/fory-json/src/test/java/org/apache/fory/json/JsonContainerTest.java
b/java/fory-json/src/test/java/org/apache/fory/json/JsonContainerTest.java
index f6dae3ec1..bb1e18606 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonContainerTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonContainerTest.java
@@ -65,12 +65,18 @@ import org.apache.fory.json.data.MapKeyFields;
import org.apache.fory.json.data.Nested;
import org.apache.fory.json.data.TokenValues;
import org.apache.fory.reflect.TypeRef;
+import org.testng.annotations.Factory;
import org.testng.annotations.Test;
public class JsonContainerTest extends ForyJsonTestModels {
+ @Factory(dataProvider = "codegen")
+ public JsonContainerTest(boolean codegen) {
+ super(codegen);
+ }
+
@Test
public void writeNestedCollections() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(
json.toJson(new Nested()),
"{\"kind\":\"FAST\",\"names\":[\"a\",\"b\"],\"scores\":{\"one\":1,\"two\":2}}");
@@ -78,7 +84,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readTypeRefList() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
List<TokenValues> values =
json.fromJson(
"[{\"count\":1,\"name\":\"alpha\",\"tags\":[\"x\",\"y\"],\"total\":2},"
@@ -93,7 +99,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readCollectionSubclassElementType() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
Shelf shelf = json.fromJson("{\"notes\":[{\"title\":\"first\"}]}",
Shelf.class);
assertEquals(shelf.notes.get(0).getClass(), Note.class);
assertEquals(shelf.notes.get(0).title, "first");
@@ -101,7 +107,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readMapSubclassValueType() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
PaletteGroups groups = json.fromJson("{\"warm\":{\"primary\":\"red\"}}",
PaletteGroups.class);
assertEquals(groups.get("warm").getClass(), PaletteCodes.class);
assertEquals(groups.get("warm").get("primary"), "red");
@@ -109,7 +115,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readTypeRefMapBytes() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
byte[] bytes =
"{\"first\":{\"count\":5,\"name\":\"gamma\",\"tags\":[\"u\"],\"total\":6}}"
.getBytes(StandardCharsets.UTF_8);
@@ -123,7 +129,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readJsonContainers() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
JSONObject object =
json.fromJson("{\"name\":\"fory\",\"items\":[1,\"你好,Fory\"]}",
JSONObject.class);
assertEquals(object.get("name"), "fory");
@@ -139,7 +145,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void parsedContainersStartSmall() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
JSONArray array = json.fromJson("[1]", JSONArray.class);
assertEquals(arrayCapacity(array), 1);
assertEquals(arrayCapacity(json.fromJson("[]", JSONArray.class)), 0);
@@ -180,7 +186,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void writeJsonContainers() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
JSONObject object = new JSONObject();
JSONArray values = new JSONArray();
values.add(Integer.valueOf(1));
@@ -194,7 +200,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void writeReadMapKeyFields() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String expected =
"{\"intNames\":{\"1\":\"one\",\"2\":\"two\"},\"scores\":{\"FAST\":1,\"SMALL\":2}}";
assertEquals(json.toJson(new MapKeyFields()), expected);
@@ -205,7 +211,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void writeRootMapNumericKeys() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
Map<Integer, Integer> value =
json.fromJson("{\"7\":70,\"8\":80}", new TypeRef<Map<Integer,
Integer>>() {});
assertEquals(json.toJson(new LinkedHashMap<>(value)),
"{\"7\":70,\"8\":80}");
@@ -213,7 +219,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readTypeRefOptional() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
Optional<TokenValues> value =
json.fromJson(
"{\"count\":9,\"name\":\"optional\",\"tags\":[\"a\"],\"total\":10}",
@@ -226,7 +232,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readTypeRefMapKeys() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
Map<Integer, String> value =
json.fromJson("{\"1\":\"one\",\"2\":\"two\"}", new
TypeRef<Map<Integer, String>>() {});
assertEquals(value, intNames());
@@ -253,7 +259,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readFastContainerTypeRefs() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(
json.fromJson("[\"alpha\",\"你好,Fory\"]", new TypeRef<List<String>>()
{}),
Arrays.asList("alpha", ZH_TEXT));
@@ -285,7 +291,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readDeclaredJdkContainers() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
DeclaredJdkContainers value =
json.fromJson(
"{\"collection\":[\"a\",\"b\"],\"list\":[\"c\"],\"sequential\":[\"d\"],"
@@ -312,7 +318,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readMutableContainersUnchanged() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
List<String> list = json.fromJson("[\"a\"]", new TypeRef<List<String>>()
{});
assertTrue(list instanceof ArrayList);
assertEquals(list, Collections.singletonList("a"));
@@ -338,7 +344,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readEnumContainersStillWork() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
EnumContainers value =
json.fromJson(
"{\"kinds\":[\"FAST\",\"SMALL\"],\"scores\":{\"FAST\":1}}",
EnumContainers.class);
@@ -350,7 +356,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void rejectRuntimeContainerClasses() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertUnsupportedCollectionClass(json, Arrays.asList("a"));
assertUnsupportedCollectionClass(json, Collections.emptyList());
assertUnsupportedCollectionClass(json, Collections.singletonList("a"));
@@ -371,7 +377,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readGuavaImmutableContainers() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
GuavaContainers value =
json.fromJson(
"{\"list\":[\"a\",\"b\"],\"set\":[\"c\",\"d\"],"
@@ -394,7 +400,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void rejectGuavaImmutableNulls() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertThrows(
ForyJsonException.class,
() -> json.fromJson("[\"a\",null]", new
TypeRef<ImmutableList<String>>() {}));
@@ -406,7 +412,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void writeReadFastContainerFields() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String input =
"{\"booleans\":[true,false],\"flags\":{\"enabled\":true,\"disabled\":false},"
+ "\"intNames\":{\"1\":\"one\",\"2\":\"two\"},\"ints\":[1,2],"
@@ -419,7 +425,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readPrimitiveArrayRoots() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new int[] {1, 2}), "[1,2]");
assertEquals(json.fromJson("[1,2]", int[].class), new int[] {1, 2});
assertEquals(
@@ -434,7 +440,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readUtf8StringArrays() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(
json.fromJson("[\"a\"]".getBytes(StandardCharsets.UTF_8),
String[].class),
new String[] {"a"});
@@ -453,7 +459,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readStringInputStringArrays() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.fromJson("[\"a\"]", String[].class), new String[] {"a"});
assertEquals(
json.fromJson("[\"a\",null,\"b\",\"c\",\"d\",\"e\",\"f\",\"g\"]",
String[].class),
@@ -469,7 +475,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readUtf8LongArrays() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(
json.fromJson("[7]".getBytes(StandardCharsets.UTF_8), long[].class),
new long[] {7L});
assertEquals(
@@ -485,7 +491,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readStringInputLongArrays() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.fromJson("[7]", long[].class), new long[] {7L});
assertEquals(
json.fromJson("[1,2,3,4,5,6,7,8]", long[].class),
@@ -503,7 +509,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void writeReadBoxedPrimitiveArrays() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new Integer[] {1, null, -2}), "[1,null,-2]");
assertEquals(
json.fromJson("[1,null,-2]".getBytes(StandardCharsets.UTF_8),
Integer[].class),
@@ -537,7 +543,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readReferenceObjectArrays() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
Note[] notes =
json.fromJson("[{\"title\":\"one\"},null,{\"title\":\"two\"}]", Note[].class);
assertEquals(notes.getClass(), Note[].class);
assertEquals(notes.length, 3);
@@ -566,7 +572,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void readReentrantObjectArray() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
ArrayNode[] roots =
json.fromJson(
"[{\"name\":\"root\",\"children\":[{\"name\":\"leaf\",\"children\":[]}]}]"
@@ -600,7 +606,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
@Test
public void writeReadAtomicArrays() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new AtomicIntegerArray(new int[] {1, -2, 3})),
"[1,-2,3]");
assertAtomicInts(json.fromJson("[1,-2,3]", AtomicIntegerArray.class), 1,
-2, 3);
assertEquals(
diff --git
a/java/fory-json/src/test/java/org/apache/fory/json/JsonDepthTest.java
b/java/fory-json/src/test/java/org/apache/fory/json/JsonDepthTest.java
index 701c40d2f..8c5f4a7f9 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonDepthTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonDepthTest.java
@@ -27,12 +27,18 @@ import java.nio.charset.StandardCharsets;
import java.util.Map;
import org.apache.fory.json.data.DepthNode;
import org.apache.fory.reflect.TypeRef;
+import org.testng.annotations.Factory;
import org.testng.annotations.Test;
public class JsonDepthTest extends ForyJsonTestModels {
+ @Factory(dataProvider = "codegen")
+ public JsonDepthTest(boolean codegen) {
+ super(codegen);
+ }
+
@Test
public void defaultMaxDepth() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertTrue(
json.fromJson(nestedArray(ForyJson.DEFAULT_MAX_DEPTH), Object.class)
instanceof JSONArray);
assertThrows(
@@ -42,7 +48,7 @@ public class JsonDepthTest extends ForyJsonTestModels {
@Test
public void readMaxDepth() {
- ForyJson json = ForyJson.builder().maxDepth(2).build();
+ ForyJson json = newJsonBuilder().maxDepth(2).build();
assertEquals(json.fromJson("{\"child\":{\"value\":1}}",
DepthNode.class).child.value, 1);
assertThrows(
ForyJsonException.class,
@@ -57,7 +63,7 @@ public class JsonDepthTest extends ForyJsonTestModels {
@Test
public void readContainerMaxDepth() {
- ForyJson json = ForyJson.builder().maxDepth(2).build();
+ ForyJson json = newJsonBuilder().maxDepth(2).build();
assertTrue(json.fromJson("[[1]]", Object.class) instanceof JSONArray);
assertThrows(ForyJsonException.class, () -> json.fromJson("[[[1]]]",
Object.class));
assertEquals(
@@ -66,7 +72,7 @@ public class JsonDepthTest extends ForyJsonTestModels {
ForyJsonException.class,
() -> json.fromJson("{\"a\":{\"b\":{\"c\":1}}}", new
TypeRef<Map<String, Object>>() {}));
- ForyJson nestedJson = ForyJson.builder().maxDepth(3).build();
+ ForyJson nestedJson = newJsonBuilder().maxDepth(3).build();
assertEquals(
nestedJson
.fromJson("{\"children\":[{\"value\":2}]}", DepthNode.class)
@@ -91,7 +97,7 @@ public class JsonDepthTest extends ForyJsonTestModels {
@Test
public void readJsonObjectMaxDepth() {
- ForyJson json = ForyJson.builder().maxDepth(2).build();
+ ForyJson json = newJsonBuilder().maxDepth(2).build();
JSONObject object = json.fromJson("{\"items\":[1]}", JSONObject.class);
assertTrue(object.get("items") instanceof JSONArray);
assertThrows(
@@ -101,7 +107,7 @@ public class JsonDepthTest extends ForyJsonTestModels {
@Test
public void readDepthReset() {
- ForyJson json = ForyJson.builder().maxDepth(1).build();
+ ForyJson json = newJsonBuilder().maxDepth(1).build();
assertThrows(
ForyJsonException.class, () ->
json.fromJson("{\"child\":{\"value\":1}}", DepthNode.class));
assertEquals(json.fromJson("{\"value\":2}", DepthNode.class).value, 2);
@@ -116,6 +122,6 @@ public class JsonDepthTest extends ForyJsonTestModels {
@Test
public void rejectInvalidMaxDepth() {
- assertThrows(IllegalArgumentException.class, () ->
ForyJson.builder().maxDepth(0));
+ assertThrows(IllegalArgumentException.class, () ->
newJsonBuilder().maxDepth(0));
}
}
diff --git
a/java/fory-json/src/test/java/org/apache/fory/json/JsonGeneratedCodecTest.java
b/java/fory-json/src/test/java/org/apache/fory/json/JsonGeneratedCodecTest.java
index e693758f4..8cf5f57e6 100644
---
a/java/fory-json/src/test/java/org/apache/fory/json/JsonGeneratedCodecTest.java
+++
b/java/fory-json/src/test/java/org/apache/fory/json/JsonGeneratedCodecTest.java
@@ -23,6 +23,7 @@ import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotEquals;
import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
@@ -42,14 +43,20 @@ import org.apache.fory.json.meta.JsonFieldNameHash;
import org.apache.fory.json.reader.Latin1JsonReader;
import org.apache.fory.json.reader.Utf8JsonReader;
import org.apache.fory.json.resolver.JsonTypeResolver;
+import org.testng.annotations.Factory;
import org.testng.annotations.Test;
public class JsonGeneratedCodecTest extends ForyJsonTestModels {
+ @Factory(dataProvider = "codegen")
+ public JsonGeneratedCodecTest(boolean codegen) {
+ super(codegen);
+ }
+
private static final String GENERATED_SUFFIX = "ForyJsonCodec";
@Test
public void writeRecursiveGeneratedTypes() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
RecursiveParent value = new RecursiveParent();
assertEquals(json.toJson(value),
"{\"child\":{\"name\":\"child\"},\"name\":\"parent\"}");
assertEquals(
@@ -61,7 +68,7 @@ public class JsonGeneratedCodecTest extends
ForyJsonTestModels {
@Test
public void writeGeneratedTokenChanges() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
TokenValues value = new TokenValues();
String first =
"{\"count\":1,\"name\":\"alpha\",\"tags\":[\"x\",\"y\"],\"total\":2}";
assertEquals(json.toJson(value), first);
@@ -80,7 +87,7 @@ public class JsonGeneratedCodecTest extends
ForyJsonTestModels {
@Test
public void writeGeneratedTokenLanes() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
TokenGroup group = new TokenGroup();
group.values =
Arrays.asList(
@@ -112,7 +119,7 @@ public class JsonGeneratedCodecTest extends
ForyJsonTestModels {
@Test
public void readGeneratedObjectCollection() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String input =
"{\"values\":[{\"count\":1,\"name\":\"alpha\",\"tags\":[\"x\"],\"total\":2}]}";
TokenGroup stringValue = json.fromJson(input, TokenGroup.class);
TokenGroup utf8Value =
json.fromJson(input.getBytes(StandardCharsets.UTF_8), TokenGroup.class);
@@ -127,7 +134,7 @@ public class JsonGeneratedCodecTest extends
ForyJsonTestModels {
@Test
public void readGeneratedCollectionFields() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String input =
"{\"kinds\":[\"FAST\",\"SMALL\"],\"names\":[\"alpha\",\"你好,Fory\"]," +
"\"numbers\":[1,2]}";
assertGeneratedCollections(json.fromJson(input,
GeneratedCollectionFields.class));
@@ -138,24 +145,38 @@ public class JsonGeneratedCodecTest extends
ForyJsonTestModels {
@Test
public void sameConfigUsesSameId() throws Exception {
- ForyJson first = ForyJson.builder().build();
- ForyJson second = ForyJson.builder().build();
- ForyJson writeNullFields =
ForyJson.builder().writeNullFields(true).build();
+ ForyJson first = newJson();
+ ForyJson second = newJson();
+ ForyJson writeNullFields = newJsonBuilder().writeNullFields(true).build();
first.toJsonBytes(new PublicFields());
second.toJsonBytes(new PublicFields());
writeNullFields.toJsonBytes(new PublicFields());
+ if (!codegenEnabled()) {
+ assertFalse(first.hasGeneratedWriter(PublicFields.class));
+ assertFalse(second.hasGeneratedWriter(PublicFields.class));
+ assertFalse(writeNullFields.hasGeneratedWriter(PublicFields.class));
+ return;
+ }
Class<?> firstWriterClass = generatedUtf8WriterClass(first,
PublicFields.class);
Class<?> secondWriterClass = generatedUtf8WriterClass(second,
PublicFields.class);
Class<?> writeNullWriterClass = generatedUtf8WriterClass(writeNullFields,
PublicFields.class);
+ ForyJson asyncDefault = ForyJson.builder().withCodegen(true).build();
+ asyncDefault.toJsonBytes(new PublicFields());
+ awaitGenerated(asyncDefault, PublicFields.class);
+ Class<?> asyncWriterClass = generatedUtf8WriterClass(asyncDefault,
PublicFields.class);
assertEquals(
firstWriterClass.getPackage().getName(),
PublicFields.class.getPackage().getName());
assertEquals(
secondWriterClass.getPackage().getName(),
PublicFields.class.getPackage().getName());
+ assertEquals(
+ asyncWriterClass.getPackage().getName(),
PublicFields.class.getPackage().getName());
assertGeneratedName(firstWriterClass, PublicFields.class, "Utf8");
assertGeneratedName(secondWriterClass, PublicFields.class, "Utf8");
assertGeneratedName(writeNullWriterClass, PublicFields.class, "Utf8");
+ assertGeneratedName(asyncWriterClass, PublicFields.class, "Utf8");
assertEquals(generatedId(secondWriterClass),
generatedId(firstWriterClass));
+ assertEquals(generatedId(asyncWriterClass), generatedId(firstWriterClass));
assertNotEquals(generatedId(writeNullWriterClass),
generatedId(firstWriterClass));
}
@@ -200,7 +221,7 @@ public class JsonGeneratedCodecTest extends
ForyJsonTestModels {
@Test
public void readGeneratedLongAsciiFields() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String input =
"{\"registered\":\"today\",\"longitude\":12.5,\"favoriteFruit\":\"apple\","
+ "\"shortName\":\"core\"}";
@@ -212,7 +233,7 @@ public class JsonGeneratedCodecTest extends
ForyJsonTestModels {
@Test
public void readSplitGeneratedFields() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String ordered =
"{\"f0\":0,\"f1\":\"one\",\"f2\":2,\"f3\":\"three\",\"f4\":4,\"f5\":\"five\","
+ "\"f6\":6,\"f7\":\"seven\",\"f8\":8,\"f9\":\"nine\",\"f10\":10,"
@@ -300,6 +321,16 @@ public class JsonGeneratedCodecTest extends
ForyJsonTestModels {
return utf8WriterField.get(codec).getClass();
}
+ private static void awaitGenerated(ForyJson json, Class<?> type) throws
InterruptedException {
+ for (int i = 0; i < 200; i++) {
+ if (json.hasGeneratedWriter(type)) {
+ return;
+ }
+ Thread.sleep(10);
+ }
+ fail("Timed out waiting for generated JSON codec for " + type);
+ }
+
private static void assertGeneratedName(
Class<?> generatedClass, Class<?> valueType, String role) {
String simpleName = generatedClass.getSimpleName();
diff --git
a/java/fory-json/src/test/java/org/apache/fory/json/JsonGuavaOptionalDependencyTest.java
b/java/fory-json/src/test/java/org/apache/fory/json/JsonGuavaOptionalDependencyTest.java
index b92c9aa95..327a1134c 100644
---
a/java/fory-json/src/test/java/org/apache/fory/json/JsonGuavaOptionalDependencyTest.java
+++
b/java/fory-json/src/test/java/org/apache/fory/json/JsonGuavaOptionalDependencyTest.java
@@ -31,16 +31,24 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.fory.platform.JdkVersion;
+import org.testng.annotations.Factory;
import org.testng.annotations.Test;
-public class JsonGuavaOptionalDependencyTest {
+public class JsonGuavaOptionalDependencyTest extends ForyJsonTestModels {
private static final String RESULT = "RESULT:ok";
+ @Factory(dataProvider = "codegen")
+ public JsonGuavaOptionalDependencyTest(boolean codegen) {
+ super(codegen);
+ }
+
@Test
public void buildWithoutGuava() throws Exception {
String filteredClassPath =
removeGuavaFromClasspath(System.getProperty("java.class.path"));
Process process =
- new ProcessBuilder(javaCommand(filteredClassPath, NoGuavaMain.class))
+ new ProcessBuilder(
+ javaCommand(
+ filteredClassPath, NoGuavaMain.class,
Boolean.toString(codegenEnabled())))
.redirectErrorStream(true)
.start();
String output = readFully(process.getInputStream());
@@ -48,7 +56,7 @@ public class JsonGuavaOptionalDependencyTest {
assertTrue(output.contains(RESULT), output);
}
- private static List<String> javaCommand(String classPath, Class<?>
mainClass) {
+ private static List<String> javaCommand(String classPath, Class<?>
mainClass, String codegen) {
List<String> command =
new java.util.ArrayList<>(
Arrays.asList(
@@ -63,6 +71,7 @@ public class JsonGuavaOptionalDependencyTest {
command.add("-cp");
command.add(classPath);
command.add(mainClass.getName());
+ command.add(codegen);
return command;
}
@@ -87,7 +96,8 @@ public class JsonGuavaOptionalDependencyTest {
assertNotLoadable("com.google.common.collect.ImmutableList");
assertNotLoadable("com.google.common.primitives.ImmutableIntArray");
- ForyJson json = ForyJson.builder().build();
+ boolean codegen = Boolean.parseBoolean(args[0]);
+ ForyJson json =
ForyJson.builder().withCodegen(codegen).withAsyncCompilation(false).build();
PlainValue stringValue =
json.fromJson("{\"name\":\"string\",\"count\":7}", PlainValue.class);
assertValue(stringValue, "string", 7);
diff --git
a/java/fory-json/src/test/java/org/apache/fory/json/JsonObjectTest.java
b/java/fory-json/src/test/java/org/apache/fory/json/JsonObjectTest.java
index 3ed61ac7e..d604d7253 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonObjectTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonObjectTest.java
@@ -40,12 +40,18 @@ import org.apache.fory.json.data.MethodsIgnored;
import org.apache.fory.json.data.ParentValue;
import org.apache.fory.json.data.PrivateFields;
import org.apache.fory.json.data.PublicFields;
+import org.testng.annotations.Factory;
import org.testng.annotations.Test;
public class JsonObjectTest extends ForyJsonTestModels {
+ @Factory(dataProvider = "codegen")
+ public JsonObjectTest(boolean codegen) {
+ super(codegen);
+ }
+
@Test
public void writePublicFields() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new PublicFields()),
"{\"active\":true,\"id\":7,\"name\":\"fory\"}");
assertEquals(
new String(json.toJsonBytes(new PublicFields()),
StandardCharsets.UTF_8),
@@ -54,7 +60,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
@Test
public void writeJsonToOutputStream() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
ByteArrayOutputStream output = new ByteArrayOutputStream();
json.writeJsonTo(new PublicFields(), output);
assertEquals(
@@ -82,7 +88,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
@Test
public void writeFirstIntGenerated() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String expected = "{\"count\":2,\"name\":\"first\"}";
assertEquals(json.toJson(new FirstIntField()), expected);
assertEquals(
@@ -92,7 +98,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
@Test
public void sharedFacadeThreads() throws Exception {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String expected = "{\"active\":true,\"id\":7,\"name\":\"fory\"}";
int threads = 8;
int iterations = 200;
@@ -129,14 +135,14 @@ public class JsonObjectTest extends ForyJsonTestModels {
@Test
public void useGeneratedWriter() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new PublicFields()),
"{\"active\":true,\"id\":7,\"name\":\"fory\"}");
assertGeneratedWhenSupported(json, PublicFields.class);
}
@Test
- public void disableGeneratedWriter() {
- ForyJson json = ForyJson.builder().withCodegen(false).build();
+ public void generatedWriterSetting() {
+ ForyJson json = newJson();
assertEquals(json.toJson(new PublicFields()),
"{\"active\":true,\"id\":7,\"name\":\"fory\"}");
PublicFields fields =
json.fromJson("{\"active\":false,\"id\":8,\"name\":\"json\"}",
PublicFields.class);
@@ -150,12 +156,12 @@ public class JsonObjectTest extends ForyJsonTestModels {
BoxedScalars.class);
assertEquals(scalars.byteValue, Byte.valueOf((byte) 2));
assertEquals(scalars.shortValue, Short.valueOf((short) 3));
- assertEquals(json.hasGeneratedWriter(PublicFields.class), false);
+ assertGeneratedWhenSupported(json, PublicFields.class);
}
@Test
public void writeNullFields() {
- ForyJson json = ForyJson.builder().writeNullFields(true).build();
+ ForyJson json = newJsonBuilder().writeNullFields(true).build();
assertEquals(
json.toJson(new PublicFields()),
"{\"active\":true,\"id\":7,\"name\":\"fory\",\"missing\":null}");
@@ -163,7 +169,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
@Test
public void fieldOnlyModeIgnoresMethods() {
- ForyJson json = ForyJson.builder().withFieldMode(true).build();
+ ForyJson json = newJsonBuilder().withFieldMode(true).build();
assertEquals(
json.toJson(new MethodsIgnored()),
"{\"setterCalls\":0,\"value\":\"field\",\"hidden\":\"hidden\"}");
@@ -176,7 +182,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
@Test
public void writeDeclaredFields() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String expected = "{\"id\":11,\"name\":\"private\"}";
assertEquals(json.toJson(new PrivateFields()), expected);
assertEquals(
@@ -193,13 +199,13 @@ public class JsonObjectTest extends ForyJsonTestModels {
@Test
public void writeDirectionalIgnore() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new DirectionalIgnore()), "{\"writeOnly\":2}");
}
@Test
public void writeDeclaredObjectFieldType() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String expected = "{\"value\":{\"parent\":1}}";
assertEquals(json.toJson(new DeclaredParentField()), expected);
assertEquals(
@@ -212,7 +218,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
@Test
public void readPublicFields() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
PublicFields fields =
json.fromJson(
"{\"unknown\":[1,true,{\"x\":\"y\"}],\"name\":\"fory\",\"id\":7,\"active\":true}",
@@ -224,7 +230,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
@Test
public void readUtf8Bytes() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
byte[] bytes =
"{\"name\":\"\uD83D\uDE00\u1234\",\"id\":8,\"active\":false}"
.getBytes(StandardCharsets.UTF_8);
@@ -236,7 +242,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
@Test
public void readDirectionalIgnore() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
DirectionalIgnore value =
json.fromJson("{\"both\":7,\"writeOnly\":8,\"readOnly\":9}",
DirectionalIgnore.class);
assertEquals(value.both, 1);
diff --git
a/java/fory-json/src/test/java/org/apache/fory/json/JsonPropertyTest.java
b/java/fory-json/src/test/java/org/apache/fory/json/JsonPropertyTest.java
index ca3991dcf..9fe145e3e 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonPropertyTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonPropertyTest.java
@@ -35,19 +35,25 @@ import org.apache.fory.json.data.BeanProperties.MixedBean;
import org.apache.fory.json.data.BeanProperties.OverloadedSetterBean;
import org.apache.fory.json.data.BeanProperties.SetterBean;
import org.apache.fory.json.data.BeanProperties.SetterOnlyBean;
+import org.testng.annotations.Factory;
import org.testng.annotations.Test;
public class JsonPropertyTest extends ForyJsonTestModels {
+ @Factory(dataProvider = "codegen")
+ public JsonPropertyTest(boolean codegen) {
+ super(codegen);
+ }
+
@Test
public void writePrivateGetters() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new GetterBean()),
"{\"id\":17,\"name\":\"getter-field\"}");
assertGeneratedWhenSupported(json, GetterBean.class);
}
@Test
public void readPrivateSetters() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
SetterBean value = json.fromJson("{\"id\":4,\"name\":\"alpha\"}",
SetterBean.class);
assertEquals(SetterBean.id(value), 5);
assertEquals(SetterBean.name(value), "set-alpha");
@@ -57,7 +63,7 @@ public class JsonPropertyTest extends ForyJsonTestModels {
@Test
public void roundTripMixedProperties() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String text = "{\"count\":3,\"name\":\"mixed\",\"score\":5}";
assertEquals(json.toJson(new MixedBean()), text);
assertEquals(json.toJson(json.fromJson(text, MixedBean.class)), text);
@@ -66,13 +72,13 @@ public class JsonPropertyTest extends ForyJsonTestModels {
@Test
public void writeBooleanIsGetter() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new BooleanBean()), "{\"ready\":true}");
}
@Test
public void getterOnlyWrites() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new GetterOnlyBean()), "{\"computed\":6}");
GetterOnlyBean value = json.fromJson("{\"computed\":99}",
GetterOnlyBean.class);
assertEquals(json.toJson(value), "{\"computed\":6}");
@@ -80,7 +86,7 @@ public class JsonPropertyTest extends ForyJsonTestModels {
@Test
public void setterOnlyReads() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new SetterOnlyBean()), "{}");
SetterOnlyBean value = json.fromJson("{\"secret\":\"alpha\"}",
SetterOnlyBean.class);
assertEquals(SetterOnlyBean.received(value), "set-alpha");
@@ -88,7 +94,7 @@ public class JsonPropertyTest extends ForyJsonTestModels {
@Test
public void fieldOnlyMode() {
- ForyJson json = ForyJson.builder().withFieldMode(true).build();
+ ForyJson json = newJsonBuilder().withFieldMode(true).build();
assertEquals(json.toJson(new GetterBean()),
"{\"id\":7,\"name\":\"field\"}");
assertEquals(json.toJson(new GetterOnlyBean()), "{}");
SetterOnlyBean value = json.fromJson("{\"secret\":\"alpha\"}",
SetterOnlyBean.class);
@@ -97,7 +103,7 @@ public class JsonPropertyTest extends ForyJsonTestModels {
@Test
public void rejectPropertyConflicts() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertThrows(ForyJsonException.class, () -> json.toJson(new
DuplicateGetterBean()));
assertThrows(
ForyJsonException.class,
@@ -107,7 +113,7 @@ public class JsonPropertyTest extends ForyJsonTestModels {
@Test
public void inheritedProperties() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new InheritedChild()),
"{\"id\":4,\"name\":\"child\"}");
InheritedChild value = json.fromJson("{\"id\":7,\"name\":\"json\"}",
InheritedChild.class);
assertEquals(InheritedParent.id(value), 8);
@@ -116,13 +122,13 @@ public class JsonPropertyTest extends ForyJsonTestModels {
@Test
public void ignoreInvalidAccessors() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new InvalidAccessorBean()),
"{\"value\":\"field\"}");
}
@Test
public void finalFieldsStayReadOnly() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new FinalFieldBean()),
"{\"id\":1,\"name\":\"field\"}");
FinalFieldBean value = json.fromJson("{\"id\":9,\"name\":\"json\"}",
FinalFieldBean.class);
assertEquals(value.id, 1);
diff --git
a/java/fory-json/src/test/java/org/apache/fory/json/JsonRecordTest.java
b/java/fory-json/src/test/java/org/apache/fory/json/JsonRecordTest.java
index d2b23f264..7c6c8e7c6 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonRecordTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonRecordTest.java
@@ -26,9 +26,15 @@ import java.util.Arrays;
import java.util.List;
import org.apache.fory.platform.JdkVersion;
import org.testng.SkipException;
+import org.testng.annotations.Factory;
import org.testng.annotations.Test;
public class JsonRecordTest extends ForyJsonTestModels {
+ @Factory(dataProvider = "codegen")
+ public JsonRecordTest(boolean codegen) {
+ super(codegen);
+ }
+
@Test
public void writeReadRecordClass() throws Exception {
if (JdkVersion.MAJOR_VERSION < 17) {
@@ -48,7 +54,7 @@ public class JsonRecordTest extends ForyJsonTestModels {
Object value =
type.getConstructor(int.class, String.class, List.class, childType)
.newInstance(7, ZH_TEXT, Arrays.asList("a", "b"), child);
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String expected =
"{\"id\":7,\"name\":\"你好,Fory\",\"tags\":[\"a\",\"b\"]," +
"\"child\":{\"label\":\"kid\"}}";
assertEquals(json.toJson(value), expected);
diff --git
a/java/fory-json/src/test/java/org/apache/fory/json/JsonScalarTest.java
b/java/fory-json/src/test/java/org/apache/fory/json/JsonScalarTest.java
index bc1c99a0a..ffb50b85f 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonScalarTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonScalarTest.java
@@ -83,12 +83,18 @@ import org.apache.fory.json.writer.StringJsonWriter;
import org.apache.fory.json.writer.Utf8JsonWriter;
import org.apache.fory.reflect.TypeRef;
import org.apache.fory.serializer.StringSerializer;
+import org.testng.annotations.Factory;
import org.testng.annotations.Test;
public class JsonScalarTest extends ForyJsonTestModels {
+ @Factory(dataProvider = "codegen")
+ public JsonScalarTest(boolean codegen) {
+ super(codegen);
+ }
+
@Test
public void writeBoxedScalars() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String expected =
"{\"bool\":true,\"byteValue\":2,\"charValue\":\"x\",\"doubleValue\":2.5,"
+
"\"floatValue\":1.5,\"intValue\":4,\"longValue\":5,\"shortValue\":3}";
@@ -126,7 +132,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeReadNonFiniteFloats() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(Double.NaN), "\"NaN\"");
assertEquals(json.toJson(Double.POSITIVE_INFINITY), "\"Infinity\"");
assertEquals(json.toJson(Double.NEGATIVE_INFINITY), "\"-Infinity\"");
@@ -189,7 +195,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeNaturalObjectValues() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String expected =
"{\"bool\":true,\"list\":[\"a\",1,false],\"map\":{\"name\":\"fory\",\"score\":9},"
+ "\"number\":7,\"text\":\"fory\"}";
@@ -200,7 +206,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeNaturalEmptyObject() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String expected = "{\"value\":{}}";
assertEquals(json.toJson(new NaturalObjectValue()), expected);
assertEquals(
@@ -209,7 +215,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void readBoxedScalars() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
BoxedScalars value =
json.fromJson(
"{\"bool\":false,\"byteValue\":6,\"charValue\":\"z\",\"doubleValue\":3.5,"
@@ -227,7 +233,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void readNumericBoundaries() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String latin1 =
"{\"intMax\":2147483647,\"intMin\":-2147483648,"
+
"\"longMax\":9223372036854775807,\"longMin\":-9223372036854775808,"
@@ -329,7 +335,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeNumericBoundaries() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
NumericBoundaries value = new NumericBoundaries();
value.intMax = Integer.MAX_VALUE;
value.intMin = Integer.MIN_VALUE;
@@ -360,7 +366,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
assertEquals(
writer.toJson(), "[\"" + ZH_TEXT +
"\",9223372036854775807,2147483648,-2147483648]");
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
Utf16NumericFields value = new Utf16NumericFields();
value.prefix = ZH_TEXT;
value.zero = 0;
@@ -392,7 +398,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeReadCoreScalarFields() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
CoreScalarFields value = new CoreScalarFields();
String expected =
"{\"atomicInt\":7,\"bigDecimal\":12345.6789,\"bigInteger\":12345678901234567890,"
@@ -427,7 +433,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeReadAtomicScalars() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.toJson(new AtomicBoolean(true)), "true");
assertEquals(json.fromJson("false", AtomicBoolean.class).get(), false);
assertEquals(json.toJson(new AtomicInteger(12)), "12");
@@ -446,7 +452,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeUtf8ScalarFormats() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
UUID uuid = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
assertEquals(
new String(json.toJsonBytes(uuid), StandardCharsets.UTF_8),
@@ -491,7 +497,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeGeneratedUtf8Scalars() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
Utf8ScalarFields fields = new Utf8ScalarFields();
fields.uuid = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
fields.decimal = new BigDecimal("12345.6789");
@@ -509,7 +515,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeCommonScalarFastFormats() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
UUID uuid = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
assertEquals(json.toJson(new StringBuilder("build")), "\"build\"");
assertEquals(
@@ -552,7 +558,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void readScalarRoots() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.fromJson("7", int.class), Integer.valueOf(7));
assertEquals(json.fromJson("true", boolean.class), Boolean.TRUE);
assertEquals(json.fromJson("0.100", BigDecimal.class), new
BigDecimal("0.100"));
@@ -579,7 +585,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void readCommonScalarFastFormats() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.fromJson("123456789", BigInteger.class),
BigInteger.valueOf(123456789L));
assertEquals(json.fromJson("123456789", BigDecimal.class), new
BigDecimal("123456789"));
assertEquals(
@@ -649,7 +655,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeReadSqlTimeScalars() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
java.sql.Date date = new java.sql.Date(123456789L);
Time time = new Time(234567890L);
Timestamp timestamp = new Timestamp(345678901L);
@@ -663,7 +669,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void readDoubleDecimalContainers() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
byte[] doublesJson = "[12.5,-0.0,1.25e2]".getBytes(StandardCharsets.UTF_8);
double[] doubles = json.fromJson(doublesJson, double[].class);
assertEquals(doubles, new double[] {12.5d, -0.0d, 125.0d});
@@ -700,7 +706,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void readGeneratedUtf8BigDecimal() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
byte[] input =
("{\"uuid\":\"123e4567-e89b-12d3-a456-426614174000\","
+ "\"decimal\":0.12345678901234567,"
@@ -714,7 +720,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void readGeneratedLatin1Scalars() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String input =
"{\"uuid\":\"123e4567-e89b-12d3-a456-426614174000\","
+ "\"decimal\":0.12345678901234567,"
@@ -730,7 +736,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void readUntypedLargeInteger() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
BigInteger unsigned = new BigInteger("18446744073709550616");
assertEquals(json.fromJson(unsigned.toString(), Object.class), unsigned);
JSONObject object = json.fromJson("{\"count\":18446744073709550616}",
JSONObject.class);
@@ -739,7 +745,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeReadDeclaredNumber() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.fromJson("7", Number.class), Long.valueOf(7));
assertEquals(
json.fromJson("9223372036854775808", Number.class), new
BigInteger("9223372036854775808"));
@@ -764,7 +770,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeReadCharSequence() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
CharSequence root = json.fromJson("\"fory\"", CharSequence.class);
assertEquals(root, "fory");
assertEquals(root.getClass(), String.class);
@@ -780,7 +786,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeReadBitSet() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
BitSet empty = new BitSet();
assertEquals(json.toJson(empty), "[]");
assertEquals(json.fromJson("[]", BitSet.class), empty);
@@ -807,7 +813,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeReadChronoDates() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
LocalDate iso = LocalDate.of(2024, 2, 3);
ChronoDateFields fields = new ChronoDateFields();
fields.hijrah = HijrahChronology.INSTANCE.date(iso);
@@ -831,7 +837,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void rejectNetworkAddressTypes() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertThrows(ForyJsonException.class, () ->
json.toJson(InetAddress.getLoopbackAddress()));
assertThrows(
ForyJsonException.class,
@@ -844,7 +850,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void rejectClassTypeByDefault() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertThrows(ForyJsonException.class, () -> json.toJson(String.class));
assertThrows(ForyJsonException.class, () -> json.toJson(int.class));
assertThrows(ForyJsonException.class, () ->
json.fromJson("\"java.lang.String\"", Class.class));
@@ -853,7 +859,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void rejectClassFields() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertThrows(ForyJsonException.class, () -> json.toJson(new
ClassFieldHolder()));
assertThrows(
ForyJsonException.class,
@@ -862,7 +868,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void rejectClassArrays() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertThrows(ForyJsonException.class, () -> json.toJson(new Class<?>[]
{String.class}));
assertThrows(
ForyJsonException.class, () -> json.fromJson("[\"java.lang.String\"]",
Class[].class));
@@ -874,7 +880,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void writeReadFileAndPath() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
File file = new File("fory-json-file.txt");
Path path = Paths.get("fory-json-path.txt");
assertEquals(json.toJson(file), "\"fory-json-file.txt\"");
@@ -894,7 +900,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void readLocalDateFromDateTime() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
LocalDate expected = LocalDate.of(2023, 7, 2);
assertEquals(json.fromJson("\"2023-07-02T16:00:00.000Z\"",
LocalDate.class), expected);
assertEquals(
@@ -908,7 +914,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void readLocalDateFallbackForms() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
LocalDate extended = LocalDate.of(10000, 2, 3);
assertEquals(json.fromJson("\"+10000-02-03\"", LocalDate.class), extended);
assertEquals(
@@ -920,7 +926,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void readOffsetDateTime() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
OffsetDateTime utc = OffsetDateTime.of(2024, 2, 3, 4, 5, 6, 0,
ZoneOffset.UTC);
assertEquals(json.fromJson("\"2024-02-03T04:05:06Z\"",
OffsetDateTime.class), utc);
assertEquals(
@@ -946,7 +952,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void readOffsetDateTimeFallbackForms() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
OffsetDateTime extended = OffsetDateTime.of(10000, 2, 3, 4, 5, 6, 0,
ZoneOffset.ofHours(8));
String input = "\"+10000-02-03T04:05:06+08:00\"";
assertEquals(json.fromJson(input, OffsetDateTime.class), extended);
@@ -971,7 +977,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
assertEquals(timestampReader.readIsoOffsetDateTime(), timestamp);
timestampReader.finish();
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
Utf16TemporalFields fields =
json.fromJson(
"{\"text\":\"中文\",\"date\":\"2023-07-02\","
@@ -985,16 +991,16 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void objectFieldUsesUtf8Codec() {
ForyJson json =
- ForyJson.builder().registerCodec(ModeAwareValue.class, new
ModeAwareCodec()).build();
+ newJsonBuilder().registerCodec(ModeAwareValue.class, new
ModeAwareCodec()).build();
ModeAwareHolder holder =
json.fromJson("{\"value\":{}}".getBytes(StandardCharsets.UTF_8),
ModeAwareHolder.class);
- assertEquals(holder.value.mode, "utf8");
+ assertEquals(holder.value.mode, codegenEnabled() ? "utf8" : "generic");
}
@Test
public void byteInputUsesUtf8Codec() {
ForyJson json =
- ForyJson.builder().registerCodec(ModeAwareValue.class, new
ModeAwareCodec()).build();
+ newJsonBuilder().registerCodec(ModeAwareValue.class, new
ModeAwareCodec()).build();
ModeAwareValue value =
json.fromJson("{}".getBytes(StandardCharsets.UTF_8),
ModeAwareValue.class);
assertEquals(value.mode, "utf8");
@@ -1003,7 +1009,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void stringInputUsesLatin1Codec() {
ForyJson json =
- ForyJson.builder().registerCodec(ModeAwareValue.class, new
ModeAwareCodec()).build();
+ newJsonBuilder().registerCodec(ModeAwareValue.class, new
ModeAwareCodec()).build();
ModeAwareValue value = json.fromJson("{}", ModeAwareValue.class);
String expected = StringSerializer.isBytesBackedString() ? "latin1" :
"utf16";
assertEquals(value.mode, expected);
@@ -1012,14 +1018,14 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void stringInputUsesUtf16Codec() {
ForyJson json =
- ForyJson.builder().registerCodec(ModeAwareValue.class, new
ModeAwareCodec()).build();
+ newJsonBuilder().registerCodec(ModeAwareValue.class, new
ModeAwareCodec()).build();
ModeAwareValue value = json.fromJson("{\"ignored\":\"\u0100\"}",
ModeAwareValue.class);
assertEquals(value.mode, "utf16");
}
@Test
public void rejectMalformedStringScalar() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertThrows(
RuntimeException.class,
() -> json.fromJson("\"2024-02-03 04:05:06\"", LocalDateTime.class));
@@ -1027,7 +1033,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
@Test
public void rejectLeadingZero() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertThrows(ForyJsonException.class, () -> json.fromJson("01",
int.class));
assertThrows(
ForyJsonException.class,
diff --git
a/java/fory-json/src/test/java/org/apache/fory/json/JsonStringTest.java
b/java/fory-json/src/test/java/org/apache/fory/json/JsonStringTest.java
index 3caf82909..6f14b5dd2 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonStringTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonStringTest.java
@@ -41,12 +41,18 @@ import org.apache.fory.json.reader.Utf16JsonReader;
import org.apache.fory.json.writer.StringJsonWriter;
import org.apache.fory.memory.NativeByteOrder;
import org.apache.fory.serializer.StringSerializer;
+import org.testng.annotations.Factory;
import org.testng.annotations.Test;
public class JsonStringTest extends ForyJsonTestModels {
+ @Factory(dataProvider = "codegen")
+ public JsonStringTest(boolean codegen) {
+ super(codegen);
+ }
+
@Test
public void escapeStrings() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
PublicFields fields = new PublicFields();
fields.name = "a\n\"b\"\\\u1234";
String stringExpected =
"{\"active\":true,\"id\":7,\"name\":\"a\\n\\\"b\\\"\\\\\u1234\"}";
@@ -56,7 +62,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void writeUtf16StringText() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
UnicodeValues values = new UnicodeValues();
String expected =
"{\"first\":\"\u1234\",\"second\":\"music \uD834\uDD1E\","
@@ -155,7 +161,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void writeUtf16Char() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
CharValue value = new CharValue();
value.value = '\u1234';
assertEquals(json.toJson(value), "{\"value\":\"\u1234\"}");
@@ -165,7 +171,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void writeNonLatin1Matrix() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
UnicodeMatrix value = new UnicodeMatrix();
String expected = unicodeMatrixJson();
assertEquals(json.toJson(value), expected);
@@ -183,7 +189,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void writeReadZhEuStrings() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertTextRoundTrip(json, ZH_TEXT);
assertTextRoundTrip(json, EU_TEXT);
}
@@ -241,7 +247,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void readStringInputLayouts() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String latin1Json = "{\"active\":true,\"id\":7,\"name\":\"café\"}";
String utf16Json = "{\"active\":true,\"id\":7,\"name\":\"你好,Fory\"}";
if (StringSerializer.isBytesBackedString()) {
@@ -256,7 +262,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void readStringInputEscapes() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String asciiEscaped =
json.fromJson("\"line\\n\\r\\t\\b\\f\\\\\\\"\\/end\"", String.class);
String latin1Escaped = json.fromJson("\"caf\\u00E9\"", String.class);
@@ -312,7 +318,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void readUnicodeFieldNames() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String direct = "{\"café\":\"" + EU_TEXT + "\",\"你好\":\"" + ZH_TEXT +
"\"}";
String escaped = "{\"caf\\u00e9\":\"" + EU_TEXT +
"\",\"\\u4f60\\u597d\":\"" + ZH_TEXT + "\"}";
UnicodeFieldNames directValue = json.fromJson(direct,
UnicodeFieldNames.class);
@@ -329,7 +335,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void readUnicodeEnum() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String direct = "{\"kind\":\"你好\"}";
String escaped = "{\"kind\":\"\\u4f60\\u597d\"}";
String asciiDirect = "{\"kind\":\"FAST\"}";
@@ -351,7 +357,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void writeLatin1NonAsciiBytes() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
PublicFields fields = new PublicFields();
fields.name = "caf\u00e9";
assertEquals(json.toJson(fields),
"{\"active\":true,\"id\":7,\"name\":\"caf\u00e9\"}");
@@ -367,7 +373,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void rejectSurrogateChar() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
CharValue value = new CharValue();
value.value = '\uD800';
assertThrows(ForyJsonException.class, () -> json.toJson(value));
@@ -376,7 +382,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void rejectSurrogateString() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
PublicFields fields = new PublicFields();
fields.name = "\uD800";
assertThrows(ForyJsonException.class, () -> json.toJson(fields));
@@ -385,7 +391,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void writeSurrogatePair() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
PublicFields fields = new PublicFields();
fields.name = "a\uD83D\uDE00";
String stringExpected =
"{\"active\":true,\"id\":7,\"name\":\"a\uD83D\uDE00\"}";
@@ -397,7 +403,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void writeStringScanBoundaries() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
for (int length :
new int[] {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 20, 23, 24, 25, 30,
31, 32, 33, 63, 64, 65
@@ -415,7 +421,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void readStringScanBoundaries() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
for (int length : new int[] {0, 1, 7, 8, 15, 16, 17, 23, 24, 31, 32, 33,
63, 64, 65}) {
String value = repeat('a', length);
String input = "\"" + value + "\"";
@@ -448,7 +454,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void readUtf8DecodedStrings() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
String ascii = readUtf8String(json, "\"plain ascii\"");
String latin1 = readUtf8String(json, "\"caf\u00e9\"");
String escaped = readUtf8String(json, "\"line\\n\\t\\\\\\\"\\/end\"");
@@ -483,7 +489,7 @@ public class JsonStringTest extends ForyJsonTestModels {
@Test
public void rejectInvalidSurrogates() {
- ForyJson json = ForyJson.builder().build();
+ ForyJson json = newJson();
assertEquals(json.fromJson("\"\\uD834\\uDD1E\"", String.class),
"\uD834\uDD1E");
assertThrows(ForyJsonException.class, () -> json.fromJson("\"\\uD800\"",
String.class));
assertThrows(ForyJsonException.class, () -> json.fromJson("\"\\uDC00\"",
String.class));
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]