SLING-9140 - utility to merge multiple value maps (#21)
SLING-9140 - utility to merge multiple value maps
- introduce o.a.s.api.wrapper.ValueMapUtil
- support merging of multiple ValueMaps
- support caching of a ValueMap
- deprecate CompositeValueMap in favour of ValueMapUtil#merge
Co-authored-by: Nicolas Peltier <1032754+npeltier@users.noreply.github.com>
diff --git a/pom.xml b/pom.xml
index 8881b65..fbabce1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
</parent>
<artifactId>org.apache.sling.api</artifactId>
- <version>2.22.1-SNAPSHOT</version>
+ <version>2.23.0-SNAPSHOT</version>
<name>Apache Sling API</name>
<description>
diff --git a/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java b/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java
index cee8cca..4f90022 100644
--- a/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java
+++ b/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java
@@ -36,8 +36,14 @@
* In case you would like to avoid duplicating properties on multiple resources,
* you can use a <code>CompositeValueMap</code> to get a concatenated map of
* properties.
+ *
* @since 2.3 (Sling API Bundle 2.5.0)
+ *
+ * @deprecated Use {@link ValueMapUtil#merge(ValueMap...)} instead. Note that it
+ * does not support the parameter {@code merge = false}. However, this could easily
+ * be achieved with another decorator that restricts the set of allowed keys.
*/
+@Deprecated
public class CompositeValueMap implements ValueMap {
/**
diff --git a/src/main/java/org/apache/sling/api/wrappers/ValueMapUtil.java b/src/main/java/org/apache/sling/api/wrappers/ValueMapUtil.java
new file mode 100644
index 0000000..5283a32
--- /dev/null
+++ b/src/main/java/org/apache/sling/api/wrappers/ValueMapUtil.java
@@ -0,0 +1,97 @@
+/*
+ * 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.sling.api.wrappers;
+
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.impl.CachingValueMap;
+import org.apache.sling.api.wrappers.impl.MergingValueMap;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+
+public final class ValueMapUtil {
+
+ /**
+ * A convenience method that turns the var-args into a {@code Collection}
+ * and delegates to {@link #merge(List)}.
+ *
+ * @param valueMaps the {@code ValueMap} instances to merge
+ * @return the merged {@code ValueMap} view
+ *
+ * @see #merge(List)
+ */
+ @NotNull
+ public static ValueMap merge(@NotNull ValueMap... valueMaps) {
+ return merge(asList(valueMaps));
+ }
+
+ /**
+ * Merge provided {@code ValueMaps} into a single view {@code ValueMap} that aggregates
+ * all key-value pairs of the given maps. The value for a key-value pair is taken from
+ * the first {@code ValueMap} (in iteration order) that has a mapping for the given key.
+ * <br>
+ * E.g. assuming {@code merge(vm1, vm2, vm3} where all maps {@code vm1, vm2, vm3} have
+ * a value mapped to the key {@code k1}, then the value from {@code vm1} is returned.
+ *
+ * @param valueMaps the {@code ValueMap} instances to merge
+ * @return the merged {@code ValueMap} view
+ */
+ @NotNull
+ public static ValueMap merge(@NotNull List<ValueMap> valueMaps) {
+ return new MergingValueMap(valueMaps);
+ }
+
+ /**
+ * Convenience method that allows creating a merged {@code ValueMap} where
+ * accessed mappings are cached to optimize repeated lookups.
+ * <br>
+ * This is equivalent to calling {@code cache(merge(valueMaps))}.
+ *
+ * @param valueMaps the {@code ValueMap} instances to merge
+ * @return the merged and cached {@code ValueMap} view
+ */
+ @NotNull
+ public static ValueMap mergeAndCache(@NotNull List<ValueMap> valueMaps) {
+ return cache(merge(valueMaps));
+ }
+
+ /**
+ * Decorates the given {@code ValueMap} with a caching layer.
+ * Every key-value pair that is accessed is cached for
+ * subsequent accesses. Calls to {@code ValueMap#keySet()},
+ * {@code ValueMap#values()} and {@code ValueMap#entrySet()}
+ * will cause all entries to be cached.
+ * <br>
+ * Note: if the underlying {@code ValueMap} is modified, the
+ * modification may not be reflected via the caching wrapper.
+ *
+ * @param valueMap the {@code ValueMap} instance to cache
+ * @return the cached {@code ValueMap} view
+ */
+ @NotNull
+ public static ValueMap cache(@NotNull ValueMap valueMap) {
+ return new CachingValueMap(valueMap);
+ }
+
+ /**
+ * private constructor to hide implicit public one
+ */
+ private ValueMapUtil() {
+ }
+}
diff --git a/src/main/java/org/apache/sling/api/wrappers/impl/CachingValueMap.java b/src/main/java/org/apache/sling/api/wrappers/impl/CachingValueMap.java
new file mode 100644
index 0000000..9a7ae0f
--- /dev/null
+++ b/src/main/java/org/apache/sling/api/wrappers/impl/CachingValueMap.java
@@ -0,0 +1,123 @@
+/*
+ * 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.sling.api.wrappers.impl;
+
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * ValueMap decorator that caches key-value pairs that were accessed before.
+ *
+ * @see ValueMapUtil#cache(org.apache.sling.api.resource.ValueMap)
+ */
+public class CachingValueMap implements ValueMap {
+
+ private static final String IMMUTABLE_ERROR_MESSAGE = "CachingValueMap is immutable";
+
+ private final ValueMap delegate;
+
+ private final Map<String, Object> cache = new HashMap<>();
+
+ private boolean fullyCached = false;
+
+ public CachingValueMap(ValueMap delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public int size() {
+ return fullyCached ? cache.size() : delegate.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return cache.containsKey(key) || delegate.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return cache.containsValue(value) || delegate.containsValue(value);
+ }
+
+ @Override
+ public Object get(Object key) {
+ return key instanceof String ? cache.computeIfAbsent((String)key, delegate::get) : null;
+ }
+
+ @NotNull
+ @Override
+ public Set<String> keySet() {
+ ensureFullyCached();
+ return cache.keySet();
+ }
+
+ @NotNull
+ @Override
+ public Collection<Object> values() {
+ ensureFullyCached();
+ return cache.values();
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<String, Object>> entrySet() {
+ ensureFullyCached();
+ return cache.entrySet();
+ }
+
+ private void ensureFullyCached() {
+ if (!fullyCached) {
+ cache.putAll(delegate);
+ fullyCached = true;
+ }
+ }
+
+ @Nullable
+ @Override
+ public Object put(String key, Object value) {
+ throw new UnsupportedOperationException(IMMUTABLE_ERROR_MESSAGE);
+ }
+
+ @Override
+ public Object remove(Object key) {
+ throw new UnsupportedOperationException(IMMUTABLE_ERROR_MESSAGE);
+ }
+
+ @Override
+ public void putAll(@NotNull Map<? extends String, ?> m) {
+ throw new UnsupportedOperationException(IMMUTABLE_ERROR_MESSAGE);
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException(IMMUTABLE_ERROR_MESSAGE);
+ }
+}
diff --git a/src/main/java/org/apache/sling/api/wrappers/impl/MergingValueMap.java b/src/main/java/org/apache/sling/api/wrappers/impl/MergingValueMap.java
new file mode 100644
index 0000000..9e2bef3
--- /dev/null
+++ b/src/main/java/org/apache/sling/api/wrappers/impl/MergingValueMap.java
@@ -0,0 +1,177 @@
+/*
+ * 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.sling.api.wrappers.impl;
+
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Merge provided {@code ValueMaps} into a single view {@code ValueMap} that aggregates
+ * all key-value pairs of the given maps. The value for a key-value pair is taken from
+ * the first {@code ValueMap} (in iteration order) that has a mapping for the given key.
+ * <br>
+ * In case you would like to avoid duplicating properties on multiple resources,
+ * you can use a <code>{@link MergingValueMap}</code> to get a concatenated map of
+ * properties.
+ *
+ * @see ValueMapUtil#merge(List)
+ * @see ValueMapUtil#merge(ValueMap...)
+ * @see ValueMapUtil#mergeAndCache(List)
+ */
+public class MergingValueMap implements ValueMap {
+
+ private static final String IMMUTABLE_ERROR_MESSAGE = "MergingValueMap is immutable";
+
+ private final List<ValueMap> valueMaps;
+
+ /**
+ * Constructor that allows merging any number of {@code ValueMap}
+ * instances into a single {@code ValueMap} view. The keys of the
+ * view are the union of the keys of all value maps. The values of
+ * the view is the mapping of all keys to their respective value.
+ * The entries are the key-value pairs. Values are retrieved by
+ * getting the value for a key for each {@code ValueMap} until a
+ * non-null value is found.
+ *
+ * @param valueMaps The ValueMaps to be merged.
+ *
+ * @see ValueMapUtil#merge(List)
+ * @see ValueMapUtil#merge(ValueMap...)
+ */
+ public MergingValueMap(@NotNull List<ValueMap> valueMaps) {
+ this.valueMaps = Collections.unmodifiableList(new ArrayList<>(valueMaps));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int size() {
+ return keySet().size();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean containsKey(final Object key) {
+ return keyStream().anyMatch(k -> Objects.equals(k, key));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean containsValue(final Object value) {
+ return valueStream().anyMatch(v -> Objects.equals(v, value));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object get(Object key) {
+ return valueMaps.stream()
+ .map(vm -> vm.get(key))
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse(null);
+ }
+
+ @NotNull
+ @Override
+ public Set<String> keySet() {
+ return keyStream().collect(Collectors.toSet());
+ }
+
+ @NotNull
+ @Override
+ public Collection<Object> values() {
+ return valueStream().collect(Collectors.toList());
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<String, Object>> entrySet() {
+ return keyStream()
+ .map(key -> new AbstractMap.SimpleEntry<>(key, get(key)))
+ .collect(Collectors.toSet());
+ }
+
+ @NotNull
+ private Stream<String> keyStream() {
+ return valueMaps.stream()
+ .map(Map::keySet)
+ .flatMap(Collection::stream)
+ .distinct();
+ }
+
+ @NotNull
+ private Stream<Object> valueStream() {
+ return keyStream().map(this::get);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object put(final String aKey, final Object value) {
+ throw new UnsupportedOperationException(IMMUTABLE_ERROR_MESSAGE);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object remove(final Object key) {
+ throw new UnsupportedOperationException(IMMUTABLE_ERROR_MESSAGE);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void putAll(final Map<? extends String, ?> properties) {
+ throw new UnsupportedOperationException(IMMUTABLE_ERROR_MESSAGE);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void clear() {
+ throw new UnsupportedOperationException(IMMUTABLE_ERROR_MESSAGE);
+ }
+}
diff --git a/src/main/java/org/apache/sling/api/wrappers/package-info.java b/src/main/java/org/apache/sling/api/wrappers/package-info.java
index aa18721..39ff070 100644
--- a/src/main/java/org/apache/sling/api/wrappers/package-info.java
+++ b/src/main/java/org/apache/sling/api/wrappers/package-info.java
@@ -17,7 +17,7 @@
* under the License.
*/
-@Version("2.6.4")
+@Version("2.7.0")
package org.apache.sling.api.wrappers;
import org.osgi.annotation.versioning.Version;
diff --git a/src/test/java/org/apache/sling/api/resource/impl/ValueMapUtilMergeTest.java b/src/test/java/org/apache/sling/api/resource/impl/ValueMapUtilMergeTest.java
new file mode 100644
index 0000000..470955a
--- /dev/null
+++ b/src/test/java/org/apache/sling/api/resource/impl/ValueMapUtilMergeTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.sling.api.resource.impl;
+
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapUtil;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+
+import static java.util.Arrays.asList;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(Parameterized.class)
+public class ValueMapUtilMergeTest {
+
+ private final Function<Collection<ValueMap>, ValueMap> mergeFn;
+
+ @Parameterized.Parameters(name = "using ValueMapUtil#{0}")
+ public static Iterable<Object[]> testedMergeMethod() {
+ return asList(
+ new Object[] { "mergeAndCache", (Function<List<ValueMap>, ValueMap>)ValueMapUtil::mergeAndCache},
+ new Object[] { "merge", (Function<List<ValueMap>, ValueMap>)ValueMapUtil::merge}
+ );
+ }
+
+ public ValueMapUtilMergeTest(String name, Function<Collection<ValueMap>, ValueMap> mergeFn) {
+ this.mergeFn = mergeFn;
+ }
+
+ private ValueMap merge(ValueMap... valueMaps) {
+ return mergeFn.apply(asList(valueMaps));
+ }
+
+ @Test
+ public void isEmptyTest() {
+ ValueMap vm = merge(createValueMap(), createValueMap());
+ assertTrue("Value map should be empty", vm.isEmpty());
+ assertFalse("Typical map should not be empty", typicalVM().isEmpty());
+ }
+
+ @Test
+ public void keySetTest() {
+ assertThat("key set should be k1-k5", typicalVM().keySet(), containsInAnyOrder("k1", "k2", "k3", "k4", "k5"));
+ }
+
+ @Test
+ public void valuesTest() {
+ assertThat("values should be the one expected", typicalVM().values(), containsInAnyOrder("11", "22", "13", "24", "35"));
+ }
+
+ @Test
+ public void testGet() {
+ ValueMap vm = typicalVM();
+ assertEquals("k1-11", "11", vm.get("k1"));
+ assertEquals("k2-22", "22", vm.get("k2"));
+ assertEquals("k3-13", "13", vm.get("k3"));
+ assertEquals("k4-24", "24", vm.get("k4"));
+ assertEquals("k5-5", "35", vm.get("k5"));
+ }
+
+ @Test
+ public void testNullGet() {
+ assertNull("null get should return null", typicalVM().get(null));
+ }
+
+ @Test
+ public void testDefaultValue() {
+ ValueMap vm = typicalVM();
+ String randomString = "not-that-random-string";
+ assertEquals("default value should work", randomString, vm.get("random-key", randomString));
+ }
+
+ @Test
+ public void testContainsKey() {
+ ValueMap vm = typicalVM();
+ assertTrue("should contain 11", vm.containsValue("11"));
+ assertFalse("should not contain 21", vm.containsValue("21"));
+ }
+
+ @Test
+ public void testContainsValue() {
+ ValueMap vm = typicalVM();
+ assertTrue("should contain k1", vm.containsKey("k1"));
+ assertTrue("should contain k2", vm.containsKey("k2"));
+ assertTrue("should contain k3", vm.containsKey("k3"));
+ assertTrue("should contain k4", vm.containsKey("k4"));
+ assertTrue("should contain k5", vm.containsKey("k5"));
+ }
+
+ @Test
+ public void testDeepGet() {
+ ValueMap vm = merge(createValueMap("k/1", "11"), createValueMap("1", "21"));
+ assertEquals("value should be 11", "11", vm.get("k/1", String.class));
+ }
+
+ @Test
+ public void testLong() {
+ ValueMap vm = merge(createValueMap("k", 11L));
+ assertEquals("result should be the same than a simple property fetching", "11", vm.get("k", String.class));
+ assertEquals("result should be the same than a simple property fetching", 11L, Objects.requireNonNull(vm.get("k", Long.class)).longValue());
+ }
+
+ @Test
+ public void testBoolean() {
+ ValueMap vm = merge(createValueMap("k", true));
+ assertEquals("result should be the same than a simple property fetching", "true", vm.get("k", String.class));
+ assertEquals("result should be the same than a simple property fetching", true, vm.get("k", boolean.class));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testPutOnVM() {
+ typicalVM().put("foo", "bar");
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testRemoveOnVM() {
+ typicalVM().remove("foo");
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testPutAllOnVM() {
+ typicalVM().putAll(typicalVM());
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testClearOnVM() {
+ typicalVM().clear();
+ }
+
+ private static ValueMap createValueMap(Object... pairs) {
+ ValueMap vm = new ValueMapDecorator(new HashMap<>());
+ for (int i = 0; i < pairs.length; i += 2) {
+ vm.put(pairs[i].toString(), pairs[i + 1]);
+ }
+ return vm;
+ }
+
+ /**
+ * @return typical vm, with 3 inner resources, sharing properties from k1 to k5, noted kX each level Y's value
+ * being YX
+ */
+ private ValueMap typicalVM() {
+ ValueMap v1 = createValueMap("k1", "11", "k3", "13");
+ ValueMap v2 = createValueMap("k1", "21", "k2", "22", "k4", "24");
+ ValueMap v3 = createValueMap("k1", "31", "k3", "33", "k4", "34", "k5", "35");
+ return merge(v1, v2, v3);
+ }
+}
diff --git a/src/test/java/org/apache/sling/api/wrappers/CompositeValueMapTest.java b/src/test/java/org/apache/sling/api/wrappers/CompositeValueMapTest.java
index 5728a16..154055b 100644
--- a/src/test/java/org/apache/sling/api/wrappers/CompositeValueMapTest.java
+++ b/src/test/java/org/apache/sling/api/wrappers/CompositeValueMapTest.java
@@ -18,15 +18,16 @@
*/
package org.apache.sling.api.wrappers;
+import org.apache.sling.api.resource.ValueMap;
+import org.junit.Assert;
+import org.junit.Test;
+
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import org.apache.commons.collections.CollectionUtils;
-import org.apache.sling.api.resource.ValueMap;
-import org.junit.Assert;
-import org.junit.Test;
+import static org.hamcrest.Matchers.containsInAnyOrder;
public class CompositeValueMapTest {
@@ -168,7 +169,7 @@
}
if (testResult.shouldHaveNewType()) {
- Assert.assertTrue("Type of property '" + property + "' should have changed", valueMap.get(property).getClass().equals(testResult.expectedNewType));
+ Assert.assertEquals("Type of property '" + property + "' should have changed",testResult.expectedNewType, valueMap.get(property).getClass());
expectedMap.put(property, testResult.extendedValue);
}
@@ -180,9 +181,9 @@
}
Assert.assertEquals("Final map size does NOT match", expectedSize, valueMap.size());
- Assert.assertEquals("Final map entries do NOT match", expectedMap.entrySet(), valueMap.entrySet());
- Assert.assertEquals("Final map keys do NOT match", expectedMap.keySet(), valueMap.keySet());
- Assert.assertTrue("Final map values do NOT match expected: <" + expectedMap.values() + "> but was: <" + valueMap.values() + ">", CollectionUtils.isEqualCollection(expectedMap.values(), valueMap.values()));
+ Assert.assertThat("Final map keys do NOT match", valueMap.keySet(), containsInAnyOrder(expectedMap.keySet().toArray()));
+ Assert.assertThat("Final map values do NOT match", valueMap.values(), containsInAnyOrder(expectedMap.values().toArray()));
+ Assert.assertThat("Final map entries do NOT match", valueMap.entrySet(), containsInAnyOrder(expectedMap.entrySet().toArray()));
}
/**