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
17package com.google.common.collect;
18
19import static com.google.common.collect.Lists.transform;
20import static com.google.common.collect.Sets.newHashSet;
21import static com.google.common.collect.Sets.newTreeSet;
22import static java.lang.reflect.Modifier.isPublic;
23import static java.lang.reflect.Modifier.isStatic;
24
25import com.google.common.base.Function;
26import com.google.common.base.Joiner;
27import com.google.common.base.Objects;
28
29import junit.framework.TestCase;
30
31import java.lang.reflect.Method;
32import java.lang.reflect.Type;
33import java.lang.reflect.TypeVariable;
34import java.util.Arrays;
35import java.util.Collections;
36import java.util.List;
37import java.util.Map;
38import 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 */
48public class FauxveridesTest extends TestCase {
49  public void testImmutableBiMap() {
50    doHasAllFauxveridesTest(ImmutableBiMap.class, ImmutableMap.class);
51  }
52
53  public void testImmutableListMultimap() {
54    doHasAllFauxveridesTest(
55        ImmutableListMultimap.class, ImmutableMultimap.class);
56  }
57
58  public void testImmutableSetMultimap() {
59    doHasAllFauxveridesTest(
60        ImmutableSetMultimap.class, ImmutableMultimap.class);
61  }
62
63  public void testImmutableSortedMap() {
64    doHasAllFauxveridesTest(ImmutableSortedMap.class, ImmutableMap.class);
65  }
66
67  public void testImmutableSortedSet() {
68    doHasAllFauxveridesTest(ImmutableSortedSet.class, ImmutableSet.class);
69  }
70
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
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
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
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
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
121  private static Set<MethodSignature> getAllRequiredToFauxveride(Class<?> ancestor) {
122    return getPublicStaticMethodsBetween(ancestor, Object.class);
123  }
124
125  private static Set<MethodSignature> getAllFauxveridden(
126      Class<?> descendant, Class<?> ancestor) {
127    return getPublicStaticMethodsBetween(descendant, ancestor);
128  }
129
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
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) */
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
177    MethodSignature(Method method) {
178      name = method.getName();
179      parameterTypes = Arrays.asList(method.getParameterTypes());
180      typeSignature = new TypeSignature(method.getTypeParameters());
181    }
182
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
194    @Override public int hashCode() {
195      return Objects.hashCode(name, parameterTypes, typeSignature);
196    }
197
198    @Override public String toString() {
199      return String.format("%s%s(%s)",
200          typeSignature, name, getTypesString(parameterTypes));
201    }
202
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
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
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
231    @Override public int hashCode() {
232      return parameterSignatures.hashCode();
233    }
234
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
246    TypeParameterSignature(TypeVariable<?> typeParameter) {
247      name = typeParameter.getName();
248      bounds = Arrays.asList(typeParameter.getBounds());
249    }
250
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
264    @Override public int hashCode() {
265      return bounds.hashCode();
266    }
267
268    @Override public String toString() {
269      return (bounds.equals(ImmutableList.of(Object.class)))
270          ? name
271          : name + " extends " + getTypesString(bounds);
272    }
273  }
274
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