• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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