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 com.google.common.collect.Maps.immutableEntry; 020import static java.util.Collections.singleton; 021import static java.util.Collections.unmodifiableList; 022import static junit.framework.TestCase.assertEquals; 023import static junit.framework.TestCase.assertTrue; 024import static junit.framework.TestCase.fail; 025 026import com.google.common.annotations.GwtCompatible; 027import com.google.common.collect.ArrayListMultimap; 028import com.google.common.collect.LinkedHashMultiset; 029import com.google.common.collect.Lists; 030import com.google.common.collect.Multimap; 031import com.google.common.collect.Multiset; 032import java.util.ArrayList; 033import java.util.Collection; 034import java.util.Iterator; 035import java.util.List; 036import java.util.Map.Entry; 037import java.util.Set; 038import org.checkerframework.checker.nullness.qual.Nullable; 039 040/** 041 * A series of tests that support asserting that collections cannot be modified, either through 042 * direct or indirect means. 043 * 044 * @author Robert Konigsberg 045 */ 046@GwtCompatible 047@ElementTypesAreNonnullByDefault 048public class UnmodifiableCollectionTests { 049 050 public static void assertMapEntryIsUnmodifiable(Entry<?, ?> entry) { 051 try { 052 // fine because the call is going to fail without modifying the entry 053 @SuppressWarnings("unchecked") 054 Entry<?, @Nullable Object> nullableValueEntry = (Entry<?, @Nullable Object>) entry; 055 nullableValueEntry.setValue(null); 056 fail("setValue on unmodifiable Map.Entry succeeded"); 057 } catch (UnsupportedOperationException expected) { 058 } 059 } 060 061 /** 062 * Verifies that an Iterator is unmodifiable. 063 * 064 * <p>This test only works with iterators that iterate over a finite set. 065 */ 066 public static void assertIteratorIsUnmodifiable(Iterator<?> iterator) { 067 while (iterator.hasNext()) { 068 iterator.next(); 069 try { 070 iterator.remove(); 071 fail("Remove on unmodifiable iterator succeeded"); 072 } catch (UnsupportedOperationException expected) { 073 } 074 } 075 } 076 077 /** 078 * Asserts that two iterators contain elements in tandem. 079 * 080 * <p>This test only works with iterators that iterate over a finite set. 081 */ 082 public static void assertIteratorsInOrder( 083 Iterator<?> expectedIterator, Iterator<?> actualIterator) { 084 int i = 0; 085 while (expectedIterator.hasNext()) { 086 Object expected = expectedIterator.next(); 087 088 assertTrue( 089 "index " + i + " expected <" + expected + "., actual is exhausted", 090 actualIterator.hasNext()); 091 092 Object actual = actualIterator.next(); 093 assertEquals("index " + i, expected, actual); 094 i++; 095 } 096 if (actualIterator.hasNext()) { 097 fail("index " + i + ", expected is exhausted, actual <" + actualIterator.next() + ">"); 098 } 099 } 100 101 /** 102 * Verifies that a collection is immutable. 103 * 104 * <p>A collection is considered immutable if: 105 * 106 * <ol> 107 * <li>All its mutation methods result in UnsupportedOperationException, and do not change the 108 * underlying contents. 109 * <li>All methods that return objects that can indirectly mutate the collection throw 110 * UnsupportedOperationException when those mutators are called. 111 * </ol> 112 * 113 * @param collection the presumed-immutable collection 114 * @param sampleElement an element of the same type as that contained by {@code collection}. 115 * {@code collection} may or may not have {@code sampleElement} as a member. 116 */ 117 public static <E extends @Nullable Object> void assertCollectionIsUnmodifiable( 118 Collection<E> collection, E sampleElement) { 119 Collection<E> siblingCollection = new ArrayList<>(); 120 siblingCollection.add(sampleElement); 121 122 Collection<E> copy = new ArrayList<>(); 123 copy.addAll(collection); 124 125 try { 126 collection.add(sampleElement); 127 fail("add succeeded on unmodifiable collection"); 128 } catch (UnsupportedOperationException expected) { 129 } 130 131 assertCollectionsAreEquivalent(copy, collection); 132 133 try { 134 collection.addAll(siblingCollection); 135 fail("addAll succeeded on unmodifiable collection"); 136 } catch (UnsupportedOperationException expected) { 137 } 138 assertCollectionsAreEquivalent(copy, collection); 139 140 try { 141 collection.clear(); 142 fail("clear succeeded on unmodifiable collection"); 143 } catch (UnsupportedOperationException expected) { 144 } 145 assertCollectionsAreEquivalent(copy, collection); 146 147 assertIteratorIsUnmodifiable(collection.iterator()); 148 assertCollectionsAreEquivalent(copy, collection); 149 150 try { 151 collection.remove(sampleElement); 152 fail("remove succeeded on unmodifiable collection"); 153 } catch (UnsupportedOperationException expected) { 154 } 155 assertCollectionsAreEquivalent(copy, collection); 156 157 try { 158 collection.removeAll(siblingCollection); 159 fail("removeAll succeeded on unmodifiable collection"); 160 } catch (UnsupportedOperationException expected) { 161 } 162 assertCollectionsAreEquivalent(copy, collection); 163 164 try { 165 collection.retainAll(siblingCollection); 166 fail("retainAll succeeded on unmodifiable collection"); 167 } catch (UnsupportedOperationException expected) { 168 } 169 assertCollectionsAreEquivalent(copy, collection); 170 } 171 172 /** 173 * Verifies that a set is immutable. 174 * 175 * <p>A set is considered immutable if: 176 * 177 * <ol> 178 * <li>All its mutation methods result in UnsupportedOperationException, and do not change the 179 * underlying contents. 180 * <li>All methods that return objects that can indirectly mutate the set throw 181 * UnsupportedOperationException when those mutators are called. 182 * </ol> 183 * 184 * @param set the presumed-immutable set 185 * @param sampleElement an element of the same type as that contained by {@code set}. {@code set} 186 * may or may not have {@code sampleElement} as a member. 187 */ 188 public static <E extends @Nullable Object> void assertSetIsUnmodifiable( 189 Set<E> set, E sampleElement) { 190 assertCollectionIsUnmodifiable(set, sampleElement); 191 } 192 193 /** 194 * Verifies that a multiset is immutable. 195 * 196 * <p>A multiset is considered immutable if: 197 * 198 * <ol> 199 * <li>All its mutation methods result in UnsupportedOperationException, and do not change the 200 * underlying contents. 201 * <li>All methods that return objects that can indirectly mutate the multiset throw 202 * UnsupportedOperationException when those mutators are called. 203 * </ol> 204 * 205 * @param multiset the presumed-immutable multiset 206 * @param sampleElement an element of the same type as that contained by {@code multiset}. {@code 207 * multiset} may or may not have {@code sampleElement} as a member. 208 */ 209 public static <E extends @Nullable Object> void assertMultisetIsUnmodifiable( 210 Multiset<E> multiset, E sampleElement) { 211 Multiset<E> copy = LinkedHashMultiset.create(multiset); 212 assertCollectionsAreEquivalent(multiset, copy); 213 214 // Multiset is a collection, so we can use all those tests. 215 assertCollectionIsUnmodifiable(multiset, sampleElement); 216 217 assertCollectionsAreEquivalent(multiset, copy); 218 219 try { 220 multiset.add(sampleElement, 2); 221 fail("add(Object, int) succeeded on unmodifiable collection"); 222 } catch (UnsupportedOperationException expected) { 223 } 224 assertCollectionsAreEquivalent(multiset, copy); 225 226 try { 227 multiset.remove(sampleElement, 2); 228 fail("remove(Object, int) succeeded on unmodifiable collection"); 229 } catch (UnsupportedOperationException expected) { 230 } 231 assertCollectionsAreEquivalent(multiset, copy); 232 233 try { 234 multiset.removeIf(x -> false); 235 fail("removeIf(Predicate) succeeded on unmodifiable collection"); 236 } catch (UnsupportedOperationException expected) { 237 } 238 assertCollectionsAreEquivalent(multiset, copy); 239 240 assertSetIsUnmodifiable(multiset.elementSet(), sampleElement); 241 assertCollectionsAreEquivalent(multiset, copy); 242 243 assertSetIsUnmodifiable( 244 multiset.entrySet(), 245 new Multiset.Entry<E>() { 246 @Override 247 public int getCount() { 248 return 1; 249 } 250 251 @Override 252 public E getElement() { 253 return sampleElement; 254 } 255 }); 256 assertCollectionsAreEquivalent(multiset, copy); 257 } 258 259 /** 260 * Verifies that a multimap is immutable. 261 * 262 * <p>A multimap is considered immutable if: 263 * 264 * <ol> 265 * <li>All its mutation methods result in UnsupportedOperationException, and do not change the 266 * underlying contents. 267 * <li>All methods that return objects that can indirectly mutate the multimap throw 268 * UnsupportedOperationException when those mutators 269 * </ol> 270 * 271 * @param multimap the presumed-immutable multimap 272 * @param sampleKey a key of the same type as that contained by {@code multimap}. {@code multimap} 273 * may or may not have {@code sampleKey} as a key. 274 * @param sampleValue a key of the same type as that contained by {@code multimap}. {@code 275 * multimap} may or may not have {@code sampleValue} as a key. 276 */ 277 public static <K extends @Nullable Object, V extends @Nullable Object> 278 void assertMultimapIsUnmodifiable(Multimap<K, V> multimap, K sampleKey, V sampleValue) { 279 List<Entry<K, V>> originalEntries = unmodifiableList(Lists.newArrayList(multimap.entries())); 280 281 assertMultimapRemainsUnmodified(multimap, originalEntries); 282 283 Collection<V> sampleValueAsCollection = singleton(sampleValue); 284 285 // Test #clear() 286 try { 287 multimap.clear(); 288 fail("clear succeeded on unmodifiable multimap"); 289 } catch (UnsupportedOperationException expected) { 290 } 291 292 assertMultimapRemainsUnmodified(multimap, originalEntries); 293 294 // Test asMap().entrySet() 295 assertSetIsUnmodifiable( 296 multimap.asMap().entrySet(), immutableEntry(sampleKey, sampleValueAsCollection)); 297 298 // Test #values() 299 300 assertMultimapRemainsUnmodified(multimap, originalEntries); 301 if (!multimap.isEmpty()) { 302 Collection<V> values = multimap.asMap().entrySet().iterator().next().getValue(); 303 304 assertCollectionIsUnmodifiable(values, sampleValue); 305 } 306 307 // Test #entries() 308 assertCollectionIsUnmodifiable(multimap.entries(), immutableEntry(sampleKey, sampleValue)); 309 assertMultimapRemainsUnmodified(multimap, originalEntries); 310 311 // Iterate over every element in the entry set 312 for (Entry<K, V> entry : multimap.entries()) { 313 assertMapEntryIsUnmodifiable(entry); 314 } 315 assertMultimapRemainsUnmodified(multimap, originalEntries); 316 317 // Test #keys() 318 assertMultisetIsUnmodifiable(multimap.keys(), sampleKey); 319 assertMultimapRemainsUnmodified(multimap, originalEntries); 320 321 // Test #keySet() 322 assertSetIsUnmodifiable(multimap.keySet(), sampleKey); 323 assertMultimapRemainsUnmodified(multimap, originalEntries); 324 325 // Test #get() 326 if (!multimap.isEmpty()) { 327 K key = multimap.keySet().iterator().next(); 328 assertCollectionIsUnmodifiable(multimap.get(key), sampleValue); 329 assertMultimapRemainsUnmodified(multimap, originalEntries); 330 } 331 332 // Test #put() 333 try { 334 multimap.put(sampleKey, sampleValue); 335 fail("put succeeded on unmodifiable multimap"); 336 } catch (UnsupportedOperationException expected) { 337 } 338 assertMultimapRemainsUnmodified(multimap, originalEntries); 339 340 // Test #putAll(K, Collection<V>) 341 try { 342 multimap.putAll(sampleKey, sampleValueAsCollection); 343 fail("putAll(K, Iterable) succeeded on unmodifiable multimap"); 344 } catch (UnsupportedOperationException expected) { 345 } 346 assertMultimapRemainsUnmodified(multimap, originalEntries); 347 348 // Test #putAll(Multimap<K, V>) 349 Multimap<K, V> multimap2 = ArrayListMultimap.create(); 350 multimap2.put(sampleKey, sampleValue); 351 try { 352 multimap.putAll(multimap2); 353 fail("putAll(Multimap<K, V>) succeeded on unmodifiable multimap"); 354 } catch (UnsupportedOperationException expected) { 355 } 356 assertMultimapRemainsUnmodified(multimap, originalEntries); 357 358 // Test #remove() 359 try { 360 multimap.remove(sampleKey, sampleValue); 361 fail("remove succeeded on unmodifiable multimap"); 362 } catch (UnsupportedOperationException expected) { 363 } 364 assertMultimapRemainsUnmodified(multimap, originalEntries); 365 366 // Test #removeAll() 367 try { 368 multimap.removeAll(sampleKey); 369 fail("removeAll succeeded on unmodifiable multimap"); 370 } catch (UnsupportedOperationException expected) { 371 } 372 assertMultimapRemainsUnmodified(multimap, originalEntries); 373 374 // Test #replaceValues() 375 try { 376 multimap.replaceValues(sampleKey, sampleValueAsCollection); 377 fail("replaceValues succeeded on unmodifiable multimap"); 378 } catch (UnsupportedOperationException expected) { 379 } 380 assertMultimapRemainsUnmodified(multimap, originalEntries); 381 382 // Test #asMap() 383 try { 384 multimap.asMap().remove(sampleKey); 385 fail("asMap().remove() succeeded on unmodifiable multimap"); 386 } catch (UnsupportedOperationException expected) { 387 } 388 assertMultimapRemainsUnmodified(multimap, originalEntries); 389 390 if (!multimap.isEmpty()) { 391 K presentKey = multimap.keySet().iterator().next(); 392 try { 393 multimap.asMap().get(presentKey).remove(sampleValue); 394 fail("asMap().get().remove() succeeded on unmodifiable multimap"); 395 } catch (UnsupportedOperationException expected) { 396 } 397 assertMultimapRemainsUnmodified(multimap, originalEntries); 398 399 try { 400 multimap.asMap().values().iterator().next().remove(sampleValue); 401 fail("asMap().values().iterator().next().remove() succeeded on unmodifiable multimap"); 402 } catch (UnsupportedOperationException expected) { 403 } 404 405 try { 406 ((Collection<?>) multimap.asMap().values().toArray()[0]).clear(); 407 fail("asMap().values().toArray()[0].clear() succeeded on unmodifiable multimap"); 408 } catch (UnsupportedOperationException expected) { 409 } 410 } 411 412 assertCollectionIsUnmodifiable(multimap.values(), sampleValue); 413 assertMultimapRemainsUnmodified(multimap, originalEntries); 414 } 415 416 private static <E extends @Nullable Object> void assertCollectionsAreEquivalent( 417 Collection<E> expected, Collection<E> actual) { 418 assertIteratorsInOrder(expected.iterator(), actual.iterator()); 419 } 420 421 private static <K extends @Nullable Object, V extends @Nullable Object> 422 void assertMultimapRemainsUnmodified(Multimap<K, V> expected, List<Entry<K, V>> actual) { 423 assertIteratorsInOrder(expected.entries().iterator(), actual.iterator()); 424 } 425}