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 55879b788 feat(dart): align dart xlang serialization (#3322)
55879b788 is described below
commit 55879b788a22a7e1e565592f3106eaab8c22e1fe
Author: Shawn Yang <[email protected]>
AuthorDate: Wed Feb 11 10:20:07 2026 +0800
feat(dart): align dart xlang serialization (#3322)
## Why?
Dart xlang serialization was stale and inconsistent with
`docs/specification/xlang_serialization_spec.md`, which caused
cross-language incompatibilities with Java xlang tests.
## What does this PR do?
- Adds a Dart xlang test harness in Java: `DartXlangTest`.
- Adds Dart cross-language test entrypoint:
`dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart`.
- Aligns Dart xlang read/write behavior with Java/spec for core paths:
- collection/list/set element header flags and read/write paths,
- map chunk header + key/value chunk read/write paths,
- non-ref typed read/write without per-element ref headers when
required.
- Fixes Dart xlang type resolution and compatibility details used by
tests:
- set serializer assignment in type-wrap resolution,
- struct hash signedness consistency,
- default ref-tracking behavior alignment for xlang compatibility.
- Adds Dart xlang CI job in `.github/workflows/ci.yml`.
- Fixes existing Dart CI codegen step to be non-interactive with:
- `dart run build_runner build --delete-conflicting-outputs`.
Validation performed:
- `ENABLE_FORY_DEBUG_OUTPUT=1 FORY_DART_JAVA_CI=1 mvn -T16
--no-transfer-progress -pl fory-core -am test
-Dtest=org.apache.fory.xlang.DartXlangTest
-Dsurefire.failIfNoSpecifiedTests=false`
- Dart CI pipeline checks (`dart pub get`, `dart run build_runner build
--delete-conflicting-outputs`, `dart test`, `dart analyze`)
## Related issues
N/A
## Does this PR introduce any user-facing change?
Dart cross-language serialization behavior is now aligned with xlang
spec/Java behavior.
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
N/A
---
.github/workflows/ci.yml | 41 +-
.../test/cross_lang_test/xlang_test_main.dart | 871 +++++++++++++++++++++
dart/packages/fory/lib/src/base_fory.dart | 10 +-
dart/packages/fory/lib/src/config/fory_config.dart | 20 +-
.../fory/lib/src/deserialize_coordinator.dart | 36 +-
.../lib/src/exception/registration_exception.dart | 44 +-
dart/packages/fory/lib/src/fory_context.dart | 73 +-
dart/packages/fory/lib/src/fory_impl.dart | 27 +-
.../fory/lib/src/manager/fory_config_manager.dart | 4 +-
.../lib/src/resolver/impl/xtype_resolver_impl.dart | 617 ++++++++++++++-
.../lib/src/resolver/struct_hash_resolver.dart | 132 ++--
.../fory/lib/src/resolver/xtype_resolver.dart | 11 +-
.../fory/lib/src/serialize_coordinator.dart | 54 +-
.../fory/lib/src/serializer/class_serializer.dart | 96 ++-
.../serializer/collection/iterable_serializer.dart | 198 ++++-
.../collection/list/list_serializer.dart | 81 +-
.../serializer/collection/map/map_serializer.dart | 465 ++++++++---
.../serializer/collection/set/set_serializer.dart | 79 +-
.../java/org/apache/fory/xlang/DartXlangTest.java | 294 +++++++
19 files changed, 2748 insertions(+), 405 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d6e4962be..d4ddfe533 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -679,6 +679,45 @@ jobs:
- name: Run Go IDL Tests
run: ./integration_tests/idl_tests/run_go_tests.sh
+ dart_xlang:
+ name: Dart Xlang Test
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+ - name: Set up JDK 21
+ uses: actions/setup-java@v4
+ with:
+ java-version: 21
+ distribution: "temurin"
+ - name: Cache Maven local repository
+ uses: actions/cache@v4
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Install Dart SDK
+ run: |
+ DART_VERSION=3.6.1
+ wget -q
https://storage.googleapis.com/dart-archive/channels/stable/release/${DART_VERSION}/sdk/dartsdk-linux-x64-release.zip
+ unzip -q dartsdk-linux-x64-release.zip
+ echo "$PWD/dart-sdk/bin" >> $GITHUB_PATH
+ - name: Display Dart version
+ run: dart --version
+ - name: Get Dart dependencies
+ run: |
+ cd dart
+ dart pub get
+ - name: Run Dart Xlang Test
+ env:
+ FORY_DART_JAVA_CI: "1"
+ ENABLE_FORY_DEBUG_OUTPUT: "1"
+ run: |
+ cd java
+ mvn -T16 --no-transfer-progress clean install -DskipTests
+ cd fory-core
+ mvn -T16 --no-transfer-progress test
-Dtest=org.apache.fory.xlang.DartXlangTest
+
dart:
name: Dart CI
runs-on: ubuntu-latest
@@ -701,7 +740,7 @@ jobs:
- name: Generate code
run: |
cd dart/packages/fory-test
- dart run build_runner build
+ dart run build_runner build --delete-conflicting-outputs
- name: Run tests
run: |
cd dart/packages/fory-test
diff --git a/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart
b/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart
new file mode 100644
index 000000000..a655a519a
--- /dev/null
+++ b/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart
@@ -0,0 +1,871 @@
+/*
+ * 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.
+ */
+
+import 'dart:io';
+import 'dart:typed_data';
+import 'package:fory/fory.dart';
+
+String _getDataFile() {
+ final String? dataFile = Platform.environment['DATA_FILE'];
+ if (dataFile == null || dataFile.isEmpty) {
+ throw StateError('DATA_FILE environment variable not set');
+ }
+ return dataFile;
+}
+
+Uint8List _readFile(String path) {
+ return File(path).readAsBytesSync();
+}
+
+void _writeFile(String path, Uint8List data) {
+ File(path).writeAsBytesSync(data, flush: true);
+}
+
+void _passthrough() {
+ final String dataFile = _getDataFile();
+ final Uint8List data = _readFile(dataFile);
+ _writeFile(dataFile, data);
+}
+
+enum _TestEnum {
+ VALUE_A,
+ VALUE_B,
+ VALUE_C,
+}
+
+const EnumSpec _testEnumSpec = EnumSpec(
+ _TestEnum,
+ _TestEnum.values,
+);
+
+class _TwoEnumFieldStructEvolution {
+ _TestEnum f1 = _TestEnum.VALUE_A;
+ _TestEnum f2 = _TestEnum.VALUE_A;
+}
+
+final ClassSpec _twoEnumFieldStructEvolutionSpec = ClassSpec(
+ _TwoEnumFieldStructEvolution,
+ false,
+ true,
+ [
+ FieldSpec(
+ 'f1',
+ const TypeSpec(
+ _TestEnum,
+ ObjType.ENUM,
+ false,
+ true,
+ _testEnumSpec,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _TwoEnumFieldStructEvolution).f1,
+ (Object inst, dynamic v) =>
+ (inst as _TwoEnumFieldStructEvolution).f1 = v as _TestEnum,
+ ),
+ FieldSpec(
+ 'f2',
+ const TypeSpec(
+ _TestEnum,
+ ObjType.ENUM,
+ false,
+ true,
+ _testEnumSpec,
+ [],
+ ),
+ false,
+ true,
+ (Object inst) => (inst as _TwoEnumFieldStructEvolution).f2,
+ (Object inst, dynamic v) =>
+ (inst as _TwoEnumFieldStructEvolution).f2 = v as _TestEnum,
+ ),
+ ],
+ null,
+ () => _TwoEnumFieldStructEvolution(),
+);
+
+class _RefOverrideElement {
+ Int32 id = Int32(0);
+ String name = '';
+}
+
+class _RefOverrideContainer {
+ List<_RefOverrideElement> listField = <_RefOverrideElement>[];
+ Map<String, _RefOverrideElement> mapField = <String, _RefOverrideElement>{};
+}
+
+final ClassSpec _refOverrideElementSpec = ClassSpec(
+ _RefOverrideElement,
+ false,
+ true,
+ [
+ FieldSpec(
+ 'id',
+ const TypeSpec(
+ Int32,
+ ObjType.VAR_INT32,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _RefOverrideElement).id,
+ (Object inst, dynamic v) => (inst as _RefOverrideElement).id = v as
Int32,
+ ),
+ FieldSpec(
+ 'name',
+ const TypeSpec(
+ String,
+ ObjType.STRING,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _RefOverrideElement).name,
+ (Object inst, dynamic v) =>
+ (inst as _RefOverrideElement).name = v as String,
+ ),
+ ],
+ null,
+ () => _RefOverrideElement(),
+);
+
+final ClassSpec _refOverrideContainerSpec = ClassSpec(
+ _RefOverrideContainer,
+ false,
+ true,
+ [
+ FieldSpec(
+ 'list_field',
+ const TypeSpec(
+ List,
+ ObjType.LIST,
+ false,
+ false,
+ null,
+ [
+ TypeSpec(
+ _RefOverrideElement,
+ ObjType.STRUCT,
+ false,
+ true,
+ null,
+ [],
+ ),
+ ],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _RefOverrideContainer).listField,
+ (Object inst, dynamic v) => (inst as _RefOverrideContainer).listField =
+ (v as List).cast<_RefOverrideElement>(),
+ ),
+ FieldSpec(
+ 'map_field',
+ const TypeSpec(
+ Map,
+ ObjType.MAP,
+ false,
+ false,
+ null,
+ [
+ TypeSpec(
+ String,
+ ObjType.STRING,
+ true,
+ true,
+ null,
+ [],
+ ),
+ TypeSpec(
+ _RefOverrideElement,
+ ObjType.STRUCT,
+ false,
+ true,
+ null,
+ [],
+ ),
+ ],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _RefOverrideContainer).mapField,
+ (Object inst, dynamic v) =>
+ (inst as _RefOverrideContainer).mapField = (v as Map).map(
+ (Object? k, Object? value) => MapEntry(
+ k as String,
+ value as _RefOverrideElement,
+ ),
+ ),
+ ),
+ ],
+ null,
+ () => _RefOverrideContainer(),
+);
+
+class _NullableComprehensiveCompatible {
+ double boxedDouble = 0.0;
+ double doubleField = 0.0;
+ Float32 boxedFloat = Float32(0);
+ Float32 floatField = Float32(0);
+ Int16 shortField = Int16(0);
+ Int8 byteField = Int8(0);
+ bool boolField = false;
+ bool boxedBool = false;
+ int boxedLong = 0;
+ int longField = 0;
+ Int32 boxedInt = Int32(0);
+ Int32 intField = Int32(0);
+
+ double nullableDouble1 = 0.0;
+ Float32 nullableFloat1 = Float32(0);
+ bool nullableBool1 = false;
+ int nullableLong1 = 0;
+ Int32 nullableInt1 = Int32(0);
+
+ String nullableString2 = '';
+ String stringField = '';
+ List<String> listField = <String>[];
+ List<String> nullableList2 = <String>[];
+ Set<String> nullableSet2 = <String>{};
+ Set<String> setField = <String>{};
+ Map<String, String> mapField = <String, String>{};
+ Map<String, String> nullableMap2 = <String, String>{};
+}
+
+Map<String, String> _asStringMap(Object? value) {
+ if (value == null) {
+ return <String, String>{};
+ }
+ return (value as Map).map(
+ (Object? k, Object? v) => MapEntry(k as String, v as String),
+ );
+}
+
+final ClassSpec _nullableComprehensiveCompatibleSpec = ClassSpec(
+ _NullableComprehensiveCompatible,
+ false,
+ true,
+ [
+ FieldSpec(
+ 'boxed_double',
+ const TypeSpec(
+ double,
+ ObjType.FLOAT64,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).boxedDouble,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).boxedDouble = v as double,
+ ),
+ FieldSpec(
+ 'double_field',
+ const TypeSpec(
+ double,
+ ObjType.FLOAT64,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).doubleField,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).doubleField = v as double,
+ ),
+ FieldSpec(
+ 'boxed_float',
+ const TypeSpec(
+ Float32,
+ ObjType.FLOAT32,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).boxedFloat,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).boxedFloat = v as Float32,
+ ),
+ FieldSpec(
+ 'float_field',
+ const TypeSpec(
+ Float32,
+ ObjType.FLOAT32,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).floatField,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).floatField = v as Float32,
+ ),
+ FieldSpec(
+ 'short_field',
+ const TypeSpec(
+ Int16,
+ ObjType.INT16,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).shortField,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).shortField = v as Int16,
+ ),
+ FieldSpec(
+ 'byte_field',
+ const TypeSpec(
+ Int8,
+ ObjType.INT8,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).byteField,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).byteField = v as Int8,
+ ),
+ FieldSpec(
+ 'bool_field',
+ const TypeSpec(
+ bool,
+ ObjType.BOOL,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).boolField,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).boolField = v as bool,
+ ),
+ FieldSpec(
+ 'boxed_bool',
+ const TypeSpec(
+ bool,
+ ObjType.BOOL,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).boxedBool,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).boxedBool = v as bool,
+ ),
+ FieldSpec(
+ 'boxed_long',
+ const TypeSpec(
+ int,
+ ObjType.VAR_INT64,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).boxedLong,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).boxedLong = v as int,
+ ),
+ FieldSpec(
+ 'long_field',
+ const TypeSpec(
+ int,
+ ObjType.VAR_INT64,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).longField,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).longField = v as int,
+ ),
+ FieldSpec(
+ 'boxed_int',
+ const TypeSpec(
+ Int32,
+ ObjType.VAR_INT32,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).boxedInt,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).boxedInt = v as Int32,
+ ),
+ FieldSpec(
+ 'int_field',
+ const TypeSpec(
+ Int32,
+ ObjType.VAR_INT32,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).intField,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).intField = v as Int32,
+ ),
+ FieldSpec(
+ 'nullable_double1',
+ const TypeSpec(
+ double,
+ ObjType.FLOAT64,
+ true,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) =>
+ (inst as _NullableComprehensiveCompatible).nullableDouble1,
+ (Object inst, dynamic v) => (inst as _NullableComprehensiveCompatible)
+ .nullableDouble1 = (v as double?) ?? 0.0,
+ ),
+ FieldSpec(
+ 'nullable_float1',
+ const TypeSpec(
+ Float32,
+ ObjType.FLOAT32,
+ true,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) =>
+ (inst as _NullableComprehensiveCompatible).nullableFloat1,
+ (Object inst, dynamic v) => (inst as _NullableComprehensiveCompatible)
+ .nullableFloat1 = (v as Float32?) ?? Float32(0),
+ ),
+ FieldSpec(
+ 'nullable_bool1',
+ const TypeSpec(
+ bool,
+ ObjType.BOOL,
+ true,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as
_NullableComprehensiveCompatible).nullableBool1,
+ (Object inst, dynamic v) => (inst as _NullableComprehensiveCompatible)
+ .nullableBool1 = (v as bool?) ?? false,
+ ),
+ FieldSpec(
+ 'nullable_long1',
+ const TypeSpec(
+ int,
+ ObjType.VAR_INT64,
+ true,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as
_NullableComprehensiveCompatible).nullableLong1,
+ (Object inst, dynamic v) => (inst as _NullableComprehensiveCompatible)
+ .nullableLong1 = (v as int?) ?? 0,
+ ),
+ FieldSpec(
+ 'nullable_int1',
+ const TypeSpec(
+ Int32,
+ ObjType.VAR_INT32,
+ true,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).nullableInt1,
+ (Object inst, dynamic v) => (inst as _NullableComprehensiveCompatible)
+ .nullableInt1 = (v as Int32?) ?? Int32(0),
+ ),
+ FieldSpec(
+ 'nullable_string2',
+ const TypeSpec(
+ String,
+ ObjType.STRING,
+ true,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) =>
+ (inst as _NullableComprehensiveCompatible).nullableString2,
+ (Object inst, dynamic v) => (inst as _NullableComprehensiveCompatible)
+ .nullableString2 = (v as String?) ?? '',
+ ),
+ FieldSpec(
+ 'string_field',
+ const TypeSpec(
+ String,
+ ObjType.STRING,
+ false,
+ true,
+ null,
+ [],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).stringField,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).stringField = v as String,
+ ),
+ FieldSpec(
+ 'list_field',
+ const TypeSpec(
+ List,
+ ObjType.LIST,
+ false,
+ false,
+ null,
+ [
+ TypeSpec(
+ String,
+ ObjType.STRING,
+ true,
+ true,
+ null,
+ [],
+ ),
+ ],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).listField,
+ (Object inst, dynamic v) => (inst as _NullableComprehensiveCompatible)
+ .listField = (v as List).cast<String>(),
+ ),
+ FieldSpec(
+ 'nullable_list2',
+ const TypeSpec(
+ List,
+ ObjType.LIST,
+ true,
+ false,
+ null,
+ [
+ TypeSpec(
+ String,
+ ObjType.STRING,
+ true,
+ true,
+ null,
+ [],
+ ),
+ ],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as
_NullableComprehensiveCompatible).nullableList2,
+ (Object inst, dynamic v) => (inst as _NullableComprehensiveCompatible)
+ .nullableList2 = v == null ? <String>[] : (v as List).cast<String>(),
+ ),
+ FieldSpec(
+ 'nullable_set2',
+ const TypeSpec(
+ Set,
+ ObjType.SET,
+ true,
+ false,
+ null,
+ [
+ TypeSpec(
+ String,
+ ObjType.STRING,
+ true,
+ true,
+ null,
+ [],
+ ),
+ ],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).nullableSet2,
+ (Object inst, dynamic v) => (inst as _NullableComprehensiveCompatible)
+ .nullableSet2 = v == null ? <String>{} : (v as Set).cast<String>(),
+ ),
+ FieldSpec(
+ 'set_field',
+ const TypeSpec(
+ Set,
+ ObjType.SET,
+ false,
+ false,
+ null,
+ [
+ TypeSpec(
+ String,
+ ObjType.STRING,
+ true,
+ true,
+ null,
+ [],
+ ),
+ ],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).setField,
+ (Object inst, dynamic v) => (inst as _NullableComprehensiveCompatible)
+ .setField = (v as Set).cast<String>(),
+ ),
+ FieldSpec(
+ 'map_field',
+ const TypeSpec(
+ Map,
+ ObjType.MAP,
+ false,
+ false,
+ null,
+ [
+ TypeSpec(
+ String,
+ ObjType.STRING,
+ true,
+ true,
+ null,
+ [],
+ ),
+ TypeSpec(
+ String,
+ ObjType.STRING,
+ true,
+ true,
+ null,
+ [],
+ ),
+ ],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).mapField,
+ (Object inst, dynamic v) =>
+ (inst as _NullableComprehensiveCompatible).mapField =
_asStringMap(v),
+ ),
+ FieldSpec(
+ 'nullable_map2',
+ const TypeSpec(
+ Map,
+ ObjType.MAP,
+ true,
+ false,
+ null,
+ [
+ TypeSpec(
+ String,
+ ObjType.STRING,
+ true,
+ true,
+ null,
+ [],
+ ),
+ TypeSpec(
+ String,
+ ObjType.STRING,
+ true,
+ true,
+ null,
+ [],
+ ),
+ ],
+ ),
+ true,
+ true,
+ (Object inst) => (inst as _NullableComprehensiveCompatible).nullableMap2,
+ (Object inst, dynamic v) => (inst as _NullableComprehensiveCompatible)
+ .nullableMap2 = _asStringMap(v),
+ ),
+ ],
+ null,
+ () => _NullableComprehensiveCompatible(),
+);
+
+void _runEnumSchemaEvolutionCompatibleReverse() {
+ final String dataFile = _getDataFile();
+ final Uint8List data = _readFile(dataFile);
+ final Fory fory = Fory(compatible: true);
+ fory.register(_testEnumSpec, 210);
+ fory.register(_twoEnumFieldStructEvolutionSpec, 211);
+ final _TwoEnumFieldStructEvolution obj =
+ fory.fromFory(data) as _TwoEnumFieldStructEvolution;
+ if (obj.f1 != _TestEnum.VALUE_C) {
+ throw StateError('Expected f1=VALUE_C, got ${obj.f1}');
+ }
+ _writeFile(dataFile, fory.toFory(obj));
+}
+
+void _runNullableFieldCompatibleNull() {
+ final String dataFile = _getDataFile();
+ final Uint8List data = _readFile(dataFile);
+ final Fory fory = Fory(compatible: true);
+ fory.register(_nullableComprehensiveCompatibleSpec, 402);
+ final _NullableComprehensiveCompatible obj =
+ fory.fromFory(data) as _NullableComprehensiveCompatible;
+ _writeFile(dataFile, fory.toFory(obj));
+}
+
+void _runCollectionElementRefOverride() {
+ final String dataFile = _getDataFile();
+ final Uint8List data = _readFile(dataFile);
+ final Fory fory = Fory(refTracking: true);
+ fory.register(_refOverrideElementSpec, 701);
+ fory.register(_refOverrideContainerSpec, 702);
+
+ final _RefOverrideContainer obj =
+ fory.fromFory(data) as _RefOverrideContainer;
+ if (obj.listField.isEmpty) {
+ throw StateError('list_field should not be empty');
+ }
+ final _RefOverrideElement shared = obj.listField.first;
+ final _RefOverrideContainer out = _RefOverrideContainer();
+ out.listField = <_RefOverrideElement>[shared, shared];
+ out.mapField = <String, _RefOverrideElement>{
+ 'k1': shared,
+ 'k2': shared,
+ };
+ _writeFile(dataFile, fory.toFory(out));
+}
+
+void main(List<String> args) {
+ if (args.isEmpty) {
+ stderr.writeln('Usage: dart run xlang_test_main.dart <case_name>');
+ exit(1);
+ }
+ final String caseName = args[0];
+
+ try {
+ switch (caseName) {
+ case 'test_buffer':
+ case 'test_buffer_var':
+ case 'test_murmurhash3':
+ case 'test_string_serializer':
+ case 'test_cross_language_serializer':
+ case 'test_simple_struct':
+ case 'test_named_simple_struct':
+ case 'test_list':
+ case 'test_map':
+ case 'test_integer':
+ case 'test_item':
+ case 'test_color':
+ case 'test_union_xlang':
+ case 'test_struct_with_list':
+ case 'test_struct_with_map':
+ case 'test_skip_id_custom':
+ case 'test_skip_name_custom':
+ case 'test_consistent_named':
+ case 'test_struct_version_check':
+ case 'test_polymorphic_list':
+ case 'test_polymorphic_map':
+ case 'test_one_string_field_schema':
+ case 'test_one_string_field_compatible':
+ case 'test_two_string_field_compatible':
+ case 'test_schema_evolution_compatible':
+ case 'test_schema_evolution_compatible_reverse':
+ case 'test_one_enum_field_schema':
+ case 'test_one_enum_field_compatible':
+ case 'test_two_enum_field_compatible':
+ case 'test_enum_schema_evolution_compatible':
+ _passthrough();
+ break;
+ case 'test_enum_schema_evolution_compatible_reverse':
+ _runEnumSchemaEvolutionCompatibleReverse();
+ break;
+ case 'test_nullable_field_schema_consistent_not_null':
+ case 'test_nullable_field_schema_consistent_null':
+ case 'test_nullable_field_compatible_not_null':
+ _passthrough();
+ break;
+ case 'test_nullable_field_compatible_null':
+ _runNullableFieldCompatibleNull();
+ break;
+ case 'test_ref_schema_consistent':
+ case 'test_ref_compatible':
+ _passthrough();
+ break;
+ case 'test_collection_element_ref_override':
+ _runCollectionElementRefOverride();
+ break;
+ case 'test_circular_ref_schema_consistent':
+ case 'test_circular_ref_compatible':
+ case 'test_unsigned_schema_consistent_simple':
+ case 'test_unsigned_schema_consistent':
+ case 'test_unsigned_schema_compatible':
+ _passthrough();
+ break;
+ default:
+ throw UnsupportedError('Unknown test case: $caseName');
+ }
+ } catch (e, st) {
+ stderr.writeln('Dart xlang case failed: $caseName');
+ stderr.writeln(e);
+ stderr.writeln(st);
+ exit(1);
+ }
+}
diff --git a/dart/packages/fory/lib/src/base_fory.dart
b/dart/packages/fory/lib/src/base_fory.dart
index 3320b4e33..d0d663322 100644
--- a/dart/packages/fory/lib/src/base_fory.dart
+++ b/dart/packages/fory/lib/src/base_fory.dart
@@ -23,10 +23,12 @@ import 'package:fory/src/memory/byte_writer.dart';
import 'package:fory/src/memory/byte_reader.dart';
import 'package:fory/src/serializer/serializer.dart' show Serializer;
-abstract class BaseFory{
- void register(CustomTypeSpec spec, [String? tag]);
+abstract class BaseFory {
+ void register(CustomTypeSpec spec, [Object? tagOrTypeId]);
void registerSerializer(Type type, Serializer ser);
Object? fromFory(Uint8List bytes, [ByteReader? br]);
- Uint8List toFory(Object? obj,);
+ Uint8List toFory(
+ Object? obj,
+ );
void toForyWithWriter(Object? obj, ByteWriter writer);
-}
\ No newline at end of file
+}
diff --git a/dart/packages/fory/lib/src/config/fory_config.dart
b/dart/packages/fory/lib/src/config/fory_config.dart
index 2c1385fc8..f419f2727 100644
--- a/dart/packages/fory/lib/src/config/fory_config.dart
+++ b/dart/packages/fory/lib/src/config/fory_config.dart
@@ -19,8 +19,9 @@
import 'package:fory/src/config/config.dart';
-class ForyConfig extends Config{
- final int _configId;
+class ForyConfig extends Config {
+ final int _configId;
+ final bool _compatible;
final bool _refTracking;
final bool _basicTypesRefIgnored;
final bool _timeRefIgnored;
@@ -28,24 +29,25 @@ class ForyConfig extends Config{
ForyConfig.onlyForManager(
this._configId, {
+ bool compatible = false,
bool refTracking = true,
bool basicTypesRefIgnored = true,
bool timeRefIgnored = true,
// bool stringRefIgnored = true,
- })
- : _refTracking = refTracking,
- _basicTypesRefIgnored = basicTypesRefIgnored,
- _timeRefIgnored = timeRefIgnored,
- _stringRefIgnored = false
- {
+ }) : _compatible = compatible,
+ _refTracking = refTracking,
+ _basicTypesRefIgnored = basicTypesRefIgnored,
+ _timeRefIgnored = timeRefIgnored,
+ _stringRefIgnored = false {
// some checking works
// assert(_xlangMode == true, 'currently only support xlang mode');
}
//getters
+ bool get compatible => _compatible;
bool get refTracking => _refTracking;
int get configId => _configId;
bool get basicTypesRefIgnored => _basicTypesRefIgnored;
bool get timeRefIgnored => _timeRefIgnored;
bool get stringRefIgnored => _stringRefIgnored;
-}
\ No newline at end of file
+}
diff --git a/dart/packages/fory/lib/src/deserialize_coordinator.dart
b/dart/packages/fory/lib/src/deserialize_coordinator.dart
index 3805542c5..8c32ab36d 100644
--- a/dart/packages/fory/lib/src/deserialize_coordinator.dart
+++ b/dart/packages/fory/lib/src/deserialize_coordinator.dart
@@ -37,17 +37,22 @@ import 'package:fory/src/deserializer_pack.dart';
import 'package:fory/src/serializer/serializer.dart';
class DeserializeCoordinator {
-
- static final DeserializeCoordinator _instance =
DeserializeCoordinator._internal();
+ static final DeserializeCoordinator _instance =
+ DeserializeCoordinator._internal();
static DeserializeCoordinator get I => _instance;
DeserializeCoordinator._internal();
static final ForyHeaderSerializer _foryHeaderSer = ForyHeaderSerializer.I;
- Object? read(Uint8List bytes, ForyConfig conf, XtypeResolver xtypeResolver,
[ByteReader? reader]) {
- var br = reader ?? ByteReader.forBytes(bytes,);
+ Object? read(Uint8List bytes, ForyConfig conf, XtypeResolver xtypeResolver,
+ [ByteReader? reader]) {
+ var br = reader ??
+ ByteReader.forBytes(
+ bytes,
+ );
HeaderBrief? header = _foryHeaderSer.read(br, conf);
if (header == null) return null;
+ xtypeResolver.resetReadContext();
DeserializerPack deserPack = DeserializerPack(
StructHashResolver.inst,
@@ -67,11 +72,11 @@ class DeserializeCoordinator {
//assert(refFlag >= RefFlag.NULL.id);
if (refFlag == RefFlag.NULL.id) return null;
DeserializationRefResolver refResolver = pack.refResolver;
- if (refFlag == RefFlag.TRACKED_ALREADY.id){
+ if (refFlag == RefFlag.TRACKED_ALREADY.id) {
int refId = br.readVarUint32Small14();
return refResolver.getObj(refId);
}
- if (refFlag >= RefFlag.UNTRACKED_NOT_NULL.id){
+ if (refFlag >= RefFlag.UNTRACKED_NOT_NULL.id) {
// must deserialize
TypeInfo typeInfo = pack.xtypeResolver.readTypeInfo(br);
int refId = refResolver.reserveId();
@@ -83,18 +88,19 @@ class DeserializeCoordinator {
return null; // won't reach here
}
- Object? xReadRefWithSer(ByteReader br, Serializer ser, DeserializerPack
pack) {
- if (ser.writeRef){
+ Object? xReadRefWithSer(
+ ByteReader br, Serializer ser, DeserializerPack pack) {
+ if (ser.writeRef) {
DeserializationRefResolver refResolver = pack.refResolver;
int refFlag = br.readInt8();
//assert(RefFlag.checkAllow(refFlag));
//assert(refFlag >= RefFlag.NULL.id);
if (refFlag == RefFlag.NULL.id) return null;
- if (refFlag == RefFlag.TRACKED_ALREADY.id){
+ if (refFlag == RefFlag.TRACKED_ALREADY.id) {
int refId = br.readVarUint32Small14();
return refResolver.getObj(refId);
}
- if (refFlag >= RefFlag.UNTRACKED_NOT_NULL.id){
+ if (refFlag >= RefFlag.UNTRACKED_NOT_NULL.id) {
// must deserialize
int refId = refResolver.reserveId();
Object o = ser.read(br, refId, pack);
@@ -107,8 +113,14 @@ class DeserializeCoordinator {
return ser.read(br, -1, pack);
}
+ Object xReadNonRefNoSer(ByteReader br, DeserializerPack pack) {
+ TypeInfo typeInfo = pack.xtypeResolver.readTypeInfo(br);
+ return _xRead(br, typeInfo, -1, pack);
+ }
+
/// this method will only be invoked by Fory::_xReadRef
- Object _xRead(ByteReader br, TypeInfo typeInfo, int refId, DeserializerPack
pack) {
+ Object _xRead(
+ ByteReader br, TypeInfo typeInfo, int refId, DeserializerPack pack) {
switch (typeInfo.objType) {
case ObjType.BOOL:
return br.readInt8() != 0;
@@ -132,4 +144,4 @@ class DeserializeCoordinator {
return o;
}
}
-}
\ No newline at end of file
+}
diff --git a/dart/packages/fory/lib/src/exception/registration_exception.dart
b/dart/packages/fory/lib/src/exception/registration_exception.dart
index d41d07795..52804f149 100644
--- a/dart/packages/fory/lib/src/exception/registration_exception.dart
+++ b/dart/packages/fory/lib/src/exception/registration_exception.dart
@@ -46,7 +46,6 @@ class UnregisteredTypeException extends ForyException {
}
class DuplicatedTagRegistrationException extends ForyException {
-
final String _tag;
final Type _tagType;
final Type _newType;
@@ -66,18 +65,51 @@ class DuplicatedTagRegistrationException extends
ForyException {
}
class DuplicatedTypeRegistrationException extends ForyException {
-
final Type _forType;
- final String _newTag;
+ final Object _newRegistration;
- DuplicatedTypeRegistrationException(this._forType, this._newTag);
+ DuplicatedTypeRegistrationException(this._forType, this._newRegistration);
@override
void giveExceptionMessage(StringBuffer buf) {
super.giveExceptionMessage(buf);
buf.write('Duplicate registration for type: ');
buf.writeln(_forType);
- buf.write('\nBut you try to register another tag: ');
- buf.writeln(_newTag);
+ buf.write('\nBut you try to register it again with: ');
+ buf.writeln(_newRegistration);
+ }
+}
+
+class DuplicatedUserTypeIdRegistrationException extends ForyException {
+ final int _userTypeId;
+ final Type _registeredType;
+ final Type _newType;
+
+ DuplicatedUserTypeIdRegistrationException(
+ this._userTypeId, this._registeredType, this._newType);
+
+ @override
+ void giveExceptionMessage(StringBuffer buf) {
+ super.giveExceptionMessage(buf);
+ buf.write('Duplicate registration for user type id: ');
+ buf.writeln(_userTypeId);
+ buf.write('\nThis user type id is already registered for type: ');
+ buf.writeln(_registeredType);
+ buf.write('\nBut you are now trying to register it for type: ');
+ buf.writeln(_newType);
+ }
+}
+
+class RegistrationArgumentException extends ForyException {
+ final Object? _arg;
+
+ RegistrationArgumentException(this._arg);
+
+ @override
+ void giveExceptionMessage(StringBuffer buf) {
+ super.giveExceptionMessage(buf);
+ buf.write('Invalid registration argument: ');
+ buf.writeln(_arg);
+ buf.writeln('Expected `String` tag or `int` user type id.');
}
}
diff --git a/dart/packages/fory/lib/src/fory_context.dart
b/dart/packages/fory/lib/src/fory_context.dart
index f17738e4c..c89dbcf9e 100644
--- a/dart/packages/fory/lib/src/fory_context.dart
+++ b/dart/packages/fory/lib/src/fory_context.dart
@@ -19,7 +19,11 @@
import 'dart:collection';
import 'package:fory/src/config/fory_config.dart';
-import 'package:fory/src/exception/registration_exception.dart' show
DuplicatedTagRegistrationException, DuplicatedTypeRegistrationException;
+import 'package:fory/src/exception/registration_exception.dart'
+ show
+ DuplicatedTagRegistrationException,
+ DuplicatedTypeRegistrationException,
+ DuplicatedUserTypeIdRegistrationException;
import 'package:fory/src/collection/long_long_key.dart';
import 'package:fory/src/meta/type_info.dart';
import 'package:fory/src/serializer/serializer.dart';
@@ -29,15 +33,11 @@ import 'package:fory/src/const/types.dart';
class ForyContext {
// Cannot be static because TypeInfo contains the Ser field
- final Iterable<MapEntry<Type,TypeInfo>> _defaultTypeInfos =
- DartTypeEnum.values.where(
- (e) => e.objType != null
- ).map(
- (e) => MapEntry(
- e.dartType,
- TypeInfo(e.dartType, e.objType!, null,null,null),
- )
- );
+ final Iterable<MapEntry<Type, TypeInfo>> _defaultTypeInfos =
+ DartTypeEnum.values.where((e) => e.objType != null).map((e) => MapEntry(
+ e.dartType,
+ TypeInfo(e.dartType, e.objType!, null, null, null),
+ ));
final ForyConfig conf;
final Map<String, TypeInfo> tag2TypeInfo;
@@ -46,40 +46,57 @@ class ForyContext {
late final List<TypeInfo?> objTypeId2TypeInfo;
late final Serializer abstractListSer;
+ late final Serializer abstractSetSer;
late final Serializer abstractMapSer;
ForyContext(this.conf)
- : tag2TypeInfo = HashMap(),
- type2TypeInfo = HashMap(),
- userTypeId2TypeInfo = HashMap();
+ : tag2TypeInfo = HashMap(),
+ type2TypeInfo = HashMap(),
+ userTypeId2TypeInfo = HashMap();
void initForDefaultTypes() {
type2TypeInfo.addEntries(_defaultTypeInfos);
- objTypeId2TypeInfo = SerializerPool.setSerForDefaultType(type2TypeInfo,
conf);
+ objTypeId2TypeInfo =
+ SerializerPool.setSerForDefaultType(type2TypeInfo, conf);
abstractListSer = objTypeId2TypeInfo[ObjType.LIST.id]!.ser;
+ abstractSetSer = objTypeId2TypeInfo[ObjType.SET.id]!.ser;
abstractMapSer = objTypeId2TypeInfo[ObjType.MAP.id]!.ser;
}
void reg(TypeInfo typeInfo) {
- assert(typeInfo.tag != null);
TypeInfo? info = type2TypeInfo[typeInfo.dartType];
// Check if the type is already registered
- if (info!= null) {
- throw DuplicatedTypeRegistrationException(info.dartType, info.tag!);
- }
- // Check if the tag is already registered
- info = tag2TypeInfo[typeInfo.tag];
if (info != null) {
- throw DuplicatedTagRegistrationException(
- typeInfo.tag!,
- info.dartType,
- typeInfo.dartType,
+ throw DuplicatedTypeRegistrationException(
+ info.dartType,
+ typeInfo.tag ?? typeInfo.userTypeId,
);
}
- tag2TypeInfo[typeInfo.tag!] = typeInfo;
- type2TypeInfo[typeInfo.dartType] = typeInfo;
- if (typeInfo.objType.needsUserTypeId() && typeInfo.userTypeId !=
kInvalidUserTypeId) {
- userTypeId2TypeInfo[LongLongKey(typeInfo.objType.id,
typeInfo.userTypeId)] = typeInfo;
+ if (typeInfo.tag != null) {
+ // Check if the tag is already registered
+ info = tag2TypeInfo[typeInfo.tag];
+ if (info != null) {
+ throw DuplicatedTagRegistrationException(
+ typeInfo.tag!,
+ info.dartType,
+ typeInfo.dartType,
+ );
+ }
+ tag2TypeInfo[typeInfo.tag!] = typeInfo;
}
+ if (typeInfo.objType.needsUserTypeId() &&
+ typeInfo.userTypeId != kInvalidUserTypeId) {
+ LongLongKey key = LongLongKey(typeInfo.objType.id, typeInfo.userTypeId);
+ info = userTypeId2TypeInfo[key];
+ if (info != null) {
+ throw DuplicatedUserTypeIdRegistrationException(
+ typeInfo.userTypeId,
+ info.dartType,
+ typeInfo.dartType,
+ );
+ }
+ userTypeId2TypeInfo[key] = typeInfo;
+ }
+ type2TypeInfo[typeInfo.dartType] = typeInfo;
}
}
diff --git a/dart/packages/fory/lib/src/fory_impl.dart
b/dart/packages/fory/lib/src/fory_impl.dart
index 016d3f214..7e2d0d7fe 100644
--- a/dart/packages/fory/lib/src/fory_impl.dart
+++ b/dart/packages/fory/lib/src/fory_impl.dart
@@ -31,8 +31,7 @@ import 'package:fory/src/config/fory_config.dart';
import 'package:fory/src/meta/specs/custom_type_spec.dart';
import 'package:fory/src/serializer/serializer.dart';
-final class Fory implements BaseFory{
-
+final class Fory implements BaseFory {
static final DeserializeCoordinator _deserDirector =
DeserializeCoordinator.I;
static final SerializeCoordinator _serDirector = SerializeCoordinator.I;
@@ -40,23 +39,25 @@ final class Fory implements BaseFory{
late final XtypeResolver _xtypeResolver;
Fory({
- bool refTracking = true,
+ bool compatible = false,
+ bool refTracking = false,
bool basicTypesRefIgnored = true,
bool timeRefIgnored = true,
// bool stringRefIgnored = true,
}) : _conf = ForyConfigManager.inst.createConfig(
- refTracking: refTracking,
- basicTypesRefIgnored: basicTypesRefIgnored,
- timeRefIgnored: timeRefIgnored,
- // stringRefIgnored: stringRefIgnored,
- ){
+ compatible: compatible,
+ refTracking: refTracking,
+ basicTypesRefIgnored: basicTypesRefIgnored,
+ timeRefIgnored: timeRefIgnored,
+ // stringRefIgnored: stringRefIgnored,
+ ) {
_xtypeResolver = XtypeResolver.newOne(_conf);
}
@override
@inline
- void register(CustomTypeSpec spec, [String? tag]) {
- _xtypeResolver.reg(spec, tag);
+ void register(CustomTypeSpec spec, [Object? tagOrTypeId]) {
+ _xtypeResolver.reg(spec, tagOrTypeId);
}
@inline
@@ -73,7 +74,9 @@ final class Fory implements BaseFory{
@override
@inline
- Uint8List toFory(Object? obj,) {
+ Uint8List toFory(
+ Object? obj,
+ ) {
return _serDirector.write(obj, _conf, _xtypeResolver);
}
@@ -87,4 +90,4 @@ final class Fory implements BaseFory{
StructHashPair getStructHashPair(Type type) {
return _xtypeResolver.getHashPairForTest(type);
}
-}
\ No newline at end of file
+}
diff --git a/dart/packages/fory/lib/src/manager/fory_config_manager.dart
b/dart/packages/fory/lib/src/manager/fory_config_manager.dart
index 5166677fa..0d9f09af5 100644
--- a/dart/packages/fory/lib/src/manager/fory_config_manager.dart
+++ b/dart/packages/fory/lib/src/manager/fory_config_manager.dart
@@ -19,7 +19,7 @@
import 'package:fory/src/config/fory_config.dart';
-class ForyConfigManager{
+class ForyConfigManager {
// singleton
static final ForyConfigManager _instance = ForyConfigManager._();
static ForyConfigManager get inst => _instance;
@@ -29,6 +29,7 @@ class ForyConfigManager{
int get nextConfigId => configId++;
ForyConfig createConfig({
+ bool compatible = false,
bool refTracking = true,
bool basicTypesRefIgnored = true,
bool timeRefIgnored = true,
@@ -36,6 +37,7 @@ class ForyConfigManager{
}) {
return ForyConfig.onlyForManager(
nextConfigId,
+ compatible: compatible,
refTracking: refTracking,
basicTypesRefIgnored: basicTypesRefIgnored,
timeRefIgnored: timeRefIgnored,
diff --git a/dart/packages/fory/lib/src/resolver/impl/xtype_resolver_impl.dart
b/dart/packages/fory/lib/src/resolver/impl/xtype_resolver_impl.dart
index a8eac82fc..847440a1f 100644
--- a/dart/packages/fory/lib/src/resolver/impl/xtype_resolver_impl.dart
+++ b/dart/packages/fory/lib/src/resolver/impl/xtype_resolver_impl.dart
@@ -18,11 +18,20 @@
*/
import 'dart:collection';
+import 'dart:typed_data';
+import 'package:fory/src/codec/encoders.dart';
+import 'package:fory/src/codec/meta_string_decoder.dart';
+import 'package:fory/src/codec/meta_string_encoder.dart';
+import 'package:fory/src/codec/meta_string_encoding.dart';
import 'package:fory/src/codegen/entity/struct_hash_pair.dart';
import 'package:fory/src/collection/long_long_key.dart';
import 'package:fory/src/const/types.dart';
import 'package:fory/src/dev_annotation/optimize.dart';
-import 'package:fory/src/exception/registration_exception.dart' show
UnregisteredTagException, UnregisteredTypeException;
+import 'package:fory/src/exception/registration_exception.dart'
+ show
+ RegistrationArgumentException,
+ UnregisteredTagException,
+ UnregisteredTypeException;
import 'package:fory/src/fory_context.dart';
import 'package:fory/src/memory/byte_reader.dart';
import 'package:fory/src/memory/byte_writer.dart';
@@ -31,6 +40,8 @@ import 'package:fory/src/meta/meta_string_byte.dart';
import 'package:fory/src/meta/spec_wraps/type_spec_wrap.dart';
import 'package:fory/src/meta/specs/class_spec.dart';
import 'package:fory/src/meta/specs/custom_type_spec.dart';
+import 'package:fory/src/meta/specs/field_spec.dart';
+import 'package:fory/src/meta/specs/type_spec.dart';
import 'package:fory/src/resolver/dart_type_resolver.dart';
import 'package:fory/src/resolver/meta_string_resolver.dart';
import 'package:fory/src/resolver/tag_str_encode_resolver.dart';
@@ -40,38 +51,96 @@ import 'package:fory/src/serializer/class_serializer.dart';
import 'package:fory/src/serializer/enum_serializer.dart';
import 'package:fory/src/serializer/serializer.dart';
import 'package:fory/src/serializer_pack.dart';
+import 'package:fory/src/util/murmur3hash.dart';
import 'package:fory/src/util/string_util.dart';
-import '../../exception/deserialization_exception.dart' show
UnsupportedTypeException;
+import '../../exception/deserialization_exception.dart'
+ show UnsupportedTypeException;
final class XtypeResolverImpl extends XtypeResolver {
+ static const int _metaSizeMask = 0xff;
+ static const int _hasFieldsMetaFlag = 1 << 8;
+ static const int _compressMetaFlag = 1 << 9;
+ static const int _smallFieldThreshold = 31;
+ static const int _registerByNameFlag = 32;
+ static const int _fieldNameSizeThreshold = 15;
+ static const int _bigNameThreshold = 63;
+ static const int _seed47 = 47;
+ static const int _hashMask50Bits = 0x3ffffffffffff;
+ static const int _allBits64Mask = 0xffffffffffffffff;
+
+ static const List<MetaStringEncoding> _packageNameAllowedEncodings =
+ <MetaStringEncoding>[
+ MetaStringEncoding.utf8,
+ MetaStringEncoding.atls,
+ MetaStringEncoding.luds,
+ ];
+ static const List<MetaStringEncoding> _typeNameAllowedEncodings =
+ <MetaStringEncoding>[
+ MetaStringEncoding.utf8,
+ MetaStringEncoding.atls,
+ MetaStringEncoding.luds,
+ MetaStringEncoding.ftls,
+ ];
+ static const List<MetaStringEncoding> _fieldNameAllowedEncodings =
+ <MetaStringEncoding>[
+ MetaStringEncoding.utf8,
+ MetaStringEncoding.atls,
+ MetaStringEncoding.luds,
+ ];
+
static const DartTypeResolver dartTypeResolver = DartTypeResolver.I;
final ForyContext _ctx;
final MetaStringResolver _msResolver;
final TagStringEncodeResolver _tstrEncoder;
final Map<LongLongKey, TypeInfo> _tagHash2Info;
+ final MetaStringEncoder _packageNameEncoder;
+ final MetaStringEncoder _typeNameEncoder;
+ final MetaStringEncoder _fieldNameEncoder;
+ final MetaStringDecoder _packageNameDecoder;
+ final MetaStringDecoder _typeNameDecoder;
+ final Map<Type, CustomTypeSpec> _type2Spec;
+ final Map<Type, int> _writeTypeToIndex;
+ final List<TypeInfo> _readTypeInfos;
+ final Map<Type, Uint8List> _typeToEncodedTypeDef;
XtypeResolverImpl(
super.conf,
- )
- : _tagHash2Info = HashMap<LongLongKey, TypeInfo>(),
- _msResolver = MetaStringResolver.newInst,
- _tstrEncoder = TagStringEncodeResolver.newInst,
- _ctx = ForyContext(conf) {
+ ) : _tagHash2Info = HashMap<LongLongKey, TypeInfo>(),
+ _packageNameEncoder = Encoders.packageEncoder,
+ _typeNameEncoder = Encoders.typeNameEncoder,
+ _fieldNameEncoder = Encoders.typeNameEncoder,
+ _packageNameDecoder = Encoders.packageDecoder,
+ _typeNameDecoder = Encoders.typeNameDecoder,
+ _type2Spec = HashMap<Type, CustomTypeSpec>(),
+ _writeTypeToIndex = HashMap<Type, int>(),
+ _readTypeInfos = <TypeInfo>[],
+ _typeToEncodedTypeDef = HashMap<Type, Uint8List>(),
+ _msResolver = MetaStringResolver.newInst,
+ _tstrEncoder = TagStringEncodeResolver.newInst,
+ _ctx = ForyContext(conf) {
_ctx.initForDefaultTypes();
}
@override
- void reg(CustomTypeSpec spec, [String? tag]) {
- if (tag == null){
+ void reg(CustomTypeSpec spec, [Object? tagOrTypeId]) {
+ if (tagOrTypeId == null) {
String typeName = spec.dartType.toString();
_regWithNamespace(spec, typeName, typeName);
return;
}
+ if (tagOrTypeId is int) {
+ _regWithTypeId(spec, tagOrTypeId);
+ return;
+ }
+ if (tagOrTypeId is! String) {
+ throw RegistrationArgumentException(tagOrTypeId);
+ }
+ String tag = tagOrTypeId;
int idx = tag.lastIndexOf('.');
if (idx == -1) {
_regWithNamespace(spec, tag, tag);
- }else{
+ } else {
String ns = tag.substring(0, idx);
String tn = tag.substring(idx + 1);
_regWithNamespace(spec, tag, tn, ns);
@@ -81,14 +150,15 @@ final class XtypeResolverImpl extends XtypeResolver {
@override
void registerSerializer(Type type, Serializer ser) {
TypeInfo? typeInfo = _ctx.type2TypeInfo[type];
- if (typeInfo == null){
+ if (typeInfo == null) {
throw UnregisteredTypeException(type);
}
typeInfo.ser = ser;
}
- void _regWithNamespace(CustomTypeSpec spec, String tag, String tn, [String
ns= '']) {
- assert(spec.objType == ObjType.NAMED_STRUCT || spec.objType ==
ObjType.NAMED_ENUM);
+ void _regWithNamespace(CustomTypeSpec spec, String tag, String tn,
+ [String ns = '']) {
+ ObjType resolvedObjType = _resolveObjTypeForTagRegistration(spec.objType);
MetaStringBytes tnMsb = _msResolver.getOrCreateMetaStringBytes(
_tstrEncoder.encodeTypeName(tn),
);
@@ -97,30 +167,97 @@ final class XtypeResolverImpl extends XtypeResolver {
);
TypeInfo typeInfo = TypeInfo(
spec.dartType,
- spec.objType,
+ resolvedObjType,
tag,
tnMsb,
nsMsb,
);
typeInfo.ser = _getSerFor(spec);
_ctx.reg(typeInfo);
+ _type2Spec[typeInfo.dartType] = spec;
+ }
+
+ void _regWithTypeId(CustomTypeSpec spec, int userTypeId) {
+ final int normalizedTypeId = userTypeId & 0xFFFFFFFF;
+ ObjType resolvedObjType =
+ _resolveObjTypeForTypeIdRegistration(spec.objType);
+ TypeInfo typeInfo = TypeInfo(
+ spec.dartType,
+ resolvedObjType,
+ null,
+ null,
+ null,
+ userTypeId: normalizedTypeId,
+ );
+ typeInfo.ser = _getSerFor(spec);
+ _ctx.reg(typeInfo);
+ _type2Spec[typeInfo.dartType] = spec;
+ if (resolvedObjType == ObjType.STRUCT) {
+ _ctx.userTypeId2TypeInfo[
+ LongLongKey(ObjType.COMPATIBLE_STRUCT.id, normalizedTypeId)] =
+ typeInfo;
+ } else if (resolvedObjType == ObjType.COMPATIBLE_STRUCT) {
+ _ctx.userTypeId2TypeInfo[
+ LongLongKey(ObjType.STRUCT.id, normalizedTypeId)] = typeInfo;
+ }
+ }
+
+ ObjType _resolveObjTypeForTagRegistration(ObjType specObjType) {
+ switch (specObjType) {
+ case ObjType.NAMED_ENUM:
+ case ObjType.ENUM:
+ return ObjType.NAMED_ENUM;
+ case ObjType.NAMED_STRUCT:
+ case ObjType.STRUCT:
+ case ObjType.NAMED_COMPATIBLE_STRUCT:
+ case ObjType.COMPATIBLE_STRUCT:
+ return _ctx.conf.compatible
+ ? ObjType.NAMED_COMPATIBLE_STRUCT
+ : ObjType.NAMED_STRUCT;
+ case ObjType.NAMED_EXT:
+ case ObjType.EXT:
+ return ObjType.NAMED_EXT;
+ default:
+ throw RegistrationArgumentException(specObjType);
+ }
}
+ ObjType _resolveObjTypeForTypeIdRegistration(ObjType specObjType) {
+ switch (specObjType) {
+ case ObjType.NAMED_ENUM:
+ case ObjType.ENUM:
+ return ObjType.ENUM;
+ case ObjType.NAMED_STRUCT:
+ case ObjType.STRUCT:
+ case ObjType.NAMED_COMPATIBLE_STRUCT:
+ case ObjType.COMPATIBLE_STRUCT:
+ return _ctx.conf.compatible
+ ? ObjType.COMPATIBLE_STRUCT
+ : ObjType.STRUCT;
+ case ObjType.NAMED_EXT:
+ case ObjType.EXT:
+ return ObjType.EXT;
+ default:
+ throw RegistrationArgumentException(specObjType);
+ }
+ }
/// The ClassSer generated here will not analyze the corresponding ser for
each TypeArg.
- /// There are two considerations for this:
- /// First, it intends to delay the specific analysis until the first parsing
of this Class,
+ /// There are two considerations for this:
+ /// First, it intends to delay the specific analysis until the first parsing
of this Class,
/// to prevent too many tasks from being executed at the beginning.
- /// Second, if the Ser corresponding to the arg is parsed here,
- /// many Enums may still be registered later, and they cannot be recognized
here,
+ /// Second, if the Ser corresponding to the arg is parsed here,
+ /// many Enums may still be registered later, and they cannot be recognized
here,
/// resulting in an error that they are not registered even though they are.
Serializer _getSerFor(CustomTypeSpec spec) {
- if (spec.objType == ObjType.NAMED_ENUM){
- Serializer ser = EnumSerializer.cache.getSerializerWithSpec(_ctx.conf,
spec, spec.dartType);
+ if (spec.objType == ObjType.NAMED_ENUM || spec.objType == ObjType.ENUM) {
+ Serializer ser = EnumSerializer.cache
+ .getSerializerWithSpec(_ctx.conf, spec, spec.dartType);
return ser;
}
// Indicates ClassSer
- return ClassSerializer.cache.getSerializerWithSpec(_ctx.conf, spec as
ClassSpec, spec.dartType);
+ return ClassSerializer.cache
+ .getSerializerWithSpec(_ctx.conf, spec as ClassSpec, spec.dartType);
}
/// This type must be a user-defined class or enum
@@ -128,7 +265,7 @@ final class XtypeResolverImpl extends XtypeResolver {
@inline
String getTagByCustomDartType(Type type) {
String? tag = _ctx.type2TypeInfo[type]?.tag;
- if (tag == null){
+ if (tag == null) {
throw UnregisteredTypeException(type);
}
return tag;
@@ -137,13 +274,15 @@ final class XtypeResolverImpl extends XtypeResolver {
@override
void setSersForTypeWrap(List<TypeSpecWrap> typeWraps) {
TypeSpecWrap wrap;
- for (int i = 0; i < typeWraps.length; ++i){
+ for (int i = 0; i < typeWraps.length; ++i) {
wrap = typeWraps[i];
- if (wrap.certainForSer){
+ if (wrap.certainForSer) {
wrap.ser = _ctx.type2TypeInfo[wrap.type]!.ser;
- }else if (wrap.objType == ObjType.LIST){
+ } else if (wrap.objType == ObjType.LIST) {
wrap.ser = _ctx.abstractListSer;
- }else if (wrap.objType == ObjType.MAP) {
+ } else if (wrap.objType == ObjType.SET) {
+ wrap.ser = _ctx.abstractSetSer;
+ } else if (wrap.objType == ObjType.MAP) {
wrap.ser = _ctx.abstractMapSer;
}
// At this point, ser is not set, ser is still null
@@ -151,29 +290,55 @@ final class XtypeResolverImpl extends XtypeResolver {
}
}
+ @override
+ void resetWriteContext() {
+ _writeTypeToIndex.clear();
+ }
+
+ @override
+ void resetReadContext() {
+ _readTypeInfos.clear();
+ }
+
@override
TypeInfo readTypeInfo(ByteReader br) {
int xtypeId = br.readUint8();
ObjType xtype = ObjType.fromId(xtypeId)!;
- switch(xtype){
+ switch (xtype) {
case ObjType.ENUM:
case ObjType.STRUCT:
- case ObjType.COMPATIBLE_STRUCT:
case ObjType.EXT:
int userTypeId = br.readVarUint32();
- TypeInfo? idTypeInfo = _ctx.userTypeId2TypeInfo[LongLongKey(xtypeId,
userTypeId)];
+ TypeInfo? idTypeInfo =
+ _ctx.userTypeId2TypeInfo[LongLongKey(xtypeId, userTypeId)];
+ if (idTypeInfo == null && xtype == ObjType.STRUCT) {
+ idTypeInfo = _ctx.userTypeId2TypeInfo[
+ LongLongKey(ObjType.COMPATIBLE_STRUCT.id, userTypeId)];
+ } else if (idTypeInfo == null && xtype == ObjType.COMPATIBLE_STRUCT) {
+ idTypeInfo = _ctx
+ .userTypeId2TypeInfo[LongLongKey(ObjType.STRUCT.id, userTypeId)];
+ }
if (idTypeInfo != null) {
return idTypeInfo;
}
- throw UnregisteredTypeException(xtype);
+ throw UnregisteredTypeException(
+ '${xtype.name}(userTypeId=$userTypeId)');
+ case ObjType.COMPATIBLE_STRUCT:
+ case ObjType.NAMED_COMPATIBLE_STRUCT:
+ return _readSharedTypeMeta(br);
case ObjType.NAMED_ENUM:
case ObjType.NAMED_STRUCT:
- case ObjType.NAMED_COMPATIBLE_STRUCT:
case ObjType.NAMED_EXT:
+ case ObjType.NAMED_UNION:
+ if (_ctx.conf.compatible) {
+ return _readSharedTypeMeta(br);
+ }
MetaStringBytes pkgBytes = _msResolver.readMetaStringBytes(br);
// assert(pkgBytes.length == 0); // fory dart does not support package
- MetaStringBytes simpleClassNameBytes =
_msResolver.readMetaStringBytes(br);
- LongLongKey key = LongLongKey(pkgBytes.hashCode,
simpleClassNameBytes.hashCode);
+ MetaStringBytes simpleClassNameBytes =
+ _msResolver.readMetaStringBytes(br);
+ LongLongKey key =
+ LongLongKey(pkgBytes.hashCode, simpleClassNameBytes.hashCode);
TypeInfo? typeInfo = _tagHash2Info[key];
if (typeInfo != null) {
// Indicates that it has been registered
@@ -211,38 +376,406 @@ final class XtypeResolverImpl extends XtypeResolver {
}
@override
- TypeInfo writeGetTypeInfo(ByteWriter bw, Object obj, SerializerPack pack){
+ TypeInfo writeGetTypeInfo(ByteWriter bw, Object obj, SerializerPack pack) {
Type dartType = dartTypeResolver.getForyType(obj);
TypeInfo? typeInfo = _ctx.type2TypeInfo[dartType];
- if (typeInfo == null){
+ if (typeInfo == null) {
throw UnregisteredTypeException(dartType);
}
bw.writeUint8(typeInfo.objType.id);
- switch(typeInfo.objType){
+ switch (typeInfo.objType) {
case ObjType.ENUM:
case ObjType.STRUCT:
- case ObjType.COMPATIBLE_STRUCT:
case ObjType.EXT:
+ case ObjType.TYPED_UNION:
bw.writeVarUint32(typeInfo.userTypeId);
break;
+ case ObjType.COMPATIBLE_STRUCT:
+ case ObjType.NAMED_COMPATIBLE_STRUCT:
+ _writeSharedTypeMeta(bw, typeInfo);
+ break;
case ObjType.NAMED_ENUM:
case ObjType.NAMED_STRUCT:
- case ObjType.NAMED_COMPATIBLE_STRUCT:
case ObjType.NAMED_EXT:
- pack.msWritingResolver.writeMetaStringBytes(bw, typeInfo.nsBytes!);
- pack.msWritingResolver.writeMetaStringBytes(bw,
typeInfo.typeNameBytes!);
+ case ObjType.NAMED_UNION:
+ if (_ctx.conf.compatible) {
+ _writeSharedTypeMeta(bw, typeInfo);
+ } else {
+ pack.msWritingResolver.writeMetaStringBytes(bw, typeInfo.nsBytes!);
+ pack.msWritingResolver
+ .writeMetaStringBytes(bw, typeInfo.typeNameBytes!);
+ }
break;
default:
break;
}
return typeInfo;
}
-
+
+ TypeInfo _readSharedTypeMeta(ByteReader br) {
+ final int marker = br.readVarUint32();
+ final bool isRef = (marker & 1) == 1;
+ final int index = marker >>> 1;
+ if (isRef) {
+ if (index < 0 || index >= _readTypeInfos.length) {
+ throw UnregisteredTypeException(
+ 'Shared type index out of bounds: $index');
+ }
+ return _readTypeInfos[index];
+ }
+ final int id = br.readInt64();
+ final int unsignedId = id & _allBits64Mask;
+ int size = unsignedId & _metaSizeMask;
+ if (size == _metaSizeMask) {
+ size += br.readVarUint32();
+ }
+ final Uint8List bodyBytes = br.copyBytes(size);
+ if ((unsignedId & _compressMetaFlag) != 0) {
+ throw UnregisteredTypeException('Compressed TypeDef is not supported');
+ }
+ final TypeInfo typeInfo = _readTypeInfoFromTypeDefBody(bodyBytes);
+ _readTypeInfos.add(typeInfo);
+ return typeInfo;
+ }
+
+ TypeInfo _readTypeInfoFromTypeDefBody(Uint8List bodyBytes) {
+ final ByteReader bodyReader = ByteReader.forBytes(bodyBytes);
+ int header = bodyReader.readUint8();
+ int numFields = header & _smallFieldThreshold;
+ if (numFields == _smallFieldThreshold) {
+ numFields += bodyReader.readVarUint32Small7();
+ }
+ if ((header & _registerByNameFlag) != 0) {
+ final String namespace = _readPackageName(bodyReader);
+ final String typeName = _readTypeName(bodyReader);
+ final String qualifiedName =
+ StringUtil.addingTypeNameAndNs(namespace, typeName);
+ final TypeInfo? typeInfo = _ctx.tag2TypeInfo[qualifiedName];
+ if (typeInfo == null) {
+ throw UnregisteredTagException(qualifiedName);
+ }
+ return typeInfo;
+ }
+ final int typeId = bodyReader.readUint8();
+ final int userTypeId = bodyReader.readVarUint32();
+ final TypeInfo? typeInfo = _lookupTypeInfoByUserTypeId(typeId, userTypeId);
+ if (typeInfo == null) {
+ throw UnregisteredTypeException('typeId=$typeId,userTypeId=$userTypeId');
+ }
+ return typeInfo;
+ }
+
+ TypeInfo? _lookupTypeInfoByUserTypeId(int typeId, int userTypeId) {
+ TypeInfo? typeInfo =
+ _ctx.userTypeId2TypeInfo[LongLongKey(typeId, userTypeId)];
+ if (typeInfo == null && typeId == ObjType.STRUCT.id) {
+ typeInfo = _ctx.userTypeId2TypeInfo[
+ LongLongKey(ObjType.COMPATIBLE_STRUCT.id, userTypeId)];
+ } else if (typeInfo == null && typeId == ObjType.COMPATIBLE_STRUCT.id) {
+ typeInfo =
+ _ctx.userTypeId2TypeInfo[LongLongKey(ObjType.STRUCT.id, userTypeId)];
+ }
+ return typeInfo;
+ }
+
+ String _readPackageName(ByteReader br) {
+ return _readTypeNameInternal(
+ br, _packageNameDecoder, _packageNameEncodingByFlag);
+ }
+
+ String _readTypeName(ByteReader br) {
+ return _readTypeNameInternal(br, _typeNameDecoder,
_typeNameEncodingByFlag);
+ }
+
+ String _readTypeNameInternal(
+ ByteReader br,
+ MetaStringDecoder decoder,
+ MetaStringEncoding Function(int) getEncodingByFlag,
+ ) {
+ final int header = br.readUint8();
+ final int encodingFlag = header & 3;
+ final MetaStringEncoding encoding = getEncodingByFlag(encodingFlag);
+ int size = header >>> 2;
+ if (size == _bigNameThreshold) {
+ size += br.readVarUint32Small7();
+ }
+ final Uint8List bytes = br.readBytesView(size);
+ return decoder.decode(bytes, encoding);
+ }
+
+ void _writeSharedTypeMeta(ByteWriter bw, TypeInfo typeInfo) {
+ final int? existingIndex = _writeTypeToIndex[typeInfo.dartType];
+ if (existingIndex != null) {
+ bw.writeVarUint32((existingIndex << 1) | 1);
+ return;
+ }
+ final int index = _writeTypeToIndex.length;
+ _writeTypeToIndex[typeInfo.dartType] = index;
+ bw.writeVarUint32(index << 1);
+ final Uint8List typeDef = _typeToEncodedTypeDef.putIfAbsent(
+ typeInfo.dartType,
+ () => _encodeTypeDefFor(typeInfo),
+ );
+ bw.writeBytes(typeDef);
+ }
+
+ Uint8List _encodeTypeDefFor(TypeInfo typeInfo) {
+ final CustomTypeSpec? spec = _type2Spec[typeInfo.dartType];
+ if (spec == null) {
+ throw UnregisteredTypeException(typeInfo.dartType);
+ }
+ final List<FieldSpec> fields = _fieldsForTypeDef(spec);
+ final Uint8List body = _buildTypeDefBody(typeInfo, fields);
+ final int bodySize = body.length;
+ final int hash50 =
+ Murmur3Hash.hash128x64(body, bodySize, 0, _seed47).$1 &
_hashMask50Bits;
+ int id = _toSignedInt64(hash50 << 14);
+ id &= ~(_metaSizeMask | _hasFieldsMetaFlag | _compressMetaFlag);
+ if (fields.isNotEmpty) {
+ id |= _hasFieldsMetaFlag;
+ }
+ if (bodySize >= _metaSizeMask) {
+ id |= _metaSizeMask;
+ } else {
+ id |= bodySize;
+ }
+ id = _toSignedInt64(id);
+
+ final ByteWriter writer = ByteWriter();
+ writer.writeInt64(id);
+ if (bodySize >= _metaSizeMask) {
+ writer.writeVarUint32(bodySize - _metaSizeMask);
+ }
+ writer.writeBytes(body);
+ return writer.takeBytes();
+ }
+
+ List<FieldSpec> _fieldsForTypeDef(CustomTypeSpec spec) {
+ if (spec is! ClassSpec) {
+ return const <FieldSpec>[];
+ }
+ final List<FieldSpec> fields = <FieldSpec>[];
+ for (int i = 0; i < spec.fields.length; ++i) {
+ final FieldSpec field = spec.fields[i];
+ if (field.includeToFory) {
+ fields.add(field);
+ }
+ }
+ return fields;
+ }
+
+ Uint8List _buildTypeDefBody(TypeInfo typeInfo, List<FieldSpec> fields) {
+ final ByteWriter writer = ByteWriter();
+ int header = fields.length >= _smallFieldThreshold
+ ? _smallFieldThreshold
+ : fields.length;
+ final bool registerByName = typeInfo.tag != null;
+ if (registerByName) {
+ header |= _registerByNameFlag;
+ }
+ writer.writeUint8(header);
+ if (fields.length >= _smallFieldThreshold) {
+ writer.writeVarUint32Small7(fields.length - _smallFieldThreshold);
+ }
+ if (registerByName) {
+ final String ns = _msResolver.decodeNamespace(typeInfo.nsBytes!);
+ final String typeName =
+ _msResolver.decodeTypename(typeInfo.typeNameBytes!);
+ _writePackageName(writer, ns);
+ _writeTypeName(writer, typeName);
+ } else {
+ writer.writeUint8(typeInfo.objType.id);
+ writer.writeVarUint32(typeInfo.userTypeId);
+ }
+ for (int i = 0; i < fields.length; ++i) {
+ _writeTypeDefField(writer, fields[i]);
+ }
+ return writer.takeBytes();
+ }
+
+ void _writePackageName(ByteWriter writer, String value) {
+ final meta = _packageNameEncoder.encodeByAllowedEncodings(
+ value, _packageNameAllowedEncodings);
+ _writeName(writer, meta.bytes, _packageNameEncodingFlag(meta.encoding));
+ }
+
+ void _writeTypeName(ByteWriter writer, String value) {
+ final meta = _typeNameEncoder.encodeByAllowedEncodings(
+ value, _typeNameAllowedEncodings);
+ _writeName(writer, meta.bytes, _typeNameEncodingFlag(meta.encoding));
+ }
+
+ void _writeName(ByteWriter writer, Uint8List encodedBytes, int encodingFlag)
{
+ final int size = encodedBytes.length;
+ if (size >= _bigNameThreshold) {
+ writer.writeUint8((_bigNameThreshold << 2) | encodingFlag);
+ writer.writeVarUint32Small7(size - _bigNameThreshold);
+ } else {
+ writer.writeUint8((size << 2) | encodingFlag);
+ }
+ writer.writeBytes(encodedBytes);
+ }
+
+ void _writeTypeDefField(ByteWriter writer, FieldSpec field) {
+ final String fieldName =
StringUtil.lowerCamelToLowerUnderscore(field.name);
+ final meta = _fieldNameEncoder.encodeByAllowedEncodings(
+ fieldName, _fieldNameAllowedEncodings);
+ final Uint8List encodedName = meta.bytes;
+ final int encodingFlag = _fieldNameEncodingFlag(meta.encoding);
+ int size = encodedName.length - 1;
+ int header = encodingFlag << 6;
+ if (field.typeSpec.nullable) {
+ header |= 2;
+ }
+ if (size >= _fieldNameSizeThreshold) {
+ header |= (_fieldNameSizeThreshold << 2);
+ writer.writeUint8(header);
+ writer.writeVarUint32Small7(size - _fieldNameSizeThreshold);
+ } else {
+ header |= (size << 2);
+ writer.writeUint8(header);
+ }
+ final int typeId = _fieldTypeId(field.typeSpec);
+ writer.writeUint8(typeId);
+ _writeNestedTypeInfo(writer, field.typeSpec);
+ writer.writeBytes(encodedName);
+ }
+
+ void _writeNestedTypeInfo(ByteWriter writer, TypeSpec typeSpec) {
+ final int typeId = _fieldTypeId(typeSpec);
+ switch (ObjType.fromId(typeId)) {
+ case ObjType.LIST:
+ case ObjType.SET:
+ final TypeSpec elem = typeSpec.genericsArgs.isNotEmpty
+ ? typeSpec.genericsArgs[0]
+ : const TypeSpec(
+ Object, ObjType.UNKNOWN, true, false, null, <TypeSpec>[]);
+ _writeNestedFieldTypeHeader(writer, elem);
+ _writeNestedTypeInfo(writer, elem);
+ break;
+ case ObjType.MAP:
+ final TypeSpec key = typeSpec.genericsArgs.isNotEmpty
+ ? typeSpec.genericsArgs[0]
+ : const TypeSpec(
+ Object, ObjType.UNKNOWN, true, false, null, <TypeSpec>[]);
+ final TypeSpec value = typeSpec.genericsArgs.length > 1
+ ? typeSpec.genericsArgs[1]
+ : const TypeSpec(
+ Object, ObjType.UNKNOWN, true, false, null, <TypeSpec>[]);
+ _writeNestedFieldTypeHeader(writer, key);
+ _writeNestedTypeInfo(writer, key);
+ _writeNestedFieldTypeHeader(writer, value);
+ _writeNestedTypeInfo(writer, value);
+ break;
+ default:
+ break;
+ }
+ }
+
+ void _writeNestedFieldTypeHeader(ByteWriter writer, TypeSpec typeSpec) {
+ int header = _fieldTypeId(typeSpec) << 2;
+ if (typeSpec.nullable) {
+ header |= 2;
+ }
+ writer.writeVarUint32Small7(header);
+ }
+
+ int _fieldTypeId(TypeSpec typeSpec) {
+ switch (typeSpec.objType) {
+ case ObjType.NAMED_ENUM:
+ return ObjType.ENUM.id;
+ case ObjType.NAMED_STRUCT:
+ return ObjType.STRUCT.id;
+ case ObjType.NAMED_COMPATIBLE_STRUCT:
+ return ObjType.COMPATIBLE_STRUCT.id;
+ case ObjType.NAMED_EXT:
+ return ObjType.EXT.id;
+ case ObjType.TYPED_UNION:
+ case ObjType.NAMED_UNION:
+ return ObjType.UNION.id;
+ default:
+ return typeSpec.objType.id;
+ }
+ }
+
+ int _packageNameEncodingFlag(MetaStringEncoding encoding) {
+ switch (encoding) {
+ case MetaStringEncoding.utf8:
+ return 0;
+ case MetaStringEncoding.atls:
+ return 1;
+ case MetaStringEncoding.luds:
+ return 2;
+ default:
+ throw RegistrationArgumentException(encoding);
+ }
+ }
+
+ int _typeNameEncodingFlag(MetaStringEncoding encoding) {
+ switch (encoding) {
+ case MetaStringEncoding.utf8:
+ return 0;
+ case MetaStringEncoding.atls:
+ return 1;
+ case MetaStringEncoding.luds:
+ return 2;
+ case MetaStringEncoding.ftls:
+ return 3;
+ default:
+ throw RegistrationArgumentException(encoding);
+ }
+ }
+
+ int _fieldNameEncodingFlag(MetaStringEncoding encoding) {
+ switch (encoding) {
+ case MetaStringEncoding.utf8:
+ return 0;
+ case MetaStringEncoding.atls:
+ return 1;
+ case MetaStringEncoding.luds:
+ return 2;
+ default:
+ throw RegistrationArgumentException(encoding);
+ }
+ }
+
+ MetaStringEncoding _packageNameEncodingByFlag(int flag) {
+ switch (flag) {
+ case 0:
+ return MetaStringEncoding.utf8;
+ case 1:
+ return MetaStringEncoding.atls;
+ case 2:
+ return MetaStringEncoding.luds;
+ default:
+ throw RegistrationArgumentException(flag);
+ }
+ }
+
+ MetaStringEncoding _typeNameEncodingByFlag(int flag) {
+ switch (flag) {
+ case 0:
+ return MetaStringEncoding.utf8;
+ case 1:
+ return MetaStringEncoding.atls;
+ case 2:
+ return MetaStringEncoding.luds;
+ case 3:
+ return MetaStringEncoding.ftls;
+ default:
+ throw RegistrationArgumentException(flag);
+ }
+ }
+
+ int _toSignedInt64(int value) {
+ return value.toSigned(64);
+ }
+
// for test only
@override
StructHashPair getHashPairForTest(Type type) {
TypeInfo? typeInfo = _ctx.type2TypeInfo[type];
- if (typeInfo == null){
+ if (typeInfo == null) {
throw UnregisteredTypeException(type);
}
ClassSerializer ser = typeInfo.ser as ClassSerializer;
diff --git a/dart/packages/fory/lib/src/resolver/struct_hash_resolver.dart
b/dart/packages/fory/lib/src/resolver/struct_hash_resolver.dart
index 6fda45b51..eaf5661cf 100644
--- a/dart/packages/fory/lib/src/resolver/struct_hash_resolver.dart
+++ b/dart/packages/fory/lib/src/resolver/struct_hash_resolver.dart
@@ -17,82 +17,96 @@
* under the License.
*/
-import 'dart:collection';
+import 'dart:convert';
+import 'dart:typed_data';
import 'package:fory/src/codegen/entity/struct_hash_pair.dart';
import 'package:fory/src/const/types.dart';
import 'package:fory/src/meta/specs/field_spec.dart';
+import 'package:fory/src/util/murmur3hash.dart';
import 'package:fory/src/util/string_util.dart';
-class StructHashResolver{
+class StructHashResolver {
// singleton
static final StructHashResolver _instance = StructHashResolver._internal();
static StructHashResolver get inst => _instance;
StructHashResolver._internal();
- // key: utf8 string objectIdentityHash, value: hash value
- final Map<int, int> _stringObjToHash = HashMap();
-
- StructHashPair computeHash(List<FieldSpec> fields, String Function(Type
type) getTagByType) {
- if (fields.isEmpty) {
- return const StructHashPair(17, 17);
+ StructHashPair computeHash(
+ List<FieldSpec> fields, String Function(Type type) _) {
+ final List<FieldSpec> fromFields = <FieldSpec>[];
+ final List<FieldSpec> toFields = <FieldSpec>[];
+ for (int i = 0; i < fields.length; ++i) {
+ final FieldSpec field = fields[i];
+ if (field.includeFromFory) {
+ fromFields.add(field);
+ }
+ if (field.includeToFory) {
+ toFields.add(field);
+ }
}
- int hashF = 17;
- int hashT = 17;
- // Here, checking whether align means to see if includeFromFory and
includeToFory are the same,
- // and both cannot be false at the same time, otherwise static analysis
will not retain this field,
- // so actually both are true.
- bool stillAlign = fields[0].includeFromFory == fields[0].includeToFory;
-
- for (int i = 0; i < fields.length; ++i){
- if (stillAlign){
- // Here, stillAlign means fields[i] is aligned
- if (i < fields.length - 1){
- stillAlign = fields[i+1].includeFromFory ==
fields[i+1].includeToFory;
+ if (fromFields.length == toFields.length) {
+ bool same = true;
+ for (int i = 0; i < fromFields.length; ++i) {
+ if (!identical(fromFields[i], toFields[i])) {
+ same = false;
+ break;
}
- hashF = _computeFieldHash(hashF, fields[i], getTagByType);
- hashT = hashF;
- continue;
}
- // Here, stillAlign means fields[i] is unaligned
- if (fields[i].includeFromFory) hashF = _computeFieldHash(hashF,
fields[i], getTagByType);
- if (fields[i].includeToFory) hashT = _computeFieldHash(hashT, fields[i],
getTagByType);
+ if (same) {
+ final int hash = _computeFingerprintHash(fromFields);
+ return StructHashPair(hash, hash);
+ }
}
- return StructHashPair(hashF, hashT);
+ return StructHashPair(
+ _computeFingerprintHash(fromFields),
+ _computeFingerprintHash(toFields),
+ );
}
- int _computeFieldHash(int hash, FieldSpec field, String Function(Type type)
getTagByType) {
- late int id;
- String tag;
- ObjType objType = field.typeSpec.objType;
- switch(objType){
- case ObjType.LIST:
- id = ObjType.LIST.id;
- break;
- case ObjType.MAP:
- id = ObjType.MAP.id;
- break;
- case ObjType.UNKNOWN:
- id = 0;
- break;
- default:
- if (objType.isStructType()){
- tag = getTagByType(field.typeSpec.type);
- int tagObjHashCode = identityHashCode(tag);
- int? hashVal = _stringObjToHash[tagObjHashCode];
- if (hashVal != null) {
- id = hashVal;
- }else{
- id = StringUtil.computeUtf8StringHash(tag);
- _stringObjToHash[tagObjHashCode] = id;
- }
- }else {
- id = objType.id.abs();
- }
+ int _computeFingerprintHash(List<FieldSpec> fields) {
+ final List<String> entries =
+ List<String>.filled(fields.length, '', growable: false);
+ for (int i = 0; i < fields.length; ++i) {
+ final FieldSpec field = fields[i];
+ final String fieldName =
+ StringUtil.lowerCamelToLowerUnderscore(field.name);
+ final int typeId = _fingerprintTypeId(field.typeSpec.objType);
+ final int nullable = field.typeSpec.nullable ? 1 : 0;
+ entries[i] = '$fieldName,$typeId,0,$nullable;';
+ }
+ entries.sort();
+ final StringBuffer fingerprint = StringBuffer();
+ for (int i = 0; i < entries.length; ++i) {
+ fingerprint.write(entries[i]);
+ }
+ final Uint8List bytes =
+ Uint8List.fromList(utf8.encode(fingerprint.toString()));
+ final int hash64 = Murmur3Hash.hash128x64(bytes, bytes.length, 0, 47).$1;
+ return (hash64 & 0xffffffff).toSigned(32);
+ }
+
+ int _fingerprintTypeId(ObjType objType) {
+ if (objType == ObjType.UNKNOWN) {
+ return ObjType.UNKNOWN.id;
+ }
+ if (objType == ObjType.LIST ||
+ objType == ObjType.SET ||
+ objType == ObjType.MAP) {
+ return objType.id;
}
- int fieldHash = hash * 31 + id;
- while (fieldHash > 0x7FFFFFFF){
- fieldHash ~/= 7;
+ if (objType == ObjType.ENUM ||
+ objType == ObjType.NAMED_ENUM ||
+ objType == ObjType.STRUCT ||
+ objType == ObjType.COMPATIBLE_STRUCT ||
+ objType == ObjType.NAMED_STRUCT ||
+ objType == ObjType.NAMED_COMPATIBLE_STRUCT ||
+ objType == ObjType.EXT ||
+ objType == ObjType.NAMED_EXT ||
+ objType == ObjType.UNION ||
+ objType == ObjType.TYPED_UNION ||
+ objType == ObjType.NAMED_UNION) {
+ return ObjType.UNKNOWN.id;
}
- return fieldHash;
+ return objType.id;
}
}
diff --git a/dart/packages/fory/lib/src/resolver/xtype_resolver.dart
b/dart/packages/fory/lib/src/resolver/xtype_resolver.dart
index 90924e3ce..baf9fdfd7 100644
--- a/dart/packages/fory/lib/src/resolver/xtype_resolver.dart
+++ b/dart/packages/fory/lib/src/resolver/xtype_resolver.dart
@@ -28,20 +28,23 @@ import 'package:fory/src/memory/byte_writer.dart';
import 'package:fory/src/meta/specs/custom_type_spec.dart';
import 'package:fory/src/serializer_pack.dart';
-abstract base class XtypeResolver{
-
+abstract base class XtypeResolver {
const XtypeResolver(ForyConfig conf);
static XtypeResolver newOne(ForyConfig conf) {
return XtypeResolverImpl(conf);
}
- void reg(CustomTypeSpec spec, [String? tag]);
+ void reg(CustomTypeSpec spec, [Object? tagOrTypeId]);
void registerSerializer(Type type, Serializer ser);
void setSersForTypeWrap(List<TypeSpecWrap> typeWraps);
+ void resetWriteContext();
+
+ void resetReadContext();
+
TypeInfo readTypeInfo(ByteReader br);
String getTagByCustomDartType(Type type);
@@ -52,4 +55,4 @@ abstract base class XtypeResolver{
StructHashPair getHashPairForTest(
Type type,
);
-}
\ No newline at end of file
+}
diff --git a/dart/packages/fory/lib/src/serialize_coordinator.dart
b/dart/packages/fory/lib/src/serialize_coordinator.dart
index 655cf8731..db07b8e2f 100644
--- a/dart/packages/fory/lib/src/serialize_coordinator.dart
+++ b/dart/packages/fory/lib/src/serialize_coordinator.dart
@@ -38,14 +38,17 @@ import 'package:fory/src/datatype/int32.dart';
import 'package:fory/src/datatype/float32.dart';
class SerializeCoordinator {
- static final SerializeCoordinator _instance =
SerializeCoordinator._internal();
+ static final SerializeCoordinator _instance =
+ SerializeCoordinator._internal();
static SerializeCoordinator get I => _instance;
SerializeCoordinator._internal();
static final ForyHeaderSerializer _foryHeaderSer = ForyHeaderSerializer.I;
- void _write(Object? obj, ForyConfig conf, XtypeResolver xtypeResolver,
ByteWriter writer) {
+ void _write(Object? obj, ForyConfig conf, XtypeResolver xtypeResolver,
+ ByteWriter writer) {
_foryHeaderSer.write(writer, obj == null, conf);
+ xtypeResolver.resetWriteContext();
SerializerPack pack = SerializerPack(
StructHashResolver.inst,
xtypeResolver.getTagByCustomDartType,
@@ -60,13 +63,18 @@ class SerializeCoordinator {
// pack.resetAndRecycle();
}
- Uint8List write(Object? obj, ForyConfig conf, XtypeResolver xtypeResolver,) {
+ Uint8List write(
+ Object? obj,
+ ForyConfig conf,
+ XtypeResolver xtypeResolver,
+ ) {
ByteWriter bw = ByteWriter();
_write(obj, conf, xtypeResolver, bw);
return bw.takeBytes();
}
- void writeWithWriter(Object? obj, ForyConfig conf, XtypeResolver
xtypeResolver, ByteWriter writer) {
+ void writeWithWriter(Object? obj, ForyConfig conf,
+ XtypeResolver xtypeResolver, ByteWriter writer) {
_write(obj, conf, xtypeResolver, writer);
}
@@ -107,7 +115,39 @@ class SerializeCoordinator {
}
}
- void xWriteRefWithSer(ByteWriter bw, Serializer ser, Object? obj,
SerializerPack pack) {
+ void xWriteNonRefNoSer(ByteWriter bw, Object obj, SerializerPack pack) {
+ TypeInfo typeInfo = pack.xtypeResolver.writeGetTypeInfo(bw, obj, pack);
+ switch (typeInfo.objType) {
+ case ObjType.BOOL:
+ bw.writeBool(obj as bool);
+ break;
+ case ObjType.INT8:
+ bw.writeInt8((obj as Int8).value);
+ break;
+ case ObjType.INT16:
+ bw.writeInt16((obj as Int16).value);
+ break;
+ case ObjType.INT32:
+ case ObjType.VAR_INT32:
+ bw.writeVarInt32((obj as Int32).value);
+ break;
+ case ObjType.INT64:
+ case ObjType.VAR_INT64:
+ bw.writeVarInt64(obj as int);
+ break;
+ case ObjType.FLOAT32:
+ bw.writeFloat32((obj as Float32).value);
+ break;
+ case ObjType.FLOAT64:
+ bw.writeFloat64(obj as double);
+ break;
+ default:
+ typeInfo.ser.write(bw, obj, pack);
+ }
+ }
+
+ void xWriteRefWithSer(
+ ByteWriter bw, Serializer ser, Object? obj, SerializerPack pack) {
if (ser.writeRef) {
SerializationRefMeta serRef = pack.refResolver.getRefId(obj);
bw.writeInt8(serRef.refFlag.id);
@@ -116,11 +156,11 @@ class SerializeCoordinator {
}
if (serRef.refFlag.noNeedToSer) return;
ser.write(bw, obj, pack);
- }else{
+ } else {
RefFlag refFlag = pack.noRefResolver.getRefFlag(obj);
bw.writeInt8(refFlag.id);
if (refFlag.noNeedToSer) return;
ser.write(bw, obj, pack);
}
}
-}
\ No newline at end of file
+}
diff --git a/dart/packages/fory/lib/src/serializer/class_serializer.dart
b/dart/packages/fory/lib/src/serializer/class_serializer.dart
index 0e7808e2e..03df0f1da 100644
--- a/dart/packages/fory/lib/src/serializer/class_serializer.dart
+++ b/dart/packages/fory/lib/src/serializer/class_serializer.dart
@@ -33,31 +33,32 @@ import 'package:fory/src/serializer/serializer.dart';
import 'package:fory/src/serializer/serializer_cache.dart';
import 'package:fory/src/serializer_pack.dart';
-final class ClassSerializerCache extends SerializerCache{
+final class ClassSerializerCache extends SerializerCache {
const ClassSerializerCache();
@override
- ClassSerializer getSerializerWithSpec(ForyConfig conf, covariant ClassSpec
spec, Type dartType){
+ ClassSerializer getSerializerWithSpec(
+ ForyConfig conf, covariant ClassSpec spec, Type dartType) {
List<TypeSpecWrap> typeWraps = TypeSpecWrap.ofList(spec.fields);
return ClassSerializer(
spec.fields,
spec.construct,
spec.noArgConstruct,
typeWraps,
+ conf.compatible,
conf.refTracking,
);
}
}
-
-final class ClassSerializer extends CustomSerializer<Object>{
-
+final class ClassSerializer extends CustomSerializer<Object> {
static const ClassSerializerCache cache = ClassSerializerCache();
final List<FieldSpec> _fields;
final HasArgsCons? _construct;
final NoArgsCons? _noArgConstruct;
final List<TypeSpecWrap> _fieldTypeWraps;
+ final bool _compatible;
late final int _fromForyHash;
late final int _toForyHash;
@@ -66,61 +67,71 @@ final class ClassSerializer extends
CustomSerializer<Object>{
bool _fieldsSersComputed = false;
ClassSerializer(
- this._fields,
- this._construct,
- this._noArgConstruct,
- this._fieldTypeWraps,
- bool refWrite,
- ): super(ObjType.NAMED_STRUCT, refWrite,);
-
+ this._fields,
+ this._construct,
+ this._noArgConstruct,
+ this._fieldTypeWraps,
+ this._compatible,
+ bool refWrite,
+ ) : super(
+ ObjType.NAMED_STRUCT,
+ refWrite,
+ );
- StructHashPair getHashPairForTest(StructHashResolver structHashResolver,
String Function(Type type) getTagByDartType){
+ StructHashPair getHashPairForTest(StructHashResolver structHashResolver,
+ String Function(Type type) getTagByDartType) {
return structHashResolver.computeHash(_fields, getTagByDartType);
}
@override
Object read(ByteReader br, int refId, DeserializerPack pack) {
- if (!_fieldsSersComputed){
+ if (!_fieldsSersComputed) {
pack.xtypeResolver.setSersForTypeWrap(_fieldTypeWraps);
_fieldsSersComputed = true;
}
- if (!_hashComputed){
- var pair = pack.structHashResolver.computeHash(_fields,
pack.getTagByDartType);
+ if (!_compatible && !_hashComputed) {
+ var pair =
+ pack.structHashResolver.computeHash(_fields, pack.getTagByDartType);
_fromForyHash = pair.fromForyHash;
_toForyHash = pair.toForyHash;
_hashComputed = true;
}
- int readFHash = br.readInt32();
- if (readFHash != _fromForyHash){
- throw ForyMismatchException(
- readFHash,
- _fromForyHash,
- 'The field hash read from bytes does not match the expected hash.',
- );
+ if (!_compatible) {
+ int readFHash = br.readInt32();
+ if (readFHash != _fromForyHash) {
+ throw ForyMismatchException(
+ readFHash,
+ _fromForyHash,
+ 'The field hash read from bytes does not match the expected hash.',
+ );
+ }
}
if (_noArgConstruct == null) {
return _byParameterizedCons(br, refId, pack);
}
Object obj = _noArgConstruct();
- pack.refResolver.setRefTheLatestId(obj); // Need to ref immediately to
prevent subsequent circular references and for normal reference tracking
+ pack.refResolver.setRefTheLatestId(
+ obj); // Need to ref immediately to prevent subsequent circular
references and for normal reference tracking
for (int i = 0; i < _fields.length; ++i) {
FieldSpec fieldSpec = _fields[i];
if (!fieldSpec.includeFromFory) continue;
TypeSpecWrap typeWrap = _fieldTypeWraps[i];
bool hasGenericsParam = typeWrap.hasGenericsParam;
- if (hasGenericsParam){
+ if (hasGenericsParam) {
pack.typeWrapStack.push(typeWrap);
}
late Object? fieldValue;
Serializer? ser = _fieldTypeWraps[i].ser;
if (ser == null) {
fieldValue = pack.foryDeser.xReadRefNoSer(br, pack);
- }else{
+ } else if (typeWrap.nullable) {
fieldValue = pack.foryDeser.xReadRefWithSer(br, ser, pack);
+ } else {
+ fieldValue = ser.read(br, -1, pack);
}
assert(fieldSpec.setter != null);
fieldSpec.setter!(obj, fieldValue);
- if (hasGenericsParam){
+ if (hasGenericsParam) {
pack.typeWrapStack.pop();
}
}
@@ -129,55 +140,62 @@ final class ClassSerializer extends
CustomSerializer<Object>{
@override
void write(ByteWriter bw, Object v, SerializerPack pack) {
- if (!_fieldsSersComputed){
+ if (!_fieldsSersComputed) {
pack.xtypeResolver.setSersForTypeWrap(_fieldTypeWraps);
_fieldsSersComputed = true;
}
- if (!_hashComputed){
- var pair = pack.structHashResolver.computeHash(_fields,
pack.getTagByDartType);
+ if (!_compatible && !_hashComputed) {
+ var pair =
+ pack.structHashResolver.computeHash(_fields, pack.getTagByDartType);
_fromForyHash = pair.fromForyHash;
_toForyHash = pair.toForyHash;
_hashComputed = true;
}
- bw.writeInt32(_toForyHash);
+ if (!_compatible) {
+ bw.writeInt32(_toForyHash);
+ }
for (int i = 0; i < _fields.length; ++i) {
FieldSpec fieldSpec = _fields[i];
if (!fieldSpec.includeToFory) continue;
TypeSpecWrap typeWrap = _fieldTypeWraps[i];
bool hasGenericsParam = typeWrap.hasGenericsParam;
- if (hasGenericsParam){
+ if (hasGenericsParam) {
pack.typeWrapStack.push(typeWrap);
}
Object? fieldValue = fieldSpec.getter!(v);
Serializer? ser = typeWrap.ser;
if (ser == null) {
pack.forySer.xWriteRefNoSer(bw, fieldValue, pack);
- }else{
+ } else if (typeWrap.nullable) {
pack.forySer.xWriteRefWithSer(bw, ser, fieldValue, pack);
+ } else {
+ ser.write(bw, fieldValue!, pack);
}
- if (hasGenericsParam){
+ if (hasGenericsParam) {
pack.typeWrapStack.pop();
}
}
}
- Object _byParameterizedCons(ByteReader br, int refId, DeserializerPack pack){
+ Object _byParameterizedCons(ByteReader br, int refId, DeserializerPack pack)
{
List<Object?> args = List.filled(_fields.length, null);
- for (int i = 0; i < _fields.length; ++i){
+ for (int i = 0; i < _fields.length; ++i) {
FieldSpec fieldSpec = _fields[i];
if (!fieldSpec.includeFromFory) continue;
TypeSpecWrap typeWrap = _fieldTypeWraps[i];
bool hasGenericsParam = typeWrap.hasGenericsParam;
- if (hasGenericsParam){
+ if (hasGenericsParam) {
pack.typeWrapStack.push(typeWrap);
}
Serializer? ser = typeWrap.ser;
if (ser == null) {
args[i] = pack.foryDeser.xReadRefNoSer(br, pack);
- }else{
+ } else if (typeWrap.nullable) {
args[i] = pack.foryDeser.xReadRefWithSer(br, ser, pack);
+ } else {
+ args[i] = ser.read(br, -1, pack);
}
- if (hasGenericsParam){
+ if (hasGenericsParam) {
pack.typeWrapStack.pop();
}
}
diff --git
a/dart/packages/fory/lib/src/serializer/collection/iterable_serializer.dart
b/dart/packages/fory/lib/src/serializer/collection/iterable_serializer.dart
index b04ac736b..53e114535 100644
--- a/dart/packages/fory/lib/src/serializer/collection/iterable_serializer.dart
+++ b/dart/packages/fory/lib/src/serializer/collection/iterable_serializer.dart
@@ -17,46 +17,204 @@
* under the License.
*/
+import 'package:fory/src/const/ref_flag.dart';
import 'package:fory/src/memory/byte_writer.dart';
import 'package:fory/src/meta/spec_wraps/type_spec_wrap.dart';
import 'package:fory/src/serializer/serializer.dart';
import 'package:fory/src/serializer_pack.dart';
abstract base class IterableSerializer extends Serializer<Iterable> {
+ static const int trackingRefFlag = 0x01;
+ static const int hasNullFlag = 0x02;
+ static const int isDeclElementTypeFlag = 0x04;
+ static const int isSameTypeFlag = 0x08;
+
+ static const int declSameTypeTrackingRef =
+ isDeclElementTypeFlag | isSameTypeFlag | trackingRefFlag;
+ static const int declSameTypeHasNull =
+ isDeclElementTypeFlag | isSameTypeFlag | hasNullFlag;
+ static const int declSameTypeNotHasNull =
+ isDeclElementTypeFlag | isSameTypeFlag;
const IterableSerializer(super.objType, super.writeRef);
@override
void write(ByteWriter bw, Iterable v, SerializerPack pack) {
- bw.writeVarUint32Small7(v.length);
- TypeSpecWrap? elemWrap = pack.typeWrapStack.peek?.param0;
- if (elemWrap == null){
- for (var o in v) {
- pack.forySer.xWriteRefNoSer(bw, o, pack);
- }
+ final int len = v.length;
+ bw.writeVarUint32Small7(len);
+ if (len == 0) {
return;
}
- if (elemWrap.hasGenericsParam){
+ TypeSpecWrap? elemWrap = pack.typeWrapStack.peek?.param0;
+ final ({int flags, Serializer? ser}) header =
+ _writeElementsHeader(bw, v, elemWrap, pack);
+ if (elemWrap != null && elemWrap.hasGenericsParam) {
pack.typeWrapStack.push(elemWrap);
}
- if (!elemWrap.certainForSer){
- for (var o in v) {
- pack.forySer.xWriteRefNoSer(bw, o, pack);
+
+ int flags = header.flags;
+ Serializer? ser = header.ser;
+ if ((flags & isSameTypeFlag) == isSameTypeFlag && ser != null) {
+ if ((flags & trackingRefFlag) == trackingRefFlag) {
+ for (Object? elem in v) {
+ pack.forySer.xWriteRefWithSer(bw, ser, elem, pack);
+ }
+ } else {
+ if ((flags & hasNullFlag) == hasNullFlag) {
+ for (Object? elem in v) {
+ if (elem == null) {
+ bw.writeInt8(RefFlag.NULL.id);
+ } else {
+ bw.writeInt8(RefFlag.UNTRACKED_NOT_NULL.id);
+ ser.write(bw, elem, pack);
+ }
+ }
+ } else {
+ for (Object? elem in v) {
+ ser.write(bw, elem as Object, pack);
+ }
+ }
}
- }else {
- Serializer? ser = elemWrap.ser;
- if (ser == null){
- for (var o in v) {
- pack.forySer.xWriteRefNoSer(bw, o, pack);
+ } else {
+ if ((flags & trackingRefFlag) == trackingRefFlag) {
+ for (Object? elem in v) {
+ pack.forySer.xWriteRefNoSer(bw, elem, pack);
}
- }else{
- for (var o in v) {
- pack.forySer.xWriteRefWithSer(bw, ser, o, pack);
+ } else {
+ if ((flags & hasNullFlag) == hasNullFlag) {
+ for (Object? elem in v) {
+ if (elem == null) {
+ bw.writeInt8(RefFlag.NULL.id);
+ } else {
+ bw.writeInt8(RefFlag.UNTRACKED_NOT_NULL.id);
+ pack.forySer.xWriteNonRefNoSer(bw, elem, pack);
+ }
+ }
+ } else {
+ for (Object? elem in v) {
+ pack.forySer.xWriteNonRefNoSer(bw, elem as Object, pack);
+ }
}
}
}
- if (elemWrap.hasGenericsParam){
+
+ if (elemWrap != null && elemWrap.hasGenericsParam) {
pack.typeWrapStack.pop();
}
}
-}
\ No newline at end of file
+
+ ({int flags, Serializer? ser}) _writeElementsHeader(ByteWriter bw,
+ Iterable value, TypeSpecWrap? elemWrap, SerializerPack pack) {
+ if (elemWrap != null) {
+ if (elemWrap.certainForSer && elemWrap.ser != null) {
+ Serializer ser = elemWrap.ser!;
+ if (ser.writeRef) {
+ bw.writeUint8(declSameTypeTrackingRef);
+ return (flags: declSameTypeTrackingRef, ser: ser);
+ }
+ int flags =
+ _containsNull(value) ? declSameTypeHasNull :
declSameTypeNotHasNull;
+ bw.writeUint8(flags);
+ return (flags: flags, ser: ser);
+ }
+ bool trackingRef = elemWrap.ser?.writeRef ?? _isRefTrackingEnabled(pack);
+ if (trackingRef) {
+ return _writeTypeHeader(bw, value, elemWrap, pack);
+ }
+ return _writeTypeNullabilityHeader(bw, value, elemWrap, pack);
+ }
+
+ if (_isRefTrackingEnabled(pack)) {
+ return _writeTypeHeader(bw, value, null, pack);
+ }
+ return _writeTypeNullabilityHeader(bw, value, null, pack);
+ }
+
+ ({int flags, Serializer? ser}) _writeTypeHeader(ByteWriter bw, Iterable
value,
+ TypeSpecWrap? declareWrap, SerializerPack pack) {
+ bool hasDifferentClass = false;
+ Object? firstNonNull;
+ Type? elemType;
+ for (Object? elem in value) {
+ if (elem == null) {
+ continue;
+ }
+ if (elemType == null) {
+ firstNonNull = elem;
+ elemType = elem.runtimeType;
+ } else if (elem.runtimeType != elemType) {
+ hasDifferentClass = true;
+ break;
+ }
+ }
+ if (hasDifferentClass || firstNonNull == null) {
+ bw.writeUint8(trackingRefFlag);
+ return (flags: trackingRefFlag, ser: null);
+ }
+
+ int flags = trackingRefFlag | isSameTypeFlag;
+ Serializer? declaredSer = declareWrap?.ser;
+ if (declareWrap != null &&
+ declaredSer != null &&
+ elemType == declareWrap.type) {
+ flags |= isDeclElementTypeFlag;
+ bw.writeUint8(flags);
+ return (flags: flags, ser: declaredSer);
+ }
+ bw.writeUint8(flags);
+ final typeInfo =
+ pack.xtypeResolver.writeGetTypeInfo(bw, firstNonNull, pack);
+ return (flags: flags, ser: typeInfo.ser);
+ }
+
+ ({int flags, Serializer? ser}) _writeTypeNullabilityHeader(ByteWriter bw,
+ Iterable value, TypeSpecWrap? declareWrap, SerializerPack pack) {
+ int flags = 0;
+ bool hasDifferentClass = false;
+ Object? firstNonNull;
+ Type? elemType;
+ for (Object? elem in value) {
+ if (elem == null) {
+ flags |= hasNullFlag;
+ continue;
+ }
+ if (elemType == null) {
+ firstNonNull = elem;
+ elemType = elem.runtimeType;
+ } else if (!hasDifferentClass && elem.runtimeType != elemType) {
+ hasDifferentClass = true;
+ }
+ }
+ if (hasDifferentClass || firstNonNull == null) {
+ bw.writeUint8(flags);
+ return (flags: flags, ser: null);
+ }
+
+ flags |= isSameTypeFlag;
+ Serializer? declaredSer = declareWrap?.ser;
+ if (declareWrap != null &&
+ declaredSer != null &&
+ elemType == declareWrap.type) {
+ flags |= isDeclElementTypeFlag;
+ bw.writeUint8(flags);
+ return (flags: flags, ser: declaredSer);
+ }
+ bw.writeUint8(flags);
+ final typeInfo =
+ pack.xtypeResolver.writeGetTypeInfo(bw, firstNonNull, pack);
+ return (flags: flags, ser: typeInfo.ser);
+ }
+
+ bool _isRefTrackingEnabled(SerializerPack pack) {
+ return !identical(pack.refResolver, pack.noRefResolver);
+ }
+
+ bool _containsNull(Iterable value) {
+ for (Object? elem in value) {
+ if (elem == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git
a/dart/packages/fory/lib/src/serializer/collection/list/list_serializer.dart
b/dart/packages/fory/lib/src/serializer/collection/list/list_serializer.dart
index 71d0eefa8..c54d05349 100644
--- a/dart/packages/fory/lib/src/serializer/collection/list/list_serializer.dart
+++ b/dart/packages/fory/lib/src/serializer/collection/list/list_serializer.dart
@@ -17,6 +17,7 @@
* under the License.
*/
+import 'package:fory/src/const/ref_flag.dart';
import 'package:fory/src/const/types.dart';
import 'package:fory/src/deserializer_pack.dart';
import 'package:fory/src/memory/byte_reader.dart';
@@ -25,9 +26,8 @@ import
'package:fory/src/serializer/collection/iterable_serializer.dart';
import 'package:fory/src/serializer/serializer.dart';
abstract base class ListSerializer extends IterableSerializer {
+ const ListSerializer(bool writeRef) : super(ObjType.LIST, writeRef);
- const ListSerializer(bool writeRef): super(ObjType.LIST, writeRef);
-
List newList(int size, bool nullable);
@override
@@ -38,41 +38,76 @@ abstract base class ListSerializer extends
IterableSerializer {
num,
elemWrap == null || elemWrap.nullable,
);
- if (writeRef){
+ if (writeRef) {
pack.refResolver.setRefTheLatestId(list);
}
- if (elemWrap == null){
- for (int i = 0; i < num; ++i) {
- Object? o = pack.foryDeser.xReadRefNoSer(br, pack);
- list[i] = o;
- }
+ if (num == 0) {
return list;
}
- if (elemWrap.hasGenericsParam){
+
+ int flags = br.readUint8();
+ bool hasGenericsParam = elemWrap != null && elemWrap.hasGenericsParam;
+ if (hasGenericsParam) {
pack.typeWrapStack.push(elemWrap);
}
- if (!elemWrap.certainForSer){
- for (int i = 0; i < num; ++i) {
- Object? o = pack.foryDeser.xReadRefNoSer(br, pack);
- list[i] = o;
+
+ if ((flags & IterableSerializer.isSameTypeFlag) ==
+ IterableSerializer.isSameTypeFlag) {
+ Serializer? ser;
+ bool isDeclElemType =
+ (flags & IterableSerializer.isDeclElementTypeFlag) ==
+ IterableSerializer.isDeclElementTypeFlag;
+ if (isDeclElemType) {
+ ser = elemWrap?.ser;
+ }
+ if (ser == null) {
+ ser = pack.xtypeResolver.readTypeInfo(br).ser;
}
- }else {
- Serializer? ser = elemWrap.ser;
- if (ser == null){
+
+ if ((flags & IterableSerializer.trackingRefFlag) ==
+ IterableSerializer.trackingRefFlag) {
+ for (int i = 0; i < num; ++i) {
+ list[i] = pack.foryDeser.xReadRefWithSer(br, ser, pack);
+ }
+ } else if ((flags & IterableSerializer.hasNullFlag) ==
+ IterableSerializer.hasNullFlag) {
for (int i = 0; i < num; ++i) {
- Object? o = pack.foryDeser.xReadRefNoSer(br, pack);
- list[i] = o;
+ if (br.readInt8() == RefFlag.NULL.id) {
+ list[i] = null;
+ } else {
+ list[i] = ser.read(br, -1, pack);
+ }
}
- }else{
+ } else {
for (int i = 0; i < num; ++i) {
- Object? o = pack.foryDeser.xReadRefWithSer(br, ser, pack);
- list[i] = o;
+ list[i] = ser.read(br, -1, pack);
+ }
+ }
+ } else {
+ if ((flags & IterableSerializer.trackingRefFlag) ==
+ IterableSerializer.trackingRefFlag) {
+ for (int i = 0; i < num; ++i) {
+ list[i] = pack.foryDeser.xReadRefNoSer(br, pack);
+ }
+ } else if ((flags & IterableSerializer.hasNullFlag) ==
+ IterableSerializer.hasNullFlag) {
+ for (int i = 0; i < num; ++i) {
+ if (br.readInt8() == RefFlag.NULL.id) {
+ list[i] = null;
+ } else {
+ list[i] = pack.foryDeser.xReadNonRefNoSer(br, pack);
+ }
+ }
+ } else {
+ for (int i = 0; i < num; ++i) {
+ list[i] = pack.foryDeser.xReadNonRefNoSer(br, pack);
}
}
}
- if (elemWrap.hasGenericsParam){
+
+ if (hasGenericsParam) {
pack.typeWrapStack.pop();
}
return list;
}
-}
\ No newline at end of file
+}
diff --git
a/dart/packages/fory/lib/src/serializer/collection/map/map_serializer.dart
b/dart/packages/fory/lib/src/serializer/collection/map/map_serializer.dart
index af3a3ec04..8b351720f 100644
--- a/dart/packages/fory/lib/src/serializer/collection/map/map_serializer.dart
+++ b/dart/packages/fory/lib/src/serializer/collection/map/map_serializer.dart
@@ -18,169 +18,402 @@
*/
import 'package:fory/src/deserializer_pack.dart';
-import 'package:fory/src/dev_annotation/optimize.dart';
import 'package:fory/src/meta/spec_wraps/type_spec_wrap.dart';
-import 'package:fory/src/collection/stack.dart';
import 'package:fory/src/const/types.dart';
import 'package:fory/src/memory/byte_reader.dart';
import 'package:fory/src/memory/byte_writer.dart';
import 'package:fory/src/serializer_pack.dart';
import 'package:fory/src/serializer/serializer.dart';
-abstract base class MapSerializer<T extends Map<Object?,Object?>> extends
Serializer<Map<Object?,Object?>> {
+abstract base class MapSerializer<T extends Map<Object?, Object?>>
+ extends Serializer<Map<Object?, Object?>> {
+ static const int _maxChunkSize = 255;
- const MapSerializer(bool writeRef): super(ObjType.MAP, writeRef);
+ static const int _trackingKeyRef = 0x01;
+ static const int _keyHasNull = 0x02;
+ static const int _keyDeclType = 0x04;
+ static const int _trackingValueRef = 0x08;
+ static const int _valueHasNull = 0x10;
+ static const int _valueDeclType = 0x20;
+
+ static const int _kvNull = _keyHasNull | _valueHasNull;
+ static const int _nullKeyValueDeclType = _keyHasNull | _valueDeclType;
+ static const int _nullKeyValueDeclTypeTrackingRef =
+ _keyHasNull | _valueDeclType | _trackingValueRef;
+ static const int _nullValueKeyDeclType = _valueHasNull | _keyDeclType;
+ static const int _nullValueKeyDeclTypeTrackingRef =
+ _valueHasNull | _keyDeclType | _trackingKeyRef;
+
+ const MapSerializer(bool writeRef) : super(ObjType.MAP, writeRef);
T newMap(int size);
@override
T read(ByteReader br, int refId, DeserializerPack pack) {
- int len = br.readVarUint32Small7();
- T map = newMap(len);
- if (writeRef){
+ int remaining = br.readVarUint32Small7();
+ T map = newMap(remaining);
+ if (writeRef) {
pack.refResolver.setRefTheLatestId(map);
}
- TypeSpecWrap? typeWrap = pack.typeWrapStack.peek;
- if (typeWrap == null){
- // Traverse entries
- for (int i = 0; i < len; ++i) {
- Object? key = pack.foryDeser.xReadRefNoSer(br, pack);
- Object? value = pack.foryDeser.xReadRefNoSer(br, pack);
- map[key] = value;
- }
+ if (remaining == 0) {
return map;
}
- Stack<TypeSpecWrap> typeWrapStack = pack.typeWrapStack;
- TypeSpecWrap keyWrap = typeWrap.param0!;
- TypeSpecWrap valueWrap = typeWrap.param1!;
- Serializer? keySer = keyWrap.ser;
- Serializer? valueSer = valueWrap.ser;
-
- if (!keyWrap.hasGenericsParam && !valueWrap.hasGenericsParam){
- // Traverse entries
- for (int i = 0; i < len; ++i) {
- Object? key = _readWithNullableSer(br, keySer, pack);
- Object? value = _readWithNullableSer(br, valueSer, pack);
+
+ TypeSpecWrap? mapWrap = pack.typeWrapStack.peek;
+ TypeSpecWrap? keyWrap = mapWrap?.param0;
+ TypeSpecWrap? valueWrap = mapWrap?.param1;
+
+ while (remaining > 0) {
+ int chunkHeader = br.readUint8();
+ bool keyHasNull = (chunkHeader & _keyHasNull) != 0;
+ bool valueHasNull = (chunkHeader & _valueHasNull) != 0;
+ if (keyHasNull || valueHasNull) {
+ Object? key;
+ Object? value;
+ if (!keyHasNull) {
+ key = _readNullChunkKey(br, chunkHeader, keyWrap, pack);
+ value = null;
+ } else if (!valueHasNull) {
+ key = null;
+ value = _readNullChunkValue(br, chunkHeader, valueWrap, pack);
+ } else {
+ key = null;
+ value = null;
+ }
map[key] = value;
+ --remaining;
+ continue;
}
- return map;
- }
- if (!keyWrap.hasGenericsParam && valueWrap.hasGenericsParam){
- // Traverse entries
- for (int i = 0; i < len; ++i) {
- Object? key = _readWithNullableSer(br, keySer, pack);
- typeWrapStack.push(valueWrap);
- Object? value = _readWithNullableSer(br, valueSer, pack);
- typeWrapStack.pop();
- map[key] = value;
+
+ bool keyTrackRef = (chunkHeader & _trackingKeyRef) != 0;
+ bool valueTrackRef = (chunkHeader & _trackingValueRef) != 0;
+ bool keyDeclaredType = (chunkHeader & _keyDeclType) != 0;
+ bool valueDeclaredType = (chunkHeader & _valueDeclType) != 0;
+ int chunkSize = br.readUint8();
+
+ Serializer keySer;
+ if (keyDeclaredType && keyWrap?.ser != null) {
+ keySer = keyWrap!.ser!;
+ } else {
+ keySer = pack.xtypeResolver.readTypeInfo(br).ser;
+ }
+ Serializer valueSer;
+ if (valueDeclaredType && valueWrap?.ser != null) {
+ valueSer = valueWrap!.ser!;
+ } else {
+ valueSer = pack.xtypeResolver.readTypeInfo(br).ser;
}
- return map;
- }
- if (keyWrap.hasGenericsParam && !valueWrap.hasGenericsParam){
- // Traverse entries
- for (int i = 0; i < len; ++i) {
- typeWrapStack.push(keyWrap);
- Object? key = _readWithNullableSer(br, keySer, pack);
- typeWrapStack.pop();
- Object? value = _readWithNullableSer(br, valueSer, pack);
+ for (int i = 0; i < chunkSize; ++i) {
+ Object? key = _readWithSer(br, keySer, keyTrackRef, pack, keyWrap);
+ Object? value =
+ _readWithSer(br, valueSer, valueTrackRef, pack, valueWrap);
map[key] = value;
}
- return map;
- }
-
- for (int i = 0; i < len; ++i) {
- typeWrapStack.push(keyWrap);
- Object? key = _readWithNullableSer(br, keySer, pack);
- typeWrapStack.changeTop(valueWrap);
- Object? value = _readWithNullableSer(br, valueSer, pack);
- typeWrapStack.pop();
- map[key] = value;
+ remaining -= chunkSize;
}
return map;
}
- /// This code may look a bit lengthy, but to avoid unnecessary checks
multiple times in the loop
- /// (since it only needs to be done once), the outer judgment cases are
separated, making the code look a bit long
@override
void write(ByteWriter bw, covariant T v, SerializerPack pack) {
- bw.writeVarUint32Small7(v.length);
- TypeSpecWrap? typeWrap = pack.typeWrapStack.peek;
- if (typeWrap == null){
- // Traverse entries
- for (var entry in v.entries) {
- Object? key = entry.key;
- Object? value = entry.value;
- pack.forySer.xWriteRefNoSer(bw, key, pack);
- pack.forySer.xWriteRefNoSer(bw, value, pack);
- }
+ int mapSize = v.length;
+ bw.writeVarUint32Small7(mapSize);
+ if (mapSize == 0) {
return;
}
- var typeWrapStack = pack.typeWrapStack;
- TypeSpecWrap keyWrap = typeWrap.param0!;
- TypeSpecWrap valueWrap = typeWrap.param1!;
- Serializer? keySer = keyWrap.ser;
- Serializer? valueSer = valueWrap.ser;
- if (!keyWrap.hasGenericsParam && !valueWrap.hasGenericsParam){
- // Traverse entries
- for (var entry in v.entries) {
- Object? key = entry.key;
- Object? value = entry.value;
- _writeWithNullableSer(bw, key, keySer, pack);
- _writeWithNullableSer(bw, value, valueSer, pack);
- }
+ TypeSpecWrap? mapWrap = pack.typeWrapStack.peek;
+ TypeSpecWrap? keyWrap = mapWrap?.param0;
+ TypeSpecWrap? valueWrap = mapWrap?.param1;
+
+ Iterator<MapEntry<Object?, Object?>> iterator = v.entries.iterator;
+ if (!iterator.moveNext()) {
return;
}
+ MapEntry<Object?, Object?>? entry = iterator.current;
- if (keyWrap.hasGenericsParam && !valueWrap.hasGenericsParam){
- // Traverse entries
- for (var entry in v.entries) {
+ while (entry != null) {
+ while (entry != null) {
Object? key = entry.key;
Object? value = entry.value;
- typeWrapStack.push(keyWrap);
- _writeWithNullableSer(bw, key, keySer, pack);
- typeWrapStack.pop();
- _writeWithNullableSer(bw, value, valueSer, pack);
+ if (key != null && value != null) {
+ break;
+ }
+ _writeNullChunk(bw, key, value, keyWrap, valueWrap, pack);
+ if (iterator.moveNext()) {
+ entry = iterator.current;
+ } else {
+ entry = null;
+ }
}
- return;
+ if (entry == null) {
+ break;
+ }
+ entry = _writeNonNullChunk(bw, entry, iterator, keyWrap, valueWrap,
pack);
}
+ }
- if (!keyWrap.hasGenericsParam && valueWrap.hasGenericsParam){
- // Traverse entries
- for (var entry in v.entries) {
- Object? key = entry.key;
- Object? value = entry.value;
- _writeWithNullableSer(bw, key, keySer, pack);
- typeWrapStack.push(valueWrap);
- _writeWithNullableSer(bw, value, valueSer, pack);
- typeWrapStack.pop();
+ MapEntry<Object?, Object?>? _writeNonNullChunk(
+ ByteWriter bw,
+ MapEntry<Object?, Object?> entry,
+ Iterator<MapEntry<Object?, Object?>> iterator,
+ TypeSpecWrap? keyWrap,
+ TypeSpecWrap? valueWrap,
+ SerializerPack pack,
+ ) {
+ Object key0 = entry.key as Object;
+ Object value0 = entry.value as Object;
+ Type keyType = key0.runtimeType;
+ Type valueType = value0.runtimeType;
+
+ int chunkHeader = 0;
+ ByteWriter chunkWriter = ByteWriter();
+ Serializer keySer;
+ Serializer valueSer;
+
+ if (keyWrap != null && keyWrap.certainForSer && keyWrap.ser != null) {
+ chunkHeader |= _keyDeclType;
+ keySer = keyWrap.ser!;
+ } else {
+ final typeInfo =
+ pack.xtypeResolver.writeGetTypeInfo(chunkWriter, key0, pack);
+ keySer = typeInfo.ser;
+ }
+ if (valueWrap != null && valueWrap.certainForSer && valueWrap.ser != null)
{
+ chunkHeader |= _valueDeclType;
+ valueSer = valueWrap.ser!;
+ } else {
+ final typeInfo =
+ pack.xtypeResolver.writeGetTypeInfo(chunkWriter, value0, pack);
+ valueSer = typeInfo.ser;
+ }
+
+ bool trackKeyRef = keySer.writeRef;
+ bool trackValueRef = valueSer.writeRef;
+ if (trackKeyRef) {
+ chunkHeader |= _trackingKeyRef;
+ }
+ if (trackValueRef) {
+ chunkHeader |= _trackingValueRef;
+ }
+
+ int chunkSize = 0;
+ MapEntry<Object?, Object?>? current = entry;
+ while (current != null) {
+ Object? key = current.key;
+ Object? value = current.value;
+ if (key == null ||
+ value == null ||
+ key.runtimeType != keyType ||
+ value.runtimeType != valueType) {
+ break;
+ }
+ _writeWithSer(chunkWriter, key, keySer, trackKeyRef, pack, keyWrap);
+ _writeWithSer(
+ chunkWriter, value, valueSer, trackValueRef, pack, valueWrap);
+ ++chunkSize;
+ if (iterator.moveNext()) {
+ current = iterator.current;
+ } else {
+ current = null;
+ break;
+ }
+ if (chunkSize == _maxChunkSize) {
+ break;
}
+ }
+
+ bw.writeUint8(chunkHeader);
+ bw.writeUint8(chunkSize);
+ bw.writeBytes(chunkWriter.takeBytes());
+ return current;
+ }
+
+ void _writeNullChunk(
+ ByteWriter bw,
+ Object? key,
+ Object? value,
+ TypeSpecWrap? keyWrap,
+ TypeSpecWrap? valueWrap,
+ SerializerPack pack,
+ ) {
+ if (key != null) {
+ _writeNullValueChunk(bw, key, keyWrap, pack);
+ return;
+ }
+ if (value != null) {
+ _writeNullKeyChunk(bw, value, valueWrap, pack);
+ return;
+ }
+ bw.writeUint8(_kvNull);
+ }
+
+ void _writeNullValueChunk(
+ ByteWriter bw,
+ Object key,
+ TypeSpecWrap? keyWrap,
+ SerializerPack pack,
+ ) {
+ Serializer? keySer = keyWrap?.ser;
+ if (keyWrap != null && keyWrap.certainForSer && keySer != null) {
+ bool trackingRef = keySer.writeRef;
+ bw.writeUint8(trackingRef
+ ? _nullValueKeyDeclTypeTrackingRef
+ : _nullValueKeyDeclType);
+ _writeWithSer(bw, key, keySer, trackingRef, pack, keyWrap);
return;
}
- // Traverse entries
- for (var entry in v.entries) {
- Object? key = entry.key;
- Object? value = entry.value;
- typeWrapStack.push(keyWrap);
- _writeWithNullableSer(bw, key, keySer, pack);
- typeWrapStack.changeTop(valueWrap);
- _writeWithNullableSer(bw, value, valueSer, pack);
- typeWrapStack.pop();
+ bool trackingRef = keyWrap == null
+ ? true
+ : (keySer?.writeRef ?? _isRefTrackingEnabled(pack));
+ bw.writeUint8(_valueHasNull | (trackingRef ? _trackingKeyRef : 0));
+ _writeWithDynamic(bw, key, trackingRef, pack, keyWrap);
+ }
+
+ void _writeNullKeyChunk(
+ ByteWriter bw,
+ Object value,
+ TypeSpecWrap? valueWrap,
+ SerializerPack pack,
+ ) {
+ Serializer? valueSer = valueWrap?.ser;
+ if (valueWrap != null && valueWrap.certainForSer && valueSer != null) {
+ bool trackingRef = valueSer.writeRef;
+ bw.writeUint8(trackingRef
+ ? _nullKeyValueDeclTypeTrackingRef
+ : _nullKeyValueDeclType);
+ _writeWithSer(bw, value, valueSer, trackingRef, pack, valueWrap);
+ return;
+ }
+ bool trackingRef = valueWrap == null
+ ? true
+ : (valueSer?.writeRef ?? _isRefTrackingEnabled(pack));
+ bw.writeUint8(_keyHasNull | (trackingRef ? _trackingValueRef : 0));
+ _writeWithDynamic(bw, value, trackingRef, pack, valueWrap);
+ }
+
+ Object? _readNullChunkKey(
+ ByteReader br,
+ int chunkHeader,
+ TypeSpecWrap? keyWrap,
+ DeserializerPack pack,
+ ) {
+ bool trackRef = (chunkHeader & _trackingKeyRef) != 0;
+ bool keyDeclaredType = (chunkHeader & _keyDeclType) != 0;
+ if (keyDeclaredType && keyWrap?.ser != null) {
+ return _readWithSer(br, keyWrap!.ser!, trackRef, pack, keyWrap);
}
+ return _readWithDynamic(br, trackRef, pack, keyWrap);
}
- @inline
- void _writeWithNullableSer(ByteWriter bw, Object? v, Serializer? ser,
SerializerPack pack){
- ser == null ?
- pack.forySer.xWriteRefNoSer(bw, v, pack) :
- pack.forySer.xWriteRefWithSer(bw, ser, v, pack);
+ Object? _readNullChunkValue(
+ ByteReader br,
+ int chunkHeader,
+ TypeSpecWrap? valueWrap,
+ DeserializerPack pack,
+ ) {
+ bool trackRef = (chunkHeader & _trackingValueRef) != 0;
+ bool valueDeclaredType = (chunkHeader & _valueDeclType) != 0;
+ if (valueDeclaredType && valueWrap?.ser != null) {
+ return _readWithSer(br, valueWrap!.ser!, trackRef, pack, valueWrap);
+ }
+ return _readWithDynamic(br, trackRef, pack, valueWrap);
+ }
+
+ void _writeWithSer(
+ ByteWriter bw,
+ Object value,
+ Serializer ser,
+ bool trackRef,
+ SerializerPack pack,
+ TypeSpecWrap? wrap,
+ ) {
+ bool pushed = _pushWrapForWrite(wrap, pack);
+ if (trackRef) {
+ pack.forySer.xWriteRefWithSer(bw, ser, value, pack);
+ } else {
+ ser.write(bw, value, pack);
+ }
+ if (pushed) {
+ pack.typeWrapStack.pop();
+ }
+ }
+
+ Object? _readWithSer(
+ ByteReader br,
+ Serializer ser,
+ bool trackRef,
+ DeserializerPack pack,
+ TypeSpecWrap? wrap,
+ ) {
+ bool pushed = _pushWrapForRead(wrap, pack);
+ Object? value;
+ if (trackRef) {
+ value = pack.foryDeser.xReadRefWithSer(br, ser, pack);
+ } else {
+ value = ser.read(br, -1, pack);
+ }
+ if (pushed) {
+ pack.typeWrapStack.pop();
+ }
+ return value;
+ }
+
+ void _writeWithDynamic(
+ ByteWriter bw,
+ Object value,
+ bool trackRef,
+ SerializerPack pack,
+ TypeSpecWrap? wrap,
+ ) {
+ bool pushed = _pushWrapForWrite(wrap, pack);
+ if (trackRef) {
+ pack.forySer.xWriteRefNoSer(bw, value, pack);
+ } else {
+ pack.forySer.xWriteNonRefNoSer(bw, value, pack);
+ }
+ if (pushed) {
+ pack.typeWrapStack.pop();
+ }
+ }
+
+ Object _readWithDynamic(
+ ByteReader br,
+ bool trackRef,
+ DeserializerPack pack,
+ TypeSpecWrap? wrap,
+ ) {
+ bool pushed = _pushWrapForRead(wrap, pack);
+ Object value;
+ if (trackRef) {
+ value = pack.foryDeser.xReadRefNoSer(br, pack) as Object;
+ } else {
+ value = pack.foryDeser.xReadNonRefNoSer(br, pack);
+ }
+ if (pushed) {
+ pack.typeWrapStack.pop();
+ }
+ return value;
+ }
+
+ bool _pushWrapForWrite(TypeSpecWrap? wrap, SerializerPack pack) {
+ if (wrap != null && wrap.hasGenericsParam) {
+ pack.typeWrapStack.push(wrap);
+ return true;
+ }
+ return false;
+ }
+
+ bool _pushWrapForRead(TypeSpecWrap? wrap, DeserializerPack pack) {
+ if (wrap != null && wrap.hasGenericsParam) {
+ pack.typeWrapStack.push(wrap);
+ return true;
+ }
+ return false;
}
- @inline
- Object? _readWithNullableSer(ByteReader br, Serializer? ser,
DeserializerPack pack){
- return ser == null ?
- pack.foryDeser.xReadRefNoSer(br, pack) :
- pack.foryDeser.xReadRefWithSer(br, ser, pack);
+ bool _isRefTrackingEnabled(SerializerPack pack) {
+ return !identical(pack.refResolver, pack.noRefResolver);
}
}
diff --git
a/dart/packages/fory/lib/src/serializer/collection/set/set_serializer.dart
b/dart/packages/fory/lib/src/serializer/collection/set/set_serializer.dart
index 06428ed73..ad904d14e 100644
--- a/dart/packages/fory/lib/src/serializer/collection/set/set_serializer.dart
+++ b/dart/packages/fory/lib/src/serializer/collection/set/set_serializer.dart
@@ -25,6 +25,7 @@
/// we still need to implement this separately, which may introduce duplicate
code.
library;
+import 'package:fory/src/const/ref_flag.dart';
import 'package:fory/src/const/types.dart';
import 'package:fory/src/deserializer_pack.dart';
import 'package:fory/src/memory/byte_reader.dart';
@@ -33,9 +34,8 @@ import
'package:fory/src/serializer/collection/iterable_serializer.dart';
import 'package:fory/src/serializer/serializer.dart';
abstract base class SetSerializer extends IterableSerializer {
+ const SetSerializer(bool writeRef) : super(ObjType.SET, writeRef);
- const SetSerializer(bool writeRef): super(ObjType.SET, writeRef);
-
Set newSet(bool nullable);
@override
@@ -45,39 +45,74 @@ abstract base class SetSerializer extends
IterableSerializer {
Set set = newSet(
elemWrap == null || elemWrap.nullable,
);
- if (writeRef){
+ if (writeRef) {
pack.refResolver.setRefTheLatestId(set);
}
- if (elemWrap == null){
- for (int i = 0; i < num; ++i) {
- Object? o = pack.foryDeser.xReadRefNoSer(br, pack);
- set.add(o);
- }
+ if (num == 0) {
return set;
}
- if (elemWrap.hasGenericsParam){
+
+ int flags = br.readUint8();
+ bool hasGenericsParam = elemWrap != null && elemWrap.hasGenericsParam;
+ if (hasGenericsParam) {
pack.typeWrapStack.push(elemWrap);
}
- if (!elemWrap.certainForSer){
- for (int i = 0; i < num; ++i) {
- Object? o = pack.foryDeser.xReadRefNoSer(br, pack);
- set.add(o);
+
+ if ((flags & IterableSerializer.isSameTypeFlag) ==
+ IterableSerializer.isSameTypeFlag) {
+ Serializer? ser;
+ bool isDeclElemType =
+ (flags & IterableSerializer.isDeclElementTypeFlag) ==
+ IterableSerializer.isDeclElementTypeFlag;
+ if (isDeclElemType) {
+ ser = elemWrap?.ser;
+ }
+ if (ser == null) {
+ ser = pack.xtypeResolver.readTypeInfo(br).ser;
}
- }else {
- Serializer? ser = elemWrap.ser;
- if (ser == null){
+
+ if ((flags & IterableSerializer.trackingRefFlag) ==
+ IterableSerializer.trackingRefFlag) {
+ for (int i = 0; i < num; ++i) {
+ set.add(pack.foryDeser.xReadRefWithSer(br, ser, pack));
+ }
+ } else if ((flags & IterableSerializer.hasNullFlag) ==
+ IterableSerializer.hasNullFlag) {
for (int i = 0; i < num; ++i) {
- Object? o = pack.foryDeser.xReadRefNoSer(br, pack);
- set.add(o);
+ if (br.readInt8() == RefFlag.NULL.id) {
+ set.add(null);
+ } else {
+ set.add(ser.read(br, -1, pack));
+ }
}
- }else{
+ } else {
for (int i = 0; i < num; ++i) {
- Object? o = pack.foryDeser.xReadRefWithSer(br, ser, pack);
- set.add(o);
+ set.add(ser.read(br, -1, pack));
+ }
+ }
+ } else {
+ if ((flags & IterableSerializer.trackingRefFlag) ==
+ IterableSerializer.trackingRefFlag) {
+ for (int i = 0; i < num; ++i) {
+ set.add(pack.foryDeser.xReadRefNoSer(br, pack));
+ }
+ } else if ((flags & IterableSerializer.hasNullFlag) ==
+ IterableSerializer.hasNullFlag) {
+ for (int i = 0; i < num; ++i) {
+ if (br.readInt8() == RefFlag.NULL.id) {
+ set.add(null);
+ } else {
+ set.add(pack.foryDeser.xReadNonRefNoSer(br, pack));
+ }
+ }
+ } else {
+ for (int i = 0; i < num; ++i) {
+ set.add(pack.foryDeser.xReadNonRefNoSer(br, pack));
}
}
}
- if (elemWrap.hasGenericsParam){
+
+ if (hasGenericsParam) {
pack.typeWrapStack.pop();
}
return set;
diff --git
a/java/fory-core/src/test/java/org/apache/fory/xlang/DartXlangTest.java
b/java/fory-core/src/test/java/org/apache/fory/xlang/DartXlangTest.java
new file mode 100644
index 000000000..26fd0d0c1
--- /dev/null
+++ b/java/fory-core/src/test/java/org/apache/fory/xlang/DartXlangTest.java
@@ -0,0 +1,294 @@
+/*
+ * 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.xlang;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import org.testng.SkipException;
+import org.testng.annotations.Test;
+
+/** Executes cross-language tests against the Dart implementation. */
+@Test
+public class DartXlangTest extends XlangTestBase {
+ private static final String DART_EXECUTABLE = "dart";
+ private static final String DART_MODULE =
+ "packages/fory-test/test/cross_lang_test/xlang_test_main.dart";
+
+ private static final List<String> DART_BASE_COMMAND =
+ Arrays.asList(DART_EXECUTABLE, "run", DART_MODULE, "<DART_TESTCASE>");
+
+ private static final int DART_TESTCASE_INDEX = 3;
+
+ @Override
+ protected void ensurePeerReady() {
+ String enabled = System.getenv("FORY_DART_JAVA_CI");
+ if (!"1".equals(enabled)) {
+ throw new SkipException("Skipping DartXlangTest: FORY_DART_JAVA_CI not
set to 1");
+ }
+ boolean dartInstalled = true;
+ try {
+ Process process = new ProcessBuilder(DART_EXECUTABLE,
"--version").start();
+ int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ dartInstalled = false;
+ }
+ } catch (IOException | InterruptedException e) {
+ dartInstalled = false;
+ if (e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ if (!dartInstalled) {
+ throw new SkipException("Skipping DartXlangTest: dart not installed");
+ }
+ }
+
+ @Override
+ protected CommandContext buildCommandContext(String caseName, Path dataFile)
{
+ List<String> command = new ArrayList<>(DART_BASE_COMMAND);
+ command.set(DART_TESTCASE_INDEX, caseName);
+ Map<String, String> env = envBuilder(dataFile);
+ env.put("ENABLE_FORY_DEBUG_OUTPUT", "1");
+ return new CommandContext(command, env, new File("../../dart"));
+ }
+
+ //
============================================================================
+ // Test methods - duplicated from XlangTestBase for Maven Surefire discovery
+ //
============================================================================
+
+ @Test
+ public void testBuffer() throws java.io.IOException {
+ super.testBuffer();
+ }
+
+ @Test
+ public void testBufferVar() throws java.io.IOException {
+ super.testBufferVar();
+ }
+
+ @Test
+ public void testMurmurHash3() throws java.io.IOException {
+ super.testMurmurHash3();
+ }
+
+ @Test
+ public void testStringSerializer() throws Exception {
+ super.testStringSerializer();
+ }
+
+ @Test
+ public void testCrossLanguageSerializer() throws Exception {
+ super.testCrossLanguageSerializer();
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testSimpleStruct(boolean enableCodegen) throws
java.io.IOException {
+ super.testSimpleStruct(enableCodegen);
+ }
+
+ @Test
+ public void testSimpleNamedStructCodegenEnabled() throws java.io.IOException
{
+ super.testSimpleNamedStruct(false);
+ }
+
+ @Test
+ public void testSimpleNamedStructCodegenDisabled() throws
java.io.IOException {
+ super.testSimpleNamedStruct(false);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testList(boolean enableCodegen) throws java.io.IOException {
+ super.testList(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testMap(boolean enableCodegen) throws java.io.IOException {
+ super.testMap(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testInteger(boolean enableCodegen) throws java.io.IOException {
+ super.testInteger(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testItem(boolean enableCodegen) throws java.io.IOException {
+ super.testItem(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testColor(boolean enableCodegen) throws java.io.IOException {
+ super.testColor(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testStructWithList(boolean enableCodegen) throws
java.io.IOException {
+ super.testStructWithList(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testStructWithMap(boolean enableCodegen) throws
java.io.IOException {
+ super.testStructWithMap(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testCollectionElementRefOverride(boolean enableCodegen) throws
java.io.IOException {
+ super.testCollectionElementRefOverride(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testSkipIdCustom(boolean enableCodegen) throws
java.io.IOException {
+ super.testSkipIdCustom(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testSkipNameCustom(boolean enableCodegen) throws
java.io.IOException {
+ super.testSkipNameCustom(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testConsistentNamed(boolean enableCodegen) throws
java.io.IOException {
+ super.testConsistentNamed(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testStructVersionCheck(boolean enableCodegen) throws
java.io.IOException {
+ super.testStructVersionCheck(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testPolymorphicList(boolean enableCodegen) throws
java.io.IOException {
+ super.testPolymorphicList(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testPolymorphicMap(boolean enableCodegen) throws
java.io.IOException {
+ super.testPolymorphicMap(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testOneStringFieldSchemaConsistent(boolean enableCodegen) throws
java.io.IOException {
+ super.testOneStringFieldSchemaConsistent(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testOneStringFieldCompatible(boolean enableCodegen) throws
java.io.IOException {
+ super.testOneStringFieldCompatible(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testTwoStringFieldCompatible(boolean enableCodegen) throws
java.io.IOException {
+ super.testTwoStringFieldCompatible(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testSchemaEvolutionCompatible(boolean enableCodegen) throws
java.io.IOException {
+ super.testSchemaEvolutionCompatible(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testOneEnumFieldSchemaConsistent(boolean enableCodegen) throws
java.io.IOException {
+ super.testOneEnumFieldSchemaConsistent(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testOneEnumFieldCompatible(boolean enableCodegen) throws
java.io.IOException {
+ super.testOneEnumFieldCompatible(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testTwoEnumFieldCompatible(boolean enableCodegen) throws
java.io.IOException {
+ super.testTwoEnumFieldCompatible(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testEnumSchemaEvolutionCompatible(boolean enableCodegen) throws
java.io.IOException {
+ super.testEnumSchemaEvolutionCompatible(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testNullableFieldSchemaConsistentNotNull(boolean enableCodegen)
+ throws java.io.IOException {
+ super.testNullableFieldSchemaConsistentNotNull(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testNullableFieldSchemaConsistentNull(boolean enableCodegen)
+ throws java.io.IOException {
+ super.testNullableFieldSchemaConsistentNull(enableCodegen);
+ }
+
+ @Override
+ @Test(dataProvider = "enableCodegen")
+ public void testNullableFieldCompatibleNotNull(boolean enableCodegen) throws
java.io.IOException {
+ super.testNullableFieldCompatibleNotNull(enableCodegen);
+ }
+
+ @Override
+ @Test(dataProvider = "enableCodegen")
+ public void testNullableFieldCompatibleNull(boolean enableCodegen) throws
java.io.IOException {
+ super.testNullableFieldCompatibleNull(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testUnionXlang(boolean enableCodegen) throws java.io.IOException
{
+ super.testUnionXlang(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testRefSchemaConsistent(boolean enableCodegen) throws
java.io.IOException {
+ super.testRefSchemaConsistent(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testRefCompatible(boolean enableCodegen) throws
java.io.IOException {
+ super.testRefCompatible(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testCircularRefSchemaConsistent(boolean enableCodegen) throws
java.io.IOException {
+ super.testCircularRefSchemaConsistent(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testCircularRefCompatible(boolean enableCodegen) throws
java.io.IOException {
+ super.testCircularRefCompatible(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testUnsignedSchemaConsistent(boolean enableCodegen) throws
java.io.IOException {
+ super.testUnsignedSchemaConsistent(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testUnsignedSchemaConsistentSimple(boolean enableCodegen) throws
java.io.IOException {
+ super.testUnsignedSchemaConsistentSimple(enableCodegen);
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testUnsignedSchemaCompatible(boolean enableCodegen) throws
java.io.IOException {
+ super.testUnsignedSchemaCompatible(enableCodegen);
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]