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}