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