| /* |
| * Copyright 1999-2004 The Apache Software Foundation |
| * |
| * Licensed 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.collections; |
| |
| import com.google.gwt.testing.TestUtils; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.ConcurrentModificationException; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import jsinterop.annotations.JsPackage; |
| import jsinterop.annotations.JsProperty; |
| |
| /** |
| * Tests base {@link java.util.Map} methods and contracts. |
| * <p> |
| * The forces at work here are similar to those in {@link TestCollection}. |
| * If your class implements the full Map interface, including optional |
| * operations, simply extend this class, and implement the {@link |
| * #makeEmptyMap()} method. |
| * <p> |
| * On the other hand, if your map implemenation 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>Supported Operation Methods</B><P> |
| * |
| * Override these methods if your map doesn't support certain operations: |
| * |
| * <UL> |
| * <LI> {@link #useDuplicateValues()} |
| * <LI> {@link #useNullKey()} |
| * <LI> {@link #useNullValue()} |
| * <LI> {@link #isAddRemoveModifiable()} |
| * <LI> {@link #isChangeable()} |
| * </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 #collectionValues} 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 methodsto perform additional verifications. For |
| * instance, {@link 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 {@link |
| * 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 #useDuplicateValues()} and have it return <code>false</code> |
| * |
| * @author Michael Smith |
| * @author Rodney Waldhoff |
| * @author Paul Jack |
| * @version $Id: TestMap.java,v 1.20.2.1 2004/05/22 12:14:05 scolebourne Exp $ |
| */ |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| public abstract class TestMap extends TestObject{ |
| |
| // 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 map; |
| |
| /** Entry set of map created by reset(). */ |
| protected Set entrySet; |
| |
| /** Key set of map created by reset(). */ |
| protected Set keySet; |
| |
| /** Values collection of map created by reset(). */ |
| protected Collection collectionValues; |
| |
| /** HashMap created by reset(). */ |
| protected Map confirmed; |
| |
| |
| |
| |
| /** |
| * Override if your map does not allow a <code>null</code> key. The |
| * default implementation returns <code>true</code> |
| **/ |
| protected boolean useNullKey() { |
| return true; |
| } |
| |
| /** |
| * Override if your map does not allow <code>null</code> values. The |
| * default implementation returns <code>true</code>. |
| **/ |
| protected boolean useNullValue() { |
| return true; |
| } |
| |
| /** |
| * Override if your map does not allow duplicate values. The default |
| * implementation returns <code>true</code>. |
| **/ |
| protected boolean useDuplicateValues() { |
| return true; |
| } |
| |
| /** |
| * Override if your map allows its mappings to be changed to new values. |
| * The default implementation returns <code>true</code>. |
| **/ |
| protected boolean isChangeable() { |
| return true; |
| } |
| |
| /** |
| * Override if your map does not allow add/remove modifications. The |
| * default implementation returns <code>true</code>. |
| **/ |
| protected boolean isAddRemoveModifiable() { |
| return true; |
| } |
| |
| /** |
| * Override if your map allows concurrent modifications. The default |
| * implementation returns <code>true</code>. |
| **/ |
| protected boolean isFailFastExpected() { |
| return true; |
| } |
| |
| /** |
| * 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 #useNullKey()} returns <code>true</code>. |
| **/ |
| protected Object[] getSampleKeys() { |
| Object[] result = new Object[] { |
| "blah", "foo", "bar", "baz", "tmp", "gosh", "golly", "gee", |
| "hello", "goodbye", "we'll", "see", "you", "all", "again", |
| "key", |
| "key2", |
| (useNullKey()) ? null : "nonnullkey" |
| }; |
| return result; |
| } |
| |
| |
| protected Object[] getOtherKeys() { |
| return TestCollection.getOtherNonNullStringElements(); |
| } |
| |
| protected Object[] getOtherValues() { |
| return TestCollection.getOtherNonNullStringElements(); |
| } |
| |
| /** |
| * 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 contructs a set of |
| * String values and includes a single null value if {@link |
| * #useNullValue()} returns <code>true</code>, and includes two values |
| * that are the same if {@link #useDuplicateValues()} returns |
| * <code>true</code>. |
| **/ |
| protected Object[] getSampleValues() { |
| Object[] result = new Object[] { |
| "blahv", "foov", "barv", "bazv", "tmpv", "goshv", "gollyv", "geev", |
| "hellov", "goodbyev", "we'llv", "seev", "youv", "allv", "againv", |
| (useNullValue()) ? null : "nonnullvalue", |
| "value", |
| (useDuplicateValues()) ? "value" : "value2", |
| }; |
| return 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 |
| * #useNullValue()} returns <code>true</code>, and includes two values |
| * that are the same if {@link #useDuplicateValues()} returns |
| * <code>true</code>. |
| **/ |
| protected Object[] getNewSampleValues() { |
| Object[] result = new Object[] { |
| (useNullValue()) ? null : "newnonnullvalue", |
| "newvalue", |
| (useDuplicateValues()) ? "newvalue" : "newvalue2", |
| "newblahv", "newfoov", "newbarv", "newbazv", "newtmpv", "newgoshv", |
| "newgollyv", "newgeev", "newhellov", "newgoodbyev", "newwe'llv", |
| "newseev", "newyouv", "newallv", "newagainv", |
| }; |
| return result; |
| } |
| |
| /** |
| * Helper method to add all the mappings described by {@link |
| * #getSampleKeys()} and {@link #getSampleValues()}. |
| **/ |
| protected void addSampleMappings(Map m) { |
| |
| Object[] keys = getSampleKeys(); |
| Object[] values = getSampleValues(); |
| |
| for(int i = 0; i < keys.length; i++) { |
| try { |
| m.put(keys[i], values[i]); |
| } catch (NullPointerException exception) { |
| assertTrue("NullPointerException only allowed to be thrown " + |
| "if either the key or value is null.", |
| keys[i] == null || values[i] == null); |
| |
| if (keys[i] == null) { |
| if (useNullKey()) { |
| throw new Error("NullPointerException on null key, but " + |
| "useNullKey is not overridden to return false.", exception); |
| } |
| } else if (values[i] == null) { |
| if (useNullValue()) { |
| throw new Error("NullPointerException on null value, but " + |
| "useNullValue is not overridden to return false.", exception); |
| } |
| } else { |
| // Unknown reason for NullPointer. |
| throw exception; |
| } |
| } |
| } |
| assertEquals("size must reflect number of mappings added.", |
| keys.length, m.size()); |
| } |
| |
| /** |
| * Return a new, empty {@link Map} to be used for testing. |
| */ |
| protected abstract Map makeEmptyMap(); |
| |
| protected Map makeConfirmedMap() { |
| return new HashMap(); |
| } |
| |
| /** |
| * 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. |
| **/ |
| protected Map makeFullMap() { |
| Map m = makeEmptyMap(); |
| addSampleMappings(m); |
| return m; |
| } |
| |
| @Override |
| public Object makeObject() { |
| return makeEmptyMap(); |
| } |
| |
| public void testSpecialKeysValues() { |
| String[] keys = {"toString", "constructor", "__proto__", "", "null"}; |
| Object[] values = {new Object(), new Object(), new Object(), new Object(), null}; |
| |
| Map map = makeEmptyMap(); |
| |
| assertMap(map, keys, values); |
| |
| Object[] undefineds = new Object[values.length]; |
| Arrays.fill(undefineds, getUndefined()); |
| assertMap(map, keys, undefineds); |
| } |
| |
| private void assertMap(Map map, String[] keys, Object[] values) { |
| assertEmptyMap(map, keys, values); |
| |
| // Fill the map with special keys/values. |
| for (int i = 0; i < keys.length; i++) { |
| map.put(keys[i], values[i]); |
| } |
| |
| // Assert the map with filled in keys/values |
| for (int i = 0; i < keys.length; i++) { |
| assertTrue(keys[i], map.containsKey(keys[i])); |
| assertTrue(keys[i], map.containsValue(values[i])); |
| assertSame(keys[i], values[i], map.get(keys[i])); |
| } |
| assertEquals(map.toString(), keys.length, map.size()); |
| |
| // Remove the keys and assert the results |
| for (int i = 0; i < keys.length; i++) { |
| assertSame(keys[i], values[i], map.remove(keys[i])); |
| } |
| assertEmptyMap(map, keys, values); |
| } |
| |
| private static void assertEmptyMap(Map map, final String[] keys, final Object[] values) { |
| for (int i = 0; i < keys.length; i++) { |
| assertFalse(keys[i], map.containsKey(keys[i])); |
| assertFalse(keys[i], map.containsValue(values[i])); |
| assertNull(keys[i], map.get(keys[i])); |
| } |
| } |
| |
| private static Object getUndefined() { |
| if (TestUtils.isJvm()) { |
| return null; |
| } |
| return getUndefinedImpl(); |
| } |
| |
| @JsProperty(name = "undefined", namespace = JsPackage.GLOBAL) |
| private native static Object getUndefinedImpl(); |
| |
| /** |
| * 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 |
| * useNullKey() returns true. The values array must only have a null |
| * value if useNullValue() is true and may only have duplicate values if |
| * useDuplicateValues() returns true. |
| **/ |
| public void testSampleMappings() { |
| Object[] keys = getSampleKeys(); |
| Object[] values = getSampleValues(); |
| Object[] newValues = getNewSampleValues(); |
| |
| assertTrue("failure in test: Must have keys returned from " + |
| "getSampleKeys.", keys != null); |
| |
| assertTrue("failure in test: Must have values returned from " + |
| "getSampleValues.", values != null); |
| |
| // 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 useNullKey " + |
| "is false.", keys[i] != null || useNullKey()); |
| assertTrue("failure in test: found null value, but useNullValue " + |
| "is false.", values[i] != null || useNullValue()); |
| assertTrue("failure in test: found null new value, but useNullValue " + |
| "is false.", newValues[i] != null || useNullValue()); |
| 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]))); |
| } |
| } |
| |
| // 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. |
| **/ |
| public void testMakeMap() { |
| Map em = makeEmptyMap(); |
| assertTrue("failure in test: makeEmptyMap must return a non-null map.", |
| em != null); |
| |
| Map em2 = makeEmptyMap(); |
| assertTrue("failure in test: makeEmptyMap must return a non-null map.", |
| em != null); |
| |
| assertTrue("failure in test: makeEmptyMap must return a new map " + |
| "with each invocation.", em != em2); |
| |
| Map fm = makeFullMap(); |
| assertTrue("failure in test: makeFullMap must return a non-null map.", |
| fm != null); |
| |
| Map fm2 = makeFullMap(); |
| assertTrue("failure in test: makeFullMap must return a non-null map.", |
| fm != null); |
| |
| assertTrue("failure in test: makeFullMap must return a new map " + |
| "with each invocation.", fm != fm2); |
| } |
| |
| /** |
| * Tests Map.isEmpty() |
| **/ |
| public void testMapIsEmpty() { |
| |
| resetEmpty(); |
| assertEquals("Map.isEmpty() should return true with an empty map", |
| true, map.isEmpty()); |
| verify(); |
| |
| resetFull(); |
| assertEquals("Map.isEmpty() should return false with a non-empty map", |
| false, map.isEmpty()); |
| verify(); |
| } |
| |
| /** |
| * Tests Map.size() |
| **/ |
| public void testMapSize() { |
| resetEmpty(); |
| assertEquals("Map.size() should be 0 with an empty map", |
| 0, map.size()); |
| verify(); |
| |
| resetFull(); |
| assertEquals("Map.size() should equal the number of entries " + |
| "in the map", getSampleKeys().length, map.size()); |
| verify(); |
| } |
| |
| /** |
| * Tests {@link Map#clear()}. If the map {@link #isAddRemoveModifiable() |
| * 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. |
| **/ |
| public void testMapClear() { |
| if (!isAddRemoveModifiable()) return; |
| |
| resetEmpty(); |
| map.clear(); |
| confirmed.clear(); |
| verify(); |
| |
| resetFull(); |
| map.clear(); |
| confirmed.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. |
| **/ |
| public void testMapContainsKey() { |
| Object[] keys = getSampleKeys(); |
| |
| resetEmpty(); |
| for(int i = 0; i < keys.length; i++) { |
| assertTrue("Map must not contain key when map is empty", |
| !map.containsKey(keys[i])); |
| } |
| verify(); |
| |
| resetFull(); |
| for(int i = 0; i < keys.length; i++) { |
| assertTrue("Map must contain key for a mapping in the map. " + |
| "Missing: " + keys[i], map.containsKey(keys[i])); |
| } |
| 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. |
| **/ |
| public void testMapContainsValue() { |
| Object[] values = getSampleValues(); |
| |
| resetEmpty(); |
| for(int i = 0; i < values.length; i++) { |
| assertTrue("Empty map must not contain value", |
| !map.containsValue(values[i])); |
| } |
| verify(); |
| |
| resetFull(); |
| for(int i = 0; i < values.length; i++) { |
| assertTrue("Map must contain value for a mapping in the map.", |
| map.containsValue(values[i])); |
| } |
| verify(); |
| } |
| |
| |
| /** |
| * Tests Map.equals(Object) |
| **/ |
| public void testMapEquals() { |
| resetEmpty(); |
| assertTrue("Empty maps unequal.", map.equals(confirmed)); |
| verify(); |
| |
| resetFull(); |
| assertTrue("Full maps unequal.", map.equals(confirmed)); |
| verify(); |
| |
| resetFull(); |
| // modify the HashMap created from the full map and make sure this |
| // change results in map.equals() to return false. |
| Iterator iter = confirmed.keySet().iterator(); |
| iter.next(); |
| iter.remove(); |
| assertTrue("Different maps equal.", !map.equals(confirmed)); |
| |
| resetFull(); |
| assertTrue("equals(null) returned true.", !map.equals(null)); |
| assertTrue("equals(new Object()) returned true.", |
| !map.equals(new Object())); |
| verify(); |
| } |
| |
| |
| /** |
| * Tests Map.get(Object) |
| **/ |
| public void testMapGet() { |
| resetEmpty(); |
| |
| Object[] keys = getSampleKeys(); |
| Object[] values = getSampleValues(); |
| |
| for (int i = 0; i < keys.length; i++) { |
| assertTrue("Empty map.get() should return null.", |
| map.get(keys[i]) == null); |
| } |
| verify(); |
| |
| resetFull(); |
| for (int i = 0; i < keys.length; i++) { |
| assertEquals("Full map.get() should return value from mapping.", |
| values[i], map.get(keys[i])); |
| } |
| } |
| |
| /** |
| * Tests Map.hashCode() |
| **/ |
| public void testMapHashCode() { |
| resetEmpty(); |
| assertTrue("Empty maps have different hashCodes.", |
| map.hashCode() == confirmed.hashCode()); |
| |
| resetFull(); |
| assertTrue("Equal maps have different hashCodes.", |
| map.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. Thereforce, |
| * 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. |
| **/ |
| public void testMapToString() { |
| resetEmpty(); |
| assertTrue("Empty map toString() should not return null", |
| map.toString() != null); |
| verify(); |
| |
| resetFull(); |
| assertTrue("Empty map toString() should not return null", |
| map.toString() != null); |
| verify(); |
| } |
| |
| |
| |
| |
| |
| /** |
| * Tests Map.put(Object, Object) |
| **/ |
| public void testMapPut() { |
| if (!isAddRemoveModifiable()) return; |
| |
| resetEmpty(); |
| |
| Object[] keys = getSampleKeys(); |
| Object[] values = getSampleValues(); |
| Object[] newValues = getNewSampleValues(); |
| |
| for(int i = 0; i < keys.length; i++) { |
| Object o = map.put(keys[i], values[i]); |
| confirmed.put(keys[i], values[i]); |
| verify(); |
| assertTrue("First map.put should return null", o == null); |
| assertTrue("Map should contain key after put", |
| map.containsKey(keys[i])); |
| assertTrue("Map should contain value after put", |
| map.containsValue(values[i])); |
| } |
| |
| for(int i = 0; i < keys.length; i++) { |
| Object o = map.put(keys[i], newValues[i]); |
| confirmed.put(keys[i], newValues[i]); |
| verify(); |
| assertEquals("Second map.put should return previous value", |
| values[i], o); |
| assertTrue("Map should still contain key after put", |
| map.containsKey(keys[i])); |
| assertTrue("Map should contain new value after put", |
| map.containsValue(newValues[i])); |
| |
| // if duplicates are allowed, we're not guarunteed that the value |
| // no longer exists, so don't try checking that. |
| if(!useDuplicateValues()) { |
| assertTrue("Map should not contain old value after second put", |
| !map.containsValue(values[i])); |
| } |
| } |
| } |
| |
| /** |
| * Tests Map.putAll(Collection) |
| **/ |
| public void testMapPutAll() { |
| if (!isAddRemoveModifiable()) return; |
| |
| resetEmpty(); |
| |
| Map m2 = makeFullMap(); |
| |
| map.putAll(m2); |
| confirmed.putAll(m2); |
| verify(); |
| |
| resetEmpty(); |
| |
| m2 = new HashMap(); |
| Object[] keys = getSampleKeys(); |
| Object[] values = getSampleValues(); |
| for(int i = 0; i < keys.length; i++) { |
| m2.put(keys[i], values[i]); |
| } |
| |
| map.putAll(m2); |
| confirmed.putAll(m2); |
| verify(); |
| } |
| |
| /** |
| * Tests Map.remove(Object) |
| **/ |
| public void testMapRemove() { |
| if (!isAddRemoveModifiable()) return; |
| |
| resetEmpty(); |
| |
| Object[] keys = getSampleKeys(); |
| Object[] values = getSampleValues(); |
| for(int i = 0; i < keys.length; i++) { |
| Object o = map.remove(keys[i]); |
| assertTrue("First map.remove should return null", o == null); |
| } |
| verify(); |
| |
| resetFull(); |
| |
| for(int i = 0; i < keys.length; i++) { |
| Object o = map.remove(keys[i]); |
| confirmed.remove(keys[i]); |
| verify(); |
| |
| assertEquals("map.remove with valid key should return value", |
| values[i], o); |
| } |
| |
| Object[] other = getOtherKeys(); |
| |
| resetFull(); |
| int size = map.size(); |
| for (int i = 0; i < other.length; i++) { |
| Object o = map.remove(other[i]); |
| assertEquals("map.remove for nonexistent key should return null", |
| o, null); |
| assertEquals("map.remove for nonexistent key should not " + |
| "shrink map", size, map.size()); |
| } |
| verify(); |
| } |
| |
| public void testFailFastEntrySet() { |
| if (!isAddRemoveModifiable()) { |
| return; |
| } |
| if (!isFailFastExpected()) { |
| return; |
| } |
| resetFull(); |
| Iterator<Map.Entry> it = map.entrySet().iterator(); |
| final Map.Entry val = it.next(); |
| map.remove(val.getKey()); |
| try { |
| it.next(); |
| fail(); |
| } catch (ConcurrentModificationException expected) {} |
| |
| resetFull(); |
| it = map.entrySet().iterator(); |
| it.next(); |
| map.clear(); |
| try { |
| it.next(); |
| fail(); |
| } catch (ConcurrentModificationException expected) {} |
| } |
| |
| public void testFailFastKeySet() { |
| if (!isAddRemoveModifiable()) { |
| return; |
| } |
| if (!isFailFastExpected()) { |
| return; |
| } |
| resetFull(); |
| Iterator it = map.keySet().iterator(); |
| final Object val = it.next(); |
| map.remove(val); |
| try { |
| it.next(); |
| fail(); |
| } catch (ConcurrentModificationException expected) {} |
| |
| resetFull(); |
| it = map.keySet().iterator(); |
| it.next(); |
| map.clear(); |
| try { |
| it.next(); |
| fail(); |
| } catch (ConcurrentModificationException expected) {} |
| } |
| |
| public void testFailFastValues() { |
| if (!isAddRemoveModifiable()) { |
| return; |
| } |
| if (!isFailFastExpected()) { |
| return; |
| } |
| resetFull(); |
| Iterator it = map.values().iterator(); |
| it.next(); |
| map.remove(map.keySet().iterator().next()); |
| try { |
| it.next(); |
| fail(); |
| } catch (ConcurrentModificationException expected) {} |
| |
| resetFull(); |
| it = map.values().iterator(); |
| it.next(); |
| map.clear(); |
| try { |
| it.next(); |
| fail(); |
| } catch (ConcurrentModificationException expected) {} |
| } |
| |
| /** |
| * 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 |
| */ |
| private Map.Entry[] makeEntryArray(Object[] keys, Object[] values) { |
| Map.Entry[] result = new Map.Entry[keys.length]; |
| for (int i = 0; i < keys.length; i++) { |
| result[i] = new DefaultMapEntry(keys[i], values[i]); |
| } |
| return result; |
| } |
| |
| |
| class TestMapEntrySet extends TestSet { |
| public TestMapEntrySet() { |
| super(""); |
| } |
| |
| // Have to implement manually; entrySet doesn't support addAll |
| @Override |
| protected Object[] getFullElements() { |
| Object[] k = getSampleKeys(); |
| Object[] v = getSampleValues(); |
| return makeEntryArray(k, v); |
| } |
| |
| // Have to implement manually; entrySet doesn't support addAll |
| @Override |
| protected Object[] getOtherElements() { |
| Object[] k = getOtherKeys(); |
| Object[] v = getOtherValues(); |
| return makeEntryArray(k, v); |
| } |
| |
| @Override |
| protected Set makeEmptySet() { |
| return makeEmptyMap().entrySet(); |
| } |
| |
| @Override |
| protected Set makeFullSet() { |
| return makeFullMap().entrySet(); |
| } |
| |
| @Override |
| protected boolean isAddSupported() { |
| // Collection views don't support add operations. |
| return false; |
| } |
| |
| @Override |
| protected boolean isRemoveSupported() { |
| // Entry set should only support remove if map does |
| return isAddRemoveModifiable(); |
| } |
| |
| @Override |
| protected void resetFull() { |
| TestMap.this.resetFull(); |
| collection = map.entrySet(); |
| TestMapEntrySet.this.confirmed = TestMap.this.confirmed.entrySet(); |
| } |
| |
| @Override |
| protected void resetEmpty() { |
| TestMap.this.resetEmpty(); |
| collection = map.entrySet(); |
| TestMapEntrySet.this.confirmed = TestMap.this.confirmed.entrySet(); |
| } |
| |
| @Override |
| protected void verify() { |
| super.verify(); |
| TestMap.this.verify(); |
| } |
| } |
| |
| |
| |
| class TestMapKeySet extends TestSet { |
| public TestMapKeySet() { |
| super(""); |
| } |
| @Override |
| protected Object[] getFullElements() { |
| return getSampleKeys(); |
| } |
| |
| @Override |
| protected Object[] getOtherElements() { |
| return getOtherKeys(); |
| } |
| |
| @Override |
| protected Set makeEmptySet() { |
| return makeEmptyMap().keySet(); |
| } |
| |
| @Override |
| protected Set makeFullSet() { |
| return makeFullMap().keySet(); |
| } |
| |
| @Override |
| protected boolean isAddSupported() { |
| return false; |
| } |
| |
| @Override |
| protected boolean isRemoveSupported() { |
| return isAddRemoveModifiable(); |
| } |
| |
| @Override |
| protected void resetEmpty() { |
| TestMap.this.resetEmpty(); |
| collection = map.keySet(); |
| TestMapKeySet.this.confirmed = TestMap.this.confirmed.keySet(); |
| } |
| |
| @Override |
| protected void resetFull() { |
| TestMap.this.resetFull(); |
| collection = map.keySet(); |
| TestMapKeySet.this.confirmed = TestMap.this.confirmed.keySet(); |
| } |
| |
| @Override |
| protected void verify() { |
| super.verify(); |
| TestMap.this.verify(); |
| } |
| } |
| |
| |
| |
| class TestMapValues extends TestCollection { |
| public TestMapValues() { |
| |
| } |
| |
| @Override |
| protected Object[] getFullElements() { |
| return getSampleValues(); |
| } |
| |
| @Override |
| protected Object[] getOtherElements() { |
| return getOtherValues(); |
| } |
| |
| @Override |
| protected Collection makeCollection() { |
| return makeEmptyMap().values(); |
| } |
| |
| @Override |
| protected Collection makeFullCollection() { |
| return makeFullMap().values(); |
| } |
| |
| @Override |
| protected boolean isAddSupported() { |
| return false; |
| } |
| |
| @Override |
| protected boolean isRemoveSupported() { |
| return isAddRemoveModifiable(); |
| } |
| |
| @Override |
| protected boolean areEqualElementsDistinguishable() { |
| // equal values are associated with different keys, so they are |
| // distinguishable. |
| return true; |
| } |
| |
| @Override |
| protected Collection makeConfirmedCollection() { |
| // never gets called, reset methods are overridden |
| return null; |
| } |
| |
| @Override |
| protected Collection makeConfirmedFullCollection() { |
| // never gets called, reset methods are overridden |
| return null; |
| } |
| |
| @Override |
| protected void resetFull() { |
| TestMap.this.resetFull(); |
| collection = map.values(); |
| TestMapValues.this.confirmed = TestMap.this.confirmed.values(); |
| } |
| |
| @Override |
| protected void resetEmpty() { |
| TestMap.this.resetEmpty(); |
| collection = map.values(); |
| TestMapValues.this.confirmed = TestMap.this.confirmed.values(); |
| } |
| |
| @Override |
| protected void verify() { |
| super.verify(); |
| TestMap.this.verify(); |
| } |
| |
| // 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 #collectionValues} and {@link #confirmed} fields to empty. |
| */ |
| protected void resetEmpty() { |
| this.map = makeEmptyMap(); |
| views(); |
| this.confirmed = makeConfirmedMap(); |
| } |
| |
| |
| /** |
| * Resets the {@link #map}, {@link #entrySet}, {@link #keySet}, |
| * {@link #collectionValues} and {@link #confirmed} fields to full. |
| */ |
| protected void resetFull() { |
| this.map = makeFullMap(); |
| views(); |
| this.confirmed = makeConfirmedMap(); |
| Object[] k = getSampleKeys(); |
| Object[] 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 = map.keySet(); |
| this.collectionValues = map.values(); |
| this.entrySet = map.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. |
| */ |
| protected void verify() { |
| verifyMap(); |
| verifyEntrySet(); |
| verifyKeySet(); |
| |
| } |
| |
| protected void verifyMap() { |
| int size = confirmed.size(); |
| boolean empty = confirmed.isEmpty(); |
| assertEquals("Map should be same size as HashMap", |
| size, map.size()); |
| assertEquals("Map should be empty if HashMap is", |
| empty, map.isEmpty()); |
| assertEquals("hashCodes should be the same", |
| confirmed.hashCode(), map.hashCode()); |
| // this fails for LRUMap because confirmed.equals() somehow modifies |
| // map, causing concurrent modification exceptions. |
| //assertEquals("Map should still equal HashMap", confirmed, map); |
| // this works though and performs the same verification: |
| assertTrue("Map should still equal HashMap", map.equals(confirmed)); |
| // TODO: this should really be rexamined to figure out why LRU map |
| // behaves like it does (the equals shouldn't modify since all accesses |
| // by the confirmed collection should be through an iterator, thus not |
| // causing LRUMap to change). |
| } |
| |
| protected void verifyEntrySet() { |
| int size = confirmed.size(); |
| boolean empty = confirmed.isEmpty(); |
| assertEquals("entrySet should be same size as HashMap's", |
| size, entrySet.size()); |
| assertEquals("entrySet should be empty if HashMap is", |
| empty, entrySet.isEmpty()); |
| assertTrue("entrySet should contain all HashMap's elements", |
| entrySet.containsAll(confirmed.entrySet())); |
| assertEquals("entrySet hashCodes should be the same", |
| confirmed.entrySet().hashCode(), entrySet.hashCode()); |
| assertEquals("Map's entry set should still equal HashMap's", |
| confirmed.entrySet(), entrySet); |
| } |
| |
| protected void verifyKeySet() { |
| int size = confirmed.size(); |
| boolean empty = confirmed.isEmpty(); |
| assertEquals("keySet should be same size as HashMap's", |
| size, keySet.size()); |
| assertEquals("keySet should be empty if HashMap is", |
| empty, keySet.isEmpty()); |
| assertTrue("keySet should contain all HashMap's elements", |
| keySet.containsAll(confirmed.keySet())); |
| assertEquals("keySet hashCodes should be the same", |
| confirmed.keySet().hashCode(), keySet.hashCode()); |
| assertEquals("Map's key set should still equal HashMap's", |
| confirmed.keySet(), keySet); |
| } |
| |
| } |