001/*
002 * Copyright (C) 2007 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.collect.testing.google;
018
019import static junit.framework.TestCase.assertEquals;
020import static junit.framework.TestCase.assertTrue;
021import static junit.framework.TestCase.fail;
022
023import com.google.common.annotations.GwtCompatible;
024import com.google.common.collect.ArrayListMultimap;
025import com.google.common.collect.Iterators;
026import com.google.common.collect.LinkedHashMultiset;
027import com.google.common.collect.Lists;
028import com.google.common.collect.Maps;
029import com.google.common.collect.Multimap;
030import com.google.common.collect.Multiset;
031import java.util.ArrayList;
032import java.util.Collection;
033import java.util.Collections;
034import java.util.Iterator;
035import java.util.List;
036import java.util.Map.Entry;
037import java.util.Set;
038
039/**
040 * A series of tests that support asserting that collections cannot be modified, either through
041 * direct or indirect means.
042 *
043 * @author Robert Konigsberg
044 */
045@GwtCompatible
046public class UnmodifiableCollectionTests {
047
048  public static void assertMapEntryIsUnmodifiable(Entry<?, ?> entry) {
049    try {
050      entry.setValue(null);
051      fail("setValue on unmodifiable Map.Entry succeeded");
052    } catch (UnsupportedOperationException expected) {
053    }
054  }
055
056  /**
057   * Verifies that an Iterator is unmodifiable.
058   *
059   * <p>This test only works with iterators that iterate over a finite set.
060   */
061  public static void assertIteratorIsUnmodifiable(Iterator<?> iterator) {
062    while (iterator.hasNext()) {
063      iterator.next();
064      try {
065        iterator.remove();
066        fail("Remove on unmodifiable iterator succeeded");
067      } catch (UnsupportedOperationException expected) {
068      }
069    }
070  }
071
072  /**
073   * Asserts that two iterators contain elements in tandem.
074   *
075   * <p>This test only works with iterators that iterate over a finite set.
076   */
077  public static void assertIteratorsInOrder(
078      Iterator<?> expectedIterator, Iterator<?> actualIterator) {
079    int i = 0;
080    while (expectedIterator.hasNext()) {
081      Object expected = expectedIterator.next();
082
083      assertTrue(
084          "index " + i + " expected <" + expected + "., actual is exhausted",
085          actualIterator.hasNext());
086
087      Object actual = actualIterator.next();
088      assertEquals("index " + i, expected, actual);
089      i++;
090    }
091    if (actualIterator.hasNext()) {
092      fail("index " + i + ", expected is exhausted, actual <" + actualIterator.next() + ">");
093    }
094  }
095
096  /**
097   * Verifies that a collection is immutable.
098   *
099   * <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   */
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   */
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   */
204  public static <E> void assertMultisetIsUnmodifiable(Multiset<E> multiset, 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    try {
228      multiset.removeIf(x -> false);
229      fail("removeIf(Predicate) succeeded on unmodifiable collection");
230    } catch (UnsupportedOperationException expected) {
231    }
232    assertCollectionsAreEquivalent(multiset, copy);
233
234    assertSetIsUnmodifiable(multiset.elementSet(), sampleElement);
235    assertCollectionsAreEquivalent(multiset, copy);
236
237    assertSetIsUnmodifiable(
238        multiset.entrySet(),
239        new Multiset.Entry<E>() {
240          @Override
241          public int getCount() {
242            return 1;
243          }
244
245          @Override
246          public E getElement() {
247            return sampleElement;
248          }
249        });
250    assertCollectionsAreEquivalent(multiset, copy);
251  }
252
253  /**
254   * Verifies that a multimap is immutable.
255   *
256   * <p>A multimap is considered immutable if:
257   *
258   * <ol>
259   *   <li>All its mutation methods result in UnsupportedOperationException, and do not change the
260   *       underlying contents.
261   *   <li>All methods that return objects that can indirectly mutate the multimap throw
262   *       UnsupportedOperationException when those mutators
263   * </ol>
264   *
265   * @param multimap the presumed-immutable multimap
266   * @param sampleKey a key of the same type as that contained by {@code multimap}. {@code multimap}
267   *     may or may not have {@code sampleKey} as a key.
268   * @param sampleValue a key of the same type as that contained by {@code multimap}. {@code
269   *     multimap} may or may not have {@code sampleValue} as a key.
270   */
271  public static <K, V> void assertMultimapIsUnmodifiable(
272      Multimap<K, V> multimap, K sampleKey, V sampleValue) {
273    List<Entry<K, V>> originalEntries =
274        Collections.unmodifiableList(Lists.newArrayList(multimap.entries()));
275
276    assertMultimapRemainsUnmodified(multimap, originalEntries);
277
278    Collection<V> sampleValueAsCollection = Collections.singleton(sampleValue);
279
280    // Test #clear()
281    try {
282      multimap.clear();
283      fail("clear succeeded on unmodifiable multimap");
284    } catch (UnsupportedOperationException expected) {
285    }
286
287    assertMultimapRemainsUnmodified(multimap, originalEntries);
288
289    // Test asMap().entrySet()
290    assertSetIsUnmodifiable(
291        multimap.asMap().entrySet(), Maps.immutableEntry(sampleKey, sampleValueAsCollection));
292
293    // Test #values()
294
295    assertMultimapRemainsUnmodified(multimap, originalEntries);
296    if (!multimap.isEmpty()) {
297      Collection<V> values = multimap.asMap().entrySet().iterator().next().getValue();
298
299      assertCollectionIsUnmodifiable(values, sampleValue);
300    }
301
302    // Test #entries()
303    assertCollectionIsUnmodifiable(multimap.entries(), Maps.immutableEntry(sampleKey, sampleValue));
304    assertMultimapRemainsUnmodified(multimap, originalEntries);
305
306    // Iterate over every element in the entry set
307    for (Entry<K, V> entry : multimap.entries()) {
308      assertMapEntryIsUnmodifiable(entry);
309    }
310    assertMultimapRemainsUnmodified(multimap, originalEntries);
311
312    // Test #keys()
313    assertMultisetIsUnmodifiable(multimap.keys(), sampleKey);
314    assertMultimapRemainsUnmodified(multimap, originalEntries);
315
316    // Test #keySet()
317    assertSetIsUnmodifiable(multimap.keySet(), sampleKey);
318    assertMultimapRemainsUnmodified(multimap, originalEntries);
319
320    // Test #get()
321    if (!multimap.isEmpty()) {
322      K key = multimap.keySet().iterator().next();
323      assertCollectionIsUnmodifiable(multimap.get(key), sampleValue);
324      assertMultimapRemainsUnmodified(multimap, originalEntries);
325    }
326
327    // Test #put()
328    try {
329      multimap.put(sampleKey, sampleValue);
330      fail("put succeeded on unmodifiable multimap");
331    } catch (UnsupportedOperationException expected) {
332    }
333    assertMultimapRemainsUnmodified(multimap, originalEntries);
334
335    // Test #putAll(K, Collection<V>)
336    try {
337      multimap.putAll(sampleKey, sampleValueAsCollection);
338      fail("putAll(K, Iterable) succeeded on unmodifiable multimap");
339    } catch (UnsupportedOperationException expected) {
340    }
341    assertMultimapRemainsUnmodified(multimap, originalEntries);
342
343    // Test #putAll(Multimap<K, V>)
344    Multimap<K, V> multimap2 = ArrayListMultimap.create();
345    multimap2.put(sampleKey, sampleValue);
346    try {
347      multimap.putAll(multimap2);
348      fail("putAll(Multimap<K, V>) succeeded on unmodifiable multimap");
349    } catch (UnsupportedOperationException expected) {
350    }
351    assertMultimapRemainsUnmodified(multimap, originalEntries);
352
353    // Test #remove()
354    try {
355      multimap.remove(sampleKey, sampleValue);
356      fail("remove succeeded on unmodifiable multimap");
357    } catch (UnsupportedOperationException expected) {
358    }
359    assertMultimapRemainsUnmodified(multimap, originalEntries);
360
361    // Test #removeAll()
362    try {
363      multimap.removeAll(sampleKey);
364      fail("removeAll succeeded on unmodifiable multimap");
365    } catch (UnsupportedOperationException expected) {
366    }
367    assertMultimapRemainsUnmodified(multimap, originalEntries);
368
369    // Test #replaceValues()
370    try {
371      multimap.replaceValues(sampleKey, sampleValueAsCollection);
372      fail("replaceValues succeeded on unmodifiable multimap");
373    } catch (UnsupportedOperationException expected) {
374    }
375    assertMultimapRemainsUnmodified(multimap, originalEntries);
376
377    // Test #asMap()
378    try {
379      multimap.asMap().remove(sampleKey);
380      fail("asMap().remove() succeeded on unmodifiable multimap");
381    } catch (UnsupportedOperationException expected) {
382    }
383    assertMultimapRemainsUnmodified(multimap, originalEntries);
384
385    if (!multimap.isEmpty()) {
386      K presentKey = multimap.keySet().iterator().next();
387      try {
388        multimap.asMap().get(presentKey).remove(sampleValue);
389        fail("asMap().get().remove() succeeded on unmodifiable multimap");
390      } catch (UnsupportedOperationException expected) {
391      }
392      assertMultimapRemainsUnmodified(multimap, originalEntries);
393
394      try {
395        multimap.asMap().values().iterator().next().remove(sampleValue);
396        fail("asMap().values().iterator().next().remove() succeeded on unmodifiable multimap");
397      } catch (UnsupportedOperationException expected) {
398      }
399
400      try {
401        ((Collection<?>) multimap.asMap().values().toArray()[0]).clear();
402        fail("asMap().values().toArray()[0].clear() succeeded on unmodifiable multimap");
403      } catch (UnsupportedOperationException expected) {
404      }
405    }
406
407    assertCollectionIsUnmodifiable(multimap.values(), sampleValue);
408    assertMultimapRemainsUnmodified(multimap, originalEntries);
409  }
410
411  private static <E> void assertCollectionsAreEquivalent(
412      Collection<E> expected, Collection<E> actual) {
413    assertIteratorsInOrder(expected.iterator(), actual.iterator());
414  }
415
416  private static <K, V> void assertMultimapRemainsUnmodified(
417      Multimap<K, V> expected, List<Entry<K, V>> actual) {
418    assertIteratorsInOrder(expected.entries().iterator(), actual.iterator());
419  }
420}