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