• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 Google LLC
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 package com.google.auto.common;
17 
18 import static com.google.common.collect.Iterables.getOnlyElement;
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 
25 import com.google.common.base.Optional;
26 import com.google.common.collect.ImmutableSet;
27 import com.google.common.collect.Sets;
28 import com.google.common.truth.Expect;
29 import com.google.testing.compile.CompilationRule;
30 import java.lang.annotation.Documented;
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 import java.util.AbstractList;
34 import java.util.Arrays;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.NoSuchElementException;
38 import java.util.Set;
39 import javax.lang.model.element.AnnotationMirror;
40 import javax.lang.model.element.Element;
41 import javax.lang.model.element.ExecutableElement;
42 import javax.lang.model.element.Modifier;
43 import javax.lang.model.element.PackageElement;
44 import javax.lang.model.element.TypeElement;
45 import javax.lang.model.type.TypeKind;
46 import javax.lang.model.type.TypeMirror;
47 import javax.lang.model.util.ElementFilter;
48 import javax.lang.model.util.Elements;
49 import javax.lang.model.util.Types;
50 import org.junit.Before;
51 import org.junit.Rule;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.junit.runners.JUnit4;
55 
56 @RunWith(JUnit4.class)
57 public class MoreElementsTest {
58   @Rule public CompilationRule compilation = new CompilationRule();
59   @Rule public Expect expect = Expect.create();
60 
61   private PackageElement javaLangPackageElement;
62   private TypeElement objectElement;
63   private TypeElement stringElement;
64 
65   @Before
initializeTestElements()66   public void initializeTestElements() {
67     Elements elements = compilation.getElements();
68     this.javaLangPackageElement = elements.getPackageElement("java.lang");
69     this.objectElement = elements.getTypeElement(Object.class.getCanonicalName());
70     this.stringElement = elements.getTypeElement(String.class.getCanonicalName());
71   }
72 
73   @Test
getPackage()74   public void getPackage() {
75     assertThat(MoreElements.getPackage(stringElement)).isEqualTo(javaLangPackageElement);
76     for (Element childElement : stringElement.getEnclosedElements()) {
77       assertThat(MoreElements.getPackage(childElement)).isEqualTo(javaLangPackageElement);
78     }
79   }
80 
81   @Test
asPackage()82   public void asPackage() {
83     assertThat(MoreElements.asPackage(javaLangPackageElement))
84         .isEqualTo(javaLangPackageElement);
85   }
86 
87   @Test
asPackage_illegalArgument()88   public void asPackage_illegalArgument() {
89     try {
90       MoreElements.asPackage(stringElement);
91       fail();
92     } catch (IllegalArgumentException expected) {}
93   }
94 
asTypeElement()95   @Test public void asTypeElement() {
96     Element typeElement =
97         compilation.getElements().getTypeElement(String.class.getCanonicalName());
98     assertTrue(MoreElements.isType(typeElement));
99     assertThat(MoreElements.asType(typeElement)).isEqualTo(typeElement);
100   }
101 
asTypeElement_notATypeElement()102   @Test public void asTypeElement_notATypeElement() {
103     TypeElement typeElement =
104         compilation.getElements().getTypeElement(String.class.getCanonicalName());
105     for (ExecutableElement e : ElementFilter.methodsIn(typeElement.getEnclosedElements())) {
106       assertFalse(MoreElements.isType(e));
107       try {
108         MoreElements.asType(e);
109         fail();
110       } catch (IllegalArgumentException expected) {
111       }
112     }
113   }
114 
115   @Test
asTypeParameterElement()116   public void asTypeParameterElement() {
117     Element typeParameterElement =
118         getOnlyElement(
119             compilation
120                 .getElements()
121                 .getTypeElement(List.class.getCanonicalName())
122                 .getTypeParameters());
123     assertThat(MoreElements.asTypeParameter(typeParameterElement)).isEqualTo(typeParameterElement);
124   }
125 
126   @Test
asTypeParameterElement_illegalArgument()127   public void asTypeParameterElement_illegalArgument() {
128     try {
129       MoreElements.asTypeParameter(javaLangPackageElement);
130       fail();
131     } catch (IllegalArgumentException expected) {
132     }
133   }
134 
135   @Test
asType()136   public void asType() {
137     assertThat(MoreElements.asType(stringElement)).isEqualTo(stringElement);
138   }
139 
140   @Test
asType_illegalArgument()141   public void asType_illegalArgument() {
142     assertFalse(MoreElements.isType(javaLangPackageElement));
143     try {
144       MoreElements.asType(javaLangPackageElement);
145       fail();
146     } catch (IllegalArgumentException expected) {}
147   }
148 
149   @Test
asVariable()150   public void asVariable() {
151     for (Element variableElement : ElementFilter.fieldsIn(stringElement.getEnclosedElements())) {
152       assertThat(MoreElements.asVariable(variableElement)).isEqualTo(variableElement);
153     }
154   }
155 
156   @Test
asVariable_illegalArgument()157   public void asVariable_illegalArgument() {
158     try {
159       MoreElements.asVariable(javaLangPackageElement);
160       fail();
161     } catch (IllegalArgumentException expected) {}
162   }
163 
164   @Test
asExecutable()165   public void asExecutable() {
166     for (Element methodElement : ElementFilter.methodsIn(stringElement.getEnclosedElements())) {
167       assertThat(MoreElements.asExecutable(methodElement)).isEqualTo(methodElement);
168     }
169     for (Element methodElement
170         : ElementFilter.constructorsIn(stringElement.getEnclosedElements())) {
171       assertThat(MoreElements.asExecutable(methodElement)).isEqualTo(methodElement);
172     }
173   }
174 
175   @Test
asExecutable_illegalArgument()176   public void asExecutable_illegalArgument() {
177     try {
178       MoreElements.asExecutable(javaLangPackageElement);
179       fail();
180     } catch (IllegalArgumentException expected) {}
181   }
182 
183   @Retention(RetentionPolicy.RUNTIME)
184   private @interface InnerAnnotation {}
185 
186   @Documented
187   @InnerAnnotation
188   private @interface AnnotatedAnnotation {}
189 
190   @Test
isAnnotationPresent()191   public void isAnnotationPresent() {
192     TypeElement annotatedAnnotationElement =
193         compilation.getElements().getTypeElement(AnnotatedAnnotation.class.getCanonicalName());
194     assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, Documented.class))
195         .isTrue();
196     assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, InnerAnnotation.class))
197         .isTrue();
198     assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, SuppressWarnings.class))
199         .isFalse();
200   }
201 
202   @Test
getAnnotationMirror()203   public void getAnnotationMirror() {
204     TypeElement element =
205         compilation.getElements().getTypeElement(AnnotatedAnnotation.class.getCanonicalName());
206 
207     Optional<AnnotationMirror> documented =
208         MoreElements.getAnnotationMirror(element, Documented.class);
209     Optional<AnnotationMirror> innerAnnotation =
210         MoreElements.getAnnotationMirror(element, InnerAnnotation.class);
211     Optional<AnnotationMirror> suppressWarnings =
212         MoreElements.getAnnotationMirror(element, SuppressWarnings.class);
213 
214     expect.that(documented).isPresent();
215     expect.that(innerAnnotation).isPresent();
216     expect.that(suppressWarnings).isAbsent();
217 
218     Element annotationElement = documented.get().getAnnotationType().asElement();
219     expect.that(MoreElements.isType(annotationElement)).isTrue();
220     expect.that(MoreElements.asType(annotationElement).getQualifiedName().toString())
221         .isEqualTo(Documented.class.getCanonicalName());
222 
223     annotationElement = innerAnnotation.get().getAnnotationType().asElement();
224     expect.that(MoreElements.isType(annotationElement)).isTrue();
225     expect.that(MoreElements.asType(annotationElement).getQualifiedName().toString())
226         .isEqualTo(InnerAnnotation.class.getCanonicalName());
227   }
228 
229   private abstract static class ParentClass {
staticMethod()230     static void staticMethod() {}
231 
foo()232     abstract String foo();
233 
privateMethod()234     private void privateMethod() {}
235   }
236 
237   private interface ParentInterface {
staticMethod()238     static void staticMethod() {}
239 
bar()240     abstract int bar();
241 
bar(long x)242     abstract int bar(long x);
243   }
244 
245   private abstract static class Child extends ParentClass implements ParentInterface {
staticMethod()246     static void staticMethod() {}
247 
248     @Override
bar()249     public int bar() {
250       return 0;
251     }
252 
baz()253     abstract void baz();
254 
buh(int x)255     void buh(int x) {}
256 
buh(int x, int y)257     void buh(int x, int y) {}
258   }
259 
260   @Test
getLocalAndInheritedMethods_Old()261   public void getLocalAndInheritedMethods_Old() {
262     Elements elements = compilation.getElements();
263     Types types = compilation.getTypes();
264     TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
265     TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG);
266     TypeElement childType = elements.getTypeElement(Child.class.getCanonicalName());
267     @SuppressWarnings("deprecation")
268     Set<ExecutableElement> childTypeMethods =
269         MoreElements.getLocalAndInheritedMethods(childType, elements);
270     Set<ExecutableElement> objectMethods = visibleMethodsFromObject();
271     assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods);
272     Set<ExecutableElement> nonObjectMethods = Sets.difference(childTypeMethods, objectMethods);
273     assertThat(nonObjectMethods).containsExactly(
274             getMethod(ParentInterface.class, "bar", longMirror),
275             getMethod(ParentClass.class, "foo"),
276             getMethod(Child.class, "bar"),
277             getMethod(Child.class, "baz"),
278             getMethod(Child.class, "buh", intMirror),
279             getMethod(Child.class, "buh", intMirror, intMirror))
280         .inOrder();;
281   }
282 
283   @Test
getLocalAndInheritedMethods()284   public void getLocalAndInheritedMethods() {
285     Elements elements = compilation.getElements();
286     Types types = compilation.getTypes();
287     TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
288     TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG);
289     TypeElement childType = elements.getTypeElement(Child.class.getCanonicalName());
290     @SuppressWarnings("deprecation")
291     Set<ExecutableElement> childTypeMethods =
292         MoreElements.getLocalAndInheritedMethods(childType, types, elements);
293     Set<ExecutableElement> objectMethods = visibleMethodsFromObject();
294     assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods);
295     Set<ExecutableElement> nonObjectMethods = Sets.difference(childTypeMethods, objectMethods);
296     assertThat(nonObjectMethods).containsExactly(
297             getMethod(ParentInterface.class, "bar", longMirror),
298             getMethod(ParentClass.class, "foo"),
299             getMethod(Child.class, "bar"),
300             getMethod(Child.class, "baz"),
301             getMethod(Child.class, "buh", intMirror),
302             getMethod(Child.class, "buh", intMirror, intMirror))
303         .inOrder();
304   }
305 
306   @Test
getAllMethods()307   public void getAllMethods() {
308     Elements elements = compilation.getElements();
309     Types types = compilation.getTypes();
310     TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
311     TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG);
312     TypeElement childType = elements.getTypeElement(Child.class.getCanonicalName());
313     @SuppressWarnings("deprecation")
314     Set<ExecutableElement> childTypeMethods =
315         MoreElements.getAllMethods(childType, types, elements);
316     Set<ExecutableElement> objectMethods = allMethodsFromObject();
317     assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods);
318     Set<ExecutableElement> nonObjectMethods = Sets.difference(childTypeMethods, objectMethods);
319     assertThat(nonObjectMethods).containsExactly(
320             getMethod(ParentInterface.class, "staticMethod"),
321             getMethod(ParentInterface.class, "bar", longMirror),
322             getMethod(ParentClass.class, "staticMethod"),
323             getMethod(ParentClass.class, "foo"),
324             getMethod(ParentClass.class, "privateMethod"),
325             getMethod(Child.class, "staticMethod"),
326             getMethod(Child.class, "bar"),
327             getMethod(Child.class, "baz"),
328             getMethod(Child.class, "buh", intMirror),
329             getMethod(Child.class, "buh", intMirror, intMirror))
330         .inOrder();
331   }
332 
333   static class Injectable {}
334 
335   public static class MenuManager {
336     public interface ParentComponent extends MenuItemA.ParentComponent, MenuItemB.ParentComponent {}
337   }
338 
339   public static class MenuItemA {
340     public interface ParentComponent {
injectable()341       Injectable injectable();
342     }
343   }
344 
345   public static class MenuItemB {
346     public interface ParentComponent {
injectable()347       Injectable injectable();
348     }
349   }
350 
351   public static class Main {
352     public interface ParentComponent extends MenuManager.ParentComponent {}
353   }
354 
355   // Example from https://github.com/williamlian/daggerbug
356   @Test
getLocalAndInheritedMethods_DaggerBug()357   public void getLocalAndInheritedMethods_DaggerBug() {
358     Elements elementUtils = compilation.getElements();
359     TypeElement main = elementUtils.getTypeElement(Main.ParentComponent.class.getCanonicalName());
360     Set<ExecutableElement> methods = MoreElements.getLocalAndInheritedMethods(
361         main, compilation.getTypes(), elementUtils);
362     assertThat(methods).hasSize(1);
363     ExecutableElement method = methods.iterator().next();
364     assertThat(method.getSimpleName().toString()).isEqualTo("injectable");
365     assertThat(method.getParameters()).isEmpty();
366   }
367 
visibleMethodsFromObject()368   private Set<ExecutableElement> visibleMethodsFromObject() {
369     Types types = compilation.getTypes();
370     TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
371     TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG);
372     Set<ExecutableElement> methods = new HashSet<ExecutableElement>();
373     for (ExecutableElement method : ElementFilter.methodsIn(objectElement.getEnclosedElements())) {
374       if (method.getModifiers().contains(Modifier.PUBLIC)
375           || method.getModifiers().contains(Modifier.PROTECTED)) {
376         methods.add(method);
377       }
378     }
379     assertThat(methods)
380         .containsAtLeast(
381             getMethod(Object.class, "clone"),
382             getMethod(Object.class, "finalize"),
383             getMethod(Object.class, "wait"),
384             getMethod(Object.class, "wait", longMirror),
385             getMethod(Object.class, "wait", longMirror, intMirror));
386     return methods;
387   }
388 
allMethodsFromObject()389   private Set<ExecutableElement> allMethodsFromObject() {
390     Types types = compilation.getTypes();
391     TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
392     TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG);
393     Set<ExecutableElement> methods = new HashSet<>();
394     methods.addAll(ElementFilter.methodsIn(objectElement.getEnclosedElements()));
395     assertThat(methods)
396         .containsAtLeast(
397             getMethod(Object.class, "clone"),
398             getMethod(Object.class, "registerNatives"),
399             getMethod(Object.class, "finalize"),
400             getMethod(Object.class, "wait"),
401             getMethod(Object.class, "wait", longMirror),
402             getMethod(Object.class, "wait", longMirror, intMirror));
403     return methods;
404   }
405 
getMethod(Class<?> c, String methodName, TypeMirror... parameterTypes)406   private ExecutableElement getMethod(Class<?> c, String methodName, TypeMirror... parameterTypes) {
407     TypeElement type = compilation.getElements().getTypeElement(c.getCanonicalName());
408     Types types = compilation.getTypes();
409     ExecutableElement found = null;
410     for (ExecutableElement method : ElementFilter.methodsIn(type.getEnclosedElements())) {
411       if (method.getSimpleName().contentEquals(methodName)
412           && method.getParameters().size() == parameterTypes.length) {
413         boolean match = true;
414         for (int i = 0; i < parameterTypes.length; i++) {
415           TypeMirror expectedType = parameterTypes[i];
416           TypeMirror actualType = method.getParameters().get(i).asType();
417           match &= types.isSameType(expectedType, actualType);
418         }
419         if (match) {
420           assertThat(found).isNull();
421           found = method;
422         }
423       }
424     }
425     assertWithMessage(methodName + Arrays.toString(parameterTypes)).that(found).isNotNull();
426     return found;
427   }
428 
429   private abstract static class AbstractAbstractList extends AbstractList<String> {}
430 
431   private static class ConcreteAbstractList<T> extends AbstractList<T> {
432     @Override
size()433     public int size() {
434       return 0;
435     }
436 
437     @Override
get(int index)438     public T get(int index) {
439       throw new NoSuchElementException();
440     }
441   }
442 
abstractMethodNamesFrom(Set<ExecutableElement> methods)443   private Set<String> abstractMethodNamesFrom(Set<ExecutableElement> methods) {
444     ImmutableSet.Builder<String> abstractMethodNames = ImmutableSet.builder();
445     for (ExecutableElement method : methods) {
446       if (method.getModifiers().contains(Modifier.ABSTRACT)) {
447         abstractMethodNames.add(method.getSimpleName().toString());
448       }
449     }
450     return abstractMethodNames.build();
451   }
452 
453   // Test that getLocalAndInheritedMethods does the right thing with AbstractList. That class
454   // inherits from Collection along two paths, via its parent AbstractCollection (which implements
455   // Collection) and via its parent List (which extends Collection). Bugs in the past have meant
456   // that the multiple paths might lead the code into thinking that all the abstract methods of
457   // Collection were still abstract in the AbstractList subclasses here, even though most of them
458   // are implemented in AbstractList.
459   @Test
getLocalAndInheritedMethods_AbstractList()460   public void getLocalAndInheritedMethods_AbstractList() {
461     Elements elements = compilation.getElements();
462 
463     TypeElement abstractType =
464         elements.getTypeElement(AbstractAbstractList.class.getCanonicalName());
465     Set<ExecutableElement> abstractTypeMethods =
466         MoreElements.getLocalAndInheritedMethods(abstractType, elements);
467     assertThat(abstractMethodNamesFrom(abstractTypeMethods)).containsExactly("get", "size");
468 
469     TypeElement concreteType =
470         elements.getTypeElement(ConcreteAbstractList.class.getCanonicalName());
471     Set<ExecutableElement> concreteTypeMethods =
472         MoreElements.getLocalAndInheritedMethods(concreteType, elements);
473     assertThat(abstractMethodNamesFrom(concreteTypeMethods)).isEmpty();
474   }
475 }
476