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}