/* * 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 original = ImmutableMap.of(new Object(), new Object(), new Object(), new Object()); try { ImmutableSortedMap.copyOf(original); fail(); } catch (ClassCastException expected) { } } public void testImmutableSortedSetCopyOfIterable() { Set original = ImmutableSet.of(new Object(), new Object()); try { ImmutableSortedSet.copyOf(original); fail(); } catch (ClassCastException expected) { } } public void testImmutableSortedSetCopyOfIterator() { Set original = ImmutableSet.of(new Object(), new Object()); try { ImmutableSortedSet.copyOf(original.iterator()); fail(); } catch (ClassCastException expected) { } } private void doHasAllFauxveridesTest(Class descendant, Class ancestor) { Set required = getAllRequiredToFauxveride(ancestor); Set found = getAllFauxveridden(descendant, ancestor); required.removeAll(found); assertEquals("Must hide public static methods from ancestor classes", Collections.emptySet(), newTreeSet(required)); } private static Set getAllRequiredToFauxveride(Class ancestor) { return getPublicStaticMethodsBetween(ancestor, Object.class); } private static Set getAllFauxveridden( Class descendant, Class ancestor) { return getPublicStaticMethodsBetween(descendant, ancestor); } private static Set getPublicStaticMethodsBetween( Class descendant, Class ancestor) { Set methods = newHashSet(); for (Class clazz : getClassesBetween(descendant, ancestor)) { methods.addAll(getPublicStaticMethods(clazz)); } return methods; } private static Set getPublicStaticMethods(Class clazz) { Set 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> getClassesBetween( Class descendant, Class ancestor) { Set> 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. *

* See JLS 8.4.2 for the definition of the related "override-equivalent." */ private static final class MethodSignature implements Comparable { final String name; final List> 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 parameterSignatures; TypeSignature(TypeVariable[] parameters) { parameterSignatures = transform(Arrays.asList(parameters), new Function, 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 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; and 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 types) { List names = transform(types, SIMPLE_NAME_GETTER); return Joiner.on(", ").join(names); } private static final Function SIMPLE_NAME_GETTER = new Function() { @Override public String apply(Type from) { if (from instanceof Class) { return ((Class) from).getSimpleName(); } return from.toString(); } }; }