blob: c96edf81417f1d84b17f6ea151e3be69d17aa30c [file] [log] [blame]
/*
* 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.map;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.collections4.AbstractObjectTest;
import org.apache.commons.collections4.BulkTest;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.collection.AbstractCollectionTest;
import org.apache.commons.collections4.keyvalue.DefaultMapEntry;
import org.apache.commons.collections4.set.AbstractSetTest;
import org.junit.jupiter.api.Test;
/**
* Abstract test class for {@link java.util.Map} methods and contracts.
* <p>
* The forces at work here are similar to those in {@link AbstractCollectionTest}.
* If your class implements the full Map interface, including optional
* operations, simply extend this class, and implement the
* {@link #makeObject()} method.
* <p>
* On the other hand, if your map implementation is weird, you may have to
* override one or more of the other protected methods. They're described
* below.
* <p>
* <b>Entry Population Methods</b>
* <p>
* Override these methods if your map requires special entries:
*
* <ul>
* <li>{@link #getSampleKeys()}
* <li>{@link #getSampleValues()}
* <li>{@link #getNewSampleValues()}
* <li>{@link #getOtherKeys()}
* <li>{@link #getOtherValues()}
* </ul>
*
* <b>Indicate Map Behaviour</b>
* <p>
* Override these if your map makes specific behaviour guarantees:
* <ul>
* <li>{@link #getIterationBehaviour()}</li>
* </ul>
*
* <b>Supported Operation Methods</b>
* <p>
* Override these methods if your map doesn't support certain operations:
*
* <ul>
* <li> {@link #isPutAddSupported()}
* <li> {@link #isPutChangeSupported()}
* <li> {@link #isSetValueSupported()}
* <li> {@link #isRemoveSupported()}
* <li> {@link #isGetStructuralModify()}
* <li> {@link #isAllowDuplicateValues()}
* <li> {@link #isAllowNullKey()}
* <li> {@link #isAllowNullValue()}
* </ul>
*
* <b>Fixture Methods</b>
* <p>
* For tests on modification operations (puts and removes), fixtures are used
* to verify that that operation results in correct state for the map and its
* collection views. Basically, the modification is performed against your
* map implementation, and an identical modification is performed against
* a <I>confirmed</I> map implementation. A confirmed map implementation is
* something like <Code>java.util.HashMap</Code>, which is known to conform
* exactly to the {@link Map} contract. After the modification takes place
* on both your map implementation and the confirmed map implementation, the
* two maps are compared to see if their state is identical. The comparison
* also compares the collection views to make sure they're still the same.<P>
*
* The upshot of all that is that <I>any</I> test that modifies the map in
* <I>any</I> way will verify that <I>all</I> of the map's state is still
* correct, including the state of its collection views. So for instance
* if a key is removed by the map's key set's iterator, then the entry set
* is checked to make sure the key/value pair no longer appears.<P>
*
* The {@link #map} field holds an instance of your collection implementation.
* The {@link #entrySet}, {@link #keySet} and {@link #values} fields hold
* that map's collection views. And the {@link #confirmed} field holds
* an instance of the confirmed collection implementation. The
* {@link #resetEmpty()} and {@link #resetFull()} methods set these fields to
* empty or full maps, so that tests can proceed from a known state.<P>
*
* After a modification operation to both {@link #map} and {@link #confirmed},
* the {@link #verify()} method is invoked to compare the results. The
* {@link #verify} method calls separate methods to verify the map and its three
* collection views ({@link #verifyMap}, {@link #verifyEntrySet},
* {@link #verifyKeySet}, and {@link #verifyValues}). You may want to override
* one of the verification methods to perform additional verifications. For
* instance, TestDoubleOrderedMap would want override its
* {@link #verifyValues()} method to verify that the values are unique and in
* ascending order.<P>
*
* <b>Other Notes</b>
* <p>
* If your {@link Map} fails one of these tests by design, you may still use
* this base set of cases. Simply override the test case (method) your map
* fails and/or the methods that define the assumptions used by the test
* cases. For example, if your map does not allow duplicate values, override
* {@link #isAllowDuplicateValues()} and have it return {@code false}
*/
public abstract class AbstractMapTest<K, V> extends AbstractObjectTest {
/**
* JDK1.2 has bugs in null handling of Maps, especially HashMap.Entry.toString
* This avoids nulls for JDK1.2
*/
private static final boolean JDK12;
static {
final String str = System.getProperty("java.version");
JDK12 = str.startsWith("1.2");
}
// These instance variables are initialized with the reset method.
// Tests for map methods that alter the map (put, putAll, remove)
// first call reset() to create the map and its views; then perform
// the modification on the map; perform the same modification on the
// confirmed; and then call verify() to ensure that the map is equal
// to the confirmed, that the already-constructed collection views
// are still equal to the confirmed's collection views.
/** Map created by reset(). */
protected Map<K, V> map;
/** Entry set of map created by reset(). */
protected Set<Map.Entry<K, V>> entrySet;
/** Key set of map created by reset(). */
protected Set<K> keySet;
/** Values collection of map created by reset(). */
protected Collection<V> values;
/** HashMap created by reset(). */
protected Map<K, V> confirmed;
/**
* JUnit constructor.
*
* @param testName the test name
*/
public AbstractMapTest(final String testName) {
super(testName);
}
/**
* Returns true if the maps produced by
* {@link #makeObject()} and {@link #makeFullMap()}
* support the {@code put} and {@code putAll} operations
* adding new mappings.
* <p>
* Default implementation returns true.
* Override if your collection class does not support put adding.
*/
public boolean isPutAddSupported() {
return true;
}
/**
* Returns true if the maps produced by
* {@link #makeObject()} and {@link #makeFullMap()}
* support the {@code put} and {@code putAll} operations
* changing existing mappings.
* <p>
* Default implementation returns true.
* Override if your collection class does not support put changing.
*/
public boolean isPutChangeSupported() {
return true;
}
/**
* Returns true if the maps produced by
* {@link #makeObject()} and {@link #makeFullMap()}
* support the {@code setValue} operation on entrySet entries.
* <p>
* Default implementation returns isPutChangeSupported().
* Override if your collection class does not support setValue but does
* support put changing.
*/
public boolean isSetValueSupported() {
return isPutChangeSupported();
}
/**
* Returns true if the maps produced by
* {@link #makeObject()} and {@link #makeFullMap()}
* support the {@code remove} and {@code clear} operations.
* <p>
* Default implementation returns true.
* Override if your collection class does not support removal operations.
*/
public boolean isRemoveSupported() {
return true;
}
/**
* Returns true if the maps produced by
* {@link #makeObject()} and {@link #makeFullMap()}
* can cause structural modification on a get(). The example is LRUMap.
* <p>
* Default implementation returns false.
* Override if your map class structurally modifies on get.
*/
public boolean isGetStructuralModify() {
return false;
}
/**
* Returns whether the sub map views of SortedMap are serializable.
* If the class being tested is based around a TreeMap then you should
* override and return false as TreeMap has a bug in deserialization.
*
* @return false
*/
public boolean isSubMapViewsSerializable() {
return true;
}
/**
* Returns true if the maps produced by
* {@link #makeObject()} and {@link #makeFullMap()}
* supports null keys.
* <p>
* Default implementation returns true.
* Override if your collection class does not support null keys.
*/
public boolean isAllowNullKey() {
return true;
}
/**
* Returns true if the maps produced by
* {@link #makeObject()} and {@link #makeFullMap()}
* supports null values.
* <p>
* Default implementation returns true.
* Override if your collection class does not support null values.
*/
public boolean isAllowNullValue() {
return true;
}
/**
* Returns true if the maps produced by
* {@link #makeObject()} and {@link #makeFullMap()}
* supports duplicate values.
* <p>
* Default implementation returns true.
* Override if your collection class does not support duplicate values.
*/
public boolean isAllowDuplicateValues() {
return true;
}
/**
* Returns true if the maps produced by
* {@link #makeObject()} and {@link #makeFullMap()}
* provide fail-fast behavior on their various iterators.
* <p>
* Default implementation returns true.
* Override if your collection class does not support fast failure.
*/
public boolean isFailFastExpected() {
return true;
}
public boolean areEqualElementsDistinguishable() {
return false;
}
/**
* Returns the set of keys in the mappings used to test the map. This
* method must return an array with the same length as {@link
* #getSampleValues()} and all array elements must be different. The
* default implementation constructs a set of String keys, and includes a
* single null key if {@link #isAllowNullKey()} returns {@code true}.
*/
@SuppressWarnings("unchecked")
public K[] getSampleKeys() {
final Object[] result = {
"blah", "foo", "bar", "baz", "tmp", "gosh", "golly", "gee",
"hello", "goodbye", "we'll", "see", "you", "all", "again",
"key",
"key2",
isAllowNullKey() && !JDK12 ? null : "nonnullkey"
};
return (K[]) result;
}
@SuppressWarnings("unchecked")
public K[] getOtherKeys() {
return (K[]) getOtherNonNullStringElements();
}
@SuppressWarnings("unchecked")
public V[] getOtherValues() {
return (V[]) getOtherNonNullStringElements();
}
@SuppressWarnings("unchecked")
protected <E> List<E> getAsList(final Object[] o) {
final ArrayList<E> result = new ArrayList<>();
for (final Object element : o) {
result.add((E) element);
}
return result;
}
/**
* Returns a list of string elements suitable for return by
* {@link #getOtherKeys()} or {@link #getOtherValues}.
*
* <p>Override getOtherElements to return the results of this method if your
* collection does not support heterogenous elements or the null element.
* </p>
*/
public Object[] getOtherNonNullStringElements() {
return new Object[] {
"For", "then", "despite", /* of */"space", "I", "would", "be", "brought",
"From", "limits", "far", "remote", "where", "thou", "dost", "stay"
};
}
/**
* Returns the set of values in the mappings used to test the map. This
* method must return an array with the same length as
* {@link #getSampleKeys()}. The default implementation constructs a set of
* String values and includes a single null value if
* {@link #isAllowNullValue()} returns {@code true}, and includes
* two values that are the same if {@link #isAllowDuplicateValues()} returns
* {@code true}.
*/
@SuppressWarnings("unchecked")
public V[] getSampleValues() {
final Object[] result = {
"blahv", "foov", "barv", "bazv", "tmpv", "goshv", "gollyv", "geev",
"hellov", "goodbyev", "we'llv", "seev", "youv", "allv", "againv",
isAllowNullValue() && !JDK12 ? null : "nonnullvalue",
"value",
isAllowDuplicateValues() ? "value" : "value2",
};
return (V[]) result;
}
/**
* Returns a the set of values that can be used to replace the values
* returned from {@link #getSampleValues()}. This method must return an
* array with the same length as {@link #getSampleValues()}. The values
* returned from this method should not be the same as those returned from
* {@link #getSampleValues()}. The default implementation constructs a
* set of String values and includes a single null value if
* {@link #isAllowNullValue()} returns {@code true}, and includes two values
* that are the same if {@link #isAllowDuplicateValues()} returns
* {@code true}.
*/
@SuppressWarnings("unchecked")
public V[] getNewSampleValues() {
final Object[] result = {
isAllowNullValue() && !JDK12 && isAllowDuplicateValues() ? null : "newnonnullvalue",
"newvalue",
isAllowDuplicateValues() ? "newvalue" : "newvalue2",
"newblahv", "newfoov", "newbarv", "newbazv", "newtmpv", "newgoshv",
"newgollyv", "newgeev", "newhellov", "newgoodbyev", "newwe'llv",
"newseev", "newyouv", "newallv", "newagainv",
};
return (V[]) result;
}
/**
* Helper method to add all the mappings described by
* {@link #getSampleKeys()} and {@link #getSampleValues()}.
*/
public void addSampleMappings(final Map<? super K, ? super V> m) {
final K[] keys = getSampleKeys();
final V[] values = getSampleValues();
for (int i = 0; i < keys.length; i++) {
try {
m.put(keys[i], values[i]);
} catch (final NullPointerException exception) {
assertTrue("NullPointerException only allowed to be thrown " +
"if either the key or value is null.",
keys[i] == null || values[i] == null);
assertTrue("NullPointerException on null key, but " +
"isAllowNullKey is not overridden to return false.",
keys[i] == null || !isAllowNullKey());
assertTrue("NullPointerException on null value, but " +
"isAllowNullValue is not overridden to return false.",
values[i] == null || !isAllowNullValue());
fail("Unknown reason for NullPointer.");
}
}
assertEquals("size must reflect number of mappings added.",
keys.length, m.size());
}
/**
* Return a new, empty {@link Map} to be used for testing.
*
* @return the map to be tested
*/
@Override
public abstract Map<K, V> makeObject();
/**
* Return a new, populated map. The mappings in the map should match the
* keys and values returned from {@link #getSampleKeys()} and
* {@link #getSampleValues()}. The default implementation uses makeEmptyMap()
* and calls {@link #addSampleMappings} to add all the mappings to the
* map.
*
* @return the map to be tested
*/
public Map<K, V> makeFullMap() {
final Map<K, V> m = makeObject();
addSampleMappings(m);
return m;
}
/**
* Override to return a map other than HashMap as the confirmed map.
*
* @return a map that is known to be valid
*/
public Map<K, V> makeConfirmedMap() {
return new HashMap<>();
}
/**
* Creates a new Map Entry that is independent of the first and the map.
*/
public static <K, V> Map.Entry<K, V> cloneMapEntry(final Map.Entry<K, V> entry) {
final HashMap<K, V> map = new HashMap<>();
map.put(entry.getKey(), entry.getValue());
return map.entrySet().iterator().next();
}
/**
* Gets the compatibility version, needed for package access.
*/
@Override
public String getCompatibilityVersion() {
return super.getCompatibilityVersion();
}
/**
* Test to ensure the test setup is working properly. This method checks
* to ensure that the getSampleKeys and getSampleValues methods are
* returning results that look appropriate. That is, they both return a
* non-null array of equal length. The keys array must not have any
* duplicate values, and may only contain a (single) null key if
* isNullKeySupported() returns true. The values array must only have a null
* value if useNullValue() is true and may only have duplicate values if
* isAllowDuplicateValues() returns true.
*/
@Test
public void testSampleMappings() {
final Object[] keys = getSampleKeys();
final Object[] values = getSampleValues();
final Object[] newValues = getNewSampleValues();
assertNotNull("failure in test: Must have keys returned from " +
"getSampleKeys.", keys);
assertNotNull("failure in test: Must have values returned from " +
"getSampleValues.", values);
// verify keys and values have equivalent lengths (in case getSampleX are
// overridden)
assertEquals("failure in test: not the same number of sample " +
"keys and values.", keys.length, values.length);
assertEquals("failure in test: not the same number of values and new values.",
values.length, newValues.length);
// verify there aren't duplicate keys, and check values
for (int i = 0; i < keys.length - 1; i++) {
for (int j = i + 1; j < keys.length; j++) {
assertTrue("failure in test: duplicate null keys.",
keys[i] != null || keys[j] != null);
assertTrue(
"failure in test: duplicate non-null key.",
keys[i] == null || keys[j] == null || !keys[i].equals(keys[j]) && !keys[j]
.equals(keys[i]));
}
assertTrue("failure in test: found null key, but isNullKeySupported " + "is false.",
keys[i] != null || isAllowNullKey());
assertTrue(
"failure in test: found null value, but isNullValueSupported " + "is false.",
values[i] != null || isAllowNullValue());
assertTrue("failure in test: found null new value, but isNullValueSupported "
+ "is false.", newValues[i] != null || isAllowNullValue());
assertTrue("failure in test: values should not be the same as new value",
values[i] != newValues[i]
&& (values[i] == null || !values[i].equals(newValues[i])));
}
}
/**
* Return a flag specifying the iteration behaviour of the collection.
* This is used to change the assertions used by specific tests.
* The default implementation returns 0 which indicates ordered iteration behaviour.
*
* @return the iteration behaviour
* @see AbstractCollectionTest#UNORDERED
*/
protected int getIterationBehaviour(){
return 0;
}
// tests begin here. Each test adds a little bit of tested functionality.
// Many methods assume previous methods passed. That is, they do not
// exhaustively recheck things that have already been checked in a previous
// test methods.
/**
* Test to ensure that makeEmptyMap and makeFull returns a new non-null
* map with each invocation.
*/
@Test
public void testMakeMap() {
final Map<K, V> em = makeObject();
assertNotNull("failure in test: makeEmptyMap must return a non-null map.", em);
final Map<K, V> em2 = makeObject();
assertNotNull("failure in test: makeEmptyMap must return a non-null map.", em);
assertNotSame("failure in test: makeEmptyMap must return a new map " +
"with each invocation.", em, em2);
final Map<K, V> fm = makeFullMap();
assertNotNull("failure in test: makeFullMap must return a non-null map.", fm);
final Map<K, V> fm2 = makeFullMap();
assertNotNull("failure in test: makeFullMap must return a non-null map.", fm);
assertNotSame("failure in test: makeFullMap must return a new map " +
"with each invocation.", fm, fm2);
}
/**
* Tests Map.isEmpty()
*/
@Test
public void testMapIsEmpty() {
resetEmpty();
assertTrue("Map.isEmpty() should return true with an empty map", getMap().isEmpty());
verify();
resetFull();
assertFalse("Map.isEmpty() should return false with a non-empty map", getMap().isEmpty());
verify();
}
/**
* Tests Map.size()
*/
@Test
public void testMapSize() {
resetEmpty();
assertEquals("Map.size() should be 0 with an empty map",
0, getMap().size());
verify();
resetFull();
assertEquals("Map.size() should equal the number of entries " +
"in the map", getSampleKeys().length, getMap().size());
verify();
}
/**
* Tests {@link Map#clear()}. If the map {@link #isRemoveSupported()}
* can add and remove elements}, then {@link Map#size()} and
* {@link Map#isEmpty()} are used to ensure that map has no elements after
* a call to clear. If the map does not support adding and removing
* elements, this method checks to ensure clear throws an
* UnsupportedOperationException.
*/
@Test
public void testMapClear() {
if (!isRemoveSupported()) {
resetFull();
assertThrows(UnsupportedOperationException.class, () -> getMap().clear(),
"Expected UnsupportedOperationException on clear");
return;
}
resetEmpty();
getMap().clear();
getConfirmed().clear();
verify();
resetFull();
getMap().clear();
getConfirmed().clear();
verify();
}
/**
* Tests Map.containsKey(Object) by verifying it returns false for all
* sample keys on a map created using an empty map and returns true for
* all sample keys returned on a full map.
*/
@Test
public void testMapContainsKey() {
final Object[] keys = getSampleKeys();
resetEmpty();
for (final Object key : keys) {
assertFalse("Map must not contain key when map is empty", getMap().containsKey(key));
}
verify();
resetFull();
for (final Object key : keys) {
assertTrue("Map must contain key for a mapping in the map. " +
"Missing: " + key, getMap().containsKey(key));
}
verify();
}
/**
* Tests Map.containsValue(Object) by verifying it returns false for all
* sample values on an empty map and returns true for all sample values on
* a full map.
*/
@Test
public void testMapContainsValue() {
final Object[] values = getSampleValues();
resetEmpty();
for (final Object value : values) {
assertFalse("Empty map must not contain value", getMap().containsValue(value));
}
verify();
resetFull();
for (final Object value : values) {
assertTrue("Map must contain value for a mapping in the map.",
getMap().containsValue(value));
}
verify();
}
/**
* Tests Map.equals(Object)
*/
@Test
public void testMapEquals() {
resetEmpty();
assertEquals("Empty maps unequal.", getMap(), confirmed);
verify();
resetFull();
assertEquals("Full maps unequal.", getMap(), confirmed);
verify();
resetFull();
// modify the HashMap created from the full map and make sure this
// change results in map.equals() to return false.
final Iterator<K> iter = confirmed.keySet().iterator();
iter.next();
iter.remove();
assertFalse("Different maps equal.", getMap().equals(confirmed));
resetFull();
assertFalse("equals(null) returned true.", getMap().equals(null));
assertFalse("equals(new Object()) returned true.", getMap().equals(new Object()));
verify();
}
/**
* Tests Map.get(Object)
*/
@Test
public void testMapGet() {
resetEmpty();
final Object[] keys = getSampleKeys();
final Object[] values = getSampleValues();
for (final Object key : keys) {
assertNull("Empty map.get() should return null.", getMap().get(key));
}
verify();
resetFull();
for (int i = 0; i < keys.length; i++) {
assertEquals("Full map.get() should return value from mapping.",
values[i], getMap().get(keys[i]));
}
}
/**
* Tests Map.hashCode()
*/
@Test
public void testMapHashCode() {
resetEmpty();
assertEquals("Empty maps have different hashCodes.", getMap().hashCode(), confirmed.hashCode());
resetFull();
assertEquals("Equal maps have different hashCodes.", getMap().hashCode(), confirmed.hashCode());
}
/**
* Tests Map.toString(). Since the format of the string returned by the
* toString() method is not defined in the Map interface, there is no
* common way to test the results of the toString() method. Therefore,
* it is encouraged that Map implementations override this test with one
* that checks the format matches any format defined in its API. This
* default implementation just verifies that the toString() method does
* not return null.
*/
@Test
public void testMapToString() {
resetEmpty();
assertNotNull("Empty map toString() should not return null", getMap().toString());
verify();
resetFull();
assertNotNull("Empty map toString() should not return null", getMap().toString());
verify();
}
/**
* Compare the current serialized form of the Map
* against the canonical version in SCM.
*/
@Test
public void testEmptyMapCompatibility() throws Exception {
/*
* Create canonical objects with this code
Map map = makeEmptyMap();
if (!(map instanceof Serializable)) return;
writeExternalFormToDisk((Serializable) map, getCanonicalEmptyCollectionName(map));
*/
// test to make sure the canonical form has been preserved
final Map<K, V> map = makeObject();
if (map instanceof Serializable && !skipSerializedCanonicalTests() && isTestSerialization()) {
@SuppressWarnings("unchecked")
final Map<K, V> map2 = (Map<K, V>) readExternalFormFromDisk(getCanonicalEmptyCollectionName(map));
assertEquals("Map is empty", 0, map2.size());
}
}
/**
* Compare the current serialized form of the Map
* against the canonical version in SCM.
*/
@Test
public void testFullMapCompatibility() throws Exception {
/*
* Create canonical objects with this code
Map map = makeFullMap();
if (!(map instanceof Serializable)) return;
writeExternalFormToDisk((Serializable) map, getCanonicalFullCollectionName(map));
*/
// test to make sure the canonical form has been preserved
final Map<K, V> map = makeFullMap();
if (map instanceof Serializable && !skipSerializedCanonicalTests() && isTestSerialization()) {
@SuppressWarnings("unchecked")
final Map<K, V> map2 = (Map<K, V>) readExternalFormFromDisk(getCanonicalFullCollectionName(map));
assertEquals("Map is the right size", getSampleKeys().length, map2.size());
}
}
/**
* Tests Map.put(Object, Object)
*/
@Test
public void testMapPut() {
resetEmpty();
final K[] keys = getSampleKeys();
final V[] values = getSampleValues();
final V[] newValues = getNewSampleValues();
if (isPutAddSupported()) {
for (int i = 0; i < keys.length; i++) {
final Object o = getMap().put(keys[i], values[i]);
getConfirmed().put(keys[i], values[i]);
verify();
assertNull("First map.put should return null", o);
assertTrue("Map should contain key after put",
getMap().containsKey(keys[i]));
assertTrue("Map should contain value after put",
getMap().containsValue(values[i]));
}
if (isPutChangeSupported()) {
for (int i = 0; i < keys.length; i++) {
final Object o = getMap().put(keys[i], newValues[i]);
getConfirmed().put(keys[i], newValues[i]);
verify();
assertEquals("Map.put should return previous value when changed", values[i], o);
assertTrue("Map should still contain key after put when changed",
getMap().containsKey(keys[i]));
assertTrue("Map should contain new value after put when changed",
getMap().containsValue(newValues[i]));
// if duplicates are allowed, we're not guaranteed that the value
// no longer exists, so don't try checking that.
if (!isAllowDuplicateValues()) {
assertFalse("Map should not contain old value after put when changed", getMap().containsValue(values[i]));
}
}
} else {
try {
// two possible exception here, either valid
getMap().put(keys[0], newValues[0]);
fail("Expected IllegalArgumentException or UnsupportedOperationException on put (change)");
} catch (final IllegalArgumentException | UnsupportedOperationException ex) {
// ignore
}
}
} else if (isPutChangeSupported()) {
resetEmpty();
try {
getMap().put(keys[0], values[0]);
fail("Expected UnsupportedOperationException or IllegalArgumentException on put (add) when fixed size");
} catch (final IllegalArgumentException | UnsupportedOperationException ex) {
// ignore
}
resetFull();
int i = 0;
for (final Iterator<K> it = getMap().keySet().iterator(); it.hasNext() && i < newValues.length; i++) {
final K key = it.next();
final V o = getMap().put(key, newValues[i]);
final V value = getConfirmed().put(key, newValues[i]);
verify();
assertEquals("Map.put should return previous value when changed", value, o);
assertTrue("Map should still contain key after put when changed", getMap()
.containsKey(key));
assertTrue("Map should contain new value after put when changed", getMap()
.containsValue(newValues[i]));
// if duplicates are allowed, we're not guaranteed that the value
// no longer exists, so don't try checking that.
if (!isAllowDuplicateValues()) {
assertFalse("Map should not contain old value after put when changed", getMap().containsValue(values[i]));
}
}
} else {
assertThrows(UnsupportedOperationException.class, () -> getMap().put(keys[0], values[0]),
"Expected UnsupportedOperationException on put (add)");
}
}
/**
* Tests Map.put(null, value)
*/
@Test
public void testMapPutNullKey() {
resetFull();
final V[] values = getSampleValues();
if (isPutAddSupported()) {
if (isAllowNullKey()) {
getMap().put(null, values[0]);
} else {
try {
getMap().put(null, values[0]);
fail("put(null, value) should throw NPE/IAE");
} catch (final NullPointerException | IllegalArgumentException ex) {}
}
}
}
/**
* Tests Map.put(null, value)
*/
@Test
public void testMapPutNullValue() {
resetFull();
final K[] keys = getSampleKeys();
if (isPutAddSupported()) {
if (isAllowNullValue()) {
getMap().put(keys[0], null);
} else {
try {
getMap().put(keys[0], null);
fail("put(key, null) should throw NPE/IAE");
} catch (final NullPointerException | IllegalArgumentException ex) {}
}
}
}
/**
* Tests Map.putAll(map)
*/
@Test
public void testMapPutAll() {
if (!isPutAddSupported()) {
if (!isPutChangeSupported()) {
final Map<K, V> temp = makeFullMap();
resetEmpty();
assertThrows(UnsupportedOperationException.class, () -> getMap().putAll(temp),
"Expected UnsupportedOperationException on putAll");
}
return;
}
// check putAll OK adding empty map to empty map
resetEmpty();
assertEquals(0, getMap().size());
getMap().putAll(new HashMap<K, V>());
assertEquals(0, getMap().size());
// check putAll OK adding empty map to non-empty map
resetFull();
final int size = getMap().size();
getMap().putAll(new HashMap<K, V>());
assertEquals(size, getMap().size());
// check putAll OK adding non-empty map to empty map
resetEmpty();
Map<K, V> m2 = makeFullMap();
getMap().putAll(m2);
getConfirmed().putAll(m2);
verify();
// check putAll OK adding non-empty JDK map to empty map
resetEmpty();
m2 = makeConfirmedMap();
final K[] keys = getSampleKeys();
final V[] values = getSampleValues();
for (int i = 0; i < keys.length; i++) {
m2.put(keys[i], values[i]);
}
getMap().putAll(m2);
getConfirmed().putAll(m2);
verify();
// check putAll OK adding non-empty JDK map to non-empty map
resetEmpty();
m2 = makeConfirmedMap();
getMap().put(keys[0], values[0]);
getConfirmed().put(keys[0], values[0]);
verify();
for (int i = 1; i < keys.length; i++) {
m2.put(keys[i], values[i]);
}
getMap().putAll(m2);
getConfirmed().putAll(m2);
verify();
}
/**
* Tests Map.remove(Object)
*/
@Test
public void testMapRemove() {
if (!isRemoveSupported()) {
resetFull();
assertThrows(UnsupportedOperationException.class, () -> getMap().remove(getMap().keySet().iterator().next()),
"Expected UnsupportedOperationException on remove");
return;
}
resetEmpty();
final Object[] keys = getSampleKeys();
final Object[] values = getSampleValues();
for (final Object key : keys) {
final Object o = getMap().remove(key);
assertNull("First map.remove should return null", o);
}
verify();
resetFull();
for (int i = 0; i < keys.length; i++) {
final Object o = getMap().remove(keys[i]);
getConfirmed().remove(keys[i]);
verify();
assertEquals("map.remove with valid key should return value",
values[i], o);
}
final Object[] other = getOtherKeys();
resetFull();
final int size = getMap().size();
for (final Object element : other) {
final Object o = getMap().remove(element);
assertNull("map.remove for nonexistent key should return null", o);
assertEquals("map.remove for nonexistent key should not " +
"shrink map", size, getMap().size());
}
verify();
}
/**
* Tests that the {@link Map#bitMaps} collection is backed by
* the underlying map for clear().
*/
@Test
public void testValuesClearChangesMap() {
if (!isRemoveSupported()) {
return;
}
// clear values, reflected in map
resetFull();
Collection<V> values = getMap().values();
assertFalse(getMap().isEmpty());
assertFalse(values.isEmpty());
values.clear();
assertTrue(getMap().isEmpty());
assertTrue(values.isEmpty());
// clear map, reflected in values
resetFull();
values = getMap().values();
assertFalse(getMap().isEmpty());
assertFalse(values.isEmpty());
getMap().clear();
assertTrue(getMap().isEmpty());
assertTrue(values.isEmpty());
}
/**
* Tests that the {@link Map#keySet} collection is backed by
* the underlying map for clear().
*/
@Test
public void testKeySetClearChangesMap() {
if (!isRemoveSupported()) {
return;
}
// clear values, reflected in map
resetFull();
Set<K> keySet = getMap().keySet();
assertFalse(getMap().isEmpty());
assertFalse(keySet.isEmpty());
keySet.clear();
assertTrue(getMap().isEmpty());
assertTrue(keySet.isEmpty());
// clear map, reflected in values
resetFull();
keySet = getMap().keySet();
assertFalse(getMap().isEmpty());
assertFalse(keySet.isEmpty());
getMap().clear();
assertTrue(getMap().isEmpty());
assertTrue(keySet.isEmpty());
}
/**
* Tests that the {@link Map#entrySet()} collection is backed by
* the underlying map for clear().
*/
@Test
public void testEntrySetClearChangesMap() {
if (!isRemoveSupported()) {
return;
}
// clear values, reflected in map
resetFull();
Set<Map.Entry<K, V>> entrySet = getMap().entrySet();
assertFalse(getMap().isEmpty());
assertFalse(entrySet.isEmpty());
entrySet.clear();
assertTrue(getMap().isEmpty());
assertTrue(entrySet.isEmpty());
// clear map, reflected in values
resetFull();
entrySet = getMap().entrySet();
assertFalse(getMap().isEmpty());
assertFalse(entrySet.isEmpty());
getMap().clear();
assertTrue(getMap().isEmpty());
assertTrue(entrySet.isEmpty());
}
@Test
public void testEntrySetContains1() {
resetFull();
final Set<Map.Entry<K, V>> entrySet = getMap().entrySet();
final Map.Entry<K, V> entry = entrySet.iterator().next();
assertTrue(entrySet.contains(entry));
}
@Test
public void testEntrySetContains2() {
resetFull();
final Set<Map.Entry<K, V>> entrySet = getMap().entrySet();
final Map.Entry<K, V> entry = entrySet.iterator().next();
final Map.Entry<K, V> test = cloneMapEntry(entry);
assertTrue(entrySet.contains(test));
}
@Test
@SuppressWarnings("unchecked")
public void testEntrySetContains3() {
resetFull();
final Set<Map.Entry<K, V>> entrySet = getMap().entrySet();
final Map.Entry<K, V> entry = entrySet.iterator().next();
final HashMap<K, V> temp = new HashMap<>();
temp.put(entry.getKey(), (V) "A VERY DIFFERENT VALUE");
final Map.Entry<K, V> test = temp.entrySet().iterator().next();
assertFalse(entrySet.contains(test));
}
@Test
public void testEntrySetRemove1() {
if (!isRemoveSupported()) {
return;
}
resetFull();
final int size = getMap().size();
final Set<Map.Entry<K, V>> entrySet = getMap().entrySet();
final Map.Entry<K, V> entry = entrySet.iterator().next();
final K key = entry.getKey();
assertTrue(entrySet.remove(entry));
assertFalse(getMap().containsKey(key));
assertEquals(size - 1, getMap().size());
}
@Test
public void testEntrySetRemove2() {
if (!isRemoveSupported()) {
return;
}
resetFull();
final int size = getMap().size();
final Set<Map.Entry<K, V>> entrySet = getMap().entrySet();
final Map.Entry<K, V> entry = entrySet.iterator().next();
final K key = entry.getKey();
final Map.Entry<K, V> test = cloneMapEntry(entry);
assertTrue(entrySet.remove(test));
assertFalse(getMap().containsKey(key));
assertEquals(size - 1, getMap().size());
}
@Test
@SuppressWarnings("unchecked")
public void testEntrySetRemove3() {
if (!isRemoveSupported()) {
return;
}
resetFull();
final int size = getMap().size();
final Set<Map.Entry<K, V>> entrySet = getMap().entrySet();
final Map.Entry<K, V> entry = entrySet.iterator().next();
final K key = entry.getKey();
final HashMap<K, V> temp = new HashMap<>();
temp.put(entry.getKey(), (V) "A VERY DIFFERENT VALUE");
final Map.Entry<K, V> test = temp.entrySet().iterator().next();
assertFalse(entrySet.remove(test));
assertTrue(getMap().containsKey(key));
assertEquals(size, getMap().size());
}
/**
* Tests that the {@link Map#bitMaps} collection is backed by
* the underlying map by removing from the values collection
* and testing if the value was removed from the map.
* <p>
* We should really test the "vice versa" case--that values removed
* from the map are removed from the values collection--also,
* but that's a more difficult test to construct (lacking a
* "removeValue" method.)
* </p>
* <p>
* See bug <a href="http://issues.apache.org/bugzilla/show_bug.cgi?id=9573">
* 9573</a>.
* </p>
*/
@Test
public void testValuesRemoveChangesMap() {
resetFull();
final V[] sampleValues = getSampleValues();
final Collection<V> values = getMap().values();
for (final V sampleValue : sampleValues) {
if (map.containsValue(sampleValue)) {
int j = 0; // loop counter prevents infinite loops when remove is broken
while (values.contains(sampleValue) && j < 10000) {
try {
values.remove(sampleValue);
} catch (final UnsupportedOperationException e) {
// if values.remove is unsupported, just skip this test
return;
}
j++;
}
assertTrue("values().remove(obj) is broken", j < 10000);
assertFalse("Value should have been removed from the underlying map.", getMap().containsValue(sampleValue));
}
}
}
/**
* Tests values.removeAll.
*/
@Test
public void testValuesRemoveAll() {
resetFull();
final Collection<V> values = getMap().values();
final List<V> sampleValuesAsList = Arrays.asList(getSampleValues());
if (!values.equals(sampleValuesAsList)) {
return;
}
try {
assertFalse(values.removeAll(Collections.<V>emptySet()));
} catch (final UnsupportedOperationException e) {
// if values.removeAll is unsupported, just skip this test
return;
}
assertEquals(sampleValuesAsList.size(), getMap().size());
try {
assertTrue(values.removeAll(sampleValuesAsList));
} catch (final UnsupportedOperationException e) {
// if values.removeAll is unsupported, just skip this test
return;
}
assertTrue(getMap().isEmpty());
}
/**
* Test values.retainAll.
*/
@Test
public void testValuesRetainAll() {
resetFull();
final Collection<V> values = getMap().values();
final List<V> sampleValuesAsList = Arrays.asList(getSampleValues());
if (!values.equals(sampleValuesAsList)) {
return;
}
try {
assertFalse(values.retainAll(sampleValuesAsList));
} catch (final UnsupportedOperationException e) {
// if values.retainAll is unsupported, just skip this test
return;
}
assertEquals(sampleValuesAsList.size(), getMap().size());
try {
assertTrue(values.retainAll(Collections.<V>emptySet()));
} catch (final UnsupportedOperationException e) {
// if values.retainAll is unsupported, just skip this test
return;
}
assertTrue(getMap().isEmpty());
}
/**
* Verifies that values.iterator.remove changes the underlying map.
*/
@Test
@SuppressWarnings("boxing") // OK in test code
public void testValuesIteratorRemoveChangesMap() {
resetFull();
final List<V> sampleValuesAsList = Arrays.asList(getSampleValues());
final Map<V, Integer> cardinality = CollectionUtils.getCardinalityMap(sampleValuesAsList);
final Collection<V> values = getMap().values();
for (final Iterator<V> iter = values.iterator(); iter.hasNext();) {
final V value = iter.next();
Integer count = cardinality.get(value);
if (count == null) {
return;
}
try {
iter.remove();
cardinality.put(value, --count);
} catch (final UnsupportedOperationException e) {
// if values.iterator.remove is unsupported, just skip this test
return;
}
final boolean expected = count > 0;
final StringBuilder msg = new StringBuilder("Value should ");
msg.append(expected ? "yet " : "no longer ");
msg.append("be present in the underlying map");
assertEquals(msg.toString(), expected, getMap().containsValue(value));
}
assertTrue(getMap().isEmpty());
}
/**
* Tests that the {@link Map#keySet} set is backed by
* the underlying map by removing from the keySet set
* and testing if the key was removed from the map.
*/
@Test
public void testKeySetRemoveChangesMap() {
resetFull();
final K[] sampleKeys = getSampleKeys();
final Set<K> keys = getMap().keySet();
for (final K sampleKey : sampleKeys) {
try {
keys.remove(sampleKey);
} catch (final UnsupportedOperationException e) {
// if key.remove is unsupported, just skip this test
return;
}
assertFalse("Key should have been removed from the underlying map.", getMap().containsKey(sampleKey));
}
}
/**
* Test keySet.removeAll.
*/
@Test
public void testKeySetRemoveAll() {
resetFull();
final Set<K> keys = getMap().keySet();
final List<K> sampleKeysAsList = Arrays.asList(getSampleKeys());
if (!keys.equals(sampleKeysAsList)) {
return;
}
try {
assertFalse(keys.removeAll(Collections.<K>emptySet()));
} catch (final UnsupportedOperationException e) {
return;
}
assertEquals(sampleKeysAsList, keys);
try {
assertTrue(keys.removeAll(sampleKeysAsList));
} catch (final UnsupportedOperationException e) {
return;
}
assertTrue(getMap().isEmpty());
}
/**
* Test keySet.retainAll.
*/
@Test
public void testKeySetRetainAll() {
resetFull();
final Set<K> keys = getMap().keySet();
final List<K> sampleKeysAsList = Arrays.asList(getSampleKeys());
if (!keys.equals(sampleKeysAsList)) {
return;
}
try {
assertFalse(keys.retainAll(sampleKeysAsList));
} catch (final UnsupportedOperationException e) {
return;
}
assertEquals(sampleKeysAsList, keys);
try {
assertTrue(keys.retainAll(Collections.<K>emptySet()));
} catch (final UnsupportedOperationException e) {
return;
}
assertTrue(getMap().isEmpty());
}
/**
* Verify that keySet.iterator.remove changes the underlying map.
*/
@Test
public void testKeySetIteratorRemoveChangesMap() {
resetFull();
for (final Iterator<K> iter = getMap().keySet().iterator(); iter.hasNext();) {
final K key = iter.next();
try {
iter.remove();
} catch (final UnsupportedOperationException e) {
return;
}
assertFalse(getMap().containsKey(key));
}
}
/**
* Tests that the {@link Map#entrySet} set is backed by
* the underlying map by removing from the entrySet set
* and testing if the entry was removed from the map.
*/
@Test
public void testEntrySetRemoveChangesMap() {
resetFull();
final K[] sampleKeys = getSampleKeys();
final V[] sampleValues = getSampleValues();
final Set<Map.Entry<K, V>> entrySet = getMap().entrySet();
for (int i = 0; i < sampleKeys.length; i++) {
try {
entrySet.remove(new DefaultMapEntry<>(sampleKeys[i], sampleValues[i]));
} catch (final UnsupportedOperationException e) {
// if entrySet removal is unsupported, just skip this test
return;
}
assertFalse("Entry should have been removed from the underlying map.", getMap().containsKey(sampleKeys[i]));
}
}
/**
* Test entrySet.removeAll.
*/
@Test
public void testEntrySetRemoveAll() {
resetFull();
final K[] sampleKeys = getSampleKeys();
final V[] sampleValues = getSampleValues();
//verify map looks as expected:
for (int i = 0; i < sampleKeys.length; i++) {
if (!getMap().containsKey(sampleKeys[i])) {
return;
}
final V value = sampleValues[i];
final V test = getMap().get(sampleKeys[i]);
if (value == test || value != null && value.equals(test)) {
continue;
}
return;
}
final Set<Map.Entry<K, V>> entrySet = getMap().entrySet();
final HashSet<Map.Entry<K, V>> comparisonSet = new HashSet<>(entrySet);
try {
assertFalse(entrySet.removeAll(Collections.<Map.Entry<K, V>>emptySet()));
} catch (final UnsupportedOperationException e) {
return;
}
assertEquals(sampleKeys.length, getMap().size());
try {
assertTrue(entrySet.removeAll(comparisonSet));
} catch (final UnsupportedOperationException e) {
return;
}
assertTrue(getMap().isEmpty());
}
/**
* Test entrySet.retainAll.
*/
@Test
public void testEntrySetRetainAll() {
resetFull();
final K[] sampleKeys = getSampleKeys();
final V[] sampleValues = getSampleValues();
//verify map looks as expected:
for (int i = 0; i < sampleKeys.length; i++) {
if (!getMap().containsKey(sampleKeys[i])) {
return;
}
final V value = sampleValues[i];
final V test = getMap().get(sampleKeys[i]);
if (value == test || value != null && value.equals(test)) {
continue;
}
return;
}
final Set<Map.Entry<K, V>> entrySet = getMap().entrySet();
final HashSet<Map.Entry<K, V>> comparisonSet = new HashSet<>(entrySet);
try {
assertFalse(entrySet.retainAll(comparisonSet));
} catch (final UnsupportedOperationException e) {
return;
}
assertEquals(sampleKeys.length, getMap().size());
try {
assertTrue(entrySet.retainAll(Collections.<Map.Entry<K, V>>emptySet()));
} catch (final UnsupportedOperationException e) {
return;
}
assertTrue(getMap().isEmpty());
}
/**
* Verify that entrySet.iterator.remove changes the underlying map.
*/
@Test
public void testEntrySetIteratorRemoveChangesMap() {
resetFull();
for (final Iterator<Map.Entry<K, V>> iter = getMap().entrySet().iterator(); iter.hasNext();) {
final K key = iter.next().getKey();
try {
iter.remove();
} catch (final UnsupportedOperationException e) {
return;
}
assertFalse(getMap().containsKey(key));
}
}
/**
* Utility methods to create an array of Map.Entry objects
* out of the given key and value arrays.<P>
*
* @param keys the array of keys
* @param values the array of values
* @return an array of Map.Entry of those keys to those values
*/
@SuppressWarnings("unchecked")
private Map.Entry<K, V>[] makeEntryArray(final K[] keys, final V[] values) {
final Map.Entry<K, V>[] result = new Map.Entry[keys.length];
for (int i = 0; i < keys.length; i++) {
final Map<K, V> map = makeConfirmedMap();
map.put(keys[i], values[i]);
result[i] = map.entrySet().iterator().next();
}
return result;
}
/**
* Bulk test {@link Map#entrySet()}. This method runs through all of
* the tests in {@link AbstractSetTest}.
* After modification operations, {@link #verify()} is invoked to ensure
* that the map and the other collection views are still valid.
*
* @return a {@link AbstractSetTest} instance for testing the map's entry set
*/
public BulkTest bulkTestMapEntrySet() {
return new TestMapEntrySet();
}
public class TestMapEntrySet extends AbstractSetTest<Map.Entry<K, V>> {
public TestMapEntrySet() {
super("MapEntrySet");
}
// Have to implement manually; entrySet doesn't support addAll
/**
* {@inheritDoc}
*/
@Override
public Entry<K, V>[] getFullElements() {
return getFullNonNullElements();
}
/**
* {@inheritDoc}
*/
@Override
public Map.Entry<K, V>[] getFullNonNullElements() {
final K[] k = getSampleKeys();
final V[] v = getSampleValues();
return makeEntryArray(k, v);
}
// Have to implement manually; entrySet doesn't support addAll
@Override
public Map.Entry<K, V>[] getOtherElements() {
final K[] k = getOtherKeys();
final V[] v = getOtherValues();
return makeEntryArray(k, v);
}
@Override
public Set<Map.Entry<K, V>> makeObject() {
return AbstractMapTest.this.makeObject().entrySet();
}
@Override
public Set<Map.Entry<K, V>> makeFullCollection() {
return makeFullMap().entrySet();
}
@Override
public boolean isAddSupported() {
// Collection views don't support add operations.
return false;
}
@Override
public boolean isRemoveSupported() {
// Entry set should only support remove if map does
return AbstractMapTest.this.isRemoveSupported();
}
public boolean isGetStructuralModify() {
return AbstractMapTest.this.isGetStructuralModify();
}
@Override
public boolean areEqualElementsDistinguishable() {
return AbstractMapTest.this.areEqualElementsDistinguishable();
}
@Override
public boolean isTestSerialization() {
return false;
}
@Override
public void resetFull() {
AbstractMapTest.this.resetFull();
setCollection(AbstractMapTest.this.getMap().entrySet());
TestMapEntrySet.this.setConfirmed(AbstractMapTest.this.getConfirmed().entrySet());
}
@Override
public void resetEmpty() {
AbstractMapTest.this.resetEmpty();
setCollection(AbstractMapTest.this.getMap().entrySet());
TestMapEntrySet.this.setConfirmed(AbstractMapTest.this.getConfirmed().entrySet());
}
@Override
protected int getIterationBehaviour(){
return AbstractMapTest.this.getIterationBehaviour();
}
@Test
public void testMapEntrySetIteratorEntry() {
resetFull();
final Iterator<Map.Entry<K, V>> it = getCollection().iterator();
int count = 0;
while (it.hasNext()) {
final Map.Entry<K, V> entry = it.next();
assertTrue(AbstractMapTest.this.getMap().containsKey(entry.getKey()));
assertTrue(AbstractMapTest.this.getMap().containsValue(entry.getValue()));
if (!isGetStructuralModify()) {
assertEquals(AbstractMapTest.this.getMap().get(entry.getKey()), entry.getValue());
}
count++;
}
assertEquals(getCollection().size(), count);
}
@Test
public void testMapEntrySetIteratorEntrySetValue() {
final K key1 = getSampleKeys()[0];
final K key2 = getSampleKeys().length == 1 ? getSampleKeys()[0] : getSampleKeys()[1];
final V newValue1 = getNewSampleValues()[0];
final V newValue2 = getNewSampleValues().length ==1 ? getNewSampleValues()[0] : getNewSampleValues()[1];
resetFull();
// explicitly get entries as sample values/keys are connected for some maps
// such as BeanMap
Iterator<Map.Entry<K, V>> it = TestMapEntrySet.this.getCollection().iterator();
final Map.Entry<K, V> entry1 = getEntry(it, key1);
it = TestMapEntrySet.this.getCollection().iterator();
final Map.Entry<K, V> entry2 = getEntry(it, key2);
Iterator<Map.Entry<K, V>> itConfirmed = TestMapEntrySet.this.getConfirmed().iterator();
final Map.Entry<K, V> entryConfirmed1 = getEntry(itConfirmed, key1);
itConfirmed = TestMapEntrySet.this.getConfirmed().iterator();
final Map.Entry<K, V> entryConfirmed2 = getEntry(itConfirmed, key2);
verify();
if (!isSetValueSupported()) {
try {
entry1.setValue(newValue1);
} catch (final UnsupportedOperationException ex) {
}
return;
}
entry1.setValue(newValue1);
entryConfirmed1.setValue(newValue1);
assertEquals(newValue1, entry1.getValue());
assertTrue(AbstractMapTest.this.getMap().containsKey(entry1.getKey()));
assertTrue(AbstractMapTest.this.getMap().containsValue(newValue1));
assertEquals(newValue1, AbstractMapTest.this.getMap().get(entry1.getKey()));
verify();
entry1.setValue(newValue1);
entryConfirmed1.setValue(newValue1);
assertEquals(newValue1, entry1.getValue());
assertTrue(AbstractMapTest.this.getMap().containsKey(entry1.getKey()));
assertTrue(AbstractMapTest.this.getMap().containsValue(newValue1));
assertEquals(newValue1, AbstractMapTest.this.getMap().get(entry1.getKey()));
verify();
entry2.setValue(newValue2);
entryConfirmed2.setValue(newValue2);
assertEquals(newValue2, entry2.getValue());
assertTrue(AbstractMapTest.this.getMap().containsKey(entry2.getKey()));
assertTrue(AbstractMapTest.this.getMap().containsValue(newValue2));
assertEquals(newValue2, AbstractMapTest.this.getMap().get(entry2.getKey()));
verify();
}
public Map.Entry<K, V> getEntry(final Iterator<Map.Entry<K, V>> itConfirmed, final K key) {
Map.Entry<K, V> entry = null;
while (itConfirmed.hasNext()) {
final Map.Entry<K, V> temp = itConfirmed.next();
if (temp.getKey() == null) {
if (key == null) {
entry = temp;
break;
}
} else if (temp.getKey().equals(key)) {
entry = temp;
break;
}
}
assertNotNull("No matching entry in map for key '" + key + "'", entry);
return entry;
}
@Test
public void testMapEntrySetRemoveNonMapEntry() {
if (!isRemoveSupported()) {
return;
}
resetFull();
assertFalse(getCollection().remove(null));
assertFalse(getCollection().remove(new Object()));
}
@Override
public void verify() {
super.verify();
AbstractMapTest.this.verify();
}
}
/**
* Bulk test {@link Map#keySet()}. This method runs through all of
* the tests in {@link AbstractSetTest}.
* After modification operations, {@link #verify()} is invoked to ensure
* that the map and the other collection views are still valid.
*
* @return a {@link AbstractSetTest} instance for testing the map's key set
*/
public BulkTest bulkTestMapKeySet() {
return new TestMapKeySet();
}
public class TestMapKeySet extends AbstractSetTest<K> {
public TestMapKeySet() {
super("");
}
@Override
public K[] getFullElements() {
return getSampleKeys();
}
@Override
public K[] getOtherElements() {
return getOtherKeys();
}
@Override
public Set<K> makeObject() {
return AbstractMapTest.this.makeObject().keySet();
}
@Override
public Set<K> makeFullCollection() {
return AbstractMapTest.this.makeFullMap().keySet();
}
@Override
public boolean isNullSupported() {
return AbstractMapTest.this.isAllowNullKey();
}
@Override
public boolean isAddSupported() {
return false;
}
@Override
public boolean isRemoveSupported() {
return AbstractMapTest.this.isRemoveSupported();
}
@Override
public boolean isTestSerialization() {
return false;
}
@Override
public void resetEmpty() {
AbstractMapTest.this.resetEmpty();
setCollection(AbstractMapTest.this.getMap().keySet());
TestMapKeySet.this.setConfirmed(AbstractMapTest.this.getConfirmed().keySet());
}
@Override
public void resetFull() {
AbstractMapTest.this.resetFull();
setCollection(AbstractMapTest.this.getMap().keySet());
TestMapKeySet.this.setConfirmed(AbstractMapTest.this.getConfirmed().keySet());
}
@Override
public void verify() {
super.verify();
AbstractMapTest.this.verify();
}
@Override
protected int getIterationBehaviour(){
return AbstractMapTest.this.getIterationBehaviour();
}
}
/**
* Bulk test {@link Map#values()}. This method runs through all of
* the tests in {@link AbstractCollectionTest}.
* After modification operations, {@link #verify()} is invoked to ensure
* that the map and the other collection views are still valid.
*
* @return a {@link AbstractCollectionTest} instance for testing the map's
* values collection
*/
public BulkTest bulkTestMapValues() {
return new TestMapValues();
}
public class TestMapValues extends AbstractCollectionTest<V> {
public TestMapValues() {
super("");
}
@Override
public V[] getFullElements() {
return getSampleValues();
}
@Override
public V[] getOtherElements() {
return getOtherValues();
}
@Override
public Collection<V> makeObject() {
return AbstractMapTest.this.makeObject().values();
}
@Override
public Collection<V> makeFullCollection() {
return AbstractMapTest.this.makeFullMap().values();
}
@Override
public boolean isNullSupported() {
return AbstractMapTest.this.isAllowNullKey();
}
@Override
public boolean isAddSupported() {
return false;
}
@Override
public boolean isRemoveSupported() {
return AbstractMapTest.this.isRemoveSupported();
}
@Override
public boolean isTestSerialization() {
return false;
}
@Override
public boolean areEqualElementsDistinguishable() {
// equal values are associated with different keys, so they are
// distinguishable.
return true;
}
@Override
public Collection<V> makeConfirmedCollection() {
// never gets called, reset methods are overridden
return null;
}
@Override
public Collection<V> makeConfirmedFullCollection() {
// never gets called, reset methods are overridden
return null;
}
@Override
public void resetFull() {
AbstractMapTest.this.resetFull();
setCollection(map.values());
TestMapValues.this.setConfirmed(AbstractMapTest.this.getConfirmed().values());
}
@Override
public void resetEmpty() {
AbstractMapTest.this.resetEmpty();
setCollection(map.values());
TestMapValues.this.setConfirmed(AbstractMapTest.this.getConfirmed().values());
}
@Override
public void verify() {
super.verify();
AbstractMapTest.this.verify();
}
@Override
protected int getIterationBehaviour(){
return AbstractMapTest.this.getIterationBehaviour();
}
// TODO: should test that a remove on the values collection view
// removes the proper mapping and not just any mapping that may have
// the value equal to the value returned from the values iterator.
}
/**
* Resets the {@link #map}, {@link #entrySet}, {@link #keySet},
* {@link #values} and {@link #confirmed} fields to empty.
*/
public void resetEmpty() {
this.map = makeObject();
views();
this.confirmed = makeConfirmedMap();
}
/**
* Resets the {@link #map}, {@link #entrySet}, {@link #keySet},
* {@link #values} and {@link #confirmed} fields to full.
*/
public void resetFull() {
this.map = makeFullMap();
views();
this.confirmed = makeConfirmedMap();
final K[] k = getSampleKeys();
final V[] v = getSampleValues();
for (int i = 0; i < k.length; i++) {
confirmed.put(k[i], v[i]);
}
}
/**
* Resets the collection view fields.
*/
private void views() {
this.keySet = getMap().keySet();
// see verifyValues: retrieve the values collection only when verifying them
// this.values = getMap().values();
this.entrySet = getMap().entrySet();
}
/**
* Verifies that {@link #map} is still equal to {@link #confirmed}.
* This method checks that the map is equal to the HashMap,
* <I>and</I> that the map's collection views are still equal to
* the HashMap's collection views. An <Code>equals</Code> test
* is done on the maps and their collection views; their size and
* <Code>isEmpty</Code> results are compared; their hashCodes are
* compared; and <Code>containsAll</Code> tests are run on the
* collection views.
*/
public void verify() {
verifyMap();
verifyEntrySet();
verifyKeySet();
verifyValues();
}
public void verifyMap() {
final int size = getConfirmed().size();
final boolean empty = getConfirmed().isEmpty();
assertEquals("Map should be same size as HashMap", size, getMap().size());
assertEquals("Map should be empty if HashMap is", empty, getMap().isEmpty());
assertEquals("hashCodes should be the same", getConfirmed().hashCode(), getMap().hashCode());
// changing the order of the assertion below fails for LRUMap because confirmed is
// another collection (e.g. treemap) and confirmed.equals() creates a normal iterator (not
// #mapIterator()), which modifies the parent expected modCount of the map object, causing
// concurrent modification exceptions.
// Because of this we have assertEquals(map, confirmed), and not the other way around.
assertEquals("Map should still equal HashMap", map, confirmed);
assertEquals("Map should still equal HashMap", getMap(), getConfirmed());
}
public void verifyEntrySet() {
final int size = getConfirmed().size();
final boolean empty = getConfirmed().isEmpty();
assertEquals("entrySet should be same size as HashMap's" +
"\nTest: " + entrySet + "\nReal: " + getConfirmed().entrySet(),
size, entrySet.size());
assertEquals("entrySet should be empty if HashMap is" +
"\nTest: " + entrySet + "\nReal: " + getConfirmed().entrySet(),
empty, entrySet.isEmpty());
assertTrue("entrySet should contain all HashMap's elements" +
"\nTest: " + entrySet + "\nReal: " + getConfirmed().entrySet(),
entrySet.containsAll(getConfirmed().entrySet()));
assertEquals("entrySet hashCodes should be the same" +
"\nTest: " + entrySet + "\nReal: " + getConfirmed().entrySet(),
getConfirmed().entrySet().hashCode(), entrySet.hashCode());
assertEquals("Map's entry set should still equal HashMap's",
getConfirmed().entrySet(), entrySet);
}
public void verifyKeySet() {
final int size = getConfirmed().size();
final boolean empty = getConfirmed().isEmpty();
assertEquals("keySet should be same size as HashMap's" +
"\nTest: " + keySet + "\nReal: " + getConfirmed().keySet(),
size, keySet.size());
assertEquals("keySet should be empty if HashMap is" +
"\nTest: " + keySet + "\nReal: " + getConfirmed().keySet(),
empty, keySet.isEmpty());
assertTrue("keySet should contain all HashMap's elements" +
"\nTest: " + keySet + "\nReal: " + getConfirmed().keySet(),
keySet.containsAll(getConfirmed().keySet()));
assertEquals("keySet hashCodes should be the same" +
"\nTest: " + keySet + "\nReal: " + getConfirmed().keySet(),
getConfirmed().keySet().hashCode(), keySet.hashCode());
assertEquals("Map's key set should still equal HashMap's",
getConfirmed().keySet(), keySet);
}
public void verifyValues() {
final List<V> known = new ArrayList<>(getConfirmed().values());
values = getMap().values();
final List<V> test = new ArrayList<>(values);
final int size = getConfirmed().size();
final boolean empty = getConfirmed().isEmpty();
assertEquals("values should be same size as HashMap's" +
"\nTest: " + test + "\nReal: " + known,
size, values.size());
assertEquals("values should be empty if HashMap is" +
"\nTest: " + test + "\nReal: " + known,
empty, values.isEmpty());
assertTrue("values should contain all HashMap's elements" +
"\nTest: " + test + "\nReal: " + known,
test.containsAll(known));
assertTrue("values should contain all HashMap's elements" +
"\nTest: " + test + "\nReal: " + known,
known.containsAll(test));
// originally coded to use a HashBag, but now separate jar so...
for (final V v : known) {
final boolean removed = test.remove(v);
assertTrue("Map's values should still equal HashMap's", removed);
}
assertTrue("Map's values should still equal HashMap's", test.isEmpty());
}
/**
* Erases any leftover instance variables by setting them to null.
*/
@Override
public void tearDown() throws Exception {
map = null;
keySet = null;
entrySet = null;
values = null;
confirmed = null;
}
/**
* Get the map.
* @return Map<K, V>
*/
public Map<K, V> getMap() {
return map;
}
/**
* Get the confirmed.
* @return Map<K, V>
*/
public Map<K, V> getConfirmed() {
return confirmed;
}
}