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 17package com.google.common.collect; 18 19import static com.google.common.collect.Lists.transform; 20import static com.google.common.collect.Sets.newHashSet; 21import static com.google.common.collect.Sets.newTreeSet; 22import static java.lang.reflect.Modifier.isPublic; 23import static java.lang.reflect.Modifier.isStatic; 24 25import com.google.common.base.Function; 26import com.google.common.base.Joiner; 27import com.google.common.base.Objects; 28 29import junit.framework.TestCase; 30 31import java.lang.reflect.Method; 32import java.lang.reflect.Type; 33import java.lang.reflect.TypeVariable; 34import java.util.Arrays; 35import java.util.Collections; 36import java.util.List; 37import java.util.Map; 38import 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 */ 48public class FauxveridesTest extends TestCase { 49 public void testImmutableBiMap() { 50 doHasAllFauxveridesTest(ImmutableBiMap.class, ImmutableMap.class); 51 } 52 53 public void testImmutableListMultimap() { 54 doHasAllFauxveridesTest( 55 ImmutableListMultimap.class, ImmutableMultimap.class); 56 } 57 58 public void testImmutableSetMultimap() { 59 doHasAllFauxveridesTest( 60 ImmutableSetMultimap.class, ImmutableMultimap.class); 61 } 62 63 public void testImmutableSortedMap() { 64 doHasAllFauxveridesTest(ImmutableSortedMap.class, ImmutableMap.class); 65 } 66 67 public void testImmutableSortedSet() { 68 doHasAllFauxveridesTest(ImmutableSortedSet.class, ImmutableSet.class); 69 } 70 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 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 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 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 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 121 private static Set<MethodSignature> getAllRequiredToFauxveride(Class<?> ancestor) { 122 return getPublicStaticMethodsBetween(ancestor, Object.class); 123 } 124 125 private static Set<MethodSignature> getAllFauxveridden( 126 Class<?> descendant, Class<?> ancestor) { 127 return getPublicStaticMethodsBetween(descendant, ancestor); 128 } 129 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 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) */ 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 177 MethodSignature(Method method) { 178 name = method.getName(); 179 parameterTypes = Arrays.asList(method.getParameterTypes()); 180 typeSignature = new TypeSignature(method.getTypeParameters()); 181 } 182 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 194 @Override public int hashCode() { 195 return Objects.hashCode(name, parameterTypes, typeSignature); 196 } 197 198 @Override public String toString() { 199 return String.format("%s%s(%s)", 200 typeSignature, name, getTypesString(parameterTypes)); 201 } 202 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 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 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 231 @Override public int hashCode() { 232 return parameterSignatures.hashCode(); 233 } 234 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 246 TypeParameterSignature(TypeVariable<?> typeParameter) { 247 name = typeParameter.getName(); 248 bounds = Arrays.asList(typeParameter.getBounds()); 249 } 250 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 264 @Override public int hashCode() { 265 return bounds.hashCode(); 266 } 267 268 @Override public String toString() { 269 return (bounds.equals(ImmutableList.of(Object.class))) 270 ? name 271 : name + " extends " + getTypesString(bounds); 272 } 273 } 274 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