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