diff --git a/sdk/clientcore/core/checkstyle-suppressions.xml b/sdk/clientcore/core/checkstyle-suppressions.xml
index fb96f9c57d74..321d99b2c49b 100644
--- a/sdk/clientcore/core/checkstyle-suppressions.xml
+++ b/sdk/clientcore/core/checkstyle-suppressions.xml
@@ -19,6 +19,7 @@
+
diff --git a/sdk/clientcore/core/spotbugs-exclude.xml b/sdk/clientcore/core/spotbugs-exclude.xml
index 6b3dfc376cd6..2858d26aad4c 100644
--- a/sdk/clientcore/core/spotbugs-exclude.xml
+++ b/sdk/clientcore/core/spotbugs-exclude.xml
@@ -35,7 +35,9 @@
+
+
@@ -280,6 +282,10 @@
+
+
+
+
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/GenericParameterizedType.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/GenericParameterizedType.java
new file mode 100644
index 000000000000..32523a90ce31
--- /dev/null
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/GenericParameterizedType.java
@@ -0,0 +1,88 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package io.clientcore.core.implementation;
+
+import io.clientcore.core.instrumentation.logging.ClientLogger;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * A {@link ParameterizedType} implementation that allows for reference type arguments.
+ */
+public final class GenericParameterizedType implements ParameterizedType {
+ private static final ClientLogger LOGGER = new ClientLogger(GenericParameterizedType.class);
+
+ private final Class> raw;
+ private final Type[] args;
+ private String cachedToString;
+
+ /**
+ * Creates a new instance of {@link GenericParameterizedType}.
+ *
+ * @param raw The raw type.
+ * @param args The type arguments.
+ */
+ public GenericParameterizedType(Class> raw, Type... args) {
+ this.raw = raw;
+
+ if (args == null) {
+ throw LOGGER.logThrowableAsError(new IllegalArgumentException("args cannot be null"));
+ }
+
+ Type[] argsCopy = new Type[args.length];
+ for (int i = 0; i < args.length; i++) {
+ if (args[i] == null) {
+ throw LOGGER.logThrowableAsError(
+ new IllegalArgumentException("args cannot contain null: null value in index " + i));
+ }
+ argsCopy[i] = args[i];
+ }
+ this.args = argsCopy;
+ }
+
+ @Override
+ public Type[] getActualTypeArguments() {
+ return args;
+ }
+
+ @Override
+ public Type getRawType() {
+ return raw;
+ }
+
+ @Override
+ public Type getOwnerType() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ if (cachedToString == null) {
+ cachedToString = raw.getTypeName() + "<"
+ + Arrays.stream(args).map(Type::getTypeName).collect(Collectors.joining(", ")) + ">";
+ }
+ return cachedToString;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ GenericParameterizedType that = (GenericParameterizedType) o;
+ return Objects.equals(raw, that.raw) && Objects.deepEquals(args, that.args);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(raw, Arrays.hashCode(args));
+ }
+}
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/util/Union.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/util/Union.java
new file mode 100644
index 000000000000..12920170d2c8
--- /dev/null
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/util/Union.java
@@ -0,0 +1,357 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package io.clientcore.core.util;
+
+import io.clientcore.core.implementation.GenericParameterizedType;
+import io.clientcore.core.instrumentation.logging.ClientLogger;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A class that represents a union of types. A type that is one of a finite set of sum types.
+ * This class is used to represent a union of types in a type-safe manner.
+ *
+ *
Create an instance
+ *
+ *
+ *
+ * Union union = Union.ofTypes(String.class, Integer.class);
+ *
+ *
+ *
+ *
Create an instance from primitives
+ *
+ *
+ *
+ * Union unionPrimitives = Union.ofTypes(int.class, double.class);
+ *
+ *
+ *
+ *
Create an instance from collections
+ *
+ *
+ *
+ * // GenericParameterizedType is a non-public helper class that allows us to specify a generic type with
+ * // a class and a type. User can define any similar class to achieve the same functionality.
+ * Union unionCollections = Union.ofTypes(
+ * new GenericParameterizedType(List.class, String.class),
+ * new GenericParameterizedType(List.class, Integer.class));
+ *
+ *
+ *
+ *
Consume the value of the Union if it is of the expected type
+ *
+ *
+ *
+ * Union union = Union.ofTypes(String.class, Integer.class);
+ * union.setValue("Hello");
+ * Object value = union.getValue();
+ * // we can write an if-else block to consume the value in Java 8+, or switch pattern match in Java 17+
+ * if (value instanceof String) {
+ * String s = (String) value;
+ * System.out.println("String value: " + s);
+ * } else if (value instanceof Integer) {
+ * Integer i = (Integer) value;
+ * System.out.println("Integer value: " + i);
+ * } else {
+ * throw new IllegalArgumentException("Unknown type: " + union.getCurrentType().getTypeName());
+ * }
+ *
+ *
+ *
+ * or
+ *
+ *
+ *
+ * Union union = Union.ofTypes(String.class, Integer.class);
+ * union.setValue("Hello");
+ * union.tryConsume(
+ * v -> System.out.println("String value: " + v), String.class);
+ * union.tryConsume(
+ * v -> System.out.println("Integer value: " + v), Integer.class);
+ *
+ *
+ *
+ */
+public final class Union {
+ private static final ClientLogger LOGGER = new ClientLogger(Union.class);
+
+ private final List types;
+ private Object value;
+ private Type currentType;
+
+ private Union(Type... types) {
+ if (types == null || types.length == 0) {
+ throw LOGGER.logThrowableAsError(new IllegalArgumentException("types cannot be null or empty"));
+ }
+
+ ArrayList typeCopy = new ArrayList<>(types.length);
+ for (int i = 0; i < types.length; i++) {
+ final Type currentType = types[i];
+ if (currentType == null) {
+ throw LOGGER.logThrowableAsError(
+ new IllegalArgumentException("types cannot contain null values: null value in index " + i));
+ } else if (!(currentType instanceof Class> || currentType instanceof ParameterizedType)) {
+ throw LOGGER.logThrowableAsError(new IllegalArgumentException(
+ String.format("types must be of type Class or ParameterizedType: type name is %s in index %d.",
+ currentType.getTypeName(), i)));
+ }
+
+ typeCopy.add(types[i]);
+ }
+ this.types = Collections.unmodifiableList(typeCopy);
+ }
+
+ /**
+ * Creates a new instance of {@link Union} with the provided types.
+ *
+ * Currently, the types can be of type {@link Class} or {@link ParameterizedType}. If the type is a {@link Class},
+ * it represents a simple type. If the type is a {@link ParameterizedType}, it represents a generic type.
+ * For example, {@code List} would be represented as {@code new GenericParameterizedType(List.class, String.class)}.
+ *
types array contains a type that is not of type {@link Class} or {@link ParameterizedType}.
+ *
+ *
+ * @param types The types of the union.
+ * @return A new instance of {@link Union}.
+ */
+ public static Union ofTypes(Type... types) {
+ return new Union(types);
+ }
+
+ /**
+ * Sets the value of the union. A new updated immutable union is returned.
+ *
+ * @param value The value of the union.
+ * @return A new updated immutable union.
+ * @throws IllegalArgumentException If the value is not of one of the types in the union.
+ */
+ @SuppressWarnings("unchecked")
+ public Union setValue(Object value) {
+ if (value == null) {
+ this.value = null;
+ return this;
+ }
+
+ for (Type type : types) {
+ if (isInstanceOfType(value, type) || isPrimitiveTypeMatch(value, type)) {
+ this.value = value;
+ this.currentType = type;
+ return this;
+ }
+ }
+
+ throw LOGGER.logThrowableAsError(new IllegalArgumentException("Invalid type: " + value.getClass().getName()));
+ }
+
+ /**
+ * Gets the type of the value.
+ *
+ * @return The type of the value.
+ */
+ public Type getCurrentType() {
+ return currentType;
+ }
+
+ /**
+ * Gets the types of the union. The types are unmodifiable.
+ *
+ * @return The types of the union.
+ */
+ public List getSupportedTypes() {
+ return types;
+ }
+
+ /**
+ * Gets the value of the union.
+ *
+ * @return The value of the union.
+ * @param The type of the value.
+ */
+ @SuppressWarnings("unchecked")
+ public T getValue() {
+ return (T) value;
+ }
+
+ /**
+ * Gets the value of the union if it is of the expected type.
+ *
+ * @param clazz The expected type of the value.
+ * @return The value of the union.
+ * @param The expected type of the value.
+ */
+ @SuppressWarnings("unchecked")
+ public T getValue(Class clazz) {
+ if (clazz == currentType) {
+ return (T) value;
+ }
+
+ if (clazz.isInstance(value)) {
+ return clazz.cast(value);
+ }
+ if (isPrimitiveTypeMatch(value, clazz)) {
+ return (T) value;
+ }
+ throw LOGGER.logThrowableAsError(new IllegalArgumentException("Value is not of type: " + clazz.getName()));
+ }
+
+ /**
+ * Gets the value of the union if it is of the expected type.
+ *
+ * @param clazz The expected type of the value.
+ * @param genericTypes The generic types of the expected type.
+ *
+ * @return The value of the union.
+ * @param The expected type of the value.
+ */
+ public T getValue(Class clazz, Class>... genericTypes) {
+ return getValue(new GenericParameterizedType(clazz, genericTypes));
+ }
+
+ /**
+ * Gets the value of the union if it is of the expected type.
+ *
+ * @param type The expected type of the value.
+ *
+ * @return The value of the union.
+ * @param The expected type of the value.
+ */
+ @SuppressWarnings("unchecked")
+ public T getValue(Type type) {
+ if (type == currentType) {
+ return (T) value;
+ }
+
+ if (isInstanceOfType(value, type)) {
+ return (T) value;
+ }
+ throw LOGGER.logThrowableAsError(new IllegalArgumentException("Value is not of type: " + type.getTypeName()));
+ }
+
+ /**
+ * This method is used to consume the value of the Union if it is of the expected type.
+ *
+ * @param consumer A consumer that will consume the value of the Union if it is of the expected type.
+ * @param clazz The expected type of the value.
+ * @return Returns true if the value was consumable by the consumer, and false if it was not.
+ * @param The value type expected by the consumer.
+ */
+ @SuppressWarnings("unchecked")
+ public boolean tryConsume(Consumer consumer, Class clazz) {
+ if (clazz == currentType) {
+ consumer.accept((T) value);
+ return true;
+ }
+
+ if (isInstanceOfType(value, clazz)) {
+ consumer.accept(clazz.cast(value));
+ return true;
+ }
+
+ if (isPrimitiveTypeMatch(value, clazz)) {
+ consumer.accept((T) value);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This method is used to consume the value of the Union if it is of the expected type.
+ *
+ * @param consumer A consumer that will consume the value of the Union if it is of the expected type.
+ * @param clazz The expected type of the value.
+ * @param genericTypes A var-args representation of generic types that are expected by the consumer, for example,
+ * List<String> would be represented as
List.class, String.class
.
+ * @return Returns true if the value was consumable by the consumer, and false if it was not.
+ * @param The value type expected by the consumer.
+ */
+ public boolean tryConsume(Consumer consumer, Class clazz, Class>... genericTypes) {
+ return tryConsume(consumer, new GenericParameterizedType(clazz, genericTypes));
+ }
+
+ /**
+ * This method is used to consume the value of the Union if it is of the expected type.
+ *
+ * @param consumer A consumer that will consume the value of the Union if it is of the expected type.
+ * @param type The expected type of the value.
+ * @return Returns true if the value was consumable by the consumer, and false if it was not.
+ * @param The value type expected by the consumer.
+ */
+ @SuppressWarnings("unchecked")
+ public boolean tryConsume(Consumer consumer, ParameterizedType type) {
+ if (type == currentType) {
+ consumer.accept((T) value);
+ return true;
+ }
+
+ if (isInstanceOfType(value, type)) {
+ consumer.accept((T) value);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return value == null
+ ? "Union{types=" + types + ", value=null" + "}"
+ : "Union{types=" + types + ", type=" + (currentType == null ? null : currentType.getTypeName()) + ", value="
+ + value + "}";
+ }
+
+ private boolean isInstanceOfType(Object value, Type type) {
+ if (value == null) {
+ return false;
+ }
+
+ if (type instanceof ParameterizedType) {
+ ParameterizedType pType = (ParameterizedType) type;
+ if (pType.getRawType() instanceof Class> && ((Class>) pType.getRawType()).isInstance(value)) {
+ Type[] actualTypeArguments = pType.getActualTypeArguments();
+ if (value instanceof Collection>) {
+ Collection> collection = (Collection>) value;
+ return collection.stream()
+ .allMatch(element -> element != null
+ && Arrays.stream(actualTypeArguments).anyMatch(arg -> isInstanceOfType(element, arg)));
+ }
+ }
+ } else if (type instanceof Class>) {
+ return ((Class>) type).isInstance(value);
+ }
+ return false;
+ }
+
+ private boolean isPrimitiveTypeMatch(Object value, Type type) {
+ if (type instanceof Class>) {
+ Class> clazz = (Class>) type;
+ if (clazz.isPrimitive()) {
+ if ((clazz == int.class && value instanceof Integer)
+ || (clazz == long.class && value instanceof Long)
+ || (clazz == float.class && value instanceof Float)
+ || (clazz == double.class && value instanceof Double)
+ || (clazz == boolean.class && value instanceof Boolean)
+ || (clazz == byte.class && value instanceof Byte)
+ || (clazz == char.class && value instanceof Character)
+ || (clazz == short.class && value instanceof Short)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/BasicUnion.java b/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/BasicUnion.java
new file mode 100644
index 000000000000..2567886c301b
--- /dev/null
+++ b/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/BasicUnion.java
@@ -0,0 +1,60 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package io.clientcore.core.util.union;
+
+import io.clientcore.core.util.Union;
+
+// This is a simple example of how to use the Union type. It allows for multiple types to be stored in a single
+// property, and provides methods to consume the value based on its type.
+public class BasicUnion {
+ public static void main(String[] args) {
+ Union union = Union.ofTypes(String.class, Integer.class, Double.class);
+
+ // This union allows for String, Integer, and Double types
+ union.setValue("Hello");
+
+ // we can (attempt to) exhaustively consume the union using a switch statement...
+ handleUnion(union);
+
+ // ... or we can pass in lambda expressions to consume the union for the types we care about
+ union.tryConsume(v -> System.out.println("String value from lambda: " + v), String.class);
+
+ // ... or we can just get the value to the type we expect it to be using the getValue methods
+ String value = union.getValue();
+ System.out.println("Value (from getValue()): " + value);
+
+ value = union.getValue(String.class);
+ System.out.println("Value (from getValue(Class> cls)): " + value);
+
+ // Of course, this union supports Integer and Double types as well:
+ union.setValue(123);
+ handleUnion(union);
+ union.setValue(3.14);
+ handleUnion(union);
+
+ // This will throw an IllegalArgumentException, as the union does not support the type Long
+ try {
+ union.setValue(123L);
+ } catch (IllegalArgumentException e) {
+ System.out.println("Caught exception: " + e.getMessage());
+ }
+ }
+
+ private static void handleUnion(Union union) {
+ // we can write an if-else block to consume the value in Java 8+, or switch pattern match in Java 17+
+ Object value = union.getValue();
+ if (value instanceof String) {
+ String s = (String) value;
+ System.out.println("String value from if-else: " + s);
+ } else if (value instanceof Integer) {
+ Integer i = (Integer) value;
+ System.out.println("Integer value from if-else: " + i);
+ } else if (value instanceof Double) {
+ Double d = (Double) value;
+ System.out.println("Double value from if-else: " + d);
+ } else {
+ throw new IllegalArgumentException("Unknown type: " + union.getCurrentType().getTypeName());
+ }
+ }
+}
diff --git a/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/GenericModelType.java b/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/GenericModelType.java
new file mode 100644
index 000000000000..a73a7569757f
--- /dev/null
+++ b/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/GenericModelType.java
@@ -0,0 +1,83 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package io.clientcore.core.util.union;
+
+import io.clientcore.core.implementation.GenericParameterizedType;
+import io.clientcore.core.util.Union;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.List;
+
+// This is an example of a Model class that uses the Union type to allow for multiple types to be stored in a single
+// property, with the additional complexity that the types are all generic types (in this case, List, List,
+// and List).
+public class GenericModelType {
+ // We specify that the Union type can be one of three types: List, List, or List.
+ private Union prop = Union.ofTypes(
+ // GenericParameterizedType is a non-public helper class that allows us to specify a generic type with
+ // a class and a type. User can define any similar class to achieve the same functionality.
+ new GenericParameterizedType(List.class, String.class),
+ new GenericParameterizedType(List.class, Integer.class),
+ new GenericParameterizedType(List.class, Float.class));
+
+ // we give access to the Union type, so that the value can be modified and retrieved.
+ public Union getProp() {
+ return prop;
+ }
+
+ // but our setter methods need to have more complex names, to differentiate them at runtime.
+ public GenericModelType setPropAsStrings(List strValues) {
+ prop.setValue(strValues);
+ return this;
+ }
+ public GenericModelType setPropAsIntegers(List intValues) {
+ prop.setValue(intValues);
+ return this;
+ }
+ public GenericModelType setPropAsFloats(List floatValues) {
+ prop.setValue(floatValues);
+ return this;
+ }
+
+ public static void main(String[] args) {
+ GenericModelType model = new GenericModelType();
+ model.setPropAsStrings(Arrays.asList("Hello", "World"));
+
+ // in this case, it isn't possible to switch over the values easily (as we could in the ModelType class), as the
+ // types are all List types (and we would need to inspect the values inside the list to be sure). Instead, we
+ // can use the tryConsume method to consume the value if it is of the expected type.
+ List types = model.getProp().getSupportedTypes();
+ for (Type type : types) {
+ if (type instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+ if (parameterizedType.getRawType() == List.class) {
+ Type actualType = parameterizedType.getActualTypeArguments()[0];
+ if (actualType == String.class) {
+ model.getProp().tryConsume(strings -> System.out.println("Strings: " + strings), List.class, String.class);
+ break;
+ } else if (actualType == Integer.class) {
+ model.getProp().tryConsume(integers -> System.out.println("Integers: " + integers), List.class, Integer.class);
+ break;
+ } else if (actualType == Float.class) {
+ model.getProp().tryConsume(floats -> System.out.println("Floats: " + floats), List.class, Float.class);
+ break;
+ }
+ }
+ }
+ }
+
+ // or, we can use the returned boolean value to determine if the value was consumed
+ if (model.getProp().tryConsume(integers -> System.out.println("Integers: " + integers), List.class, Integer.class)) {
+ System.out.println("Consumed as Integers");
+ } else if (model.getProp().tryConsume(strings -> System.out.println("Strings: " + strings), List.class, String.class)) {
+ System.out.println("consumed as Strings");
+ } else if (model.getProp().tryConsume(floats -> System.out.println("Floats: " + floats), List.class, Float.class)) {
+ System.out.println("consumed as Floats");
+ } else {
+ System.out.println("Not consumed as Integers, Strings, Floats");
+ }
+ }
+}
diff --git a/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/ModelType.java b/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/ModelType.java
new file mode 100644
index 000000000000..ec4302e89537
--- /dev/null
+++ b/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/ModelType.java
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package io.clientcore.core.util.union;
+
+import io.clientcore.core.util.Union;
+
+// This is an example of a Model class that uses the Union type to allow for multiple types to be stored in a single
+// property. This is useful when you have a property that can be one of a few types, but you want to ensure that the
+// types are known at compile time, and that you can easily switch on the type of the value.
+public class ModelType {
+ private Union prop = Union.ofTypes(String.class, Integer.class, Double.class);
+
+ public Union getProp() {
+ return prop;
+ }
+
+ // In this case, because all three values of the Union type are distinct, we can have three setter methods to
+ // modify the Union in a type-safe way. If the types were not distinct, we would need to use a single setter method
+ // that took an Object type, and then rely on the Union type to ensure that the value was of the correct type.
+ // This would be the case (as we see in GenericModelType) where there are multiple types of the same class, such as
+ // List, List, and List.
+ public ModelType setProp(String str) {
+ prop.setValue(str);
+ return this;
+ }
+ public ModelType setProp(Integer integer) {
+ prop.setValue(integer);
+ return this;
+ }
+ public ModelType setProp(Double dbl) {
+ prop.setValue(dbl);
+ return this;
+ }
+
+ public static void main(String[] args) {
+ ModelType modelType = new ModelType();
+ modelType.setProp(23);
+
+ // we can just call the getValue(Class cls) method to get the value as the type we expect it to be
+ System.out.println(modelType.getProp().getValue(Integer.class));
+
+ // or we can use the tryConsume method
+ modelType.getProp().tryConsume(v -> System.out.println("Value from lambda: " + v), Integer.class);
+
+ // or we can write an if-else block to consume the value in Java 8+, or switch pattern match in Java 17+
+ Object value = modelType.getProp().getValue();
+ if (value instanceof String) {
+ String s = (String) value;
+ System.out.println("String value from if-else: " + s);
+ } else if (value instanceof Integer) {
+ Integer i = (Integer) value;
+ System.out.println("Integer value from if-else: " + i);
+ } else if (value instanceof Double) {
+ Double d = (Double) value;
+ System.out.println("Double value from if-else: " + d);
+ } else {
+ throw new IllegalArgumentException("Unknown type: " + modelType.getProp().getCurrentType().getTypeName());
+ }
+ }
+}
diff --git a/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/NestedUnion.java b/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/NestedUnion.java
new file mode 100644
index 000000000000..9380e842b07d
--- /dev/null
+++ b/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/NestedUnion.java
@@ -0,0 +1,65 @@
+package io.clientcore.core.util.union;
+
+import io.clientcore.core.implementation.GenericParameterizedType;
+import io.clientcore.core.util.Union;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This is an example of a model class A that uses the Union type to allow for nested union type to be stored in a single
+ * property.
+ */
+public class NestedUnion {
+ public static void main(String[] args) {
+ NestedClassB nestedClassB = new NestedClassB();
+ nestedClassB.setProp(Arrays.asList(1, 2, 3));
+ System.out.println("Current Type of Nested Class B: " + nestedClassB.getProp().getCurrentType());
+ System.out.println("Value from Nested Class B: " +
+ nestedClassB.getProp().getValue(new GenericParameterizedType(List.class, Integer.class)));
+
+ ClassA outerClassA = new ClassA();
+ outerClassA.setProp(nestedClassB);
+ NestedClassB nestedClassBFromA = outerClassA.getProp().getValue(NestedClassB.class);
+ System.out.println("Current Type of Nested Class B from Class A: " + nestedClassBFromA.getProp().getCurrentType());
+ System.out.println("Value of Nested Class B from Class A: " +
+ nestedClassBFromA.getProp().getValue(new GenericParameterizedType(List.class, Integer.class)));
+ }
+
+ private static class ClassA {
+ Union prop = Union.ofTypes(String.class, NestedClassB.class);
+
+ public Union getProp() {
+ return prop;
+ }
+
+ public ClassA setProp(String str) {
+ prop.setValue(str);
+ return this;
+ }
+
+ // Nested Class B contains a Union type property.
+ public ClassA setProp(NestedClassB b) {
+ prop.setValue(b);
+ return this;
+ }
+ }
+
+ private static class NestedClassB {
+ Union prop = Union.ofTypes(String.class, new GenericParameterizedType(List.class, Integer.class));
+
+ public Union getProp() {
+ return prop;
+ }
+
+ public NestedClassB setProp(String str) {
+ prop.setValue(str);
+ return this;
+ }
+
+ public NestedClassB setProp(List intList) {
+ prop.setValue(intList);
+ return this;
+ }
+ }
+}
diff --git a/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/PrimitiveUnionType.java b/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/PrimitiveUnionType.java
new file mode 100644
index 000000000000..ac1d88a5251d
--- /dev/null
+++ b/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/PrimitiveUnionType.java
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package io.clientcore.core.util.union;
+
+
+import io.clientcore.core.util.Union;
+
+// This is an example of a Model class that uses the Union type to allow for multiple types to be stored in a single
+// property. This is useful when you have a property that can be one of a few types, but you want to ensure that the
+// types are known at compile time, and that you can easily switch on the type of the value.
+public class PrimitiveUnionType {
+ private Union prop = Union.ofTypes(int.class, float.class, double.class);
+
+ public Union getProp() {
+ return prop;
+ }
+
+ // In this case, because all three values of the Union type are distinct, we can have three setter methods to
+ // modify the Union in a type-safe way. If the types were not distinct, we would need to use a single setter method
+ // that took an Object type, and then rely on the Union type to ensure that the value was of the correct type.
+ // This would be the case (as we see in GenericModelType) where there are multiple types of the same class, such as
+ // List, List, and List.
+ public PrimitiveUnionType setProp(int i) {
+ prop.setValue(i);
+ return this;
+ }
+ public PrimitiveUnionType setProp(float f) {
+ prop.setValue(f);
+ return this;
+ }
+ public PrimitiveUnionType setProp(double d) {
+ prop.setValue(d);
+ return this;
+ }
+
+ public static void main(String[] args) {
+ PrimitiveUnionType modelType = new PrimitiveUnionType();
+ modelType.setProp(23);
+ System.out.println(modelType.getProp().getCurrentType());
+
+ // we can just call the getValue(Class cls) method to get the value as the type we expect it to be
+ System.out.println(modelType.getProp().getValue(int.class));
+
+ // or we can use the tryConsume method
+ modelType.getProp().tryConsume(v -> System.out.println("Value from lambda: " + v), int.class);
+
+ // or we can write an if-else block to consume the value in Java 8+, or switch pattern match in Java 17+
+ // but the switch expression doesn't work directly - we need to rely on the autoboxing to save us (Integer works, int doesn't)
+ Object value = modelType.getProp().getValue();
+ if (value instanceof String) {
+ String s = (String) value;
+ System.out.println("String value from if-else: " + s);
+ } else if (value instanceof Integer) {
+ Integer i = (Integer) value;
+ System.out.println("Integer value from if-else: " + i);
+ } else if (value instanceof Double) {
+ Double d = (Double) value;
+ System.out.println("Double value from if-else: " + d);
+ } else {
+ throw new IllegalArgumentException("Unknown type: " + modelType.getProp().getCurrentType().getTypeName());
+ }
+ }
+}
diff --git a/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/codesnippets/UnionJavaDocCodeSnippets.java b/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/codesnippets/UnionJavaDocCodeSnippets.java
new file mode 100644
index 000000000000..3f00d5ac6dba
--- /dev/null
+++ b/sdk/clientcore/core/src/samples/java/io/clientcore/core/util/union/codesnippets/UnionJavaDocCodeSnippets.java
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package io.clientcore.core.util.union.codesnippets;
+
+import io.clientcore.core.implementation.GenericParameterizedType;
+import io.clientcore.core.util.Union;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class UnionJavaDocCodeSnippets {
+
+ @Test
+ public void unionCreation() {
+ // BEGIN: io.clientcore.core.util.union.UnionJavaDocCodeSnippetsBasic
+ Union union = Union.ofTypes(String.class, Integer.class);
+ // END: io.clientcore.core.util.union.UnionJavaDocCodeSnippetsBasic
+
+ // BEGIN: io.clientcore.core.util.union.UnionJavaDocCodeSnippetsPrimitiveType
+ Union unionPrimitives = Union.ofTypes(int.class, double.class);
+ // END: io.clientcore.core.util.union.UnionJavaDocCodeSnippetsPrimitiveType
+
+ // BEGIN: io.clientcore.core.util.union.UnionJavaDocCodeSnippetsCollectionType
+ // GenericParameterizedType is a non-public helper class that allows us to specify a generic type with
+ // a class and a type. User can define any similar class to achieve the same functionality.
+ Union unionCollections = Union.ofTypes(
+ new GenericParameterizedType(List.class, String.class),
+ new GenericParameterizedType(List.class, Integer.class));
+ // END: io.clientcore.core.util.union.UnionJavaDocCodeSnippetsCollectionType
+ }
+
+ @Test
+ public void unionConsumeIfElseStatement() {
+ // BEGIN: io.clientcore.core.util.union.UnionJavaDocCodeSnippetsIfElseStatement
+ Union union = Union.ofTypes(String.class, Integer.class);
+ union.setValue("Hello");
+ Object value = union.getValue();
+ // we can write an if-else block to consume the value in Java 8+, or switch pattern match in Java 17+
+ if (value instanceof String) {
+ String s = (String) value;
+ System.out.println("String value: " + s);
+ } else if (value instanceof Integer) {
+ Integer i = (Integer) value;
+ System.out.println("Integer value: " + i);
+ } else {
+ throw new IllegalArgumentException("Unknown type: " + union.getCurrentType().getTypeName());
+ }
+ // END: io.clientcore.core.util.union.UnionJavaDocCodeSnippetsIfElseStatement
+ }
+
+ @Test
+ public void unionConsumeLambda() {
+ // BEGIN: io.clientcore.core.util.union.UnionJavaDocCodeSnippetsLambda
+ Union union = Union.ofTypes(String.class, Integer.class);
+ union.setValue("Hello");
+ union.tryConsume(
+ v -> System.out.println("String value: " + v), String.class);
+ union.tryConsume(
+ v -> System.out.println("Integer value: " + v), Integer.class);
+ // END: io.clientcore.core.util.union.UnionJavaDocCodeSnippetsLambda
+ }
+
+}
diff --git a/sdk/clientcore/core/src/test/java/io/clientcore/core/util/UnionTests.java b/sdk/clientcore/core/src/test/java/io/clientcore/core/util/UnionTests.java
new file mode 100644
index 000000000000..99e02e0f65c5
--- /dev/null
+++ b/sdk/clientcore/core/src/test/java/io/clientcore/core/util/UnionTests.java
@@ -0,0 +1,436 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package io.clientcore.core.util;
+
+import io.clientcore.core.implementation.GenericParameterizedType;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Test class for {@link Union}.
+ */
+public class UnionTests {
+ private static final String STRING_VALUE = "Hello, world!";
+ private static final int INT_VALUE = 42;
+ private static final long LONG_VALUE = 42L;
+ private static final double DOUBLE_VALUE = 3.11d;
+ private static final float FLOAT_VALUE = 3.11f;
+
+ private static final String[] STRING_ARRAY_VALUE = { "Hello", "world", "!" };
+ private static final int[] INT_ARRAY_VALUE = { 1, 2, 3 };
+ private static final long[] LONG_ARRAY_VALUE = { 1L, 2L, 3L };
+ private static final float[] FLOAT_ARRAY_VALUE = { 1.1f, 2.2f, 3.3f };
+ private static final double[] DOUBLE_ARRAY_VALUE = { 1.1d, 2.2d, 3.3d };
+
+ private static final GenericParameterizedType LIST_OF_STRING_TYPE
+ = new GenericParameterizedType(List.class, String.class);
+ private static final GenericParameterizedType LIST_OF_INTEGER_TYPE
+ = new GenericParameterizedType(List.class, Integer.class);
+ private static final GenericParameterizedType LIST_OF_LONG_TYPE
+ = new GenericParameterizedType(List.class, Long.class);
+ private static final GenericParameterizedType LIST_OF_FLOAT_TYPE
+ = new GenericParameterizedType(List.class, Float.class);
+ private static final GenericParameterizedType LIST_OF_DOUBLE_TYPE
+ = new GenericParameterizedType(List.class, Double.class);
+ private static final GenericParameterizedType SET_OF_STRING_TYPE
+ = new GenericParameterizedType(Set.class, String.class);
+
+ private static final List LIST_OF_STRING_VALUE = Arrays.asList("Hello", "world", "!");
+ private static final List LIST_OF_INTEGER_VALUE = Arrays.asList(1, 2, 3);
+ private static final List LIST_OF_LONG_VALUE = Arrays.asList(1L, 2L, 3L);
+ private static final List LIST_OF_FLOAT_VALUE = Arrays.asList(1.1f, 2.2f, 3.3f);
+ private static final List LIST_OF_DOUBLE_VALUE = Arrays.asList(1.1d, 2.2d, 3.3d);
+
+ private static final Set SET_OF_STRING_VALUE
+ = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("Hello", "world", "!")));
+
+ @Test
+ void createUnionWithMultipleTypes() {
+ Union union = Union.ofTypes(String.class, Integer.class, Double.class);
+ assertNotNull(union);
+ assertEquals(3, union.getSupportedTypes().size());
+ }
+
+ @Test
+ void setAndGetValue() {
+ Union union = Union.ofTypes(String.class, Integer.class, Double.class);
+
+ union.setValue(STRING_VALUE);
+ assertEquals(String.class, union.getCurrentType());
+ assertEquals(STRING_VALUE, union.getValue());
+
+ union.setValue(INT_VALUE);
+ assertEquals(Integer.class, union.getCurrentType());
+ assertEquals(INT_VALUE, union.getValue(Integer.class));
+
+ union.setValue(DOUBLE_VALUE);
+ assertEquals(Double.class, union.getCurrentType());
+ assertEquals(DOUBLE_VALUE, union.getValue(Double.class));
+ }
+
+ @Test
+ void setValueWithInvalidType() {
+ Union union = Union.ofTypes(String.class, Integer.class, Double.class);
+ assertThrows(IllegalArgumentException.class, () -> union.setValue(LONG_VALUE));
+ assertThrows(IllegalArgumentException.class, () -> union.setValue(FLOAT_VALUE));
+ }
+
+ @Test
+ void tryConsumeWithValidType() {
+ Union union = Union.ofTypes(String.class, Integer.class, Double.class);
+ union.setValue(STRING_VALUE);
+ assertTrue(union.tryConsume(value -> assertEquals(STRING_VALUE, value), String.class));
+ union.setValue(INT_VALUE);
+ assertTrue(union.tryConsume(value -> assertEquals(INT_VALUE, value), Integer.class));
+ union.setValue(DOUBLE_VALUE);
+ assertTrue(union.tryConsume(value -> assertEquals(DOUBLE_VALUE, value), Double.class));
+ }
+
+ @Test
+ void tryConsumeWithInvalidType() {
+ Union union = Union.ofTypes(String.class, Integer.class, Double.class);
+
+ union.setValue(INT_VALUE);
+ assertTrue(union.tryConsume(value -> assertEquals(INT_VALUE, value), Integer.class));
+ assertFalse(union.tryConsume(value -> fail("Should not consume String"), String.class));
+ assertFalse(union.tryConsume(value -> fail("Should not consume Double"), Double.class));
+ assertFalse(union.tryConsume(value -> fail("Should not consume Long"), Long.class));
+ assertFalse(union.tryConsume(value -> fail("Should not consume Float"), Float.class));
+ }
+
+ // Autoboxing tests
+
+ @Test
+ void createUnionWithMultipleTypesAutoboxing() {
+ Union union = Union.ofTypes(String.class, int.class, double.class);
+ union.setValue(STRING_VALUE);
+ assertEquals(String.class, union.getCurrentType());
+ assertEquals(STRING_VALUE, union.getValue());
+
+ union.setValue(42);
+ assertEquals(int.class, union.getCurrentType());
+ assertEquals(42, union.getValue(int.class));
+
+ union.setValue(DOUBLE_VALUE);
+ assertEquals(double.class, union.getCurrentType());
+ assertEquals(DOUBLE_VALUE, union.getValue(double.class));
+ }
+
+ @Test
+ void setAndGetValueAutoboxing() {
+ Union union = Union.ofTypes(String.class, double.class, int.class);
+ union.setValue(STRING_VALUE);
+ assertEquals(String.class, union.getCurrentType());
+ assertEquals(STRING_VALUE, union.getValue(String.class));
+
+ union.setValue(INT_VALUE);
+ assertEquals(int.class, union.getCurrentType());
+ assertEquals(INT_VALUE, union.getValue(int.class));
+
+ union.setValue(DOUBLE_VALUE);
+ assertEquals(double.class, union.getCurrentType());
+ assertEquals(DOUBLE_VALUE, union.getValue(double.class));
+ }
+
+ @Test
+ void setValueWithInvalidTypeAutoboxing() {
+ Union union = Union.ofTypes(String.class, int.class, double.class);
+ assertThrows(IllegalArgumentException.class, () -> union.setValue(FLOAT_VALUE));
+ }
+
+ @Test
+ void tryConsumeWithValidTypeAutoboxing() {
+ Union union = Union.ofTypes(String.class, int.class, double.class);
+
+ union.setValue(STRING_VALUE);
+ assertEquals(String.class, union.getCurrentType());
+ assertTrue(union.tryConsume(value -> assertEquals(STRING_VALUE, value), String.class));
+
+ union.setValue(INT_VALUE);
+ assertEquals(int.class, union.getCurrentType());
+ assertTrue(union.tryConsume(value -> assertEquals(INT_VALUE, value), int.class));
+
+ union.setValue(DOUBLE_VALUE);
+ assertEquals(double.class, union.getCurrentType());
+ assertTrue(union.tryConsume(value -> assertEquals(DOUBLE_VALUE, value), double.class));
+ }
+
+ @Test
+ void tryConsumeWithInvalidTypeAutoboxing() {
+ Union union = Union.ofTypes(String.class, int.class, double.class);
+ union.setValue(INT_VALUE);
+ assertTrue(union.tryConsume(value -> assertEquals(INT_VALUE, value), int.class));
+ assertFalse(union.tryConsume(value -> fail("Should not consume String"), String.class));
+ assertFalse(union.tryConsume(value -> fail("Should not consume Double"), Double.class));
+ assertFalse(union.tryConsume(value -> fail("Should not consume Long"), Long.class));
+ assertFalse(union.tryConsume(value -> fail("Should not consume Float"), Float.class));
+ }
+
+ // Array types tests
+
+ @Test
+ void createUnionWithArrayTypes() {
+ Union union = Union.ofTypes(String[].class, int[].class, float[].class);
+ assertNotNull(union);
+ assertEquals(3, union.getSupportedTypes().size());
+ }
+
+ @Test
+ void setAndGetValueWithArrayTypes() {
+ Union union = Union.ofTypes(String[].class, int[].class, float[].class);
+
+ union.setValue(STRING_ARRAY_VALUE);
+ assertEquals(String[].class, union.getCurrentType());
+ assertArrayEquals(STRING_ARRAY_VALUE, union.getValue(String[].class));
+
+ union.setValue(INT_ARRAY_VALUE);
+ assertEquals(int[].class, union.getCurrentType());
+ assertArrayEquals(INT_ARRAY_VALUE, union.getValue(int[].class));
+
+ union.setValue(FLOAT_ARRAY_VALUE);
+ assertEquals(float[].class, union.getCurrentType());
+ assertArrayEquals(FLOAT_ARRAY_VALUE, union.getValue(float[].class));
+ }
+
+ @Test
+ void setValueWithInvalidArrayType() {
+ Union union = Union.ofTypes(String[].class, int[].class, float[].class);
+ assertThrows(IllegalArgumentException.class, () -> union.setValue(LONG_ARRAY_VALUE));
+ assertThrows(IllegalArgumentException.class, () -> union.setValue(DOUBLE_ARRAY_VALUE));
+ }
+
+ @Test
+ void tryConsumeWithValidArrayType() {
+ Union union = Union.ofTypes(String[].class, int[].class, float[].class);
+
+ union.setValue(STRING_ARRAY_VALUE);
+ assertTrue(union.tryConsume(value -> assertArrayEquals(STRING_ARRAY_VALUE, value), String[].class));
+
+ union.setValue(INT_ARRAY_VALUE);
+ assertTrue(union.tryConsume(value -> assertArrayEquals(INT_ARRAY_VALUE, value), int[].class));
+
+ union.setValue(FLOAT_ARRAY_VALUE);
+ assertTrue(union.tryConsume(value -> assertArrayEquals(FLOAT_ARRAY_VALUE, value), float[].class));
+ }
+
+ @Test
+ void tryConsumeWithInvalidArrayType() {
+ Union union = Union.ofTypes(String[].class, int[].class, float[].class);
+ union.setValue(INT_ARRAY_VALUE);
+
+ assertTrue(union.tryConsume(value -> assertArrayEquals(INT_ARRAY_VALUE, value), int[].class));
+ assertFalse(union.tryConsume(value -> fail("Should not consume Double"), double[].class));
+ assertFalse(union.tryConsume(value -> fail("Should not consume Long"), long[].class));
+ }
+
+ // Parameterized types tests
+
+ @Test
+ void createUnionWithParameterizedTypes() {
+ Union union = Union.ofTypes(LIST_OF_STRING_TYPE, LIST_OF_INTEGER_TYPE, LIST_OF_DOUBLE_TYPE);
+ assertNotNull(union);
+ assertEquals(3, union.getSupportedTypes().size());
+ }
+
+ @Test
+ void setAndGetValueWithParameterizedTypes() {
+ Union union = Union.ofTypes(LIST_OF_STRING_TYPE, LIST_OF_INTEGER_TYPE, LIST_OF_DOUBLE_TYPE);
+
+ union.setValue(LIST_OF_STRING_VALUE);
+ assertEquals(LIST_OF_STRING_TYPE, union.getCurrentType());
+ assertEquals(LIST_OF_STRING_VALUE, union.getValue(LIST_OF_STRING_TYPE));
+
+ union.setValue(LIST_OF_INTEGER_VALUE);
+ assertEquals(LIST_OF_INTEGER_TYPE, union.getCurrentType());
+ assertEquals(LIST_OF_INTEGER_VALUE, union.getValue(LIST_OF_INTEGER_TYPE));
+
+ union.setValue(LIST_OF_DOUBLE_VALUE);
+ assertEquals(LIST_OF_DOUBLE_TYPE, union.getCurrentType());
+ assertEquals(LIST_OF_DOUBLE_VALUE, union.getValue(LIST_OF_DOUBLE_TYPE));
+ }
+
+ @Test
+ void setValueWithInvalidParameterizedType() {
+ Union union = Union.ofTypes(LIST_OF_STRING_TYPE, LIST_OF_INTEGER_TYPE, LIST_OF_DOUBLE_TYPE);
+
+ assertThrows(IllegalArgumentException.class, () -> union.setValue(LIST_OF_LONG_VALUE));
+ assertThrows(IllegalArgumentException.class, () -> union.setValue(LIST_OF_FLOAT_VALUE));
+ assertThrows(IllegalArgumentException.class, () -> union.setValue(SET_OF_STRING_VALUE));
+ }
+
+ @Test
+ void tryConsumeWithValidParameterizedType() {
+ Union union = Union.ofTypes(LIST_OF_STRING_TYPE, LIST_OF_INTEGER_TYPE, LIST_OF_DOUBLE_TYPE);
+
+ union.setValue(LIST_OF_STRING_VALUE);
+ assertTrue(union.tryConsume(value -> assertEquals(LIST_OF_STRING_VALUE, value), LIST_OF_STRING_TYPE));
+
+ union.setValue(LIST_OF_INTEGER_VALUE);
+ assertTrue(union.tryConsume(value -> assertEquals(LIST_OF_INTEGER_VALUE, value), LIST_OF_INTEGER_TYPE));
+
+ union.setValue(LIST_OF_DOUBLE_VALUE);
+ assertTrue(union.tryConsume(value -> assertEquals(LIST_OF_DOUBLE_VALUE, value), LIST_OF_DOUBLE_TYPE));
+ }
+
+ @Test
+ void tryConsumeWithInvalidParameterizedType() {
+ Union union = Union.ofTypes(LIST_OF_STRING_TYPE, LIST_OF_INTEGER_TYPE, LIST_OF_DOUBLE_TYPE, LIST_OF_FLOAT_TYPE);
+
+ union.setValue(LIST_OF_INTEGER_VALUE);
+ assertTrue(union.tryConsume(value -> assertEquals(LIST_OF_INTEGER_VALUE, value), LIST_OF_INTEGER_TYPE));
+ assertFalse(union.tryConsume(value -> fail("Should not consume List"), LIST_OF_STRING_TYPE));
+ assertFalse(union.tryConsume(value -> fail("Should not consume List"), LIST_OF_LONG_TYPE));
+ assertFalse(union.tryConsume(value -> fail("Should not consume List"), LIST_OF_FLOAT_TYPE));
+ assertFalse(union.tryConsume(value -> fail("Should not consume List"), LIST_OF_DOUBLE_TYPE));
+ assertFalse(union.tryConsume(value -> fail("Should not consume Set"), SET_OF_STRING_TYPE));
+ }
+
+ // Additional tests
+ @Test
+ void createUnionWithNullTypes() {
+ assertThrows(IllegalArgumentException.class, () -> Union.ofTypes((Type) null));
+ assertThrows(IllegalArgumentException.class, () -> Union.ofTypes(String.class, null, int.class));
+ }
+
+ @Test
+ void setAndGetValueWithNull() {
+ Union union = Union.ofTypes(String.class, Integer.class, Double.class);
+ union.setValue(null);
+ assertNull(union.getValue());
+ }
+
+ @Test
+ void setAndGetValueWithEmptyCollection() {
+ Union union = Union.ofTypes(LIST_OF_STRING_TYPE);
+ List emptyList = Arrays.asList();
+ union.setValue(emptyList);
+ assertEquals(LIST_OF_STRING_TYPE, union.getCurrentType());
+ assertEquals(emptyList, union.getValue(LIST_OF_STRING_TYPE));
+ }
+
+ @Test
+ void setValueWithMixedTypeCollection() {
+ Union union = Union.ofTypes(LIST_OF_STRING_TYPE);
+
+ List