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 java.util.Collections.emptyMap;
026import static java.util.Collections.singletonList;
027import static org.junit.Assert.assertThrows;
028
029import com.google.common.annotations.GwtCompatible;
030import com.google.common.annotations.GwtIncompatible;
031import com.google.common.annotations.J2ktIncompatible;
032import com.google.common.collect.testing.AbstractMapTester;
033import com.google.common.collect.testing.MinimalCollection;
034import com.google.common.collect.testing.features.CollectionSize;
035import com.google.common.collect.testing.features.MapFeature;
036import java.lang.reflect.Method;
037import java.util.ConcurrentModificationException;
038import java.util.Iterator;
039import java.util.LinkedHashMap;
040import java.util.List;
041import java.util.Map;
042import java.util.Map.Entry;
043import org.jspecify.annotations.NullMarked;
044import org.jspecify.annotations.Nullable;
045
046/**
047 * A generic JUnit test which tests {@code putAll} operations on a map. Can't be invoked directly;
048 * please see {@link com.google.common.collect.testing.MapTestSuiteBuilder}.
049 *
050 * @author Chris Povirk
051 * @author Kevin Bourrillion
052 */
053@GwtCompatible
054@NullMarked
055public class MapPutAllTester<K extends @Nullable Object, V extends @Nullable Object>
056    extends AbstractMapTester<K, V> {
057  private List<Entry<K, V>> containsNullKey;
058  private List<Entry<K, V>> containsNullValue;
059
060  @Override
061  public void setUp() throws Exception {
062    super.setUp();
063    containsNullKey = singletonList(entry(null, v3()));
064    containsNullValue = singletonList(entry(k3(), null));
065  }
066
067  @MapFeature.Require(SUPPORTS_PUT)
068  public void testPutAll_supportedNothing() {
069    getMap().putAll(emptyMap());
070    expectUnchanged();
071  }
072
073  @MapFeature.Require(absent = SUPPORTS_PUT)
074  public void testPutAll_unsupportedNothing() {
075    try {
076      getMap().putAll(emptyMap());
077    } catch (UnsupportedOperationException tolerated) {
078    }
079    expectUnchanged();
080  }
081
082  @MapFeature.Require(SUPPORTS_PUT)
083  public void testPutAll_supportedNonePresent() {
084    putAll(createDisjointCollection());
085    expectAdded(e3(), e4());
086  }
087
088  @MapFeature.Require(absent = SUPPORTS_PUT)
089  public void testPutAll_unsupportedNonePresent() {
090    assertThrows(UnsupportedOperationException.class, () -> putAll(createDisjointCollection()));
091    expectUnchanged();
092    expectMissing(e3(), e4());
093  }
094
095  @MapFeature.Require(SUPPORTS_PUT)
096  @CollectionSize.Require(absent = ZERO)
097  public void testPutAll_supportedSomePresent() {
098    putAll(MinimalCollection.of(e3(), e0()));
099    expectAdded(e3());
100  }
101
102  @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT})
103  @CollectionSize.Require(absent = ZERO)
104  public void testPutAllSomePresentConcurrentWithEntrySetIteration() {
105    Iterator<Entry<K, V>> iterator = getMap().entrySet().iterator();
106    putAll(MinimalCollection.of(e3(), e0()));
107    assertThrows(ConcurrentModificationException.class, iterator::next);
108  }
109
110  @MapFeature.Require(absent = SUPPORTS_PUT)
111  @CollectionSize.Require(absent = ZERO)
112  public void testPutAll_unsupportedSomePresent() {
113    assertThrows(
114        UnsupportedOperationException.class, () -> putAll(MinimalCollection.of(e3(), e0())));
115    expectUnchanged();
116  }
117
118  @MapFeature.Require(absent = SUPPORTS_PUT)
119  @CollectionSize.Require(absent = ZERO)
120  public void testPutAll_unsupportedAllPresent() {
121    try {
122      putAll(MinimalCollection.of(e0()));
123    } catch (UnsupportedOperationException tolerated) {
124    }
125    expectUnchanged();
126  }
127
128  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS})
129  public void testPutAll_nullKeySupported() {
130    putAll(containsNullKey);
131    expectAdded(containsNullKey.get(0));
132  }
133
134  @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_KEYS)
135  public void testPutAll_nullKeyUnsupported() {
136    assertThrows(NullPointerException.class, () -> putAll(containsNullKey));
137    expectUnchanged();
138    expectNullKeyMissingWhenNullKeysUnsupported(
139        "Should not contain null key after unsupported putAll(containsNullKey)");
140  }
141
142  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES})
143  public void testPutAll_nullValueSupported() {
144    putAll(containsNullValue);
145    expectAdded(containsNullValue.get(0));
146  }
147
148  @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES)
149  public void testPutAll_nullValueUnsupported() {
150    assertThrows(NullPointerException.class, () -> putAll(containsNullValue));
151    expectUnchanged();
152    expectNullValueMissingWhenNullValuesUnsupported(
153        "Should not contain null value after unsupported putAll(containsNullValue)");
154  }
155
156  @MapFeature.Require(SUPPORTS_PUT)
157  public void testPutAll_nullCollectionReference() {
158    assertThrows(NullPointerException.class, () -> getMap().putAll(null));
159  }
160
161  private void putAll(Iterable<Entry<K, V>> entries) {
162    Map<K, V> map = new LinkedHashMap<>();
163    for (Entry<K, V> entry : entries) {
164      map.put(entry.getKey(), entry.getValue());
165    }
166    getMap().putAll(map);
167  }
168
169  /**
170   * Returns the {@link Method} instance for {@link #testPutAll_nullKeyUnsupported()} so that tests
171   * can suppress it with {@code FeatureSpecificTestSuiteBuilder.suppressing()} until <a
172   * href="https://bugs.openjdk.org/browse/JDK-5045147">JDK-5045147</a> is fixed.
173   */
174  @J2ktIncompatible
175  @GwtIncompatible // reflection
176  public static Method getPutAllNullKeyUnsupportedMethod() {
177    return getMethod(MapPutAllTester.class, "testPutAll_nullKeyUnsupported");
178  }
179}