• 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.componentgenerator;
18 
19 import static com.google.common.base.CaseFormat.LOWER_CAMEL;
20 import static com.google.common.base.CaseFormat.UPPER_CAMEL;
21 import static com.google.common.base.Preconditions.checkArgument;
22 import static com.google.common.collect.Iterables.getOnlyElement;
23 import static com.squareup.javapoet.MethodSpec.constructorBuilder;
24 import static dagger.internal.codegen.base.ComponentCreatorKind.BUILDER;
25 import static dagger.internal.codegen.javapoet.TypeSpecs.addSupertype;
26 import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom;
27 import static dagger.internal.codegen.writing.ComponentNames.getTopLevelClassName;
28 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
29 import static dagger.internal.codegen.xprocessing.XTypeElements.getAllUnimplementedMethods;
30 import static javax.lang.model.element.Modifier.FINAL;
31 import static javax.lang.model.element.Modifier.PRIVATE;
32 import static javax.lang.model.element.Modifier.PUBLIC;
33 import static javax.lang.model.element.Modifier.STATIC;
34 
35 import androidx.room.compiler.processing.XElement;
36 import androidx.room.compiler.processing.XFiler;
37 import androidx.room.compiler.processing.XMethodElement;
38 import androidx.room.compiler.processing.XProcessingEnv;
39 import androidx.room.compiler.processing.XType;
40 import androidx.room.compiler.processing.XTypeElement;
41 import com.google.common.base.Ascii;
42 import com.google.common.collect.ImmutableList;
43 import com.google.common.collect.Sets;
44 import com.squareup.javapoet.ClassName;
45 import com.squareup.javapoet.MethodSpec;
46 import com.squareup.javapoet.TypeName;
47 import com.squareup.javapoet.TypeSpec;
48 import dagger.internal.codegen.base.ComponentCreatorKind;
49 import dagger.internal.codegen.base.SourceFileGenerator;
50 import dagger.internal.codegen.binding.ComponentCreatorDescriptor;
51 import dagger.internal.codegen.binding.ComponentDescriptor;
52 import dagger.internal.codegen.binding.ComponentRequirement;
53 import dagger.internal.codegen.binding.MethodSignature;
54 import dagger.internal.codegen.compileroption.CompilerOptions;
55 import dagger.internal.codegen.javapoet.TypeNames;
56 import dagger.internal.codegen.xprocessing.MethodSpecs;
57 import java.util.Set;
58 import java.util.stream.Stream;
59 import javax.inject.Inject;
60 
61 /**
62  * A component generator that emits only API, without any actual implementation.
63  *
64  * <p>When compiling a header jar (hjar), Bazel needs to run annotation processors that generate
65  * API, like Dagger, to see what code they might output. Full binding graph analysis is costly and
66  * unnecessary from the perspective of the header compiler; it's sole goal is to pass along a
67  * slimmed down version of what will be the jar for a particular compilation, whether or not that
68  * compilation succeeds. If it does not, the compilation pipeline will fail, even if header
69  * compilation succeeded.
70  *
71  * <p>The components emitted by this processing step include all of the API elements exposed by the
72  * normal step. Method bodies are omitted as Turbine ignores them entirely.
73  */
74 final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescriptor> {
75   private final XProcessingEnv processingEnv;
76   private final CompilerOptions compilerOptions;
77 
78   @Inject
ComponentHjarGenerator( XFiler filer, XProcessingEnv processingEnv, CompilerOptions compilerOptions)79   ComponentHjarGenerator(
80       XFiler filer, XProcessingEnv processingEnv, CompilerOptions compilerOptions) {
81     super(filer, processingEnv);
82     this.processingEnv = processingEnv;
83     this.compilerOptions = compilerOptions;
84   }
85 
86   @Override
originatingElement(ComponentDescriptor input)87   public XElement originatingElement(ComponentDescriptor input) {
88     return input.typeElement();
89   }
90 
91   @Override
topLevelTypes(ComponentDescriptor componentDescriptor)92   public ImmutableList<TypeSpec.Builder> topLevelTypes(ComponentDescriptor componentDescriptor) {
93     ClassName generatedTypeName = getTopLevelClassName(componentDescriptor);
94     TypeSpec.Builder generatedComponent =
95         TypeSpec.classBuilder(generatedTypeName)
96             .addModifiers(FINAL)
97             .addMethod(privateConstructor());
98     if (componentDescriptor.typeElement().isPublic()) {
99       generatedComponent.addModifiers(PUBLIC);
100     }
101 
102     XTypeElement componentElement = componentDescriptor.typeElement();
103     if (compilerOptions.generatedClassExtendsComponent()) {
104       addSupertype(generatedComponent, componentElement);
105     }
106 
107     TypeName builderMethodReturnType;
108     ComponentCreatorKind creatorKind;
109     boolean noArgFactoryMethod;
110     if (componentDescriptor.creatorDescriptor().isPresent()) {
111       ComponentCreatorDescriptor creatorDescriptor = componentDescriptor.creatorDescriptor().get();
112       builderMethodReturnType = creatorDescriptor.typeElement().getClassName();
113       creatorKind = creatorDescriptor.kind();
114       noArgFactoryMethod = creatorDescriptor.factoryParameters().isEmpty();
115     } else {
116       TypeSpec.Builder builder =
117           TypeSpec.classBuilder("Builder")
118               .addModifiers(STATIC, FINAL)
119               .addMethod(privateConstructor());
120       if (componentDescriptor.typeElement().isPublic()) {
121         builder.addModifiers(PUBLIC);
122       }
123 
124       ClassName builderClassName = generatedTypeName.nestedClass("Builder");
125       builderMethodReturnType = builderClassName;
126       creatorKind = BUILDER;
127       noArgFactoryMethod = true;
128       componentRequirements(componentDescriptor)
129           .map(requirement -> builderSetterMethod(requirement.typeElement(), builderClassName))
130           .forEach(builder::addMethod);
131       builder.addMethod(builderBuildMethod(componentDescriptor));
132       generatedComponent.addType(builder.build());
133     }
134 
135     generatedComponent.addMethod(staticCreatorMethod(builderMethodReturnType, creatorKind));
136 
137     if (noArgFactoryMethod
138         && !hasBindsInstanceMethods(componentDescriptor)
139         && componentRequirements(componentDescriptor)
140             .noneMatch(ComponentRequirement::requiresAPassedInstance)) {
141       generatedComponent.addMethod(createMethod(componentDescriptor));
142     }
143 
144     if (compilerOptions.generatedClassExtendsComponent()) {
145       XType componentType = componentElement.getType();
146       // TODO(ronshapiro): unify with ComponentImplementationBuilder
147       Set<MethodSignature> methodSignatures =
148           Sets.newHashSetWithExpectedSize(componentDescriptor.componentMethods().size());
149       componentDescriptor.componentMethods().stream()
150           .filter(
151               method ->
152                   methodSignatures.add(
153                       MethodSignature.forComponentMethod(method, componentType, processingEnv)))
154           .forEach(
155               method ->
156                   generatedComponent.addMethod(
157                       emptyComponentMethod(componentElement, method.methodElement())));
158 
159       if (componentDescriptor.isProduction()) {
160         generatedComponent
161             .addSuperinterface(TypeNames.CANCELLATION_LISTENER)
162             .addMethod(onProducerFutureCancelledMethod());
163       }
164     }
165 
166     return ImmutableList.of(generatedComponent);
167   }
168 
emptyComponentMethod(XTypeElement typeElement, XMethodElement baseMethod)169   private MethodSpec emptyComponentMethod(XTypeElement typeElement, XMethodElement baseMethod) {
170     return MethodSpecs.overriding(baseMethod, typeElement.getType()).build();
171   }
172 
privateConstructor()173   private static MethodSpec privateConstructor() {
174     return constructorBuilder().addModifiers(PRIVATE).build();
175   }
176 
177   /**
178    * Returns the {@link ComponentRequirement}s for a component that does not have a {@link
179    * ComponentDescriptor#creatorDescriptor()}.
180    */
componentRequirements(ComponentDescriptor component)181   private static Stream<ComponentRequirement> componentRequirements(ComponentDescriptor component) {
182     // TODO(b/152802759): See if you can merge logics that normal component processing and hjar
183     // component processing use. So that there would't be a duplicated logic (like the lines below)
184     // everytime we modify the generated code for the component.
185     checkArgument(!component.isSubcomponent());
186     return Stream.concat(
187         component.dependencies().stream(),
188         component.modules().stream()
189             .filter(
190                 module ->
191                     !module.moduleElement().isAbstract()
192                         && isElementAccessibleFrom(
193                             module.moduleElement(),
194                             component.typeElement().getClassName().packageName()))
195             .map(module -> ComponentRequirement.forModule(module.moduleElement().getType()))
196             // If the user hasn't defined an explicit creator/builder then we need to prune out the
197             // module requirements that don't require a module instance to match the non-hjar
198             // implementation.
199             .filter(
200                 requirement ->
201                     component.creatorDescriptor().isPresent()
202                         || requirement.requiresModuleInstance()));
203   }
204 
hasBindsInstanceMethods(ComponentDescriptor componentDescriptor)205   private boolean hasBindsInstanceMethods(ComponentDescriptor componentDescriptor) {
206     return componentDescriptor.creatorDescriptor().isPresent()
207         && getAllUnimplementedMethods(componentDescriptor.creatorDescriptor().get().typeElement())
208             .stream()
209             .anyMatch(method -> isBindsInstance(method));
210   }
211 
isBindsInstance(XMethodElement method)212   private static boolean isBindsInstance(XMethodElement method) {
213     return method.hasAnnotation(TypeNames.BINDS_INSTANCE)
214         || (method.getParameters().size() == 1
215             && getOnlyElement(method.getParameters()).hasAnnotation(TypeNames.BINDS_INSTANCE));
216   }
217 
builderSetterMethod( XTypeElement componentRequirement, ClassName builderClass)218   private static MethodSpec builderSetterMethod(
219       XTypeElement componentRequirement, ClassName builderClass) {
220     String simpleName = UPPER_CAMEL.to(LOWER_CAMEL, getSimpleName(componentRequirement));
221     return MethodSpec.methodBuilder(simpleName)
222         .addModifiers(PUBLIC)
223         .addParameter(componentRequirement.getClassName(), simpleName)
224         .returns(builderClass)
225         .build();
226   }
227 
builderBuildMethod(ComponentDescriptor component)228   private static MethodSpec builderBuildMethod(ComponentDescriptor component) {
229     return MethodSpec.methodBuilder("build")
230         .addModifiers(PUBLIC)
231         .returns(component.typeElement().getClassName())
232         .build();
233   }
234 
staticCreatorMethod( TypeName creatorMethodReturnType, ComponentCreatorKind creatorKind)235   private static MethodSpec staticCreatorMethod(
236       TypeName creatorMethodReturnType, ComponentCreatorKind creatorKind) {
237     return MethodSpec.methodBuilder(Ascii.toLowerCase(creatorKind.typeName()))
238         .addModifiers(PUBLIC, STATIC)
239         .returns(creatorMethodReturnType)
240         .build();
241   }
242 
createMethod(ComponentDescriptor componentDescriptor)243   private static MethodSpec createMethod(ComponentDescriptor componentDescriptor) {
244     return MethodSpec.methodBuilder("create")
245         .addModifiers(PUBLIC, STATIC)
246         .returns(componentDescriptor.typeElement().getClassName())
247         .build();
248   }
249 
onProducerFutureCancelledMethod()250   private static MethodSpec onProducerFutureCancelledMethod() {
251     return MethodSpec.methodBuilder("onProducerFutureCancelled")
252         .addModifiers(PUBLIC)
253         .addParameter(TypeName.BOOLEAN, "mayInterruptIfRunning")
254         .build();
255   }
256 }
257