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; 18 19 import static com.google.common.collect.Iterables.getOnlyElement; 20 import static com.google.common.collect.Iterables.unmodifiableIterable; 21 import static com.google.common.collect.Sets.newHashSet; 22 import static java.lang.reflect.Proxy.newProxyInstance; 23 import static java.util.Arrays.asList; 24 25 import com.google.common.annotations.GwtIncompatible; 26 import java.lang.reflect.InvocationHandler; 27 import java.lang.reflect.InvocationTargetException; 28 import java.lang.reflect.Method; 29 import java.util.Collection; 30 import java.util.Iterator; 31 import java.util.List; 32 import java.util.Set; 33 import java.util.concurrent.CopyOnWriteArrayList; 34 import junit.framework.TestCase; 35 36 @GwtIncompatible // reflection 37 public class ImmutableListCopyOfConcurrentlyModifiedInputTest extends TestCase { 38 enum WrapWithIterable { 39 WRAP, 40 NO_WRAP 41 } 42 runConcurrentlyMutatedTest( Collection<Integer> initialContents, Iterable<ListFrobber> actionsToPerformConcurrently, WrapWithIterable wrap)43 private static void runConcurrentlyMutatedTest( 44 Collection<Integer> initialContents, 45 Iterable<ListFrobber> actionsToPerformConcurrently, 46 WrapWithIterable wrap) { 47 ConcurrentlyMutatedList<Integer> concurrentlyMutatedList = 48 newConcurrentlyMutatedList(initialContents, actionsToPerformConcurrently); 49 50 Iterable<Integer> iterableToCopy = 51 wrap == WrapWithIterable.WRAP 52 ? unmodifiableIterable(concurrentlyMutatedList) 53 : concurrentlyMutatedList; 54 55 ImmutableList<Integer> copyOfIterable = ImmutableList.copyOf(iterableToCopy); 56 57 assertTrue(concurrentlyMutatedList.getAllStates().contains(copyOfIterable)); 58 } 59 runConcurrentlyMutatedTest(WrapWithIterable wrap)60 private static void runConcurrentlyMutatedTest(WrapWithIterable wrap) { 61 /* 62 * TODO: Iterate over many array sizes and all possible operation lists, 63 * performing adds and removes in different ways. 64 */ 65 runConcurrentlyMutatedTest(elements(), ops(add(1), add(2)), wrap); 66 67 runConcurrentlyMutatedTest(elements(), ops(add(1), nop()), wrap); 68 69 runConcurrentlyMutatedTest(elements(), ops(add(1), remove()), wrap); 70 71 runConcurrentlyMutatedTest(elements(), ops(nop(), add(1)), wrap); 72 73 runConcurrentlyMutatedTest(elements(1), ops(remove(), nop()), wrap); 74 75 runConcurrentlyMutatedTest(elements(1), ops(remove(), add(2)), wrap); 76 77 runConcurrentlyMutatedTest(elements(1, 2), ops(remove(), remove()), wrap); 78 79 runConcurrentlyMutatedTest(elements(1, 2), ops(remove(), nop()), wrap); 80 81 runConcurrentlyMutatedTest(elements(1, 2), ops(remove(), add(3)), wrap); 82 83 runConcurrentlyMutatedTest(elements(1, 2), ops(nop(), remove()), wrap); 84 85 runConcurrentlyMutatedTest(elements(1, 2, 3), ops(remove(), remove()), wrap); 86 } 87 elements(Integer... elements)88 private static ImmutableList<Integer> elements(Integer... elements) { 89 return ImmutableList.copyOf(elements); 90 } 91 ops(ListFrobber... elements)92 private static ImmutableList<ListFrobber> ops(ListFrobber... elements) { 93 return ImmutableList.copyOf(elements); 94 } 95 testCopyOf_concurrentlyMutatedList()96 public void testCopyOf_concurrentlyMutatedList() { 97 runConcurrentlyMutatedTest(WrapWithIterable.NO_WRAP); 98 } 99 testCopyOf_concurrentlyMutatedIterable()100 public void testCopyOf_concurrentlyMutatedIterable() { 101 runConcurrentlyMutatedTest(WrapWithIterable.WRAP); 102 } 103 104 /** An operation to perform on a list. */ 105 interface ListFrobber { perform(List<Integer> list)106 void perform(List<Integer> list); 107 } 108 add(final int element)109 static ListFrobber add(final int element) { 110 return new ListFrobber() { 111 @Override 112 public void perform(List<Integer> list) { 113 list.add(0, element); 114 } 115 }; 116 } 117 118 static ListFrobber remove() { 119 return new ListFrobber() { 120 @Override 121 public void perform(List<Integer> list) { 122 list.remove(0); 123 } 124 }; 125 } 126 127 static ListFrobber nop() { 128 return new ListFrobber() { 129 @Override 130 public void perform(List<Integer> list) {} 131 }; 132 } 133 134 /** A list that mutates itself after every call to each of its {@link List} methods. */ 135 interface ConcurrentlyMutatedList<E> extends List<E> { 136 /** 137 * The elements of a {@link ConcurrentlyMutatedList} are added and removed over time. This 138 * method returns every state that the list has passed through at some point. 139 */ 140 Set<List<E>> getAllStates(); 141 } 142 143 /** 144 * Returns a {@link ConcurrentlyMutatedList} that performs the given operations as its concurrent 145 * modifications. The mutations occur in the same thread as the triggering method call. 146 */ 147 private static ConcurrentlyMutatedList<Integer> newConcurrentlyMutatedList( 148 final Collection<Integer> initialContents, 149 final Iterable<ListFrobber> actionsToPerformConcurrently) { 150 InvocationHandler invocationHandler = 151 new InvocationHandler() { 152 final CopyOnWriteArrayList<Integer> delegate = 153 new CopyOnWriteArrayList<>(initialContents); 154 155 final Method getAllStatesMethod = 156 getOnlyElement(asList(ConcurrentlyMutatedList.class.getDeclaredMethods())); 157 158 final Iterator<ListFrobber> remainingActions = actionsToPerformConcurrently.iterator(); 159 160 final Set<List<Integer>> allStates = newHashSet(); 161 162 @Override 163 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 164 return method.equals(getAllStatesMethod) 165 ? getAllStates() 166 : invokeListMethod(method, args); 167 } 168 169 private Set<List<Integer>> getAllStates() { 170 return allStates; 171 } 172 173 private Object invokeListMethod(Method method, Object[] args) throws Throwable { 174 try { 175 Object returnValue = method.invoke(delegate, args); 176 mutateDelegate(); 177 return returnValue; 178 } catch (InvocationTargetException e) { 179 throw e.getCause(); 180 } catch (IllegalAccessException e) { 181 throw new AssertionError(e); 182 } 183 } 184 185 private void mutateDelegate() { 186 allStates.add(ImmutableList.copyOf(delegate)); 187 remainingActions.next().perform(delegate); 188 allStates.add(ImmutableList.copyOf(delegate)); 189 } 190 }; 191 192 @SuppressWarnings("unchecked") 193 ConcurrentlyMutatedList<Integer> list = 194 (ConcurrentlyMutatedList<Integer>) 195 newProxyInstance( 196 ImmutableListCopyOfConcurrentlyModifiedInputTest.class.getClassLoader(), 197 new Class[] {ConcurrentlyMutatedList.class}, 198 invocationHandler); 199 return list; 200 } 201 } 202