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.difference;
21 import static com.google.common.collect.Sets.newHashSet;
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 import java.lang.reflect.Method;
29 import java.lang.reflect.Type;
30 import java.lang.reflect.TypeVariable;
31 import java.util.Arrays;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Set;
36 import junit.framework.TestCase;
37 
38 /**
39  * Tests that all {@code public static} methods "inherited" from superclasses are "overridden" in
40  * each immutable-collection class. This ensures, for example, that a call written "{@code
41  * ImmutableSortedSet.copyOf()}" cannot secretly be a call to {@code ImmutableSet.copyOf()}.
42  *
43  * @author Chris Povirk
44  */
45 public class FauxveridesTest extends TestCase {
testImmutableBiMap()46   public void testImmutableBiMap() {
47     doHasAllFauxveridesTest(ImmutableBiMap.class, ImmutableMap.class);
48   }
49 
testImmutableListMultimap()50   public void testImmutableListMultimap() {
51     doHasAllFauxveridesTest(ImmutableListMultimap.class, ImmutableMultimap.class);
52   }
53 
testImmutableSetMultimap()54   public void testImmutableSetMultimap() {
55     doHasAllFauxveridesTest(ImmutableSetMultimap.class, ImmutableMultimap.class);
56   }
57 
testImmutableSortedMap()58   public void testImmutableSortedMap() {
59     doHasAllFauxveridesTest(ImmutableSortedMap.class, ImmutableMap.class);
60   }
61 
testImmutableSortedSet()62   public void testImmutableSortedSet() {
63     doHasAllFauxveridesTest(ImmutableSortedSet.class, ImmutableSet.class);
64   }
65 
testImmutableSortedMultiset()66   public void testImmutableSortedMultiset() {
67     doHasAllFauxveridesTest(ImmutableSortedMultiset.class, ImmutableMultiset.class);
68   }
69 
70   /*
71    * Demonstrate that ClassCastException is possible when calling
72    * ImmutableSorted{Set,Map}.copyOf(), whose type parameters we are unable to
73    * restrict (see ImmutableSortedSetFauxverideShim).
74    */
75 
testImmutableSortedMapCopyOfMap()76   public void testImmutableSortedMapCopyOfMap() {
77     Map<Object, Object> original =
78         ImmutableMap.of(new Object(), new Object(), new Object(), new Object());
79 
80     try {
81       ImmutableSortedMap.copyOf(original);
82       fail();
83     } catch (ClassCastException expected) {
84     }
85   }
86 
testImmutableSortedSetCopyOfIterable()87   public void testImmutableSortedSetCopyOfIterable() {
88     Set<Object> original = ImmutableSet.of(new Object(), new Object());
89 
90     try {
91       ImmutableSortedSet.copyOf(original);
92       fail();
93     } catch (ClassCastException expected) {
94     }
95   }
96 
testImmutableSortedSetCopyOfIterator()97   public void testImmutableSortedSetCopyOfIterator() {
98     Set<Object> original = ImmutableSet.of(new Object(), new Object());
99 
100     try {
101       ImmutableSortedSet.copyOf(original.iterator());
102       fail();
103     } catch (ClassCastException expected) {
104     }
105   }
106 
doHasAllFauxveridesTest(Class<?> descendant, Class<?> ancestor)107   private void doHasAllFauxveridesTest(Class<?> descendant, Class<?> ancestor) {
108     Set<MethodSignature> required = getAllRequiredToFauxveride(ancestor);
109     Set<MethodSignature> found = getAllFauxveridden(descendant, ancestor);
110     Set<MethodSignature> missing = ImmutableSortedSet.copyOf(difference(required, found));
111     if (!missing.isEmpty()) {
112       fail(
113           rootLocaleFormat(
114               "%s should hide the public static methods declared in %s: %s",
115               descendant.getSimpleName(), ancestor.getSimpleName(), missing));
116     }
117   }
118 
getAllRequiredToFauxveride(Class<?> ancestor)119   private static Set<MethodSignature> getAllRequiredToFauxveride(Class<?> ancestor) {
120     return getPublicStaticMethodsBetween(ancestor, Object.class);
121   }
122 
getAllFauxveridden(Class<?> descendant, Class<?> ancestor)123   private static Set<MethodSignature> getAllFauxveridden(Class<?> descendant, Class<?> ancestor) {
124     return getPublicStaticMethodsBetween(descendant, ancestor);
125   }
126 
getPublicStaticMethodsBetween( Class<?> descendant, Class<?> ancestor)127   private static Set<MethodSignature> getPublicStaticMethodsBetween(
128       Class<?> descendant, Class<?> ancestor) {
129     Set<MethodSignature> methods = newHashSet();
130     for (Class<?> clazz : getClassesBetween(descendant, ancestor)) {
131       methods.addAll(getPublicStaticMethods(clazz));
132     }
133     return methods;
134   }
135 
getPublicStaticMethods(Class<?> clazz)136   private static Set<MethodSignature> getPublicStaticMethods(Class<?> clazz) {
137     Set<MethodSignature> publicStaticMethods = newHashSet();
138 
139     for (Method method : clazz.getDeclaredMethods()) {
140       int modifiers = method.getModifiers();
141       if (isPublic(modifiers) && isStatic(modifiers)) {
142         publicStaticMethods.add(new MethodSignature(method));
143       }
144     }
145 
146     return publicStaticMethods;
147   }
148 
149   /** [descendant, ancestor) */
getClassesBetween(Class<?> descendant, Class<?> ancestor)150   private static Set<Class<?>> getClassesBetween(Class<?> descendant, Class<?> ancestor) {
151     Set<Class<?>> classes = newHashSet();
152 
153     while (!descendant.equals(ancestor)) {
154       classes.add(descendant);
155       descendant = descendant.getSuperclass();
156     }
157 
158     return classes;
159   }
160 
161   /**
162    * Not really a signature -- just the parts that affect whether one method is a fauxveride of a
163    * method from an ancestor class.
164    *
165    * <p>See JLS 8.4.2 for the definition of the related "override-equivalent."
166    */
167   private static final class MethodSignature implements Comparable<MethodSignature> {
168     final String name;
169     final List<Class<?>> parameterTypes;
170     final TypeSignature typeSignature;
171 
MethodSignature(Method method)172     MethodSignature(Method method) {
173       name = method.getName();
174       parameterTypes = Arrays.asList(method.getParameterTypes());
175       typeSignature = new TypeSignature(method.getTypeParameters());
176     }
177 
178     @Override
equals(Object obj)179     public boolean equals(Object obj) {
180       if (obj instanceof MethodSignature) {
181         MethodSignature other = (MethodSignature) obj;
182         return name.equals(other.name)
183             && parameterTypes.equals(other.parameterTypes)
184             && typeSignature.equals(other.typeSignature);
185       }
186 
187       return false;
188     }
189 
190     @Override
hashCode()191     public int hashCode() {
192       return Objects.hashCode(name, parameterTypes, typeSignature);
193     }
194 
195     @Override
toString()196     public String toString() {
197       return rootLocaleFormat("%s%s(%s)", typeSignature, name, getTypesString(parameterTypes));
198     }
199 
200     @Override
compareTo(MethodSignature o)201     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(
212               Arrays.asList(parameters),
213               new Function<TypeVariable<?>, TypeParameterSignature>() {
214                 @Override
215                 public TypeParameterSignature apply(TypeVariable<?> from) {
216                   return new TypeParameterSignature(from);
217                 }
218               });
219     }
220 
221     @Override
equals(Object obj)222     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
hashCode()232     public int hashCode() {
233       return parameterSignatures.hashCode();
234     }
235 
236     @Override
toString()237     public String toString() {
238       return (parameterSignatures.isEmpty())
239           ? ""
240           : "<" + Joiner.on(", ").join(parameterSignatures) + "> ";
241     }
242   }
243 
244   private static final class TypeParameterSignature {
245     final String name;
246     final List<Type> bounds;
247 
TypeParameterSignature(TypeVariable<?> typeParameter)248     TypeParameterSignature(TypeVariable<?> typeParameter) {
249       name = typeParameter.getName();
250       bounds = Arrays.asList(typeParameter.getBounds());
251     }
252 
253     @Override
equals(Object obj)254     public boolean equals(Object obj) {
255       if (obj instanceof TypeParameterSignature) {
256         TypeParameterSignature other = (TypeParameterSignature) obj;
257         /*
258          * The name is here only for display purposes; <E extends Number> and <T
259          * extends Number> are equivalent.
260          */
261         return bounds.equals(other.bounds);
262       }
263 
264       return false;
265     }
266 
267     @Override
hashCode()268     public int hashCode() {
269       return bounds.hashCode();
270     }
271 
272     @Override
toString()273     public String toString() {
274       return (bounds.equals(ImmutableList.of(Object.class)))
275           ? name
276           : name + " extends " + getTypesString(bounds);
277     }
278   }
279 
getTypesString(List<? extends Type> types)280   private static String getTypesString(List<? extends Type> types) {
281     List<String> names = transform(types, SIMPLE_NAME_GETTER);
282     return Joiner.on(", ").join(names);
283   }
284 
285   private static final Function<Type, String> SIMPLE_NAME_GETTER =
286       new Function<Type, String>() {
287         @Override
288         public String apply(Type from) {
289           if (from instanceof Class) {
290             return ((Class<?>) from).getSimpleName();
291           }
292           return from.toString();
293         }
294       };
295 
rootLocaleFormat(String format, Object... args)296   private static String rootLocaleFormat(String format, Object... args) {
297     return String.format(Locale.ROOT, format, args);
298   }
299 }
300