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   public void testImmutableSortedMultiset() {
     72     doHasAllFauxveridesTest(ImmutableSortedMultiset.class, ImmutableMultiset.class);
     73   }
     74 
     75   /*
     76    * Demonstrate that ClassCastException is possible when calling
     77    * ImmutableSorted{Set,Map}.copyOf(), whose type parameters we are unable to
     78    * restrict (see ImmutableSortedSetFauxverideShim).
     79    */
     80 
     81   public void testImmutableSortedMapCopyOfMap() {
     82     Map<Object, Object> original =
     83         ImmutableMap.of(new Object(), new Object(), new Object(), new Object());
     84 
     85     try {
     86       ImmutableSortedMap.copyOf(original);
     87       fail();
     88     } catch (ClassCastException expected) {
     89     }
     90   }
     91 
     92   public void testImmutableSortedSetCopyOfIterable() {
     93     Set<Object> original = ImmutableSet.of(new Object(), new Object());
     94 
     95     try {
     96       ImmutableSortedSet.copyOf(original);
     97       fail();
     98     } catch (ClassCastException expected) {
     99     }
    100   }
    101 
    102   public void testImmutableSortedSetCopyOfIterator() {
    103     Set<Object> original = ImmutableSet.of(new Object(), new Object());
    104 
    105     try {
    106       ImmutableSortedSet.copyOf(original.iterator());
    107       fail();
    108     } catch (ClassCastException expected) {
    109     }
    110   }
    111 
    112   private void doHasAllFauxveridesTest(Class<?> descendant, Class<?> ancestor) {
    113     Set<MethodSignature> required = getAllRequiredToFauxveride(ancestor);
    114     Set<MethodSignature> found = getAllFauxveridden(descendant, ancestor);
    115     required.removeAll(found);
    116 
    117     assertEquals("Must hide public static methods from ancestor classes",
    118         Collections.emptySet(), newTreeSet(required));
    119   }
    120 
    121   private static Set<MethodSignature> getAllRequiredToFauxveride(Class<?> ancestor) {
    122     return getPublicStaticMethodsBetween(ancestor, Object.class);
    123   }
    124 
    125   private static Set<MethodSignature> getAllFauxveridden(
    126       Class<?> descendant, Class<?> ancestor) {
    127     return getPublicStaticMethodsBetween(descendant, ancestor);
    128   }
    129 
    130   private static Set<MethodSignature> getPublicStaticMethodsBetween(
    131       Class<?> descendant, Class<?> ancestor) {
    132     Set<MethodSignature> methods = newHashSet();
    133     for (Class<?> clazz : getClassesBetween(descendant, ancestor)) {
    134       methods.addAll(getPublicStaticMethods(clazz));
    135     }
    136     return methods;
    137   }
    138 
    139   private static Set<MethodSignature> getPublicStaticMethods(Class<?> clazz) {
    140     Set<MethodSignature> publicStaticMethods = newHashSet();
    141 
    142     for (Method method : clazz.getDeclaredMethods()) {
    143       int modifiers = method.getModifiers();
    144       if (isPublic(modifiers) && isStatic(modifiers)) {
    145         publicStaticMethods.add(new MethodSignature(method));
    146       }
    147     }
    148 
    149     return publicStaticMethods;
    150   }
    151 
    152   /** [descendant, ancestor) */
    153   private static Set<Class<?>> getClassesBetween(
    154       Class<?> descendant, Class<?> ancestor) {
    155     Set<Class<?>> classes = newHashSet();
    156 
    157     while (!descendant.equals(ancestor)) {
    158       classes.add(descendant);
    159       descendant = descendant.getSuperclass();
    160     }
    161 
    162     return classes;
    163   }
    164 
    165   /**
    166    * Not really a signature -- just the parts that affect whether one method is
    167    * a fauxveride of a method from an ancestor class.
    168    * <p>
    169    * See JLS 8.4.2 for the definition of the related "override-equivalent."
    170    */
    171   private static final class MethodSignature
    172       implements Comparable<MethodSignature> {
    173     final String name;
    174     final List<Class<?>> parameterTypes;
    175     final TypeSignature typeSignature;
    176 
    177     MethodSignature(Method method) {
    178       name = method.getName();
    179       parameterTypes = Arrays.asList(method.getParameterTypes());
    180       typeSignature = new TypeSignature(method.getTypeParameters());
    181     }
    182 
    183     @Override public boolean equals(Object obj) {
    184       if (obj instanceof MethodSignature) {
    185         MethodSignature other = (MethodSignature) obj;
    186         return name.equals(other.name)
    187             && parameterTypes.equals(other.parameterTypes)
    188             && typeSignature.equals(other.typeSignature);
    189       }
    190 
    191       return false;
    192     }
    193 
    194     @Override public int hashCode() {
    195       return Objects.hashCode(name, parameterTypes, typeSignature);
    196     }
    197 
    198     @Override public String toString() {
    199       return String.format("%s%s(%s)",
    200           typeSignature, name, getTypesString(parameterTypes));
    201     }
    202 
    203     @Override public int compareTo(MethodSignature o) {
    204       return toString().compareTo(o.toString());
    205     }
    206   }
    207 
    208   private static final class TypeSignature {
    209     final List<TypeParameterSignature> parameterSignatures;
    210 
    211     TypeSignature(TypeVariable<Method>[] parameters) {
    212       parameterSignatures =
    213           transform(Arrays.asList(parameters),
    214               new Function<TypeVariable<?>, TypeParameterSignature>() {
    215                 @Override
    216                 public TypeParameterSignature apply(TypeVariable<?> from) {
    217                   return new TypeParameterSignature(from);
    218                 }
    219               });
    220     }
    221 
    222     @Override public boolean equals(Object obj) {
    223       if (obj instanceof TypeSignature) {
    224         TypeSignature other = (TypeSignature) obj;
    225         return parameterSignatures.equals(other.parameterSignatures);
    226       }
    227 
    228       return false;
    229     }
    230 
    231     @Override public int hashCode() {
    232       return parameterSignatures.hashCode();
    233     }
    234 
    235     @Override public String toString() {
    236       return (parameterSignatures.isEmpty())
    237           ? ""
    238           : "<" + Joiner.on(", ").join(parameterSignatures) + "> ";
    239     }
    240   }
    241 
    242   private static final class TypeParameterSignature {
    243     final String name;
    244     final List<Type> bounds;
    245 
    246     TypeParameterSignature(TypeVariable<?> typeParameter) {
    247       name = typeParameter.getName();
    248       bounds = Arrays.asList(typeParameter.getBounds());
    249     }
    250 
    251     @Override public boolean equals(Object obj) {
    252       if (obj instanceof TypeParameterSignature) {
    253         TypeParameterSignature other = (TypeParameterSignature) obj;
    254         /*
    255          * The name is here only for display purposes; <E extends Number> and <T
    256          * extends Number> are equivalent.
    257          */
    258         return bounds.equals(other.bounds);
    259       }
    260 
    261       return false;
    262     }
    263 
    264     @Override public int hashCode() {
    265       return bounds.hashCode();
    266     }
    267 
    268     @Override public String toString() {
    269       return (bounds.equals(ImmutableList.of(Object.class)))
    270           ? name
    271           : name + " extends " + getTypesString(bounds);
    272     }
    273   }
    274 
    275   private static String getTypesString(List<? extends Type> types) {
    276     List<String> names = transform(types, SIMPLE_NAME_GETTER);
    277     return Joiner.on(", ").join(names);
    278   }
    279 
    280   private static final Function<Type, String> SIMPLE_NAME_GETTER =
    281       new Function<Type, String>() {
    282         @Override
    283         public String apply(Type from) {
    284           if (from instanceof Class) {
    285             return ((Class<?>) from).getSimpleName();
    286           }
    287           return from.toString();
    288         }
    289       };
    290 }
    291