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, 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   */
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
406  private static <E> void assertCollectionsAreEquivalent(
407      Collection<E> expected, Collection<E> actual) {
408    assertIteratorsInOrder(expected.iterator(), actual.iterator());
409  }
410
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}