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.LinkedHashMultiset; 026import com.google.common.collect.Lists; 027import com.google.common.collect.Maps; 028import com.google.common.collect.Multimap; 029import com.google.common.collect.Multiset; 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.Collections; 033import java.util.Iterator; 034import java.util.List; 035import java.util.Map.Entry; 036import java.util.Set; 037import org.checkerframework.checker.nullness.qual.Nullable; 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 046@ElementTypesAreNonnullByDefault 047public class UnmodifiableCollectionTests { 048 049 public static void assertMapEntryIsUnmodifiable(Entry<?, ?> entry) { 050 try { 051 // fine because the call is going to fail without modifying the entry 052 @SuppressWarnings("unchecked") 053 Entry<?, @Nullable Object> nullableValueEntry = (Entry<?, @Nullable Object>) entry; 054 nullableValueEntry.setValue(null); 055 fail("setValue on unmodifiable Map.Entry succeeded"); 056 } catch (UnsupportedOperationException expected) { 057 } 058 } 059 060 /** 061 * Verifies that an Iterator is unmodifiable. 062 * 063 * <p>This test only works with iterators that iterate over a finite set. 064 */ 065 public static void assertIteratorIsUnmodifiable(Iterator<?> iterator) { 066 while (iterator.hasNext()) { 067 iterator.next(); 068 try { 069 iterator.remove(); 070 fail("Remove on unmodifiable iterator succeeded"); 071 } catch (UnsupportedOperationException expected) { 072 } 073 } 074 } 075 076 /** 077 * Asserts that two iterators contain elements in tandem. 078 * 079 * <p>This test only works with iterators that iterate over a finite set. 080 */ 081 public static void assertIteratorsInOrder( 082 Iterator<?> expectedIterator, Iterator<?> actualIterator) { 083 int i = 0; 084 while (expectedIterator.hasNext()) { 085 Object expected = expectedIterator.next(); 086 087 assertTrue( 088 "index " + i + " expected <" + expected + "., actual is exhausted", 089 actualIterator.hasNext()); 090 091 Object actual = actualIterator.next(); 092 assertEquals("index " + i, expected, actual); 093 i++; 094 } 095 if (actualIterator.hasNext()) { 096 fail("index " + i + ", expected is exhausted, actual <" + actualIterator.next() + ">"); 097 } 098 } 099 100 /** 101 * Verifies that a collection is immutable. 102 * 103 * <p>A collection is considered immutable if: 104 * 105 * <ol> 106 * <li>All its mutation methods result in UnsupportedOperationException, and do not change the 107 * underlying contents. 108 * <li>All methods that return objects that can indirectly mutate the collection throw 109 * UnsupportedOperationException when those mutators are called. 110 * </ol> 111 * 112 * @param collection the presumed-immutable collection 113 * @param sampleElement an element of the same type as that contained by {@code collection}. 114 * {@code collection} may or may not have {@code sampleElement} as a member. 115 */ 116 public static <E extends @Nullable Object> void assertCollectionIsUnmodifiable( 117 Collection<E> collection, E sampleElement) { 118 Collection<E> siblingCollection = new ArrayList<>(); 119 siblingCollection.add(sampleElement); 120 121 Collection<E> copy = new ArrayList<>(); 122 copy.addAll(collection); 123 124 try { 125 collection.add(sampleElement); 126 fail("add succeeded on unmodifiable collection"); 127 } catch (UnsupportedOperationException expected) { 128 } 129 130 assertCollectionsAreEquivalent(copy, collection); 131 132 try { 133 collection.addAll(siblingCollection); 134 fail("addAll succeeded on unmodifiable collection"); 135 } catch (UnsupportedOperationException expected) { 136 } 137 assertCollectionsAreEquivalent(copy, collection); 138 139 try { 140 collection.clear(); 141 fail("clear succeeded on unmodifiable collection"); 142 } catch (UnsupportedOperationException expected) { 143 } 144 assertCollectionsAreEquivalent(copy, collection); 145 146 assertIteratorIsUnmodifiable(collection.iterator()); 147 assertCollectionsAreEquivalent(copy, collection); 148 149 try { 150 collection.remove(sampleElement); 151 fail("remove succeeded on unmodifiable collection"); 152 } catch (UnsupportedOperationException expected) { 153 } 154 assertCollectionsAreEquivalent(copy, collection); 155 156 try { 157 collection.removeAll(siblingCollection); 158 fail("removeAll succeeded on unmodifiable collection"); 159 } catch (UnsupportedOperationException expected) { 160 } 161 assertCollectionsAreEquivalent(copy, collection); 162 163 try { 164 collection.retainAll(siblingCollection); 165 fail("retainAll succeeded on unmodifiable collection"); 166 } catch (UnsupportedOperationException expected) { 167 } 168 assertCollectionsAreEquivalent(copy, collection); 169 } 170 171 /** 172 * Verifies that a set is immutable. 173 * 174 * <p>A set is considered immutable if: 175 * 176 * <ol> 177 * <li>All its mutation methods result in UnsupportedOperationException, and do not change the 178 * underlying contents. 179 * <li>All methods that return objects that can indirectly mutate the set throw 180 * UnsupportedOperationException when those mutators are called. 181 * </ol> 182 * 183 * @param set the presumed-immutable set 184 * @param sampleElement an element of the same type as that contained by {@code set}. {@code set} 185 * may or may not have {@code sampleElement} as a member. 186 */ 187 public static <E extends @Nullable Object> void assertSetIsUnmodifiable( 188 Set<E> set, E sampleElement) { 189 assertCollectionIsUnmodifiable(set, sampleElement); 190 } 191 192 /** 193 * Verifies that a multiset is immutable. 194 * 195 * <p>A multiset is considered immutable if: 196 * 197 * <ol> 198 * <li>All its mutation methods result in UnsupportedOperationException, and do not change the 199 * underlying contents. 200 * <li>All methods that return objects that can indirectly mutate the multiset throw 201 * UnsupportedOperationException when those mutators are called. 202 * </ol> 203 * 204 * @param multiset the presumed-immutable multiset 205 * @param sampleElement an element of the same type as that contained by {@code multiset}. {@code 206 * multiset} may or may not have {@code sampleElement} as a member. 207 */ 208 public static <E extends @Nullable Object> void assertMultisetIsUnmodifiable( 209 Multiset<E> multiset, E sampleElement) { 210 Multiset<E> copy = LinkedHashMultiset.create(multiset); 211 assertCollectionsAreEquivalent(multiset, copy); 212 213 // Multiset is a collection, so we can use all those tests. 214 assertCollectionIsUnmodifiable(multiset, sampleElement); 215 216 assertCollectionsAreEquivalent(multiset, copy); 217 218 try { 219 multiset.add(sampleElement, 2); 220 fail("add(Object, int) succeeded on unmodifiable collection"); 221 } catch (UnsupportedOperationException expected) { 222 } 223 assertCollectionsAreEquivalent(multiset, copy); 224 225 try { 226 multiset.remove(sampleElement, 2); 227 fail("remove(Object, int) succeeded on unmodifiable collection"); 228 } catch (UnsupportedOperationException expected) { 229 } 230 assertCollectionsAreEquivalent(multiset, copy); 231 232 try { 233 multiset.removeIf(x -> false); 234 fail("removeIf(Predicate) succeeded on unmodifiable collection"); 235 } catch (UnsupportedOperationException expected) { 236 } 237 assertCollectionsAreEquivalent(multiset, copy); 238 239 assertSetIsUnmodifiable(multiset.elementSet(), sampleElement); 240 assertCollectionsAreEquivalent(multiset, copy); 241 242 assertSetIsUnmodifiable( 243 multiset.entrySet(), 244 new Multiset.Entry<E>() { 245 @Override 246 public int getCount() { 247 return 1; 248 } 249 250 @Override 251 public E getElement() { 252 return sampleElement; 253 } 254 }); 255 assertCollectionsAreEquivalent(multiset, copy); 256 } 257 258 /** 259 * Verifies that a multimap is immutable. 260 * 261 * <p>A multimap is considered immutable if: 262 * 263 * <ol> 264 * <li>All its mutation methods result in UnsupportedOperationException, and do not change the 265 * underlying contents. 266 * <li>All methods that return objects that can indirectly mutate the multimap throw 267 * UnsupportedOperationException when those mutators 268 * </ol> 269 * 270 * @param multimap the presumed-immutable multimap 271 * @param sampleKey a key of the same type as that contained by {@code multimap}. {@code multimap} 272 * may or may not have {@code sampleKey} as a key. 273 * @param sampleValue a key of the same type as that contained by {@code multimap}. {@code 274 * multimap} may or may not have {@code sampleValue} as a key. 275 */ 276 public static <K extends @Nullable Object, V extends @Nullable Object> 277 void assertMultimapIsUnmodifiable(Multimap<K, V> multimap, K sampleKey, V sampleValue) { 278 List<Entry<K, V>> originalEntries = 279 Collections.unmodifiableList(Lists.newArrayList(multimap.entries())); 280 281 assertMultimapRemainsUnmodified(multimap, originalEntries); 282 283 Collection<V> sampleValueAsCollection = Collections.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(), Maps.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(), Maps.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}