COLLECTIONS-871 Added LinkedHashSetValuedLinkedHashMap

This commit is contained in:
Peter De Maeyer 2024-10-20 20:45:48 +02:00 committed by Gary Gregory
parent 064530e1e0
commit 2d0b85ac65
5 changed files with 352 additions and 2 deletions

View File

@ -0,0 +1,144 @@
/*
* 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.LinkedHashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.collections4.MultiValuedMap;
/**
* Implements a {@code SetValuedMap}, using a {@link LinkedHashMap} to provide data
* storage and {@link LinkedHashSet}s as value collections. This is the standard
* implementation of a SetValuedMap.
* <p>
* <strong>Note that LinkedHashSetValuedLinkedHashMap is not synchronized and is not
* thread-safe.</strong> 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.
* </p>
*
* @param <K> the type of the keys in this map
* @param <V> the type of the values in this map
* @since 4.5
*/
public class LinkedHashSetValuedLinkedHashMap<K, V> extends AbstractSetValuedMap<K, V>
implements Serializable {
/** Serialization Version */
private static final long serialVersionUID = 20241020L;
/**
* The initial map capacity used when none specified in constructor.
*/
private static final int DEFAULT_INITIAL_MAP_CAPACITY = 16;
/**
* The initial set capacity when using none specified in constructor.
*/
private static final int DEFAULT_INITIAL_SET_CAPACITY = 3;
/**
* The initial list capacity when creating a new value collection.
*/
private final int initialSetCapacity;
/**
* Creates an empty LinkedHashSetValuedHashMap with the default initial
* map capacity (16) and the default initial set capacity (3).
*/
public LinkedHashSetValuedLinkedHashMap() {
this(DEFAULT_INITIAL_MAP_CAPACITY, DEFAULT_INITIAL_SET_CAPACITY);
}
/**
* Creates an empty LinkedHashSetValuedHashMap with the default initial
* map capacity (16) and the specified initial set capacity.
*
* @param initialSetCapacity the initial capacity used for value collections
*/
public LinkedHashSetValuedLinkedHashMap(final int initialSetCapacity) {
this(DEFAULT_INITIAL_MAP_CAPACITY, initialSetCapacity);
}
/**
* Creates an empty LinkedHashSetValuedHashMap with the specified initial
* map and list capacities.
*
* @param initialMapCapacity the initial hashmap capacity
* @param initialSetCapacity the initial capacity used for value collections
*/
public LinkedHashSetValuedLinkedHashMap(final int initialMapCapacity, final int initialSetCapacity) {
super(new LinkedHashMap<>(initialMapCapacity));
this.initialSetCapacity = initialSetCapacity;
}
/**
* Creates an LinkedHashSetValuedHashMap copying all the mappings of the given map.
*
* @param map a {@code Map} to copy into this map
*/
public LinkedHashSetValuedLinkedHashMap(final Map<? extends K, ? extends V> map) {
this(map.size(), DEFAULT_INITIAL_SET_CAPACITY);
super.putAll(map);
}
/**
* Creates an LinkedHashSetValuedHashMap copying all the mappings of the given map.
*
* @param map a {@code MultiValuedMap} to copy into this map
*/
public LinkedHashSetValuedLinkedHashMap(final MultiValuedMap<? extends K, ? extends V> map) {
this(map.size(), DEFAULT_INITIAL_SET_CAPACITY);
super.putAll(map);
}
@Override
protected LinkedHashSet<V> createCollection() {
return new LinkedHashSet<>(initialSetCapacity);
}
/**
* 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);
}
/**
* 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);
}
}

View File

@ -193,9 +193,9 @@ public class HashSetValuedHashMapTest<K, V> extends AbstractMultiValuedMapTest<K
// public void testCreate() throws Exception {
// writeExternalFormToDisk((java.io.Serializable) makeObject(),
// "src/test/resources/data/test/HashSetValuedHashMap.emptyCollection.version4.1.obj");
// "src/test/resources/org/apache/commons/collections4/data/test/HashSetValuedHashMap.emptyCollection.version4.1.obj");
// writeExternalFormToDisk((java.io.Serializable) makeFullMap(),
// "src/test/resources/data/test/HashSetValuedHashMap.fullCollection.version4.1.obj");
// "src/test/resources/org/apache/commons/collections4/data/test/HashSetValuedHashMap.fullCollection.version4.1.obj");
// }
}

View File

@ -0,0 +1,206 @@
/*
* 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.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.SetValuedMap;
import org.apache.commons.collections4.collection.AbstractCollectionTest;
import org.junit.jupiter.api.Test;
/**
* Tests {@link LinkedHashSetValuedLinkedHashMap}.
*/
public class LinkedHashSetValuedLinkedHashMapTest<K, V> extends AbstractMultiValuedMapTest<K, V> {
public LinkedHashSetValuedLinkedHashMapTest() {
super(LinkedHashSetValuedLinkedHashMapTest.class.getSimpleName());
}
@Override
protected int getIterationBehaviour() {
return AbstractCollectionTest.UNORDERED;
}
@Override
public boolean isHashSetValue() {
return true;
}
@Override
public MultiValuedMap<K, V> makeConfirmedMap() {
return new LinkedHashSetValuedLinkedHashMap<>();
}
@Override
public SetValuedMap<K, V> makeObject() {
return new LinkedHashSetValuedLinkedHashMap<>();
}
@Override
public String getCompatibilityVersion() {
return "4.5"; // LinkedHashSetValuedLinkedHashMap was added in version 4.5
}
@Test
public void testLinkedHashSetValuedLinkedHashMap_2() {
final Map<K, V> map = new HashMap<>();
final SetValuedMap<K, V> map1;
final SetValuedMap<K, V> map2;
map.put((K) "A", (V) "W");
map.put((K) "B", (V) "X");
map.put((K) "C", (V) "F");
map1 = new LinkedHashSetValuedLinkedHashMap<>(map);
assertEquals(1, map1.get((K) "A").size());
map.remove("A");
map.remove("B");
map.remove("C");
map2 = new LinkedHashSetValuedLinkedHashMap<>(map);
assertEquals("{}", map2.toString());
}
@Test
public void testHashSetValueHashMap() {
final SetValuedMap<K, V> setMap = new LinkedHashSetValuedLinkedHashMap<>(4);
assertEquals(0, setMap.get((K) "whatever").size());
final Set<V> set = setMap.get((K) "A");
set.add((V) "W");
set.add((V) "X");
set.add((V) "F");
assertEquals(3, setMap.get((K) "A").size());
}
@Test
public void testHashSetValueHashMap_1() {
final MultiValuedMap<K, V> map = new ArrayListValuedHashMap<>();
final SetValuedMap<K, V> map1;
final SetValuedMap<K, V> map2 = makeObject();
final SetValuedMap<K, V> map3;
map.put((K) "A", (V) "W");
map.put((K) "A", (V) "X");
map.put((K) "A", (V) "F");
map1 = new LinkedHashSetValuedLinkedHashMap<>(map);
assertEquals(3, map1.get((K) "A").size());
map2.put((K) "A", (V) "X");
map2.put((K) "A", (V) "F");
map2.put((K) "A", (V) "W");
assertEquals(map1, map2);
assertEquals(map1.hashCode(), map2.hashCode());
map.remove("A");
map3 = new LinkedHashSetValuedLinkedHashMap<>(map);
assertEquals("{}", map3.toString());
}
@Test
@SuppressWarnings("unchecked")
public void testSetValuedMapAdd() {
final SetValuedMap<K, V> setMap = makeObject();
assertTrue(setMap.get((K) "whatever") instanceof Set);
final Set<V> set = setMap.get((K) "A");
assertTrue(set.add((V) "a1"));
assertTrue(set.add((V) "a2"));
assertFalse(set.add((V) "a1"));
assertEquals(2, setMap.size());
assertTrue(setMap.containsKey("A"));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void testSetValuedMapEqualsHashCodeContract() {
final SetValuedMap map1 = makeObject();
final SetValuedMap map2 = makeObject();
map1.put("a", "a1");
map1.put("a", "a2");
map2.put("a", "a2");
map2.put("a", "a1");
assertEquals(map1, map2);
assertEquals(map1.hashCode(), map2.hashCode());
map2.put("a", "a2");
assertEquals(map1, map2);
assertEquals(map1.hashCode(), map2.hashCode());
map2.put("a", "a3");
assertNotSame(map1, map2);
assertNotSame(map1.hashCode(), map2.hashCode());
}
@Test
@SuppressWarnings("unchecked")
public void testSetValuedMapRemove() {
final SetValuedMap<K, V> setMap = makeObject();
assertTrue(setMap.get((K) "whatever") instanceof Set);
final Set<V> set = setMap.get((K) "A");
assertTrue(set.add((V) "a1"));
assertTrue(set.add((V) "a2"));
assertFalse(set.add((V) "a1"));
assertEquals(2, setMap.size());
assertTrue(setMap.containsKey("A"));
assertTrue(set.remove("a1"));
assertTrue(set.remove("a2"));
assertFalse(set.remove("a1"));
assertEquals(0, setMap.size());
assertFalse(setMap.containsKey("A"));
}
@Test
@SuppressWarnings("unchecked")
public void testSetValuedMapRemoveViaIterator() {
final SetValuedMap<K, V> setMap = makeObject();
assertTrue(setMap.get((K) "whatever") instanceof Set);
final Set<V> set = setMap.get((K) "A");
set.add((V) "a1");
set.add((V) "a2");
set.add((V) "a1");
final Iterator<V> it = set.iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
assertEquals(0, setMap.size());
assertFalse(setMap.containsKey("A"));
}
// public void testCreate() throws Exception {
// writeExternalFormToDisk((java.io.Serializable) makeObject(),
// "src/test/resources/org/apache/commons/collections4/data/test/LinkedHashSetValuedLinkedHashMap.emptyCollection.version4.5.obj");
// writeExternalFormToDisk((java.io.Serializable) makeFullMap(),
// "src/test/resources/org/apache/commons/collections4/data/test/LinkedHashSetValuedLinkedHashMap.fullCollection.version4.5.obj");
// }
}