1/*
2 * Copyright (C) 2011 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;
18
19import com.google.common.annotations.GwtCompatible;
20import com.google.common.base.Function;
21import com.google.common.base.Functions;
22import com.google.common.collect.testing.MapInterfaceTest;
23
24import java.util.Collection;
25import java.util.Iterator;
26import java.util.Map;
27import java.util.Set;
28
29import javax.annotation.Nullable;
30
31/**
32 * Tests for {@link Maps#transformValues} when the backing map's views
33 * have iterators that don't support {@code remove()}.
34 *
35 * @author Jared Levy
36 */
37@GwtCompatible
38public class MapsTransformValuesUnmodifiableIteratorTest extends MapInterfaceTest<String, String> {
39  // TODO(jlevy): Move shared code of this class and MapsTransformValuesTest
40  // to a superclass.
41
42  public MapsTransformValuesUnmodifiableIteratorTest() {
43    super(true, true, false /*supportsPut*/, true, true, false);
44  }
45
46  private static class UnmodifiableIteratorMap<K, V> extends ForwardingMap<K, V> {
47    final Map<K, V> delegate;
48
49    UnmodifiableIteratorMap(Map<K, V> delegate) {
50      this.delegate = delegate;
51    }
52
53    @Override protected Map<K, V> delegate() {
54      return delegate;
55    }
56
57    @Override public Set<K> keySet() {
58      return new ForwardingSet<K>() {
59        @Override protected Set<K> delegate() {
60          return delegate.keySet();
61        }
62        @Override public Iterator<K> iterator() {
63          return Iterators.unmodifiableIterator(delegate.keySet().iterator());
64        }
65        @Override public boolean removeAll(Collection<?> c) {
66          return delegate.keySet().removeAll(c);
67        }
68        @Override public boolean retainAll(Collection<?> c) {
69          return delegate.keySet().retainAll(c);
70        }
71      };
72    }
73
74    @Override public Collection<V> values() {
75      return new ForwardingCollection<V>() {
76        @Override protected Collection<V> delegate() {
77          return delegate.values();
78        }
79        @Override public Iterator<V> iterator() {
80          return Iterators.unmodifiableIterator(delegate.values().iterator());
81        }
82        @Override public boolean removeAll(Collection<?> c) {
83          return delegate.values().removeAll(c);
84        }
85        @Override public boolean retainAll(Collection<?> c) {
86          return delegate.values().retainAll(c);
87        }
88      };
89    }
90
91    @Override public Set<Entry<K, V>> entrySet() {
92      return new ForwardingSet<Entry<K, V>>() {
93        @Override protected Set<Entry<K, V>> delegate() {
94          return delegate.entrySet();
95        }
96        @Override public Iterator<Entry<K, V>> iterator() {
97          return Iterators.unmodifiableIterator(delegate.entrySet().iterator());
98        }
99        @Override public boolean removeAll(Collection<?> c) {
100          return delegate.entrySet().removeAll(c);
101        }
102        @Override public boolean retainAll(Collection<?> c) {
103          return delegate.entrySet().retainAll(c);
104        }
105      };
106    }
107  }
108
109  @Override protected Map<String, String> makeEmptyMap() {
110    Map<String, Integer> underlying = Maps.newHashMap();
111    return Maps.transformValues(
112        new UnmodifiableIteratorMap<String, Integer>(underlying), Functions.toStringFunction());
113  }
114
115  @Override protected Map<String, String> makePopulatedMap() {
116    Map<String, Integer> underlying = Maps.newHashMap();
117    underlying.put("a", 1);
118    underlying.put("b", 2);
119    underlying.put("c", 3);
120    return Maps.transformValues(
121        new UnmodifiableIteratorMap<String, Integer>(underlying), Functions.toStringFunction());
122  }
123
124  @Override protected String getKeyNotInPopulatedMap()
125      throws UnsupportedOperationException {
126    return "z";
127  }
128
129  @Override protected String getValueNotInPopulatedMap()
130      throws UnsupportedOperationException {
131    return "26";
132  }
133
134  /** Helper assertion comparing two maps */
135  private void assertMapsEqual(Map<?, ?> expected, Map<?, ?> map) {
136    assertEquals(expected, map);
137    assertEquals(expected.hashCode(), map.hashCode());
138    assertEquals(expected.entrySet(), map.entrySet());
139
140    // Assert that expectedValues > mapValues and that
141    // mapValues > expectedValues; i.e. that expectedValues == mapValues.
142    Collection<?> expectedValues = expected.values();
143    Collection<?> mapValues = map.values();
144    assertEquals(expectedValues.size(), mapValues.size());
145    assertTrue(expectedValues.containsAll(mapValues));
146    assertTrue(mapValues.containsAll(expectedValues));
147  }
148
149  public void testTransformEmptyMapEquality() {
150    Map<String, String> map = Maps.transformValues(
151        ImmutableMap.<String, Integer>of(), Functions.toStringFunction());
152    assertMapsEqual(Maps.newHashMap(), map);
153  }
154
155  public void testTransformSingletonMapEquality() {
156    Map<String, String> map = Maps.transformValues(
157        ImmutableMap.of("a", 1), Functions.toStringFunction());
158    Map<String, String> expected = ImmutableMap.of("a", "1");
159    assertMapsEqual(expected, map);
160    assertEquals(expected.get("a"), map.get("a"));
161  }
162
163  public void testTransformIdentityFunctionEquality() {
164    Map<String, Integer> underlying = ImmutableMap.of("a", 1);
165    Map<String, Integer> map = Maps.transformValues(
166        underlying, Functions.<Integer>identity());
167    assertMapsEqual(underlying, map);
168  }
169
170  public void testTransformPutEntryIsUnsupported() {
171    Map<String, String> map = Maps.transformValues(
172        ImmutableMap.of("a", 1), Functions.toStringFunction());
173    try {
174      map.put("b", "2");
175      fail();
176    } catch (UnsupportedOperationException expected) {
177    }
178
179    try {
180      map.putAll(ImmutableMap.of("b", "2"));
181      fail();
182    } catch (UnsupportedOperationException expected) {
183    }
184
185    try {
186      map.entrySet().iterator().next().setValue("one");
187      fail();
188    } catch (UnsupportedOperationException expected) {
189    }
190  }
191
192  public void testTransformRemoveEntry() {
193    Map<String, Integer> underlying = Maps.newHashMap();
194    underlying.put("a", 1);
195    Map<String, String> map
196        = Maps.transformValues(underlying, Functions.toStringFunction());
197    assertEquals("1", map.remove("a"));
198    assertNull(map.remove("b"));
199  }
200
201  public void testTransformEqualityOfMapsWithNullValues() {
202    Map<String, String> underlying = Maps.newHashMap();
203    underlying.put("a", null);
204    underlying.put("b", "");
205
206    Map<String, Boolean> map = Maps.transformValues(underlying,
207        new Function<String, Boolean>() {
208          @Override
209          public Boolean apply(@Nullable String from) {
210            return from == null;
211          }
212        }
213    );
214    Map<String, Boolean> expected = ImmutableMap.of("a", true, "b", false);
215    assertMapsEqual(expected, map);
216    assertEquals(expected.get("a"), map.get("a"));
217    assertEquals(expected.containsKey("a"), map.containsKey("a"));
218    assertEquals(expected.get("b"), map.get("b"));
219    assertEquals(expected.containsKey("b"), map.containsKey("b"));
220    assertEquals(expected.get("c"), map.get("c"));
221    assertEquals(expected.containsKey("c"), map.containsKey("c"));
222  }
223
224  public void testTransformReflectsUnderlyingMap() {
225    Map<String, Integer> underlying = Maps.newHashMap();
226    underlying.put("a", 1);
227    underlying.put("b", 2);
228    underlying.put("c", 3);
229    Map<String, String> map
230        = Maps.transformValues(underlying, Functions.toStringFunction());
231    assertEquals(underlying.size(), map.size());
232
233    underlying.put("d", 4);
234    assertEquals(underlying.size(), map.size());
235    assertEquals("4", map.get("d"));
236
237    underlying.remove("c");
238    assertEquals(underlying.size(), map.size());
239    assertFalse(map.containsKey("c"));
240
241    underlying.clear();
242    assertEquals(underlying.size(), map.size());
243  }
244
245  public void testTransformChangesAreReflectedInUnderlyingMap() {
246    Map<String, Integer> underlying = Maps.newLinkedHashMap();
247    underlying.put("a", 1);
248    underlying.put("b", 2);
249    underlying.put("c", 3);
250    underlying.put("d", 4);
251    underlying.put("e", 5);
252    underlying.put("f", 6);
253    underlying.put("g", 7);
254    Map<String, String> map
255        = Maps.transformValues(underlying, Functions.toStringFunction());
256
257    map.remove("a");
258    assertFalse(underlying.containsKey("a"));
259
260    Set<String> keys = map.keySet();
261    keys.remove("b");
262    assertFalse(underlying.containsKey("b"));
263
264    Iterator<String> keyIterator = keys.iterator();
265    keyIterator.next();
266    keyIterator.remove();
267    assertFalse(underlying.containsKey("c"));
268
269    Collection<String> values = map.values();
270    values.remove("4");
271    assertFalse(underlying.containsKey("d"));
272
273    Iterator<String> valueIterator = values.iterator();
274    valueIterator.next();
275    valueIterator.remove();
276    assertFalse(underlying.containsKey("e"));
277
278    Set<Map.Entry<String, String>> entries = map.entrySet();
279    Map.Entry<String, String> firstEntry = entries.iterator().next();
280    entries.remove(firstEntry);
281    assertFalse(underlying.containsKey("f"));
282
283    Iterator<Map.Entry<String, String>> entryIterator = entries.iterator();
284    entryIterator.next();
285    entryIterator.remove();
286    assertFalse(underlying.containsKey("g"));
287
288    assertTrue(underlying.isEmpty());
289    assertTrue(map.isEmpty());
290    assertTrue(keys.isEmpty());
291    assertTrue(values.isEmpty());
292    assertTrue(entries.isEmpty());
293  }
294
295  public void testTransformEquals() {
296    Map<String, Integer> underlying = ImmutableMap.of("a", 0, "b", 1, "c", 2);
297    Map<String, Integer> expected
298        = Maps.transformValues(underlying, Functions.<Integer>identity());
299
300    assertMapsEqual(expected, expected);
301
302    Map<String, Integer> equalToUnderlying = Maps.newTreeMap();
303    equalToUnderlying.putAll(underlying);
304    Map<String, Integer> map = Maps.transformValues(
305        equalToUnderlying, Functions.<Integer>identity());
306    assertMapsEqual(expected, map);
307
308    map = Maps.transformValues(ImmutableMap.of("a", 1, "b", 2, "c", 3),
309        new Function<Integer, Integer>() {
310          @Override
311          public Integer apply(Integer from) {
312            return from - 1;
313          }
314        }
315    );
316    assertMapsEqual(expected, map);
317  }
318
319  public void testTransformEntrySetContains() {
320    Map<String, Boolean> underlying = Maps.newHashMap();
321    underlying.put("a", null);
322    underlying.put("b", true);
323    underlying.put(null, true);
324
325    Map<String, Boolean> map = Maps.transformValues(
326        underlying, new Function<Boolean, Boolean>() {
327          @Override
328          public Boolean apply(@Nullable Boolean from) {
329            return (from == null) ? true : null;
330          }
331        }
332    );
333
334    Set<Map.Entry<String, Boolean>> entries = map.entrySet();
335    assertTrue(entries.contains(Maps.immutableEntry("a", true)));
336    assertTrue(entries.contains(Maps.immutableEntry("b", (Boolean) null)));
337    assertTrue(entries.contains(
338        Maps.immutableEntry((String) null, (Boolean) null)));
339
340    assertFalse(entries.contains(Maps.immutableEntry("c", (Boolean) null)));
341    assertFalse(entries.contains(Maps.immutableEntry((String) null, true)));
342  }
343
344  @Override public void testKeySetRemoveAllNullFromEmpty() {
345    try {
346      super.testKeySetRemoveAllNullFromEmpty();
347    } catch (RuntimeException tolerated) {
348      // GWT's HashMap.keySet().removeAll(null) doesn't throws NPE.
349    }
350  }
351
352  @Override public void testEntrySetRemoveAllNullFromEmpty() {
353    try {
354      super.testEntrySetRemoveAllNullFromEmpty();
355    } catch (RuntimeException tolerated) {
356      // GWT's HashMap.entrySet().removeAll(null) doesn't throws NPE.
357    }
358  }
359}
360