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 9e47f7cfe feat(java): implement Union type support for cross-language
serialization (#3062)
9e47f7cfe is described below
commit 9e47f7cfe897f822983726b45c59e831e9aa9fbe
Author: zhan7236 <[email protected]>
AuthorDate: Mon Dec 22 21:29:30 2025 +0800
feat(java): implement Union type support for cross-language serialization
(#3062)
## Why?
Java lacks native support for Union/Variant types that are common in
other languages (C++ std::variant, Rust enum, Python typing.Union). This
limits cross-language serialization interoperability when working with
union types.
## What does this PR do?
Implements Union type support in Java for cross-language serialization,
addressing issue #3030.
**Changes:**
1. **Types.java**: Added `UNION` (38) and `NONE` (39) type constants
following the xlang specification
2. **Union.java**: A tagged union wrapper class that can hold one of
several alternative types
3. **UnionSerializer.java**: Handles serialization/deserialization with
xlang protocol support
4. **ClassResolver.java**: Registered UnionSerializer as default
serializer
5. **XtypeResolver.java**: Registered Union type with UNION type id for
xlang serialization
6. **xlang_type_mapping.md**: Updated type mapping documentation
**Serialization format:**
- Variant index (varuint32): identifies which alternative is active
- Type info: type information for the active alternative
- Value data: serialized value
## Related issues
Closes #3030
## Does this PR introduce any user-facing change?
Yes. Users can now use the `Union` class for cross-language union type
serialization.
- [x] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
Not applicable - this adds new functionality without modifying existing
serialization paths.
---
docs/specification/xlang_type_mapping.md | 2 +-
.../main/java/org/apache/fory/meta/ClassDef.java | 36 ++
.../org/apache/fory/resolver/XtypeResolver.java | 26 +-
.../apache/fory/serializer/UnionSerializer.java | 116 ++++++
.../src/main/java/org/apache/fory/type/Types.java | 6 +
.../java/org/apache/fory/type/union/Union.java | 131 +++++++
.../java/org/apache/fory/type/union/Union2.java | 195 ++++++++++
.../java/org/apache/fory/type/union/Union3.java | 231 +++++++++++
.../java/org/apache/fory/type/union/Union4.java | 268 +++++++++++++
.../java/org/apache/fory/type/union/Union5.java | 310 +++++++++++++++
.../java/org/apache/fory/type/union/Union6.java | 356 +++++++++++++++++
.../fory-core/native-image.properties | 7 +
.../fory/serializer/UnionSerializerTest.java | 430 +++++++++++++++++++++
13 files changed, 2112 insertions(+), 2 deletions(-)
diff --git a/docs/specification/xlang_type_mapping.md
b/docs/specification/xlang_type_mapping.md
index 1661d0b77..58e45f166 100644
--- a/docs/specification/xlang_type_mapping.md
+++ b/docs/specification/xlang_type_mapping.md
@@ -66,7 +66,7 @@ Note:
| float16_array | 35 | float[] | ndarray(float16)
| / | `fory::float16_t[n]/vector<T>` |
`[n]float16/[]T` | `Vec<fory::f16>` |
| float32_array | 36 | float[] | ndarray(float32)
| / | `float[n]/vector<T>` |
`[n]float32/[]T` | `Vec<f32>` |
| float64_array | 37 | double[] | ndarray(float64)
| / | `double[n]/vector<T>` |
`[n]float64/[]T` | `Vec<f64>` |
-| union | 38 | / | /
| / | `std::variant<Ts...>` | /
| tagged union enum |
+| union | 38 | Union | typing.Union
| / | `std::variant<Ts...>` | /
| tagged union enum |
| none | 39 | null | None
| null | `std::monostate` | nil
| `()` |
## Type info(not implemented currently)
diff --git a/java/fory-core/src/main/java/org/apache/fory/meta/ClassDef.java
b/java/fory-core/src/main/java/org/apache/fory/meta/ClassDef.java
index 4855a41a6..99c70efeb 100644
--- a/java/fory-core/src/main/java/org/apache/fory/meta/ClassDef.java
+++ b/java/fory-core/src/main/java/org/apache/fory/meta/ClassDef.java
@@ -754,6 +754,8 @@ public class ClassDef implements Serializable {
case Types.ENUM:
case Types.NAMED_ENUM:
return new EnumFieldType(nullable, xtypeId);
+ case Types.UNION:
+ return new UnionFieldType(nullable, trackingRef);
case Types.UNKNOWN:
return new ObjectFieldType(xtypeId, false, nullable, trackingRef);
default:
@@ -1159,6 +1161,40 @@ public class ClassDef implements Serializable {
}
}
+ /** Class for Union field type. Union types are always monomorphic and use
declared type. */
+ public static class UnionFieldType extends FieldType {
+
+ public UnionFieldType(boolean nullable, boolean trackingRef) {
+ super(Types.UNION, true, nullable, trackingRef);
+ }
+
+ @Override
+ public TypeRef<?> toTypeToken(TypeResolver classResolver, TypeRef<?>
declared) {
+ // Union types use the declared field type directly
+ if (declared != null) {
+ return declared;
+ }
+ // Fallback to base Union class if no declared type
+ return TypeRef.of(
+ org.apache.fory.type.union.Union.class, new TypeExtMeta(nullable,
trackingRef));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "UnionFieldType{" + "nullable=" + nullable + ", trackingRef=" +
trackingRef + '}';
+ }
+ }
+
/** Build field type from generics, nested generics will be extracted too. */
static FieldType buildFieldType(TypeResolver resolver, Field field) {
Preconditions.checkNotNull(field);
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 fb1b797d2..94e1a2774 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
@@ -82,6 +82,7 @@ import org.apache.fory.serializer.SerializationUtils;
import org.apache.fory.serializer.Serializer;
import org.apache.fory.serializer.Serializers;
import org.apache.fory.serializer.TimeSerializers;
+import org.apache.fory.serializer.UnionSerializer;
import org.apache.fory.serializer.collection.CollectionLikeSerializer;
import org.apache.fory.serializer.collection.CollectionSerializer;
import
org.apache.fory.serializer.collection.CollectionSerializers.ArrayListSerializer;
@@ -445,7 +446,8 @@ public class XtypeResolver extends TypeResolver {
Serializer<?> s = classInfo.serializer;
if (s instanceof TimeSerializers.TimeSerializer
|| s instanceof MapLikeSerializer
- || s instanceof CollectionLikeSerializer) {
+ || s instanceof CollectionLikeSerializer
+ || s instanceof UnionSerializer) {
return true;
}
@@ -618,6 +620,7 @@ public class XtypeResolver extends TypeResolver {
registerDefaultTypes(Types.SET, HashSet.class, LinkedHashSet.class,
Set.class);
registerDefaultTypes(Types.MAP, HashMap.class, LinkedHashMap.class,
Map.class);
registerDefaultTypes(Types.LOCAL_DATE, LocalDate.class);
+ registerUnionTypes();
}
private void registerDefaultTypes(int xtypeId, Class<?> defaultType,
Class<?>... otherTypes) {
@@ -645,6 +648,27 @@ public class XtypeResolver extends TypeResolver {
}
}
+ private void registerUnionTypes() {
+ Class<?>[] unionClasses =
+ new Class<?>[] {
+ org.apache.fory.type.union.Union.class,
+ org.apache.fory.type.union.Union2.class,
+ org.apache.fory.type.union.Union3.class,
+ org.apache.fory.type.union.Union4.class,
+ org.apache.fory.type.union.Union5.class,
+ org.apache.fory.type.union.Union6.class
+ };
+ for (Class<?> cls : unionClasses) {
+ @SuppressWarnings("unchecked")
+ Class<? extends org.apache.fory.type.union.Union> unionCls =
+ (Class<? extends org.apache.fory.type.union.Union>) cls;
+ UnionSerializer serializer = new UnionSerializer(fory, unionCls);
+ ClassInfo classInfo = newClassInfo(cls, serializer, (short) Types.UNION);
+ classInfoMap.put(cls, classInfo);
+ }
+ xtypeIdToClassMap.put(Types.UNION,
classInfoMap.get(org.apache.fory.type.union.Union.class));
+ }
+
public ClassInfo writeClassInfo(MemoryBuffer buffer, Object obj) {
ClassInfo classInfo = getClassInfo(obj.getClass(), classInfoCache);
writeClassInfo(buffer, classInfo);
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
new file mode 100644
index 000000000..2d95fec7e
--- /dev/null
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/UnionSerializer.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.serializer;
+
+import java.util.function.BiFunction;
+import org.apache.fory.Fory;
+import org.apache.fory.memory.MemoryBuffer;
+import org.apache.fory.type.union.Union;
+import org.apache.fory.type.union.Union2;
+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;
+
+/**
+ * Serializer for {@link Union} and its subclasses ({@link Union2}, {@link
Union3}, {@link Union4},
+ * {@link Union5}, {@link Union6}).
+ *
+ * <p>The serialization format is:
+ *
+ * <ul>
+ * <li>Variant index (varuint32): identifies which alternative type is active
+ * <li>Value data: the serialized value of the active alternative
+ * </ul>
+ *
+ * <p>The Union type (Union, Union2, etc.) is determined by the declared field
type during
+ * 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.
+ */
+public class UnionSerializer extends Serializer<Union> {
+ /** 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,
+ (BiFunction<Integer, Object, Union>) (index, value) ->
Union2.of(index, value),
+ (BiFunction<Integer, Object, Union>) (index, value) ->
Union3.of(index, value),
+ (BiFunction<Integer, Object, Union>) (index, value) ->
Union4.of(index, value),
+ (BiFunction<Integer, Object, Union>) (index, value) ->
Union5.of(index, value),
+ (BiFunction<Integer, Object, Union>) (index, value) ->
Union6.of(index, value)
+ };
+
+ private final BiFunction<Integer, Object, Union> factory;
+
+ @SuppressWarnings("unchecked")
+ public UnionSerializer(Fory fory, Class<? extends Union> cls) {
+ super(fory, (Class<Union>) cls);
+ this.factory = FACTORIES[getTypeIndex(cls)];
+ }
+
+ private static int getTypeIndex(Class<? extends Union> cls) {
+ if (cls == Union.class) {
+ return 0;
+ } else if (cls == Union2.class) {
+ return 1;
+ } else if (cls == Union3.class) {
+ return 2;
+ } else if (cls == Union4.class) {
+ return 3;
+ } else if (cls == Union5.class) {
+ return 4;
+ } else if (cls == Union6.class) {
+ return 5;
+ } else {
+ // Default to base Union for unknown subclasses
+ return 0;
+ }
+ }
+
+ @Override
+ public void xwrite(MemoryBuffer buffer, Union union) {
+ int index = union.getIndex();
+ buffer.writeVarUint32(index);
+
+ Object value = union.getValue();
+ if (value != null) {
+ fory.xwriteRef(buffer, value);
+ } else {
+ buffer.writeByte(Fory.NULL_FLAG);
+ }
+ }
+
+ @Override
+ public Union xread(MemoryBuffer buffer) {
+ int index = buffer.readVarUint32();
+ Object value = fory.xreadRef(buffer);
+ return factory.apply(index, value);
+ }
+
+ @Override
+ public Union copy(Union union) {
+ if (union == null) {
+ return null;
+ }
+ Object value = union.getValue();
+ Object copiedValue = value != null ? fory.copyObject(value) : null;
+ return factory.apply(union.getIndex(), copiedValue);
+ }
+}
diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Types.java
b/java/fory-core/src/main/java/org/apache/fory/type/Types.java
index 051a2ea00..c93331946 100644
--- a/java/fory-core/src/main/java/org/apache/fory/type/Types.java
+++ b/java/fory-core/src/main/java/org/apache/fory/type/Types.java
@@ -159,6 +159,12 @@ public class Types {
/** One dimensional float64 array. */
public static final int FLOAT64_ARRAY = 37;
+ /** A tagged union type that can hold one of several alternative types. */
+ public static final int UNION = 38;
+
+ /** Represents an empty/unit value with no data (e.g., for empty union
alternatives). */
+ public static final int NONE = 39;
+
/** Bound value for range checks (types with id >= BOUND are not internal
types). */
public static final int BOUND = 64;
diff --git a/java/fory-core/src/main/java/org/apache/fory/type/union/Union.java
b/java/fory-core/src/main/java/org/apache/fory/type/union/Union.java
new file mode 100644
index 000000000..e77214a2f
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/type/union/Union.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.type.union;
+
+import java.util.Objects;
+
+/**
+ * A general union that holds a value and an index. This is the base class for
all typed unions
+ * (Union2, Union3, etc.).
+ *
+ * <p>This class can be used directly when deserializing union data without
knowing the specific
+ * union type, or when you need more than 6 alternative types.
+ *
+ * <p>For type-safe unions with up to 6 types, use {@link Union2}, {@link
Union3}, {@link Union4},
+ * {@link Union5}, or {@link Union6} instead.
+ *
+ * <p>Usage example:
+ *
+ * <pre>{@code
+ * // When deserializing union data without type information
+ * Union union = new Union(0, "hello");
+ * Object value = union.getValue();
+ * int index = union.getIndex();
+ *
+ * // For unions with more than 6 types
+ * Union union = new Union(7, someValue);
+ * }</pre>
+ *
+ * @see Union2
+ * @see Union3
+ * @see Union4
+ * @see Union5
+ * @see Union6
+ */
+public class Union {
+ /** The index indicating which alternative is active. */
+ protected final int index;
+
+ /** The current value. */
+ protected final Object value;
+
+ /**
+ * Creates a new Union with the specified index and value.
+ *
+ * @param index the index of the active alternative
+ * @param value the value
+ */
+ public Union(int index, Object value) {
+ this.index = index;
+ this.value = value;
+ }
+
+ /**
+ * Gets the index of the active alternative.
+ *
+ * @return the index
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Gets the current value.
+ *
+ * @return the value
+ */
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * Gets the current value cast to the specified type.
+ *
+ * @param <T> the expected type
+ * @param type the class of the expected type
+ * @return the current value cast to the specified type
+ * @throws ClassCastException if the value cannot be cast to the specified
type
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T getValue(Class<T> type) {
+ return (T) value;
+ }
+
+ /**
+ * Checks if this union currently holds a value.
+ *
+ * @return true if a value is set (not null), false otherwise
+ */
+ public boolean hasValue() {
+ return value != null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Union)) {
+ return false;
+ }
+ Union union = (Union) o;
+ return index == union.getIndex() && Objects.equals(value,
union.getValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(index, value);
+ }
+
+ @Override
+ public String toString() {
+ return "Union{index=" + index + ", value=" + value + "}";
+ }
+}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/type/union/Union2.java
b/java/fory-core/src/main/java/org/apache/fory/type/union/Union2.java
new file mode 100644
index 000000000..2d71bd71e
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/type/union/Union2.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.type.union;
+
+/**
+ * A typed union that can hold one of two alternative types. The active
alternative is identified by
+ * an index (0 or 1).
+ *
+ * <p>This class extends {@link Union} and provides a type-safe way to
represent values that can be
+ * one of two types, similar to Rust's Result or Either types.
+ *
+ * <p>Usage example:
+ *
+ * <pre>{@code
+ * // Create a union holding a String (type T1, index 0)
+ * Union2<String, Long> union1 = Union2.ofT1("hello");
+ *
+ * // Create a union holding a Long (type T2, index 1)
+ * Union2<String, Long> union2 = Union2.ofT2(100L);
+ *
+ * // Get the value
+ * if (union1.isT1()) {
+ * String value = union1.getT1();
+ * }
+ * }</pre>
+ *
+ * <p>For unions with more than 6 types, use the generic {@link Union} class
instead.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @see Union
+ * @see Union3
+ * @see Union4
+ * @see Union5
+ * @see Union6
+ */
+public final class Union2<T1, T2> extends Union {
+
+ private Union2(int index, Object value) {
+ super(index, value);
+ }
+
+ /**
+ * Creates a Union2 holding a value of the first type T1.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param value the value of type T1
+ * @return a new Union2 instance
+ */
+ public static <T1, T2> Union2<T1, T2> ofT1(T1 value) {
+ Union2<T1, T2> union = new Union2<>(0, value);
+ assert union.getIndex() == 0 : "ofT1 should create union with index 0";
+ return union;
+ }
+
+ /**
+ * Creates a Union2 holding a value of the second type T2.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param value the value of type T2
+ * @return a new Union2 instance
+ */
+ public static <T1, T2> Union2<T1, T2> ofT2(T2 value) {
+ Union2<T1, T2> union = new Union2<>(1, value);
+ assert union.getIndex() == 1 : "ofT2 should create union with index 1";
+ return union;
+ }
+
+ /**
+ * Creates a Union2 from an index and value. Used primarily for
deserialization.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param index the index (0 or 1)
+ * @param value the value
+ * @return a new Union2 instance
+ */
+ public static <T1, T2> Union2<T1, T2> of(int index, Object value) {
+ if (index < 0 || index > 1) {
+ throw new IllegalArgumentException("Index must be 0 or 1 for Union2,
got: " + index);
+ }
+ return new Union2<>(index, value);
+ }
+
+ /**
+ * Gets the index of the active alternative.
+ *
+ * @return 0 if T1 is active, 1 if T2 is active
+ */
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Gets the value as the first type T1.
+ *
+ * @return the value cast to T1
+ * @throws ClassCastException if the active type is not T1
+ */
+ @SuppressWarnings("unchecked")
+ public T1 getT1() {
+ return (T1) value;
+ }
+
+ /**
+ * Gets the value as the second type T2.
+ *
+ * @return the value cast to T2
+ * @throws ClassCastException if the active type is not T2
+ */
+ @SuppressWarnings("unchecked")
+ public T2 getT2() {
+ return (T2) value;
+ }
+
+ /**
+ * Gets the raw value without type casting.
+ *
+ * @return the value
+ */
+ @Override
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * Checks if the first type T1 is active.
+ *
+ * @return true if index is 0
+ */
+ public boolean isT1() {
+ return index == 0;
+ }
+
+ /**
+ * Checks if the second type T2 is active.
+ *
+ * @return true if index is 1
+ */
+ public boolean isT2() {
+ return index == 1;
+ }
+
+ /**
+ * Checks if this union currently holds a non-null value.
+ *
+ * @return true if a value is set (not null), false otherwise
+ */
+ @Override
+ public boolean hasValue() {
+ return value != null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Union)) {
+ return false;
+ }
+ Union union = (Union) o;
+ return index == union.getIndex() && java.util.Objects.equals(value,
union.getValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(index, value);
+ }
+
+ @Override
+ public String toString() {
+ return "Union2{index=" + index + ", value=" + value + "}";
+ }
+}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/type/union/Union3.java
b/java/fory-core/src/main/java/org/apache/fory/type/union/Union3.java
new file mode 100644
index 000000000..ae83df509
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/type/union/Union3.java
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.type.union;
+
+/**
+ * A typed union that can hold one of three alternative types. The active
alternative is identified
+ * by an index (0, 1, or 2).
+ *
+ * <p>This class extends {@link Union} and provides a type-safe way to
represent values that can be
+ * one of three types.
+ *
+ * <p>Usage example:
+ *
+ * <pre>{@code
+ * // Create a union holding an Integer (type T1, index 0)
+ * Union3<Integer, String, Double> union = Union3.ofT1(42);
+ *
+ * // Get the value
+ * if (union.isT1()) {
+ * Integer value = union.getT1();
+ * }
+ * }</pre>
+ *
+ * <p>For unions with more than 6 types, use the generic {@link Union} class
instead.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @see Union
+ * @see Union2
+ * @see Union4
+ * @see Union5
+ * @see Union6
+ */
+public final class Union3<T1, T2, T3> extends Union {
+
+ private Union3(int index, Object value) {
+ super(index, value);
+ }
+
+ /**
+ * Creates a Union3 holding a value of the first type T1.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param value the value of type T1
+ * @return a new Union3 instance
+ */
+ public static <T1, T2, T3> Union3<T1, T2, T3> ofT1(T1 value) {
+ Union3<T1, T2, T3> union = new Union3<>(0, value);
+ assert union.getIndex() == 0 : "ofT1 should create union with index 0";
+ return union;
+ }
+
+ /**
+ * Creates a Union3 holding a value of the second type T2.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param value the value of type T2
+ * @return a new Union3 instance
+ */
+ public static <T1, T2, T3> Union3<T1, T2, T3> ofT2(T2 value) {
+ Union3<T1, T2, T3> union = new Union3<>(1, value);
+ assert union.getIndex() == 1 : "ofT2 should create union with index 1";
+ return union;
+ }
+
+ /**
+ * Creates a Union3 holding a value of the third type T3.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param value the value of type T3
+ * @return a new Union3 instance
+ */
+ public static <T1, T2, T3> Union3<T1, T2, T3> ofT3(T3 value) {
+ Union3<T1, T2, T3> union = new Union3<>(2, value);
+ assert union.getIndex() == 2 : "ofT3 should create union with index 2";
+ return union;
+ }
+
+ /**
+ * Creates a Union3 from an index and value. Used primarily for
deserialization.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param index the index (0, 1, or 2)
+ * @param value the value
+ * @return a new Union3 instance
+ */
+ public static <T1, T2, T3> Union3<T1, T2, T3> of(int index, Object value) {
+ if (index < 0 || index > 2) {
+ throw new IllegalArgumentException("Index must be 0, 1, or 2 for Union3,
got: " + index);
+ }
+ return new Union3<>(index, value);
+ }
+
+ /**
+ * Gets the index of the active alternative.
+ *
+ * @return 0 if T1 is active, 1 if T2 is active, 2 if T3 is active
+ */
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Gets the value as the first type T1.
+ *
+ * @return the value cast to T1
+ * @throws ClassCastException if the active type is not T1
+ */
+ @SuppressWarnings("unchecked")
+ public T1 getT1() {
+ return (T1) value;
+ }
+
+ /**
+ * Gets the value as the second type T2.
+ *
+ * @return the value cast to T2
+ * @throws ClassCastException if the active type is not T2
+ */
+ @SuppressWarnings("unchecked")
+ public T2 getT2() {
+ return (T2) value;
+ }
+
+ /**
+ * Gets the value as the third type T3.
+ *
+ * @return the value cast to T3
+ * @throws ClassCastException if the active type is not T3
+ */
+ @SuppressWarnings("unchecked")
+ public T3 getT3() {
+ return (T3) value;
+ }
+
+ /**
+ * Gets the raw value without type casting.
+ *
+ * @return the value
+ */
+ @Override
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * Checks if the first type T1 is active.
+ *
+ * @return true if index is 0
+ */
+ public boolean isT1() {
+ return index == 0;
+ }
+
+ /**
+ * Checks if the second type T2 is active.
+ *
+ * @return true if index is 1
+ */
+ public boolean isT2() {
+ return index == 1;
+ }
+
+ /**
+ * Checks if the third type T3 is active.
+ *
+ * @return true if index is 2
+ */
+ public boolean isT3() {
+ return index == 2;
+ }
+
+ /**
+ * Checks if this union currently holds a non-null value.
+ *
+ * @return true if a value is set (not null), false otherwise
+ */
+ @Override
+ public boolean hasValue() {
+ return value != null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Union)) {
+ return false;
+ }
+ Union union = (Union) o;
+ return index == union.getIndex() && java.util.Objects.equals(value,
union.getValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(index, value);
+ }
+
+ @Override
+ public String toString() {
+ return "Union3{index=" + index + ", value=" + value + "}";
+ }
+}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/type/union/Union4.java
b/java/fory-core/src/main/java/org/apache/fory/type/union/Union4.java
new file mode 100644
index 000000000..0d96b8365
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/type/union/Union4.java
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.type.union;
+
+/**
+ * A typed union that can hold one of four alternative types. The active
alternative is identified
+ * by an index (0, 1, 2, or 3).
+ *
+ * <p>This class extends {@link Union} and provides a type-safe way to
represent values that can be
+ * one of four types.
+ *
+ * <p>Usage example:
+ *
+ * <pre>{@code
+ * // Create a union holding a String (type T2, index 1)
+ * Union4<Integer, String, Double, Boolean> union = Union4.ofT2("hello");
+ *
+ * // Get the value
+ * if (union.isT2()) {
+ * String value = union.getT2();
+ * }
+ * }</pre>
+ *
+ * <p>For unions with more than 6 types, use the generic {@link Union} class
instead.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @see Union
+ * @see Union2
+ * @see Union3
+ * @see Union5
+ * @see Union6
+ */
+public final class Union4<T1, T2, T3, T4> extends Union {
+
+ private Union4(int index, Object value) {
+ super(index, value);
+ }
+
+ /**
+ * Creates a Union4 holding a value of the first type T1.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param value the value of type T1
+ * @return a new Union4 instance
+ */
+ public static <T1, T2, T3, T4> Union4<T1, T2, T3, T4> ofT1(T1 value) {
+ Union4<T1, T2, T3, T4> union = new Union4<>(0, value);
+ assert union.getIndex() == 0 : "ofT1 should create union with index 0";
+ return union;
+ }
+
+ /**
+ * Creates a Union4 holding a value of the second type T2.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param value the value of type T2
+ * @return a new Union4 instance
+ */
+ public static <T1, T2, T3, T4> Union4<T1, T2, T3, T4> ofT2(T2 value) {
+ Union4<T1, T2, T3, T4> union = new Union4<>(1, value);
+ assert union.getIndex() == 1 : "ofT2 should create union with index 1";
+ return union;
+ }
+
+ /**
+ * Creates a Union4 holding a value of the third type T3.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param value the value of type T3
+ * @return a new Union4 instance
+ */
+ public static <T1, T2, T3, T4> Union4<T1, T2, T3, T4> ofT3(T3 value) {
+ Union4<T1, T2, T3, T4> union = new Union4<>(2, value);
+ assert union.getIndex() == 2 : "ofT3 should create union with index 2";
+ return union;
+ }
+
+ /**
+ * Creates a Union4 holding a value of the fourth type T4.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param value the value of type T4
+ * @return a new Union4 instance
+ */
+ public static <T1, T2, T3, T4> Union4<T1, T2, T3, T4> ofT4(T4 value) {
+ Union4<T1, T2, T3, T4> union = new Union4<>(3, value);
+ assert union.getIndex() == 3 : "ofT4 should create union with index 3";
+ return union;
+ }
+
+ /**
+ * Creates a Union4 from an index and value. Used primarily for
deserialization.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param index the index (0, 1, 2, or 3)
+ * @param value the value
+ * @return a new Union4 instance
+ */
+ public static <T1, T2, T3, T4> Union4<T1, T2, T3, T4> of(int index, Object
value) {
+ if (index < 0 || index > 3) {
+ throw new IllegalArgumentException("Index must be 0-3 for Union4, got: "
+ index);
+ }
+ return new Union4<>(index, value);
+ }
+
+ /**
+ * Gets the index of the active alternative.
+ *
+ * @return 0-3 indicating which type is active
+ */
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Gets the value as the first type T1.
+ *
+ * @return the value cast to T1
+ */
+ @SuppressWarnings("unchecked")
+ public T1 getT1() {
+ return (T1) value;
+ }
+
+ /**
+ * Gets the value as the second type T2.
+ *
+ * @return the value cast to T2
+ */
+ @SuppressWarnings("unchecked")
+ public T2 getT2() {
+ return (T2) value;
+ }
+
+ /**
+ * Gets the value as the third type T3.
+ *
+ * @return the value cast to T3
+ */
+ @SuppressWarnings("unchecked")
+ public T3 getT3() {
+ return (T3) value;
+ }
+
+ /**
+ * Gets the value as the fourth type T4.
+ *
+ * @return the value cast to T4
+ */
+ @SuppressWarnings("unchecked")
+ public T4 getT4() {
+ return (T4) value;
+ }
+
+ /**
+ * Gets the raw value without type casting.
+ *
+ * @return the value
+ */
+ @Override
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * Checks if the first type T1 is active.
+ *
+ * @return true if index is 0
+ */
+ public boolean isT1() {
+ return index == 0;
+ }
+
+ /**
+ * Checks if the second type T2 is active.
+ *
+ * @return true if index is 1
+ */
+ public boolean isT2() {
+ return index == 1;
+ }
+
+ /**
+ * Checks if the third type T3 is active.
+ *
+ * @return true if index is 2
+ */
+ public boolean isT3() {
+ return index == 2;
+ }
+
+ /**
+ * Checks if the fourth type T4 is active.
+ *
+ * @return true if index is 3
+ */
+ public boolean isT4() {
+ return index == 3;
+ }
+
+ /**
+ * Checks if this union currently holds a non-null value.
+ *
+ * @return true if a value is set (not null), false otherwise
+ */
+ @Override
+ public boolean hasValue() {
+ return value != null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Union)) {
+ return false;
+ }
+ Union union = (Union) o;
+ return index == union.getIndex() && java.util.Objects.equals(value,
union.getValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(index, value);
+ }
+
+ @Override
+ public String toString() {
+ return "Union4{index=" + index + ", value=" + value + "}";
+ }
+}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/type/union/Union5.java
b/java/fory-core/src/main/java/org/apache/fory/type/union/Union5.java
new file mode 100644
index 000000000..9770668c0
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/type/union/Union5.java
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.type.union;
+
+/**
+ * A typed union that can hold one of five alternative types. The active
alternative is identified
+ * by an index (0-4).
+ *
+ * <p>This class extends {@link Union} and provides a type-safe way to
represent values that can be
+ * one of five types.
+ *
+ * <p>Usage example:
+ *
+ * <pre>{@code
+ * // Create a union holding a String (type T2, index 1)
+ * Union5<Integer, String, Double, Boolean, Long> union = Union5.ofT2("hello");
+ *
+ * // Get the value
+ * if (union.isT2()) {
+ * String value = union.getT2();
+ * }
+ * }</pre>
+ *
+ * <p>For unions with more than 6 types, use the generic {@link Union} class
instead.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @see Union
+ * @see Union2
+ * @see Union3
+ * @see Union4
+ * @see Union6
+ */
+public final class Union5<T1, T2, T3, T4, T5> extends Union {
+
+ private Union5(int index, Object value) {
+ super(index, value);
+ }
+
+ /**
+ * Creates a Union5 holding a value of the first type T1.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param value the value of type T1
+ * @return a new Union5 instance
+ */
+ public static <T1, T2, T3, T4, T5> Union5<T1, T2, T3, T4, T5> ofT1(T1 value)
{
+ Union5<T1, T2, T3, T4, T5> union = new Union5<>(0, value);
+ assert union.getIndex() == 0 : "ofT1 should create union with index 0";
+ return union;
+ }
+
+ /**
+ * Creates a Union5 holding a value of the second type T2.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param value the value of type T2
+ * @return a new Union5 instance
+ */
+ public static <T1, T2, T3, T4, T5> Union5<T1, T2, T3, T4, T5> ofT2(T2 value)
{
+ Union5<T1, T2, T3, T4, T5> union = new Union5<>(1, value);
+ assert union.getIndex() == 1 : "ofT2 should create union with index 1";
+ return union;
+ }
+
+ /**
+ * Creates a Union5 holding a value of the third type T3.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param value the value of type T3
+ * @return a new Union5 instance
+ */
+ public static <T1, T2, T3, T4, T5> Union5<T1, T2, T3, T4, T5> ofT3(T3 value)
{
+ Union5<T1, T2, T3, T4, T5> union = new Union5<>(2, value);
+ assert union.getIndex() == 2 : "ofT3 should create union with index 2";
+ return union;
+ }
+
+ /**
+ * Creates a Union5 holding a value of the fourth type T4.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param value the value of type T4
+ * @return a new Union5 instance
+ */
+ public static <T1, T2, T3, T4, T5> Union5<T1, T2, T3, T4, T5> ofT4(T4 value)
{
+ Union5<T1, T2, T3, T4, T5> union = new Union5<>(3, value);
+ assert union.getIndex() == 3 : "ofT4 should create union with index 3";
+ return union;
+ }
+
+ /**
+ * Creates a Union5 holding a value of the fifth type T5.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param value the value of type T5
+ * @return a new Union5 instance
+ */
+ public static <T1, T2, T3, T4, T5> Union5<T1, T2, T3, T4, T5> ofT5(T5 value)
{
+ Union5<T1, T2, T3, T4, T5> union = new Union5<>(4, value);
+ assert union.getIndex() == 4 : "ofT5 should create union with index 4";
+ return union;
+ }
+
+ /**
+ * Creates a Union5 from an index and value. Used primarily for
deserialization.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param index the index (0-4)
+ * @param value the value
+ * @return a new Union5 instance
+ */
+ public static <T1, T2, T3, T4, T5> Union5<T1, T2, T3, T4, T5> of(int index,
Object value) {
+ if (index < 0 || index > 4) {
+ throw new IllegalArgumentException("Index must be 0-4 for Union5, got: "
+ index);
+ }
+ return new Union5<>(index, value);
+ }
+
+ /**
+ * Gets the index of the active alternative.
+ *
+ * @return 0-4 indicating which type is active
+ */
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Gets the value as the first type T1.
+ *
+ * @return the value cast to T1
+ */
+ @SuppressWarnings("unchecked")
+ public T1 getT1() {
+ return (T1) value;
+ }
+
+ /**
+ * Gets the value as the second type T2.
+ *
+ * @return the value cast to T2
+ */
+ @SuppressWarnings("unchecked")
+ public T2 getT2() {
+ return (T2) value;
+ }
+
+ /**
+ * Gets the value as the third type T3.
+ *
+ * @return the value cast to T3
+ */
+ @SuppressWarnings("unchecked")
+ public T3 getT3() {
+ return (T3) value;
+ }
+
+ /**
+ * Gets the value as the fourth type T4.
+ *
+ * @return the value cast to T4
+ */
+ @SuppressWarnings("unchecked")
+ public T4 getT4() {
+ return (T4) value;
+ }
+
+ /**
+ * Gets the value as the fifth type T5.
+ *
+ * @return the value cast to T5
+ */
+ @SuppressWarnings("unchecked")
+ public T5 getT5() {
+ return (T5) value;
+ }
+
+ /**
+ * Gets the raw value without type casting.
+ *
+ * @return the value
+ */
+ @Override
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * Checks if the first type T1 is active.
+ *
+ * @return true if index is 0
+ */
+ public boolean isT1() {
+ return index == 0;
+ }
+
+ /**
+ * Checks if the second type T2 is active.
+ *
+ * @return true if index is 1
+ */
+ public boolean isT2() {
+ return index == 1;
+ }
+
+ /**
+ * Checks if the third type T3 is active.
+ *
+ * @return true if index is 2
+ */
+ public boolean isT3() {
+ return index == 2;
+ }
+
+ /**
+ * Checks if the fourth type T4 is active.
+ *
+ * @return true if index is 3
+ */
+ public boolean isT4() {
+ return index == 3;
+ }
+
+ /**
+ * Checks if the fifth type T5 is active.
+ *
+ * @return true if index is 4
+ */
+ public boolean isT5() {
+ return index == 4;
+ }
+
+ /**
+ * Checks if this union currently holds a non-null value.
+ *
+ * @return true if a value is set (not null), false otherwise
+ */
+ @Override
+ public boolean hasValue() {
+ return value != null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Union)) {
+ return false;
+ }
+ Union union = (Union) o;
+ return index == union.getIndex() && java.util.Objects.equals(value,
union.getValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(index, value);
+ }
+
+ @Override
+ public String toString() {
+ return "Union5{index=" + index + ", value=" + value + "}";
+ }
+}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/type/union/Union6.java
b/java/fory-core/src/main/java/org/apache/fory/type/union/Union6.java
new file mode 100644
index 000000000..2a9a5d783
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/type/union/Union6.java
@@ -0,0 +1,356 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.type.union;
+
+/**
+ * A typed union that can hold one of six alternative types. The active
alternative is identified by
+ * an index (0-5).
+ *
+ * <p>This class extends {@link Union} and provides a type-safe way to
represent values that can be
+ * one of six types. Six types should cover most use cases. For unions with
more than 6 types, use
+ * the generic {@link Union} class instead.
+ *
+ * <p>Usage example:
+ *
+ * <pre>{@code
+ * // Create a union holding a String (type T2, index 1)
+ * Union6<Integer, String, Double, Boolean, Long, Float> union =
Union6.ofT2("hello");
+ *
+ * // Get the value
+ * if (union.isT2()) {
+ * String value = union.getT2();
+ * }
+ * }</pre>
+ *
+ * <p>For unions with more than 6 types, use the generic {@link Union} class
instead.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param <T6> the sixth alternative type
+ * @see Union
+ * @see Union2
+ * @see Union3
+ * @see Union4
+ * @see Union5
+ */
+public final class Union6<T1, T2, T3, T4, T5, T6> extends Union {
+
+ private Union6(int index, Object value) {
+ super(index, value);
+ }
+
+ /**
+ * Creates a Union6 holding a value of the first type T1.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param <T6> the sixth alternative type
+ * @param value the value of type T1
+ * @return a new Union6 instance
+ */
+ public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6>
ofT1(T1 value) {
+ Union6<T1, T2, T3, T4, T5, T6> union = new Union6<>(0, value);
+ assert union.getIndex() == 0 : "ofT1 should create union with index 0";
+ return union;
+ }
+
+ /**
+ * Creates a Union6 holding a value of the second type T2.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param <T6> the sixth alternative type
+ * @param value the value of type T2
+ * @return a new Union6 instance
+ */
+ public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6>
ofT2(T2 value) {
+ Union6<T1, T2, T3, T4, T5, T6> union = new Union6<>(1, value);
+ assert union.getIndex() == 1 : "ofT2 should create union with index 1";
+ return union;
+ }
+
+ /**
+ * Creates a Union6 holding a value of the third type T3.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param <T6> the sixth alternative type
+ * @param value the value of type T3
+ * @return a new Union6 instance
+ */
+ public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6>
ofT3(T3 value) {
+ Union6<T1, T2, T3, T4, T5, T6> union = new Union6<>(2, value);
+ assert union.getIndex() == 2 : "ofT3 should create union with index 2";
+ return union;
+ }
+
+ /**
+ * Creates a Union6 holding a value of the fourth type T4.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param <T6> the sixth alternative type
+ * @param value the value of type T4
+ * @return a new Union6 instance
+ */
+ public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6>
ofT4(T4 value) {
+ Union6<T1, T2, T3, T4, T5, T6> union = new Union6<>(3, value);
+ assert union.getIndex() == 3 : "ofT4 should create union with index 3";
+ return union;
+ }
+
+ /**
+ * Creates a Union6 holding a value of the fifth type T5.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param <T6> the sixth alternative type
+ * @param value the value of type T5
+ * @return a new Union6 instance
+ */
+ public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6>
ofT5(T5 value) {
+ Union6<T1, T2, T3, T4, T5, T6> union = new Union6<>(4, value);
+ assert union.getIndex() == 4 : "ofT5 should create union with index 4";
+ return union;
+ }
+
+ /**
+ * Creates a Union6 holding a value of the sixth type T6.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param <T6> the sixth alternative type
+ * @param value the value of type T6
+ * @return a new Union6 instance
+ */
+ public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6>
ofT6(T6 value) {
+ Union6<T1, T2, T3, T4, T5, T6> union = new Union6<>(5, value);
+ assert union.getIndex() == 5 : "ofT6 should create union with index 5";
+ return union;
+ }
+
+ /**
+ * Creates a Union6 from an index and value. Used primarily for
deserialization.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param <T6> the sixth alternative type
+ * @param index the index (0-5)
+ * @param value the value
+ * @return a new Union6 instance
+ */
+ public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6> of(
+ int index, Object value) {
+ if (index < 0 || index > 5) {
+ throw new IllegalArgumentException("Index must be 0-5 for Union6, got: "
+ index);
+ }
+ return new Union6<>(index, value);
+ }
+
+ /**
+ * Gets the index of the active alternative.
+ *
+ * @return 0-5 indicating which type is active
+ */
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Gets the value as the first type T1.
+ *
+ * @return the value cast to T1
+ */
+ @SuppressWarnings("unchecked")
+ public T1 getT1() {
+ return (T1) value;
+ }
+
+ /**
+ * Gets the value as the second type T2.
+ *
+ * @return the value cast to T2
+ */
+ @SuppressWarnings("unchecked")
+ public T2 getT2() {
+ return (T2) value;
+ }
+
+ /**
+ * Gets the value as the third type T3.
+ *
+ * @return the value cast to T3
+ */
+ @SuppressWarnings("unchecked")
+ public T3 getT3() {
+ return (T3) value;
+ }
+
+ /**
+ * Gets the value as the fourth type T4.
+ *
+ * @return the value cast to T4
+ */
+ @SuppressWarnings("unchecked")
+ public T4 getT4() {
+ return (T4) value;
+ }
+
+ /**
+ * Gets the value as the fifth type T5.
+ *
+ * @return the value cast to T5
+ */
+ @SuppressWarnings("unchecked")
+ public T5 getT5() {
+ return (T5) value;
+ }
+
+ /**
+ * Gets the value as the sixth type T6.
+ *
+ * @return the value cast to T6
+ */
+ @SuppressWarnings("unchecked")
+ public T6 getT6() {
+ return (T6) value;
+ }
+
+ /**
+ * Gets the raw value without type casting.
+ *
+ * @return the value
+ */
+ @Override
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * Checks if the first type T1 is active.
+ *
+ * @return true if index is 0
+ */
+ public boolean isT1() {
+ return index == 0;
+ }
+
+ /**
+ * Checks if the second type T2 is active.
+ *
+ * @return true if index is 1
+ */
+ public boolean isT2() {
+ return index == 1;
+ }
+
+ /**
+ * Checks if the third type T3 is active.
+ *
+ * @return true if index is 2
+ */
+ public boolean isT3() {
+ return index == 2;
+ }
+
+ /**
+ * Checks if the fourth type T4 is active.
+ *
+ * @return true if index is 3
+ */
+ public boolean isT4() {
+ return index == 3;
+ }
+
+ /**
+ * Checks if the fifth type T5 is active.
+ *
+ * @return true if index is 4
+ */
+ public boolean isT5() {
+ return index == 4;
+ }
+
+ /**
+ * Checks if the sixth type T6 is active.
+ *
+ * @return true if index is 5
+ */
+ public boolean isT6() {
+ return index == 5;
+ }
+
+ /**
+ * Checks if this union currently holds a non-null value.
+ *
+ * @return true if a value is set (not null), false otherwise
+ */
+ @Override
+ public boolean hasValue() {
+ return value != null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Union)) {
+ return false;
+ }
+ Union union = (Union) o;
+ return index == union.getIndex() && java.util.Objects.equals(value,
union.getValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(index, value);
+ }
+
+ @Override
+ public String toString() {
+ return "Union6{index=" + index + ", value=" + value + "}";
+ }
+}
diff --git
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
index 27de52e84..09a295654 100644
---
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
+++
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
@@ -385,6 +385,7 @@
Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\
org.apache.fory.serializer.TimeSerializers$YearSerializer,\
org.apache.fory.serializer.TimeSerializers$ZoneOffsetSerializer,\
org.apache.fory.serializer.TimeSerializers$ZonedDateTimeSerializer,\
+ org.apache.fory.serializer.UnionSerializer,\
org.apache.fory.serializer.collection.CollectionSerializer,\
org.apache.fory.serializer.collection.CollectionSerializers$ArrayBlockingQueueSerializer,\
org.apache.fory.serializer.collection.CollectionSerializers$ArrayDequeSerializer,\
@@ -487,6 +488,12 @@
Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\
org.apache.fory.type.Type,\
org.apache.fory.type.Types,\
org.apache.fory.type.TypeUtils,\
+ org.apache.fory.type.union.Union,\
+ org.apache.fory.type.union.Union2,\
+ org.apache.fory.type.union.Union3,\
+ org.apache.fory.type.union.Union4,\
+ org.apache.fory.type.union.Union5,\
+ org.apache.fory.type.union.Union6,\
org.apache.fory.util.DefaultValueUtils,\
org.apache.fory.util.DefaultValueUtils$DefaultValueField,\
org.apache.fory.util.DefaultValueUtils$ScalaDefaultValueSupport,\
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/UnionSerializerTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/UnionSerializerTest.java
new file mode 100644
index 000000000..ae5655c08
--- /dev/null
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/UnionSerializerTest.java
@@ -0,0 +1,430 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.serializer;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.fory.Fory;
+import org.apache.fory.ForyTestBase;
+import org.apache.fory.config.CompatibleMode;
+import org.apache.fory.config.Language;
+import org.apache.fory.type.union.Union;
+import org.apache.fory.type.union.Union2;
+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.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for {@link UnionSerializer}. Union serialization is only used for
xlang mode. Union types
+ * are serialized as part of struct fields, where the declared field type
determines which Union
+ * serializer is used.
+ */
+public class UnionSerializerTest extends ForyTestBase {
+
+ @DataProvider(name = "compatibleMode")
+ public static Object[][] compatibleModeProvider() {
+ return new Object[][] {{CompatibleMode.COMPATIBLE},
{CompatibleMode.SCHEMA_CONSISTENT}};
+ }
+
+ private Fory createXlangFory(CompatibleMode mode) {
+ Fory fory =
+ Fory.builder()
+ .withLanguage(Language.XLANG)
+ .withCompatibleMode(mode)
+ .requireClassRegistration(true)
+ .build();
+ fory.register(StructWithUnion.class);
+ fory.register(StructWithUnion2.class);
+ fory.register(StructWithUnion3.class);
+ fory.register(StructWithUnion4.class);
+ fory.register(StructWithUnion5.class);
+ fory.register(StructWithUnion6.class);
+ return fory;
+ }
+
+ // Test struct classes with Union fields
+ public static class StructWithUnion {
+ public Union union;
+
+ public StructWithUnion() {}
+
+ public StructWithUnion(Union union) {
+ this.union = union;
+ }
+ }
+
+ public static class StructWithUnion2 {
+ public Union2<String, Long> union;
+
+ public StructWithUnion2() {}
+
+ public StructWithUnion2(Union2<String, Long> union) {
+ this.union = union;
+ }
+ }
+
+ public static class StructWithUnion3 {
+ public Union3<Integer, String, Double> union;
+
+ public StructWithUnion3() {}
+
+ public StructWithUnion3(Union3<Integer, String, Double> union) {
+ this.union = union;
+ }
+ }
+
+ public static class StructWithUnion4 {
+ public Union4<Integer, String, Double, Boolean> union;
+
+ public StructWithUnion4() {}
+
+ public StructWithUnion4(Union4<Integer, String, Double, Boolean> union) {
+ this.union = union;
+ }
+ }
+
+ public static class StructWithUnion5 {
+ public Union5<Integer, String, Double, Boolean, Long> union;
+
+ public StructWithUnion5() {}
+
+ public StructWithUnion5(Union5<Integer, String, Double, Boolean, Long>
union) {
+ this.union = union;
+ }
+ }
+
+ public static class StructWithUnion6 {
+ public Union6<Integer, String, Double, Boolean, Long, Float> union;
+
+ public StructWithUnion6() {}
+
+ public StructWithUnion6(Union6<Integer, String, Double, Boolean, Long,
Float> union) {
+ this.union = union;
+ }
+ }
+
+ @Test(dataProvider = "compatibleMode")
+ public void testUnionBasicTypes(CompatibleMode mode) {
+ Fory fory = createXlangFory(mode);
+
+ // Test with Integer value via struct
+ StructWithUnion struct = new StructWithUnion(new Union(0, 42));
+ byte[] bytes = fory.serialize(struct);
+ StructWithUnion deserialized = (StructWithUnion) fory.deserialize(bytes);
+ assertEquals(deserialized.union.getValue(), 42);
+ assertEquals(deserialized.union.getIndex(), 0);
+
+ // Test with String value
+ struct = new StructWithUnion(new Union(1, "hello"));
+ bytes = fory.serialize(struct);
+ deserialized = (StructWithUnion) fory.deserialize(bytes);
+ assertEquals(deserialized.union.getValue(), "hello");
+ assertEquals(deserialized.union.getIndex(), 1);
+ }
+
+ @Test(dataProvider = "compatibleMode")
+ public void testUnion2Serialization(CompatibleMode mode) {
+ Fory fory = createXlangFory(mode);
+
+ // Test with T1 (String) via struct field
+ StructWithUnion2 struct = new StructWithUnion2(Union2.ofT1("hello"));
+ byte[] bytes = fory.serialize(struct);
+ StructWithUnion2 deserialized = (StructWithUnion2) fory.deserialize(bytes);
+ assertEquals(deserialized.union.getValue(), "hello");
+ assertEquals(deserialized.union.getIndex(), 0);
+ assertTrue(deserialized.union.isT1());
+ assertTrue(deserialized.union instanceof Union2);
+
+ // Test with T2 (Long)
+ struct = new StructWithUnion2(Union2.ofT2(100L));
+ bytes = fory.serialize(struct);
+ deserialized = (StructWithUnion2) fory.deserialize(bytes);
+ assertEquals(deserialized.union.getValue(), 100L);
+ assertEquals(deserialized.union.getIndex(), 1);
+ assertTrue(deserialized.union.isT2());
+ }
+
+ @Test(dataProvider = "compatibleMode")
+ public void testUnion3Serialization(CompatibleMode mode) {
+ Fory fory = createXlangFory(mode);
+
+ // Test with T1
+ StructWithUnion3 struct = new StructWithUnion3(Union3.ofT1(42));
+ byte[] bytes = fory.serialize(struct);
+ StructWithUnion3 deserialized = (StructWithUnion3) fory.deserialize(bytes);
+ assertEquals(deserialized.union.getValue(), 42);
+ assertEquals(deserialized.union.getIndex(), 0);
+ assertTrue(deserialized.union.isT1());
+ assertTrue(deserialized.union instanceof Union3);
+
+ // Test with T2
+ struct = new StructWithUnion3(Union3.ofT2("test"));
+ bytes = fory.serialize(struct);
+ deserialized = (StructWithUnion3) fory.deserialize(bytes);
+ assertEquals(deserialized.union.getValue(), "test");
+ assertEquals(deserialized.union.getIndex(), 1);
+ assertTrue(deserialized.union.isT2());
+
+ // Test with T3
+ struct = new StructWithUnion3(Union3.ofT3(3.14));
+ bytes = fory.serialize(struct);
+ deserialized = (StructWithUnion3) fory.deserialize(bytes);
+ assertEquals((Double) deserialized.union.getValue(), 3.14, 0.0001);
+ assertEquals(deserialized.union.getIndex(), 2);
+ assertTrue(deserialized.union.isT3());
+ }
+
+ @Test(dataProvider = "compatibleMode")
+ public void testUnion4Serialization(CompatibleMode mode) {
+ Fory fory = createXlangFory(mode);
+
+ // Test with T1
+ StructWithUnion4 struct = new StructWithUnion4(Union4.ofT1(42));
+ byte[] bytes = fory.serialize(struct);
+ StructWithUnion4 deserialized = (StructWithUnion4) fory.deserialize(bytes);
+ assertEquals(deserialized.union.getValue(), 42);
+ assertEquals(deserialized.union.getIndex(), 0);
+ assertTrue(deserialized.union.isT1());
+ assertTrue(deserialized.union instanceof Union4);
+
+ // Test with T4
+ struct = new StructWithUnion4(Union4.ofT4(true));
+ bytes = fory.serialize(struct);
+ deserialized = (StructWithUnion4) fory.deserialize(bytes);
+ assertEquals(deserialized.union.getValue(), true);
+ assertEquals(deserialized.union.getIndex(), 3);
+ assertTrue(deserialized.union.isT4());
+ }
+
+ @Test(dataProvider = "compatibleMode")
+ public void testUnion5Serialization(CompatibleMode mode) {
+ Fory fory = createXlangFory(mode);
+
+ // Test with T1
+ StructWithUnion5 struct = new StructWithUnion5(Union5.ofT1(42));
+ byte[] bytes = fory.serialize(struct);
+ StructWithUnion5 deserialized = (StructWithUnion5) fory.deserialize(bytes);
+ assertEquals(deserialized.union.getValue(), 42);
+ assertEquals(deserialized.union.getIndex(), 0);
+ assertTrue(deserialized.union.isT1());
+ assertTrue(deserialized.union instanceof Union5);
+
+ // Test with T5
+ struct = new StructWithUnion5(Union5.ofT5(999L));
+ bytes = fory.serialize(struct);
+ deserialized = (StructWithUnion5) fory.deserialize(bytes);
+ assertEquals(deserialized.union.getValue(), 999L);
+ assertEquals(deserialized.union.getIndex(), 4);
+ assertTrue(deserialized.union.isT5());
+ }
+
+ @Test(dataProvider = "compatibleMode")
+ public void testUnion6Serialization(CompatibleMode mode) {
+ Fory fory = createXlangFory(mode);
+
+ // Test with T1
+ StructWithUnion6 struct = new StructWithUnion6(Union6.ofT1(42));
+ byte[] bytes = fory.serialize(struct);
+ StructWithUnion6 deserialized = (StructWithUnion6) fory.deserialize(bytes);
+ assertEquals(deserialized.union.getValue(), 42);
+ assertEquals(deserialized.union.getIndex(), 0);
+ assertTrue(deserialized.union.isT1());
+ assertTrue(deserialized.union instanceof Union6);
+
+ // Test with T6
+ struct = new StructWithUnion6(Union6.ofT6(1.5f));
+ bytes = fory.serialize(struct);
+ deserialized = (StructWithUnion6) fory.deserialize(bytes);
+ assertEquals(deserialized.union.getValue(), 1.5f);
+ assertEquals(deserialized.union.getIndex(), 5);
+ assertTrue(deserialized.union.isT6());
+ }
+
+ @Test(dataProvider = "compatibleMode")
+ public void testUnionWithCollections(CompatibleMode mode) {
+ Fory fory = createXlangFory(mode);
+
+ // Test with List
+ List<Integer> list = new ArrayList<>();
+ list.add(1);
+ list.add(2);
+ list.add(3);
+ StructWithUnion struct = new StructWithUnion(new Union(0, list));
+ byte[] bytes = fory.serialize(struct);
+ StructWithUnion deserialized = (StructWithUnion) fory.deserialize(bytes);
+ assertTrue(deserialized.union.getValue() instanceof List);
+ assertEquals(deserialized.union.getValue(), list);
+
+ // Test with Map
+ Map<String, Integer> map = new HashMap<>();
+ map.put("a", 1);
+ map.put("b", 2);
+ struct = new StructWithUnion(new Union(1, map));
+ bytes = fory.serialize(struct);
+ deserialized = (StructWithUnion) fory.deserialize(bytes);
+ assertTrue(deserialized.union.getValue() instanceof Map);
+ assertEquals(deserialized.union.getValue(), map);
+ }
+
+ @Test(dataProvider = "compatibleMode")
+ public void testUnionWithNull(CompatibleMode mode) {
+ Fory fory = createXlangFory(mode);
+
+ Union union = new Union(0, null);
+ assertFalse(union.hasValue());
+
+ StructWithUnion struct = new StructWithUnion(union);
+ byte[] bytes = fory.serialize(struct);
+ StructWithUnion deserialized = (StructWithUnion) fory.deserialize(bytes);
+ assertNotNull(deserialized.union);
+ assertNull(deserialized.union.getValue());
+ assertEquals(deserialized.union.getIndex(), 0);
+ }
+
+ @Test
+ public void testUnion2TypeSafety() {
+ Union2<String, Long> union = Union2.ofT1("hello");
+ assertTrue(union.isT1());
+ assertFalse(union.isT2());
+ assertEquals(union.getT1(), "hello");
+ assertEquals(union.getIndex(), 0);
+
+ Union2<String, Long> union2 = Union2.ofT2(100L);
+ assertFalse(union2.isT1());
+ assertTrue(union2.isT2());
+ assertEquals(union2.getT2(), Long.valueOf(100L));
+ assertEquals(union2.getIndex(), 1);
+ }
+
+ @Test
+ public void testUnion3TypeSafety() {
+ Union3<Integer, String, Double> union = Union3.ofT2("test");
+ assertFalse(union.isT1());
+ assertTrue(union.isT2());
+ assertFalse(union.isT3());
+ assertEquals(union.getT2(), "test");
+ }
+
+ @Test
+ public void testUnion4TypeSafety() {
+ Union4<Integer, String, Double, Boolean> union = Union4.ofT3(3.14);
+ assertFalse(union.isT1());
+ assertFalse(union.isT2());
+ assertTrue(union.isT3());
+ assertFalse(union.isT4());
+ assertEquals(union.getT3(), 3.14);
+ }
+
+ @Test
+ public void testUnion5TypeSafety() {
+ Union5<Integer, String, Double, Boolean, Long> union = Union5.ofT3(3.14);
+ assertFalse(union.isT1());
+ assertFalse(union.isT2());
+ assertTrue(union.isT3());
+ assertFalse(union.isT4());
+ assertFalse(union.isT5());
+ assertEquals(union.getT3(), 3.14);
+ }
+
+ @Test
+ public void testUnion6TypeSafety() {
+ Union6<Integer, String, Double, Boolean, Long, Float> union =
Union6.ofT4(true);
+ assertFalse(union.isT1());
+ assertFalse(union.isT2());
+ assertFalse(union.isT3());
+ assertTrue(union.isT4());
+ assertFalse(union.isT5());
+ assertFalse(union.isT6());
+ assertEquals(union.getT4(), true);
+ }
+
+ @Test
+ public void testUnionEquality() {
+ Union union1 = new Union(0, 42);
+ Union union2 = new Union(0, 42);
+ assertEquals(union1, union2);
+ assertEquals(union1.hashCode(), union2.hashCode());
+
+ Union2<String, Long> u2a = Union2.ofT1("hello");
+ Union2<String, Long> u2b = Union2.ofT1("hello");
+ assertEquals(u2a, u2b);
+ assertEquals(u2a.hashCode(), u2b.hashCode());
+
+ // Union2-6 extends Union, so they should be equal to Union with same
index/value
+ Union baseUnion = new Union(0, "hello");
+ assertEquals(u2a, baseUnion);
+ assertEquals(baseUnion, u2a);
+ }
+
+ @Test
+ public void testUnionToString() {
+ Union union = new Union(0, 42);
+ String str = union.toString();
+ assertTrue(str.contains("42"));
+ assertTrue(str.contains("0"));
+
+ Union2<String, Long> union2 = Union2.ofT1("hello");
+ String str2 = union2.toString();
+ assertTrue(str2.contains("hello"));
+ }
+
+ @Test
+ public void testUnionGetValueTyped() {
+ Union union = new Union(0, 42);
+ Integer value = union.getValue(Integer.class);
+ assertEquals(value, Integer.valueOf(42));
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testUnion2InvalidIndex() {
+ Union2.of(5, "test"); // Index out of bounds for Union2
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testUnion3InvalidIndex() {
+ Union3.of(5, "test"); // Index out of bounds for Union3
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testUnion4InvalidIndex() {
+ Union4.of(5, "test"); // Index out of bounds for Union4
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testUnion5InvalidIndex() {
+ Union5.of(6, "test"); // Index out of bounds for Union5
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testUnion6InvalidIndex() {
+ Union6.of(7, "test"); // Index out of bounds for Union6
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]