blob: 5185c4b5e2df73147a7d208c117107090ef8b3fb [file] [log] [blame]
ronshapirobff6fd62017-11-07 11:18:42 -08001/*
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
17package dagger.internal.codegen;
18
19import static com.google.auto.common.MoreElements.isAnnotationPresent;
20import static com.google.common.base.CaseFormat.LOWER_CAMEL;
21import static com.google.common.base.CaseFormat.UPPER_CAMEL;
22import static com.google.common.base.Preconditions.checkArgument;
23import static com.squareup.javapoet.MethodSpec.constructorBuilder;
24import static dagger.internal.codegen.ComponentGenerator.componentName;
dpb59ffce22017-11-07 13:13:56 -080025import static dagger.internal.codegen.ComponentProcessingStep.getElementsFromAnnotations;
ronshapirobff6fd62017-11-07 11:18:42 -080026import static dagger.internal.codegen.TypeSpecs.addSupertype;
27import static javax.lang.model.element.Modifier.ABSTRACT;
28import static javax.lang.model.element.Modifier.FINAL;
29import static javax.lang.model.element.Modifier.PRIVATE;
30import static javax.lang.model.element.Modifier.PUBLIC;
31import static javax.lang.model.element.Modifier.STATIC;
32import static javax.lang.model.util.ElementFilter.methodsIn;
33
34import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep;
35import com.google.auto.common.MoreElements;
36import com.google.auto.common.MoreTypes;
37import com.google.common.collect.ImmutableSet;
38import com.google.common.collect.SetMultimap;
39import com.google.common.collect.Sets;
40import com.squareup.javapoet.ClassName;
41import com.squareup.javapoet.MethodSpec;
42import com.squareup.javapoet.TypeName;
43import com.squareup.javapoet.TypeSpec;
44import dagger.BindsInstance;
dpb59ffce22017-11-07 13:13:56 -080045import dagger.Component;
ronshapirobff6fd62017-11-07 11:18:42 -080046import dagger.internal.codegen.ComponentDescriptor.Factory;
47import dagger.internal.codegen.ComponentValidator.ComponentValidationReport;
dpb59ffce22017-11-07 13:13:56 -080048import dagger.producers.ProductionComponent;
ronshapirobff6fd62017-11-07 11:18:42 -080049import java.lang.annotation.Annotation;
50import java.util.Optional;
51import java.util.Set;
52import java.util.stream.Stream;
53import javax.annotation.processing.Filer;
54import javax.annotation.processing.Messager;
ronshapiroed0d8522018-01-04 12:01:06 -080055import javax.inject.Inject;
cushon41a67552018-01-26 14:08:56 -080056import javax.lang.model.SourceVersion;
ronshapirobff6fd62017-11-07 11:18:42 -080057import javax.lang.model.element.Element;
58import javax.lang.model.element.ExecutableElement;
59import javax.lang.model.element.TypeElement;
60import javax.lang.model.type.DeclaredType;
ronshapirobff6fd62017-11-07 11:18:42 -080061import javax.lang.model.util.Elements;
62import javax.lang.model.util.Types;
63
64/**
65 * A processing step that emits the API of a generated component, without any actual implementation.
66 *
67 * <p>When compiling a header jar (hjar), Bazel needs to run annotation processors that generate
68 * API, like Dagger, to see what code they might output. Full {@link BindingGraph} analysis is
69 * costly and unnecessary from the perspective of the header compiler; it's sole goal is to pass
70 * along a slimmed down version of what will be the jar for a particular compilation, whether or not
71 * that compilation succeeds. If it does not, the compilation pipeline will fail, even if header
72 * compilation succeeded.
73 *
74 * <p>The components emitted by this processing step include all of the API elements exposed by the
75 * normal {@link AbstractComponentWriter}. Method bodies are omitted as Turbine ignores them
76 * entirely.
77 */
78final class ComponentHjarProcessingStep implements ProcessingStep {
ronshapirobff6fd62017-11-07 11:18:42 -080079 private final Elements elements;
cushon41a67552018-01-26 14:08:56 -080080 private final SourceVersion sourceVersion;
ronshapirobff6fd62017-11-07 11:18:42 -080081 private final Types types;
82 private final Filer filer;
83 private final Messager messager;
84 private final ComponentValidator componentValidator;
85 private final ComponentDescriptor.Factory componentDescriptorFactory;
86
ronshapiroed0d8522018-01-04 12:01:06 -080087 @Inject
ronshapirobff6fd62017-11-07 11:18:42 -080088 ComponentHjarProcessingStep(
ronshapirobff6fd62017-11-07 11:18:42 -080089 Elements elements,
cushon41a67552018-01-26 14:08:56 -080090 SourceVersion sourceVersion,
ronshapirobff6fd62017-11-07 11:18:42 -080091 Types types,
92 Filer filer,
93 Messager messager,
94 ComponentValidator componentValidator,
95 Factory componentDescriptorFactory) {
ronshapirobff6fd62017-11-07 11:18:42 -080096 this.elements = elements;
cushon41a67552018-01-26 14:08:56 -080097 this.sourceVersion = sourceVersion;
ronshapirobff6fd62017-11-07 11:18:42 -080098 this.types = types;
99 this.filer = filer;
100 this.messager = messager;
101 this.componentValidator = componentValidator;
102 this.componentDescriptorFactory = componentDescriptorFactory;
103 }
104
105 @Override
dpb59ffce22017-11-07 13:13:56 -0800106 public Set<Class<? extends Annotation>> annotations() {
107 return ImmutableSet.of(Component.class, ProductionComponent.class);
ronshapirobff6fd62017-11-07 11:18:42 -0800108 }
109
110 @Override
dpb59ffce22017-11-07 13:13:56 -0800111 public ImmutableSet<Element> process(
ronshapirobff6fd62017-11-07 11:18:42 -0800112 SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
113 ImmutableSet.Builder<Element> rejectedElements = ImmutableSet.builder();
114
dpb59ffce22017-11-07 13:13:56 -0800115 ImmutableSet<Element> componentElements =
116 getElementsFromAnnotations(
117 elementsByAnnotation, Component.class, ProductionComponent.class);
118
119 for (Element element : componentElements) {
ronshapirobff6fd62017-11-07 11:18:42 -0800120 TypeElement componentTypeElement = MoreElements.asType(element);
121 try {
122 // TODO(ronshapiro): component validation might not be necessary. We should measure it and
123 // figure out if it's worth seeing if removing it will still work. We could potentially
124 // add a new catch clause for any exception that's not TypeNotPresentException and ignore
125 // the component entirely in that case.
126 ComponentValidationReport validationReport =
127 componentValidator.validate(componentTypeElement, ImmutableSet.of(), ImmutableSet.of());
128 validationReport.report().printMessagesTo(messager);
129 if (validationReport.report().isClean()) {
cushon41a67552018-01-26 14:08:56 -0800130 new EmptyComponentGenerator(filer, elements, sourceVersion)
ronshapirobff6fd62017-11-07 11:18:42 -0800131 .generate(componentDescriptorFactory.forComponent(componentTypeElement), messager);
132 }
133 } catch (TypeNotPresentException e) {
134 rejectedElements.add(componentTypeElement);
135 }
136 }
137 return rejectedElements.build();
138 }
139
140 private final class EmptyComponentGenerator extends SourceFileGenerator<ComponentDescriptor> {
cushon41a67552018-01-26 14:08:56 -0800141 EmptyComponentGenerator(Filer filer, Elements elements, SourceVersion sourceVersion) {
142 super(filer, elements, sourceVersion);
ronshapirobff6fd62017-11-07 11:18:42 -0800143 }
144
145 @Override
146 ClassName nameGeneratedType(ComponentDescriptor input) {
147 return componentName(input.componentDefinitionType());
148 }
149
150 @Override
151 Optional<? extends Element> getElementForErrorReporting(ComponentDescriptor input) {
152 return Optional.of(input.componentDefinitionType());
153 }
154
155 @Override
156 Optional<TypeSpec.Builder> write(
157 ClassName generatedTypeName, ComponentDescriptor componentDescriptor) {
158 TypeSpec.Builder generatedComponent =
159 TypeSpec.classBuilder(generatedTypeName)
160 .addModifiers(PUBLIC, FINAL)
161 .addMethod(privateConstructor());
dpb02db2132018-01-08 07:20:23 -0800162 TypeElement componentElement = componentDescriptor.componentDefinitionType();
163 addSupertype(generatedComponent, componentElement);
ronshapirobff6fd62017-11-07 11:18:42 -0800164
165 TypeName builderMethodReturnType;
166 if (componentDescriptor.builderSpec().isPresent()) {
167 builderMethodReturnType =
168 ClassName.get(componentDescriptor.builderSpec().get().builderDefinitionType());
169 } else {
170 TypeSpec.Builder builder =
171 TypeSpec.classBuilder("Builder")
172 .addModifiers(PUBLIC, STATIC, FINAL)
173 .addMethod(privateConstructor());
174 ClassName builderClassName = generatedTypeName.nestedClass("Builder");
175 builderMethodReturnType = builderClassName;
176 componentRequirements(componentDescriptor)
177 .map(requirement -> builderInstanceMethod(requirement.typeElement(), builderClassName))
178 .forEach(builder::addMethod);
179 builder.addMethod(builderBuildMethod(componentDescriptor));
180 generatedComponent.addType(builder.build());
181 }
182
183 generatedComponent.addMethod(staticBuilderMethod(builderMethodReturnType));
184
185 if (componentRequirements(componentDescriptor)
186 .noneMatch(requirement -> requirement.requiresAPassedInstance(elements, types))
187 && !hasBindsInstanceMethods(componentDescriptor)) {
188 generatedComponent.addMethod(createMethod(componentDescriptor));
189 }
190
dpb02db2132018-01-08 07:20:23 -0800191 DeclaredType componentType = MoreTypes.asDeclared(componentElement.asType());
ronshapirobff6fd62017-11-07 11:18:42 -0800192 // TODO(ronshapiro): unify with AbstractComponentWriter
193 Set<MethodSignature> methodSignatures =
194 Sets.newHashSetWithExpectedSize(componentDescriptor.componentMethods().size());
195 componentDescriptor
196 .componentMethods()
197 .stream()
dpb02db2132018-01-08 07:20:23 -0800198 .filter(
199 method -> {
200 return methodSignatures.add(
201 MethodSignature.forComponentMethod(method, componentType, types));
202 })
ronshapirobff6fd62017-11-07 11:18:42 -0800203 .forEach(
204 method ->
205 generatedComponent.addMethod(
dpb02db2132018-01-08 07:20:23 -0800206 emptyComponentMethod(componentElement, method.methodElement())));
ronshapirobff6fd62017-11-07 11:18:42 -0800207
208 return Optional.of(generatedComponent);
209 }
210 }
211
ronshapirobff6fd62017-11-07 11:18:42 -0800212 private MethodSpec emptyComponentMethod(TypeElement typeElement, ExecutableElement baseMethod) {
213 return MethodSpec.overriding(baseMethod, MoreTypes.asDeclared(typeElement.asType()), types)
214 .build();
215 }
216
217 private MethodSpec privateConstructor() {
218 return constructorBuilder().addModifiers(PRIVATE).build();
219 }
220
221 /**
222 * Returns the {@link ComponentRequirement}s for a component that does not have a {@link
223 * ComponentDescriptor#builderSpec()}.
224 */
225 private Stream<ComponentRequirement> componentRequirements(ComponentDescriptor component) {
226 checkArgument(component.kind().isTopLevel());
227 return Stream.concat(
dpbbe0e7d32017-12-20 08:27:48 -0800228 component.dependencies().stream(),
ronshapirobff6fd62017-11-07 11:18:42 -0800229 component
230 .transitiveModules()
231 .stream()
232 .filter(module -> !module.moduleElement().getModifiers().contains(ABSTRACT))
233 .map(module -> ComponentRequirement.forModule(module.moduleElement().asType())));
234 }
235
236 private boolean hasBindsInstanceMethods(ComponentDescriptor componentDescriptor) {
237 return componentDescriptor.builderSpec().isPresent()
238 && methodsIn(
239 elements.getAllMembers(
240 componentDescriptor.builderSpec().get().builderDefinitionType()))
241 .stream()
242 .anyMatch(method -> isAnnotationPresent(method, BindsInstance.class));
243 }
244
245 private MethodSpec builderInstanceMethod(
246 TypeElement componentRequirement, ClassName builderClass) {
247 String simpleName =
248 UPPER_CAMEL.to(LOWER_CAMEL, componentRequirement.getSimpleName().toString());
249 return MethodSpec.methodBuilder(simpleName)
250 .addModifiers(PUBLIC)
251 .addParameter(ClassName.get(componentRequirement), simpleName)
252 .returns(builderClass)
253 .build();
254 }
255
256 private MethodSpec builderBuildMethod(ComponentDescriptor component) {
257 return MethodSpec.methodBuilder("build")
258 .addModifiers(PUBLIC)
259 .returns(ClassName.get(component.componentDefinitionType()))
260 .build();
261 }
262
263 private MethodSpec staticBuilderMethod(TypeName builderMethodReturnType) {
264 return MethodSpec.methodBuilder("builder")
265 .addModifiers(PUBLIC, STATIC)
266 .returns(builderMethodReturnType)
267 .build();
268 }
269
270 private MethodSpec createMethod(ComponentDescriptor componentDescriptor) {
271 return MethodSpec.methodBuilder("create")
272 .addModifiers(PUBLIC, STATIC)
273 .returns(ClassName.get(componentDescriptor.componentDefinitionType()))
274 .build();
275 }
276}