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 {
testImmutableBiMap()49   public void testImmutableBiMap() {
50     doHasAllFauxveridesTest(ImmutableBiMap.class, ImmutableMap.class);
51   }
52 
testImmutableListMultimap()53   public void testImmutableListMultimap() {
54     doHasAllFauxveridesTest(
55         ImmutableListMultimap.class, ImmutableMultimap.class);
56   }
57 
testImmutableSetMultimap()58   public void testImmutableSetMultimap() {
59     doHasAllFauxveridesTest(
60         ImmutableSetMultimap.class, ImmutableMultimap.class);
61   }
62 
testImmutableSortedMap()63   public void testImmutableSortedMap() {
64     doHasAllFauxveridesTest(ImmutableSortedMap.class, ImmutableMap.class);
65   }
66 
testImmutableSortedSet()67   public void testImmutableSortedSet() {
68     doHasAllFauxveridesTest(ImmutableSortedSet.class, ImmutableSet.class);
69   }
70 
testImmutableSortedMultiset()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 
testImmutableSortedMapCopyOfMap()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 
testImmutableSortedSetCopyOfIterable()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 
testImmutableSortedSetCopyOfIterator()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 
doHasAllFauxveridesTest(Class<?> descendant, Class<?> ancestor)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 
getAllRequiredToFauxveride(Class<?> ancestor)121   private static Set<MethodSignature> getAllRequiredToFauxveride(Class<?> ancestor) {
122     return getPublicStaticMethodsBetween(ancestor, Object.class);
123   }
124 
getAllFauxveridden( Class<?> descendant, Class<?> ancestor)125   private static Set<MethodSignature> getAllFauxveridden(
126       Class<?> descendant, Class<?> ancestor) {
127     return getPublicStaticMethodsBetween(descendant, ancestor);
128   }
129 
getPublicStaticMethodsBetween( Class<?> descendant, Class<?> ancestor)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 
getPublicStaticMethods(Class<?> clazz)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) */
getClassesBetween( Class<?> descendant, Class<?> 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 
MethodSignature(Method method)177     MethodSignature(Method method) {
178       name = method.getName();
179       parameterTypes = Arrays.asList(method.getParameterTypes());
180       typeSignature = new TypeSignature(method.getTypeParameters());
181     }
182 
equals(Object obj)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 
hashCode()194     @Override public int hashCode() {
195       return Objects.hashCode(name, parameterTypes, typeSignature);
196     }
197 
toString()198     @Override public String toString() {
199       return String.format("%s%s(%s)",
200           typeSignature, name, getTypesString(parameterTypes));
201     }
202 
compareTo(MethodSignature o)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 
TypeSignature(TypeVariable<Method>[] parameters)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 
equals(Object obj)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 
hashCode()231     @Override public int hashCode() {
232       return parameterSignatures.hashCode();
233     }
234 
toString()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 
TypeParameterSignature(TypeVariable<?> typeParameter)246     TypeParameterSignature(TypeVariable<?> typeParameter) {
247       name = typeParameter.getName();
248       bounds = Arrays.asList(typeParameter.getBounds());
249     }
250 
equals(Object obj)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 
hashCode()264     @Override public int hashCode() {
265       return bounds.hashCode();
266     }
267 
toString()268     @Override public String toString() {
269       return (bounds.equals(ImmutableList.of(Object.class)))
270           ? name
271           : name + " extends " + getTypesString(bounds);
272     }
273   }
274 
getTypesString(List<? extends Type> types)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