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