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 com.google.common.collect.Lists.transform; 20 import static com.google.common.collect.Sets.difference; 21 import static com.google.common.collect.Sets.newHashSet; 22 import static java.lang.reflect.Modifier.isPublic; 23 import static java.lang.reflect.Modifier.isStatic; 24 import static org.junit.Assert.assertThrows; 25 26 import com.google.common.base.Function; 27 import com.google.common.base.Joiner; 28 import com.google.common.base.Objects; 29 import java.lang.reflect.Method; 30 import java.lang.reflect.Type; 31 import java.lang.reflect.TypeVariable; 32 import java.util.Arrays; 33 import java.util.List; 34 import java.util.Locale; 35 import java.util.Map; 36 import java.util.Set; 37 import junit.framework.TestCase; 38 import org.checkerframework.checker.nullness.qual.Nullable; 39 40 /** 41 * Tests that all {@code public static} methods "inherited" from superclasses are "overridden" in 42 * each immutable-collection class. This ensures, for example, that a call written "{@code 43 * ImmutableSortedSet.copyOf()}" cannot secretly be a call to {@code ImmutableSet.copyOf()}. 44 * 45 * @author Chris Povirk 46 */ 47 public class FauxveridesTest extends TestCase { 48 @AndroidIncompatible // similar to ImmutableTableTest.testNullPointerInstance testImmutableBiMap()49 public void testImmutableBiMap() { 50 doHasAllFauxveridesTest(ImmutableBiMap.class, ImmutableMap.class); 51 } 52 53 @AndroidIncompatible // similar to ImmutableTableTest.testNullPointerInstance testImmutableListMultimap()54 public void testImmutableListMultimap() { 55 doHasAllFauxveridesTest(ImmutableListMultimap.class, ImmutableMultimap.class); 56 } 57 58 @AndroidIncompatible // similar to ImmutableTableTest.testNullPointerInstance testImmutableSetMultimap()59 public void testImmutableSetMultimap() { 60 doHasAllFauxveridesTest(ImmutableSetMultimap.class, ImmutableMultimap.class); 61 } 62 63 @AndroidIncompatible // similar to ImmutableTableTest.testNullPointerInstance testImmutableSortedMap()64 public void testImmutableSortedMap() { 65 doHasAllFauxveridesTest(ImmutableSortedMap.class, ImmutableMap.class); 66 } 67 68 @AndroidIncompatible // similar to ImmutableTableTest.testNullPointerInstance testImmutableSortedSet()69 public void testImmutableSortedSet() { 70 doHasAllFauxveridesTest(ImmutableSortedSet.class, ImmutableSet.class); 71 } 72 73 @AndroidIncompatible // similar to ImmutableTableTest.testNullPointerInstance testImmutableSortedMultiset()74 public void testImmutableSortedMultiset() { 75 doHasAllFauxveridesTest(ImmutableSortedMultiset.class, ImmutableMultiset.class); 76 } 77 78 /* 79 * Demonstrate that ClassCastException is possible when calling 80 * ImmutableSorted{Set,Map}.copyOf(), whose type parameters we are unable to 81 * restrict (see ImmutableSortedSetFauxverideShim). 82 */ 83 testImmutableSortedMapCopyOfMap()84 public void testImmutableSortedMapCopyOfMap() { 85 Map<Object, Object> original = 86 ImmutableMap.of(new Object(), new Object(), new Object(), new Object()); 87 88 assertThrows(ClassCastException.class, () -> ImmutableSortedMap.copyOf(original)); 89 } 90 testImmutableSortedSetCopyOfIterable()91 public void testImmutableSortedSetCopyOfIterable() { 92 Set<Object> original = ImmutableSet.of(new Object(), new Object()); 93 94 assertThrows(ClassCastException.class, () -> ImmutableSortedSet.copyOf(original)); 95 } 96 testImmutableSortedSetCopyOfIterator()97 public void testImmutableSortedSetCopyOfIterator() { 98 Set<Object> original = ImmutableSet.of(new Object(), new Object()); 99 100 assertThrows(ClassCastException.class, () -> ImmutableSortedSet.copyOf(original.iterator())); 101 } 102 doHasAllFauxveridesTest(Class<?> descendant, Class<?> ancestor)103 private void doHasAllFauxveridesTest(Class<?> descendant, Class<?> ancestor) { 104 Set<MethodSignature> required = getAllRequiredToFauxveride(ancestor); 105 Set<MethodSignature> found = getAllFauxveridden(descendant, ancestor); 106 Set<MethodSignature> missing = ImmutableSortedSet.copyOf(difference(required, found)); 107 if (!missing.isEmpty()) { 108 fail( 109 rootLocaleFormat( 110 "%s should hide the public static methods declared in %s: %s", 111 descendant.getSimpleName(), ancestor.getSimpleName(), missing)); 112 } 113 } 114 getAllRequiredToFauxveride(Class<?> ancestor)115 private static Set<MethodSignature> getAllRequiredToFauxveride(Class<?> ancestor) { 116 return getPublicStaticMethodsBetween(ancestor, Object.class); 117 } 118 getAllFauxveridden(Class<?> descendant, Class<?> ancestor)119 private static Set<MethodSignature> getAllFauxveridden(Class<?> descendant, Class<?> ancestor) { 120 return getPublicStaticMethodsBetween(descendant, ancestor); 121 } 122 getPublicStaticMethodsBetween( Class<?> descendant, Class<?> ancestor)123 private static Set<MethodSignature> getPublicStaticMethodsBetween( 124 Class<?> descendant, Class<?> ancestor) { 125 Set<MethodSignature> methods = newHashSet(); 126 for (Class<?> clazz : getClassesBetween(descendant, ancestor)) { 127 methods.addAll(getPublicStaticMethods(clazz)); 128 } 129 return methods; 130 } 131 getPublicStaticMethods(Class<?> clazz)132 private static Set<MethodSignature> getPublicStaticMethods(Class<?> clazz) { 133 Set<MethodSignature> publicStaticMethods = newHashSet(); 134 135 for (Method method : clazz.getDeclaredMethods()) { 136 int modifiers = method.getModifiers(); 137 if (isPublic(modifiers) && isStatic(modifiers)) { 138 publicStaticMethods.add(new MethodSignature(method)); 139 } 140 } 141 142 return publicStaticMethods; 143 } 144 145 /** [descendant, ancestor) */ getClassesBetween(Class<?> descendant, Class<?> ancestor)146 private static Set<Class<?>> getClassesBetween(Class<?> descendant, Class<?> ancestor) { 147 Set<Class<?>> classes = newHashSet(); 148 149 while (!descendant.equals(ancestor)) { 150 classes.add(descendant); 151 descendant = descendant.getSuperclass(); 152 } 153 154 return classes; 155 } 156 157 /** 158 * Not really a signature -- just the parts that affect whether one method is a fauxveride of a 159 * method from an ancestor class. 160 * 161 * <p>See JLS 8.4.2 for the definition of the related "override-equivalent." 162 */ 163 private static final class MethodSignature implements Comparable<MethodSignature> { 164 final String name; 165 final List<Class<?>> parameterTypes; 166 final TypeSignature typeSignature; 167 MethodSignature(Method method)168 MethodSignature(Method method) { 169 name = method.getName(); 170 parameterTypes = Arrays.asList(method.getParameterTypes()); 171 typeSignature = new TypeSignature(method.getTypeParameters()); 172 } 173 174 @Override equals(@ullable Object obj)175 public boolean equals(@Nullable Object obj) { 176 if (obj instanceof MethodSignature) { 177 MethodSignature other = (MethodSignature) obj; 178 return name.equals(other.name) 179 && parameterTypes.equals(other.parameterTypes) 180 && typeSignature.equals(other.typeSignature); 181 } 182 183 return false; 184 } 185 186 @Override hashCode()187 public int hashCode() { 188 return Objects.hashCode(name, parameterTypes, typeSignature); 189 } 190 191 @Override toString()192 public String toString() { 193 return rootLocaleFormat("%s%s(%s)", typeSignature, name, getTypesString(parameterTypes)); 194 } 195 196 @Override compareTo(MethodSignature o)197 public int compareTo(MethodSignature o) { 198 return toString().compareTo(o.toString()); 199 } 200 } 201 202 private static final class TypeSignature { 203 final List<TypeParameterSignature> parameterSignatures; 204 TypeSignature(TypeVariable<Method>[] parameters)205 TypeSignature(TypeVariable<Method>[] parameters) { 206 parameterSignatures = 207 transform( 208 Arrays.asList(parameters), 209 new Function<TypeVariable<?>, TypeParameterSignature>() { 210 @Override 211 public TypeParameterSignature apply(TypeVariable<?> from) { 212 return new TypeParameterSignature(from); 213 } 214 }); 215 } 216 217 @Override equals(@ullable Object obj)218 public boolean equals(@Nullable Object obj) { 219 if (obj instanceof TypeSignature) { 220 TypeSignature other = (TypeSignature) obj; 221 return parameterSignatures.equals(other.parameterSignatures); 222 } 223 224 return false; 225 } 226 227 @Override hashCode()228 public int hashCode() { 229 return parameterSignatures.hashCode(); 230 } 231 232 @Override toString()233 public String toString() { 234 return (parameterSignatures.isEmpty()) 235 ? "" 236 : "<" + Joiner.on(", ").join(parameterSignatures) + "> "; 237 } 238 } 239 240 private static final class TypeParameterSignature { 241 final String name; 242 final List<Type> bounds; 243 TypeParameterSignature(TypeVariable<?> typeParameter)244 TypeParameterSignature(TypeVariable<?> typeParameter) { 245 name = typeParameter.getName(); 246 bounds = Arrays.asList(typeParameter.getBounds()); 247 } 248 249 @Override equals(@ullable Object obj)250 public boolean equals(@Nullable Object obj) { 251 if (obj instanceof TypeParameterSignature) { 252 TypeParameterSignature other = (TypeParameterSignature) obj; 253 /* 254 * The name is here only for display purposes; <E extends Number> and <T 255 * extends Number> are equivalent. 256 */ 257 return bounds.equals(other.bounds); 258 } 259 260 return false; 261 } 262 263 @Override hashCode()264 public int hashCode() { 265 return bounds.hashCode(); 266 } 267 268 @Override toString()269 public String toString() { 270 return (bounds.equals(ImmutableList.of(Object.class))) 271 ? name 272 : name + " extends " + getTypesString(bounds); 273 } 274 } 275 getTypesString(List<? extends Type> types)276 private static String getTypesString(List<? extends Type> types) { 277 List<String> names = transform(types, SIMPLE_NAME_GETTER); 278 return Joiner.on(", ").join(names); 279 } 280 281 private static final Function<Type, String> SIMPLE_NAME_GETTER = 282 new Function<Type, String>() { 283 @Override 284 public String apply(Type from) { 285 if (from instanceof Class) { 286 return ((Class<?>) from).getSimpleName(); 287 } 288 return from.toString(); 289 } 290 }; 291 rootLocaleFormat(String format, Object... args)292 private static String rootLocaleFormat(String format, Object... args) { 293 return String.format(Locale.ROOT, format, args); 294 } 295 } 296