1 /* 2 * Copyright (C) 2019 The Dagger 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 17 package dagger.hilt.android.processor.internal; 18 19 import com.google.common.base.Preconditions; 20 import com.google.common.collect.Iterables; 21 import java.util.Optional; 22 import java.util.Set; 23 import java.util.stream.Collectors; 24 import javax.lang.model.element.ExecutableElement; 25 import javax.lang.model.element.TypeElement; 26 import javax.lang.model.type.ArrayType; 27 import javax.lang.model.type.DeclaredType; 28 import javax.lang.model.type.ErrorType; 29 import javax.lang.model.type.ExecutableType; 30 import javax.lang.model.type.TypeKind; 31 import javax.lang.model.type.TypeMirror; 32 import javax.lang.model.util.ElementFilter; 33 import javax.lang.model.util.SimpleTypeVisitor7; 34 import javax.lang.model.util.Types; 35 36 /** More utility methods for types. */ 37 public final class MoreTypes { MoreTypes()38 private MoreTypes() {} 39 40 /** 41 * If the received mirror represents a declared type or an array of declared types, this returns 42 * the represented declared type. Otherwise throws an IllegalStateException. 43 */ getDeclaredType(TypeMirror type)44 public static DeclaredType getDeclaredType(TypeMirror type) { 45 return type.accept( 46 new SimpleTypeVisitor7<DeclaredType, Void>() { 47 @Override public DeclaredType visitArray(ArrayType type, Void unused) { 48 return getDeclaredType(type.getComponentType()); 49 } 50 51 @Override public DeclaredType visitDeclared(DeclaredType type, Void unused) { 52 return type; 53 } 54 55 @Override public DeclaredType visitError(ErrorType type, Void unused) { 56 return type; 57 } 58 59 @Override public DeclaredType defaultAction(TypeMirror type, Void unused) { 60 throw new IllegalStateException("Unhandled type: " + type); 61 } 62 }, null /* the Void accumulator */); 63 } 64 65 /** Returns the TypeElement corresponding to a TypeMirror. */ 66 public static TypeElement asTypeElement(TypeMirror type) { 67 return asTypeElement(getDeclaredType(type)); 68 } 69 70 /** Returns the TypeElement corresponding to a DeclaredType. */ 71 public static TypeElement asTypeElement(DeclaredType type) { 72 return (TypeElement) type.asElement(); 73 } 74 75 /** 76 * Returns a {@link ExecutableType} if the {@link TypeMirror} represents an executable type such 77 * as a method, constructor, or initializer or throws an {@link IllegalArgumentException}. 78 */ 79 public static ExecutableType asExecutable(TypeMirror maybeExecutableType) { 80 return maybeExecutableType.accept(ExecutableTypeVisitor.INSTANCE, null); 81 } 82 83 private static final class ExecutableTypeVisitor extends CastingTypeVisitor<ExecutableType> { 84 private static final ExecutableTypeVisitor INSTANCE = new ExecutableTypeVisitor(); 85 86 ExecutableTypeVisitor() { 87 super("executable type"); 88 } 89 90 @Override 91 public ExecutableType visitExecutable(ExecutableType type, Void ignore) { 92 return type; 93 } 94 } 95 96 private abstract static class CastingTypeVisitor<T> extends SimpleTypeVisitor7<T, Void> { 97 private final String label; 98 99 CastingTypeVisitor(String label) { 100 this.label = label; 101 } 102 103 @Override 104 protected T defaultAction(TypeMirror e, Void v) { 105 throw new IllegalArgumentException(e + " does not represent a " + label); 106 } 107 } 108 109 /** 110 * Returns the first matching method, if one exists (starting with classElement, then searching 111 * each sub classes). 112 */ 113 public static Optional<ExecutableElement> findInheritedMethod( 114 Types types, TypeElement classElement, ExecutableElement method) { 115 Optional<ExecutableElement> match = Optional.empty(); 116 while (!match.isPresent() && !classElement.asType().getKind().equals(TypeKind.NONE)) { 117 match = findMethod(types, classElement, method); 118 classElement = MoreTypes.asTypeElement(classElement.getSuperclass()); 119 } 120 return match; 121 } 122 123 /** Returns a method with a matching signature in classElement if one exists. */ 124 public static Optional<ExecutableElement> findMethod( 125 Types types, TypeElement classElement, ExecutableElement method) { 126 ExecutableType methodType = asExecutable(method.asType()); 127 Set<ExecutableElement> matchingMethods = 128 findMethods(classElement, method.getSimpleName().toString()) 129 .stream() 130 .filter(clsMethod -> types.isSubsignature(asExecutable(clsMethod.asType()), methodType)) 131 .collect(Collectors.toSet()); 132 133 Preconditions.checkState( 134 matchingMethods.size() <= 1, 135 "Found multiple methods with matching signature in class %s: %s", 136 classElement, 137 matchingMethods); 138 139 return matchingMethods.size() == 1 140 ? Optional.of(Iterables.getOnlyElement(matchingMethods)) 141 : Optional.empty(); 142 } 143 144 /** Returns methods with a matching name in classElement. */ 145 public static Set<ExecutableElement> findMethods(TypeElement classElement, String name) { 146 return ElementFilter.methodsIn(classElement.getEnclosedElements()) 147 .stream() 148 .filter(clsMethod -> clsMethod.getSimpleName().contentEquals(name)) 149 .collect(Collectors.toSet()); 150 } 151 } 152