/*
* Copyright (C) 2007 The Guava Authors
*
* 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 com.google.common.collect.testing.google;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
import static junit.framework.TestCase.fail;
import com.google.common.annotations.GwtCompatible;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.LinkedHashMultiset;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
/**
* A series of tests that support asserting that collections cannot be
* modified, either through direct or indirect means.
*
* @author Robert Konigsberg
*/
@GwtCompatible
public class UnmodifiableCollectionTests {
public static void assertMapEntryIsUnmodifiable(Entry, ?> entry) {
try {
entry.setValue(null);
fail("setValue on unmodifiable Map.Entry succeeded");
} catch (UnsupportedOperationException expected) {
}
}
/**
* Verifies that an Iterator is unmodifiable.
*
*
This test only works with iterators that iterate over a finite set.
*/
public static void assertIteratorIsUnmodifiable(Iterator> iterator) {
while (iterator.hasNext()) {
iterator.next();
try {
iterator.remove();
fail("Remove on unmodifiable iterator succeeded");
} catch (UnsupportedOperationException expected) {
}
}
}
/**
* Asserts that two iterators contain elements in tandem.
*
*
This test only works with iterators that iterate over a finite set.
*/
public static void assertIteratorsInOrder(
Iterator> expectedIterator, Iterator> actualIterator) {
int i = 0;
while (expectedIterator.hasNext()) {
Object expected = expectedIterator.next();
assertTrue(
"index " + i + " expected <" + expected + "., actual is exhausted",
actualIterator.hasNext());
Object actual = actualIterator.next();
assertEquals("index " + i, expected, actual);
i++;
}
if (actualIterator.hasNext()) {
fail("index " + i
+ ", expected is exhausted, actual <" + actualIterator.next() + ">");
}
}
/**
* Verifies that a collection is immutable.
*
*
A collection is considered immutable if:
*
* - All its mutation methods result in UnsupportedOperationException, and
* do not change the underlying contents.
*
- All methods that return objects that can indirectly mutate the
* collection throw UnsupportedOperationException when those mutators
* are called.
*
*
* @param collection the presumed-immutable collection
* @param sampleElement an element of the same type as that contained by
* {@code collection}. {@code collection} may or may not have {@code
* sampleElement} as a member.
*/
public static void assertCollectionIsUnmodifiable(
Collection collection, E sampleElement) {
Collection siblingCollection = new ArrayList();
siblingCollection.add(sampleElement);
Collection copy = new ArrayList();
copy.addAll(collection);
try {
collection.add(sampleElement);
fail("add succeeded on unmodifiable collection");
} catch (UnsupportedOperationException expected) {
}
assertCollectionsAreEquivalent(copy, collection);
try {
collection.addAll(siblingCollection);
fail("addAll succeeded on unmodifiable collection");
} catch (UnsupportedOperationException expected) {
}
assertCollectionsAreEquivalent(copy, collection);
try {
collection.clear();
fail("clear succeeded on unmodifiable collection");
} catch (UnsupportedOperationException expected) {
}
assertCollectionsAreEquivalent(copy, collection);
assertIteratorIsUnmodifiable(collection.iterator());
assertCollectionsAreEquivalent(copy, collection);
try {
collection.remove(sampleElement);
fail("remove succeeded on unmodifiable collection");
} catch (UnsupportedOperationException expected) {
}
assertCollectionsAreEquivalent(copy, collection);
try {
collection.removeAll(siblingCollection);
fail("removeAll succeeded on unmodifiable collection");
} catch (UnsupportedOperationException expected) {
}
assertCollectionsAreEquivalent(copy, collection);
try {
collection.retainAll(siblingCollection);
fail("retainAll succeeded on unmodifiable collection");
} catch (UnsupportedOperationException expected) {
}
assertCollectionsAreEquivalent(copy, collection);
}
/**
* Verifies that a set is immutable.
*
* A set is considered immutable if:
*
* - All its mutation methods result in UnsupportedOperationException, and
* do not change the underlying contents.
*
- All methods that return objects that can indirectly mutate the
* set throw UnsupportedOperationException when those mutators
* are called.
*
*
* @param set the presumed-immutable set
* @param sampleElement an element of the same type as that contained by
* {@code set}. {@code set} may or may not have {@code sampleElement} as a
* member.
*/
public static void assertSetIsUnmodifiable(
Set set, E sampleElement) {
assertCollectionIsUnmodifiable(set, sampleElement);
}
/**
* Verifies that a multiset is immutable.
*
* A multiset is considered immutable if:
*
* - All its mutation methods result in UnsupportedOperationException, and
* do not change the underlying contents.
*
- All methods that return objects that can indirectly mutate the
* multiset throw UnsupportedOperationException when those mutators
* are called.
*
*
* @param multiset the presumed-immutable multiset
* @param sampleElement an element of the same type as that contained by
* {@code multiset}. {@code multiset} may or may not have {@code
* sampleElement} as a member.
*/
public static void assertMultisetIsUnmodifiable(Multiset multiset,
final E sampleElement) {
Multiset copy = LinkedHashMultiset.create(multiset);
assertCollectionsAreEquivalent(multiset, copy);
// Multiset is a collection, so we can use all those tests.
assertCollectionIsUnmodifiable(multiset, sampleElement);
assertCollectionsAreEquivalent(multiset, copy);
try {
multiset.add(sampleElement, 2);
fail("add(Object, int) succeeded on unmodifiable collection");
} catch (UnsupportedOperationException expected) {
}
assertCollectionsAreEquivalent(multiset, copy);
try {
multiset.remove(sampleElement, 2);
fail("remove(Object, int) succeeded on unmodifiable collection");
} catch (UnsupportedOperationException expected) {
}
assertCollectionsAreEquivalent(multiset, copy);
assertCollectionsAreEquivalent(multiset, copy);
assertSetIsUnmodifiable(multiset.elementSet(), sampleElement);
assertCollectionsAreEquivalent(multiset, copy);
assertSetIsUnmodifiable(
multiset.entrySet(), new Multiset.Entry() {
@Override
public int getCount() {
return 1;
}
@Override
public E getElement() {
return sampleElement;
}
});
assertCollectionsAreEquivalent(multiset, copy);
}
/**
* Verifies that a multimap is immutable.
*
* A multimap is considered immutable if:
*
* - All its mutation methods result in UnsupportedOperationException, and
* do not change the underlying contents.
*
- All methods that return objects that can indirectly mutate the
* multimap throw UnsupportedOperationException when those mutators
*
*
* @param multimap the presumed-immutable multimap
* @param sampleKey a key of the same type as that contained by
* {@code multimap}. {@code multimap} may or may not have {@code sampleKey} as
* a key.
* @param sampleValue a key of the same type as that contained by
* {@code multimap}. {@code multimap} may or may not have {@code sampleValue}
* as a key.
*/
public static void assertMultimapIsUnmodifiable(
Multimap multimap, final K sampleKey, final V sampleValue) {
List> originalEntries =
Collections.unmodifiableList(Lists.newArrayList(multimap.entries()));
assertMultimapRemainsUnmodified(multimap, originalEntries);
Collection sampleValueAsCollection = Collections.singleton(sampleValue);
// Test #clear()
try {
multimap.clear();
fail("clear succeeded on unmodifiable multimap");
} catch (UnsupportedOperationException expected) {
}
assertMultimapRemainsUnmodified(multimap, originalEntries);
// Test asMap().entrySet()
assertSetIsUnmodifiable(
multimap.asMap().entrySet(),
Maps.immutableEntry(sampleKey, sampleValueAsCollection));
// Test #values()
assertMultimapRemainsUnmodified(multimap, originalEntries);
if (!multimap.isEmpty()) {
Collection values =
multimap.asMap().entrySet().iterator().next().getValue();
assertCollectionIsUnmodifiable(values, sampleValue);
}
// Test #entries()
assertCollectionIsUnmodifiable(
multimap.entries(),
Maps.immutableEntry(sampleKey, sampleValue));
assertMultimapRemainsUnmodified(multimap, originalEntries);
// Iterate over every element in the entry set
for (Entry entry : multimap.entries()) {
assertMapEntryIsUnmodifiable(entry);
}
assertMultimapRemainsUnmodified(multimap, originalEntries);
// Test #keys()
assertMultisetIsUnmodifiable(multimap.keys(), sampleKey);
assertMultimapRemainsUnmodified(multimap, originalEntries);
// Test #keySet()
assertSetIsUnmodifiable(multimap.keySet(), sampleKey);
assertMultimapRemainsUnmodified(multimap, originalEntries);
// Test #get()
if (!multimap.isEmpty()) {
K key = multimap.keySet().iterator().next();
assertCollectionIsUnmodifiable(multimap.get(key), sampleValue);
assertMultimapRemainsUnmodified(multimap, originalEntries);
}
// Test #put()
try {
multimap.put(sampleKey, sampleValue);
fail("put succeeded on unmodifiable multimap");
} catch (UnsupportedOperationException expected) {
}
assertMultimapRemainsUnmodified(multimap, originalEntries);
// Test #putAll(K, Collection)
try {
multimap.putAll(sampleKey, sampleValueAsCollection);
fail("putAll(K, Iterable) succeeded on unmodifiable multimap");
} catch (UnsupportedOperationException expected) {
}
assertMultimapRemainsUnmodified(multimap, originalEntries);
// Test #putAll(Multimap)
Multimap multimap2 = ArrayListMultimap.create();
multimap2.put(sampleKey, sampleValue);
try {
multimap.putAll(multimap2);
fail("putAll(Multimap) succeeded on unmodifiable multimap");
} catch (UnsupportedOperationException expected) {
}
assertMultimapRemainsUnmodified(multimap, originalEntries);
// Test #remove()
try {
multimap.remove(sampleKey, sampleValue);
fail("remove succeeded on unmodifiable multimap");
} catch (UnsupportedOperationException expected) {
}
assertMultimapRemainsUnmodified(multimap, originalEntries);
// Test #removeAll()
try {
multimap.removeAll(sampleKey);
fail("removeAll succeeded on unmodifiable multimap");
} catch (UnsupportedOperationException expected) {
}
assertMultimapRemainsUnmodified(multimap, originalEntries);
// Test #replaceValues()
try {
multimap.replaceValues(sampleKey, sampleValueAsCollection);
fail("replaceValues succeeded on unmodifiable multimap");
} catch (UnsupportedOperationException expected) {
}
assertMultimapRemainsUnmodified(multimap, originalEntries);
// Test #asMap()
try {
multimap.asMap().remove(sampleKey);
fail("asMap().remove() succeeded on unmodifiable multimap");
} catch (UnsupportedOperationException expected) {
}
assertMultimapRemainsUnmodified(multimap, originalEntries);
if (!multimap.isEmpty()) {
K presentKey = multimap.keySet().iterator().next();
try {
multimap.asMap().get(presentKey).remove(sampleValue);
fail("asMap().get().remove() succeeded on unmodifiable multimap");
} catch (UnsupportedOperationException expected) {
}
assertMultimapRemainsUnmodified(multimap, originalEntries);
try {
multimap.asMap().values().iterator().next().remove(sampleValue);
fail("asMap().values().iterator().next().remove() succeeded on " +
"unmodifiable multimap");
} catch (UnsupportedOperationException expected) {
}
try {
((Collection>) multimap.asMap().values().toArray()[0]).clear();
fail("asMap().values().toArray()[0].clear() succeeded on " +
"unmodifiable multimap");
} catch (UnsupportedOperationException expected) {
}
}
assertCollectionIsUnmodifiable(multimap.values(), sampleValue);
assertMultimapRemainsUnmodified(multimap, originalEntries);
}
private static void assertCollectionsAreEquivalent(
Collection expected, Collection actual) {
assertIteratorsInOrder(expected.iterator(), actual.iterator());
}
private static void assertMultimapRemainsUnmodified(
Multimap expected, List> actual) {
assertIteratorsInOrder(
expected.entries().iterator(), actual.iterator());
}
}