Home | History | Annotate | Download | only in collect
      1 /*
      2  * Copyright (C) 2009 The Guava Authors
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.common.collect;
     18 
     19 import static com.google.common.collect.Lists.transform;
     20 import static com.google.common.collect.Sets.newHashSet;
     21 import static com.google.common.collect.Sets.newTreeSet;
     22 import static java.lang.reflect.Modifier.isPublic;
     23 import static java.lang.reflect.Modifier.isStatic;
     24 
     25 import com.google.common.base.Function;
     26 import com.google.common.base.Joiner;
     27 import com.google.common.base.Objects;
     28 
     29 import junit.framework.TestCase;
     30 
     31 import java.lang.reflect.Method;
     32 import java.lang.reflect.Type;
     33 import java.lang.reflect.TypeVariable;
     34 import java.util.Arrays;
     35 import java.util.Collections;
     36 import java.util.List;
     37 import java.util.Map;
     38 import java.util.Set;
     39 
     40 /**
     41  * Tests that all {@code public static} methods "inherited" from superclasses
     42  * are "overridden" in each immutable-collection class. This ensures, for
     43  * example, that a call written "{@code ImmutableSortedSet.copyOf()}" cannot
     44  * secretly be a call to {@code ImmutableSet.copyOf()}.
     45  *
     46  * @author Chris Povirk
     47  */
     48 public class FauxveridesTest extends TestCase {
     49   public void testImmutableBiMap() {
     50     doHasAllFauxveridesTest(ImmutableBiMap.class, ImmutableMap.class);
     51   }
     52 
     53   public void testImmutableListMultimap() {
     54     doHasAllFauxveridesTest(
     55         ImmutableListMultimap.class, ImmutableMultimap.class);
     56   }
     57 
     58   public void testImmutableSetMultimap() {
     59     doHasAllFauxveridesTest(
     60         ImmutableSetMultimap.class, ImmutableMultimap.class);
     61   }
     62 
     63   public void testImmutableSortedMap() {
     64     doHasAllFauxveridesTest(ImmutableSortedMap.class, ImmutableMap.class);
     65   }
     66 
     67   public void testImmutableSortedSet() {
     68     doHasAllFauxveridesTest(ImmutableSortedSet.class, ImmutableSet.class);
     69   }
     70 
     71   /*
     72    * Demonstrate that ClassCastException is possible when calling
     73    * ImmutableSorted{Set,Map}.copyOf(), whose type parameters we are unable to
     74    * restrict (see ImmutableSortedSetFauxverideShim).
     75    */
     76 
     77   public void testImmutableSortedMapCopyOfMap() {
     78     Map<Object, Object> original =
     79         ImmutableMap.of(new Object(), new Object(), new Object(), new Object());
     80 
     81     try {
     82       ImmutableSortedMap.copyOf(original);
     83       fail();
     84     } catch (ClassCastException expected) {
     85     }
     86   }
     87 
     88   public void testImmutableSortedSetCopyOfIterable() {
     89     Set<Object> original = ImmutableSet.of(new Object(), new Object());
     90 
     91     try {
     92       ImmutableSortedSet.copyOf(original);
     93       fail();
     94     } catch (ClassCastException expected) {
     95     }
     96   }
     97 
     98   public void testImmutableSortedSetCopyOfIterator() {
     99     Set<Object> original = ImmutableSet.of(new Object(), new Object());
    100 
    101     try {
    102       ImmutableSortedSet.copyOf(original.iterator());
    103       fail();
    104     } catch (ClassCastException expected) {
    105     }
    106   }
    107 
    108   private void doHasAllFauxveridesTest(Class<?> descendant, Class<?> ancestor) {
    109     Set<MethodSignature> required =
    110         getAllRequiredToFauxveride(descendant, ancestor);
    111     Set<MethodSignature> found = getAllFauxveridden(descendant, ancestor);
    112     required.removeAll(found);
    113 
    114     assertEquals("Must hide public static methods from ancestor classes",
    115         Collections.emptySet(), newTreeSet(required));
    116   }
    117 
    118   private static Set<MethodSignature> getAllRequiredToFauxveride(
    119       Class<?> descendant, Class<?> ancestor) {
    120     return getPublicStaticMethodsBetween(ancestor, Object.class);
    121   }
    122 
    123   private static Set<MethodSignature> getAllFauxveridden(
    124       Class<?> descendant, Class<?> ancestor) {
    125     return getPublicStaticMethodsBetween(descendant, ancestor);
    126   }
    127 
    128   private static Set<MethodSignature> getPublicStaticMethodsBetween(
    129       Class<?> descendant, Class<?> ancestor) {
    130     Set<MethodSignature> methods = newHashSet();
    131     for (Class<?> clazz : getClassesBetween(descendant, ancestor)) {
    132       methods.addAll(getPublicStaticMethods(clazz));
    133     }
    134     return methods;
    135   }
    136 
    137   private static Set<MethodSignature> getPublicStaticMethods(Class<?> clazz) {
    138     Set<MethodSignature> publicStaticMethods = newHashSet();
    139 
    140     for (Method method : clazz.getDeclaredMethods()) {
    141       int modifiers = method.getModifiers();
    142       if (isPublic(modifiers) && isStatic(modifiers)) {
    143         publicStaticMethods.add(new MethodSignature(method));
    144       }
    145     }
    146 
    147     return publicStaticMethods;
    148   }
    149 
    150   /** [descendant, ancestor) */
    151   private static Set<Class<?>> getClassesBetween(
    152       Class<?> descendant, Class<?> ancestor) {
    153     Set<Class<?>> classes = newHashSet();
    154 
    155     while (!descendant.equals(ancestor)) {
    156       classes.add(descendant);
    157       descendant = descendant.getSuperclass();
    158     }
    159 
    160     return classes;
    161   }
    162 
    163   /**
    164    * Not really a signature -- just the parts that affect whether one method is
    165    * a fauxveride of a method from an ancestor class.
    166    * <p>
    167    * See JLS 8.4.2 for the definition of the related "override-equivalent."
    168    */
    169   private static final class MethodSignature
    170       implements Comparable<MethodSignature> {
    171     final String name;
    172     final List<Class<?>> parameterTypes;
    173     final TypeSignature typeSignature;
    174 
    175     MethodSignature(Method method) {
    176       name = method.getName();
    177       parameterTypes = Arrays.asList(method.getParameterTypes());
    178       typeSignature = new TypeSignature(method.getTypeParameters());
    179     }
    180 
    181     @Override public boolean equals(Object obj) {
    182       if (obj instanceof MethodSignature) {
    183         MethodSignature other = (MethodSignature) obj;
    184         return name.equals(other.name)
    185             && parameterTypes.equals(other.parameterTypes)
    186             && typeSignature.equals(other.typeSignature);
    187       }
    188 
    189       return false;
    190     }
    191 
    192     @Override public int hashCode() {
    193       return Objects.hashCode(name, parameterTypes, typeSignature);
    194     }
    195 
    196     @Override public String toString() {
    197       return String.format("%s%s(%s)",
    198           typeSignature, name, getTypesString(parameterTypes));
    199     }
    200 
    201     @Override public int compareTo(MethodSignature o) {
    202       return toString().compareTo(o.toString());
    203     }
    204   }
    205 
    206   private static final class TypeSignature {
    207     final List<TypeParameterSignature> parameterSignatures;
    208 
    209     TypeSignature(TypeVariable<Method>[] parameters) {
    210       parameterSignatures =
    211           transform(Arrays.asList(parameters),
    212               new Function<TypeVariable<?>, TypeParameterSignature>() {
    213                 @Override
    214                 public TypeParameterSignature apply(TypeVariable<?> from) {
    215                   return new TypeParameterSignature(from);
    216                 }
    217               });
    218     }
    219 
    220     @Override public boolean equals(Object obj) {
    221       if (obj instanceof TypeSignature) {
    222         TypeSignature other = (TypeSignature) obj;
    223         return parameterSignatures.equals(other.parameterSignatures);
    224       }
    225 
    226       return false;
    227     }
    228 
    229     @Override public int hashCode() {
    230       return parameterSignatures.hashCode();
    231     }
    232 
    233     @Override public String toString() {
    234       return (parameterSignatures.isEmpty())
    235           ? ""
    236           : "<" + Joiner.on(", ").join(parameterSignatures) + "> ";
    237     }
    238   }
    239 
    240   private static final class TypeParameterSignature {
    241     final String name;
    242     final List<Type> bounds;
    243 
    244     TypeParameterSignature(TypeVariable<?> typeParameter) {
    245       name = typeParameter.getName();
    246       bounds = Arrays.asList(typeParameter.getBounds());
    247     }
    248 
    249     @Override public boolean equals(Object obj) {
    250       if (obj instanceof TypeParameterSignature) {
    251         TypeParameterSignature other = (TypeParameterSignature) obj;
    252         /*
    253          * The name is here only for display purposes; <E extends Number> and <T
    254          * extends Number> are equivalent.
    255          */
    256         return bounds.equals(other.bounds);
    257       }
    258 
    259       return false;
    260     }
    261 
    262     @Override public int hashCode() {
    263       return bounds.hashCode();
    264     }
    265 
    266     @Override public String toString() {
    267       return (bounds.equals(ImmutableList.of(Object.class)))
    268           ? name
    269           : name + " extends " + getTypesString(bounds);
    270     }
    271   }
    272 
    273   private static String getTypesString(List<? extends Type> types) {
    274     List<String> names = transform(types, SIMPLE_NAME_GETTER);
    275     return Joiner.on(", ").join(names);
    276   }
    277 
    278   private static final Function<Type, String> SIMPLE_NAME_GETTER =
    279       new Function<Type, String>() {
    280         @Override
    281         public String apply(Type from) {
    282           if (from instanceof Class) {
    283             return ((Class<?>) from).getSimpleName();
    284           }
    285           return from.toString();
    286         }
    287       };
    288 }
    289