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
17package com.google.common.collect.testing.google;
18
19import static junit.framework.TestCase.assertEquals;
20import static junit.framework.TestCase.assertTrue;
21import static junit.framework.TestCase.fail;
22
23import com.google.common.annotations.GwtCompatible;
24import com.google.common.collect.ArrayListMultimap;
25import com.google.common.collect.LinkedHashMultiset;
26import com.google.common.collect.Lists;
27import com.google.common.collect.Maps;
28import com.google.common.collect.Multimap;
29import com.google.common.collect.Multiset;
30
31import java.util.ArrayList;
32import java.util.Collection;
33import java.util.Collections;
34import java.util.Iterator;
35import java.util.List;
36import java.util.Map.Entry;
37import 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
46public 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
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   */
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   */
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   */
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   */
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
415  private static <E> void assertCollectionsAreEquivalent(
416      Collection<E> expected, Collection<E> actual) {
417    assertIteratorsInOrder(expected.iterator(), actual.iterator());
418  }
419
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