| /* |
| * Copyright (C) 2009 Google Inc. |
| * |
| * 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 copyOf(), |
| * which we are unable to fauxveride (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>() { |
| 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>() { |
| public String apply(Type from) { |
| if (from instanceof Class) { |
| return ((Class<?>) from).getSimpleName(); |
| } |
| return from.toString(); |
| } |
| }; |
| } |