• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.internal.codegen.binding;
18 
19 import static com.google.auto.common.MoreElements.asExecutable;
20 import static com.google.auto.common.MoreElements.isAnnotationPresent;
21 import static com.google.auto.common.MoreTypes.asDeclared;
22 import static com.google.auto.common.MoreTypes.asExecutable;
23 import static com.google.auto.common.MoreTypes.asTypeElement;
24 import static com.google.common.base.Preconditions.checkArgument;
25 import static com.google.common.collect.Iterables.getOnlyElement;
26 import static dagger.internal.codegen.base.MoreAnnotationValues.getStringValue;
27 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
28 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
29 import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror;
30 import static javax.lang.model.element.Modifier.ABSTRACT;
31 import static javax.lang.model.util.ElementFilter.constructorsIn;
32 
33 import com.google.auto.common.MoreElements;
34 import com.google.auto.common.MoreTypes;
35 import com.google.auto.value.AutoValue;
36 import com.google.auto.value.extension.memoized.Memoized;
37 import com.google.common.base.Equivalence;
38 import com.google.common.collect.ImmutableList;
39 import com.google.common.collect.ImmutableMap;
40 import com.google.common.collect.ImmutableSet;
41 import com.squareup.javapoet.ParameterSpec;
42 import com.squareup.javapoet.TypeName;
43 import dagger.assisted.Assisted;
44 import dagger.assisted.AssistedFactory;
45 import dagger.assisted.AssistedInject;
46 import dagger.internal.codegen.langmodel.DaggerElements;
47 import dagger.internal.codegen.langmodel.DaggerTypes;
48 import dagger.model.BindingKind;
49 import java.util.List;
50 import javax.lang.model.element.Element;
51 import javax.lang.model.element.ExecutableElement;
52 import javax.lang.model.element.TypeElement;
53 import javax.lang.model.element.VariableElement;
54 import javax.lang.model.type.DeclaredType;
55 import javax.lang.model.type.ExecutableType;
56 import javax.lang.model.type.TypeMirror;
57 
58 /** Assisted injection utility methods. */
59 public final class AssistedInjectionAnnotations {
60   /** Returns the factory method for the given factory {@link TypeElement}. */
assistedFactoryMethod( TypeElement factory, DaggerElements elements, DaggerTypes types)61   public static ExecutableElement assistedFactoryMethod(
62       TypeElement factory, DaggerElements elements, DaggerTypes types) {
63     return getOnlyElement(assistedFactoryMethods(factory, elements, types));
64   }
65 
66   /** Returns the list of abstract factory methods for the given factory {@link TypeElement}. */
assistedFactoryMethods( TypeElement factory, DaggerElements elements, DaggerTypes types)67   public static ImmutableSet<ExecutableElement> assistedFactoryMethods(
68       TypeElement factory, DaggerElements elements, DaggerTypes types) {
69     return MoreElements.getLocalAndInheritedMethods(factory, types, elements).stream()
70         .filter(method -> method.getModifiers().contains(ABSTRACT))
71         .filter(method -> !method.isDefault())
72         .collect(toImmutableSet());
73   }
74 
75   /** Returns {@code true} if the element uses assisted injection. */
isAssistedInjectionType(TypeElement typeElement)76   public static boolean isAssistedInjectionType(TypeElement typeElement) {
77     ImmutableSet<ExecutableElement> injectConstructors = assistedInjectedConstructors(typeElement);
78     return !injectConstructors.isEmpty()
79         && isAnnotationPresent(getOnlyElement(injectConstructors), AssistedInject.class);
80   }
81 
82   /** Returns {@code true} if this binding is an assisted factory. */
isAssistedFactoryType(Element element)83   public static boolean isAssistedFactoryType(Element element) {
84     return isAnnotationPresent(element, AssistedFactory.class);
85   }
86 
87   /**
88    * Returns the list of assisted parameters as {@link ParameterSpec}s.
89    *
90    * <p>The type of each parameter will be the resolved type given by the binding key, and the name
91    * of each parameter will be the name given in the {@link
92    * dagger.assisted.AssistedInject}-annotated constructor.
93    */
assistedParameterSpecs( Binding binding, DaggerTypes types)94   public static ImmutableList<ParameterSpec> assistedParameterSpecs(
95       Binding binding, DaggerTypes types) {
96     checkArgument(binding.kind() == BindingKind.ASSISTED_INJECTION);
97     ExecutableElement constructor = asExecutable(binding.bindingElement().get());
98     ExecutableType constructorType =
99         asExecutable(types.asMemberOf(asDeclared(binding.key().type()), constructor));
100     return assistedParameterSpecs(constructor.getParameters(), constructorType.getParameterTypes());
101   }
102 
assistedParameterSpecs( List<? extends VariableElement> paramElements, List<? extends TypeMirror> paramTypes)103   private static ImmutableList<ParameterSpec> assistedParameterSpecs(
104       List<? extends VariableElement> paramElements, List<? extends TypeMirror> paramTypes) {
105     ImmutableList.Builder<ParameterSpec> assistedParameterSpecs = ImmutableList.builder();
106     for (int i = 0; i < paramElements.size(); i++) {
107       VariableElement paramElement = paramElements.get(i);
108       TypeMirror paramType = paramTypes.get(i);
109       if (isAssistedParameter(paramElement)) {
110         assistedParameterSpecs.add(
111             ParameterSpec.builder(TypeName.get(paramType), paramElement.getSimpleName().toString())
112                 .build());
113       }
114     }
115     return assistedParameterSpecs.build();
116   }
117 
118   /**
119    * Returns the list of assisted factory parameters as {@link ParameterSpec}s.
120    *
121    * <p>The type of each parameter will be the resolved type given by the binding key, and the name
122    * of each parameter will be the name given in the {@link
123    * dagger.assisted.AssistedInject}-annotated constructor.
124    */
assistedFactoryParameterSpecs( Binding binding, DaggerElements elements, DaggerTypes types)125   public static ImmutableList<ParameterSpec> assistedFactoryParameterSpecs(
126       Binding binding, DaggerElements elements, DaggerTypes types) {
127     checkArgument(binding.kind() == BindingKind.ASSISTED_FACTORY);
128 
129     AssistedFactoryMetadata metadata =
130         AssistedFactoryMetadata.create(binding.bindingElement().get().asType(), elements, types);
131     ExecutableType factoryMethodType =
132         asExecutable(types.asMemberOf(asDeclared(binding.key().type()), metadata.factoryMethod()));
133     return assistedParameterSpecs(
134         // Use the order of the parameters from the @AssistedFactory method but use the parameter
135         // names of the @AssistedInject constructor.
136         metadata.assistedFactoryAssistedParameters().stream()
137             .map(metadata.assistedInjectAssistedParametersMap()::get)
138             .collect(toImmutableList()),
139         factoryMethodType.getParameterTypes());
140   }
141 
142   /** Returns the constructors in {@code type} that are annotated with {@link AssistedInject}. */
assistedInjectedConstructors(TypeElement type)143   public static ImmutableSet<ExecutableElement> assistedInjectedConstructors(TypeElement type) {
144     return constructorsIn(type.getEnclosedElements()).stream()
145         .filter(constructor -> isAnnotationPresent(constructor, AssistedInject.class))
146         .collect(toImmutableSet());
147   }
148 
assistedParameters(Binding binding)149   public static ImmutableList<VariableElement> assistedParameters(Binding binding) {
150     return binding.kind() == BindingKind.ASSISTED_INJECTION
151         ? assistedParameters(asExecutable(binding.bindingElement().get()))
152         : ImmutableList.of();
153   }
154 
assistedParameters(ExecutableElement constructor)155   private static ImmutableList<VariableElement> assistedParameters(ExecutableElement constructor) {
156     return constructor.getParameters().stream()
157         .filter(AssistedInjectionAnnotations::isAssistedParameter)
158         .collect(toImmutableList());
159   }
160 
161   /** Returns {@code true} if this binding is uses assisted injection. */
isAssistedParameter(VariableElement param)162   public static boolean isAssistedParameter(VariableElement param) {
163     return isAnnotationPresent(MoreElements.asVariable(param), Assisted.class);
164   }
165 
166   /** Metadata about an {@link dagger.assisted.AssistedFactory} annotated type. */
167   @AutoValue
168   public abstract static class AssistedFactoryMetadata {
create( TypeMirror factory, DaggerElements elements, DaggerTypes types)169     public static AssistedFactoryMetadata create(
170         TypeMirror factory, DaggerElements elements, DaggerTypes types) {
171       DeclaredType factoryType = asDeclared(factory);
172       TypeElement factoryElement = asTypeElement(factoryType);
173       ExecutableElement factoryMethod = assistedFactoryMethod(factoryElement, elements, types);
174       ExecutableType factoryMethodType = asExecutable(types.asMemberOf(factoryType, factoryMethod));
175       DeclaredType assistedInjectType = asDeclared(factoryMethodType.getReturnType());
176       return new AutoValue_AssistedInjectionAnnotations_AssistedFactoryMetadata(
177           factoryElement,
178           factoryType,
179           factoryMethod,
180           factoryMethodType,
181           asTypeElement(assistedInjectType),
182           assistedInjectType,
183           AssistedInjectionAnnotations.assistedInjectAssistedParameters(assistedInjectType, types),
184           AssistedInjectionAnnotations.assistedFactoryAssistedParameters(
185               factoryMethod, factoryMethodType));
186     }
187 
factory()188     public abstract TypeElement factory();
189 
factoryType()190     public abstract DeclaredType factoryType();
191 
factoryMethod()192     public abstract ExecutableElement factoryMethod();
193 
factoryMethodType()194     public abstract ExecutableType factoryMethodType();
195 
assistedInjectElement()196     public abstract TypeElement assistedInjectElement();
197 
assistedInjectType()198     public abstract DeclaredType assistedInjectType();
199 
assistedInjectAssistedParameters()200     public abstract ImmutableList<AssistedParameter> assistedInjectAssistedParameters();
201 
assistedFactoryAssistedParameters()202     public abstract ImmutableList<AssistedParameter> assistedFactoryAssistedParameters();
203 
204     @Memoized
assistedInjectAssistedParametersMap()205     public ImmutableMap<AssistedParameter, VariableElement> assistedInjectAssistedParametersMap() {
206       ImmutableMap.Builder<AssistedParameter, VariableElement> builder = ImmutableMap.builder();
207       for (AssistedParameter assistedParameter : assistedInjectAssistedParameters()) {
208         builder.put(assistedParameter, assistedParameter.variableElement);
209       }
210       return builder.build();
211     }
212 
213     @Memoized
assistedFactoryAssistedParametersMap()214     public ImmutableMap<AssistedParameter, VariableElement> assistedFactoryAssistedParametersMap() {
215       ImmutableMap.Builder<AssistedParameter, VariableElement> builder = ImmutableMap.builder();
216       for (AssistedParameter assistedParameter : assistedFactoryAssistedParameters()) {
217         builder.put(assistedParameter, assistedParameter.variableElement);
218       }
219       return builder.build();
220     }
221   }
222 
223   /**
224    * Metadata about an {@link Assisted} annotated parameter.
225    *
226    * <p>This parameter can represent an {@link Assisted} annotated parameter from an {@link
227    * AssistedInject} constructor or an {@link AssistedFactory} method.
228    */
229   @AutoValue
230   public abstract static class AssistedParameter {
create(VariableElement parameter, TypeMirror parameterType)231     public static AssistedParameter create(VariableElement parameter, TypeMirror parameterType) {
232       AssistedParameter assistedParameter =
233           new AutoValue_AssistedInjectionAnnotations_AssistedParameter(
234               getAnnotationMirror(parameter, Assisted.class)
235                   .map(assisted -> getStringValue(assisted, "value"))
236                   .orElse(""),
237               MoreTypes.equivalence().wrap(parameterType));
238       assistedParameter.variableElement = parameter;
239       return assistedParameter;
240     }
241 
242     private VariableElement variableElement;
243 
244     /** Returns the string qualifier from the {@link Assisted#value()}. */
qualifier()245     public abstract String qualifier();
246 
247     /** Returns the wrapper for the type annotated with {@link Assisted}. */
wrappedType()248     public abstract Equivalence.Wrapper<TypeMirror> wrappedType();
249 
250     /** Returns the type annotated with {@link Assisted}. */
type()251     public final TypeMirror type() {
252       return wrappedType().get();
253     }
254 
variableElement()255     public final VariableElement variableElement() {
256       return variableElement;
257     }
258 
259     @Override
toString()260     public final String toString() {
261       return qualifier().isEmpty()
262           ? String.format("@Assisted %s", type())
263           : String.format("@Assisted(\"%s\") %s", qualifier(), type());
264     }
265   }
266 
assistedInjectAssistedParameters( DeclaredType assistedInjectType, DaggerTypes types)267   public static ImmutableList<AssistedParameter> assistedInjectAssistedParameters(
268       DeclaredType assistedInjectType, DaggerTypes types) {
269     // We keep track of the constructor both as an ExecutableElement to access @Assisted
270     // parameters and as an ExecutableType to access the resolved parameter types.
271     ExecutableElement assistedInjectConstructor =
272         getOnlyElement(assistedInjectedConstructors(asTypeElement(assistedInjectType)));
273     ExecutableType assistedInjectConstructorType =
274         asExecutable(types.asMemberOf(assistedInjectType, assistedInjectConstructor));
275 
276     ImmutableList.Builder<AssistedParameter> builder = ImmutableList.builder();
277     for (int i = 0; i < assistedInjectConstructor.getParameters().size(); i++) {
278       VariableElement parameter = assistedInjectConstructor.getParameters().get(i);
279       TypeMirror parameterType = assistedInjectConstructorType.getParameterTypes().get(i);
280       if (isAnnotationPresent(parameter, Assisted.class)) {
281         builder.add(AssistedParameter.create(parameter, parameterType));
282       }
283     }
284     return builder.build();
285   }
286 
assistedFactoryAssistedParameters( ExecutableElement factoryMethod, ExecutableType factoryMethodType)287   public static ImmutableList<AssistedParameter> assistedFactoryAssistedParameters(
288       ExecutableElement factoryMethod, ExecutableType factoryMethodType) {
289     ImmutableList.Builder<AssistedParameter> builder = ImmutableList.builder();
290     for (int i = 0; i < factoryMethod.getParameters().size(); i++) {
291       VariableElement parameter = factoryMethod.getParameters().get(i);
292       TypeMirror parameterType = factoryMethodType.getParameterTypes().get(i);
293       builder.add(AssistedParameter.create(parameter, parameterType));
294     }
295     return builder.build();
296   }
297 
AssistedInjectionAnnotations()298   private AssistedInjectionAnnotations() {}
299 }
300