1 /* 2 * Copyright (C) 2009 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; 18 19 import static java.lang.reflect.Modifier.STATIC; 20 import static org.mockito.ArgumentMatchers.any; 21 import static org.mockito.Mockito.atLeast; 22 import static org.mockito.Mockito.mock; 23 import static org.mockito.Mockito.verify; 24 import static org.mockito.Mockito.verifyNoMoreInteractions; 25 26 import com.google.common.base.Function; 27 import com.google.common.collect.testing.MapTestSuiteBuilder; 28 import com.google.common.collect.testing.TestStringMapGenerator; 29 import com.google.common.collect.testing.features.CollectionFeature; 30 import com.google.common.collect.testing.features.CollectionSize; 31 import com.google.common.collect.testing.features.MapFeature; 32 import com.google.common.reflect.AbstractInvocationHandler; 33 import com.google.common.reflect.Parameter; 34 import com.google.common.reflect.Reflection; 35 import com.google.common.reflect.TypeToken; 36 import com.google.common.testing.ArbitraryInstances; 37 import com.google.common.testing.EqualsTester; 38 import com.google.common.testing.ForwardingWrapperTester; 39 import java.lang.reflect.InvocationTargetException; 40 import java.lang.reflect.Method; 41 import java.util.Arrays; 42 import java.util.Collection; 43 import java.util.Iterator; 44 import java.util.Map; 45 import java.util.Map.Entry; 46 import java.util.Set; 47 import junit.framework.Test; 48 import junit.framework.TestCase; 49 import junit.framework.TestSuite; 50 51 /** 52 * Unit test for {@link ForwardingMap}. 53 * 54 * @author Hayward Chan 55 * @author Louis Wasserman 56 */ 57 public class ForwardingMapTest extends TestCase { 58 static class StandardImplForwardingMap<K, V> extends ForwardingMap<K, V> { 59 private final Map<K, V> backingMap; 60 StandardImplForwardingMap(Map<K, V> backingMap)61 StandardImplForwardingMap(Map<K, V> backingMap) { 62 this.backingMap = backingMap; 63 } 64 65 @Override delegate()66 protected Map<K, V> delegate() { 67 return backingMap; 68 } 69 70 @Override containsKey(Object key)71 public boolean containsKey(Object key) { 72 return standardContainsKey(key); 73 } 74 75 @Override containsValue(Object value)76 public boolean containsValue(Object value) { 77 return standardContainsValue(value); 78 } 79 80 @Override putAll(Map<? extends K, ? extends V> map)81 public void putAll(Map<? extends K, ? extends V> map) { 82 standardPutAll(map); 83 } 84 85 @Override remove(Object object)86 public V remove(Object object) { 87 return standardRemove(object); 88 } 89 90 @Override equals(Object object)91 public boolean equals(Object object) { 92 return standardEquals(object); 93 } 94 95 @Override hashCode()96 public int hashCode() { 97 return standardHashCode(); 98 } 99 100 @Override keySet()101 public Set<K> keySet() { 102 return new StandardKeySet(); 103 } 104 105 @Override values()106 public Collection<V> values() { 107 return new StandardValues(); 108 } 109 110 @Override toString()111 public String toString() { 112 return standardToString(); 113 } 114 115 @Override entrySet()116 public Set<Entry<K, V>> entrySet() { 117 return new StandardEntrySet() { 118 @Override 119 public Iterator<Entry<K, V>> iterator() { 120 return delegate().entrySet().iterator(); 121 } 122 }; 123 } 124 125 @Override clear()126 public void clear() { 127 standardClear(); 128 } 129 130 @Override isEmpty()131 public boolean isEmpty() { 132 return standardIsEmpty(); 133 } 134 } 135 136 public static Test suite() { 137 TestSuite suite = new TestSuite(); 138 139 suite.addTestSuite(ForwardingMapTest.class); 140 suite.addTest( 141 MapTestSuiteBuilder.using( 142 new TestStringMapGenerator() { 143 144 @Override 145 protected Map<String, String> create(Entry<String, String>[] entries) { 146 Map<String, String> map = Maps.newLinkedHashMap(); 147 for (Entry<String, String> entry : entries) { 148 map.put(entry.getKey(), entry.getValue()); 149 } 150 return new StandardImplForwardingMap<>(map); 151 } 152 }) 153 .named("ForwardingMap[LinkedHashMap] with standard implementations") 154 .withFeatures( 155 CollectionSize.ANY, 156 MapFeature.ALLOWS_NULL_VALUES, 157 MapFeature.ALLOWS_NULL_KEYS, 158 MapFeature.ALLOWS_ANY_NULL_QUERIES, 159 MapFeature.GENERAL_PURPOSE, 160 CollectionFeature.SUPPORTS_ITERATOR_REMOVE, 161 CollectionFeature.KNOWN_ORDER) 162 .createTestSuite()); 163 suite.addTest( 164 MapTestSuiteBuilder.using( 165 new TestStringMapGenerator() { 166 167 @Override 168 protected Map<String, String> create(Entry<String, String>[] entries) { 169 ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); 170 for (Entry<String, String> entry : entries) { 171 builder.put(entry.getKey(), entry.getValue()); 172 } 173 return new StandardImplForwardingMap<>(builder.build()); 174 } 175 }) 176 .named("ForwardingMap[ImmutableMap] with standard implementations") 177 .withFeatures( 178 CollectionSize.ANY, 179 MapFeature.REJECTS_DUPLICATES_AT_CREATION, 180 MapFeature.ALLOWS_ANY_NULL_QUERIES, 181 CollectionFeature.KNOWN_ORDER) 182 .createTestSuite()); 183 184 return suite; 185 } 186 187 @SuppressWarnings({"rawtypes", "unchecked"}) 188 public void testForwarding() { 189 new ForwardingWrapperTester() 190 .testForwarding( 191 Map.class, 192 new Function<Map, Map>() { 193 @Override 194 public Map apply(Map delegate) { 195 return wrap(delegate); 196 } 197 }); 198 } 199 200 public void testEquals() { 201 Map<Integer, String> map1 = ImmutableMap.of(1, "one"); 202 Map<Integer, String> map2 = ImmutableMap.of(2, "two"); 203 new EqualsTester() 204 .addEqualityGroup(map1, wrap(map1), wrap(map1)) 205 .addEqualityGroup(map2, wrap(map2)) 206 .testEquals(); 207 } 208 209 public void testStandardEntrySet() throws InvocationTargetException { 210 @SuppressWarnings("unchecked") 211 final Map<String, Boolean> map = mock(Map.class); 212 213 Map<String, Boolean> forward = 214 new ForwardingMap<String, Boolean>() { 215 @Override 216 protected Map<String, Boolean> delegate() { 217 return map; 218 } 219 220 @Override 221 public Set<Entry<String, Boolean>> entrySet() { 222 return new StandardEntrySet() { 223 @Override 224 public Iterator<Entry<String, Boolean>> iterator() { 225 return Iterators.emptyIterator(); 226 } 227 }; 228 } 229 }; 230 callAllPublicMethods(new TypeToken<Set<Entry<String, Boolean>>>() {}, forward.entrySet()); 231 232 // These are the methods specified by StandardEntrySet 233 verify(map, atLeast(0)).clear(); 234 verify(map, atLeast(0)).containsKey(any()); 235 verify(map, atLeast(0)).get(any()); 236 verify(map, atLeast(0)).isEmpty(); 237 verify(map, atLeast(0)).remove(any()); 238 verify(map, atLeast(0)).size(); 239 verifyNoMoreInteractions(map); 240 } 241 242 public void testStandardKeySet() throws InvocationTargetException { 243 @SuppressWarnings("unchecked") 244 final Map<String, Boolean> map = mock(Map.class); 245 246 Map<String, Boolean> forward = 247 new ForwardingMap<String, Boolean>() { 248 @Override 249 protected Map<String, Boolean> delegate() { 250 return map; 251 } 252 253 @Override 254 public Set<String> keySet() { 255 return new StandardKeySet(); 256 } 257 }; 258 callAllPublicMethods(new TypeToken<Set<String>>() {}, forward.keySet()); 259 260 // These are the methods specified by StandardKeySet 261 verify(map, atLeast(0)).clear(); 262 verify(map, atLeast(0)).containsKey(any()); 263 verify(map, atLeast(0)).isEmpty(); 264 verify(map, atLeast(0)).remove(any()); 265 verify(map, atLeast(0)).size(); 266 verify(map, atLeast(0)).entrySet(); 267 verifyNoMoreInteractions(map); 268 } 269 270 public void testStandardValues() throws InvocationTargetException { 271 @SuppressWarnings("unchecked") 272 final Map<String, Boolean> map = mock(Map.class); 273 274 Map<String, Boolean> forward = 275 new ForwardingMap<String, Boolean>() { 276 @Override 277 protected Map<String, Boolean> delegate() { 278 return map; 279 } 280 281 @Override 282 public Collection<Boolean> values() { 283 return new StandardValues(); 284 } 285 }; 286 callAllPublicMethods(new TypeToken<Collection<Boolean>>() {}, forward.values()); 287 288 // These are the methods specified by StandardValues 289 verify(map, atLeast(0)).clear(); 290 verify(map, atLeast(0)).containsValue(any()); 291 verify(map, atLeast(0)).isEmpty(); 292 verify(map, atLeast(0)).size(); 293 verify(map, atLeast(0)).entrySet(); 294 verifyNoMoreInteractions(map); 295 } 296 297 public void testToStringWithNullKeys() throws Exception { 298 Map<String, String> hashmap = Maps.newHashMap(); 299 hashmap.put("foo", "bar"); 300 hashmap.put(null, "baz"); 301 302 StandardImplForwardingMap<String, String> forwardingMap = 303 new StandardImplForwardingMap<>(Maps.<String, String>newHashMap()); 304 forwardingMap.put("foo", "bar"); 305 forwardingMap.put(null, "baz"); 306 307 assertEquals(hashmap.toString(), forwardingMap.toString()); 308 } 309 310 public void testToStringWithNullValues() throws Exception { 311 Map<String, String> hashmap = Maps.newHashMap(); 312 hashmap.put("foo", "bar"); 313 hashmap.put("baz", null); 314 315 StandardImplForwardingMap<String, String> forwardingMap = 316 new StandardImplForwardingMap<>(Maps.<String, String>newHashMap()); 317 forwardingMap.put("foo", "bar"); 318 forwardingMap.put("baz", null); 319 320 assertEquals(hashmap.toString(), forwardingMap.toString()); 321 } 322 323 private static <K, V> Map<K, V> wrap(final Map<K, V> delegate) { 324 return new ForwardingMap<K, V>() { 325 @Override 326 protected Map<K, V> delegate() { 327 return delegate; 328 } 329 }; 330 } 331 332 private static final ImmutableMap<String, String> JUF_METHODS = 333 ImmutableMap.of( 334 "java.util.function.Predicate", "test", 335 "java.util.function.Consumer", "accept", 336 "java.util.function.IntFunction", "apply"); 337 338 private static Object getDefaultValue(final TypeToken<?> type) { 339 Class<?> rawType = type.getRawType(); 340 Object defaultValue = ArbitraryInstances.get(rawType); 341 if (defaultValue != null) { 342 return defaultValue; 343 } 344 345 final String typeName = rawType.getCanonicalName(); 346 if (JUF_METHODS.containsKey(typeName)) { 347 // Generally, methods that accept java.util.function.* instances 348 // don't like to get null values. We generate them dynamically 349 // using Proxy so that we can have Java 7 compliant code. 350 return Reflection.newProxy( 351 rawType, 352 new AbstractInvocationHandler() { 353 @Override 354 public Object handleInvocation(Object proxy, Method method, Object[] args) { 355 // Crude, but acceptable until we can use Java 8. Other 356 // methods have default implementations, and it is hard to 357 // distinguish. 358 if (method.getName().equals(JUF_METHODS.get(typeName))) { 359 return getDefaultValue(type.method(method).getReturnType()); 360 } 361 throw new IllegalStateException("Unexpected " + method + " invoked on " + proxy); 362 } 363 }); 364 } else { 365 return null; 366 } 367 } 368 369 private static <T> void callAllPublicMethods(TypeToken<T> type, T object) 370 throws InvocationTargetException { 371 for (Method method : type.getRawType().getMethods()) { 372 if ((method.getModifiers() & STATIC) != 0) { 373 continue; 374 } 375 ImmutableList<Parameter> parameters = type.method(method).getParameters(); 376 Object[] args = new Object[parameters.size()]; 377 for (int i = 0; i < parameters.size(); i++) { 378 args[i] = getDefaultValue(parameters.get(i).getType()); 379 } 380 try { 381 try { 382 method.invoke(object, args); 383 } catch (InvocationTargetException ex) { 384 try { 385 throw ex.getCause(); 386 } catch (UnsupportedOperationException unsupported) { 387 // this is a legit exception 388 } 389 } 390 } catch (Throwable cause) { 391 throw new InvocationTargetException(cause, method + " with args: " + Arrays.toString(args)); 392 } 393 } 394 } 395 } 396