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; 018 019import com.google.common.annotations.GwtCompatible; 020import java.util.Collection; 021import java.util.Iterator; 022import java.util.List; 023import java.util.ListIterator; 024import java.util.Map; 025import java.util.Map.Entry; 026import org.checkerframework.checker.nullness.qual.Nullable; 027import org.junit.Ignore; 028 029/** 030 * Base class for map testers. 031 * 032 * <p>TODO: see how much of this is actually needed once Map testers are written. (It was cloned 033 * from AbstractCollectionTester.) 034 * 035 * @param <K> the key type of the map to be tested. 036 * @param <V> the value type of the map to be tested. 037 * @author George van den Driessche 038 */ 039@GwtCompatible 040@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. 041@SuppressWarnings("JUnit4ClassUsedInJUnit3") 042@ElementTypesAreNonnullByDefault 043public abstract class AbstractMapTester<K extends @Nullable Object, V extends @Nullable Object> 044 extends AbstractContainerTester<Map<K, V>, Entry<K, V>> { 045 protected Map<K, V> getMap() { 046 return container; 047 } 048 049 @Override 050 protected Collection<Entry<K, V>> actualContents() { 051 return getMap().entrySet(); 052 } 053 054 /** @see AbstractContainerTester#resetContainer() */ 055 protected final void resetMap() { 056 resetContainer(); 057 } 058 059 protected void resetMap(Entry<K, V>[] entries) { 060 resetContainer(getSubjectGenerator().create((Object[]) entries)); 061 } 062 063 protected void expectMissingKeys(K... elements) { 064 for (K element : elements) { 065 assertFalse("Should not contain key " + element, getMap().containsKey(element)); 066 } 067 } 068 069 protected void expectMissingValues(V... elements) { 070 for (V element : elements) { 071 assertFalse("Should not contain value " + element, getMap().containsValue(element)); 072 } 073 } 074 075 /** @return an array of the proper size with {@code null} as the key of the middle element. */ 076 protected Entry<K, V>[] createArrayWithNullKey() { 077 Entry<K, V>[] array = createSamplesArray(); 078 int nullKeyLocation = getNullLocation(); 079 Entry<K, V> oldEntry = array[nullKeyLocation]; 080 array[nullKeyLocation] = entry(null, oldEntry.getValue()); 081 return array; 082 } 083 084 protected V getValueForNullKey() { 085 return getEntryNullReplaces().getValue(); 086 } 087 088 protected K getKeyForNullValue() { 089 return getEntryNullReplaces().getKey(); 090 } 091 092 private Entry<K, V> getEntryNullReplaces() { 093 Iterator<Entry<K, V>> entries = getSampleElements().iterator(); 094 for (int i = 0; i < getNullLocation(); i++) { 095 entries.next(); 096 } 097 return entries.next(); 098 } 099 100 /** @return an array of the proper size with {@code null} as the value of the middle element. */ 101 protected Entry<K, V>[] createArrayWithNullValue() { 102 Entry<K, V>[] array = createSamplesArray(); 103 int nullValueLocation = getNullLocation(); 104 Entry<K, V> oldEntry = array[nullValueLocation]; 105 array[nullValueLocation] = entry(oldEntry.getKey(), null); 106 return array; 107 } 108 109 protected void initMapWithNullKey() { 110 resetMap(createArrayWithNullKey()); 111 } 112 113 protected void initMapWithNullValue() { 114 resetMap(createArrayWithNullValue()); 115 } 116 117 /** 118 * Equivalent to {@link #expectMissingKeys(Object[]) expectMissingKeys} {@code (null)} except that 119 * the call to {@code contains(null)} is permitted to throw a {@code NullPointerException}. 120 * 121 * @param message message to use upon assertion failure 122 */ 123 protected void expectNullKeyMissingWhenNullKeysUnsupported(String message) { 124 try { 125 assertFalse(message, getMap().containsKey(null)); 126 } catch (NullPointerException tolerated) { 127 // Tolerated 128 } 129 } 130 131 /** 132 * Equivalent to {@link #expectMissingValues(Object[]) expectMissingValues} {@code (null)} except 133 * that the call to {@code contains(null)} is permitted to throw a {@code NullPointerException}. 134 * 135 * @param message message to use upon assertion failure 136 */ 137 protected void expectNullValueMissingWhenNullValuesUnsupported(String message) { 138 try { 139 assertFalse(message, getMap().containsValue(null)); 140 } catch (NullPointerException tolerated) { 141 // Tolerated 142 } 143 } 144 145 @Override 146 protected MinimalCollection<Entry<K, V>> createDisjointCollection() { 147 return MinimalCollection.of(e3(), e4()); 148 } 149 150 protected int getNumEntries() { 151 return getNumElements(); 152 } 153 154 protected Collection<Entry<K, V>> getSampleEntries(int howMany) { 155 return getSampleElements(howMany); 156 } 157 158 protected Collection<Entry<K, V>> getSampleEntries() { 159 return getSampleElements(); 160 } 161 162 @Override 163 protected void expectMissing(Entry<K, V>... entries) { 164 for (Entry<K, V> entry : entries) { 165 assertFalse("Should not contain entry " + entry, actualContents().contains(entry)); 166 assertFalse( 167 "Should not contain key " + entry.getKey() + " mapped to value " + entry.getValue(), 168 equal(getMap().get(entry.getKey()), entry.getValue())); 169 } 170 } 171 172 private static boolean equal(@Nullable Object a, @Nullable Object b) { 173 return a == b || (a != null && a.equals(b)); 174 } 175 176 // This one-liner saves us from some ugly casts 177 protected Entry<K, V> entry(K key, V value) { 178 return Helpers.mapEntry(key, value); 179 } 180 181 @Override 182 protected void expectContents(Collection<Entry<K, V>> expected) { 183 // TODO: move this to invariant checks once the appropriate hook exists? 184 super.expectContents(expected); 185 for (Entry<K, V> entry : expected) { 186 assertEquals( 187 "Wrong value for key " + entry.getKey(), entry.getValue(), getMap().get(entry.getKey())); 188 } 189 } 190 191 protected final void expectReplacement(Entry<K, V> newEntry) { 192 List<Entry<K, V>> expected = Helpers.copyToList(getSampleElements()); 193 replaceValue(expected, newEntry); 194 expectContents(expected); 195 } 196 197 private void replaceValue(List<Entry<K, V>> expected, Entry<K, V> newEntry) { 198 for (ListIterator<Entry<K, V>> i = expected.listIterator(); i.hasNext(); ) { 199 if (Helpers.equal(i.next().getKey(), newEntry.getKey())) { 200 i.set(newEntry); 201 return; 202 } 203 } 204 205 throw new IllegalArgumentException( 206 Platform.format("key %s not found in entries %s", newEntry.getKey(), expected)); 207 } 208 209 /** 210 * Wrapper for {@link Map#get(Object)} that forces the caller to pass in a key of the same type as 211 * the map. Besides being slightly shorter than code that uses {@link #getMap()}, it also ensures 212 * that callers don't pass an {@link Entry} by mistake. 213 */ 214 protected V get(K key) { 215 return getMap().get(key); 216 } 217 218 protected final K k0() { 219 return e0().getKey(); 220 } 221 222 protected final V v0() { 223 return e0().getValue(); 224 } 225 226 protected final K k1() { 227 return e1().getKey(); 228 } 229 230 protected final V v1() { 231 return e1().getValue(); 232 } 233 234 protected final K k2() { 235 return e2().getKey(); 236 } 237 238 protected final V v2() { 239 return e2().getValue(); 240 } 241 242 protected final K k3() { 243 return e3().getKey(); 244 } 245 246 protected final V v3() { 247 return e3().getValue(); 248 } 249 250 protected final K k4() { 251 return e4().getKey(); 252 } 253 254 protected final V v4() { 255 return e4().getValue(); 256 } 257}