001/*
002 * Copyright (C) 2009 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 com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES;
020import static com.google.common.collect.testing.features.CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION;
021import static com.google.common.collect.testing.features.CollectionFeature.RESTRICTS_ELEMENTS;
022import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ADD;
023import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE;
024import static com.google.common.collect.testing.features.CollectionSize.SEVERAL;
025import static com.google.common.collect.testing.features.CollectionSize.ZERO;
026
027import com.google.common.annotations.GwtCompatible;
028import com.google.common.annotations.GwtIncompatible;
029import com.google.common.collect.Multiset;
030import com.google.common.collect.Multiset.Entry;
031import com.google.common.collect.testing.Helpers;
032import com.google.common.collect.testing.features.CollectionFeature;
033import com.google.common.collect.testing.features.CollectionSize;
034import java.lang.reflect.Method;
035import java.util.Arrays;
036import java.util.ConcurrentModificationException;
037import java.util.Iterator;
038import java.util.List;
039
040/**
041 * Common superclass for {@link MultisetSetCountUnconditionallyTester} and {@link
042 * MultisetSetCountConditionallyTester}. It is used by those testers to test calls to the
043 * unconditional {@code setCount()} method and calls to the conditional {@code setCount()} method
044 * when the expected present count is correct.
045 *
046 * @author Chris Povirk
047 */
048@GwtCompatible(emulated = true)
049public abstract class AbstractMultisetSetCountTester<E> extends AbstractMultisetTester<E> {
050  /*
051   * TODO: consider adding MultisetFeatures.SUPPORTS_SET_COUNT. Currently we
052   * assume that using setCount() to increase the count is permitted iff add()
053   * is permitted and similarly for decrease/remove(). We assume that a
054   * setCount() no-op is permitted if either add() or remove() is permitted,
055   * though we also allow it to "succeed" if neither is permitted.
056   */
057
058  private void assertSetCount(E element, int count) {
059    setCountCheckReturnValue(element, count);
060
061    assertEquals(
062        "multiset.count() should return the value passed to setCount()",
063        count,
064        getMultiset().count(element));
065
066    int size = 0;
067    for (Multiset.Entry<E> entry : getMultiset().entrySet()) {
068      size += entry.getCount();
069    }
070    assertEquals(
071        "multiset.size() should be the sum of the counts of all entries",
072        size,
073        getMultiset().size());
074  }
075
076  /** Call the {@code setCount()} method under test, and check its return value. */
077  abstract void setCountCheckReturnValue(E element, int count);
078
079  /**
080   * Call the {@code setCount()} method under test, but do not check its return value. Callers
081   * should use this method over {@link #setCountCheckReturnValue(Object, int)} when they expect
082   * {@code setCount()} to throw an exception, as checking the return value could produce an
083   * incorrect error message like "setCount() should return the original count" instead of the
084   * message passed to a later invocation of {@code fail()}, like "setCount should throw
085   * UnsupportedOperationException."
086   */
087  abstract void setCountNoCheckReturnValue(E element, int count);
088
089  private void assertSetCountIncreasingFailure(E element, int count) {
090    try {
091      setCountNoCheckReturnValue(element, count);
092      fail("a call to multiset.setCount() to increase an element's count should throw");
093    } catch (UnsupportedOperationException expected) {
094    }
095  }
096
097  private void assertSetCountDecreasingFailure(E element, int count) {
098    try {
099      setCountNoCheckReturnValue(element, count);
100      fail("a call to multiset.setCount() to decrease an element's count should throw");
101    } catch (UnsupportedOperationException expected) {
102    }
103  }
104
105  // Unconditional setCount no-ops.
106
107  private void assertZeroToZero() {
108    assertSetCount(e3(), 0);
109  }
110
111  private void assertOneToOne() {
112    assertSetCount(e0(), 1);
113  }
114
115  private void assertThreeToThree() {
116    initThreeCopies();
117    assertSetCount(e0(), 3);
118  }
119
120  @CollectionFeature.Require(SUPPORTS_ADD)
121  public void testSetCount_zeroToZero_addSupported() {
122    assertZeroToZero();
123  }
124
125  @CollectionFeature.Require(SUPPORTS_REMOVE)
126  public void testSetCount_zeroToZero_removeSupported() {
127    assertZeroToZero();
128  }
129
130  @CollectionFeature.Require(absent = {SUPPORTS_ADD, SUPPORTS_REMOVE})
131  public void testSetCount_zeroToZero_unsupported() {
132    try {
133      assertZeroToZero();
134    } catch (UnsupportedOperationException tolerated) {
135    }
136  }
137
138  @CollectionSize.Require(absent = ZERO)
139  @CollectionFeature.Require(SUPPORTS_ADD)
140  public void testSetCount_oneToOne_addSupported() {
141    assertOneToOne();
142  }
143
144  @CollectionSize.Require(absent = ZERO)
145  @CollectionFeature.Require(SUPPORTS_REMOVE)
146  public void testSetCount_oneToOne_removeSupported() {
147    assertOneToOne();
148  }
149
150  @CollectionSize.Require(absent = ZERO)
151  @CollectionFeature.Require(absent = {SUPPORTS_ADD, SUPPORTS_REMOVE})
152  public void testSetCount_oneToOne_unsupported() {
153    try {
154      assertOneToOne();
155    } catch (UnsupportedOperationException tolerated) {
156    }
157  }
158
159  @CollectionSize.Require(SEVERAL)
160  @CollectionFeature.Require(SUPPORTS_ADD)
161  public void testSetCount_threeToThree_addSupported() {
162    assertThreeToThree();
163  }
164
165  @CollectionSize.Require(SEVERAL)
166  @CollectionFeature.Require(SUPPORTS_REMOVE)
167  public void testSetCount_threeToThree_removeSupported() {
168    assertThreeToThree();
169  }
170
171  @CollectionSize.Require(SEVERAL)
172  @CollectionFeature.Require(absent = {SUPPORTS_ADD, SUPPORTS_REMOVE})
173  public void testSetCount_threeToThree_unsupported() {
174    try {
175      assertThreeToThree();
176    } catch (UnsupportedOperationException tolerated) {
177    }
178  }
179
180  // Unconditional setCount size increases:
181
182  @CollectionFeature.Require(SUPPORTS_ADD)
183  public void testSetCount_zeroToOne_supported() {
184    assertSetCount(e3(), 1);
185  }
186
187  @CollectionFeature.Require({SUPPORTS_ADD, FAILS_FAST_ON_CONCURRENT_MODIFICATION})
188  public void testSetCountZeroToOneConcurrentWithIteration() {
189    try {
190      Iterator<E> iterator = collection.iterator();
191      assertSetCount(e3(), 1);
192      iterator.next();
193      fail("Expected ConcurrentModificationException");
194    } catch (ConcurrentModificationException expected) {
195      // success
196    }
197  }
198
199  @CollectionFeature.Require({SUPPORTS_ADD, FAILS_FAST_ON_CONCURRENT_MODIFICATION})
200  public void testSetCountZeroToOneConcurrentWithEntrySetIteration() {
201    try {
202      Iterator<Entry<E>> iterator = getMultiset().entrySet().iterator();
203      assertSetCount(e3(), 1);
204      iterator.next();
205      fail("Expected ConcurrentModificationException");
206    } catch (ConcurrentModificationException expected) {
207      // success
208    }
209  }
210
211  @CollectionFeature.Require(SUPPORTS_ADD)
212  public void testSetCount_zeroToThree_supported() {
213    assertSetCount(e3(), 3);
214  }
215
216  @CollectionSize.Require(absent = ZERO)
217  @CollectionFeature.Require(SUPPORTS_ADD)
218  public void testSetCount_oneToThree_supported() {
219    assertSetCount(e0(), 3);
220  }
221
222  @CollectionFeature.Require(absent = SUPPORTS_ADD)
223  public void testSetCount_zeroToOne_unsupported() {
224    assertSetCountIncreasingFailure(e3(), 1);
225  }
226
227  @CollectionFeature.Require(absent = SUPPORTS_ADD)
228  public void testSetCount_zeroToThree_unsupported() {
229    assertSetCountIncreasingFailure(e3(), 3);
230  }
231
232  @CollectionSize.Require(absent = ZERO)
233  @CollectionFeature.Require(absent = SUPPORTS_ADD)
234  public void testSetCount_oneToThree_unsupported() {
235    assertSetCountIncreasingFailure(e3(), 3);
236  }
237
238  // Unconditional setCount size decreases:
239
240  @CollectionSize.Require(absent = ZERO)
241  @CollectionFeature.Require(SUPPORTS_REMOVE)
242  public void testSetCount_oneToZero_supported() {
243    assertSetCount(e0(), 0);
244  }
245
246  @CollectionFeature.Require({SUPPORTS_REMOVE, FAILS_FAST_ON_CONCURRENT_MODIFICATION})
247  @CollectionSize.Require(absent = ZERO)
248  public void testSetCountOneToZeroConcurrentWithIteration() {
249    try {
250      Iterator<E> iterator = collection.iterator();
251      assertSetCount(e0(), 0);
252      iterator.next();
253      fail("Expected ConcurrentModificationException");
254    } catch (ConcurrentModificationException expected) {
255      // success
256    }
257  }
258
259  @CollectionFeature.Require({SUPPORTS_REMOVE, FAILS_FAST_ON_CONCURRENT_MODIFICATION})
260  @CollectionSize.Require(absent = ZERO)
261  public void testSetCountOneToZeroConcurrentWithEntrySetIteration() {
262    try {
263      Iterator<Entry<E>> iterator = getMultiset().entrySet().iterator();
264      assertSetCount(e0(), 0);
265      iterator.next();
266      fail("Expected ConcurrentModificationException");
267    } catch (ConcurrentModificationException expected) {
268      // success
269    }
270  }
271
272  @CollectionSize.Require(SEVERAL)
273  @CollectionFeature.Require(SUPPORTS_REMOVE)
274  public void testSetCount_threeToZero_supported() {
275    initThreeCopies();
276    assertSetCount(e0(), 0);
277  }
278
279  @CollectionSize.Require(SEVERAL)
280  @CollectionFeature.Require(SUPPORTS_REMOVE)
281  public void testSetCount_threeToOne_supported() {
282    initThreeCopies();
283    assertSetCount(e0(), 1);
284  }
285
286  @CollectionSize.Require(absent = ZERO)
287  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
288  public void testSetCount_oneToZero_unsupported() {
289    assertSetCountDecreasingFailure(e0(), 0);
290  }
291
292  @CollectionSize.Require(SEVERAL)
293  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
294  public void testSetCount_threeToZero_unsupported() {
295    initThreeCopies();
296    assertSetCountDecreasingFailure(e0(), 0);
297  }
298
299  @CollectionSize.Require(SEVERAL)
300  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
301  public void testSetCount_threeToOne_unsupported() {
302    initThreeCopies();
303    assertSetCountDecreasingFailure(e0(), 1);
304  }
305
306  // setCount with nulls:
307
308  @CollectionSize.Require(absent = ZERO)
309  @CollectionFeature.Require({SUPPORTS_REMOVE, ALLOWS_NULL_VALUES})
310  public void testSetCount_removeNull_nullSupported() {
311    initCollectionWithNullElement();
312    assertSetCount(null, 0);
313  }
314
315  @CollectionFeature.Require(
316    value = {SUPPORTS_ADD, ALLOWS_NULL_VALUES},
317    absent = RESTRICTS_ELEMENTS
318  )
319  public void testSetCount_addNull_nullSupported() {
320    assertSetCount(null, 1);
321  }
322
323  @CollectionFeature.Require(value = SUPPORTS_ADD, absent = ALLOWS_NULL_VALUES)
324  public void testSetCount_addNull_nullUnsupported() {
325    try {
326      setCountNoCheckReturnValue(null, 1);
327      fail("adding null with setCount() should throw NullPointerException");
328    } catch (NullPointerException expected) {
329    }
330  }
331
332  @CollectionFeature.Require(ALLOWS_NULL_VALUES)
333  public void testSetCount_noOpNull_nullSupported() {
334    try {
335      assertSetCount(null, 0);
336    } catch (UnsupportedOperationException tolerated) {
337    }
338  }
339
340  @CollectionFeature.Require(absent = ALLOWS_NULL_VALUES)
341  public void testSetCount_noOpNull_nullUnsupported() {
342    try {
343      assertSetCount(null, 0);
344    } catch (NullPointerException | UnsupportedOperationException tolerated) {
345    }
346  }
347
348  @CollectionSize.Require(absent = ZERO)
349  @CollectionFeature.Require(ALLOWS_NULL_VALUES)
350  public void testSetCount_existingNoNopNull_nullSupported() {
351    initCollectionWithNullElement();
352    try {
353      assertSetCount(null, 1);
354    } catch (UnsupportedOperationException tolerated) {
355    }
356  }
357
358  // Negative count.
359
360  @CollectionFeature.Require(SUPPORTS_REMOVE)
361  public void testSetCount_negative_removeSupported() {
362    try {
363      setCountNoCheckReturnValue(e3(), -1);
364      fail("calling setCount() with a negative count should throw IllegalArgumentException");
365    } catch (IllegalArgumentException expected) {
366    }
367  }
368
369  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
370  public void testSetCount_negative_removeUnsupported() {
371    try {
372      setCountNoCheckReturnValue(e3(), -1);
373      fail(
374          "calling setCount() with a negative count should throw "
375              + "IllegalArgumentException or UnsupportedOperationException");
376    } catch (IllegalArgumentException | UnsupportedOperationException expected) {
377    }
378  }
379
380  // TODO: test adding element of wrong type
381
382  /**
383   * Returns {@link Method} instances for the {@code setCount()} tests that assume multisets support
384   * duplicates so that the test of {@code Multisets.forSet()} can suppress them.
385   */
386  @GwtIncompatible // reflection
387  public static List<Method> getSetCountDuplicateInitializingMethods() {
388    return Arrays.asList(
389        getMethod("testSetCount_threeToThree_removeSupported"),
390        getMethod("testSetCount_threeToZero_supported"),
391        getMethod("testSetCount_threeToOne_supported"));
392  }
393
394  @GwtIncompatible // reflection
395  private static Method getMethod(String methodName) {
396    return Helpers.getMethod(AbstractMultisetSetCountTester.class, methodName);
397  }
398}