1 /*
2  * Copyright (C) 2007 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.collect.testing.google;
18 
19 import static junit.framework.TestCase.assertEquals;
20 import static junit.framework.TestCase.assertTrue;
21 import static junit.framework.TestCase.fail;
22 
23 import com.google.common.annotations.GwtCompatible;
24 import com.google.common.collect.ArrayListMultimap;
25 import com.google.common.collect.LinkedHashMultiset;
26 import com.google.common.collect.Lists;
27 import com.google.common.collect.Maps;
28 import com.google.common.collect.Multimap;
29 import com.google.common.collect.Multiset;
30 
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.Collections;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map.Entry;
37 import java.util.Set;
38 
39 /**
40  * A series of tests that support asserting that collections cannot be
41  * modified, either through direct or indirect means.
42  *
43  * @author Robert Konigsberg
44  */
45 @GwtCompatible
46 public class UnmodifiableCollectionTests {
47 
assertMapEntryIsUnmodifiable(Entry<?, ?> entry)48   public static void assertMapEntryIsUnmodifiable(Entry<?, ?> entry) {
49     try {
50       entry.setValue(null);
51       fail("setValue on unmodifiable Map.Entry succeeded");
52     } catch (UnsupportedOperationException expected) {
53     }
54   }
55 
56   /**
57    * Verifies that an Iterator is unmodifiable.
58    *
59    * <p>This test only works with iterators that iterate over a finite set.
60    */
assertIteratorIsUnmodifiable(Iterator<?> iterator)61   public static void assertIteratorIsUnmodifiable(Iterator<?> iterator) {
62     while (iterator.hasNext()) {
63       iterator.next();
64       try {
65         iterator.remove();
66         fail("Remove on unmodifiable iterator succeeded");
67       } catch (UnsupportedOperationException expected) {
68       }
69     }
70   }
71 
72   /**
73    * Asserts that two iterators contain elements in tandem.
74    *
75    * <p>This test only works with iterators that iterate over a finite set.
76    */
assertIteratorsInOrder( Iterator<?> expectedIterator, Iterator<?> actualIterator)77   public static void assertIteratorsInOrder(
78       Iterator<?> expectedIterator, Iterator<?> actualIterator) {
79     int i = 0;
80     while (expectedIterator.hasNext()) {
81       Object expected = expectedIterator.next();
82 
83       assertTrue(
84           "index " + i + " expected <" + expected + "., actual is exhausted",
85         actualIterator.hasNext());
86 
87       Object actual = actualIterator.next();
88       assertEquals("index " + i, expected, actual);
89       i++;
90     }
91     if (actualIterator.hasNext()) {
92       fail("index " + i
93           + ", expected is exhausted, actual <" + actualIterator.next() + ">");
94     }
95   }
96 
97   /**
98    * Verifies that a collection is immutable.
99    *
100    * <p>A collection is considered immutable if:
101    * <ol>
102    * <li>All its mutation methods result in UnsupportedOperationException, and
103    * do not change the underlying contents.
104    * <li>All methods that return objects that can indirectly mutate the
105    * collection throw UnsupportedOperationException when those mutators
106    * are called.
107    * </ol>
108    *
109    * @param collection the presumed-immutable collection
110    * @param sampleElement an element of the same type as that contained by
111    * {@code collection}. {@code collection} may or may not have {@code
112    * sampleElement} as a member.
113    */
assertCollectionIsUnmodifiable( Collection<E> collection, E sampleElement)114   public static <E> void assertCollectionIsUnmodifiable(
115       Collection<E> collection, E sampleElement) {
116     Collection<E> siblingCollection = new ArrayList<E>();
117     siblingCollection.add(sampleElement);
118     Collection<E> copy = new ArrayList<E>();
119     copy.addAll(collection);
120     try {
121       collection.add(sampleElement);
122       fail("add succeeded on unmodifiable collection");
123     } catch (UnsupportedOperationException expected) {
124     }
125 
126     assertCollectionsAreEquivalent(copy, collection);
127 
128     try {
129       collection.addAll(siblingCollection);
130       fail("addAll succeeded on unmodifiable collection");
131     } catch (UnsupportedOperationException expected) {
132     }
133     assertCollectionsAreEquivalent(copy, collection);
134 
135     try {
136       collection.clear();
137       fail("clear succeeded on unmodifiable collection");
138     } catch (UnsupportedOperationException expected) {
139     }
140     assertCollectionsAreEquivalent(copy, collection);
141 
142     assertIteratorIsUnmodifiable(collection.iterator());
143     assertCollectionsAreEquivalent(copy, collection);
144 
145     try {
146       collection.remove(sampleElement);
147       fail("remove succeeded on unmodifiable collection");
148     } catch (UnsupportedOperationException expected) {
149     }
150     assertCollectionsAreEquivalent(copy, collection);
151 
152     try {
153       collection.removeAll(siblingCollection);
154       fail("removeAll succeeded on unmodifiable collection");
155     } catch (UnsupportedOperationException expected) {
156     }
157     assertCollectionsAreEquivalent(copy, collection);
158 
159     try {
160       collection.retainAll(siblingCollection);
161       fail("retainAll succeeded on unmodifiable collection");
162     } catch (UnsupportedOperationException expected) {
163     }
164     assertCollectionsAreEquivalent(copy, collection);
165   }
166 
167   /**
168    * Verifies that a set is immutable.
169    *
170    * <p>A set is considered immutable if:
171    * <ol>
172    * <li>All its mutation methods result in UnsupportedOperationException, and
173    * do not change the underlying contents.
174    * <li>All methods that return objects that can indirectly mutate the
175    * set throw UnsupportedOperationException when those mutators
176    * are called.
177    * </ol>
178    *
179    * @param set the presumed-immutable set
180    * @param sampleElement an element of the same type as that contained by
181    * {@code set}. {@code set} may or may not have {@code sampleElement} as a
182    * member.
183    */
assertSetIsUnmodifiable( Set<E> set, E sampleElement)184   public static <E> void assertSetIsUnmodifiable(
185       Set<E> set, E sampleElement) {
186     assertCollectionIsUnmodifiable(set, sampleElement);
187   }
188 
189   /**
190    * Verifies that a multiset is immutable.
191    *
192    * <p>A multiset is considered immutable if:
193    * <ol>
194    * <li>All its mutation methods result in UnsupportedOperationException, and
195    * do not change the underlying contents.
196    * <li>All methods that return objects that can indirectly mutate the
197    * multiset throw UnsupportedOperationException when those mutators
198    * are called.
199    * </ol>
200    *
201    * @param multiset the presumed-immutable multiset
202    * @param sampleElement an element of the same type as that contained by
203    * {@code multiset}. {@code multiset} may or may not have {@code
204    * sampleElement} as a member.
205    */
assertMultisetIsUnmodifiable(Multiset<E> multiset, final E sampleElement)206   public static <E> void assertMultisetIsUnmodifiable(Multiset<E> multiset,
207       final E sampleElement) {
208     Multiset<E> copy = LinkedHashMultiset.create(multiset);
209     assertCollectionsAreEquivalent(multiset, copy);
210 
211     // Multiset is a collection, so we can use all those tests.
212     assertCollectionIsUnmodifiable(multiset, sampleElement);
213 
214     assertCollectionsAreEquivalent(multiset, copy);
215 
216     try {
217       multiset.add(sampleElement, 2);
218       fail("add(Object, int) succeeded on unmodifiable collection");
219     } catch (UnsupportedOperationException expected) {
220     }
221     assertCollectionsAreEquivalent(multiset, copy);
222 
223     try {
224       multiset.remove(sampleElement, 2);
225       fail("remove(Object, int) succeeded on unmodifiable collection");
226     } catch (UnsupportedOperationException expected) {
227     }
228     assertCollectionsAreEquivalent(multiset, copy);
229 
230     assertCollectionsAreEquivalent(multiset, copy);
231 
232     assertSetIsUnmodifiable(multiset.elementSet(), sampleElement);
233     assertCollectionsAreEquivalent(multiset, copy);
234 
235     assertSetIsUnmodifiable(
236       multiset.entrySet(), new Multiset.Entry<E>() {
237         @Override
238         public int getCount() {
239           return 1;
240         }
241 
242         @Override
243         public E getElement() {
244           return sampleElement;
245         }
246       });
247     assertCollectionsAreEquivalent(multiset, copy);
248   }
249 
250   /**
251    * Verifies that a multimap is immutable.
252    *
253    * <p>A multimap is considered immutable if:
254    * <ol>
255    * <li>All its mutation methods result in UnsupportedOperationException, and
256    * do not change the underlying contents.
257    * <li>All methods that return objects that can indirectly mutate the
258    * multimap throw UnsupportedOperationException when those mutators
259    * </ol>
260    *
261    * @param multimap the presumed-immutable multimap
262    * @param sampleKey a key of the same type as that contained by
263    * {@code multimap}. {@code multimap} may or may not have {@code sampleKey} as
264    * a key.
265    * @param sampleValue a key of the same type as that contained by
266    * {@code multimap}. {@code multimap} may or may not have {@code sampleValue}
267    * as a key.
268    */
assertMultimapIsUnmodifiable( Multimap<K, V> multimap, final K sampleKey, final V sampleValue)269   public static <K, V> void assertMultimapIsUnmodifiable(
270       Multimap<K, V> multimap, final K sampleKey, final V sampleValue) {
271     List<Entry<K, V>> originalEntries =
272         Collections.unmodifiableList(Lists.newArrayList(multimap.entries()));
273 
274     assertMultimapRemainsUnmodified(multimap, originalEntries);
275 
276     Collection<V> sampleValueAsCollection = Collections.singleton(sampleValue);
277 
278     // Test #clear()
279     try {
280       multimap.clear();
281       fail("clear succeeded on unmodifiable multimap");
282     } catch (UnsupportedOperationException expected) {
283     }
284 
285     assertMultimapRemainsUnmodified(multimap, originalEntries);
286 
287     // Test asMap().entrySet()
288     assertSetIsUnmodifiable(
289       multimap.asMap().entrySet(),
290       Maps.immutableEntry(sampleKey, sampleValueAsCollection));
291 
292     // Test #values()
293 
294     assertMultimapRemainsUnmodified(multimap, originalEntries);
295     if (!multimap.isEmpty()) {
296       Collection<V> values =
297           multimap.asMap().entrySet().iterator().next().getValue();
298 
299       assertCollectionIsUnmodifiable(values, sampleValue);
300     }
301 
302     // Test #entries()
303     assertCollectionIsUnmodifiable(
304       multimap.entries(),
305       Maps.immutableEntry(sampleKey, sampleValue));
306     assertMultimapRemainsUnmodified(multimap, originalEntries);
307 
308     // Iterate over every element in the entry set
309     for (Entry<K, V> entry : multimap.entries()) {
310       assertMapEntryIsUnmodifiable(entry);
311     }
312     assertMultimapRemainsUnmodified(multimap, originalEntries);
313 
314     // Test #keys()
315     assertMultisetIsUnmodifiable(multimap.keys(), sampleKey);
316     assertMultimapRemainsUnmodified(multimap, originalEntries);
317 
318     // Test #keySet()
319     assertSetIsUnmodifiable(multimap.keySet(), sampleKey);
320     assertMultimapRemainsUnmodified(multimap, originalEntries);
321 
322     // Test #get()
323     if (!multimap.isEmpty()) {
324       K key = multimap.keySet().iterator().next();
325       assertCollectionIsUnmodifiable(multimap.get(key), sampleValue);
326       assertMultimapRemainsUnmodified(multimap, originalEntries);
327     }
328 
329     // Test #put()
330     try {
331       multimap.put(sampleKey, sampleValue);
332       fail("put succeeded on unmodifiable multimap");
333     } catch (UnsupportedOperationException expected) {
334     }
335     assertMultimapRemainsUnmodified(multimap, originalEntries);
336 
337     // Test #putAll(K, Collection<V>)
338     try {
339       multimap.putAll(sampleKey, sampleValueAsCollection);
340       fail("putAll(K, Iterable) succeeded on unmodifiable multimap");
341     } catch (UnsupportedOperationException expected) {
342     }
343     assertMultimapRemainsUnmodified(multimap, originalEntries);
344 
345     // Test #putAll(Multimap<K, V>)
346     Multimap<K, V> multimap2 = ArrayListMultimap.create();
347     multimap2.put(sampleKey, sampleValue);
348     try {
349       multimap.putAll(multimap2);
350       fail("putAll(Multimap<K, V>) succeeded on unmodifiable multimap");
351     } catch (UnsupportedOperationException expected) {
352     }
353     assertMultimapRemainsUnmodified(multimap, originalEntries);
354 
355     // Test #remove()
356     try {
357       multimap.remove(sampleKey, sampleValue);
358       fail("remove succeeded on unmodifiable multimap");
359     } catch (UnsupportedOperationException expected) {
360     }
361     assertMultimapRemainsUnmodified(multimap, originalEntries);
362 
363     // Test #removeAll()
364     try {
365       multimap.removeAll(sampleKey);
366       fail("removeAll succeeded on unmodifiable multimap");
367     } catch (UnsupportedOperationException expected) {
368     }
369     assertMultimapRemainsUnmodified(multimap, originalEntries);
370 
371     // Test #replaceValues()
372     try {
373       multimap.replaceValues(sampleKey, sampleValueAsCollection);
374       fail("replaceValues succeeded on unmodifiable multimap");
375     } catch (UnsupportedOperationException expected) {
376     }
377     assertMultimapRemainsUnmodified(multimap, originalEntries);
378 
379     // Test #asMap()
380     try {
381       multimap.asMap().remove(sampleKey);
382       fail("asMap().remove() succeeded on unmodifiable multimap");
383     } catch (UnsupportedOperationException expected) {
384     }
385     assertMultimapRemainsUnmodified(multimap, originalEntries);
386 
387     if (!multimap.isEmpty()) {
388       K presentKey = multimap.keySet().iterator().next();
389       try {
390         multimap.asMap().get(presentKey).remove(sampleValue);
391         fail("asMap().get().remove() succeeded on unmodifiable multimap");
392       } catch (UnsupportedOperationException expected) {
393       }
394       assertMultimapRemainsUnmodified(multimap, originalEntries);
395 
396       try {
397         multimap.asMap().values().iterator().next().remove(sampleValue);
398         fail("asMap().values().iterator().next().remove() succeeded on " +
399                 "unmodifiable multimap");
400       } catch (UnsupportedOperationException expected) {
401       }
402 
403       try {
404         ((Collection<?>) multimap.asMap().values().toArray()[0]).clear();
405         fail("asMap().values().toArray()[0].clear() succeeded on " +
406                 "unmodifiable multimap");
407       } catch (UnsupportedOperationException expected) {
408       }
409     }
410 
411     assertCollectionIsUnmodifiable(multimap.values(), sampleValue);
412     assertMultimapRemainsUnmodified(multimap, originalEntries);
413   }
414 
assertCollectionsAreEquivalent( Collection<E> expected, Collection<E> actual)415   private static <E> void assertCollectionsAreEquivalent(
416       Collection<E> expected, Collection<E> actual) {
417     assertIteratorsInOrder(expected.iterator(), actual.iterator());
418   }
419 
assertMultimapRemainsUnmodified( Multimap<K, V> expected, List<Entry<K, V>> actual)420   private static <K, V> void assertMultimapRemainsUnmodified(
421       Multimap<K, V> expected, List<Entry<K, V>> actual) {
422     assertIteratorsInOrder(
423       expected.entries().iterator(), actual.iterator());
424   }
425 }
426