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 e53cbf278 feat(java): generate java List for primitive array (#3247)
e53cbf278 is described below
commit e53cbf278ceefc3fd413287fdb0e4d4a7aaecd12
Author: Shawn Yang <[email protected]>
AuthorDate: Sat Jan 31 14:05:25 2026 +0800
feat(java): generate java List for primitive array (#3247)
## Why?
Default Java codegen for repeated integer fields should prefer
Int*/Uint*List for stable type identity while keeping array mode
available, and the runtime/tests need to treat these list types as
built-in for cross-language compatibility.
## What does this PR do?
- Add `java_array` field option and switch Java generator defaults for
repeated integer fields to Int*/Uint*List while preserving array
annotations when opted in.
- Extend FDL parsing to allow union repeated fields/options and improve
ref option handling for list/map fields.
- Treat primitive list types as builtin/primitive arrays in Java runtime
(FieldTypes/resolvers/fingerprint) and add union case conversion between
lists and arrays.
- Update C++/Go serializers for unsigned/float16 slice handling aligned
with the array protocol.
- Add collection IDL and cross-language roundtrip tests, plus Java
array/list compatibility test.
## Related issues
Closes #3246
## Does this PR introduce any user-facing change?
Yes. Java codegen defaults for repeated integer fields now use
Int*/Uint*List unless `use_java_array=true` is specified; union field
options and repeated union cases are now supported.
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
---
.gitignore | 4 +
compiler/fory_compiler/frontend/fdl/parser.py | 12 +-
compiler/fory_compiler/generators/java.py | 101 +++++++-
compiler/fory_compiler/generators/rust.py | 6 +
cpp/fory/serialization/unsigned_serializer.h | 118 ++++++----
go/fory/reader.go | 42 ++++
go/fory/slice_primitive.go | 232 ++++++++++++++++++-
go/fory/struct.go | 36 +++
go/fory/type_resolver.go | 8 +
go/fory/types.go | 20 +-
go/fory/writer.go | 45 ++++
integration_tests/idl_tests/cpp/main.cc | 137 +++++++++++
integration_tests/idl_tests/generate_idl.py | 2 +
.../idl_tests/go/idl_roundtrip_test.go | 205 +++++++++++++++++
integration_tests/idl_tests/idl/collection.fdl | 70 ++++++
.../apache/fory/idl_tests/IdlRoundTripTest.java | 178 ++++++++++++++-
.../idl_tests/python/src/idl_tests/roundtrip.py | 177 ++++++++++++++
integration_tests/idl_tests/rust/src/lib.rs | 1 +
.../idl_tests/rust/tests/idl_roundtrip.rs | 115 ++++++++++
.../java/org/apache/fory/collection/Int16List.java | 26 ++-
.../java/org/apache/fory/collection/Int32List.java | 26 ++-
.../java/org/apache/fory/collection/Int64List.java | 26 ++-
.../java/org/apache/fory/collection/Int8List.java | 26 ++-
.../org/apache/fory/collection/Uint16List.java | 26 ++-
.../org/apache/fory/collection/Uint32List.java | 26 ++-
.../org/apache/fory/collection/Uint64List.java | 26 ++-
.../java/org/apache/fory/collection/Uint8List.java | 26 ++-
.../main/java/org/apache/fory/meta/FieldTypes.java | 48 ++++
.../org/apache/fory/resolver/ClassResolver.java | 6 +
.../org/apache/fory/resolver/TypeResolver.java | 13 +-
.../org/apache/fory/resolver/XtypeResolver.java | 9 +
.../org/apache/fory/serializer/Serializers.java | 16 ++
.../apache/fory/serializer/UnionSerializer.java | 253 +++++++++++++--------
.../apache/fory/serializer/struct/Fingerprint.java | 52 +++++
.../main/java/org/apache/fory/type/TypeUtils.java | 28 +++
.../fory/serializer/PrimitiveSerializersTest.java | 123 ++++++++++
36 files changed, 2108 insertions(+), 157 deletions(-)
diff --git a/.gitignore b/.gitignore
index 3a07acae7..142f7b8f0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,9 +49,11 @@ integration_tests/idl_tests/go/monster/
integration_tests/idl_tests/go/optional_types/
integration_tests/idl_tests/go/tree/
integration_tests/idl_tests/go/graph/
+integration_tests/idl_tests/go/collection/
integration_tests/idl_tests/java/src/main/
integration_tests/idl_tests/go/root/
integration_tests/idl_tests/go/addressbook/
+integration_tests/idl_tests/java/src/main/java/collection/
integration_tests/idl_tests/java/src/main/java/addressbook/
integration_tests/idl_tests/java/src/main/java/complex_fbs/
integration_tests/idl_tests/java/src/main/java/complex_pb/
@@ -67,6 +69,7 @@ integration_tests/idl_tests/python/src/complex_fbs.py
integration_tests/idl_tests/python/src/complex_pb.py
integration_tests/idl_tests/python/src/monster.py
integration_tests/idl_tests/python/src/optional_types.py
+integration_tests/idl_tests/python/src/collection.py
integration_tests/idl_tests/python/src/tree.py
integration_tests/idl_tests/python/src/graph.py
integration_tests/idl_tests/python/src/root.py
@@ -77,6 +80,7 @@ integration_tests/idl_tests/rust/src/complex_fbs.rs
integration_tests/idl_tests/rust/src/complex_pb.rs
integration_tests/idl_tests/rust/src/monster.rs
integration_tests/idl_tests/rust/src/optional_types.rs
+integration_tests/idl_tests/rust/src/collection.rs
integration_tests/idl_tests/rust/src/tree.rs
integration_tests/idl_tests/rust/src/graph.rs
integration_tests/idl_tests/rust/src/root.rs
diff --git a/compiler/fory_compiler/frontend/fdl/parser.py
b/compiler/fory_compiler/frontend/fdl/parser.py
index 197d11e6e..feb627c40 100644
--- a/compiler/fory_compiler/frontend/fdl/parser.py
+++ b/compiler/fory_compiler/frontend/fdl/parser.py
@@ -67,6 +67,7 @@ KNOWN_FIELD_OPTIONS: Set[str] = {
"nullable",
"thread_safe_pointer",
"weak_ref",
+ "java_array",
}
KNOWN_REF_OPTIONS: Set[str] = {
@@ -530,17 +531,23 @@ class Parser:
if self.check(TokenType.OPTIONAL) or self.check(TokenType.REF):
raise self.error("Union cases do not support optional/ref
modifiers")
+
+ repeated = False
if self.check(TokenType.REPEATED):
- raise self.error("Union cases do not support repeated modifiers")
+ self.advance()
+ repeated = True
field_type = self.parse_type()
+ if repeated:
+ field_type = ListType(field_type,
location=self.make_location(start))
name = self.consume(TokenType.IDENT, "Expected union case name").value
self.consume(TokenType.EQUALS, "Expected '=' after union case name")
number_token = self.consume(TokenType.INT, "Expected union case id")
number = int(number_token.value)
+ field_options = {}
if self.check(TokenType.LBRACKET):
- raise self.error("Union cases do not support field options")
+ field_options = self.parse_field_options(name)
self.consume(TokenType.SEMI, "Expected ';' after union case")
@@ -549,6 +556,7 @@ class Parser:
field_type=field_type,
number=number,
tag_id=number,
+ options=field_options,
line=start.line,
column=start.column,
location=self.make_location(start),
diff --git a/compiler/fory_compiler/generators/java.py
b/compiler/fory_compiler/generators/java.py
index 47f2e7cf7..11c479e10 100644
--- a/compiler/fory_compiler/generators/java.py
+++ b/compiler/fory_compiler/generators/java.py
@@ -283,6 +283,24 @@ class JavaGenerator(BaseGenerator):
PrimitiveKind.FLOAT64: "double[]",
}
+ # Primitive list types for repeated integer fields (default mode)
+ PRIMITIVE_LIST_MAP = {
+ PrimitiveKind.INT8: "Int8List",
+ PrimitiveKind.INT16: "Int16List",
+ PrimitiveKind.INT32: "Int32List",
+ PrimitiveKind.VARINT32: "Int32List",
+ PrimitiveKind.INT64: "Int64List",
+ PrimitiveKind.VARINT64: "Int64List",
+ PrimitiveKind.TAGGED_INT64: "Int64List",
+ PrimitiveKind.UINT8: "Uint8List",
+ PrimitiveKind.UINT16: "Uint16List",
+ PrimitiveKind.UINT32: "Uint32List",
+ PrimitiveKind.VAR_UINT32: "Uint32List",
+ PrimitiveKind.UINT64: "Uint64List",
+ PrimitiveKind.VAR_UINT64: "Uint64List",
+ PrimitiveKind.TAGGED_UINT64: "Uint64List",
+ }
+
def generate(self) -> List[GeneratedFile]:
"""Generate Java files for the schema.
@@ -588,6 +606,7 @@ class JavaGenerator(BaseGenerator):
imports,
field.element_optional,
field.element_ref,
+ field,
)
def has_array_field_recursive(self, message: Message) -> bool:
@@ -701,6 +720,17 @@ class JavaGenerator(BaseGenerator):
case_enum_name = self.to_upper_snake_case(field.name)
case_type = self.get_union_case_type(field)
cast_type = self.get_union_case_cast_type(field)
+ wrap_array_type: Optional[str] = None
+ wrap_list_type: Optional[str] = None
+ if (
+ isinstance(field.field_type, ListType)
+ and isinstance(field.field_type.element_type, PrimitiveType)
+ and field.field_type.element_type.kind in
self.PRIMITIVE_LIST_MAP
+ and not self.java_array(field)
+ ):
+ kind = field.field_type.element_type.kind
+ wrap_list_type = self.PRIMITIVE_LIST_MAP[kind]
+ wrap_array_type = self.PRIMITIVE_ARRAY_MAP[kind]
lines.append(f"{ind} public boolean has{case_name}() {{")
lines.append(
@@ -716,6 +746,12 @@ class JavaGenerator(BaseGenerator):
f'{ind} throw new
IllegalStateException("{union.name} is not {case_enum_name}");'
)
lines.append(f"{ind} }}")
+ if wrap_array_type and wrap_list_type:
+ lines.append(f"{ind} if (value instanceof
{wrap_array_type}) {{")
+ lines.append(
+ f"{ind} value = new
{wrap_list_type}(({wrap_array_type}) value);"
+ )
+ lines.append(f"{ind} }}")
lines.append(f"{ind} return ({cast_type}) value;")
lines.append(f"{ind} }}")
lines.append("")
@@ -765,6 +801,7 @@ class JavaGenerator(BaseGenerator):
False,
field.element_optional,
field.element_ref,
+ field,
)
def get_union_case_cast_type(self, field: Field) -> str:
@@ -809,6 +846,34 @@ class JavaGenerator(BaseGenerator):
}
return primitive_type_ids.get(kind, "Types.UNKNOWN")
if isinstance(field.field_type, ListType):
+ if (
+ isinstance(field.field_type.element_type, PrimitiveType)
+ and not field.element_optional
+ and not field.element_ref
+ ):
+ kind = field.field_type.element_type.kind
+ array_type_ids = {
+ PrimitiveKind.BOOL: "Types.BOOL_ARRAY",
+ PrimitiveKind.INT8: "Types.INT8_ARRAY",
+ PrimitiveKind.INT16: "Types.INT16_ARRAY",
+ PrimitiveKind.INT32: "Types.INT32_ARRAY",
+ PrimitiveKind.VARINT32: "Types.INT32_ARRAY",
+ PrimitiveKind.INT64: "Types.INT64_ARRAY",
+ PrimitiveKind.VARINT64: "Types.INT64_ARRAY",
+ PrimitiveKind.TAGGED_INT64: "Types.INT64_ARRAY",
+ PrimitiveKind.UINT8: "Types.UINT8_ARRAY",
+ PrimitiveKind.UINT16: "Types.UINT16_ARRAY",
+ PrimitiveKind.UINT32: "Types.UINT32_ARRAY",
+ PrimitiveKind.VAR_UINT32: "Types.UINT32_ARRAY",
+ PrimitiveKind.UINT64: "Types.UINT64_ARRAY",
+ PrimitiveKind.VAR_UINT64: "Types.UINT64_ARRAY",
+ PrimitiveKind.TAGGED_UINT64: "Types.UINT64_ARRAY",
+ PrimitiveKind.FLOAT16: "Types.FLOAT16_ARRAY",
+ PrimitiveKind.FLOAT32: "Types.FLOAT32_ARRAY",
+ PrimitiveKind.FLOAT64: "Types.FLOAT64_ARRAY",
+ }
+ if kind in array_type_ids:
+ return array_type_ids[kind]
return "Types.LIST"
if isinstance(field.field_type, MapType):
return "Types.MAP"
@@ -983,6 +1048,7 @@ class JavaGenerator(BaseGenerator):
nullable,
field.element_optional,
field.element_ref,
+ field,
)
lines.append(f"private {java_type} {self.to_camel_case(field.name)};")
@@ -1003,6 +1069,7 @@ class JavaGenerator(BaseGenerator):
nullable,
field.element_optional,
field.element_ref,
+ field,
)
field_name = self.to_camel_case(field.name)
pascal_name = self.to_pascal_case(field.name)
@@ -1027,6 +1094,7 @@ class JavaGenerator(BaseGenerator):
nullable: bool = False,
element_optional: bool = False,
element_ref: bool = False,
+ field: Optional[Field] = None,
) -> str:
"""Generate Java type string."""
if isinstance(field_type, PrimitiveType):
@@ -1043,13 +1111,18 @@ class JavaGenerator(BaseGenerator):
return field_type.name
elif isinstance(field_type, ListType):
- # Use primitive arrays for numeric types
+ # Use primitive arrays for numeric types, or primitive lists by
default for integers
if isinstance(field_type.element_type, PrimitiveType):
if (
field_type.element_type.kind in self.PRIMITIVE_ARRAY_MAP
and not element_optional
and not element_ref
):
+ if (
+ field_type.element_type.kind in self.PRIMITIVE_LIST_MAP
+ and not self.java_array(field)
+ ):
+ return
self.PRIMITIVE_LIST_MAP[field_type.element_type.kind]
return
self.PRIMITIVE_ARRAY_MAP[field_type.element_type.kind]
element_type = self.generate_type(field_type.element_type, True)
if self.is_ref_target_type(field_type.element_type):
@@ -1075,6 +1148,7 @@ class JavaGenerator(BaseGenerator):
imports: Set[str],
element_optional: bool = False,
element_ref: bool = False,
+ field: Optional[Field] = None,
):
"""Collect required imports for a field type."""
if isinstance(field_type, PrimitiveType):
@@ -1091,7 +1165,15 @@ class JavaGenerator(BaseGenerator):
and not element_optional
and not element_ref
):
- return # No import needed for primitive arrays
+ if (
+ field_type.element_type.kind in self.PRIMITIVE_LIST_MAP
+ and not self.java_array(field)
+ ):
+ imports.add(
+ "org.apache.fory.collection."
+ +
self.PRIMITIVE_LIST_MAP[field_type.element_type.kind]
+ )
+ return # No import needed for primitive arrays or
primitive lists
imports.add("java.util.List")
if self.is_ref_target_type(field_type.element_type):
imports.add("org.apache.fory.annotation.Ref")
@@ -1115,6 +1197,7 @@ class JavaGenerator(BaseGenerator):
imports,
field.element_optional,
field.element_ref,
+ field,
)
self.collect_integer_imports(field.field_type, imports)
self.collect_array_imports(field, imports)
@@ -1127,10 +1210,17 @@ class JavaGenerator(BaseGenerator):
resolved = self.schema.get_type(field_type.name)
return isinstance(resolved, (Message, Union))
+ def java_array(self, field: Optional[Field]) -> bool:
+ if field is None:
+ return False
+ return bool(field.options.get("java_array"))
+
def collect_array_imports(self, field: Field, imports: Set[str]) -> None:
"""Collect imports for primitive array type annotations."""
if not isinstance(field.field_type, ListType):
return
+ if not self.java_array(field):
+ return
if field.element_optional or field.element_ref:
return
element_type = field.field_type.element_type
@@ -1207,6 +1297,8 @@ class JavaGenerator(BaseGenerator):
"""Return array type annotation for primitive list fields."""
if not isinstance(field.field_type, ListType):
return None
+ if not self.java_array(field):
+ return None
if field.element_optional or field.element_ref:
return None
element_type = field.field_type.element_type
@@ -1245,6 +1337,11 @@ class JavaGenerator(BaseGenerator):
return field.field_type.kind == PrimitiveKind.BYTES
if isinstance(field.field_type, ListType):
if isinstance(field.field_type.element_type, PrimitiveType):
+ if (
+ field.field_type.element_type.kind in
self.PRIMITIVE_LIST_MAP
+ and not self.java_array(field)
+ ):
+ return False
return (
field.field_type.element_type.kind in
self.PRIMITIVE_ARRAY_MAP
and not field.element_optional
diff --git a/compiler/fory_compiler/generators/rust.py
b/compiler/fory_compiler/generators/rust.py
index 27daefe9d..777fce9a5 100644
--- a/compiler/fory_compiler/generators/rust.py
+++ b/compiler/fory_compiler/generators/rust.py
@@ -203,8 +203,12 @@ class RustGenerator(BaseGenerator):
uses.add("use std::sync::OnceLock")
for message in self.schema.messages:
+ if self.is_imported_type(message):
+ continue
self.collect_message_uses(message, uses)
for union in self.schema.unions:
+ if self.is_imported_type(union):
+ continue
self.collect_union_uses(union, uses)
# License header
@@ -359,6 +363,8 @@ class RustGenerator(BaseGenerator):
has_any = any(
self.field_type_has_any(field.field_type) for field in union.fields
)
+ if self.to_pascal_case(union.name) != union.name:
+ lines.append("#[allow(non_camel_case_types)]")
derives = ["ForyObject", "Debug"]
if not has_any:
derives.extend(["Clone", "PartialEq"])
diff --git a/cpp/fory/serialization/unsigned_serializer.h
b/cpp/fory/serialization/unsigned_serializer.h
index 809e0136b..b6961b55e 100644
--- a/cpp/fory/serialization/unsigned_serializer.h
+++ b/cpp/fory/serialization/unsigned_serializer.h
@@ -25,6 +25,7 @@
#include "fory/util/error.h"
#include <array>
#include <cstdint>
+#include <limits>
#include <vector>
namespace fory {
@@ -753,17 +754,22 @@ template <> struct Serializer<std::vector<uint16_t>> {
static inline void write_data(const std::vector<uint16_t> &vec,
WriteContext &ctx) {
+ uint64_t total_bytes = static_cast<uint64_t>(vec.size()) *
sizeof(uint16_t);
+ if (total_bytes > std::numeric_limits<uint32_t>::max()) {
+ ctx.set_error(Error::invalid("Vector byte size exceeds uint32_t range"));
+ return;
+ }
Buffer &buffer = ctx.buffer();
- size_t data_size = vec.size() * sizeof(uint16_t);
- size_t max_size = 8 + data_size;
+ size_t max_size = 8 + static_cast<size_t>(total_bytes);
buffer.grow(static_cast<uint32_t>(max_size));
uint32_t writer_index = buffer.writer_index();
writer_index +=
- buffer.put_var_uint32(writer_index, static_cast<uint32_t>(vec.size()));
- if (!vec.empty()) {
- buffer.unsafe_put(writer_index, vec.data(), data_size);
+ buffer.put_var_uint32(writer_index,
static_cast<uint32_t>(total_bytes));
+ if (total_bytes > 0) {
+ buffer.unsafe_put(writer_index, vec.data(),
+ static_cast<uint32_t>(total_bytes));
}
- buffer.writer_index(writer_index + static_cast<uint32_t>(data_size));
+ buffer.writer_index(writer_index + static_cast<uint32_t>(total_bytes));
}
static inline void write_data_generic(const std::vector<uint16_t> &vec,
@@ -788,16 +794,24 @@ template <> struct Serializer<std::vector<uint16_t>> {
}
static inline std::vector<uint16_t> read_data(ReadContext &ctx) {
- uint32_t length = ctx.read_var_uint32(ctx.error());
- if (FORY_PREDICT_FALSE(length * sizeof(uint16_t) >
- ctx.buffer().remaining_size())) {
- ctx.set_error(
- Error::invalid_data("Invalid length: " + std::to_string(length)));
+ uint32_t total_bytes = ctx.read_var_uint32(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return std::vector<uint16_t>();
+ }
+ if (total_bytes % sizeof(uint16_t) != 0) {
+ ctx.set_error(Error::invalid_data("Invalid length: " +
+ std::to_string(total_bytes)));
return std::vector<uint16_t>();
}
+ if (FORY_PREDICT_FALSE(total_bytes > ctx.buffer().remaining_size())) {
+ ctx.set_error(Error::invalid_data("Invalid length: " +
+ std::to_string(total_bytes)));
+ return std::vector<uint16_t>();
+ }
+ size_t length = total_bytes / sizeof(uint16_t);
std::vector<uint16_t> vec(length);
- if (length > 0) {
- ctx.read_bytes(vec.data(), length * sizeof(uint16_t), ctx.error());
+ if (total_bytes > 0) {
+ ctx.read_bytes(vec.data(), total_bytes, ctx.error());
}
return vec;
}
@@ -842,17 +856,22 @@ template <> struct Serializer<std::vector<uint32_t>> {
static inline void write_data(const std::vector<uint32_t> &vec,
WriteContext &ctx) {
+ uint64_t total_bytes = static_cast<uint64_t>(vec.size()) *
sizeof(uint32_t);
+ if (total_bytes > std::numeric_limits<uint32_t>::max()) {
+ ctx.set_error(Error::invalid("Vector byte size exceeds uint32_t range"));
+ return;
+ }
Buffer &buffer = ctx.buffer();
- size_t data_size = vec.size() * sizeof(uint32_t);
- size_t max_size = 8 + data_size;
+ size_t max_size = 8 + static_cast<size_t>(total_bytes);
buffer.grow(static_cast<uint32_t>(max_size));
uint32_t writer_index = buffer.writer_index();
writer_index +=
- buffer.put_var_uint32(writer_index, static_cast<uint32_t>(vec.size()));
- if (!vec.empty()) {
- buffer.unsafe_put(writer_index, vec.data(), data_size);
+ buffer.put_var_uint32(writer_index,
static_cast<uint32_t>(total_bytes));
+ if (total_bytes > 0) {
+ buffer.unsafe_put(writer_index, vec.data(),
+ static_cast<uint32_t>(total_bytes));
}
- buffer.writer_index(writer_index + static_cast<uint32_t>(data_size));
+ buffer.writer_index(writer_index + static_cast<uint32_t>(total_bytes));
}
static inline void write_data_generic(const std::vector<uint32_t> &vec,
@@ -877,16 +896,24 @@ template <> struct Serializer<std::vector<uint32_t>> {
}
static inline std::vector<uint32_t> read_data(ReadContext &ctx) {
- uint32_t length = ctx.read_var_uint32(ctx.error());
- if (FORY_PREDICT_FALSE(length * sizeof(uint32_t) >
- ctx.buffer().remaining_size())) {
- ctx.set_error(
- Error::invalid_data("Invalid length: " + std::to_string(length)));
+ uint32_t total_bytes = ctx.read_var_uint32(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
return std::vector<uint32_t>();
}
+ if (total_bytes % sizeof(uint32_t) != 0) {
+ ctx.set_error(Error::invalid_data("Invalid length: " +
+ std::to_string(total_bytes)));
+ return std::vector<uint32_t>();
+ }
+ if (FORY_PREDICT_FALSE(total_bytes > ctx.buffer().remaining_size())) {
+ ctx.set_error(Error::invalid_data("Invalid length: " +
+ std::to_string(total_bytes)));
+ return std::vector<uint32_t>();
+ }
+ size_t length = total_bytes / sizeof(uint32_t);
std::vector<uint32_t> vec(length);
- if (length > 0) {
- ctx.read_bytes(vec.data(), length * sizeof(uint32_t), ctx.error());
+ if (total_bytes > 0) {
+ ctx.read_bytes(vec.data(), total_bytes, ctx.error());
}
return vec;
}
@@ -931,17 +958,22 @@ template <> struct Serializer<std::vector<uint64_t>> {
static inline void write_data(const std::vector<uint64_t> &vec,
WriteContext &ctx) {
+ uint64_t total_bytes = static_cast<uint64_t>(vec.size()) *
sizeof(uint64_t);
+ if (total_bytes > std::numeric_limits<uint32_t>::max()) {
+ ctx.set_error(Error::invalid("Vector byte size exceeds uint32_t range"));
+ return;
+ }
Buffer &buffer = ctx.buffer();
- size_t data_size = vec.size() * sizeof(uint64_t);
- size_t max_size = 8 + data_size;
+ size_t max_size = 8 + static_cast<size_t>(total_bytes);
buffer.grow(static_cast<uint32_t>(max_size));
uint32_t writer_index = buffer.writer_index();
writer_index +=
- buffer.put_var_uint32(writer_index, static_cast<uint32_t>(vec.size()));
- if (!vec.empty()) {
- buffer.unsafe_put(writer_index, vec.data(), data_size);
+ buffer.put_var_uint32(writer_index,
static_cast<uint32_t>(total_bytes));
+ if (total_bytes > 0) {
+ buffer.unsafe_put(writer_index, vec.data(),
+ static_cast<uint32_t>(total_bytes));
}
- buffer.writer_index(writer_index + static_cast<uint32_t>(data_size));
+ buffer.writer_index(writer_index + static_cast<uint32_t>(total_bytes));
}
static inline void write_data_generic(const std::vector<uint64_t> &vec,
@@ -966,16 +998,24 @@ template <> struct Serializer<std::vector<uint64_t>> {
}
static inline std::vector<uint64_t> read_data(ReadContext &ctx) {
- uint32_t length = ctx.read_var_uint32(ctx.error());
- if (FORY_PREDICT_FALSE(length * sizeof(uint64_t) >
- ctx.buffer().remaining_size())) {
- ctx.set_error(
- Error::invalid_data("Invalid length: " + std::to_string(length)));
+ uint32_t total_bytes = ctx.read_var_uint32(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return std::vector<uint64_t>();
+ }
+ if (total_bytes % sizeof(uint64_t) != 0) {
+ ctx.set_error(Error::invalid_data("Invalid length: " +
+ std::to_string(total_bytes)));
+ return std::vector<uint64_t>();
+ }
+ if (FORY_PREDICT_FALSE(total_bytes > ctx.buffer().remaining_size())) {
+ ctx.set_error(Error::invalid_data("Invalid length: " +
+ std::to_string(total_bytes)));
return std::vector<uint64_t>();
}
+ size_t length = total_bytes / sizeof(uint64_t);
std::vector<uint64_t> vec(length);
- if (length > 0) {
- ctx.read_bytes(vec.data(), length * sizeof(uint64_t), ctx.error());
+ if (total_bytes > 0) {
+ ctx.read_bytes(vec.data(), total_bytes, ctx.error());
}
return vec;
}
diff --git a/go/fory/reader.go b/go/fory/reader.go
index dd9a837bb..e8fd1a863 100644
--- a/go/fory/reader.go
+++ b/go/fory/reader.go
@@ -325,6 +325,48 @@ func (c *ReadContext) ReadInt64Slice(refMode RefMode,
readType bool) []int64 {
return ReadInt64Slice(c.buffer, err)
}
+// ReadUint16Slice reads []uint16 with optional ref/type info
+func (c *ReadContext) ReadUint16Slice(refMode RefMode, readType bool) []uint16
{
+ err := c.Err()
+ if refMode != RefModeNone {
+ if c.buffer.ReadInt8(err) == NullFlag {
+ return nil
+ }
+ }
+ if readType {
+ _ = c.buffer.ReadVarUint32Small7(err)
+ }
+ return ReadUint16Slice(c.buffer, err)
+}
+
+// ReadUint32Slice reads []uint32 with optional ref/type info
+func (c *ReadContext) ReadUint32Slice(refMode RefMode, readType bool) []uint32
{
+ err := c.Err()
+ if refMode != RefModeNone {
+ if c.buffer.ReadInt8(err) == NullFlag {
+ return nil
+ }
+ }
+ if readType {
+ _ = c.buffer.ReadVarUint32Small7(err)
+ }
+ return ReadUint32Slice(c.buffer, err)
+}
+
+// ReadUint64Slice reads []uint64 with optional ref/type info
+func (c *ReadContext) ReadUint64Slice(refMode RefMode, readType bool) []uint64
{
+ err := c.Err()
+ if refMode != RefModeNone {
+ if c.buffer.ReadInt8(err) == NullFlag {
+ return nil
+ }
+ }
+ if readType {
+ _ = c.buffer.ReadVarUint32Small7(err)
+ }
+ return ReadUint64Slice(c.buffer, err)
+}
+
// ReadIntSlice reads []int with optional ref/type info
func (c *ReadContext) ReadIntSlice(refMode RefMode, readType bool) []int {
err := c.Err()
diff --git a/go/fory/slice_primitive.go b/go/fory/slice_primitive.go
index cf83ab76b..0b6c7dc05 100644
--- a/go/fory/slice_primitive.go
+++ b/go/fory/slice_primitive.go
@@ -59,8 +59,8 @@ func (s byteSliceSerializer) Read(ctx *ReadContext, refMode
RefMode, readType bo
if done || ctx.HasError() {
return
}
- if readType && typeId != uint32(BINARY) {
- ctx.SetError(DeserializationErrorf("slice type mismatch:
expected BINARY (%d), got %d", BINARY, typeId))
+ if readType && typeId != uint32(BINARY) && typeId !=
uint32(UINT8_ARRAY) {
+ ctx.SetError(DeserializationErrorf("slice type mismatch:
expected BINARY (%d) or UINT8_ARRAY (%d), got %d", BINARY, UINT8_ARRAY, typeId))
return
}
s.ReadData(ctx, value)
@@ -291,6 +291,120 @@ func (s int64SliceSerializer) ReadData(ctx *ReadContext,
value reflect.Value) {
*(*[]int64)(value.Addr().UnsafePointer()) =
ReadInt64Slice(ctx.Buffer(), ctx.Err())
}
+// ============================================================================
+// uint16SliceSerializer - optimized []uint16 serialization
+// ============================================================================
+
+type uint16SliceSerializer struct{}
+
+func (s uint16SliceSerializer) WriteData(ctx *WriteContext, value
reflect.Value) {
+ WriteUint16Slice(ctx.Buffer(), value.Interface().([]uint16))
+}
+
+func (s uint16SliceSerializer) Write(ctx *WriteContext, refMode RefMode,
writeType bool, hasGenerics bool, value reflect.Value) {
+ done := writeSliceRefAndType(ctx, refMode, writeType, value,
UINT16_ARRAY)
+ if done || ctx.HasError() {
+ return
+ }
+ s.WriteData(ctx, value)
+}
+
+func (s uint16SliceSerializer) Read(ctx *ReadContext, refMode RefMode,
readType bool, hasGenerics bool, value reflect.Value) {
+ done, typeId := readSliceRefAndType(ctx, refMode, readType, value)
+ if done || ctx.HasError() {
+ return
+ }
+ if readType && typeId != uint32(UINT16_ARRAY) {
+ ctx.SetError(DeserializationErrorf("slice type mismatch:
expected UINT16_ARRAY (%d), got %d", UINT16_ARRAY, typeId))
+ return
+ }
+ s.ReadData(ctx, value)
+}
+
+func (s uint16SliceSerializer) ReadWithTypeInfo(ctx *ReadContext, refMode
RefMode, typeInfo *TypeInfo, value reflect.Value) {
+ s.Read(ctx, refMode, false, false, value)
+}
+
+func (s uint16SliceSerializer) ReadData(ctx *ReadContext, value reflect.Value)
{
+ *(*[]uint16)(value.Addr().UnsafePointer()) =
ReadUint16Slice(ctx.Buffer(), ctx.Err())
+}
+
+// ============================================================================
+// uint32SliceSerializer - optimized []uint32 serialization
+// ============================================================================
+
+type uint32SliceSerializer struct{}
+
+func (s uint32SliceSerializer) WriteData(ctx *WriteContext, value
reflect.Value) {
+ WriteUint32Slice(ctx.Buffer(), value.Interface().([]uint32))
+}
+
+func (s uint32SliceSerializer) Write(ctx *WriteContext, refMode RefMode,
writeType bool, hasGenerics bool, value reflect.Value) {
+ done := writeSliceRefAndType(ctx, refMode, writeType, value,
UINT32_ARRAY)
+ if done || ctx.HasError() {
+ return
+ }
+ s.WriteData(ctx, value)
+}
+
+func (s uint32SliceSerializer) Read(ctx *ReadContext, refMode RefMode,
readType bool, hasGenerics bool, value reflect.Value) {
+ done, typeId := readSliceRefAndType(ctx, refMode, readType, value)
+ if done || ctx.HasError() {
+ return
+ }
+ if readType && typeId != uint32(UINT32_ARRAY) {
+ ctx.SetError(DeserializationErrorf("slice type mismatch:
expected UINT32_ARRAY (%d), got %d", UINT32_ARRAY, typeId))
+ return
+ }
+ s.ReadData(ctx, value)
+}
+
+func (s uint32SliceSerializer) ReadWithTypeInfo(ctx *ReadContext, refMode
RefMode, typeInfo *TypeInfo, value reflect.Value) {
+ s.Read(ctx, refMode, false, false, value)
+}
+
+func (s uint32SliceSerializer) ReadData(ctx *ReadContext, value reflect.Value)
{
+ *(*[]uint32)(value.Addr().UnsafePointer()) =
ReadUint32Slice(ctx.Buffer(), ctx.Err())
+}
+
+// ============================================================================
+// uint64SliceSerializer - optimized []uint64 serialization
+// ============================================================================
+
+type uint64SliceSerializer struct{}
+
+func (s uint64SliceSerializer) WriteData(ctx *WriteContext, value
reflect.Value) {
+ WriteUint64Slice(ctx.Buffer(), value.Interface().([]uint64))
+}
+
+func (s uint64SliceSerializer) Write(ctx *WriteContext, refMode RefMode,
writeType bool, hasGenerics bool, value reflect.Value) {
+ done := writeSliceRefAndType(ctx, refMode, writeType, value,
UINT64_ARRAY)
+ if done || ctx.HasError() {
+ return
+ }
+ s.WriteData(ctx, value)
+}
+
+func (s uint64SliceSerializer) Read(ctx *ReadContext, refMode RefMode,
readType bool, hasGenerics bool, value reflect.Value) {
+ done, typeId := readSliceRefAndType(ctx, refMode, readType, value)
+ if done || ctx.HasError() {
+ return
+ }
+ if readType && typeId != uint32(UINT64_ARRAY) {
+ ctx.SetError(DeserializationErrorf("slice type mismatch:
expected UINT64_ARRAY (%d), got %d", UINT64_ARRAY, typeId))
+ return
+ }
+ s.ReadData(ctx, value)
+}
+
+func (s uint64SliceSerializer) ReadWithTypeInfo(ctx *ReadContext, refMode
RefMode, typeInfo *TypeInfo, value reflect.Value) {
+ s.Read(ctx, refMode, false, false, value)
+}
+
+func (s uint64SliceSerializer) ReadData(ctx *ReadContext, value reflect.Value)
{
+ *(*[]uint64)(value.Addr().UnsafePointer()) =
ReadUint64Slice(ctx.Buffer(), ctx.Err())
+}
+
// ============================================================================
// float32SliceSerializer - optimized []float32 serialization
// ============================================================================
@@ -754,6 +868,120 @@ func ReadInt64Slice(buf *ByteBuffer, err *Error) []int64 {
return result
}
+// WriteUint16Slice writes []uint16 to buffer using ARRAY protocol
+//
+//go:inline
+func WriteUint16Slice(buf *ByteBuffer, value []uint16) {
+ size := len(value) * 2
+ buf.WriteLength(size)
+ if len(value) > 0 {
+ if isLittleEndian {
+
buf.WriteBinary(unsafe.Slice((*byte)(unsafe.Pointer(&value[0])), size))
+ } else {
+ for i := 0; i < len(value); i++ {
+ buf.WriteInt16(int16(value[i]))
+ }
+ }
+ }
+}
+
+// ReadUint16Slice reads []uint16 from buffer using ARRAY protocol
+//
+//go:inline
+func ReadUint16Slice(buf *ByteBuffer, err *Error) []uint16 {
+ size := buf.ReadLength(err)
+ length := size / 2
+ if length == 0 {
+ return make([]uint16, 0)
+ }
+ result := make([]uint16, length)
+ if isLittleEndian {
+ raw := buf.ReadBinary(size, err)
+ copy(unsafe.Slice((*byte)(unsafe.Pointer(&result[0])), size),
raw)
+ } else {
+ for i := 0; i < length; i++ {
+ result[i] = uint16(buf.ReadInt16(err))
+ }
+ }
+ return result
+}
+
+// WriteUint32Slice writes []uint32 to buffer using ARRAY protocol
+//
+//go:inline
+func WriteUint32Slice(buf *ByteBuffer, value []uint32) {
+ size := len(value) * 4
+ buf.WriteLength(size)
+ if len(value) > 0 {
+ if isLittleEndian {
+
buf.WriteBinary(unsafe.Slice((*byte)(unsafe.Pointer(&value[0])), size))
+ } else {
+ for i := 0; i < len(value); i++ {
+ buf.WriteInt32(int32(value[i]))
+ }
+ }
+ }
+}
+
+// ReadUint32Slice reads []uint32 from buffer using ARRAY protocol
+//
+//go:inline
+func ReadUint32Slice(buf *ByteBuffer, err *Error) []uint32 {
+ size := buf.ReadLength(err)
+ length := size / 4
+ if length == 0 {
+ return make([]uint32, 0)
+ }
+ result := make([]uint32, length)
+ if isLittleEndian {
+ raw := buf.ReadBinary(size, err)
+ copy(unsafe.Slice((*byte)(unsafe.Pointer(&result[0])), size),
raw)
+ } else {
+ for i := 0; i < length; i++ {
+ result[i] = uint32(buf.ReadInt32(err))
+ }
+ }
+ return result
+}
+
+// WriteUint64Slice writes []uint64 to buffer using ARRAY protocol
+//
+//go:inline
+func WriteUint64Slice(buf *ByteBuffer, value []uint64) {
+ size := len(value) * 8
+ buf.WriteLength(size)
+ if len(value) > 0 {
+ if isLittleEndian {
+
buf.WriteBinary(unsafe.Slice((*byte)(unsafe.Pointer(&value[0])), size))
+ } else {
+ for i := 0; i < len(value); i++ {
+ buf.WriteInt64(int64(value[i]))
+ }
+ }
+ }
+}
+
+// ReadUint64Slice reads []uint64 from buffer using ARRAY protocol
+//
+//go:inline
+func ReadUint64Slice(buf *ByteBuffer, err *Error) []uint64 {
+ size := buf.ReadLength(err)
+ length := size / 8
+ if length == 0 {
+ return make([]uint64, 0)
+ }
+ result := make([]uint64, length)
+ if isLittleEndian {
+ raw := buf.ReadBinary(size, err)
+ copy(unsafe.Slice((*byte)(unsafe.Pointer(&result[0])), size),
raw)
+ } else {
+ for i := 0; i < length; i++ {
+ result[i] = uint64(buf.ReadInt64(err))
+ }
+ }
+ return result
+}
+
// WriteFloat32Slice writes []float32 to buffer using ARRAY protocol
//
//go:inline
diff --git a/go/fory/struct.go b/go/fory/struct.go
index ff4aaece9..53b81f0a5 100644
--- a/go/fory/struct.go
+++ b/go/fory/struct.go
@@ -1590,6 +1590,24 @@ func (s *structSerializer) writeRemainingField(ctx
*WriteContext, ptr unsafe.Poi
}
ctx.WriteInt64Slice(*(*[]int64)(fieldPtr),
field.RefMode, false)
return
+ case Uint16SliceDispatchId:
+ if field.RefMode == RefModeTracking {
+ break
+ }
+ ctx.WriteUint16Slice(*(*[]uint16)(fieldPtr),
field.RefMode, false)
+ return
+ case Uint32SliceDispatchId:
+ if field.RefMode == RefModeTracking {
+ break
+ }
+ ctx.WriteUint32Slice(*(*[]uint32)(fieldPtr),
field.RefMode, false)
+ return
+ case Uint64SliceDispatchId:
+ if field.RefMode == RefModeTracking {
+ break
+ }
+ ctx.WriteUint64Slice(*(*[]uint64)(fieldPtr),
field.RefMode, false)
+ return
case IntSliceDispatchId:
if field.RefMode == RefModeTracking {
break
@@ -2769,6 +2787,24 @@ func (s *structSerializer) readRemainingField(ctx
*ReadContext, ptr unsafe.Point
}
*(*[]int64)(fieldPtr) =
ctx.ReadInt64Slice(field.RefMode, false)
return
+ case Uint16SliceDispatchId:
+ if field.RefMode == RefModeTracking {
+ break
+ }
+ *(*[]uint16)(fieldPtr) =
ctx.ReadUint16Slice(field.RefMode, false)
+ return
+ case Uint32SliceDispatchId:
+ if field.RefMode == RefModeTracking {
+ break
+ }
+ *(*[]uint32)(fieldPtr) =
ctx.ReadUint32Slice(field.RefMode, false)
+ return
+ case Uint64SliceDispatchId:
+ if field.RefMode == RefModeTracking {
+ break
+ }
+ *(*[]uint64)(fieldPtr) =
ctx.ReadUint64Slice(field.RefMode, false)
+ return
case IntSliceDispatchId:
if field.RefMode == RefModeTracking {
break
diff --git a/go/fory/type_resolver.go b/go/fory/type_resolver.go
index 0069dfc0d..971fb5627 100644
--- a/go/fory/type_resolver.go
+++ b/go/fory/type_resolver.go
@@ -67,6 +67,9 @@ var (
int64SliceType = reflect.TypeOf((*[]int64)(nil)).Elem()
intSliceType = reflect.TypeOf((*[]int)(nil)).Elem()
uintSliceType = reflect.TypeOf((*[]uint)(nil)).Elem()
+ uint16SliceType = reflect.TypeOf((*[]uint16)(nil)).Elem()
+ uint32SliceType = reflect.TypeOf((*[]uint32)(nil)).Elem()
+ uint64SliceType = reflect.TypeOf((*[]uint64)(nil)).Elem()
float32SliceType = reflect.TypeOf((*[]float32)(nil)).Elem()
float64SliceType = reflect.TypeOf((*[]float64)(nil)).Elem()
float16SliceType = reflect.TypeOf((*[]float16.Float16)(nil)).Elem()
@@ -398,6 +401,9 @@ func (r *TypeResolver) initialize() {
{int64SliceType, INT64_ARRAY, int64SliceSerializer{}},
{intSliceType, INT64_ARRAY, intSliceSerializer{}}, // int is
typically 64-bit
{uintSliceType, INT64_ARRAY, uintSliceSerializer{}},
+ {uint16SliceType, UINT16_ARRAY, uint16SliceSerializer{}},
+ {uint32SliceType, UINT32_ARRAY, uint32SliceSerializer{}},
+ {uint64SliceType, UINT64_ARRAY, uint64SliceSerializer{}},
{float32SliceType, FLOAT32_ARRAY, float32SliceSerializer{}},
{float64SliceType, FLOAT64_ARRAY, float64SliceSerializer{}},
{float16SliceType, FLOAT16_ARRAY, float16SliceSerializer{}},
@@ -444,6 +450,8 @@ func (r *TypeResolver) initialize() {
typeId TypeId
goType reflect.Type
}{
+ // Byte slice can be encoded as BINARY or UINT8_ARRAY
+ {UINT8_ARRAY, byteSliceType},
// Fixed-size integer encodings (in addition to varint defaults)
{UINT32, uint32Type}, // Fixed UINT32 (11) → uint32
{UINT64, uint64Type}, // Fixed UINT64 (13) → uint64
diff --git a/go/fory/types.go b/go/fory/types.go
index 5227cdbad..b72d27db5 100644
--- a/go/fory/types.go
+++ b/go/fory/types.go
@@ -353,6 +353,9 @@ const (
Int64SliceDispatchId
IntSliceDispatchId
UintSliceDispatchId
+ Uint16SliceDispatchId
+ Uint32SliceDispatchId
+ Uint64SliceDispatchId
Float32SliceDispatchId
Float64SliceDispatchId
Float16SliceDispatchId
@@ -432,19 +435,20 @@ func GetDispatchId(t reflect.Type) DispatchId {
return IntSliceDispatchId
case reflect.Uint:
return UintSliceDispatchId
- case reflect.Float32:
- return Float32SliceDispatchId
- case reflect.Float64:
- return Float64SliceDispatchId
case reflect.Uint16:
// Check if it's float16 slice
if t.Elem().Name() == "Float16" && (t.Elem().PkgPath()
== "github.com/apache/fory/go/fory/float16" ||
strings.HasSuffix(t.Elem().PkgPath(), "/float16")) {
return Float16SliceDispatchId
}
- // Use Int16SliceDispatchId for Uint16 as they share
the same 2-byte size
- // and serialization logic in many cases, or it falls
back to generic if needed.
- return Int16SliceDispatchId
-
+ return Uint16SliceDispatchId
+ case reflect.Uint32:
+ return Uint32SliceDispatchId
+ case reflect.Uint64:
+ return Uint64SliceDispatchId
+ case reflect.Float32:
+ return Float32SliceDispatchId
+ case reflect.Float64:
+ return Float64SliceDispatchId
case reflect.Bool:
return BoolSliceDispatchId
case reflect.String:
diff --git a/go/fory/writer.go b/go/fory/writer.go
index 27907a587..47849e93a 100644
--- a/go/fory/writer.go
+++ b/go/fory/writer.go
@@ -307,6 +307,51 @@ func (c *WriteContext) WriteInt64Slice(value []int64,
refMode RefMode, writeType
WriteInt64Slice(c.buffer, value)
}
+// WriteUint16Slice writes []uint16 with ref/type info
+func (c *WriteContext) WriteUint16Slice(value []uint16, refMode RefMode,
writeTypeInfo bool) {
+ if refMode != RefModeNone {
+ if value == nil {
+ c.buffer.WriteInt8(NullFlag)
+ return
+ }
+ c.buffer.WriteInt8(NotNullValueFlag)
+ }
+ if writeTypeInfo {
+ c.WriteTypeId(UINT16_ARRAY)
+ }
+ WriteUint16Slice(c.buffer, value)
+}
+
+// WriteUint32Slice writes []uint32 with ref/type info
+func (c *WriteContext) WriteUint32Slice(value []uint32, refMode RefMode,
writeTypeInfo bool) {
+ if refMode != RefModeNone {
+ if value == nil {
+ c.buffer.WriteInt8(NullFlag)
+ return
+ }
+ c.buffer.WriteInt8(NotNullValueFlag)
+ }
+ if writeTypeInfo {
+ c.WriteTypeId(UINT32_ARRAY)
+ }
+ WriteUint32Slice(c.buffer, value)
+}
+
+// WriteUint64Slice writes []uint64 with ref/type info
+func (c *WriteContext) WriteUint64Slice(value []uint64, refMode RefMode,
writeTypeInfo bool) {
+ if refMode != RefModeNone {
+ if value == nil {
+ c.buffer.WriteInt8(NullFlag)
+ return
+ }
+ c.buffer.WriteInt8(NotNullValueFlag)
+ }
+ if writeTypeInfo {
+ c.WriteTypeId(UINT64_ARRAY)
+ }
+ WriteUint64Slice(c.buffer, value)
+}
+
// WriteIntSlice writes []int with ref/type info
func (c *WriteContext) WriteIntSlice(value []int, refMode RefMode,
writeTypeInfo bool) {
if refMode != RefModeNone {
diff --git a/integration_tests/idl_tests/cpp/main.cc
b/integration_tests/idl_tests/cpp/main.cc
index d8ef4ead5..a429bb332 100644
--- a/integration_tests/idl_tests/cpp/main.cc
+++ b/integration_tests/idl_tests/cpp/main.cc
@@ -30,6 +30,7 @@
#include "addressbook.h"
#include "any_example.h"
+#include "collection.h"
#include "complex_fbs.h"
#include "complex_pb.h"
#include "fory/serialization/any_serializer.h"
@@ -171,6 +172,7 @@ fory::Result<void, fory::Error> RunRoundTrip(bool
compatible) {
addressbook::register_types(fory);
monster::register_types(fory);
complex_fbs::register_types(fory);
+ collection::register_types(fory);
optional_types::register_types(fory);
any_example::register_types(fory);
@@ -310,6 +312,86 @@ fory::Result<void, fory::Error> RunRoundTrip(bool
compatible) {
fory::Error::invalid("primitive types roundtrip mismatch"));
}
+ collection::NumericCollections collections;
+ *collections.mutable_int8_values() = {
+ static_cast<int8_t>(1), static_cast<int8_t>(-2), static_cast<int8_t>(3)};
+ *collections.mutable_int16_values() = {static_cast<int16_t>(100),
+ static_cast<int16_t>(-200),
+ static_cast<int16_t>(300)};
+ *collections.mutable_int32_values() = {1000, -2000, 3000};
+ *collections.mutable_int64_values() = {10000, -20000, 30000};
+ *collections.mutable_uint8_values() = {static_cast<uint8_t>(200),
+ static_cast<uint8_t>(250)};
+ *collections.mutable_uint16_values() = {static_cast<uint16_t>(50000),
+ static_cast<uint16_t>(60000)};
+ *collections.mutable_uint32_values() = {2000000000U, 2100000000U};
+ *collections.mutable_uint64_values() = {9000000000ULL, 12000000000ULL};
+ *collections.mutable_float32_values() = {1.5F, 2.5F};
+ *collections.mutable_float64_values() = {3.5, 4.5};
+
+ collection::NumericCollectionUnion collection_union =
+ collection::NumericCollectionUnion::int32_values(
+ std::vector<int32_t>{7, 8, 9});
+
+ collection::NumericCollectionsArray collections_array;
+ *collections_array.mutable_int8_values() = {
+ static_cast<int8_t>(1), static_cast<int8_t>(-2), static_cast<int8_t>(3)};
+ *collections_array.mutable_int16_values() = {static_cast<int16_t>(100),
+ static_cast<int16_t>(-200),
+ static_cast<int16_t>(300)};
+ *collections_array.mutable_int32_values() = {1000, -2000, 3000};
+ *collections_array.mutable_int64_values() = {10000, -20000, 30000};
+ *collections_array.mutable_uint8_values() = {static_cast<uint8_t>(200),
+ static_cast<uint8_t>(250)};
+ *collections_array.mutable_uint16_values() = {static_cast<uint16_t>(50000),
+ static_cast<uint16_t>(60000)};
+ *collections_array.mutable_uint32_values() = {2000000000U, 2100000000U};
+ *collections_array.mutable_uint64_values() = {9000000000ULL, 12000000000ULL};
+ *collections_array.mutable_float32_values() = {1.5F, 2.5F};
+ *collections_array.mutable_float64_values() = {3.5, 4.5};
+
+ collection::NumericCollectionArrayUnion collection_array_union =
+ collection::NumericCollectionArrayUnion::uint16_values(
+ std::vector<uint16_t>{1000, 2000, 3000});
+
+ FORY_TRY(collection_bytes, fory.serialize(collections));
+ FORY_TRY(collection_roundtrip,
+ fory.deserialize<collection::NumericCollections>(
+ collection_bytes.data(), collection_bytes.size()));
+ if (!(collection_roundtrip == collections)) {
+ return fory::Unexpected(
+ fory::Error::invalid("collection roundtrip mismatch"));
+ }
+
+ FORY_TRY(collection_union_bytes, fory.serialize(collection_union));
+ FORY_TRY(collection_union_roundtrip,
+ fory.deserialize<collection::NumericCollectionUnion>(
+ collection_union_bytes.data(), collection_union_bytes.size()));
+ if (!(collection_union_roundtrip == collection_union)) {
+ return fory::Unexpected(
+ fory::Error::invalid("collection union roundtrip mismatch"));
+ }
+
+ FORY_TRY(collection_array_bytes, fory.serialize(collections_array));
+ FORY_TRY(collection_array_roundtrip,
+ fory.deserialize<collection::NumericCollectionsArray>(
+ collection_array_bytes.data(), collection_array_bytes.size()));
+ if (!(collection_array_roundtrip == collections_array)) {
+ return fory::Unexpected(
+ fory::Error::invalid("collection array roundtrip mismatch"));
+ }
+
+ FORY_TRY(collection_array_union_bytes,
+ fory.serialize(collection_array_union));
+ FORY_TRY(collection_array_union_roundtrip,
+ fory.deserialize<collection::NumericCollectionArrayUnion>(
+ collection_array_union_bytes.data(),
+ collection_array_union_bytes.size()));
+ if (!(collection_array_union_roundtrip == collection_array_union)) {
+ return fory::Unexpected(
+ fory::Error::invalid("collection array union roundtrip mismatch"));
+ }
+
monster::Vec3 pos;
pos.set_x(1.0F);
pos.set_y(2.0F);
@@ -468,6 +550,61 @@ fory::Result<void, fory::Error> RunRoundTrip(bool
compatible) {
FORY_RETURN_IF_ERROR(WriteFile(primitive_file, peer_bytes));
}
+ const char *collection_file = std::getenv("DATA_FILE_COLLECTION");
+ if (collection_file != nullptr && collection_file[0] != '\0') {
+ FORY_TRY(payload, ReadFile(collection_file));
+ FORY_TRY(peer_collections,
fory.deserialize<collection::NumericCollections>(
+ payload.data(), payload.size()));
+ if (!(peer_collections == collections)) {
+ return fory::Unexpected(
+ fory::Error::invalid("peer collection payload mismatch"));
+ }
+ FORY_TRY(peer_bytes, fory.serialize(peer_collections));
+ FORY_RETURN_IF_ERROR(WriteFile(collection_file, peer_bytes));
+ }
+
+ const char *collection_union_file =
std::getenv("DATA_FILE_COLLECTION_UNION");
+ if (collection_union_file != nullptr && collection_union_file[0] != '\0') {
+ FORY_TRY(payload, ReadFile(collection_union_file));
+ FORY_TRY(peer_union, fory.deserialize<collection::NumericCollectionUnion>(
+ payload.data(), payload.size()));
+ if (!(peer_union == collection_union)) {
+ return fory::Unexpected(
+ fory::Error::invalid("peer collection union payload mismatch"));
+ }
+ FORY_TRY(peer_bytes, fory.serialize(peer_union));
+ FORY_RETURN_IF_ERROR(WriteFile(collection_union_file, peer_bytes));
+ }
+
+ const char *collection_array_file =
std::getenv("DATA_FILE_COLLECTION_ARRAY");
+ if (collection_array_file != nullptr && collection_array_file[0] != '\0') {
+ FORY_TRY(payload, ReadFile(collection_array_file));
+ FORY_TRY(peer_array, fory.deserialize<collection::NumericCollectionsArray>(
+ payload.data(), payload.size()));
+ if (!(peer_array == collections_array)) {
+ return fory::Unexpected(
+ fory::Error::invalid("peer collection array payload mismatch"));
+ }
+ FORY_TRY(peer_bytes, fory.serialize(peer_array));
+ FORY_RETURN_IF_ERROR(WriteFile(collection_array_file, peer_bytes));
+ }
+
+ const char *collection_array_union_file =
+ std::getenv("DATA_FILE_COLLECTION_ARRAY_UNION");
+ if (collection_array_union_file != nullptr &&
+ collection_array_union_file[0] != '\0') {
+ FORY_TRY(payload, ReadFile(collection_array_union_file));
+ FORY_TRY(peer_array_union,
+ fory.deserialize<collection::NumericCollectionArrayUnion>(
+ payload.data(), payload.size()));
+ if (!(peer_array_union == collection_array_union)) {
+ return fory::Unexpected(
+ fory::Error::invalid("peer collection array union payload
mismatch"));
+ }
+ FORY_TRY(peer_bytes, fory.serialize(peer_array_union));
+ FORY_RETURN_IF_ERROR(WriteFile(collection_array_union_file, peer_bytes));
+ }
+
const char *monster_file = std::getenv("DATA_FILE_FLATBUFFERS_MONSTER");
if (monster_file != nullptr && monster_file[0] != '\0') {
FORY_TRY(payload, ReadFile(monster_file));
diff --git a/integration_tests/idl_tests/generate_idl.py
b/integration_tests/idl_tests/generate_idl.py
index c98aaec7a..d6d0da11d 100755
--- a/integration_tests/idl_tests/generate_idl.py
+++ b/integration_tests/idl_tests/generate_idl.py
@@ -26,6 +26,7 @@ REPO_ROOT = Path(__file__).resolve().parents[2]
IDL_DIR = Path(__file__).resolve().parent
SCHEMAS = [
IDL_DIR / "idl" / "addressbook.fdl",
+ IDL_DIR / "idl" / "collection.fdl",
IDL_DIR / "idl" / "optional_types.fdl",
IDL_DIR / "idl" / "tree.fdl",
IDL_DIR / "idl" / "graph.fdl",
@@ -46,6 +47,7 @@ LANG_OUTPUTS = {
GO_OUTPUT_OVERRIDES = {
"addressbook.fdl": IDL_DIR / "go" / "addressbook",
+ "collection.fdl": IDL_DIR / "go" / "collection",
"monster.fbs": IDL_DIR / "go" / "monster",
"complex_fbs.fbs": IDL_DIR / "go" / "complex_fbs",
"optional_types.fdl": IDL_DIR / "go" / "optional_types",
diff --git a/integration_tests/idl_tests/go/idl_roundtrip_test.go
b/integration_tests/idl_tests/go/idl_roundtrip_test.go
index abe5d31fe..f1853766a 100644
--- a/integration_tests/idl_tests/go/idl_roundtrip_test.go
+++ b/integration_tests/idl_tests/go/idl_roundtrip_test.go
@@ -27,6 +27,7 @@ import (
"github.com/apache/fory/go/fory/optional"
addressbook
"github.com/apache/fory/integration_tests/idl_tests/go/addressbook"
anyexample
"github.com/apache/fory/integration_tests/idl_tests/go/any_example"
+ collection
"github.com/apache/fory/integration_tests/idl_tests/go/collection"
complexfbs
"github.com/apache/fory/integration_tests/idl_tests/go/complex_fbs"
complexpb
"github.com/apache/fory/integration_tests/idl_tests/go/complex_pb"
graphpkg "github.com/apache/fory/integration_tests/idl_tests/go/graph"
@@ -97,6 +98,9 @@ func runAddressBookRoundTrip(t *testing.T, compatible bool) {
if err := complexfbs.RegisterTypes(f); err != nil {
t.Fatalf("register flatbuffers types: %v", err)
}
+ if err := collection.RegisterTypes(f); err != nil {
+ t.Fatalf("register collection types: %v", err)
+ }
if err := optionaltypes.RegisterTypes(f); err != nil {
t.Fatalf("register optional types: %v", err)
}
@@ -112,6 +116,13 @@ func runAddressBookRoundTrip(t *testing.T, compatible
bool) {
runLocalPrimitiveRoundTrip(t, f, types)
runFilePrimitiveRoundTrip(t, f, types)
+ collections := buildNumericCollections()
+ collectionUnion := buildNumericCollectionUnion()
+ collectionsArray := buildNumericCollectionsArray()
+ collectionArrayUnion := buildNumericCollectionArrayUnion()
+ runLocalCollectionRoundTrip(t, f, collections, collectionUnion,
collectionsArray, collectionArrayUnion)
+ runFileCollectionRoundTrip(t, f, collections, collectionUnion,
collectionsArray, collectionArrayUnion)
+
monster := buildMonster()
runLocalMonsterRoundTrip(t, f, monster)
runFileMonsterRoundTrip(t, f, monster)
@@ -441,6 +452,44 @@ func buildPrimitiveTypes() complexpb.PrimitiveTypes {
}
}
+func buildNumericCollections() collection.NumericCollections {
+ return collection.NumericCollections{
+ Int8Values: []int8{1, -2, 3},
+ Int16Values: []int16{100, -200, 300},
+ Int32Values: []int32{1000, -2000, 3000},
+ Int64Values: []int64{10000, -20000, 30000},
+ Uint8Values: []uint8{200, 250},
+ Uint16Values: []uint16{50000, 60000},
+ Uint32Values: []uint32{2000000000, 2100000000},
+ Uint64Values: []uint64{9000000000, 12000000000},
+ Float32Values: []float32{1.5, 2.5},
+ Float64Values: []float64{3.5, 4.5},
+ }
+}
+
+func buildNumericCollectionUnion() collection.NumericCollectionUnion {
+ return collection.Int32ValuesNumericCollectionUnion([]int32{7, 8, 9})
+}
+
+func buildNumericCollectionsArray() collection.NumericCollectionsArray {
+ return collection.NumericCollectionsArray{
+ Int8Values: []int8{1, -2, 3},
+ Int16Values: []int16{100, -200, 300},
+ Int32Values: []int32{1000, -2000, 3000},
+ Int64Values: []int64{10000, -20000, 30000},
+ Uint8Values: []uint8{200, 250},
+ Uint16Values: []uint16{50000, 60000},
+ Uint32Values: []uint32{2000000000, 2100000000},
+ Uint64Values: []uint64{9000000000, 12000000000},
+ Float32Values: []float32{1.5, 2.5},
+ Float64Values: []float64{3.5, 4.5},
+ }
+}
+
+func buildNumericCollectionArrayUnion() collection.NumericCollectionArrayUnion
{
+ return
collection.Uint16ValuesNumericCollectionArrayUnion([]uint16{1000, 2000, 3000})
+}
+
func runLocalPrimitiveRoundTrip(t *testing.T, f *fory.Fory, types
complexpb.PrimitiveTypes) {
data, err := f.Serialize(&types)
if err != nil {
@@ -484,6 +533,162 @@ func runFilePrimitiveRoundTrip(t *testing.T, f
*fory.Fory, types complexpb.Primi
}
}
+func runLocalCollectionRoundTrip(
+ t *testing.T,
+ f *fory.Fory,
+ collections collection.NumericCollections,
+ unionValue collection.NumericCollectionUnion,
+ collectionsArray collection.NumericCollectionsArray,
+ arrayUnion collection.NumericCollectionArrayUnion,
+) {
+ data, err := f.Serialize(&collections)
+ if err != nil {
+ t.Fatalf("serialize collections: %v", err)
+ }
+
+ var out collection.NumericCollections
+ if err := f.Deserialize(data, &out); err != nil {
+ t.Fatalf("deserialize collections: %v", err)
+ }
+
+ if !reflect.DeepEqual(collections, out) {
+ t.Fatalf("collection roundtrip mismatch: %#v != %#v",
collections, out)
+ }
+
+ unionData, err := f.Serialize(&unionValue)
+ if err != nil {
+ t.Fatalf("serialize collection union: %v", err)
+ }
+ var unionOut collection.NumericCollectionUnion
+ if err := f.Deserialize(unionData, &unionOut); err != nil {
+ t.Fatalf("deserialize collection union: %v", err)
+ }
+ if !reflect.DeepEqual(unionValue, unionOut) {
+ t.Fatalf("collection union mismatch: %#v != %#v", unionValue,
unionOut)
+ }
+
+ arrayData, err := f.Serialize(&collectionsArray)
+ if err != nil {
+ t.Fatalf("serialize collection array: %v", err)
+ }
+ var arrayOut collection.NumericCollectionsArray
+ if err := f.Deserialize(arrayData, &arrayOut); err != nil {
+ t.Fatalf("deserialize collection array: %v", err)
+ }
+ if !reflect.DeepEqual(collectionsArray, arrayOut) {
+ t.Fatalf("collection array mismatch: %#v != %#v",
collectionsArray, arrayOut)
+ }
+
+ arrayUnionData, err := f.Serialize(&arrayUnion)
+ if err != nil {
+ t.Fatalf("serialize collection array union: %v", err)
+ }
+ var arrayUnionOut collection.NumericCollectionArrayUnion
+ if err := f.Deserialize(arrayUnionData, &arrayUnionOut); err != nil {
+ t.Fatalf("deserialize collection array union: %v", err)
+ }
+ if !reflect.DeepEqual(arrayUnion, arrayUnionOut) {
+ t.Fatalf("collection array union mismatch: %#v != %#v",
arrayUnion, arrayUnionOut)
+ }
+}
+
+func runFileCollectionRoundTrip(
+ t *testing.T,
+ f *fory.Fory,
+ collections collection.NumericCollections,
+ unionValue collection.NumericCollectionUnion,
+ collectionsArray collection.NumericCollectionsArray,
+ arrayUnion collection.NumericCollectionArrayUnion,
+) {
+ dataFile := os.Getenv("DATA_FILE_COLLECTION")
+ if dataFile != "" {
+ payload, err := os.ReadFile(dataFile)
+ if err != nil {
+ t.Fatalf("read collection file: %v", err)
+ }
+ var decoded collection.NumericCollections
+ if err := f.Deserialize(payload, &decoded); err != nil {
+ t.Fatalf("deserialize collection file: %v", err)
+ }
+ if !reflect.DeepEqual(collections, decoded) {
+ t.Fatalf("collection file mismatch: %#v != %#v",
collections, decoded)
+ }
+ out, err := f.Serialize(&decoded)
+ if err != nil {
+ t.Fatalf("serialize collection file: %v", err)
+ }
+ if err := os.WriteFile(dataFile, out, 0o644); err != nil {
+ t.Fatalf("write collection file: %v", err)
+ }
+ }
+
+ unionFile := os.Getenv("DATA_FILE_COLLECTION_UNION")
+ if unionFile != "" {
+ payload, err := os.ReadFile(unionFile)
+ if err != nil {
+ t.Fatalf("read collection union file: %v", err)
+ }
+ var decoded collection.NumericCollectionUnion
+ if err := f.Deserialize(payload, &decoded); err != nil {
+ t.Fatalf("deserialize collection union file: %v", err)
+ }
+ if !reflect.DeepEqual(unionValue, decoded) {
+ t.Fatalf("collection union file mismatch: %#v != %#v",
unionValue, decoded)
+ }
+ out, err := f.Serialize(&decoded)
+ if err != nil {
+ t.Fatalf("serialize collection union file: %v", err)
+ }
+ if err := os.WriteFile(unionFile, out, 0o644); err != nil {
+ t.Fatalf("write collection union file: %v", err)
+ }
+ }
+
+ arrayFile := os.Getenv("DATA_FILE_COLLECTION_ARRAY")
+ if arrayFile != "" {
+ payload, err := os.ReadFile(arrayFile)
+ if err != nil {
+ t.Fatalf("read collection array file: %v", err)
+ }
+ var decoded collection.NumericCollectionsArray
+ if err := f.Deserialize(payload, &decoded); err != nil {
+ t.Fatalf("deserialize collection array file: %v", err)
+ }
+ if !reflect.DeepEqual(collectionsArray, decoded) {
+ t.Fatalf("collection array file mismatch: %#v != %#v",
collectionsArray, decoded)
+ }
+ out, err := f.Serialize(&decoded)
+ if err != nil {
+ t.Fatalf("serialize collection array file: %v", err)
+ }
+ if err := os.WriteFile(arrayFile, out, 0o644); err != nil {
+ t.Fatalf("write collection array file: %v", err)
+ }
+ }
+
+ arrayUnionFile := os.Getenv("DATA_FILE_COLLECTION_ARRAY_UNION")
+ if arrayUnionFile != "" {
+ payload, err := os.ReadFile(arrayUnionFile)
+ if err != nil {
+ t.Fatalf("read collection array union file: %v", err)
+ }
+ var decoded collection.NumericCollectionArrayUnion
+ if err := f.Deserialize(payload, &decoded); err != nil {
+ t.Fatalf("deserialize collection array union file: %v",
err)
+ }
+ if !reflect.DeepEqual(arrayUnion, decoded) {
+ t.Fatalf("collection array union file mismatch: %#v !=
%#v", arrayUnion, decoded)
+ }
+ out, err := f.Serialize(&decoded)
+ if err != nil {
+ t.Fatalf("serialize collection array union file: %v",
err)
+ }
+ if err := os.WriteFile(arrayUnionFile, out, 0o644); err != nil {
+ t.Fatalf("write collection array union file: %v", err)
+ }
+ }
+}
+
func buildMonster() monster.Monster {
pos := &monster.Vec3{
X: 1.0,
diff --git a/integration_tests/idl_tests/idl/collection.fdl
b/integration_tests/idl_tests/idl/collection.fdl
new file mode 100644
index 000000000..091f11243
--- /dev/null
+++ b/integration_tests/idl_tests/idl/collection.fdl
@@ -0,0 +1,70 @@
+// 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 collection;
+
+message NumericCollections [id=210] {
+ repeated int8 int8_values = 1;
+ repeated int16 int16_values = 2;
+ repeated int32 int32_values = 3;
+ repeated int64 int64_values = 4;
+ repeated uint8 uint8_values = 5;
+ repeated uint16 uint16_values = 6;
+ repeated uint32 uint32_values = 7;
+ repeated uint64 uint64_values = 8;
+ repeated float32 float32_values = 9;
+ repeated float64 float64_values = 10;
+}
+
+union NumericCollectionUnion [id=211] {
+ repeated int8 int8_values = 1;
+ repeated int16 int16_values = 2;
+ repeated int32 int32_values = 3;
+ repeated int64 int64_values = 4;
+ repeated uint8 uint8_values = 5;
+ repeated uint16 uint16_values = 6;
+ repeated uint32 uint32_values = 7;
+ repeated uint64 uint64_values = 8;
+ repeated float32 float32_values = 9;
+ repeated float64 float64_values = 10;
+}
+
+message NumericCollectionsArray [id=212] {
+ repeated int8 int8_values = 1 [java_array = true];
+ repeated int16 int16_values = 2 [java_array = true];
+ repeated int32 int32_values = 3 [java_array = true];
+ repeated int64 int64_values = 4 [java_array = true];
+ repeated uint8 uint8_values = 5 [java_array = true];
+ repeated uint16 uint16_values = 6 [java_array = true];
+ repeated uint32 uint32_values = 7 [java_array = true];
+ repeated uint64 uint64_values = 8 [java_array = true];
+ repeated float32 float32_values = 9 [java_array = true];
+ repeated float64 float64_values = 10 [java_array = true];
+}
+
+union NumericCollectionArrayUnion [id=213] {
+ repeated int8 int8_values = 1 [java_array = true];
+ repeated int16 int16_values = 2 [java_array = true];
+ repeated int32 int32_values = 3 [java_array = true];
+ repeated int64 int64_values = 4 [java_array = true];
+ repeated uint8 uint8_values = 5 [java_array = true];
+ repeated uint16 uint16_values = 6 [java_array = true];
+ repeated uint32 uint32_values = 7 [java_array = true];
+ repeated uint64 uint64_values = 8 [java_array = true];
+ repeated float32 float32_values = 9 [java_array = true];
+ repeated float64 float64_values = 10 [java_array = true];
+}
diff --git
a/integration_tests/idl_tests/java/src/test/java/org/apache/fory/idl_tests/IdlRoundTripTest.java
b/integration_tests/idl_tests/java/src/test/java/org/apache/fory/idl_tests/IdlRoundTripTest.java
index ff64b0977..dfec1d79a 100644
---
a/integration_tests/idl_tests/java/src/test/java/org/apache/fory/idl_tests/IdlRoundTripTest.java
+++
b/integration_tests/idl_tests/java/src/test/java/org/apache/fory/idl_tests/IdlRoundTripTest.java
@@ -27,6 +27,11 @@ import addressbook.Dog;
import addressbook.Person;
import addressbook.Person.PhoneNumber;
import addressbook.Person.PhoneType;
+import collection.CollectionForyRegistration;
+import collection.NumericCollectionArrayUnion;
+import collection.NumericCollectionUnion;
+import collection.NumericCollections;
+import collection.NumericCollectionsArray;
import complex_pb.ComplexPbForyRegistration;
import complex_pb.PrimitiveTypes;
import any_example.AnyExampleForyRegistration;
@@ -71,6 +76,14 @@ import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.fory.Fory;
+import org.apache.fory.collection.Int16List;
+import org.apache.fory.collection.Int32List;
+import org.apache.fory.collection.Int64List;
+import org.apache.fory.collection.Int8List;
+import org.apache.fory.collection.Uint16List;
+import org.apache.fory.collection.Uint32List;
+import org.apache.fory.collection.Uint64List;
+import org.apache.fory.collection.Uint8List;
import org.apache.fory.config.CompatibleMode;
import org.apache.fory.config.Language;
import org.testng.Assert;
@@ -204,6 +217,93 @@ public class IdlRoundTripTest {
}
}
+ @Test
+ public void testCollectionRoundTripCompatible() throws Exception {
+ runCollectionRoundTrip(true);
+ }
+
+ @Test
+ public void testCollectionRoundTripSchemaConsistent() throws Exception {
+ runCollectionRoundTrip(false);
+ }
+
+ private void runCollectionRoundTrip(boolean compatible) throws Exception {
+ Fory fory = buildFory(compatible);
+ CollectionForyRegistration.register(fory);
+
+ NumericCollections collections = buildNumericCollections();
+ NumericCollectionUnion collectionUnion = buildNumericCollectionUnion();
+ NumericCollectionsArray collectionsArray = buildNumericCollectionsArray();
+ NumericCollectionArrayUnion collectionArrayUnion =
buildNumericCollectionArrayUnion();
+
+ byte[] collectionsBytes = fory.serialize(collections);
+ Object collectionsDecoded = fory.deserialize(collectionsBytes);
+ Assert.assertTrue(collectionsDecoded instanceof NumericCollections);
+ Assert.assertEquals(collectionsDecoded, collections);
+
+ byte[] unionBytes = fory.serialize(collectionUnion);
+ Object unionDecoded = fory.deserialize(unionBytes);
+ assertNumericCollectionUnion(unionDecoded, collectionUnion);
+
+ byte[] arrayBytes = fory.serialize(collectionsArray);
+ Object arrayDecoded = fory.deserialize(arrayBytes);
+ Assert.assertTrue(arrayDecoded instanceof NumericCollectionsArray);
+ Assert.assertEquals(arrayDecoded, collectionsArray);
+
+ byte[] arrayUnionBytes = fory.serialize(collectionArrayUnion);
+ Object arrayUnionDecoded = fory.deserialize(arrayUnionBytes);
+ assertNumericCollectionArrayUnion(arrayUnionDecoded, collectionArrayUnion);
+
+ for (String peer : resolvePeers()) {
+ Path collectionsFile =
+ Files.createTempFile("idl-collections-" + peer + "-", ".bin");
+ collectionsFile.toFile().deleteOnExit();
+ Files.write(collectionsFile, collectionsBytes);
+
+ Path unionFile =
+ Files.createTempFile("idl-collection-union-" + peer + "-", ".bin");
+ unionFile.toFile().deleteOnExit();
+ Files.write(unionFile, unionBytes);
+
+ Path arrayFile =
+ Files.createTempFile("idl-collections-array-" + peer + "-", ".bin");
+ arrayFile.toFile().deleteOnExit();
+ Files.write(arrayFile, arrayBytes);
+
+ Path arrayUnionFile =
+ Files.createTempFile("idl-collection-array-union-" + peer + "-",
".bin");
+ arrayUnionFile.toFile().deleteOnExit();
+ Files.write(arrayUnionFile, arrayUnionBytes);
+
+ Map<String, String> env = new HashMap<>();
+ env.put("DATA_FILE_COLLECTION",
collectionsFile.toAbsolutePath().toString());
+ env.put("DATA_FILE_COLLECTION_UNION",
unionFile.toAbsolutePath().toString());
+ env.put("DATA_FILE_COLLECTION_ARRAY",
arrayFile.toAbsolutePath().toString());
+ env.put(
+ "DATA_FILE_COLLECTION_ARRAY_UNION",
arrayUnionFile.toAbsolutePath().toString());
+ PeerCommand command = buildPeerCommand(peer, env, compatible);
+ runPeer(command, peer);
+
+ byte[] peerCollectionsBytes = Files.readAllBytes(collectionsFile);
+ Object collectionsRoundTrip = fory.deserialize(peerCollectionsBytes);
+ Assert.assertTrue(collectionsRoundTrip instanceof NumericCollections);
+ Assert.assertEquals(collectionsRoundTrip, collections);
+
+ byte[] peerUnionBytes = Files.readAllBytes(unionFile);
+ Object unionRoundTrip = fory.deserialize(peerUnionBytes);
+ assertNumericCollectionUnion(unionRoundTrip, collectionUnion);
+
+ byte[] peerArrayBytes = Files.readAllBytes(arrayFile);
+ Object arrayRoundTrip = fory.deserialize(peerArrayBytes);
+ Assert.assertTrue(arrayRoundTrip instanceof NumericCollectionsArray);
+ Assert.assertEquals(arrayRoundTrip, collectionsArray);
+
+ byte[] peerArrayUnionBytes = Files.readAllBytes(arrayUnionFile);
+ Object arrayUnionRoundTrip = fory.deserialize(peerArrayUnionBytes);
+ assertNumericCollectionArrayUnion(arrayUnionRoundTrip,
collectionArrayUnion);
+ }
+ }
+
@Test
public void testOptionalTypesRoundTripCompatible() throws Exception {
runOptionalTypesRoundTrip(true);
@@ -591,6 +691,76 @@ public class IdlRoundTripTest {
return types;
}
+ private NumericCollections buildNumericCollections() {
+ NumericCollections collections = new NumericCollections();
+ collections.setInt8Values(new Int8List(new byte[] {1, -2, 3}));
+ collections.setInt16Values(new Int16List(new short[] {100, -200, 300}));
+ collections.setInt32Values(new Int32List(new int[] {1000, -2000, 3000}));
+ collections.setInt64Values(new Int64List(new long[] {10000L, -20000L,
30000L}));
+ collections.setUint8Values(new Uint8List(new byte[] {(byte) 200, (byte)
250}));
+ collections.setUint16Values(new Uint16List(new short[] {(short) 50000,
(short) 60000}));
+ collections.setUint32Values(new Uint32List(new int[] {2000000000,
2100000000}));
+ collections.setUint64Values(new Uint64List(new long[] {9000000000L,
12000000000L}));
+ collections.setFloat32Values(new float[] {1.5f, 2.5f});
+ collections.setFloat64Values(new double[] {3.5d, 4.5d});
+ return collections;
+ }
+
+ private NumericCollectionUnion buildNumericCollectionUnion() {
+ return NumericCollectionUnion.ofInt32Values(new Int32List(new int[] {7, 8,
9}));
+ }
+
+ private NumericCollectionsArray buildNumericCollectionsArray() {
+ NumericCollectionsArray collections = new NumericCollectionsArray();
+ collections.setInt8Values(new byte[] {1, -2, 3});
+ collections.setInt16Values(new short[] {100, -200, 300});
+ collections.setInt32Values(new int[] {1000, -2000, 3000});
+ collections.setInt64Values(new long[] {10000L, -20000L, 30000L});
+ collections.setUint8Values(new byte[] {(byte) 200, (byte) 250});
+ collections.setUint16Values(new short[] {(short) 50000, (short) 60000});
+ collections.setUint32Values(new int[] {2000000000, 2100000000});
+ collections.setUint64Values(new long[] {9000000000L, 12000000000L});
+ collections.setFloat32Values(new float[] {1.5f, 2.5f});
+ collections.setFloat64Values(new double[] {3.5d, 4.5d});
+ return collections;
+ }
+
+ private NumericCollectionArrayUnion buildNumericCollectionArrayUnion() {
+ return NumericCollectionArrayUnion.ofUint16Values(new short[] {1000, 2000,
3000});
+ }
+
+ private void assertNumericCollectionUnion(
+ Object decoded, NumericCollectionUnion expected) {
+ Assert.assertTrue(decoded instanceof NumericCollectionUnion);
+ NumericCollectionUnion union = (NumericCollectionUnion) decoded;
+ Assert.assertEquals(union.getNumericCollectionUnionCase(),
expected.getNumericCollectionUnionCase());
+ switch (union.getNumericCollectionUnionCase()) {
+ case INT32_VALUES:
+ Assert.assertEquals(union.getInt32Values(), expected.getInt32Values());
+ break;
+ default:
+ Assert.fail("Unexpected union case: " +
union.getNumericCollectionUnionCase());
+ }
+ }
+
+ private void assertNumericCollectionArrayUnion(
+ Object decoded, NumericCollectionArrayUnion expected) {
+ Assert.assertTrue(decoded instanceof NumericCollectionArrayUnion);
+ NumericCollectionArrayUnion union = (NumericCollectionArrayUnion) decoded;
+ Assert.assertEquals(
+ union.getNumericCollectionArrayUnionCase(),
+ expected.getNumericCollectionArrayUnionCase());
+ switch (union.getNumericCollectionArrayUnionCase()) {
+ case UINT16_VALUES:
+ Assert.assertTrue(
+ Arrays.equals(union.getUint16Values(),
expected.getUint16Values()));
+ break;
+ default:
+ Assert.fail(
+ "Unexpected array union case: " +
union.getNumericCollectionArrayUnionCase());
+ }
+ }
+
private Monster buildMonster() {
Vec3 pos = new Vec3();
pos.setX(1.0f);
@@ -603,7 +773,7 @@ public class IdlRoundTripTest {
monster.setHp((short) 80);
monster.setName("Orc");
monster.setFriendly(true);
- monster.setInventory(new byte[] {(byte) 1, (byte) 2, (byte) 3});
+ monster.setInventory(new Uint8List(new byte[] {(byte) 1, (byte) 2, (byte)
3}));
monster.setColor(Color.Blue);
return monster;
}
@@ -635,7 +805,7 @@ public class IdlRoundTripTest {
allTypes.setBytesValue(new byte[] {1, 2, 3});
allTypes.setDateValue(LocalDate.of(2024, 1, 2));
allTypes.setTimestampValue(Instant.parse("2024-01-02T03:04:05Z"));
- allTypes.setInt32List(new int[] {1, 2, 3});
+ allTypes.setInt32List(new Int32List(new int[] {1, 2, 3}));
allTypes.setStringList(Arrays.asList("alpha", "beta"));
Map<String, Long> int64Map = new HashMap<>();
int64Map.put("alpha", 10L);
@@ -749,8 +919,8 @@ public class IdlRoundTripTest {
Container container = new Container();
container.setId(9876543210L);
container.setStatus(Status.STARTED);
- container.setBytes(new byte[] {(byte) 1, (byte) 2, (byte) 3});
- container.setNumbers(new int[] {10, 20, 30});
+ container.setBytes(new Int8List(new byte[] {(byte) 1, (byte) 2, (byte)
3}));
+ container.setNumbers(new Int32List(new int[] {10, 20, 30}));
container.setScalars(pack);
container.setNames(Arrays.asList("alpha", "beta"));
container.setFlags(new boolean[] {true, false});
diff --git a/integration_tests/idl_tests/python/src/idl_tests/roundtrip.py
b/integration_tests/idl_tests/python/src/idl_tests/roundtrip.py
index 7cc573880..869d079e0 100644
--- a/integration_tests/idl_tests/python/src/idl_tests/roundtrip.py
+++ b/integration_tests/idl_tests/python/src/idl_tests/roundtrip.py
@@ -25,6 +25,7 @@ import addressbook
import any_example
import complex_fbs
import complex_pb
+import collection
import monster
import optional_types
import graph
@@ -153,6 +154,106 @@ def build_primitive_types() ->
"complex_pb.PrimitiveTypes":
)
+def build_numeric_collections() -> "collection.NumericCollections":
+ return collection.NumericCollections(
+ int8_values=np.array([1, -2, 3], dtype=np.int8),
+ int16_values=np.array([100, -200, 300], dtype=np.int16),
+ int32_values=np.array([1000, -2000, 3000], dtype=np.int32),
+ int64_values=np.array([10000, -20000, 30000], dtype=np.int64),
+ uint8_values=np.array([200, 250], dtype=np.uint8),
+ uint16_values=np.array([50000, 60000], dtype=np.uint16),
+ uint32_values=np.array([2000000000, 2100000000], dtype=np.uint32),
+ uint64_values=np.array([9000000000, 12000000000], dtype=np.uint64),
+ float32_values=np.array([1.5, 2.5], dtype=np.float32),
+ float64_values=np.array([3.5, 4.5], dtype=np.float64),
+ )
+
+
+def build_numeric_collection_union() -> "collection.NumericCollectionUnion":
+ return collection.NumericCollectionUnion.int32_values(
+ np.array([7, 8, 9], dtype=np.int32)
+ )
+
+
+def build_numeric_collections_array() -> "collection.NumericCollectionsArray":
+ return collection.NumericCollectionsArray(
+ int8_values=np.array([1, -2, 3], dtype=np.int8),
+ int16_values=np.array([100, -200, 300], dtype=np.int16),
+ int32_values=np.array([1000, -2000, 3000], dtype=np.int32),
+ int64_values=np.array([10000, -20000, 30000], dtype=np.int64),
+ uint8_values=np.array([200, 250], dtype=np.uint8),
+ uint16_values=np.array([50000, 60000], dtype=np.uint16),
+ uint32_values=np.array([2000000000, 2100000000], dtype=np.uint32),
+ uint64_values=np.array([9000000000, 12000000000], dtype=np.uint64),
+ float32_values=np.array([1.5, 2.5], dtype=np.float32),
+ float64_values=np.array([3.5, 4.5], dtype=np.float64),
+ )
+
+
+def build_numeric_collection_array_union() ->
"collection.NumericCollectionArrayUnion":
+ return collection.NumericCollectionArrayUnion.uint16_values(
+ np.array([1000, 2000, 3000], dtype=np.uint16)
+ )
+
+
+def assert_numeric_collections_equal(
+ decoded: "collection.NumericCollections",
+ expected: "collection.NumericCollections",
+) -> None:
+ assert np.array_equal(decoded.int8_values, expected.int8_values)
+ assert np.array_equal(decoded.int16_values, expected.int16_values)
+ assert np.array_equal(decoded.int32_values, expected.int32_values)
+ assert np.array_equal(decoded.int64_values, expected.int64_values)
+ assert np.array_equal(decoded.uint8_values, expected.uint8_values)
+ assert np.array_equal(decoded.uint16_values, expected.uint16_values)
+ assert np.array_equal(decoded.uint32_values, expected.uint32_values)
+ assert np.array_equal(decoded.uint64_values, expected.uint64_values)
+ assert np.array_equal(decoded.float32_values, expected.float32_values)
+ assert np.array_equal(decoded.float64_values, expected.float64_values)
+
+
+def assert_numeric_collections_array_equal(
+ decoded: "collection.NumericCollectionsArray",
+ expected: "collection.NumericCollectionsArray",
+) -> None:
+ assert np.array_equal(decoded.int8_values, expected.int8_values)
+ assert np.array_equal(decoded.int16_values, expected.int16_values)
+ assert np.array_equal(decoded.int32_values, expected.int32_values)
+ assert np.array_equal(decoded.int64_values, expected.int64_values)
+ assert np.array_equal(decoded.uint8_values, expected.uint8_values)
+ assert np.array_equal(decoded.uint16_values, expected.uint16_values)
+ assert np.array_equal(decoded.uint32_values, expected.uint32_values)
+ assert np.array_equal(decoded.uint64_values, expected.uint64_values)
+ assert np.array_equal(decoded.float32_values, expected.float32_values)
+ assert np.array_equal(decoded.float64_values, expected.float64_values)
+
+
+def assert_numeric_collection_union_equal(
+ decoded: "collection.NumericCollectionUnion",
+ expected: "collection.NumericCollectionUnion",
+) -> None:
+ assert decoded.case() == expected.case()
+ if decoded.case() == collection.NumericCollectionUnionCase.INT32_VALUES:
+ assert np.array_equal(
+ decoded.int32_values_value(), expected.int32_values_value()
+ )
+ return
+ raise AssertionError(f"unexpected union case: {decoded.case()}")
+
+
+def assert_numeric_collection_array_union_equal(
+ decoded: "collection.NumericCollectionArrayUnion",
+ expected: "collection.NumericCollectionArrayUnion",
+) -> None:
+ assert decoded.case() == expected.case()
+ if decoded.case() ==
collection.NumericCollectionArrayUnionCase.UINT16_VALUES:
+ assert np.array_equal(
+ decoded.uint16_values_value(), expected.uint16_values_value()
+ )
+ return
+ raise AssertionError(f"unexpected union case: {decoded.case()}")
+
+
def build_optional_holder() -> "optional_types.OptionalHolder":
all_types = optional_types.AllOptionalTypes(
bool_value=True,
@@ -340,6 +441,70 @@ def file_roundtrip_primitives(
Path(data_file).write_bytes(fory.serialize(decoded))
+def local_roundtrip_collections(
+ fory: pyfory.Fory,
+ collections_value: "collection.NumericCollections",
+ union_value: "collection.NumericCollectionUnion",
+ array_value: "collection.NumericCollectionsArray",
+ array_union_value: "collection.NumericCollectionArrayUnion",
+) -> None:
+ decoded = fory.deserialize(fory.serialize(collections_value))
+ assert isinstance(decoded, collection.NumericCollections)
+ assert_numeric_collections_equal(decoded, collections_value)
+
+ decoded_union = fory.deserialize(fory.serialize(union_value))
+ assert isinstance(decoded_union, collection.NumericCollectionUnion)
+ assert_numeric_collection_union_equal(decoded_union, union_value)
+
+ decoded_array = fory.deserialize(fory.serialize(array_value))
+ assert isinstance(decoded_array, collection.NumericCollectionsArray)
+ assert_numeric_collections_array_equal(decoded_array, array_value)
+
+ decoded_array_union = fory.deserialize(fory.serialize(array_union_value))
+ assert isinstance(decoded_array_union,
collection.NumericCollectionArrayUnion)
+ assert_numeric_collection_array_union_equal(decoded_array_union,
array_union_value)
+
+
+def file_roundtrip_collections(
+ fory: pyfory.Fory,
+ collections_value: "collection.NumericCollections",
+ union_value: "collection.NumericCollectionUnion",
+ array_value: "collection.NumericCollectionsArray",
+ array_union_value: "collection.NumericCollectionArrayUnion",
+) -> None:
+ data_file = os.environ.get("DATA_FILE_COLLECTION")
+ if data_file:
+ payload = Path(data_file).read_bytes()
+ decoded = fory.deserialize(payload)
+ assert isinstance(decoded, collection.NumericCollections)
+ assert_numeric_collections_equal(decoded, collections_value)
+ Path(data_file).write_bytes(fory.serialize(decoded))
+
+ union_file = os.environ.get("DATA_FILE_COLLECTION_UNION")
+ if union_file:
+ payload = Path(union_file).read_bytes()
+ decoded = fory.deserialize(payload)
+ assert isinstance(decoded, collection.NumericCollectionUnion)
+ assert_numeric_collection_union_equal(decoded, union_value)
+ Path(union_file).write_bytes(fory.serialize(decoded))
+
+ array_file = os.environ.get("DATA_FILE_COLLECTION_ARRAY")
+ if array_file:
+ payload = Path(array_file).read_bytes()
+ decoded = fory.deserialize(payload)
+ assert isinstance(decoded, collection.NumericCollectionsArray)
+ assert_numeric_collections_array_equal(decoded, array_value)
+ Path(array_file).write_bytes(fory.serialize(decoded))
+
+ array_union_file = os.environ.get("DATA_FILE_COLLECTION_ARRAY_UNION")
+ if array_union_file:
+ payload = Path(array_union_file).read_bytes()
+ decoded = fory.deserialize(payload)
+ assert isinstance(decoded, collection.NumericCollectionArrayUnion)
+ assert_numeric_collection_array_union_equal(decoded, array_union_value)
+ Path(array_union_file).write_bytes(fory.serialize(decoded))
+
+
def assert_optional_types_equal(
decoded: "optional_types.AllOptionalTypes",
expected: "optional_types.AllOptionalTypes",
@@ -506,6 +671,7 @@ def run_roundtrip(compatible: bool) -> None:
addressbook.register_addressbook_types(fory)
monster.register_monster_types(fory)
complex_fbs.register_complex_fbs_types(fory)
+ collection.register_collection_types(fory)
optional_types.register_optional_types_types(fory)
any_example.register_any_example_types(fory)
@@ -519,6 +685,17 @@ def run_roundtrip(compatible: bool) -> None:
local_roundtrip_primitives(fory, primitives)
file_roundtrip_primitives(fory, primitives)
+ collections_value = build_numeric_collections()
+ union_value = build_numeric_collection_union()
+ array_value = build_numeric_collections_array()
+ array_union_value = build_numeric_collection_array_union()
+ local_roundtrip_collections(
+ fory, collections_value, union_value, array_value, array_union_value
+ )
+ file_roundtrip_collections(
+ fory, collections_value, union_value, array_value, array_union_value
+ )
+
monster_value = build_monster()
local_roundtrip_monster(fory, monster_value)
file_roundtrip_monster(fory, monster_value)
diff --git a/integration_tests/idl_tests/rust/src/lib.rs
b/integration_tests/idl_tests/rust/src/lib.rs
index cc601bd6b..47e0e4b70 100644
--- a/integration_tests/idl_tests/rust/src/lib.rs
+++ b/integration_tests/idl_tests/rust/src/lib.rs
@@ -20,6 +20,7 @@ pub mod any_example;
pub mod any_example_pb;
pub mod complex_fbs;
pub mod complex_pb;
+pub mod collection;
pub mod monster;
pub mod root;
pub mod optional_types;
diff --git a/integration_tests/idl_tests/rust/tests/idl_roundtrip.rs
b/integration_tests/idl_tests/rust/tests/idl_roundtrip.rs
index ec95b5e87..82c549938 100644
--- a/integration_tests/idl_tests/rust/tests/idl_roundtrip.rs
+++ b/integration_tests/idl_tests/rust/tests/idl_roundtrip.rs
@@ -28,6 +28,7 @@ use idl_tests::addressbook::{
};
use idl_tests::complex_pb::{self, PrimitiveTypes};
use idl_tests::complex_fbs::{self, Container, Note, Payload, ScalarPack,
Status};
+use idl_tests::collection::{self, NumericCollectionArrayUnion,
NumericCollectionUnion, NumericCollections, NumericCollectionsArray};
use idl_tests::monster::{self, Color, Monster, Vec3};
use idl_tests::optional_types::{self, AllOptionalTypes, OptionalHolder,
OptionalUnion};
use idl_tests::any_example::{self, AnyHolder, AnyInner, AnyUnion};
@@ -155,6 +156,44 @@ fn build_primitive_types() -> PrimitiveTypes {
}
}
+fn build_numeric_collections() -> NumericCollections {
+ NumericCollections {
+ int8_values: vec![1, -2, 3],
+ int16_values: vec![100, -200, 300],
+ int32_values: vec![1000, -2000, 3000],
+ int64_values: vec![10000, -20000, 30000],
+ uint8_values: vec![200, 250],
+ uint16_values: vec![50000, 60000],
+ uint32_values: vec![2000000000, 2100000000],
+ uint64_values: vec![9000000000, 12000000000],
+ float32_values: vec![1.5, 2.5],
+ float64_values: vec![3.5, 4.5],
+ }
+}
+
+fn build_numeric_collection_union() -> NumericCollectionUnion {
+ NumericCollectionUnion::Int32Values(vec![7, 8, 9])
+}
+
+fn build_numeric_collections_array() -> NumericCollectionsArray {
+ NumericCollectionsArray {
+ int8_values: vec![1, -2, 3],
+ int16_values: vec![100, -200, 300],
+ int32_values: vec![1000, -2000, 3000],
+ int64_values: vec![10000, -20000, 30000],
+ uint8_values: vec![200, 250],
+ uint16_values: vec![50000, 60000],
+ uint32_values: vec![2000000000, 2100000000],
+ uint64_values: vec![9000000000, 12000000000],
+ float32_values: vec![1.5, 2.5],
+ float64_values: vec![3.5, 4.5],
+ }
+}
+
+fn build_numeric_collection_array_union() -> NumericCollectionArrayUnion {
+ NumericCollectionArrayUnion::Uint16Values(vec![1000, 2000, 3000])
+}
+
fn build_monster() -> Monster {
let pos = Vec3 {
x: 1.0,
@@ -451,6 +490,7 @@ fn run_address_book_roundtrip(compatible: bool) {
addressbook::register_types(&mut fory).expect("register types");
monster::register_types(&mut fory).expect("register monster types");
complex_fbs::register_types(&mut fory).expect("register flatbuffers
types");
+ collection::register_types(&mut fory).expect("register collection types");
optional_types::register_types(&mut fory).expect("register optional
types");
any_example::register_types(&mut fory).expect("register any example
types");
@@ -489,6 +529,81 @@ fn run_address_book_roundtrip(compatible: bool) {
let encoded = fory.serialize(&peer_types).expect("serialize peer payload");
fs::write(primitive_file, encoded).expect("write data file");
+ let collections = build_numeric_collections();
+ let bytes = fory.serialize(&collections).expect("serialize collections");
+ let roundtrip: NumericCollections =
fory.deserialize(&bytes).expect("deserialize");
+ assert_eq!(collections, roundtrip);
+
+ if let Ok(data_file) = env::var("DATA_FILE_COLLECTION") {
+ let payload = fs::read(&data_file).expect("read data file");
+ let peer_collections: NumericCollections = fory
+ .deserialize(&payload)
+ .expect("deserialize peer payload");
+ assert_eq!(collections, peer_collections);
+ let encoded = fory
+ .serialize(&peer_collections)
+ .expect("serialize peer payload");
+ fs::write(data_file, encoded).expect("write data file");
+ }
+
+ let collection_union = build_numeric_collection_union();
+ let bytes = fory
+ .serialize(&collection_union)
+ .expect("serialize collection union");
+ let roundtrip: NumericCollectionUnion =
fory.deserialize(&bytes).expect("deserialize");
+ assert_eq!(collection_union, roundtrip);
+
+ if let Ok(data_file) = env::var("DATA_FILE_COLLECTION_UNION") {
+ let payload = fs::read(&data_file).expect("read data file");
+ let peer_union: NumericCollectionUnion = fory
+ .deserialize(&payload)
+ .expect("deserialize peer payload");
+ assert_eq!(collection_union, peer_union);
+ let encoded = fory
+ .serialize(&peer_union)
+ .expect("serialize peer payload");
+ fs::write(data_file, encoded).expect("write data file");
+ }
+
+ let collections_array = build_numeric_collections_array();
+ let bytes = fory
+ .serialize(&collections_array)
+ .expect("serialize collection array");
+ let roundtrip: NumericCollectionsArray =
fory.deserialize(&bytes).expect("deserialize");
+ assert_eq!(collections_array, roundtrip);
+
+ if let Ok(data_file) = env::var("DATA_FILE_COLLECTION_ARRAY") {
+ let payload = fs::read(&data_file).expect("read data file");
+ let peer_array: NumericCollectionsArray = fory
+ .deserialize(&payload)
+ .expect("deserialize peer payload");
+ assert_eq!(collections_array, peer_array);
+ let encoded = fory
+ .serialize(&peer_array)
+ .expect("serialize peer payload");
+ fs::write(data_file, encoded).expect("write data file");
+ }
+
+ let collection_array_union = build_numeric_collection_array_union();
+ let bytes = fory
+ .serialize(&collection_array_union)
+ .expect("serialize collection array union");
+ let roundtrip: NumericCollectionArrayUnion =
+ fory.deserialize(&bytes).expect("deserialize");
+ assert_eq!(collection_array_union, roundtrip);
+
+ if let Ok(data_file) = env::var("DATA_FILE_COLLECTION_ARRAY_UNION") {
+ let payload = fs::read(&data_file).expect("read data file");
+ let peer_union: NumericCollectionArrayUnion = fory
+ .deserialize(&payload)
+ .expect("deserialize peer payload");
+ assert_eq!(collection_array_union, peer_union);
+ let encoded = fory
+ .serialize(&peer_union)
+ .expect("serialize peer payload");
+ fs::write(data_file, encoded).expect("write data file");
+ }
+
let monster = build_monster();
let bytes = fory.serialize(&monster).expect("serialize");
let roundtrip: Monster = fory.deserialize(&bytes).expect("deserialize");
diff --git
a/java/fory-core/src/main/java/org/apache/fory/collection/Int16List.java
b/java/fory-core/src/main/java/org/apache/fory/collection/Int16List.java
index 57f8356c3..90116cd7e 100644
--- a/java/fory-core/src/main/java/org/apache/fory/collection/Int16List.java
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/Int16List.java
@@ -145,7 +145,31 @@ public final class Int16List extends AbstractList<Short>
implements RandomAccess
return true;
}
- /** Returns the live backing array; elements beyond {@code size()} are
undefined. */
+ /**
+ * Returns {@code true} if this list is backed by an accessible heap array.
+ *
+ * <p>If this method returns {@code true}, then {@link #getArray()} may be
safely called to obtain
+ * the underlying storage without incurring a data copy.
+ *
+ * @return {@code true} if this list is backed by a primitive array and is
not read-only
+ */
+ public boolean hasArray() {
+ return array != null;
+ }
+
+ /**
+ * Returns the underlying heap array that backs this list.
+ *
+ * <p><b>Warning:</b> Users should check {@link #hasArray()} before calling
this method. If {@code
+ * hasArray()} returns {@code false}, this method will return a copy that
does not reflect
+ * subsequent mutations.
+ *
+ * <p>Modifying the returned array will directly affect the list, and vice
versa. Elements beyond
+ * {@link #size()} are undefined.
+ *
+ * @return the backing array
+ * @throws UnsupportedOperationException if this list is not backed by an
accessible heap array
+ */
public short[] getArray() {
return array;
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/collection/Int32List.java
b/java/fory-core/src/main/java/org/apache/fory/collection/Int32List.java
index 082e4f82e..59802b70e 100644
--- a/java/fory-core/src/main/java/org/apache/fory/collection/Int32List.java
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/Int32List.java
@@ -140,7 +140,31 @@ public final class Int32List extends AbstractList<Integer>
implements RandomAcce
return true;
}
- /** Returns the live backing array; elements beyond {@code size()} are
undefined. */
+ /**
+ * Returns {@code true} if this list is backed by an accessible heap array.
+ *
+ * <p>If this method returns {@code true}, then {@link #getArray()} may be
safely called to obtain
+ * the underlying storage without incurring a data copy.
+ *
+ * @return {@code true} if this list is backed by a primitive array and is
not read-only
+ */
+ public boolean hasArray() {
+ return array != null;
+ }
+
+ /**
+ * Returns the underlying heap array that backs this list.
+ *
+ * <p><b>Warning:</b> Users should check {@link #hasArray()} before calling
this method. If {@code
+ * hasArray()} returns {@code false}, this method will return a copy that
does not reflect
+ * subsequent mutations.
+ *
+ * <p>Modifying the returned array will directly affect the list, and vice
versa. Elements beyond
+ * {@link #size()} are undefined.
+ *
+ * @return the backing array
+ * @throws UnsupportedOperationException if this list is not backed by an
accessible heap array
+ */
public int[] getArray() {
return array;
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/collection/Int64List.java
b/java/fory-core/src/main/java/org/apache/fory/collection/Int64List.java
index a80b5801f..adae411f7 100644
--- a/java/fory-core/src/main/java/org/apache/fory/collection/Int64List.java
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/Int64List.java
@@ -126,7 +126,31 @@ public final class Int64List extends AbstractList<Long>
implements RandomAccess
return true;
}
- /** Returns the live backing array; elements beyond {@code size()} are
undefined. */
+ /**
+ * Returns {@code true} if this list is backed by an accessible heap array.
+ *
+ * <p>If this method returns {@code true}, then {@link #getArray()} may be
safely called to obtain
+ * the underlying storage without incurring a data copy.
+ *
+ * @return {@code true} if this list is backed by a primitive array and is
not read-only
+ */
+ public boolean hasArray() {
+ return array != null;
+ }
+
+ /**
+ * Returns the underlying heap array that backs this list.
+ *
+ * <p><b>Warning:</b> Users should check {@link #hasArray()} before calling
this method. If {@code
+ * hasArray()} returns {@code false}, this method will return a copy that
does not reflect
+ * subsequent mutations.
+ *
+ * <p>Modifying the returned array will directly affect the list, and vice
versa. Elements beyond
+ * {@link #size()} are undefined.
+ *
+ * @return the backing array
+ * @throws UnsupportedOperationException if this list is not backed by an
accessible heap array
+ */
public long[] getArray() {
return array;
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/collection/Int8List.java
b/java/fory-core/src/main/java/org/apache/fory/collection/Int8List.java
index 3927262eb..cf6e4b5c7 100644
--- a/java/fory-core/src/main/java/org/apache/fory/collection/Int8List.java
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/Int8List.java
@@ -145,7 +145,31 @@ public final class Int8List extends AbstractList<Byte>
implements RandomAccess {
return true;
}
- /** Returns the live backing array; elements beyond {@code size()} are
undefined. */
+ /**
+ * Returns {@code true} if this list is backed by an accessible heap array.
+ *
+ * <p>If this method returns {@code true}, then {@link #getArray()} may be
safely called to obtain
+ * the underlying storage without incurring a data copy.
+ *
+ * @return {@code true} if this list is backed by a primitive array and is
not read-only
+ */
+ public boolean hasArray() {
+ return array != null;
+ }
+
+ /**
+ * Returns the underlying heap array that backs this list.
+ *
+ * <p><b>Warning:</b> Users should check {@link #hasArray()} before calling
this method. If {@code
+ * hasArray()} returns {@code false}, this method will return a copy that
does not reflect
+ * subsequent mutations.
+ *
+ * <p>Modifying the returned array will directly affect the list, and vice
versa. Elements beyond
+ * {@link #size()} are undefined.
+ *
+ * @return the backing array
+ * @throws UnsupportedOperationException if this list is not backed by an
accessible heap array
+ */
public byte[] getArray() {
return array;
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/collection/Uint16List.java
b/java/fory-core/src/main/java/org/apache/fory/collection/Uint16List.java
index 3c0239886..17a5ced92 100644
--- a/java/fory-core/src/main/java/org/apache/fory/collection/Uint16List.java
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/Uint16List.java
@@ -145,7 +145,31 @@ public final class Uint16List extends AbstractList<Uint16>
implements RandomAcce
return array[index];
}
- /** Returns the live backing array; elements beyond {@code size()} are
undefined. */
+ /**
+ * Returns {@code true} if this list is backed by an accessible heap array.
+ *
+ * <p>If this method returns {@code true}, then {@link #getArray()} may be
safely called to obtain
+ * the underlying storage without incurring a data copy.
+ *
+ * @return {@code true} if this list is backed by a primitive array and is
not read-only
+ */
+ public boolean hasArray() {
+ return array != null;
+ }
+
+ /**
+ * Returns the underlying heap array that backs this list.
+ *
+ * <p><b>Warning:</b> Users should check {@link #hasArray()} before calling
this method. If {@code
+ * hasArray()} returns {@code false}, this method will return a copy that
does not reflect
+ * subsequent mutations.
+ *
+ * <p>Modifying the returned array will directly affect the list, and vice
versa. Elements beyond
+ * {@link #size()} are undefined.
+ *
+ * @return the backing array
+ * @throws UnsupportedOperationException if this list is not backed by an
accessible heap array
+ */
public short[] getArray() {
return array;
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/collection/Uint32List.java
b/java/fory-core/src/main/java/org/apache/fory/collection/Uint32List.java
index 654155aae..7d1e8618e 100644
--- a/java/fory-core/src/main/java/org/apache/fory/collection/Uint32List.java
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/Uint32List.java
@@ -145,7 +145,31 @@ public final class Uint32List extends AbstractList<Uint32>
implements RandomAcce
return array[index];
}
- /** Returns the live backing array; elements beyond {@code size()} are
undefined. */
+ /**
+ * Returns {@code true} if this list is backed by an accessible heap array.
+ *
+ * <p>If this method returns {@code true}, then {@link #getArray()} may be
safely called to obtain
+ * the underlying storage without incurring a data copy.
+ *
+ * @return {@code true} if this list is backed by a primitive array and is
not read-only
+ */
+ public boolean hasArray() {
+ return array != null;
+ }
+
+ /**
+ * Returns the underlying heap array that backs this list.
+ *
+ * <p><b>Warning:</b> Users should check {@link #hasArray()} before calling
this method. If {@code
+ * hasArray()} returns {@code false}, this method will return a copy that
does not reflect
+ * subsequent mutations.
+ *
+ * <p>Modifying the returned array will directly affect the list, and vice
versa. Elements beyond
+ * {@link #size()} are undefined.
+ *
+ * @return the backing array
+ * @throws UnsupportedOperationException if this list is not backed by an
accessible heap array
+ */
public int[] getArray() {
return array;
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/collection/Uint64List.java
b/java/fory-core/src/main/java/org/apache/fory/collection/Uint64List.java
index 93297f49b..057d82c6b 100644
--- a/java/fory-core/src/main/java/org/apache/fory/collection/Uint64List.java
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/Uint64List.java
@@ -126,7 +126,31 @@ public final class Uint64List extends AbstractList<Uint64>
implements RandomAcce
return array[index];
}
- /** Returns the live backing array; elements beyond {@code size()} are
undefined. */
+ /**
+ * Returns {@code true} if this list is backed by an accessible heap array.
+ *
+ * <p>If this method returns {@code true}, then {@link #getArray()} may be
safely called to obtain
+ * the underlying storage without incurring a data copy.
+ *
+ * @return {@code true} if this list is backed by a primitive array and is
not read-only
+ */
+ public boolean hasArray() {
+ return array != null;
+ }
+
+ /**
+ * Returns the underlying heap array that backs this list.
+ *
+ * <p><b>Warning:</b> Users should check {@link #hasArray()} before calling
this method. If {@code
+ * hasArray()} returns {@code false}, this method will return a copy that
does not reflect
+ * subsequent mutations.
+ *
+ * <p>Modifying the returned array will directly affect the list, and vice
versa. Elements beyond
+ * {@link #size()} are undefined.
+ *
+ * @return the backing array
+ * @throws UnsupportedOperationException if this list is not backed by an
accessible heap array
+ */
public long[] getArray() {
return array;
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/collection/Uint8List.java
b/java/fory-core/src/main/java/org/apache/fory/collection/Uint8List.java
index 3bce16ebe..ef2b810ab 100644
--- a/java/fory-core/src/main/java/org/apache/fory/collection/Uint8List.java
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/Uint8List.java
@@ -145,7 +145,31 @@ public final class Uint8List extends AbstractList<Uint8>
implements RandomAccess
return array[index];
}
- /** Returns the live backing array; elements beyond {@code size()} are
undefined. */
+ /**
+ * Returns {@code true} if this list is backed by an accessible heap array.
+ *
+ * <p>If this method returns {@code true}, then {@link #getArray()} may be
safely called to obtain
+ * the underlying storage without incurring a data copy.
+ *
+ * @return {@code true} if this list is backed by a primitive array and is
not read-only
+ */
+ public boolean hasArray() {
+ return array != null;
+ }
+
+ /**
+ * Returns the underlying heap array that backs this list.
+ *
+ * <p><b>Warning:</b> Users should check {@link #hasArray()} before calling
this method. If {@code
+ * hasArray()} returns {@code false}, this method will return a copy that
does not reflect
+ * subsequent mutations.
+ *
+ * <p>Modifying the returned array will directly affect the list, and vice
versa. Elements beyond
+ * {@link #size()} are undefined.
+ *
+ * @return the backing array
+ * @throws UnsupportedOperationException if this list is not backed by an
accessible heap array
+ */
public byte[] getArray() {
return array;
}
diff --git a/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
b/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
index 3f653b378..07753aee2 100644
--- a/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
+++ b/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
@@ -32,7 +32,18 @@ import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Objects;
import org.apache.fory.annotation.ForyField;
+import org.apache.fory.collection.BoolList;
+import org.apache.fory.collection.Float32List;
+import org.apache.fory.collection.Float64List;
+import org.apache.fory.collection.Int16List;
+import org.apache.fory.collection.Int32List;
+import org.apache.fory.collection.Int64List;
+import org.apache.fory.collection.Int8List;
import org.apache.fory.collection.Tuple2;
+import org.apache.fory.collection.Uint16List;
+import org.apache.fory.collection.Uint32List;
+import org.apache.fory.collection.Uint64List;
+import org.apache.fory.collection.Uint8List;
import org.apache.fory.logging.Logger;
import org.apache.fory.logging.LoggerFactory;
import org.apache.fory.memory.MemoryBuffer;
@@ -157,6 +168,10 @@ public class FieldTypes {
typeId = Types.UNION;
}
+ if (Types.isPrimitiveArray(typeId & 0xff)) {
+ return new RegisteredFieldType(nullable, trackingRef, typeId);
+ }
+
if (COLLECTION_TYPE.isSupertypeOf(genericType.getTypeRef())) {
return new CollectionFieldType(
typeId,
@@ -484,6 +499,10 @@ public class FieldTypes {
if (declaredRaw.isArray()) {
return TypeRef.of(declaredRaw, new TypeExtMeta(typeId, nullable,
trackingRef));
}
+ Class<?> listClass = getPrimitiveListClass(internalTypeId);
+ if (listClass != null && listClass.isAssignableFrom(declaredRaw)) {
+ return TypeRef.of(declaredRaw, new TypeExtMeta(typeId, nullable,
trackingRef));
+ }
}
cls = getPrimitiveArrayClass(internalTypeId);
if (cls != null) {
@@ -580,6 +599,35 @@ public class FieldTypes {
}
}
+ private static Class<?> getPrimitiveListClass(int typeId) {
+ switch (typeId) {
+ case Types.BOOL_ARRAY:
+ return BoolList.class;
+ case Types.INT8_ARRAY:
+ return Int8List.class;
+ case Types.UINT8_ARRAY:
+ return Uint8List.class;
+ case Types.INT16_ARRAY:
+ return Int16List.class;
+ case Types.UINT16_ARRAY:
+ return Uint16List.class;
+ case Types.INT32_ARRAY:
+ return Int32List.class;
+ case Types.UINT32_ARRAY:
+ return Uint32List.class;
+ case Types.INT64_ARRAY:
+ return Int64List.class;
+ case Types.UINT64_ARRAY:
+ return Uint64List.class;
+ case Types.FLOAT32_ARRAY:
+ return Float32List.class;
+ case Types.FLOAT64_ARRAY:
+ return Float64List.class;
+ default:
+ return null;
+ }
+ }
+
/**
* Class for collection field type, which store collection element type
information. Nested
* collection/map generics example:
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
index e758ebd6f..099433c00 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
@@ -871,6 +871,9 @@ public class ClassResolver extends TypeResolver {
*/
@Override
public boolean isMonomorphic(Class<?> clz) {
+ if (TypeUtils.isPrimitiveListClass(clz)) {
+ return true;
+ }
if (fory.getConfig().isMetaShareEnabled()) {
// can't create final map/collection type using
TypeUtils.mapOf(TypeToken<K>,
// TypeToken<V>)
@@ -893,6 +896,9 @@ public class ClassResolver extends TypeResolver {
}
public boolean isBuildIn(Descriptor descriptor) {
+ if (TypeUtils.isPrimitiveListClass(descriptor.getRawType())) {
+ return true;
+ }
return isMonomorphic(descriptor);
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
index 6e136d225..c973b50b7 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
@@ -992,15 +992,19 @@ public abstract class TypeResolver {
public abstract <T> void setSerializerIfAbsent(Class<T> cls, Serializer<T>
serializer);
- public final Serializer<?> getSerializerByTypeId(int typeId) {
+ public final ClassInfo getClassInfoByTypeId(int typeId) {
int internalTypeId = typeId & 0xFF;
if (Types.isUserDefinedType((byte) internalTypeId)) {
int userId = typeId >>> 8;
if (userId != 0) {
- return requireUserTypeInfoByTypeId(userId).getSerializer();
+ return requireUserTypeInfoByTypeId(userId);
}
}
- return requireInternalTypeInfoByTypeId(internalTypeId).getSerializer();
+ return requireInternalTypeInfoByTypeId(internalTypeId);
+ }
+
+ public final Serializer<?> getSerializerByTypeId(int typeId) {
+ return getClassInfoByTypeId(typeId).getSerializer();
}
public final ClassInfo nilClassInfo() {
@@ -1144,6 +1148,9 @@ public abstract class TypeResolver {
}
public final boolean isCollection(Class<?> cls) {
+ if (TypeUtils.isPrimitiveListClass(cls)) {
+ return false;
+ }
if (Collection.class.isAssignableFrom(cls)) {
return true;
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
index 4f8cd30bb..785dd2266 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
@@ -561,6 +561,9 @@ public class XtypeResolver extends TypeResolver {
return true;
default:
Class<?> rawType = descriptor.getRawType();
+ if (TypeUtils.isPrimitiveListClass(rawType)) {
+ return true;
+ }
if (rawType == Object.class) {
return false;
}
@@ -583,6 +586,9 @@ public class XtypeResolver extends TypeResolver {
@Override
public boolean isMonomorphic(Class<?> clz) {
+ if (TypeUtils.isPrimitiveListClass(clz)) {
+ return true;
+ }
if (clz == Object.class) {
return false;
}
@@ -618,6 +624,9 @@ public class XtypeResolver extends TypeResolver {
public boolean isBuildIn(Descriptor descriptor) {
Class<?> rawType = descriptor.getRawType();
+ if (TypeUtils.isPrimitiveListClass(rawType)) {
+ return true;
+ }
byte typeIdByte = getInternalTypeId(descriptor);
if (NonexistentClass.class.isAssignableFrom(rawType)) {
return false;
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
index 5f0ff49b9..48654a80d 100644
--- a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
+++ b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
@@ -176,6 +176,22 @@ public class Serializers {
}
}
+ public static <T> void write(MemoryBuffer buffer, Serializer<T> serializer,
T obj) {
+ if (serializer.isJava) {
+ serializer.write(buffer, obj);
+ } else {
+ serializer.xwrite(buffer, obj);
+ }
+ }
+
+ public static <T> T read(MemoryBuffer buffer, Serializer<T> serializer) {
+ if (serializer.isJava) {
+ return serializer.read(buffer);
+ } else {
+ return serializer.xread(buffer);
+ }
+ }
+
public abstract static class CrossLanguageCompatibleSerializer<T> extends
Serializer<T> {
public CrossLanguageCompatibleSerializer(Fory fory, Class<T> cls) {
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/UnionSerializer.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/UnionSerializer.java
index da8c38297..c8987e104 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/UnionSerializer.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/UnionSerializer.java
@@ -21,15 +21,20 @@ package org.apache.fory.serializer;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
import java.util.function.BiFunction;
import org.apache.fory.Fory;
+import org.apache.fory.collection.LongMap;
import org.apache.fory.logging.Logger;
import org.apache.fory.logging.LoggerFactory;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.resolver.ClassInfo;
import org.apache.fory.resolver.RefResolver;
import org.apache.fory.resolver.TypeResolver;
-import org.apache.fory.resolver.XtypeResolver;
import org.apache.fory.type.Types;
import org.apache.fory.type.union.Union;
import org.apache.fory.type.union.Union2;
@@ -37,6 +42,7 @@ import org.apache.fory.type.union.Union3;
import org.apache.fory.type.union.Union4;
import org.apache.fory.type.union.Union5;
import org.apache.fory.type.union.Union6;
+import org.apache.fory.util.Preconditions;
/**
* Serializer for {@link Union} and its subclasses ({@link Union2}, {@link
Union3}, {@link Union4},
@@ -53,11 +59,11 @@ import org.apache.fory.type.union.Union6;
* deserialization, not from the serialized data. This allows cross-language
interoperability with
* union types in other languages like C++'s std::variant, Rust's enum, or
Python's typing.Union.
*/
+@SuppressWarnings({"rawtypes", "unchecked"})
public class UnionSerializer extends Serializer<Union> {
private static final Logger LOG =
LoggerFactory.getLogger(UnionSerializer.class);
/** Array of factories for creating Union instances by type tag. */
- @SuppressWarnings("unchecked")
private static final BiFunction<Integer, Object, Union>[] FACTORIES =
new BiFunction[] {
(BiFunction<Integer, Object, Union>) Union::new,
@@ -69,8 +75,11 @@ public class UnionSerializer extends Serializer<Union> {
};
private final BiFunction<Integer, Object, Union> factory;
+ private final Map<Integer, Class<?>> caseValueTypes;
+ private final LongMap<ClassInfo> finalCaseClassInfo;
+ private boolean finalCaseSerializersResolved;
+ private final TypeResolver resolver;
- @SuppressWarnings("unchecked")
public UnionSerializer(Fory fory, Class<? extends Union> cls) {
super(fory, (Class<Union>) cls);
int typeIndex = getTypeIndex(cls);
@@ -79,6 +88,9 @@ public class UnionSerializer extends Serializer<Union> {
} else {
this.factory = createFactory(cls);
}
+ finalCaseClassInfo = new LongMap<>();
+ this.caseValueTypes = resolveCaseValueTypes(cls);
+ resolver = fory.getTypeResolver();
}
private static int getTypeIndex(Class<? extends Union> cls) {
@@ -107,7 +119,7 @@ public class UnionSerializer extends Serializer<Union> {
MethodHandle handle = MethodHandles.lookup().unreflectConstructor(ctor);
return (index, value) -> {
try {
- return (Union) handle.invoke(index.intValue(), value);
+ return (Union) handle.invoke(index, value);
} catch (Throwable t) {
throw new IllegalStateException("Failed to construct union type " +
cls.getName(), t);
}
@@ -135,13 +147,17 @@ public class UnionSerializer extends Serializer<Union> {
int valueTypeId = union.getValueTypeId();
if (valueTypeId == Types.UNKNOWN) {
if (value != null) {
- fory.xwriteRef(buffer, value);
+ if (fory.isCrossLanguage()) {
+ fory.xwriteRef(buffer, value);
+ } else {
+ fory.writeRef(buffer, value);
+ }
} else {
buffer.writeByte(Fory.NULL_FLAG);
}
return;
}
- writeCaseValue(buffer, value, valueTypeId);
+ writeCaseValue(buffer, value, valueTypeId, index);
}
@Override
@@ -152,8 +168,22 @@ public class UnionSerializer extends Serializer<Union> {
@Override
public Union xread(MemoryBuffer buffer) {
int index = buffer.readVarUint32();
- Object value = fory.xreadRef(buffer);
- return factory.apply(index, value);
+ Object caseValue;
+ int nextReadRefId = refResolver.tryPreserveRefId(buffer);
+ if (nextReadRefId >= Fory.NOT_NULL_VALUE_FLAG) {
+ // ref value or not-null value
+ ClassInfo declared = getFinalCaseClassInfo(index);
+ ClassInfo readClassInfo = resolver.readClassInfo(buffer, declared);
+ if (declared != null) {
+ caseValue = Serializers.read(buffer, declared.getSerializer());
+ } else {
+ caseValue = Serializers.read(buffer, readClassInfo.getSerializer());
+ }
+ refResolver.setReadObject(nextReadRefId, caseValue);
+ } else {
+ caseValue = refResolver.getReadObject();
+ }
+ return factory.apply(index, caseValue);
}
@Override
@@ -166,66 +196,40 @@ public class UnionSerializer extends Serializer<Union> {
return factory.apply(union.getIndex(), copiedValue);
}
- private void writeCaseValue(MemoryBuffer buffer, Object value, int typeId) {
- Serializer serializer = null;
- if (value != null) {
- serializer = getSerializer(typeId, value);
+ private void writeCaseValue(MemoryBuffer buffer, Object value, int typeId,
int caseId) {
+ byte internalTypeId = (byte) (typeId & 0xff);
+ boolean primitiveArray = Types.isPrimitiveArray(internalTypeId);
+ Serializer serializer;
+ ClassInfo classInfo;
+ if (value == null) {
+ buffer.writeByte(Fory.NULL_FLAG);
+ return;
}
+ classInfo = getFinalCaseClassInfo(caseId);
+ if (classInfo == null) {
+ Preconditions.checkArgument(!primitiveArray);
+ if (!Types.isUserDefinedType(internalTypeId)) {
+ classInfo = resolver.getClassInfoByTypeId(internalTypeId);
+ } else {
+ classInfo = resolver.getClassInfo(value.getClass());
+ }
+ }
+ Preconditions.checkArgument(classInfo != null);
+ serializer = classInfo.getSerializer();
RefResolver refResolver = fory.getRefResolver();
if (serializer != null && serializer.needToWriteRef()) {
if (refResolver.writeRefOrNull(buffer, value)) {
return;
}
} else {
- if (value == null) {
- buffer.writeByte(Fory.NULL_FLAG);
- return;
- }
buffer.writeByte(Fory.NOT_NULL_VALUE_FLAG);
}
- writeTypeInfo(buffer, typeId, value);
- writeValue(buffer, value, typeId, serializer);
- }
-
- private Serializer getSerializer(int typeId, Object value) {
- int internalTypeId = typeId & 0xff;
- if (isPrimitiveType(internalTypeId)) {
- return null;
- }
- ClassInfo classInfo = getClassInfo(typeId, value);
- return classInfo == null ? null : classInfo.getSerializer();
- }
-
- private void writeTypeInfo(MemoryBuffer buffer, int typeId, Object value) {
- ClassInfo classInfo = getClassInfo(typeId, value);
- if (classInfo == null) {
+ if (!Types.isUserDefinedType(internalTypeId)) {
buffer.writeVarUint32Small7(typeId);
- return;
- }
- fory.getTypeResolver().writeClassInfo(buffer, classInfo);
- }
-
- private ClassInfo getClassInfo(int typeId, Object value) {
- TypeResolver resolver = fory.getTypeResolver();
- int internalTypeId = typeId & 0xff;
- if (typeId >= 256 && resolver instanceof XtypeResolver) {
- ClassInfo classInfo = ((XtypeResolver) resolver).getUserTypeInfo(typeId
>>> 8);
- if (classInfo != null) {
- if ((classInfo.getTypeId() & 0xff) == internalTypeId) {
- return classInfo;
- }
- }
- }
- if (isNamedType(internalTypeId)) {
- return resolver.getClassInfo(value.getClass());
- }
- if (resolver instanceof XtypeResolver) {
- ClassInfo classInfo = ((XtypeResolver) resolver).getXtypeInfo(typeId);
- if (classInfo != null) {
- return classInfo;
- }
+ } else {
+ resolver.writeClassInfo(buffer, classInfo);
}
- return resolver.getClassInfo(value.getClass());
+ writeValue(buffer, value, typeId, serializer);
}
private void writeValue(MemoryBuffer buffer, Object value, int typeId,
Serializer serializer) {
@@ -284,45 +288,116 @@ public class UnionSerializer extends Serializer<Union> {
break;
}
if (serializer != null) {
- serializer.xwrite(buffer, value);
+ Serializers.write(buffer, serializer, value);
return;
}
throw new IllegalStateException("Missing serializer for union type id " +
typeId);
}
- private static boolean isNamedType(int internalTypeId) {
- return internalTypeId == Types.NAMED_ENUM
- || internalTypeId == Types.NAMED_STRUCT
- || internalTypeId == Types.NAMED_EXT
- || internalTypeId == Types.NAMED_UNION
- || internalTypeId == Types.NAMED_COMPATIBLE_STRUCT;
+ private ClassInfo getFinalCaseClassInfo(int caseId) {
+ if (!finalCaseSerializersResolved) {
+ resolveFinalCaseClassInfo();
+ finalCaseSerializersResolved = true;
+ }
+ return finalCaseClassInfo.get(caseId);
}
- private static boolean isPrimitiveType(int internalTypeId) {
- switch (internalTypeId) {
- case Types.BOOL:
- case Types.INT8:
- case Types.INT16:
- case Types.INT32:
- case Types.VARINT32:
- case Types.INT64:
- case Types.VARINT64:
- case Types.TAGGED_INT64:
- case Types.UINT8:
- case Types.UINT16:
- case Types.UINT32:
- case Types.VAR_UINT32:
- case Types.UINT64:
- case Types.VAR_UINT64:
- case Types.TAGGED_UINT64:
- case Types.FLOAT16:
- case Types.FLOAT32:
- case Types.FLOAT64:
- case Types.STRING:
- case Types.BINARY:
- return true;
- default:
- return false;
+ private void resolveFinalCaseClassInfo() {
+ for (Map.Entry<Integer, Class<?>> entry : caseValueTypes.entrySet()) {
+ Class<?> expectedType = entry.getValue();
+ if (!isFinalCaseType(expectedType)) {
+ continue;
+ }
+ if (expectedType.isPrimitive()) {
+ continue;
+ }
+ ClassInfo classInfo = fory.getTypeResolver().getClassInfo(expectedType);
+ finalCaseClassInfo.put(entry.getKey(), classInfo);
+ }
+ }
+
+ private static boolean isFinalCaseType(Class<?> expectedType) {
+ return expectedType.isArray() ||
Modifier.isFinal(expectedType.getModifiers());
+ }
+
+ private Map<Integer, Class<?>> resolveCaseValueTypes(Class<? extends Union>
unionClass) {
+ Map<Integer, Class<?>> mapping = new HashMap<>();
+ Class<? extends Enum<?>> caseEnum = null;
+ Field idField = null;
+ for (Class<?> nested : unionClass.getDeclaredClasses()) {
+ if (nested.isEnum() && nested.getSimpleName().endsWith("Case")) {
+ @SuppressWarnings("unchecked")
+ Class<? extends Enum<?>> enumClass = (Class<? extends Enum<?>>) nested;
+ try {
+ Field field = enumClass.getDeclaredField("id");
+ if (field.getType() == int.class) {
+ caseEnum = enumClass;
+ idField = field;
+ idField.setAccessible(true);
+ break;
+ }
+ } catch (NoSuchFieldException ignored) {
+ // try next enum
+ }
+ }
+ }
+ if (caseEnum == null) {
+ return mapping;
+ }
+ for (Enum<?> constant : caseEnum.getEnumConstants()) {
+ int caseId;
+ try {
+ caseId = (int) idField.get(constant);
+ } catch (IllegalAccessException e) {
+ continue;
+ }
+ String suffix = toPascalCase(constant.name());
+ Class<?> expected = findCaseValueType(unionClass, suffix);
+ if (expected != null) {
+ mapping.put(caseId, expected);
+ }
+ }
+ return mapping;
+ }
+
+ private static Class<?> findCaseValueType(Class<? extends Union> unionClass,
String suffix) {
+ String setterName = "set" + suffix;
+ for (Method method : unionClass.getMethods()) {
+ if (!Modifier.isPublic(method.getModifiers())) {
+ continue;
+ }
+ if (method.getName().equals(setterName) && method.getParameterCount() ==
1) {
+ return method.getParameterTypes()[0];
+ }
+ }
+ String getterName = "get" + suffix;
+ for (Method method : unionClass.getMethods()) {
+ if (!Modifier.isPublic(method.getModifiers())) {
+ continue;
+ }
+ if (method.getName().equals(getterName) && method.getParameterCount() ==
0) {
+ Class<?> returnType = method.getReturnType();
+ if (returnType != void.class) {
+ return returnType;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static String toPascalCase(String upperSnake) {
+ StringBuilder builder = new StringBuilder();
+ String[] parts = upperSnake.split("_");
+ for (String part : parts) {
+ if (part.isEmpty()) {
+ continue;
+ }
+ String lower = part.toLowerCase();
+ builder.append(Character.toUpperCase(lower.charAt(0)));
+ if (lower.length() > 1) {
+ builder.append(lower.substring(1));
+ }
}
+ return builder.toString();
}
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/struct/Fingerprint.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/struct/Fingerprint.java
index 75d2c9e11..d31f62295 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/struct/Fingerprint.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/struct/Fingerprint.java
@@ -23,6 +23,17 @@ import java.util.ArrayList;
import java.util.List;
import org.apache.fory.Fory;
import org.apache.fory.annotation.ForyField;
+import org.apache.fory.collection.BoolList;
+import org.apache.fory.collection.Float32List;
+import org.apache.fory.collection.Float64List;
+import org.apache.fory.collection.Int16List;
+import org.apache.fory.collection.Int32List;
+import org.apache.fory.collection.Int64List;
+import org.apache.fory.collection.Int8List;
+import org.apache.fory.collection.Uint16List;
+import org.apache.fory.collection.Uint32List;
+import org.apache.fory.collection.Uint64List;
+import org.apache.fory.collection.Uint8List;
import org.apache.fory.logging.Logger;
import org.apache.fory.logging.LoggerFactory;
import org.apache.fory.reflect.ReflectionUtils;
@@ -144,6 +155,10 @@ public class Fingerprint {
private static int getTypeId(Fory fory, Descriptor descriptor) {
Class<?> cls = descriptor.getTypeRef().getRawType();
+ Integer primitiveListTypeId = getPrimitiveListTypeId(cls);
+ if (primitiveListTypeId != null) {
+ return primitiveListTypeId;
+ }
TypeResolver resolver = fory.getTypeResolver();
if (resolver.isSet(cls)) {
return Types.SET;
@@ -169,4 +184,41 @@ public class Fingerprint {
return typeId;
}
}
+
+ private static Integer getPrimitiveListTypeId(Class<?> cls) {
+ if (cls == BoolList.class) {
+ return Types.BOOL_ARRAY;
+ }
+ if (cls == Int8List.class) {
+ return Types.INT8_ARRAY;
+ }
+ if (cls == Int16List.class) {
+ return Types.INT16_ARRAY;
+ }
+ if (cls == Int32List.class) {
+ return Types.INT32_ARRAY;
+ }
+ if (cls == Int64List.class) {
+ return Types.INT64_ARRAY;
+ }
+ if (cls == Uint8List.class) {
+ return Types.UINT8_ARRAY;
+ }
+ if (cls == Uint16List.class) {
+ return Types.UINT16_ARRAY;
+ }
+ if (cls == Uint32List.class) {
+ return Types.UINT32_ARRAY;
+ }
+ if (cls == Uint64List.class) {
+ return Types.UINT64_ARRAY;
+ }
+ if (cls == Float32List.class) {
+ return Types.FLOAT32_ARRAY;
+ }
+ if (cls == Float64List.class) {
+ return Types.FLOAT64_ARRAY;
+ }
+ return null;
+ }
}
diff --git a/java/fory-core/src/main/java/org/apache/fory/type/TypeUtils.java
b/java/fory-core/src/main/java/org/apache/fory/type/TypeUtils.java
index 5859e4c62..a8820435f 100644
--- a/java/fory-core/src/main/java/org/apache/fory/type/TypeUtils.java
+++ b/java/fory-core/src/main/java/org/apache/fory/type/TypeUtils.java
@@ -70,8 +70,19 @@ import java.util.TimeZone;
import java.util.WeakHashMap;
import java.util.stream.Collectors;
import org.apache.fory.annotation.Ref;
+import org.apache.fory.collection.BoolList;
+import org.apache.fory.collection.Float32List;
+import org.apache.fory.collection.Float64List;
import org.apache.fory.collection.IdentityMap;
+import org.apache.fory.collection.Int16List;
+import org.apache.fory.collection.Int32List;
+import org.apache.fory.collection.Int64List;
+import org.apache.fory.collection.Int8List;
import org.apache.fory.collection.Tuple2;
+import org.apache.fory.collection.Uint16List;
+import org.apache.fory.collection.Uint32List;
+import org.apache.fory.collection.Uint64List;
+import org.apache.fory.collection.Uint8List;
import org.apache.fory.meta.TypeExtMeta;
import org.apache.fory.reflect.ReflectionUtils;
import org.apache.fory.reflect.TypeParameter;
@@ -655,9 +666,26 @@ public class TypeUtils {
}
public static boolean isCollection(Class<?> cls) {
+ if (isPrimitiveListClass(cls)) {
+ return false;
+ }
return cls == ArrayList.class || Collection.class.isAssignableFrom(cls);
}
+ public static boolean isPrimitiveListClass(Class<?> cls) {
+ return cls == BoolList.class
+ || cls == Int8List.class
+ || cls == Int16List.class
+ || cls == Int32List.class
+ || cls == Int64List.class
+ || cls == Uint8List.class
+ || cls == Uint16List.class
+ || cls == Uint32List.class
+ || cls == Uint64List.class
+ || cls == Float32List.class
+ || cls == Float64List.class;
+ }
+
public static boolean isMap(Class<?> cls) {
if (cls == NonexistentClass.NonexistentMetaShared.class) {
return false;
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/PrimitiveSerializersTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/PrimitiveSerializersTest.java
index 5ef68ba19..c662eaf6c 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/PrimitiveSerializersTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/PrimitiveSerializersTest.java
@@ -25,10 +25,25 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.fory.Fory;
import org.apache.fory.ForyTestBase;
+import org.apache.fory.annotation.Int8ArrayType;
+import org.apache.fory.annotation.Uint16ArrayType;
+import org.apache.fory.annotation.Uint32ArrayType;
+import org.apache.fory.annotation.Uint64ArrayType;
+import org.apache.fory.annotation.Uint8ArrayType;
+import org.apache.fory.collection.Int16List;
+import org.apache.fory.collection.Int32List;
+import org.apache.fory.collection.Int64List;
+import org.apache.fory.collection.Int8List;
+import org.apache.fory.collection.Uint16List;
+import org.apache.fory.collection.Uint32List;
+import org.apache.fory.collection.Uint64List;
+import org.apache.fory.collection.Uint8List;
+import org.apache.fory.config.CompatibleMode;
import org.apache.fory.config.ForyBuilder;
import org.apache.fory.config.Language;
import org.apache.fory.config.LongEncoding;
import org.apache.fory.memory.MemoryBuffer;
+import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class PrimitiveSerializersTest extends ForyTestBase {
@@ -143,4 +158,112 @@ public class PrimitiveSerializersTest extends
ForyTestBase {
Double.MIN_VALUE);
copyCheck(fory, struct);
}
+
+ @DataProvider(name = "compatibleMode")
+ public static Object[][] compatibleModeProvider() {
+ return new Object[][] {{false}, {true}};
+ }
+
+ public static class PrimitiveArrayStruct {
+ @Int8ArrayType public byte[] int8Values;
+ public short[] int16Values;
+ public int[] int32Values;
+ public long[] int64Values;
+ @Uint8ArrayType public byte[] uint8Values;
+ @Uint16ArrayType public short[] uint16Values;
+ @Uint32ArrayType public int[] uint32Values;
+ @Uint64ArrayType public long[] uint64Values;
+ }
+
+ public static class PrimitiveListStruct {
+ public Int8List int8Values;
+ public Int16List int16Values;
+ public Int32List int32Values;
+ public Int64List int64Values;
+ public Uint8List uint8Values;
+ public Uint16List uint16Values;
+ public Uint32List uint32Values;
+ public Uint64List uint64Values;
+ }
+
+ @Test(dataProvider = "compatibleMode")
+ public void testPrimitiveArrayListRoundTrip(boolean compatible) {
+ CompatibleMode mode = compatible ? CompatibleMode.COMPATIBLE :
CompatibleMode.SCHEMA_CONSISTENT;
+ Fory arrayFory =
+ Fory.builder()
+ .withLanguage(Language.XLANG)
+ .withCompatibleMode(mode)
+ .requireClassRegistration(true)
+ .build();
+ Fory listFory =
+ Fory.builder()
+ .withLanguage(Language.XLANG)
+ .withCompatibleMode(mode)
+ .requireClassRegistration(true)
+ .build();
+
+ arrayFory.register(PrimitiveArrayStruct.class, 1001);
+ listFory.register(PrimitiveListStruct.class, 1001);
+
+ PrimitiveArrayStruct arrayStruct = buildPrimitiveArrayStruct();
+ PrimitiveListStruct listStruct = buildPrimitiveListStruct(arrayStruct);
+
+ PrimitiveListStruct listRoundTrip =
+ listFory.deserialize(arrayFory.serialize(arrayStruct),
PrimitiveListStruct.class);
+ assertListEqualsArray(listRoundTrip, arrayStruct);
+
+ PrimitiveArrayStruct arrayRoundTrip =
+ arrayFory.deserialize(listFory.serialize(listStruct),
PrimitiveArrayStruct.class);
+ assertArrayEqualsList(arrayRoundTrip, listStruct);
+ }
+
+ private PrimitiveArrayStruct buildPrimitiveArrayStruct() {
+ PrimitiveArrayStruct struct = new PrimitiveArrayStruct();
+ struct.int8Values = new byte[] {1, -2, 3};
+ struct.int16Values = new short[] {100, -200, 300};
+ struct.int32Values = new int[] {1000, -2000, 3000};
+ struct.int64Values = new long[] {10000L, -20000L, 30000L};
+ struct.uint8Values = new byte[] {(byte) 200, (byte) 250};
+ struct.uint16Values = new short[] {(short) 50000, (short) 60000};
+ struct.uint32Values = new int[] {2000000000, 2100000000};
+ struct.uint64Values = new long[] {9000000000L, 12000000000L};
+ return struct;
+ }
+
+ private PrimitiveListStruct buildPrimitiveListStruct(PrimitiveArrayStruct
arrays) {
+ PrimitiveListStruct struct = new PrimitiveListStruct();
+ struct.int8Values = new Int8List(arrays.int8Values);
+ struct.int16Values = new Int16List(arrays.int16Values);
+ struct.int32Values = new Int32List(arrays.int32Values);
+ struct.int64Values = new Int64List(arrays.int64Values);
+ struct.uint8Values = new Uint8List(arrays.uint8Values);
+ struct.uint16Values = new Uint16List(arrays.uint16Values);
+ struct.uint32Values = new Uint32List(arrays.uint32Values);
+ struct.uint64Values = new Uint64List(arrays.uint64Values);
+ return struct;
+ }
+
+ private void assertListEqualsArray(PrimitiveListStruct list,
PrimitiveArrayStruct arrays) {
+ assertNotNull(list);
+ assertTrue(java.util.Arrays.equals(list.int8Values.copyArray(),
arrays.int8Values));
+ assertTrue(java.util.Arrays.equals(list.int16Values.copyArray(),
arrays.int16Values));
+ assertTrue(java.util.Arrays.equals(list.int32Values.copyArray(),
arrays.int32Values));
+ assertTrue(java.util.Arrays.equals(list.int64Values.copyArray(),
arrays.int64Values));
+ assertTrue(java.util.Arrays.equals(list.uint8Values.copyArray(),
arrays.uint8Values));
+ assertTrue(java.util.Arrays.equals(list.uint16Values.copyArray(),
arrays.uint16Values));
+ assertTrue(java.util.Arrays.equals(list.uint32Values.copyArray(),
arrays.uint32Values));
+ assertTrue(java.util.Arrays.equals(list.uint64Values.copyArray(),
arrays.uint64Values));
+ }
+
+ private void assertArrayEqualsList(PrimitiveArrayStruct arrays,
PrimitiveListStruct list) {
+ assertNotNull(arrays);
+ assertTrue(java.util.Arrays.equals(arrays.int8Values,
list.int8Values.copyArray()));
+ assertTrue(java.util.Arrays.equals(arrays.int16Values,
list.int16Values.copyArray()));
+ assertTrue(java.util.Arrays.equals(arrays.int32Values,
list.int32Values.copyArray()));
+ assertTrue(java.util.Arrays.equals(arrays.int64Values,
list.int64Values.copyArray()));
+ assertTrue(java.util.Arrays.equals(arrays.uint8Values,
list.uint8Values.copyArray()));
+ assertTrue(java.util.Arrays.equals(arrays.uint16Values,
list.uint16Values.copyArray()));
+ assertTrue(java.util.Arrays.equals(arrays.uint32Values,
list.uint32Values.copyArray()));
+ assertTrue(java.util.Arrays.equals(arrays.uint64Values,
list.uint64Values.copyArray()));
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]