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.testers;
018
019import static com.google.common.collect.testing.Helpers.getMethod;
020import static com.google.common.collect.testing.features.CollectionSize.ZERO;
021import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS;
022import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES;
023import static com.google.common.collect.testing.features.MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION;
024import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT;
025import static org.junit.Assert.assertThrows;
026
027import com.google.common.annotations.GwtCompatible;
028import com.google.common.annotations.GwtIncompatible;
029import com.google.common.annotations.J2ktIncompatible;
030import com.google.common.collect.testing.AbstractMapTester;
031import com.google.common.collect.testing.features.CollectionSize;
032import com.google.common.collect.testing.features.MapFeature;
033import com.google.errorprone.annotations.CanIgnoreReturnValue;
034import java.lang.reflect.Method;
035import java.util.ConcurrentModificationException;
036import java.util.Iterator;
037import java.util.Map.Entry;
038import org.jspecify.annotations.Nullable;
039
040/**
041 * A generic JUnit test which tests {@code put} operations on a map. Can't be invoked directly;
042 * please see {@link com.google.common.collect.testing.MapTestSuiteBuilder}.
043 *
044 * @author Chris Povirk
045 * @author Kevin Bourrillion
046 */
047@GwtCompatible
048public class MapPutTester<K, V> extends AbstractMapTester<K, V> {
049  private Entry<K, V> nullKeyEntry;
050  private Entry<K, V> nullValueEntry;
051  private Entry<K, V> nullKeyValueEntry;
052  private Entry<K, V> presentKeyNullValueEntry;
053
054  @Override
055  public void setUp() throws Exception {
056    super.setUp();
057    nullKeyEntry = entry(null, v3());
058    nullValueEntry = entry(k3(), null);
059    nullKeyValueEntry = entry(null, null);
060    presentKeyNullValueEntry = entry(k0(), null);
061  }
062
063  @MapFeature.Require(SUPPORTS_PUT)
064  @CollectionSize.Require(absent = ZERO)
065  public void testPut_supportedPresent() {
066    assertEquals("put(present, value) should return the old value", v0(), getMap().put(k0(), v3()));
067    expectReplacement(entry(k0(), v3()));
068  }
069
070  @MapFeature.Require(SUPPORTS_PUT)
071  public void testPut_supportedNotPresent() {
072    assertNull("put(notPresent, value) should return null", put(e3()));
073    expectAdded(e3());
074  }
075
076  @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT})
077  @CollectionSize.Require(absent = ZERO)
078  public void testPutAbsentConcurrentWithEntrySetIteration() {
079    Iterator<Entry<K, V>> iterator = getMap().entrySet().iterator();
080    put(e3());
081    assertThrows(ConcurrentModificationException.class, iterator::next);
082  }
083
084  @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT})
085  @CollectionSize.Require(absent = ZERO)
086  public void testPutAbsentConcurrentWithKeySetIteration() {
087    Iterator<K> iterator = getMap().keySet().iterator();
088    put(e3());
089    assertThrows(ConcurrentModificationException.class, iterator::next);
090  }
091
092  @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT})
093  @CollectionSize.Require(absent = ZERO)
094  public void testPutAbsentConcurrentWithValueIteration() {
095    Iterator<V> iterator = getMap().values().iterator();
096    put(e3());
097    assertThrows(ConcurrentModificationException.class, iterator::next);
098  }
099
100  @MapFeature.Require(absent = SUPPORTS_PUT)
101  public void testPut_unsupportedNotPresent() {
102    assertThrows(UnsupportedOperationException.class, () -> put(e3()));
103    expectUnchanged();
104    expectMissing(e3());
105  }
106
107  @MapFeature.Require(absent = SUPPORTS_PUT)
108  @CollectionSize.Require(absent = ZERO)
109  public void testPut_unsupportedPresentExistingValue() {
110    try {
111      assertEquals("put(present, existingValue) should return present or throw", v0(), put(e0()));
112    } catch (UnsupportedOperationException tolerated) {
113    }
114    expectUnchanged();
115  }
116
117  @MapFeature.Require(absent = SUPPORTS_PUT)
118  @CollectionSize.Require(absent = ZERO)
119  public void testPut_unsupportedPresentDifferentValue() {
120    assertThrows(UnsupportedOperationException.class, () -> getMap().put(k0(), v3()));
121    expectUnchanged();
122  }
123
124  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS})
125  public void testPut_nullKeySupportedNotPresent() {
126    assertNull("put(null, value) should return null", put(nullKeyEntry));
127    expectAdded(nullKeyEntry);
128  }
129
130  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS})
131  @CollectionSize.Require(absent = ZERO)
132  public void testPut_nullKeySupportedPresent() {
133    Entry<K, V> newEntry = entry(null, v3());
134    initMapWithNullKey();
135    assertEquals(
136        "put(present, value) should return the associated value",
137        getValueForNullKey(),
138        put(newEntry));
139
140    Entry<K, V>[] expected = createArrayWithNullKey();
141    expected[getNullLocation()] = newEntry;
142    expectContents(expected);
143  }
144
145  @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_KEYS)
146  public void testPut_nullKeyUnsupported() {
147    assertThrows(NullPointerException.class, () -> put(nullKeyEntry));
148    expectUnchanged();
149    expectNullKeyMissingWhenNullKeysUnsupported(
150        "Should not contain null key after unsupported put(null, value)");
151  }
152
153  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES})
154  public void testPut_nullValueSupported() {
155    assertNull("put(key, null) should return null", put(nullValueEntry));
156    expectAdded(nullValueEntry);
157  }
158
159  @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES)
160  public void testPut_nullValueUnsupported() {
161    assertThrows(NullPointerException.class, () -> put(nullValueEntry));
162    expectUnchanged();
163    expectNullValueMissingWhenNullValuesUnsupported(
164        "Should not contain null value after unsupported put(key, null)");
165  }
166
167  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES})
168  @CollectionSize.Require(absent = ZERO)
169  public void testPut_replaceWithNullValueSupported() {
170    assertEquals(
171        "put(present, null) should return the associated value",
172        v0(),
173        put(presentKeyNullValueEntry));
174    expectReplacement(presentKeyNullValueEntry);
175  }
176
177  @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES)
178  @CollectionSize.Require(absent = ZERO)
179  public void testPut_replaceWithNullValueUnsupported() {
180    assertThrows(NullPointerException.class, () -> put(presentKeyNullValueEntry));
181    expectUnchanged();
182    expectNullValueMissingWhenNullValuesUnsupported(
183        "Should not contain null after unsupported put(present, null)");
184  }
185
186  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES})
187  @CollectionSize.Require(absent = ZERO)
188  public void testPut_replaceNullValueWithNullSupported() {
189    initMapWithNullValue();
190    assertNull(
191        "put(present, null) should return the associated value (null)",
192        getMap().put(getKeyForNullValue(), null));
193    expectContents(createArrayWithNullValue());
194  }
195
196  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES})
197  @CollectionSize.Require(absent = ZERO)
198  public void testPut_replaceNullValueWithNonNullSupported() {
199    Entry<K, V> newEntry = entry(getKeyForNullValue(), v3());
200    initMapWithNullValue();
201    assertNull("put(present, value) should return the associated value (null)", put(newEntry));
202
203    Entry<K, V>[] expected = createArrayWithNullValue();
204    expected[getNullLocation()] = newEntry;
205    expectContents(expected);
206  }
207
208  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS, ALLOWS_NULL_VALUES})
209  public void testPut_nullKeyAndValueSupported() {
210    assertNull("put(null, null) should return null", put(nullKeyValueEntry));
211    expectAdded(nullKeyValueEntry);
212  }
213
214  @CanIgnoreReturnValue
215  private @Nullable V put(Entry<K, V> entry) {
216    return getMap().put(entry.getKey(), entry.getValue());
217  }
218
219  /**
220   * Returns the {@link Method} instance for {@link #testPut_nullKeyUnsupported()} so that tests of
221   * {@link java.util.TreeMap} can suppress it with {@code
222   * FeatureSpecificTestSuiteBuilder.suppressing()} until <a
223   * href="https://bugs.openjdk.org/browse/JDK-5045147">JDK-5045147</a> is fixed.
224   */
225  @J2ktIncompatible
226  @GwtIncompatible // reflection
227  public static Method getPutNullKeyUnsupportedMethod() {
228    return getMethod(MapPutTester.class, "testPut_nullKeyUnsupported");
229  }
230}