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 
17 package com.google.common.collect.testing;
18 
19 import com.google.common.annotations.GwtCompatible;
20 
21 import java.util.Collection;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.ListIterator;
25 import java.util.Map;
26 import java.util.Map.Entry;
27 
28 /**
29  * Base class for map testers.
30  *
31  * TODO: see how much of this is actually needed once Map testers are written.
32  * (It was cloned from AbstractCollectionTester.)
33  *
34  * @param <K> the key type of the map to be tested.
35  * @param <V> the value type of the map to be tested.
36  *
37  * @author George van den Driessche
38  */
39 @GwtCompatible
40 public abstract class AbstractMapTester<K, V> extends
41     AbstractContainerTester<Map<K, V>, Map.Entry<K, V>> {
42   protected Map<K, V> getMap() {
43     return container;
44   }
45 
46   @Override public void setUp() throws Exception {
47     super.setUp();
48     samples = this.getSubjectGenerator().samples();
49     resetMap();
50   }
51 
52   @Override protected Collection<Map.Entry<K, V>> actualContents() {
53     return getMap().entrySet();
54   }
55 
56   /** @see AbstractContainerTester#resetContainer() */
57   protected void resetMap() {
58     resetContainer();
59   }
60 
61   protected void expectMissingKeys(K... elements) {
62     for (K element : elements) {
63       assertFalse("Should not contain key " + element,
64           getMap().containsKey(element));
65     }
66   }
67 
68   protected void expectMissingValues(V... elements) {
69     for (V element : elements) {
70       assertFalse("Should not contain value " + element,
71           getMap().containsValue(element));
72     }
73   }
74 
75   /**
76    * @return an array of the proper size with {@code null} as the key of the
77    * middle element.
78    */
79   protected Map.Entry<K, V>[] createArrayWithNullKey() {
80     Map.Entry<K, V>[] array = createSamplesArray();
81     final int nullKeyLocation = getNullLocation();
82     final Map.Entry<K, V> oldEntry = array[nullKeyLocation];
83     array[nullKeyLocation] = entry(null, oldEntry.getValue());
84     return array;
85   }
86 
87   protected V getValueForNullKey() {
88     return getEntryNullReplaces().getValue();
89   }
90 
91   protected K getKeyForNullValue() {
92     return getEntryNullReplaces().getKey();
93   }
94 
95   private Entry<K, V> getEntryNullReplaces() {
96     Iterator<Entry<K, V>> entries = getSampleElements().iterator();
97     for (int i = 0; i < getNullLocation(); i++) {
98       entries.next();
99     }
100     return entries.next();
101   }
102 
103   /**
104    * @return an array of the proper size with {@code null} as the value of the
105    * middle element.
106    */
107   protected Map.Entry<K, V>[] createArrayWithNullValue() {
108     Map.Entry<K, V>[] array = createSamplesArray();
109     final int nullValueLocation = getNullLocation();
110     final Map.Entry<K, V> oldEntry = array[nullValueLocation];
111     array[nullValueLocation] = entry(oldEntry.getKey(), null);
112     return array;
113   }
114 
115   protected void initMapWithNullKey() {
116     resetMap(createArrayWithNullKey());
117   }
118 
119   protected void initMapWithNullValue() {
120     resetMap(createArrayWithNullValue());
121   }
122 
123   /**
124    * Equivalent to {@link #expectMissingKeys(Object[]) expectMissingKeys}
125    * {@code (null)}
126    * except that the call to {@code contains(null)} is permitted to throw a
127    * {@code NullPointerException}.
128    * @param message message to use upon assertion failure
129    */
130   protected void expectNullKeyMissingWhenNullKeysUnsupported(String message) {
131     try {
132       assertFalse(message, getMap().containsKey(null));
133     } catch (NullPointerException tolerated) {
134       // Tolerated
135     }
136   }
137 
138   /**
139    * Equivalent to {@link #expectMissingValues(Object[]) expectMissingValues}
140    * {@code (null)}
141    * except that the call to {@code contains(null)} is permitted to throw a
142    * {@code NullPointerException}.
143    * @param message message to use upon assertion failure
144    */
145   protected void expectNullValueMissingWhenNullValuesUnsupported(
146       String message) {
147     try {
148       assertFalse(message, getMap().containsValue(null));
149     } catch (NullPointerException tolerated) {
150       // Tolerated
151     }
152   }
153 
154   @SuppressWarnings("unchecked")
155   @Override protected MinimalCollection<Map.Entry<K, V>>
156       createDisjointCollection() {
157     return MinimalCollection.of(samples.e3, samples.e4);
158   }
159 
160   protected int getNumEntries() {
161     return getNumElements();
162   }
163 
164   protected Collection<Map.Entry<K, V>> getSampleEntries(int howMany) {
165     return getSampleElements(howMany);
166   }
167 
168   protected Collection<Map.Entry<K, V>> getSampleEntries() {
169     return getSampleElements();
170   }
171 
172   @Override protected void expectMissing(Entry<K, V>... entries) {
173     for (Entry<K, V> entry : entries) {
174       assertFalse("Should not contain entry " + entry,
175           actualContents().contains(entry));
176       assertFalse("Should not contain key " + entry.getKey() + " mapped to"
177           + " value " + entry.getValue(),
178           equal(getMap().get(entry.getKey()), entry.getValue()));
179     }
180   }
181 
182   private static boolean equal(Object a, Object b) {
183     return a == b || (a != null && a.equals(b));
184   }
185 
186   // This one-liner saves us from some ugly casts
187   protected Entry<K, V> entry(K key, V value) {
188     return Helpers.mapEntry(key, value);
189   }
190 
191   @Override protected void expectContents(Collection<Entry<K, V>> expected) {
192     // TODO: move this to invariant checks once the appropriate hook exists?
193     super.expectContents(expected);
194     for (Entry<K, V> entry : expected) {
195       assertEquals("Wrong value for key " + entry.getKey(),
196           entry.getValue(), getMap().get(entry.getKey()));
197     }
198   }
199 
200   protected final void expectReplacement(Entry<K, V> newEntry) {
201     List<Entry<K, V>> expected = Helpers.copyToList(getSampleElements());
202     replaceValue(expected, newEntry);
203     expectContents(expected);
204   }
205 
206   private void replaceValue(List<Entry<K, V>> expected, Entry<K, V> newEntry) {
207     for (ListIterator<Entry<K, V>> i = expected.listIterator(); i.hasNext();) {
208       if (Helpers.equal(i.next().getKey(), newEntry.getKey())) {
209         i.set(newEntry);
210         return;
211       }
212     }
213 
214     throw new IllegalArgumentException(Platform.format(
215         "key %s not found in entries %s", newEntry.getKey(), expected));
216   }
217 
218   /**
219    * Wrapper for {@link Map#get(Object)} that forces the caller to pass in a key
220    * of the same type as the map. Besides being slightly shorter than code that
221    * uses {@link #getMap()}, it also ensures that callers don't pass an
222    * {@link Entry} by mistake.
223    */
224   protected V get(K key) {
225     return getMap().get(key);
226   }
227 
228   protected void resetMap(Entry<K, V>[] entries) {
229     resetContainer(getSubjectGenerator().create((Object[]) entries));
230   }
231 }
232