• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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