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