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