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