/* * Copyright (C) 2009 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.collect; import static com.google.common.collect.Lists.transform; import static com.google.common.collect.Sets.newHashSet; import static com.google.common.collect.Sets.newTreeSet; import static java.lang.reflect.Modifier.isPublic; import static java.lang.reflect.Modifier.isStatic; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Objects; import junit.framework.TestCase; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; /** * Tests that all {@code public static} methods "inherited" from superclasses * are "overridden" in each immutable-collection class. This ensures, for * example, that a call written "{@code ImmutableSortedSet.copyOf()}" cannot * secretly be a call to {@code ImmutableSet.copyOf()}. * * @author Chris Povirk */ public class FauxveridesTest extends TestCase { public void testImmutableBiMap() { doHasAllFauxveridesTest(ImmutableBiMap.class, ImmutableMap.class); } public void testImmutableListMultimap() { doHasAllFauxveridesTest( ImmutableListMultimap.class, ImmutableMultimap.class); } public void testImmutableSetMultimap() { doHasAllFauxveridesTest( ImmutableSetMultimap.class, ImmutableMultimap.class); } public void testImmutableSortedMap() { doHasAllFauxveridesTest(ImmutableSortedMap.class, ImmutableMap.class); } public void testImmutableSortedSet() { doHasAllFauxveridesTest(ImmutableSortedSet.class, ImmutableSet.class); } public void testImmutableSortedMultiset() { doHasAllFauxveridesTest(ImmutableSortedMultiset.class, ImmutableMultiset.class); } /* * Demonstrate that ClassCastException is possible when calling * ImmutableSorted{Set,Map}.copyOf(), whose type parameters we are unable to * restrict (see ImmutableSortedSetFauxverideShim). */ public void testImmutableSortedMapCopyOfMap() { Map<Object, Object> original = ImmutableMap.of(new Object(), new Object(), new Object(), new Object()); try { ImmutableSortedMap.copyOf(original); fail(); } catch (ClassCastException expected) { } } public void testImmutableSortedSetCopyOfIterable() { Set<Object> original = ImmutableSet.of(new Object(), new Object()); try { ImmutableSortedSet.copyOf(original); fail(); } catch (ClassCastException expected) { } } public void testImmutableSortedSetCopyOfIterator() { Set<Object> original = ImmutableSet.of(new Object(), new Object()); try { ImmutableSortedSet.copyOf(original.iterator()); fail(); } catch (ClassCastException expected) { } } private void doHasAllFauxveridesTest(Class<?> descendant, Class<?> ancestor) { Set<MethodSignature> required = getAllRequiredToFauxveride(ancestor); Set<MethodSignature> found = getAllFauxveridden(descendant, ancestor); required.removeAll(found); assertEquals("Must hide public static methods from ancestor classes", Collections.emptySet(), newTreeSet(required)); } private static Set<MethodSignature> getAllRequiredToFauxveride(Class<?> ancestor) { return getPublicStaticMethodsBetween(ancestor, Object.class); } private static Set<MethodSignature> getAllFauxveridden( Class<?> descendant, Class<?> ancestor) { return getPublicStaticMethodsBetween(descendant, ancestor); } private static Set<MethodSignature> getPublicStaticMethodsBetween( Class<?> descendant, Class<?> ancestor) { Set<MethodSignature> methods = newHashSet(); for (Class<?> clazz : getClassesBetween(descendant, ancestor)) { methods.addAll(getPublicStaticMethods(clazz)); } return methods; } private static Set<MethodSignature> getPublicStaticMethods(Class<?> clazz) { Set<MethodSignature> publicStaticMethods = newHashSet(); for (Method method : clazz.getDeclaredMethods()) { int modifiers = method.getModifiers(); if (isPublic(modifiers) && isStatic(modifiers)) { publicStaticMethods.add(new MethodSignature(method)); } } return publicStaticMethods; } /** [descendant, ancestor) */ private static Set<Class<?>> getClassesBetween( Class<?> descendant, Class<?> ancestor) { Set<Class<?>> classes = newHashSet(); while (!descendant.equals(ancestor)) { classes.add(descendant); descendant = descendant.getSuperclass(); } return classes; } /** * Not really a signature -- just the parts that affect whether one method is * a fauxveride of a method from an ancestor class. * <p> * See JLS 8.4.2 for the definition of the related "override-equivalent." */ private static final class MethodSignature implements Comparable<MethodSignature> { final String name; final List<Class<?>> parameterTypes; final TypeSignature typeSignature; MethodSignature(Method method) { name = method.getName(); parameterTypes = Arrays.asList(method.getParameterTypes()); typeSignature = new TypeSignature(method.getTypeParameters()); } @Override public boolean equals(Object obj) { if (obj instanceof MethodSignature) { MethodSignature other = (MethodSignature) obj; return name.equals(other.name) && parameterTypes.equals(other.parameterTypes) && typeSignature.equals(other.typeSignature); } return false; } @Override public int hashCode() { return Objects.hashCode(name, parameterTypes, typeSignature); } @Override public String toString() { return String.format("%s%s(%s)", typeSignature, name, getTypesString(parameterTypes)); } @Override public int compareTo(MethodSignature o) { return toString().compareTo(o.toString()); } } private static final class TypeSignature { final List<TypeParameterSignature> parameterSignatures; TypeSignature(TypeVariable<Method>[] parameters) { parameterSignatures = transform(Arrays.asList(parameters), new Function<TypeVariable<?>, TypeParameterSignature>() { @Override public TypeParameterSignature apply(TypeVariable<?> from) { return new TypeParameterSignature(from); } }); } @Override public boolean equals(Object obj) { if (obj instanceof TypeSignature) { TypeSignature other = (TypeSignature) obj; return parameterSignatures.equals(other.parameterSignatures); } return false; } @Override public int hashCode() { return parameterSignatures.hashCode(); } @Override public String toString() { return (parameterSignatures.isEmpty()) ? "" : "<" + Joiner.on(", ").join(parameterSignatures) + "> "; } } private static final class TypeParameterSignature { final String name; final List<Type> bounds; TypeParameterSignature(TypeVariable<?> typeParameter) { name = typeParameter.getName(); bounds = Arrays.asList(typeParameter.getBounds()); } @Override public boolean equals(Object obj) { if (obj instanceof TypeParameterSignature) { TypeParameterSignature other = (TypeParameterSignature) obj; /* * The name is here only for display purposes; <E extends Number> and <T * extends Number> are equivalent. */ return bounds.equals(other.bounds); } return false; } @Override public int hashCode() { return bounds.hashCode(); } @Override public String toString() { return (bounds.equals(ImmutableList.of(Object.class))) ? name : name + " extends " + getTypesString(bounds); } } private static String getTypesString(List<? extends Type> types) { List<String> names = transform(types, SIMPLE_NAME_GETTER); return Joiner.on(", ").join(names); } private static final Function<Type, String> SIMPLE_NAME_GETTER = new Function<Type, String>() { @Override public String apply(Type from) { if (from instanceof Class) { return ((Class<?>) from).getSimpleName(); } return from.toString(); } }; }