View Javadoc
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.Iterators;
26  import com.google.common.collect.LinkedHashMultiset;
27  import com.google.common.collect.Lists;
28  import com.google.common.collect.Maps;
29  import com.google.common.collect.Multimap;
30  import com.google.common.collect.Multiset;
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  
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     */
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     */
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 + ", expected is exhausted, actual <" + actualIterator.next() + ">");
93      }
94    }
95  
96    /**
97     * Verifies that a collection is immutable.
98     *
99     * <p>A collection is considered immutable if:
100    * <ol>
101    * <li>All its mutation methods result in UnsupportedOperationException, and
102    * do not change the underlying contents.
103    * <li>All methods that return objects that can indirectly mutate the
104    * collection throw UnsupportedOperationException when those mutators
105    * are called.
106    * </ol>
107    *
108    * @param collection the presumed-immutable collection
109    * @param sampleElement an element of the same type as that contained by
110    * {@code collection}. {@code collection} may or may not have {@code
111    * sampleElement} as a member.
112    */
113   public static <E> void assertCollectionIsUnmodifiable(Collection<E> collection, E sampleElement) {
114     Collection<E> siblingCollection = new ArrayList<>();
115     siblingCollection.add(sampleElement);
116 
117     Collection<E> copy = new ArrayList<>();
118     // Avoid copy.addAll(collection), which runs afoul of an Android bug in older versions:
119     // http://b.android.com/72073 http://r.android.com/98929
120     Iterators.addAll(copy, collection.iterator());
121 
122     try {
123       collection.add(sampleElement);
124       fail("add succeeded on unmodifiable collection");
125     } catch (UnsupportedOperationException expected) {
126     }
127 
128     assertCollectionsAreEquivalent(copy, collection);
129 
130     try {
131       collection.addAll(siblingCollection);
132       fail("addAll succeeded on unmodifiable collection");
133     } catch (UnsupportedOperationException expected) {
134     }
135     assertCollectionsAreEquivalent(copy, collection);
136 
137     try {
138       collection.clear();
139       fail("clear succeeded on unmodifiable collection");
140     } catch (UnsupportedOperationException expected) {
141     }
142     assertCollectionsAreEquivalent(copy, collection);
143 
144     assertIteratorIsUnmodifiable(collection.iterator());
145     assertCollectionsAreEquivalent(copy, collection);
146 
147     try {
148       collection.remove(sampleElement);
149       fail("remove succeeded on unmodifiable collection");
150     } catch (UnsupportedOperationException expected) {
151     }
152     assertCollectionsAreEquivalent(copy, collection);
153 
154     try {
155       collection.removeAll(siblingCollection);
156       fail("removeAll succeeded on unmodifiable collection");
157     } catch (UnsupportedOperationException expected) {
158     }
159     assertCollectionsAreEquivalent(copy, collection);
160 
161     try {
162       collection.retainAll(siblingCollection);
163       fail("retainAll succeeded on unmodifiable collection");
164     } catch (UnsupportedOperationException expected) {
165     }
166     assertCollectionsAreEquivalent(copy, collection);
167   }
168 
169   /**
170    * Verifies that a set is immutable.
171    *
172    * <p>A set is considered immutable if:
173    * <ol>
174    * <li>All its mutation methods result in UnsupportedOperationException, and
175    * do not change the underlying contents.
176    * <li>All methods that return objects that can indirectly mutate the
177    * set throw UnsupportedOperationException when those mutators
178    * are called.
179    * </ol>
180    *
181    * @param set the presumed-immutable set
182    * @param sampleElement an element of the same type as that contained by
183    * {@code set}. {@code set} may or may not have {@code sampleElement} as a
184    * member.
185    */
186   public static <E> void assertSetIsUnmodifiable(Set<E> set, E sampleElement) {
187     assertCollectionIsUnmodifiable(set, sampleElement);
188   }
189 
190   /**
191    * Verifies that a multiset is immutable.
192    *
193    * <p>A multiset is considered immutable if:
194    * <ol>
195    * <li>All its mutation methods result in UnsupportedOperationException, and
196    * do not change the underlying contents.
197    * <li>All methods that return objects that can indirectly mutate the
198    * multiset throw UnsupportedOperationException when those mutators
199    * are called.
200    * </ol>
201    *
202    * @param multiset the presumed-immutable multiset
203    * @param sampleElement an element of the same type as that contained by
204    * {@code multiset}. {@code multiset} may or may not have {@code
205    * sampleElement} as a member.
206    */
207   public static <E> void assertMultisetIsUnmodifiable(Multiset<E> multiset, 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(),
237         new Multiset.Entry<E>() {
238           @Override
239           public int getCount() {
240             return 1;
241           }
242 
243           @Override
244           public E getElement() {
245             return sampleElement;
246           }
247         });
248     assertCollectionsAreEquivalent(multiset, copy);
249   }
250 
251   /**
252    * Verifies that a multimap is immutable.
253    *
254    * <p>A multimap is considered immutable if:
255    * <ol>
256    * <li>All its mutation methods result in UnsupportedOperationException, and
257    * do not change the underlying contents.
258    * <li>All methods that return objects that can indirectly mutate the
259    * multimap throw UnsupportedOperationException when those mutators
260    * </ol>
261    *
262    * @param multimap the presumed-immutable multimap
263    * @param sampleKey a key of the same type as that contained by
264    * {@code multimap}. {@code multimap} may or may not have {@code sampleKey} as
265    * a key.
266    * @param sampleValue a key of the same type as that contained by
267    * {@code multimap}. {@code multimap} may or may not have {@code sampleValue}
268    * as a key.
269    */
270   public static <K, V> void assertMultimapIsUnmodifiable(
271       Multimap<K, V> multimap, final K sampleKey, final V sampleValue) {
272     List<Entry<K, V>> originalEntries =
273         Collections.unmodifiableList(Lists.newArrayList(multimap.entries()));
274 
275     assertMultimapRemainsUnmodified(multimap, originalEntries);
276 
277     Collection<V> sampleValueAsCollection = Collections.singleton(sampleValue);
278 
279     // Test #clear()
280     try {
281       multimap.clear();
282       fail("clear succeeded on unmodifiable multimap");
283     } catch (UnsupportedOperationException expected) {
284     }
285 
286     assertMultimapRemainsUnmodified(multimap, originalEntries);
287 
288     // Test asMap().entrySet()
289     assertSetIsUnmodifiable(
290         multimap.asMap().entrySet(), Maps.immutableEntry(sampleKey, sampleValueAsCollection));
291 
292     // Test #values()
293 
294     assertMultimapRemainsUnmodified(multimap, originalEntries);
295     if (!multimap.isEmpty()) {
296       Collection<V> values = multimap.asMap().entrySet().iterator().next().getValue();
297 
298       assertCollectionIsUnmodifiable(values, sampleValue);
299     }
300 
301     // Test #entries()
302     assertCollectionIsUnmodifiable(multimap.entries(), Maps.immutableEntry(sampleKey, sampleValue));
303     assertMultimapRemainsUnmodified(multimap, originalEntries);
304 
305     // Iterate over every element in the entry set
306     for (Entry<K, V> entry : multimap.entries()) {
307       assertMapEntryIsUnmodifiable(entry);
308     }
309     assertMultimapRemainsUnmodified(multimap, originalEntries);
310 
311     // Test #keys()
312     assertMultisetIsUnmodifiable(multimap.keys(), sampleKey);
313     assertMultimapRemainsUnmodified(multimap, originalEntries);
314 
315     // Test #keySet()
316     assertSetIsUnmodifiable(multimap.keySet(), sampleKey);
317     assertMultimapRemainsUnmodified(multimap, originalEntries);
318 
319     // Test #get()
320     if (!multimap.isEmpty()) {
321       K key = multimap.keySet().iterator().next();
322       assertCollectionIsUnmodifiable(multimap.get(key), sampleValue);
323       assertMultimapRemainsUnmodified(multimap, originalEntries);
324     }
325 
326     // Test #put()
327     try {
328       multimap.put(sampleKey, sampleValue);
329       fail("put succeeded on unmodifiable multimap");
330     } catch (UnsupportedOperationException expected) {
331     }
332     assertMultimapRemainsUnmodified(multimap, originalEntries);
333 
334     // Test #putAll(K, Collection<V>)
335     try {
336       multimap.putAll(sampleKey, sampleValueAsCollection);
337       fail("putAll(K, Iterable) succeeded on unmodifiable multimap");
338     } catch (UnsupportedOperationException expected) {
339     }
340     assertMultimapRemainsUnmodified(multimap, originalEntries);
341 
342     // Test #putAll(Multimap<K, V>)
343     Multimap<K, V> multimap2 = ArrayListMultimap.create();
344     multimap2.put(sampleKey, sampleValue);
345     try {
346       multimap.putAll(multimap2);
347       fail("putAll(Multimap<K, V>) succeeded on unmodifiable multimap");
348     } catch (UnsupportedOperationException expected) {
349     }
350     assertMultimapRemainsUnmodified(multimap, originalEntries);
351 
352     // Test #remove()
353     try {
354       multimap.remove(sampleKey, sampleValue);
355       fail("remove succeeded on unmodifiable multimap");
356     } catch (UnsupportedOperationException expected) {
357     }
358     assertMultimapRemainsUnmodified(multimap, originalEntries);
359 
360     // Test #removeAll()
361     try {
362       multimap.removeAll(sampleKey);
363       fail("removeAll succeeded on unmodifiable multimap");
364     } catch (UnsupportedOperationException expected) {
365     }
366     assertMultimapRemainsUnmodified(multimap, originalEntries);
367 
368     // Test #replaceValues()
369     try {
370       multimap.replaceValues(sampleKey, sampleValueAsCollection);
371       fail("replaceValues succeeded on unmodifiable multimap");
372     } catch (UnsupportedOperationException expected) {
373     }
374     assertMultimapRemainsUnmodified(multimap, originalEntries);
375 
376     // Test #asMap()
377     try {
378       multimap.asMap().remove(sampleKey);
379       fail("asMap().remove() succeeded on unmodifiable multimap");
380     } catch (UnsupportedOperationException expected) {
381     }
382     assertMultimapRemainsUnmodified(multimap, originalEntries);
383 
384     if (!multimap.isEmpty()) {
385       K presentKey = multimap.keySet().iterator().next();
386       try {
387         multimap.asMap().get(presentKey).remove(sampleValue);
388         fail("asMap().get().remove() succeeded on unmodifiable multimap");
389       } catch (UnsupportedOperationException expected) {
390       }
391       assertMultimapRemainsUnmodified(multimap, originalEntries);
392 
393       try {
394         multimap.asMap().values().iterator().next().remove(sampleValue);
395         fail("asMap().values().iterator().next().remove() succeeded on unmodifiable multimap");
396       } catch (UnsupportedOperationException expected) {
397       }
398 
399       try {
400         ((Collection<?>) multimap.asMap().values().toArray()[0]).clear();
401         fail("asMap().values().toArray()[0].clear() succeeded on unmodifiable multimap");
402       } catch (UnsupportedOperationException expected) {
403       }
404     }
405 
406     assertCollectionIsUnmodifiable(multimap.values(), sampleValue);
407     assertMultimapRemainsUnmodified(multimap, originalEntries);
408   }
409 
410   private static <E> void assertCollectionsAreEquivalent(
411       Collection<E> expected, Collection<E> actual) {
412     assertIteratorsInOrder(expected.iterator(), actual.iterator());
413   }
414 
415   private static <K, V> void assertMultimapRemainsUnmodified(
416       Multimap<K, V> expected, List<Entry<K, V>> actual) {
417     assertIteratorsInOrder(expected.entries().iterator(), actual.iterator());
418   }
419 }