• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.android.processor;
18 
19 import androidx.room.compiler.processing.JavaPoetExtKt;
20 import androidx.room.compiler.processing.XAnnotation;
21 import androidx.room.compiler.processing.XAnnotationValue;
22 import androidx.room.compiler.processing.XElement;
23 import androidx.room.compiler.processing.XExecutableElement;
24 import androidx.room.compiler.processing.XMessager;
25 import androidx.room.compiler.processing.XMethodElement;
26 import androidx.room.compiler.processing.XType;
27 import androidx.room.compiler.processing.XTypeElement;
28 import com.google.auto.value.AutoValue;
29 import com.google.common.collect.ImmutableList;
30 import com.google.common.collect.ImmutableSet;
31 import com.google.common.collect.Sets;
32 import com.squareup.javapoet.AnnotationSpec;
33 import com.squareup.javapoet.ClassName;
34 import dagger.internal.codegen.xprocessing.XElements;
35 import java.util.Optional;
36 import javax.tools.Diagnostic.Kind;
37 
38 /**
39  * A descriptor of a generated {@link dagger.Module} and {@link dagger.Subcomponent} to be generated
40  * from a {@code ContributesAndroidInjector} method.
41  */
42 @AutoValue
43 abstract class AndroidInjectorDescriptor {
44   /** The type to be injected; the return type of the {@code ContributesAndroidInjector} method. */
injectedType()45   abstract ClassName injectedType();
46 
47   /** Scopes to apply to the generated {@link dagger.Subcomponent}. */
scopes()48   abstract ImmutableSet<AnnotationSpec> scopes();
49 
50   /** See {@code ContributesAndroidInjector#modules()} */
modules()51   abstract ImmutableSet<ClassName> modules();
52 
53   /** The {@link dagger.Module} that contains the {@code ContributesAndroidInjector} method. */
enclosingModule()54   abstract ClassName enclosingModule();
55 
56   /** The method annotated with {@code ContributesAndroidInjector}. */
method()57   abstract XExecutableElement method();
58 
59   @AutoValue.Builder
60   abstract static class Builder {
injectedType(ClassName injectedType)61     abstract Builder injectedType(ClassName injectedType);
62 
scopesBuilder()63     abstract ImmutableSet.Builder<AnnotationSpec> scopesBuilder();
64 
modulesBuilder()65     abstract ImmutableSet.Builder<ClassName> modulesBuilder();
66 
enclosingModule(ClassName enclosingModule)67     abstract Builder enclosingModule(ClassName enclosingModule);
68 
method(XExecutableElement method)69     abstract Builder method(XExecutableElement method);
70 
build()71     abstract AndroidInjectorDescriptor build();
72   }
73 
74   static final class Validator {
75     private final XMessager messager;
76 
Validator(XMessager messager)77     Validator(XMessager messager) {
78       this.messager = messager;
79     }
80 
81     /**
82      * Validates a {@code ContributesAndroidInjector} method, returning an {@link
83      * AndroidInjectorDescriptor} if it is valid, or {@link Optional#empty()} otherwise.
84      */
createIfValid(XMethodElement method)85     Optional<AndroidInjectorDescriptor> createIfValid(XMethodElement method) {
86       ErrorReporter reporter = new ErrorReporter(method, messager);
87 
88       if (!method.isAbstract()) {
89         reporter.reportError("@ContributesAndroidInjector methods must be abstract");
90       }
91 
92       if (!method.getParameters().isEmpty()) {
93         reporter.reportError("@ContributesAndroidInjector methods cannot have parameters");
94       }
95 
96       AndroidInjectorDescriptor.Builder builder =
97           new AutoValue_AndroidInjectorDescriptor.Builder().method(method);
98       XTypeElement enclosingElement = XElements.asTypeElement(method.getEnclosingElement());
99       if (!enclosingElement.hasAnnotation(TypeNames.MODULE)) {
100         reporter.reportError("@ContributesAndroidInjector methods must be in a @Module");
101       }
102       builder.enclosingModule(enclosingElement.getClassName());
103 
104       XType injectedType = method.getReturnType();
105       if (injectedType.getTypeArguments().isEmpty()) {
106         builder.injectedType(injectedType.getTypeElement().getClassName());
107       } else {
108         reporter.reportError(
109             "@ContributesAndroidInjector methods cannot return parameterized types");
110       }
111 
112       XAnnotation annotation = method.getAnnotation(TypeNames.CONTRIBUTES_ANDROID_INJECTOR);
113       for (XType module : getTypeList(annotation.getAnnotationValue("modules"))) {
114         if (module.getTypeElement().hasAnnotation(TypeNames.MODULE)) {
115           builder.modulesBuilder().add((ClassName) module.getTypeName());
116         } else {
117           reporter.reportError(String.format("%s is not a @Module", module), annotation);
118         }
119       }
120 
121       for (XAnnotation scope :
122           Sets.union(
123               method.getAnnotationsAnnotatedWith(TypeNames.SCOPE),
124               method.getAnnotationsAnnotatedWith(TypeNames.SCOPE_JAVAX))) {
125         builder.scopesBuilder().add(JavaPoetExtKt.toAnnotationSpec(scope));
126       }
127 
128       for (XAnnotation qualifier :
129           Sets.union(
130               method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER),
131               method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER_JAVAX))) {
132         reporter.reportError(
133             "@ContributesAndroidInjector methods cannot have qualifiers", qualifier);
134       }
135 
136       return reporter.hasError ? Optional.empty() : Optional.of(builder.build());
137     }
138 
getTypeList(XAnnotationValue annotationValue)139     private static ImmutableList<XType> getTypeList(XAnnotationValue annotationValue) {
140       if (annotationValue.hasTypeListValue()) {
141         return ImmutableList.copyOf(annotationValue.asTypeList());
142       }
143       if (annotationValue.hasTypeValue()) {
144         return ImmutableList.of(annotationValue.asType());
145       }
146       throw new IllegalArgumentException("Does not have type list");
147     }
148 
149     // TODO(ronshapiro): use ValidationReport once it is moved out of the compiler
150     private static class ErrorReporter {
151       private final XElement subject;
152       private final XMessager messager;
153       private boolean hasError;
154 
ErrorReporter(XElement subject, XMessager messager)155       ErrorReporter(XElement subject, XMessager messager) {
156         this.subject = subject;
157         this.messager = messager;
158       }
159 
reportError(String error)160       void reportError(String error) {
161         hasError = true;
162         messager.printMessage(Kind.ERROR, error, subject);
163       }
164 
reportError(String error, XAnnotation annotation)165       void reportError(String error, XAnnotation annotation) {
166         hasError = true;
167         messager.printMessage(Kind.ERROR, error, subject, annotation);
168       }
169     }
170   }
171 }
172