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