From bde0d12c51278f1c214f170fea4f804699d92329 Mon Sep 17 00:00:00 2001 From: Peter De Maeyer Date: Mon, 14 Oct 2024 21:22:06 +0200 Subject: [PATCH] COLLECTIONS-533 Added ArrayListValuedLinkedHashMap --- .../ArrayListValuedLinkedHashMap.java | 155 +++++++++ .../multimap/ArrayListValuedHashMapTest.java | 12 +- .../ArrayListValuedLinkedHashMapTest.java | 295 ++++++++++++++++++ ...nkedHashMap.emptyCollection.version4.5.obj | Bin 0 -> 123 bytes ...inkedHashMap.fullCollection.version4.5.obj | Bin 0 -> 20893 bytes 5 files changed, 460 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMap.java create mode 100644 src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMapTest.java create mode 100644 src/test/resources/org/apache/commons/collections4/data/test/ArrayListValuedLinkedHashMap.emptyCollection.version4.5.obj create mode 100644 src/test/resources/org/apache/commons/collections4/data/test/ArrayListValuedLinkedHashMap.fullCollection.version4.5.obj diff --git a/src/main/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMap.java b/src/main/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMap.java new file mode 100644 index 0000000000..3c238156f7 --- /dev/null +++ b/src/main/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMap.java @@ -0,0 +1,155 @@ +/* + * 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.commons.collections4.multimap; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.collections4.MultiValuedMap; + +/** + * Implements a {@code ListValuedMap}, using a {@link LinkedHashMap} to provide data + * storage and {@link ArrayList}s as value collections. This is the standard + * implementation of a ListValuedMap. + *

+ * Note that ArrayListValuedLinkedHashMap is not synchronized and is not + * thread-safe. If you wish to use this map from multiple threads + * concurrently, you must use appropriate synchronization. This class may throw + * exceptions when accessed by concurrent threads without synchronization. + *

+ * + * @param the type of the keys in this map + * @param the type of the values in this map + * @since 4.5 + */ +public class ArrayListValuedLinkedHashMap extends AbstractListValuedMap + implements Serializable { + + /** Serialization Version */ + private static final long serialVersionUID = 20241014L; + + /** + * The initial map capacity used when none specified in constructor. + */ + private static final int DEFAULT_INITIAL_MAP_CAPACITY = 16; + + /** + * The initial list capacity when using none specified in constructor. + */ + private static final int DEFAULT_INITIAL_LIST_CAPACITY = 3; + + /** + * The initial list capacity when creating a new value collection. + */ + private final int initialListCapacity; + + /** + * Creates an empty ArrayListValuedHashMap with the default initial + * map capacity (16) and the default initial list capacity (3). + */ + public ArrayListValuedLinkedHashMap() { + this(DEFAULT_INITIAL_MAP_CAPACITY, DEFAULT_INITIAL_LIST_CAPACITY); + } + + /** + * Creates an empty ArrayListValuedHashMap with the default initial + * map capacity (16) and the specified initial list capacity. + * + * @param initialListCapacity the initial capacity used for value collections + */ + public ArrayListValuedLinkedHashMap(final int initialListCapacity) { + this(DEFAULT_INITIAL_MAP_CAPACITY, initialListCapacity); + } + + /** + * Creates an empty ArrayListValuedHashMap with the specified initial + * map and list capacities. + * + * @param initialMapCapacity the initial hashmap capacity + * @param initialListCapacity the initial capacity used for value collections + */ + public ArrayListValuedLinkedHashMap(final int initialMapCapacity, final int initialListCapacity) { + super(new LinkedHashMap<>(initialMapCapacity)); + this.initialListCapacity = initialListCapacity; + } + + /** + * Creates an ArrayListValuedHashMap copying all the mappings of the given map. + * + * @param map a {@code Map} to copy into this map + */ + public ArrayListValuedLinkedHashMap(final Map map) { + this(map.size(), DEFAULT_INITIAL_LIST_CAPACITY); + super.putAll(map); + } + + /** + * Creates an ArrayListValuedHashMap copying all the mappings of the given map. + * + * @param map a {@code MultiValuedMap} to copy into this map + */ + public ArrayListValuedLinkedHashMap(final MultiValuedMap map) { + this(map.size(), DEFAULT_INITIAL_LIST_CAPACITY); + super.putAll(map); + } + + @Override + protected ArrayList createCollection() { + return new ArrayList<>(initialListCapacity); + } + + /** + * Deserializes an instance from an ObjectInputStream. + * + * @param in The source ObjectInputStream. + * @throws IOException Any of the usual Input/Output related exceptions. + * @throws ClassNotFoundException A class of a serialized object cannot be found. + */ + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + setMap(new LinkedHashMap<>()); + doReadObject(in); + } + + /** + * Trims the capacity of all value collections to their current size. + */ + public void trimToSize() { + for (final Collection coll : getMap().values()) { + final ArrayList list = (ArrayList) coll; + list.trimToSize(); + } + } + + /** + * Serializes this object to an ObjectOutputStream. + * + * @param out the target ObjectOutputStream. + * @throws IOException thrown when an I/O errors occur writing to the target stream. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + doWriteObject(out); + } + +} diff --git a/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedHashMapTest.java b/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedHashMapTest.java index 89830a9d08..aed4b14144 100644 --- a/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedHashMapTest.java +++ b/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedHashMapTest.java @@ -254,11 +254,19 @@ public void testWrappedListAddAll() { assertEquals("Q", list3.get(2)); } + @Test + public void testCopyConstructorWithMultiValuedMap() { + final ListValuedMap map = makeObject(); + map.put((K) "key", (V) "sleutel"); + final ListValuedMap copy = new ArrayListValuedHashMap<>(map); + assertEquals(map, copy); + } + // public void testCreate() throws Exception { // writeExternalFormToDisk((java.io.Serializable) makeObject(), -// "src/test/resources/data/test/ArrayListValuedHashMap.emptyCollection.version4.1.obj"); +// "src/test/resources/org/apache/commons/collections4/data/test/ArrayListValuedHashMap.emptyCollection.version4.1.obj"); // writeExternalFormToDisk((java.io.Serializable) makeFullMap(), -// "src/test/resources/data/test/ArrayListValuedHashMap.fullCollection.version4.1.obj"); +// "src/test/resources/org/apache/commons/collections4/data/test/ArrayListValuedHashMap.fullCollection.version4.1.obj"); // } } diff --git a/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMapTest.java b/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMapTest.java new file mode 100644 index 0000000000..a05758f63d --- /dev/null +++ b/src/test/java/org/apache/commons/collections4/multimap/ArrayListValuedLinkedHashMapTest.java @@ -0,0 +1,295 @@ +/* + * 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.commons.collections4.multimap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.apache.commons.collections4.ListValuedMap; +import org.apache.commons.collections4.MapIterator; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.collection.AbstractCollectionTest; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link ArrayListValuedLinkedHashMap}. + */ +public class ArrayListValuedLinkedHashMapTest extends AbstractMultiValuedMapTest { + + public ArrayListValuedLinkedHashMapTest() { + super(ArrayListValuedLinkedHashMapTest.class.getSimpleName()); + } + + @Override + protected int getIterationBehaviour() { + return AbstractCollectionTest.UNORDERED; + } + + @Override + public ListValuedMap makeObject() { + return new ArrayListValuedLinkedHashMap<>(); + } + + @Override + public String getCompatibilityVersion() { + return "4.5"; // ArrayListValuedLinkedHashMap has been added in version 4.5 + } + + @Test + public void testArrayListValuedLinkedHashMap() { + final ListValuedMap listMap; + final ListValuedMap listMap1; + final Map map = new HashMap<>(); + final Map map1 = new HashMap<>(); + map.put((K) "A", (V) "W"); + map.put((K) "B", (V) "X"); + map.put((K) "C", (V) "F"); + + listMap = new ArrayListValuedLinkedHashMap<>(map); + assertEquals(1, listMap.get((K) "A").size()); + assertEquals(1, listMap.get((K) "B").size()); + assertEquals(1, listMap.get((K) "C").size()); + + listMap1 = new ArrayListValuedLinkedHashMap<>(map1); + assertEquals("{}", listMap1.toString()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testEqualsHashCodeContract() { + final MultiValuedMap map1 = makeObject(); + final MultiValuedMap map2 = makeObject(); + + map1.put("a", "a1"); + map1.put("a", "a2"); + map2.put("a", "a1"); + map2.put("a", "a2"); + assertEquals(map1, map2); + assertEquals(map1.hashCode(), map2.hashCode()); + + map2.put("a", "a2"); + assertNotSame(map1, map2); + assertNotSame(map1.hashCode(), map2.hashCode()); + } + + @Test + @SuppressWarnings("unchecked") + public void testListValuedMapAdd() { + final ListValuedMap listMap = makeObject(); + assertTrue(listMap.get((K) "whatever") instanceof List); + final List list = listMap.get((K) "A"); + list.add((V) "a1"); + assertEquals(1, listMap.size()); + assertTrue(listMap.containsKey("A")); + } + + @Test + @SuppressWarnings("unchecked") + public void testListValuedMapAddViaListIterator() { + final ListValuedMap listMap = makeObject(); + final ListIterator listIt = listMap.get((K) "B").listIterator(); + assertFalse(listIt.hasNext()); + listIt.add((V) "b1"); + listIt.add((V) "b2"); + listIt.add((V) "b3"); + assertEquals(3, listMap.size()); + assertTrue(listMap.containsKey("B")); + // As ListIterator always adds before the current cursor + assertFalse(listIt.hasNext()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testListValuedMapEqualsHashCodeContract() { + final ListValuedMap map1 = makeObject(); + final ListValuedMap map2 = makeObject(); + + map1.put("a", "a1"); + map1.put("a", "a2"); + map2.put("a", "a1"); + map2.put("a", "a2"); + assertEquals(map1, map2); + assertEquals(map1.hashCode(), map2.hashCode()); + + map1.put("b", "b1"); + map1.put("b", "b2"); + map2.put("b", "b2"); + map2.put("b", "b1"); + assertNotSame(map1, map2); + assertNotSame(map1.hashCode(), map2.hashCode()); + } + + @Test + @SuppressWarnings("unchecked") + public void testListValuedMapRemove() { + final ListValuedMap listMap = makeObject(); + final List list = listMap.get((K) "A"); + list.add((V) "a1"); + list.add((V) "a2"); + list.add((V) "a3"); + assertEquals(3, listMap.size()); + assertEquals("a1", list.remove(0)); + assertEquals(2, listMap.size()); + assertEquals("a2", list.remove(0)); + assertEquals(1, listMap.size()); + assertEquals("a3", list.remove(0)); + assertEquals(0, listMap.size()); + assertFalse(listMap.containsKey("A")); + } + + @Test + @SuppressWarnings("unchecked") + public void testListValuedMapRemoveViaListIterator() { + final ListValuedMap listMap = makeObject(); + ListIterator listIt = listMap.get((K) "B").listIterator(); + listIt.add((V) "b1"); + listIt.add((V) "b2"); + assertEquals(2, listMap.size()); + assertTrue(listMap.containsKey("B")); + listIt = listMap.get((K) "B").listIterator(); + while (listIt.hasNext()) { + listIt.next(); + listIt.remove(); + } + assertFalse(listMap.containsKey("B")); + listIt.add((V) "b1"); + listIt.add((V) "b2"); + assertTrue(listMap.containsKey("B")); + assertEquals(2, listMap.get((K) "B").size()); + } + + @Test + public void testTrimToSize() { + final ArrayListValuedLinkedHashMap listMap = new ArrayListValuedLinkedHashMap<>(4); + + assertEquals("{}", listMap.toString()); + listMap.put((K) "A", (V) "W"); + listMap.put((K) "A", (V) "X"); + listMap.put((K) "B", (V) "F"); + assertEquals(2, listMap.get((K) "A").size()); + assertEquals(1, listMap.get((K) "B").size()); + + listMap.trimToSize(); + assertEquals(2, listMap.get((K) "A").size()); + assertEquals(1, listMap.get((K) "B").size()); + } + + @Test + public void testValuesListIteratorMethods() { + final ListValuedMap listMap = makeObject(); + final List listA = listMap.get((K) "A"); + final List list = Arrays.asList((V) "W", (V) "X", (V) "F", (V) "Q", (V) "Q", (V) "F"); + listA.addAll(0, list); + final ListIterator it = listMap.get((K) "A").listIterator(1); + assertTrue(it.hasNext()); + assertEquals("X", it.next()); + assertEquals("F", it.next()); + assertTrue(it.hasPrevious()); + assertEquals("F", it.previous()); + assertEquals(2, it.nextIndex()); + assertEquals(1, it.previousIndex()); + it.set((V) "Z"); + assertEquals("Z", it.next()); + assertEquals("Q", it.next()); + } + + @Test + public void testWrappedListAdd() { + final ListValuedMap listMap = makeObject(); + final List listA = listMap.get((K) "A"); + listA.add(0, (V) "W"); + listA.add(1, (V) "X"); + listA.add(2, (V) "F"); + assertEquals("{A=[W, X, F]}", listMap.toString()); + listMap.get((K) "A").set(1, (V) "Q"); + assertEquals("{A=[W, Q, F]}", listMap.toString()); + } + + @Test + public void testWrappedListAddAll() { + final ListValuedMap listMap = makeObject(); + final List listA = listMap.get((K) "A"); + final List list = Arrays.asList((V) "W", (V) "X", (V) "F"); + listA.addAll(0, list); + assertEquals("{A=[W, X, F]}", listMap.toString()); + + final List list1 = Arrays.asList((V) "Q", (V) "Q", (V) "L"); + listA.addAll(3, list1); + assertEquals("{A=[W, X, F, Q, Q, L]}", listMap.toString()); + assertEquals("W", listMap.get((K) "A").get(0)); + assertEquals("X", listMap.get((K) "A").get(1)); + assertEquals("F", listMap.get((K) "A").get(2)); + assertEquals("Q", listMap.get((K) "A").get(3)); + assertEquals("Q", listMap.get((K) "A").get(4)); + assertEquals("L", listMap.get((K) "A").get(5)); + assertEquals(0, listMap.get((K) "A").indexOf("W")); + assertEquals(2, listMap.get((K) "A").indexOf("F")); + assertEquals(-1, listMap.get((K) "A").indexOf("C")); + assertEquals(3, listMap.get((K) "A").indexOf("Q")); + assertEquals(4, listMap.get((K) "A").lastIndexOf("Q")); + assertEquals(-1, listMap.get((K) "A").lastIndexOf("A")); + + final List list2 = new ArrayList<>(); + listMap.get((K) "B").addAll(0, list2); + assertEquals("{A=[W, X, F, Q, Q, L]}", listMap.toString()); + final List list3 = listMap.get((K) "A").subList(1, 4); + assertEquals(3, list3.size()); + assertEquals("Q", list3.get(2)); + } + + @Test + public void testPreservesKeyInsertionOrder() { + final ListValuedMap map = makeObject(); + map.put((K) Integer.valueOf(5), (V) "five"); + map.put((K) Integer.valueOf(1), (V) "one"); + map.put((K) Integer.valueOf(5), (V) "vijf"); // "vijf" = "five" in Dutch + MapIterator mapIterator = map.mapIterator(); + assertEquals(5, mapIterator.next()); + assertEquals("five", mapIterator.getValue()); + assertEquals(5, mapIterator.next()); + assertEquals("vijf", mapIterator.getValue()); + assertEquals(1, mapIterator.next()); + assertEquals("one", mapIterator.getValue()); + assertFalse(mapIterator.hasNext()); + } + + @Test + public void testCopyConstructorWithMultiValuedMap() { + final ListValuedMap map = makeObject(); + map.put((K) "key", (V) "sleutel"); + final ListValuedMap copy = new ArrayListValuedLinkedHashMap<>(map); + assertEquals(map, copy); + } + +// @Test +// public void testCreate() throws Exception { +// writeExternalFormToDisk((java.io.Serializable) makeObject(), +// "src/test/resources/org/apache/commons/collections4/data/test/ArrayListValuedLinkedHashMap.emptyCollection.version4.5.obj"); +// writeExternalFormToDisk((java.io.Serializable) makeFullMap(), +// "src/test/resources/org/apache/commons/collections4/data/test/ArrayListValuedLinkedHashMap.fullCollection.version4.5.obj"); +// } + +} diff --git a/src/test/resources/org/apache/commons/collections4/data/test/ArrayListValuedLinkedHashMap.emptyCollection.version4.5.obj b/src/test/resources/org/apache/commons/collections4/data/test/ArrayListValuedLinkedHashMap.emptyCollection.version4.5.obj new file mode 100644 index 0000000000000000000000000000000000000000..1b2bd01f838ee7d3ba6b6e598e7354f98b0fbabe GIT binary patch literal 123 zcmZ4UmVvdnh`}|#C|xhHATc>3RWCU|H#a}87)a;jq$ZbS0@)^dxurQJnYoDtdX7a! ziIqN?#U){hIi;y7KACyhsVN?b#TmYd1q?vIXmYEJnSs%hK{zunvm`Sy2c*^+WOQap OWd%qHb2$r0VFduq5h(ot literal 0 HcmV?d00001 diff --git a/src/test/resources/org/apache/commons/collections4/data/test/ArrayListValuedLinkedHashMap.fullCollection.version4.5.obj b/src/test/resources/org/apache/commons/collections4/data/test/ArrayListValuedLinkedHashMap.fullCollection.version4.5.obj new file mode 100644 index 0000000000000000000000000000000000000000..7499484e035d62115e9a74a67dd4e2b2d57a2ed9 GIT binary patch literal 20893 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