• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 androidx.room.compiler.processing.XElementKt.isConstructor;
20 import static androidx.room.compiler.processing.XElementKt.isField;
21 import static androidx.room.compiler.processing.XElementKt.isMethod;
22 import static androidx.room.compiler.processing.XElementKt.isMethodParameter;
23 import static androidx.room.compiler.processing.XElementKt.isTypeElement;
24 import static com.google.common.base.Preconditions.checkNotNull;
25 import static com.google.common.base.Preconditions.checkState;
26 import static com.google.common.collect.Iterables.getOnlyElement;
27 import static dagger.internal.codegen.binding.SourceFiles.factoryNameForElement;
28 import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable;
29 import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType;
30 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
31 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
32 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
33 import static dagger.internal.codegen.xprocessing.XElements.asField;
34 import static dagger.internal.codegen.xprocessing.XElements.asMethod;
35 import static dagger.internal.codegen.xprocessing.XElements.asMethodParameter;
36 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
37 import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;
38 
39 import androidx.room.compiler.codegen.XClassName;
40 import androidx.room.compiler.processing.XAnnotation;
41 import androidx.room.compiler.processing.XConstructorElement;
42 import androidx.room.compiler.processing.XElement;
43 import androidx.room.compiler.processing.XExecutableElement;
44 import androidx.room.compiler.processing.XFieldElement;
45 import androidx.room.compiler.processing.XProcessingEnv;
46 import androidx.room.compiler.processing.XTypeElement;
47 import com.google.common.collect.ImmutableList;
48 import com.google.common.collect.ImmutableSet;
49 import dagger.internal.codegen.base.DaggerSuperficialValidation;
50 import dagger.internal.codegen.base.ElementFormatter;
51 import dagger.internal.codegen.compileroption.CompilerOptions;
52 import dagger.internal.codegen.javapoet.TypeNames;
53 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
54 import dagger.internal.codegen.model.DaggerAnnotation;
55 import dagger.internal.codegen.model.Scope;
56 import dagger.internal.codegen.xprocessing.XAnnotations;
57 import dagger.internal.codegen.xprocessing.XTypeNames;
58 import java.util.Optional;
59 import java.util.stream.Stream;
60 import javax.inject.Inject;
61 
62 /** Utilities relating to annotations defined in the {@code javax.inject} package. */
63 public final class InjectionAnnotations {
64   private final XProcessingEnv processingEnv;
65   private final KotlinMetadataUtil kotlinMetadataUtil;
66   private final DaggerSuperficialValidation superficialValidation;
67   private final CompilerOptions compilerOptions;
68 
69   @Inject
InjectionAnnotations( XProcessingEnv processingEnv, KotlinMetadataUtil kotlinMetadataUtil, DaggerSuperficialValidation superficialValidation, CompilerOptions compilerOptions)70   InjectionAnnotations(
71       XProcessingEnv processingEnv,
72       KotlinMetadataUtil kotlinMetadataUtil,
73       DaggerSuperficialValidation superficialValidation,
74       CompilerOptions compilerOptions) {
75     this.processingEnv = processingEnv;
76     this.kotlinMetadataUtil = kotlinMetadataUtil;
77     this.superficialValidation = superficialValidation;
78     this.compilerOptions = compilerOptions;
79   }
80 
81   /**
82    * Returns the scope on the given element if it exists.
83    *
84    * <p>The {@code ScopeMetadata} is used to avoid superficial validation on unnecessary
85    * annotations. If the {@code ScopeMetadata} does not exist, then all annotations must be
86    * superficially validated before we can determine if they are scopes or not.
87    *
88    * @throws IllegalArgumentException if the given element has more than one scope.
89    */
getScope(XElement element)90   public Optional<Scope> getScope(XElement element) {
91     return getScopes(element).stream().collect(toOptional());
92   }
93 
94   /**
95    * Returns the scopes on the given element, or an empty set if none exist.
96    *
97    * <p>Note: Use {@link #getScope(XElement)} if the usage of the scope on the given element has
98    * already been validated and known to be unique. This method should typically only be used in the
99    * process of such validation.
100    *
101    * <p>The {@code ScopeMetadata} is used to avoid superficial validation on unnecessary
102    * annotations. If the {@code ScopeMetadata} does not exist, then all annotations must be
103    * superficially validated before we can determine if they are scopes or not.
104    */
getScopes(XElement element)105   public ImmutableSet<Scope> getScopes(XElement element) {
106     superficialValidation.validateTypeOf(element);
107     ImmutableSet<Scope> scopes =
108         getScopesWithMetadata(element).orElseGet(() -> getScopesWithoutMetadata(element));
109 
110     // Fully validate each scope to ensure its values are also valid.
111     scopes.stream()
112         .map(scope -> scope.scopeAnnotation().xprocessing())
113         .forEach(scope -> superficialValidation.validateAnnotationOf(element, scope));
114     return scopes;
115   }
116 
getScopesWithoutMetadata(XElement element)117   private ImmutableSet<Scope> getScopesWithoutMetadata(XElement element) {
118     // Validate the annotation types before we check for @Scope, otherwise the @Scope
119     // annotation may appear to be missing (b/213880825).
120     superficialValidation.validateAnnotationTypesOf(element);
121     return element.getAllAnnotations().stream()
122         .filter(InjectionAnnotations::hasScopeAnnotation)
123         .map(DaggerAnnotation::from)
124         .map(Scope::scope)
125         .collect(toImmutableSet());
126   }
127 
getScopesWithMetadata(XElement element)128   private Optional<ImmutableSet<Scope>> getScopesWithMetadata(XElement element) {
129     Optional<XAnnotation> scopeMetadata = getScopeMetadata(element);
130     if (!scopeMetadata.isPresent()) {
131       return Optional.empty();
132     }
133     String scopeName = scopeMetadata.get().getAsString("value");
134     if (scopeName.isEmpty()) {
135       return Optional.of(ImmutableSet.of());
136     }
137     ImmutableList<XAnnotation> scopeAnnotations =
138         element.getAllAnnotations().stream()
139             .filter(
140                 annotation ->
141                     scopeName.contentEquals(
142                         annotation.getType().getTypeElement().getQualifiedName()))
143             .collect(toImmutableList());
144     checkState(
145         scopeAnnotations.size() == 1,
146         "Expected %s to have a scope annotation for %s but found: %s",
147         ElementFormatter.elementToString(element),
148         scopeName,
149         scopeAnnotations.stream().map(XAnnotations::toStableString).collect(toImmutableList()));
150     XAnnotation scopeAnnotation = getOnlyElement(scopeAnnotations);
151     // Do superficial validation before we convert to a Scope, otherwise the @Scope annotation may
152     // appear to be missing from the annotation if it's no longer on the classpath.
153     superficialValidation.validateAnnotationTypeOf(element, scopeAnnotation);
154 
155     // If strictSuperficialValidation is disabled, then we fall back to the old behavior where
156     // we may potentially miss a scope rather than report an exception.
157     if (compilerOptions.strictSuperficialValidation()) {
158       return Optional.of(ImmutableSet.of(Scope.scope(DaggerAnnotation.from(scopeAnnotation))));
159     } else {
160       return Scope.isScope(DaggerAnnotation.from(scopeAnnotation))
161           ? Optional.of(ImmutableSet.of(Scope.scope(DaggerAnnotation.from(scopeAnnotation))))
162           : Optional.empty();
163     }
164   }
165 
getScopeMetadata(XElement element)166   private Optional<XAnnotation> getScopeMetadata(XElement element) {
167     return getGeneratedNameForScopeMetadata(element)
168         .flatMap(factoryName -> Optional.ofNullable(processingEnv.findTypeElement(factoryName)))
169         .flatMap(factory -> Optional.ofNullable(factory.getAnnotation(XTypeNames.SCOPE_METADATA)));
170   }
171 
getGeneratedNameForScopeMetadata(XElement element)172   private Optional<XClassName> getGeneratedNameForScopeMetadata(XElement element) {
173     // Currently, we only support ScopeMetadata for inject-constructor types and provides methods.
174     if (isTypeElement(element)) {
175       return asTypeElement(element).getConstructors().stream()
176           .filter(InjectionAnnotations::hasInjectOrAssistedInjectAnnotation)
177           .findFirst()
178           .map(SourceFiles::factoryNameForElement);
179     } else if (isMethod(element) && element.hasAnnotation(TypeNames.PROVIDES)) {
180       return Optional.of(factoryNameForElement(asMethod(element)));
181     }
182     return Optional.empty();
183   }
184 
185   /**
186    * Returns the qualifier on the given element if it exists.
187    *
188    * <p>The {@code QualifierMetadata} is used to avoid superficial validation on unnecessary
189    * annotations. If the {@code QualifierMetadata} does not exist, then all annotations must be
190    * superficially validated before we can determine if they are qualifiers or not.
191    *
192    * @throws IllegalArgumentException if the given element has more than one qualifier.
193    */
getQualifier(XElement element)194   public Optional<XAnnotation> getQualifier(XElement element) {
195     checkNotNull(element);
196     ImmutableSet<XAnnotation> qualifierAnnotations = getQualifiers(element);
197     switch (qualifierAnnotations.size()) {
198       case 0:
199         return Optional.empty();
200       case 1:
201         return Optional.of(getOnlyElement(qualifierAnnotations));
202       default:
203         throw new IllegalArgumentException(
204             element + " was annotated with more than one @Qualifier annotation");
205     }
206   }
207 
208   /*
209    * Returns the qualifiers on the given element, or an empty set if none exist.
210    *
211    * <p>The {@code QualifierMetadata} is used to avoid superficial validation on unnecessary
212    * annotations. If the {@code QualifierMetadata} does not exist, then all annotations must be
213    * superficially validated before we can determine if they are qualifiers or not.
214    */
getQualifiers(XElement element)215   public ImmutableSet<XAnnotation> getQualifiers(XElement element) {
216     superficialValidation.validateTypeOf(element);
217     ImmutableSet<XAnnotation> qualifiers =
218         getQualifiersWithMetadata(element)
219             .orElseGet(() -> getQualifiersWithoutMetadata(element));
220 
221     if (isField(element)) {
222       XFieldElement field = asField(element);
223       // static/top-level injected fields are not supported,
224       // no need to get qualifier from kotlin metadata
225       if (!field.isStatic()
226           && isTypeElement(field.getEnclosingElement())
227           && hasInjectAnnotation(field)
228           && kotlinMetadataUtil.hasMetadata(field)) {
229         qualifiers =
230             Stream.concat(qualifiers.stream(), getQualifiersForKotlinProperty(field).stream())
231                 .map(DaggerAnnotation::from) // Wrap in DaggerAnnotation to deduplicate
232                 .distinct()
233                 .map(DaggerAnnotation::xprocessing)
234                 .collect(toImmutableSet());
235       }
236     }
237 
238     // Fully validate each qualifier to ensure its values are also valid.
239     qualifiers.forEach(qualifier -> superficialValidation.validateAnnotationOf(element, qualifier));
240 
241     return qualifiers;
242   }
243 
getQualifiersWithoutMetadata(XElement element)244   private ImmutableSet<XAnnotation> getQualifiersWithoutMetadata(XElement element) {
245     // Validate the annotation types before we check for @Qualifier, otherwise the
246     // @Qualifier annotation may appear to be missing (b/213880825).
247     superficialValidation.validateAnnotationTypesOf(element);
248     return element.getAllAnnotations().stream()
249         .filter(InjectionAnnotations::hasQualifierAnnotation)
250         .collect(toImmutableSet());
251   }
252 
getQualifiersWithMetadata(XElement element)253   private Optional<ImmutableSet<XAnnotation>> getQualifiersWithMetadata(XElement element) {
254     Optional<XAnnotation> qualifierMetadata = getQualifierMetadata(element);
255     if (!qualifierMetadata.isPresent()) {
256       return Optional.empty();
257     }
258     ImmutableSet<String> qualifierNames =
259         ImmutableSet.copyOf(qualifierMetadata.get().getAsStringList("value"));
260     if (qualifierNames.isEmpty()) {
261       return Optional.of(ImmutableSet.of());
262     }
263     ImmutableSet<XAnnotation> qualifierAnnotations =
264         element.getAllAnnotations().stream()
265             .filter(
266                 annotation ->
267                     qualifierNames.contains(
268                         annotation.getType().getTypeElement().getQualifiedName()))
269             .collect(toImmutableSet());
270     if (qualifierAnnotations.isEmpty()) {
271       return Optional.of(ImmutableSet.of());
272     }
273     // We should be guaranteed that there's exactly one qualifier since the existance of
274     // @QualifierMetadata means that this element has already been processed and multiple
275     // qualifiers would have been caught already.
276     XAnnotation qualifierAnnotation = getOnlyElement(qualifierAnnotations);
277 
278     // Ensure the annotation type is superficially valid before we check for @Qualifier, otherwise
279     // the @Qualifier marker may appear to be missing from the annotation (b/213880825).
280     superficialValidation.validateAnnotationTypeOf(element, qualifierAnnotation);
281     if (compilerOptions.strictSuperficialValidation()) {
282       return Optional.of(ImmutableSet.of(qualifierAnnotation));
283     } else {
284       // If strictSuperficialValidation is disabled, then we fall back to the old behavior where
285       // we may potentially miss a qualifier rather than report an exception.
286       return hasQualifierAnnotation(qualifierAnnotation)
287           ? Optional.of(ImmutableSet.of(qualifierAnnotation))
288           : Optional.empty();
289     }
290   }
291 
292   /**
293    * Returns {@code QualifierMetadata} annotation.
294    *
295    * <p>Currently, {@code QualifierMetadata} is only associated with inject constructor parameters,
296    * inject fields, inject method parameters, provide methods, and provide method parameters.
297    */
getQualifierMetadata(XElement element)298   private Optional<XAnnotation> getQualifierMetadata(XElement element) {
299     return getGeneratedNameForQualifierMetadata(element)
300         .flatMap(name -> Optional.ofNullable(processingEnv.findTypeElement(name)))
301         .flatMap(type -> Optional.ofNullable(type.getAnnotation(XTypeNames.QUALIFIER_METADATA)));
302   }
303 
getGeneratedNameForQualifierMetadata(XElement element)304   private Optional<XClassName> getGeneratedNameForQualifierMetadata(XElement element) {
305     // Currently we only support @QualifierMetadata for @Inject fields, @Inject method parameters,
306     // @Inject constructor parameters, @Provides methods, and @Provides method parameters.
307     if (isField(element) && hasInjectAnnotation(element)) {
308       return Optional.of(membersInjectorNameForType(closestEnclosingTypeElement(element)));
309     } else if (isMethod(element) && element.hasAnnotation(TypeNames.PROVIDES)) {
310       return Optional.of(factoryNameForElement(asMethod(element)));
311     } else if (isMethodParameter(element)) {
312       XExecutableElement executableElement = asMethodParameter(element).getEnclosingElement();
313       if (isConstructor(executableElement)
314           && hasInjectOrAssistedInjectAnnotation(executableElement)) {
315         return Optional.of(factoryNameForElement(executableElement));
316       }
317       if (isMethod(executableElement) && hasInjectAnnotation(executableElement)) {
318         return Optional.of(membersInjectorNameForType(closestEnclosingTypeElement(element)));
319       }
320       if (isMethod(executableElement) && executableElement.hasAnnotation(TypeNames.PROVIDES)) {
321         return Optional.of(factoryNameForElement(executableElement));
322       }
323     }
324     return Optional.empty();
325   }
326 
327   /** Returns the constructors in {@code type} that are annotated with {@link Inject}. */
injectedConstructors(XTypeElement type)328   public static ImmutableSet<XConstructorElement> injectedConstructors(XTypeElement type) {
329     return type.getConstructors().stream()
330         .filter(InjectionAnnotations::hasInjectAnnotation)
331         .collect(toImmutableSet());
332   }
333 
hasQualifierAnnotation(XAnnotation annotation)334   private static boolean hasQualifierAnnotation(XAnnotation annotation) {
335     return annotation
336         .getType()
337         .getTypeElement()
338         .hasAnyAnnotation(TypeNames.QUALIFIER, TypeNames.QUALIFIER_JAVAX);
339   }
340 
hasScopeAnnotation(XAnnotation annotation)341   private static boolean hasScopeAnnotation(XAnnotation annotation) {
342     return annotation
343         .getType()
344         .getTypeElement()
345         .hasAnyAnnotation(TypeNames.SCOPE, TypeNames.SCOPE_JAVAX);
346   }
347 
348   /** Returns true if the given element is annotated with {@link Inject}. */
hasInjectAnnotation(XElement element)349   public static boolean hasInjectAnnotation(XElement element) {
350     return element.hasAnyAnnotation(TypeNames.INJECT, TypeNames.INJECT_JAVAX);
351   }
352 
hasInjectOrAssistedInjectAnnotation(XElement element)353   private static boolean hasInjectOrAssistedInjectAnnotation(XElement element) {
354     return element.hasAnyAnnotation(
355         TypeNames.INJECT, TypeNames.INJECT_JAVAX, TypeNames.ASSISTED_INJECT);
356   }
357 
358   /**
359    * Gets the qualifiers annotation of a Kotlin Property. Finding these annotations involve finding
360    * the synthetic method for annotations as described by the Kotlin metadata or finding the
361    * corresponding MembersInjector method for the field, which also contains the qualifier
362    * annotation.
363    */
getQualifiersForKotlinProperty(XFieldElement field)364   private ImmutableSet<XAnnotation> getQualifiersForKotlinProperty(XFieldElement field) {
365     // TODO(bcorso): Consider moving this to KotlinMetadataUtil
366     if (kotlinMetadataUtil.isMissingSyntheticPropertyForAnnotations(field)) {
367       // If we detect that the synthetic method for annotations is missing, possibly due to the
368       // element being from a compiled class, then find the MembersInjector that was generated
369       // for the enclosing class and extract the qualifier information from it.
370       XTypeElement membersInjector =
371           processingEnv.findTypeElement(
372               membersInjectorNameForType(asTypeElement(field.getEnclosingElement())));
373       if (membersInjector != null) {
374         String memberInjectedFieldSignature = memberInjectedFieldSignatureForVariable(field);
375         // TODO(danysantiago): We have to iterate over all the injection methods for every qualifier
376         //  look up. Making this N^2 when looking through all the injected fields. :(
377         return membersInjector.getDeclaredMethods().stream()
378             .filter(
379                 method ->
380                     Optional.ofNullable(method.getAnnotation(TypeNames.INJECTED_FIELD_SIGNATURE))
381                         .map(annotation -> annotation.getAsString("value"))
382                         .map(memberInjectedFieldSignature::equals)
383                         // If a method is not an @InjectedFieldSignature method then filter it out
384                         .orElse(false))
385             .collect(toOptional())
386             .map(this::getQualifiers)
387             .orElseThrow(
388                 () ->
389                     new IllegalStateException(
390                         String.format(
391                             "No matching InjectedFieldSignature for %1$s. This likely means that "
392                                 + "%1$s was compiled with an older, incompatible version of "
393                                 + "Dagger. Please update all Dagger dependencies to the same "
394                                 + "version.",
395                             memberInjectedFieldSignature)));
396       } else {
397         throw new IllegalStateException(
398             "No MembersInjector found for " + field.getEnclosingElement());
399       }
400     } else {
401       return Stream.concat(
402               kotlinMetadataUtil
403                   .getSyntheticPropertyAnnotations(field, TypeNames.QUALIFIER)
404                   .stream(),
405               kotlinMetadataUtil
406                   .getSyntheticPropertyAnnotations(field, TypeNames.QUALIFIER_JAVAX)
407                   .stream())
408           .collect(toImmutableSet());
409     }
410   }
411 }
412