/*
* 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);
}
/*
* 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(descendant, 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<?> descendant, 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();
}
};
}