This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch gvalue
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/gvalue by this push:
new 682cb4b050 wip - refactoring, tests, comments
682cb4b050 is described below
commit 682cb4b0500a25bedac233fa33303daedb15b74c
Author: Stephen Mallette <[email protected]>
AuthorDate: Wed Aug 28 11:29:18 2024 -0400
wip - refactoring, tests, comments
---
.../tinkerpop/gremlin/jsr223/JavaTranslator.java | 5 +
.../language/grammar/TraversalMethodVisitor.java | 2 +-
.../tinkerpop/gremlin/process/traversal/P.java | 8 +-
.../gremlin/process/traversal/step/GValue.java | 73 +++++-------
.../process/traversal/step/filter/CoinStep.java | 4 -
.../process/traversal/step/map/GraphStep.java | 4 +-
.../process/traversal/step/map/VertexStep.java | 2 +-
.../traversal/step/sideEffect/InjectStep.java | 2 +-
.../optimization/InlineFilterStrategy.java | 2 +-
.../tinkerpop/gremlin/util/CollectionUtil.java | 23 +++-
.../gremlin/process/traversal/step/GValueTest.java | 131 +++++++++++++++++++++
.../tinkerpop/gremlin/util/CollectionUtilTest.java | 110 +++++++++++++++++
12 files changed, 306 insertions(+), 60 deletions(-)
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
index 96a4450309..8ac0b507f3 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
@@ -338,6 +338,11 @@ public final class JavaTranslator<S extends
TraversalSource, T extends Traversal
private synchronized static void buildMethodCache(final Object delegate,
final Map<String, List<ReflectedMethod>> methodCache) {
if (methodCache.isEmpty()) {
for (final Method method : delegate.getClass().getMethods()) {
+
+ // skip GValue arguments as they make selecting the right
method ambiguous for the translator. in any
+ // event a GValue shouldn't get here since bytecode can't
carry it. also, bytecode is gone in 4.0.0
+ // so this will be deleted anyway. mostly adding this
exception so the tests can continue to pass until
+ // that removal completes.
final boolean freeOfGValue =
Arrays.stream(method.getParameters()).
noneMatch(p -> p.getType() == GValue.class ||
p.getType().getComponentType() == GValue.class);
if (freeOfGValue) {
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
index ce010e326e..4d28a0b8e9 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
@@ -765,7 +765,7 @@ public class TraversalMethodVisitor extends
TraversalRootVisitor<GraphTraversal>
// if any are GValue then they all need to be GValue to call
hasLabel
if (literalOrVar instanceof GValue ||
Arrays.stream(literalOrVars).anyMatch(lov -> lov instanceof GValue)) {
literalOrVar = GValue.of(literalOrVar);
- literalOrVars =
Arrays.stream(literalOrVars).map(GValue::of).toArray();
+ literalOrVars = GValue.ensureGValues(literalOrVars);
}
// since we normalized above to gvalue or literal we can just test
the first arg for gvalue-ness
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java
index 622a3c3e3a..a878bbb839 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java
@@ -82,13 +82,7 @@ public class P<V> implements Predicate<V>, Serializable,
Cloneable {
} else {
// this might be a bunch of GValue that need to be resolved. zomg
if (this.value instanceof List) {
- return this.biPredicate.test(testValue, (V) ((List)
this.value).stream().map(o -> {
- if (o instanceof GValue) {
- return ((GValue) o).get();
- } else {
- return o;
- }
- }).collect(Collectors.toList()));
+ return this.biPredicate.test(testValue, (V) ((List)
this.value).stream().map(GValue::valueOf).collect(Collectors.toList()));
} else {
return this.biPredicate.test(testValue, this.value);
}
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValue.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValue.java
index 0b1e88b15c..cdc5aa6972 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValue.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValue.java
@@ -57,47 +57,6 @@ public class GValue<V> implements Cloneable, Serializable {
this.value = value;
}
- /**
- * The elements in object array argument are examined to see if they are
{@link GValue} objects. If they are, they
- * are preserved as is. If they are not then they are wrapped in a {@link
GValue} object.
- */
- public static <T> GValue<T>[] convertToGValues(final Object[] args) {
- return Stream.of(args).map(id -> {
- if (id instanceof GValue)
- return (GValue<?>) id;
- else
- return of(id);
- }).toArray(GValue[]::new);
- }
-
- /**
- * Converts {@link GValue} objects arguments to their values to prevent
them from leaking to the Graph API.
- * Providers extending from this step should use this method to get actual
values to prevent any {@link GValue}
- * objects from leaking to the Graph API.
- */
- public static Object[] resolveToValues(final List<GValue<?>> gvalues) {
- // convert gvalues to array
- final Object[] newIds = new Object[gvalues.size()];
- int i = 0;
- for (final GValue<?> gvalue : gvalues) {
- newIds[i++] = gvalue.get();
- }
- return newIds;
- }
-
- /**
- * Converts {@link GValue} objects argument array to their values to
prevent them from leaking to the Graph API.
- * Providers extending from this step should use this method to get actual
values to prevent any {@link GValue}
- * objects from leaking to the Graph API.
- */
- public static Object[] resolveToValues(final GValue<?>[] gvalues) {
- final Object[] newIds = new Object[gvalues.length];
- for (int i = 0; i < gvalues.length; i++) {
- newIds[i] = gvalues[i].get();
- }
- return newIds;
- }
-
/**
* Determines if the value held by this object was defined as a variable
or a literal value. Literal values simply
* have no name.
@@ -437,4 +396,36 @@ public class GValue<V> implements Cloneable, Serializable {
public static boolean instanceOfNumber(final Object o) {
return o instanceof Number || (o instanceof GValue && ((GValue)
o).getType().isNumeric());
}
+
+ /**
+ * The elements in object array argument are examined to see if they are
{@link GValue} objects. If they are, they
+ * are preserved as is. If they are not then they are wrapped in a {@link
GValue} object.
+ */
+ public static <T> GValue<T>[] ensureGValues(final Object[] args) {
+ return Stream.of(args).map(GValue::of).toArray(GValue[]::new);
+ }
+
+ /**
+ * Converts {@link GValue} objects argument array to their values to
prevent them from leaking to the Graph API.
+ * Providers extending from this step should use this method to get actual
values to prevent any {@link GValue}
+ * objects from leaking to the Graph API.
+ */
+ public static Object[] resolveToValues(final GValue<?>[] gvalues) {
+ final Object[] values = new Object[gvalues.length];
+ for (int i = 0; i < gvalues.length; i++) {
+ values[i] = gvalues[i].get();
+ }
+ return values;
+ }
+
+ /**
+ * Takes an argument that is either a {@code GValue} or an object and if
the former returns the child object and
+ * if the latter returns the object itself.
+ */
+ public static Object valueOf(final Object either) {
+ if (either instanceof GValue)
+ return ((GValue<?>) either).get();
+ else
+ return either;
+ }
}
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java
index 33b3c80944..5fd63ee3de 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java
@@ -39,10 +39,6 @@ public final class CoinStep<S> extends FilterStep<S>
implements Seedable {
private final Random random = new Random();
private final GValue<Double> probability;
- /**
- * @deprecated As of release 3.7.3, replaced by {@link
#CoinStep(Traversal.Admin, GValue)}
- */
- @Deprecated
public CoinStep(final Traversal.Admin traversal, final double probability)
{
super(traversal);
this.probability = GValue.ofDouble(probability);
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java
index f65ecb7e81..c009588864 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java
@@ -70,7 +70,7 @@ public class GraphStep<S, E extends Element> extends
AbstractStep<S, E> implemen
this.returnClass = returnClass;
// if ids is a single collection like g.V(['a','b','c']), then unroll
it into an array of ids
- this.ids =
GValue.convertToGValues(tryUnrollSingleCollectionArgument(ids));
+ this.ids =
GValue.ensureGValues(tryUnrollSingleCollectionArgument(ids));
this.isStart = isStart;
@@ -169,7 +169,7 @@ public class GraphStep<S, E extends Element> extends
AbstractStep<S, E> implemen
this.legacyLogicForPassingNoIds = newIds.length == 1 && ((newIds[0]
instanceof List && ((List) newIds[0]).isEmpty()) ||
(newIds[0] instanceof GValue && ((GValue)
newIds[0]).getType().isCollection() && ((List) ((GValue)
newIds[0]).get()).isEmpty()));
- final GValue[] gvalues =
GValue.convertToGValues(tryUnrollSingleCollectionArgument(newIds));
+ final GValue[] gvalues =
GValue.ensureGValues(tryUnrollSingleCollectionArgument(newIds));
this.ids = ArrayUtils.addAll(this.ids, gvalues);
}
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/VertexStep.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/VertexStep.java
index 896934668d..b6230e0538 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/VertexStep.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/VertexStep.java
@@ -54,7 +54,7 @@ public class VertexStep<E extends Element> extends
FlatMapStep<Vertex, E> implem
private final Class<E> returnClass;
public VertexStep(final Traversal.Admin traversal, final Class<E>
returnClass, final Direction direction, final String... edgeLabels) {
- this(traversal, returnClass, direction,
GValue.convertToGValues(edgeLabels));
+ this(traversal, returnClass, direction,
GValue.ensureGValues(edgeLabels));
}
public VertexStep(final Traversal.Admin traversal, final Class<E>
returnClass, final Direction direction, final GValue<String>... edgeLabels) {
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectStep.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectStep.java
index 73bf40b199..fa27d5af92 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectStep.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectStep.java
@@ -32,7 +32,7 @@ public final class InjectStep<S> extends StartStep<S> {
@SafeVarargs
public InjectStep(final Traversal.Admin traversal, final S... injections) {
super(traversal);
- this.injections = GValue.convertToGValues(injections);
+ this.injections = GValue.ensureGValues(injections);
this.start = new
ArrayIterator<>(GValue.resolveToValues(this.injections));
}
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/InlineFilterStrategy.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/InlineFilterStrategy.java
index ad0ccf395f..e8b154fef6 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/InlineFilterStrategy.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/InlineFilterStrategy.java
@@ -158,7 +158,7 @@ public final class InlineFilterStrategy extends
AbstractTraversalStrategy<Traver
}
}
if (!edgeLabels.isEmpty()) {
- final VertexStep<Edge> newVertexStep = new
VertexStep<>(traversal, Edge.class, previousStep.getDirection(),
GValue.convertToGValues(edgeLabels.toArray()));
+ final VertexStep<Edge> newVertexStep = new
VertexStep<>(traversal, Edge.class, previousStep.getDirection(),
GValue.ensureGValues(edgeLabels.toArray()));
TraversalHelper.replaceStep(previousStep, newVertexStep,
traversal);
TraversalHelper.copyLabels(previousStep, newVertexStep, false);
if (step.getHasContainers().isEmpty()) {
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/CollectionUtil.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/CollectionUtil.java
index 61d9d7573d..27d41d8e18 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/CollectionUtil.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/CollectionUtil.java
@@ -32,22 +32,36 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+/**
+ * Utility class for working with collections and arrays.
+ */
public final class CollectionUtil {
- private CollectionUtil() {
- }
+ private CollectionUtil() { }
+ /**
+ * Converts varargs to a {@code List}.
+ */
public static <E> List<E> asList(final E... elements) {
return new ArrayList<>(Arrays.asList(elements));
}
+ /**
+ * Converts varargs to a {@code Set}.
+ */
public static <E> LinkedHashSet<E> asSet(final E... elements) {
return asSet(Arrays.asList(elements));
}
+ /**
+ * Converts {@code Collection} to a {@code Set}.
+ */
public static <E> LinkedHashSet<E> asSet(final Collection<E> elements) {
return new LinkedHashSet<>(elements);
}
+ /**
+ * Converts varargs to a {@code Map} where the elements are key/value
pairs.
+ */
public static <K,V> LinkedHashMap<K,V> asMap(final Object... elements) {
final LinkedHashMap<K,V> map = new LinkedHashMap<>();
for (int i = 0; i < elements.length; i+=2) {
@@ -58,6 +72,11 @@ public final class CollectionUtil {
return map;
}
+ /**
+ * Clones a given {@code ConcurrentHashMap} by creating a new map and
copying all entries from the original map.
+ * If the value of an entry is a {@code Set} or an {@code ArrayList}, a
deep copy of the value is created.
+ * Otherwise, the value is copied as is.
+ */
public static <K,V> ConcurrentHashMap<K,V> clone(final
ConcurrentHashMap<K,V> map) {
final ConcurrentHashMap<K, V> result = new
ConcurrentHashMap<>(map.size());
diff --git
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueTest.java
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueTest.java
index f6aff53c8d..625bfaeb5a 100644
---
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueTest.java
+++
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueTest.java
@@ -34,9 +34,11 @@ import java.util.Set;
import org.junit.Test;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
@@ -475,4 +477,133 @@ public class GValueTest {
public void valueInstanceOfNumericShouldReturnFalseForNullObject() {
assertThat(GValue.valueInstanceOfNumeric(null), is(false));
}
+
+ @Test
+ public void shouldConvertObjectArrayToGValues() {
+ final Object[] input = {1, "string", true};
+ final GValue<?>[] expected = {GValue.of(1), GValue.of("string"),
GValue.of(true)};
+ final GValue<?>[] result = GValue.ensureGValues(input);
+ assertArrayEquals(expected, result);
+ }
+
+ @Test
+ public void shouldPreserveExistingGValuesInArray() {
+ final GValue<Integer> gValue = GValue.of(123);
+ final Object[] input = {gValue, "string"};
+ final GValue<?>[] expected = {gValue, GValue.of("string")};
+ final GValue<?>[] result = GValue.ensureGValues(input);
+ assertArrayEquals(expected, result);
+ }
+
+ @Test
+ public void shouldHandleEmptyArray() {
+ final Object[] input = {};
+ final GValue<?>[] expected = {};
+ final GValue<?>[] result = GValue.ensureGValues(input);
+ assertArrayEquals(expected, result);
+ }
+
+ @Test
+ public void shouldHandleArrayWithNullValues() {
+ final Object[] input = {null, "string"};
+ final GValue<?>[] expected = {GValue.of(null), GValue.of("string")};
+ final GValue<?>[] result = GValue.ensureGValues(input);
+ assertArrayEquals(expected, result);
+ }
+
+ @Test
+ public void shouldResolveGValuesToValues() {
+ final GValue<?>[] input = {GValue.of(1), GValue.of("string"),
GValue.of(true)};
+ final Object[] expected = {1, "string", true};
+ final Object[] result = GValue.resolveToValues(input);
+ assertArrayEquals(expected, result);
+ }
+
+ @Test
+ public void shouldHandleEmptyGValuesArray() {
+ final GValue<?>[] input = {};
+ final Object[] expected = {};
+ final Object[] result = GValue.resolveToValues(input);
+ assertArrayEquals(expected, result);
+ }
+
+ @Test
+ public void shouldHandleGValuesArrayWithNullValues() {
+ final GValue<?>[] input = {GValue.of(null), GValue.of("string")};
+ final Object[] expected = {null, "string"};
+ final Object[] result = GValue.resolveToValues(input);
+ assertArrayEquals(expected, result);
+ }
+
+ @Test
+ public void shouldHandleMixedTypeGValuesArray() {
+ final GValue<?>[] input = {GValue.of(1), GValue.of("string"),
GValue.of(true), GValue.of(123.45)};
+ final Object[] expected = {1, "string", true, 123.45};
+ final Object[] result = GValue.resolveToValues(input);
+ assertArrayEquals(expected, result);
+ }
+
+ @Test
+ public void equalsShouldReturnTrueForSameObject() {
+ final GValue<Integer> gValue = GValue.of(123);
+ assertEquals(true, gValue.equals(gValue));
+ }
+
+ @Test
+ public void equalsShouldReturnFalseForNull() {
+ final GValue<Integer> gValue = GValue.of(123);
+ assertEquals(false, gValue.equals(null));
+ }
+
+ @Test
+ public void equalsShouldReturnFalseForDifferentClass() {
+ final GValue<Integer> gValue = GValue.of(123);
+ assertEquals(false, gValue.equals("string"));
+ }
+
+ @Test
+ public void equalsShouldReturnTrueForEqualGValues() {
+ final GValue<Integer> gValue1 = GValue.of(123);
+ final GValue<Integer> gValue2 = GValue.of(123);
+ assertEquals(true, gValue1.equals(gValue2));
+ }
+
+ @Test
+ public void equalsShouldReturnFalseForDifferentNames() {
+ final GValue<Integer> gValue1 = GValue.of("name1", 123);
+ final GValue<Integer> gValue2 = GValue.of("name2", 123);
+ assertEquals(false, gValue1.equals(gValue2));
+ }
+
+ @Test
+ public void equalsShouldReturnFalseForDifferentTypes() {
+ final GValue<Integer> gValue1 = GValue.of(123);
+ final GValue<String> gValue2 = GValue.of("123");
+ assertEquals(false, gValue1.equals(gValue2));
+ }
+
+ @Test
+ public void equalsShouldReturnFalseForDifferentValues() {
+ final GValue<Integer> gValue1 = GValue.of(123);
+ final GValue<Integer> gValue2 = GValue.of(456);
+ assertEquals(false, gValue1.equals(gValue2));
+ }
+
+ @Test
+ public void valueOfShouldReturnGValueValue() {
+ final GValue<Integer> gValue = GValue.of(123);
+ assertEquals(123, GValue.valueOf(gValue));
+ }
+
+ @Test
+ public void valueOfShouldReturnObjectAsIs() {
+ final String value = "test";
+ assertEquals("test", GValue.valueOf(value));
+ }
+
+ @Test
+ public void valueOfShouldReturnNullForNullInput() {
+ assertNull(GValue.valueOf(null));
+ assertNull(null);
+ }
}
\ No newline at end of file
diff --git
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/CollectionUtilTest.java
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/CollectionUtilTest.java
index 73bd39b415..7b938c1a22 100644
---
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/CollectionUtilTest.java
+++
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/CollectionUtilTest.java
@@ -22,10 +22,20 @@ import org.apache.tinkerpop.gremlin.AssertHelper;
import org.junit.Test;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class CollectionUtilTest {
@@ -70,4 +80,104 @@ public class CollectionUtilTest {
final ConcurrentHashMap<?, ?> cloned = CollectionUtil.clone(source);
assertTrue(source.equals(cloned));
}
+
+ @Test
+ public void shouldCloneEmptyConcurrentHashMap() {
+ final ConcurrentHashMap<String, String> source = new
ConcurrentHashMap<>();
+ final ConcurrentHashMap<?, ?> cloned = CollectionUtil.clone(source);
+ assertTrue(source.equals(cloned));
+ }
+
+ @Test
+ public void shouldCloneConcurrentHashMapWithMixedTypes() {
+ final ConcurrentHashMap<String, Object> source = new
ConcurrentHashMap<>();
+ source.put("key1", "value1");
+ source.put("key2", new ArrayList<>(Arrays.asList("a", "b")));
+ source.put("key3", new HashSet<>(Arrays.asList("x", "y")));
+
+ final ConcurrentHashMap<?, ?> cloned = CollectionUtil.clone(source);
+ assertTrue(source.equals(cloned));
+ }
+
+ @Test
+ public void shouldAddFirstWhenBothArgumentsNull() {
+ String[] result = CollectionUtil.addFirst(null, null, String.class);
+ assertArrayEquals(new String[]{null}, result);
+ }
+
+ @Test
+ public void shouldAddFirstWhenArrayNull() {
+ String[] result = CollectionUtil.addFirst(null, "element",
String.class);
+ assertArrayEquals(new String[]{"element"}, result);
+ }
+
+ @Test
+ public void shoulAddFirstWhenNeitherArgumentNull() {
+ Integer[] array = {1, 2, 3};
+ Integer[] result = CollectionUtil.addFirst(array, 0, Integer.class);
+ assertArrayEquals(new Integer[]{0, 1, 2, 3}, result);
+ }
+
+ @Test
+ public void shouldAddFirstWhenEmptyArray() {
+ String[] array = {};
+ String[] result = CollectionUtil.addFirst(array, "element",
String.class);
+ assertArrayEquals(new String[]{"element"}, result);
+ }
+
+ @Test
+ public void shouldAddFirstWhenIntegers() {
+ Integer[] array = {1, 2, 3};
+ Integer[] result = CollectionUtil.addFirst(array, 0, Integer.class);
+ assertArrayEquals(new Integer[]{0, 1, 2, 3}, result);
+ }
+
+ @Test
+ public void shouldConvertVarargsToList() {
+ List<String> result = CollectionUtil.asList("a", "b", "c");
+ assertEquals(Arrays.asList("a", "b", "c"), result);
+ }
+
+ @Test
+ public void shouldConvertEmptyVarargsToList() {
+ List<String> result = CollectionUtil.asList();
+ assertEquals(Collections.emptyList(), result);
+ }
+
+ @Test
+ public void shouldConvertVarargsToSet() {
+ Set<String> result = CollectionUtil.asSet("a", "b", "c");
+ assertEquals(new LinkedHashSet<>(Arrays.asList("a", "b", "c")),
result);
+ }
+
+ @Test
+ public void shouldConvertEmptyVarargsToSet() {
+ Set<String> result = CollectionUtil.asSet();
+ assertEquals(new LinkedHashSet<>(), result);
+ }
+
+ @Test
+ public void shouldConvertCollectionToSet() {
+ Collection<String> collection = Arrays.asList("a", "b", "c");
+ Set<String> result = CollectionUtil.asSet(collection);
+ assertEquals(new LinkedHashSet<>(collection), result);
+ }
+
+ @Test
+ public void shouldConvertVarargsToMap() {
+ Map<String, String> result = CollectionUtil.asMap("key1", "value1",
"key2", "value2");
+ Map<String, String> expected = new LinkedHashMap<>();
+ expected.put("key1", "value1");
+ expected.put("key2", "value2");
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void shouldConvertVarargsToMapWithOddNumberOfElements() {
+ Map<String, String> result = CollectionUtil.asMap("key1", "value1",
"key2");
+ Map<String, String> expected = new LinkedHashMap<>();
+ expected.put("key1", "value1");
+ expected.put("key2", null);
+ assertEquals(expected, result);
+ }
}