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
17package com.google.common.collect.testing.testers;
18
19import static com.google.common.collect.testing.features.CollectionSize.ZERO;
20import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS;
21import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES;
22import static com.google.common.collect.testing.features.MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION;
23import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT;
24
25import com.google.common.annotations.GwtCompatible;
26import com.google.common.annotations.GwtIncompatible;
27import com.google.common.collect.testing.AbstractMapTester;
28import com.google.common.collect.testing.Helpers;
29import com.google.common.collect.testing.features.CollectionSize;
30import com.google.common.collect.testing.features.MapFeature;
31
32import java.lang.reflect.Method;
33import java.util.ConcurrentModificationException;
34import java.util.Iterator;
35import java.util.Map;
36import java.util.Map.Entry;
37
38/**
39 * A generic JUnit test which tests {@code put} operations on a map. Can't be
40 * invoked directly; please see
41 * {@link com.google.common.collect.testing.MapTestSuiteBuilder}.
42 *
43 * @author Chris Povirk
44 * @author Kevin Bourrillion
45 */
46@SuppressWarnings("unchecked") // too many "unchecked generic array creations"
47@GwtCompatible(emulated = true)
48public class MapPutTester<K, V> extends AbstractMapTester<K, V> {
49  private Entry<K, V> nullKeyEntry;
50  private Entry<K, V> nullValueEntry;
51  private Entry<K, V> nullKeyValueEntry;
52  private Entry<K, V> presentKeyNullValueEntry;
53
54  @Override public void setUp() throws Exception {
55    super.setUp();
56    nullKeyEntry = entry(null, samples.e3.getValue());
57    nullValueEntry = entry(samples.e3.getKey(), null);
58    nullKeyValueEntry = entry(null, null);
59    presentKeyNullValueEntry = entry(samples.e0.getKey(), null);
60  }
61
62  @MapFeature.Require(SUPPORTS_PUT)
63  public void testPut_supportedNotPresent() {
64    assertNull("put(notPresent, value) should return null", put(samples.e3));
65    expectAdded(samples.e3);
66  }
67
68  @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT})
69  @CollectionSize.Require(absent = ZERO)
70  public void testPutAbsentConcurrentWithEntrySetIteration() {
71    try {
72      Iterator<Entry<K, V>> iterator = getMap().entrySet().iterator();
73      put(samples.e3);
74      iterator.next();
75      fail("Expected ConcurrentModificationException");
76    } catch (ConcurrentModificationException expected) {
77      // success
78    }
79  }
80
81  @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT})
82  @CollectionSize.Require(absent = ZERO)
83  public void testPutAbsentConcurrentWithKeySetIteration() {
84    try {
85      Iterator<K> iterator = getMap().keySet().iterator();
86      put(samples.e3);
87      iterator.next();
88      fail("Expected ConcurrentModificationException");
89    } catch (ConcurrentModificationException expected) {
90      // success
91    }
92  }
93
94  @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT})
95  @CollectionSize.Require(absent = ZERO)
96  public void testPutAbsentConcurrentWithValueIteration() {
97    try {
98      Iterator<V> iterator = getMap().values().iterator();
99      put(samples.e3);
100      iterator.next();
101      fail("Expected ConcurrentModificationException");
102    } catch (ConcurrentModificationException expected) {
103      // success
104    }
105  }
106
107  @MapFeature.Require(absent = SUPPORTS_PUT)
108  public void testPut_unsupportedNotPresent() {
109    try {
110      put(samples.e3);
111      fail("put(notPresent, value) should throw");
112    } catch (UnsupportedOperationException expected) {
113    }
114    expectUnchanged();
115    expectMissing(samples.e3);
116  }
117
118  @MapFeature.Require(absent = SUPPORTS_PUT)
119  @CollectionSize.Require(absent = ZERO)
120  public void testPut_unsupportedPresentExistingValue() {
121    try {
122      assertEquals("put(present, existingValue) should return present or throw",
123          samples.e0.getValue(), put(samples.e0));
124    } catch (UnsupportedOperationException tolerated) {
125    }
126    expectUnchanged();
127  }
128
129  @MapFeature.Require(absent = SUPPORTS_PUT)
130  @CollectionSize.Require(absent = ZERO)
131  public void testPut_unsupportedPresentDifferentValue() {
132    try {
133      getMap().put(samples.e0.getKey(), samples.e3.getValue());
134      fail("put(present, differentValue) should throw");
135    } catch (UnsupportedOperationException expected) {
136    }
137    expectUnchanged();
138  }
139
140  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS})
141  public void testPut_nullKeySupportedNotPresent() {
142    assertNull("put(null, value) should return null", put(nullKeyEntry));
143    expectAdded(nullKeyEntry);
144  }
145
146  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS})
147  @CollectionSize.Require(absent = ZERO)
148  public void testPut_nullKeySupportedPresent() {
149    Entry<K, V> newEntry = entry(null, samples.e3.getValue());
150    initMapWithNullKey();
151    assertEquals("put(present, value) should return the associated value",
152        getValueForNullKey(), put(newEntry));
153
154    Entry<K, V>[] expected = createArrayWithNullKey();
155    expected[getNullLocation()] = newEntry;
156    expectContents(expected);
157  }
158
159  @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_KEYS)
160  public void testPut_nullKeyUnsupported() {
161    try {
162      put(nullKeyEntry);
163      fail("put(null, value) should throw");
164    } catch (NullPointerException expected) {
165    }
166    expectUnchanged();
167    expectNullKeyMissingWhenNullKeysUnsupported(
168        "Should not contain null key after unsupported put(null, value)");
169  }
170
171  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES})
172  public void testPut_nullValueSupported() {
173    assertNull("put(key, null) should return null", put(nullValueEntry));
174    expectAdded(nullValueEntry);
175  }
176
177  @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES)
178  public void testPut_nullValueUnsupported() {
179    try {
180      put(nullValueEntry);
181      fail("put(key, null) should throw");
182    } catch (NullPointerException expected) {
183    }
184    expectUnchanged();
185    expectNullValueMissingWhenNullValuesUnsupported(
186        "Should not contain null value after unsupported put(key, null)");
187  }
188
189  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES})
190  @CollectionSize.Require(absent = ZERO)
191  public void testPut_replaceWithNullValueSupported() {
192    assertEquals("put(present, null) should return the associated value",
193        samples.e0.getValue(), put(presentKeyNullValueEntry));
194    expectReplacement(presentKeyNullValueEntry);
195  }
196
197  @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES)
198  @CollectionSize.Require(absent = ZERO)
199  public void testPut_replaceWithNullValueUnsupported() {
200    try {
201      put(presentKeyNullValueEntry);
202      fail("put(present, null) should throw");
203    } catch (NullPointerException expected) {
204    }
205    expectUnchanged();
206    expectNullValueMissingWhenNullValuesUnsupported(
207        "Should not contain null after unsupported put(present, null)");
208  }
209
210  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES})
211  @CollectionSize.Require(absent = ZERO)
212  public void testPut_replaceNullValueWithNullSupported() {
213    initMapWithNullValue();
214    assertNull("put(present, null) should return the associated value (null)",
215        getMap().put(getKeyForNullValue(), null));
216    expectContents(createArrayWithNullValue());
217  }
218
219  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES})
220  @CollectionSize.Require(absent = ZERO)
221  public void testPut_replaceNullValueWithNonNullSupported() {
222    Entry<K, V> newEntry = entry(getKeyForNullValue(), samples.e3.getValue());
223    initMapWithNullValue();
224    assertNull("put(present, value) should return the associated value (null)",
225        put(newEntry));
226
227    Entry<K, V>[] expected = createArrayWithNullValue();
228    expected[getNullLocation()] = newEntry;
229    expectContents(expected);
230  }
231
232  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS, ALLOWS_NULL_VALUES})
233  public void testPut_nullKeyAndValueSupported() {
234    assertNull("put(null, null) should return null", put(nullKeyValueEntry));
235    expectAdded(nullKeyValueEntry);
236  }
237
238  private V put(Map.Entry<K, V> entry) {
239    return getMap().put(entry.getKey(), entry.getValue());
240  }
241
242  /**
243   * Returns the {@link Method} instance for {@link
244   * #testPut_nullKeyUnsupported()} so that tests of {@link java.util.TreeMap}
245   * can suppress it with {@code FeatureSpecificTestSuiteBuilder.suppressing()}
246   * until <a
247   * href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5045147">Sun bug
248   * 5045147</a> is fixed.
249   */
250  @GwtIncompatible("reflection")
251  public static Method getPutNullKeyUnsupportedMethod() {
252    return Helpers.getMethod(MapPutTester.class, "testPut_nullKeyUnsupported");
253  }
254}
255