300 lines
9.1 KiB
Java
300 lines
9.1 KiB
Java
/*
|
|
* 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.difference;
|
|
import static com.google.common.collect.Sets.newHashSet;
|
|
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 java.lang.reflect.Method;
|
|
import java.lang.reflect.Type;
|
|
import java.lang.reflect.TypeVariable;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import junit.framework.TestCase;
|
|
|
|
/**
|
|
* 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);
|
|
Set<MethodSignature> missing = ImmutableSortedSet.copyOf(difference(required, found));
|
|
if (!missing.isEmpty()) {
|
|
fail(
|
|
rootLocaleFormat(
|
|
"%s should hide the public static methods declared in %s: %s",
|
|
descendant.getSimpleName(), ancestor.getSimpleName(), missing));
|
|
}
|
|
}
|
|
|
|
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 rootLocaleFormat("%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();
|
|
}
|
|
};
|
|
|
|
private static String rootLocaleFormat(String format, Object... args) {
|
|
return String.format(Locale.ROOT, format, args);
|
|
}
|
|
}
|