• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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 
testImmutableSortedMapCopyOfMap()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 
testImmutableSortedSetCopyOfIterable()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 
testImmutableSortedSetCopyOfIterator()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 
doHasAllFauxveridesTest(Class<?> descendant, Class<?> ancestor)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 
getAllRequiredToFauxveride( Class<?> descendant, Class<?> ancestor)118   private static Set<MethodSignature> getAllRequiredToFauxveride(
119       Class<?> descendant, Class<?> ancestor) {
120     return getPublicStaticMethodsBetween(ancestor, Object.class);
121   }
122 
getAllFauxveridden( Class<?> descendant, Class<?> ancestor)123   private static Set<MethodSignature> getAllFauxveridden(
124       Class<?> descendant, Class<?> ancestor) {
125     return getPublicStaticMethodsBetween(descendant, ancestor);
126   }
127 
getPublicStaticMethodsBetween( Class<?> descendant, Class<?> ancestor)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 
getPublicStaticMethods(Class<?> clazz)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) */
getClassesBetween( Class<?> descendant, Class<?> 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 
MethodSignature(Method method)175     MethodSignature(Method method) {
176       name = method.getName();
177       parameterTypes = Arrays.asList(method.getParameterTypes());
178       typeSignature = new TypeSignature(method.getTypeParameters());
179     }
180 
equals(Object obj)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 
hashCode()192     @Override public int hashCode() {
193       return Objects.hashCode(name, parameterTypes, typeSignature);
194     }
195 
toString()196     @Override public String toString() {
197       return String.format("%s%s(%s)",
198           typeSignature, name, getTypesString(parameterTypes));
199     }
200 
compareTo(MethodSignature o)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 
TypeSignature(TypeVariable<Method>[] parameters)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 
equals(Object obj)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 
hashCode()229     @Override public int hashCode() {
230       return parameterSignatures.hashCode();
231     }
232 
toString()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 
TypeParameterSignature(TypeVariable<?> typeParameter)244     TypeParameterSignature(TypeVariable<?> typeParameter) {
245       name = typeParameter.getName();
246       bounds = Arrays.asList(typeParameter.getBounds());
247     }
248 
equals(Object obj)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 
hashCode()262     @Override public int hashCode() {
263       return bounds.hashCode();
264     }
265 
toString()266     @Override public String toString() {
267       return (bounds.equals(ImmutableList.of(Object.class)))
268           ? name
269           : name + " extends " + getTypesString(bounds);
270     }
271   }
272 
getTypesString(List<? extends Type> types)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