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 17 package com.google.common.collect.testing.google; 18 19 import static junit.framework.TestCase.assertEquals; 20 import static junit.framework.TestCase.assertTrue; 21 import static junit.framework.TestCase.fail; 22 23 import com.google.common.annotations.GwtCompatible; 24 import com.google.common.collect.ArrayListMultimap; 25 import com.google.common.collect.LinkedHashMultiset; 26 import com.google.common.collect.Lists; 27 import com.google.common.collect.Maps; 28 import com.google.common.collect.Multimap; 29 import com.google.common.collect.Multiset; 30 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.Collections; 34 import java.util.Iterator; 35 import java.util.List; 36 import java.util.Map.Entry; 37 import 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 46 public class UnmodifiableCollectionTests { 47 assertMapEntryIsUnmodifiable(Entry<?, ?> entry)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 */ assertIteratorIsUnmodifiable(Iterator<?> iterator)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 */ assertIteratorsInOrder( Iterator<?> expectedIterator, Iterator<?> actualIterator)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 */ assertCollectionIsUnmodifiable( Collection<E> collection, E sampleElement)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 */ assertSetIsUnmodifiable( Set<E> set, E sampleElement)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 */ assertMultisetIsUnmodifiable(Multiset<E> multiset, final E sampleElement)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 */ assertMultimapIsUnmodifiable( Multimap<K, V> multimap, final K sampleKey, final V sampleValue)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 assertCollectionsAreEquivalent( Collection<E> expected, Collection<E> actual)415 private static <E> void assertCollectionsAreEquivalent( 416 Collection<E> expected, Collection<E> actual) { 417 assertIteratorsInOrder(expected.iterator(), actual.iterator()); 418 } 419 assertMultimapRemainsUnmodified( Multimap<K, V> expected, List<Entry<K, V>> actual)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