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