| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 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.internal.codegen; |
| 18 | |
| 19 | import static com.google.auto.common.MoreElements.isAnnotationPresent; |
| 20 | import static com.google.common.base.CaseFormat.LOWER_CAMEL; |
| 21 | import static com.google.common.base.CaseFormat.UPPER_CAMEL; |
| 22 | import static com.google.common.base.Preconditions.checkArgument; |
| 23 | import static com.squareup.javapoet.MethodSpec.constructorBuilder; |
| 24 | import static dagger.internal.codegen.ComponentGenerator.componentName; |
| dpb | 59ffce2 | 2017-11-07 13:13:56 -0800 | [diff] [blame] | 25 | import static dagger.internal.codegen.ComponentProcessingStep.getElementsFromAnnotations; |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 26 | import static dagger.internal.codegen.TypeSpecs.addSupertype; |
| 27 | import static javax.lang.model.element.Modifier.ABSTRACT; |
| 28 | import static javax.lang.model.element.Modifier.FINAL; |
| 29 | import static javax.lang.model.element.Modifier.PRIVATE; |
| 30 | import static javax.lang.model.element.Modifier.PUBLIC; |
| 31 | import static javax.lang.model.element.Modifier.STATIC; |
| 32 | import static javax.lang.model.util.ElementFilter.methodsIn; |
| 33 | |
| 34 | import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; |
| 35 | import com.google.auto.common.MoreElements; |
| 36 | import com.google.auto.common.MoreTypes; |
| 37 | import com.google.common.collect.ImmutableSet; |
| 38 | import com.google.common.collect.SetMultimap; |
| 39 | import com.google.common.collect.Sets; |
| 40 | import com.squareup.javapoet.ClassName; |
| 41 | import com.squareup.javapoet.MethodSpec; |
| 42 | import com.squareup.javapoet.TypeName; |
| 43 | import com.squareup.javapoet.TypeSpec; |
| 44 | import dagger.BindsInstance; |
| dpb | 59ffce2 | 2017-11-07 13:13:56 -0800 | [diff] [blame] | 45 | import dagger.Component; |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 46 | import dagger.internal.codegen.ComponentDescriptor.Factory; |
| 47 | import dagger.internal.codegen.ComponentValidator.ComponentValidationReport; |
| dpb | 59ffce2 | 2017-11-07 13:13:56 -0800 | [diff] [blame] | 48 | import dagger.producers.ProductionComponent; |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 49 | import java.lang.annotation.Annotation; |
| 50 | import java.util.Optional; |
| 51 | import java.util.Set; |
| 52 | import java.util.stream.Stream; |
| 53 | import javax.annotation.processing.Filer; |
| 54 | import javax.annotation.processing.Messager; |
| ronshapiro | ed0d852 | 2018-01-04 12:01:06 -0800 | [diff] [blame] | 55 | import javax.inject.Inject; |
| cushon | 41a6755 | 2018-01-26 14:08:56 -0800 | [diff] [blame^] | 56 | import javax.lang.model.SourceVersion; |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 57 | import javax.lang.model.element.Element; |
| 58 | import javax.lang.model.element.ExecutableElement; |
| 59 | import javax.lang.model.element.TypeElement; |
| 60 | import javax.lang.model.type.DeclaredType; |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 61 | import javax.lang.model.util.Elements; |
| 62 | import 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 | */ |
| 78 | final class ComponentHjarProcessingStep implements ProcessingStep { |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 79 | private final Elements elements; |
| cushon | 41a6755 | 2018-01-26 14:08:56 -0800 | [diff] [blame^] | 80 | private final SourceVersion sourceVersion; |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 81 | 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 | |
| ronshapiro | ed0d852 | 2018-01-04 12:01:06 -0800 | [diff] [blame] | 87 | @Inject |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 88 | ComponentHjarProcessingStep( |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 89 | Elements elements, |
| cushon | 41a6755 | 2018-01-26 14:08:56 -0800 | [diff] [blame^] | 90 | SourceVersion sourceVersion, |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 91 | Types types, |
| 92 | Filer filer, |
| 93 | Messager messager, |
| 94 | ComponentValidator componentValidator, |
| 95 | Factory componentDescriptorFactory) { |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 96 | this.elements = elements; |
| cushon | 41a6755 | 2018-01-26 14:08:56 -0800 | [diff] [blame^] | 97 | this.sourceVersion = sourceVersion; |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 98 | this.types = types; |
| 99 | this.filer = filer; |
| 100 | this.messager = messager; |
| 101 | this.componentValidator = componentValidator; |
| 102 | this.componentDescriptorFactory = componentDescriptorFactory; |
| 103 | } |
| 104 | |
| 105 | @Override |
| dpb | 59ffce2 | 2017-11-07 13:13:56 -0800 | [diff] [blame] | 106 | public Set<Class<? extends Annotation>> annotations() { |
| 107 | return ImmutableSet.of(Component.class, ProductionComponent.class); |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 108 | } |
| 109 | |
| 110 | @Override |
| dpb | 59ffce2 | 2017-11-07 13:13:56 -0800 | [diff] [blame] | 111 | public ImmutableSet<Element> process( |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 112 | SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { |
| 113 | ImmutableSet.Builder<Element> rejectedElements = ImmutableSet.builder(); |
| 114 | |
| dpb | 59ffce2 | 2017-11-07 13:13:56 -0800 | [diff] [blame] | 115 | ImmutableSet<Element> componentElements = |
| 116 | getElementsFromAnnotations( |
| 117 | elementsByAnnotation, Component.class, ProductionComponent.class); |
| 118 | |
| 119 | for (Element element : componentElements) { |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 120 | 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()) { |
| cushon | 41a6755 | 2018-01-26 14:08:56 -0800 | [diff] [blame^] | 130 | new EmptyComponentGenerator(filer, elements, sourceVersion) |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 131 | .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> { |
| cushon | 41a6755 | 2018-01-26 14:08:56 -0800 | [diff] [blame^] | 141 | EmptyComponentGenerator(Filer filer, Elements elements, SourceVersion sourceVersion) { |
| 142 | super(filer, elements, sourceVersion); |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 143 | } |
| 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()); |
| dpb | 02db213 | 2018-01-08 07:20:23 -0800 | [diff] [blame] | 162 | TypeElement componentElement = componentDescriptor.componentDefinitionType(); |
| 163 | addSupertype(generatedComponent, componentElement); |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 164 | |
| 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 | |
| dpb | 02db213 | 2018-01-08 07:20:23 -0800 | [diff] [blame] | 191 | DeclaredType componentType = MoreTypes.asDeclared(componentElement.asType()); |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 192 | // TODO(ronshapiro): unify with AbstractComponentWriter |
| 193 | Set<MethodSignature> methodSignatures = |
| 194 | Sets.newHashSetWithExpectedSize(componentDescriptor.componentMethods().size()); |
| 195 | componentDescriptor |
| 196 | .componentMethods() |
| 197 | .stream() |
| dpb | 02db213 | 2018-01-08 07:20:23 -0800 | [diff] [blame] | 198 | .filter( |
| 199 | method -> { |
| 200 | return methodSignatures.add( |
| 201 | MethodSignature.forComponentMethod(method, componentType, types)); |
| 202 | }) |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 203 | .forEach( |
| 204 | method -> |
| 205 | generatedComponent.addMethod( |
| dpb | 02db213 | 2018-01-08 07:20:23 -0800 | [diff] [blame] | 206 | emptyComponentMethod(componentElement, method.methodElement()))); |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 207 | |
| 208 | return Optional.of(generatedComponent); |
| 209 | } |
| 210 | } |
| 211 | |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 212 | 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( |
| dpb | be0e7d3 | 2017-12-20 08:27:48 -0800 | [diff] [blame] | 228 | component.dependencies().stream(), |
| ronshapiro | bff6fd6 | 2017-11-07 11:18:42 -0800 | [diff] [blame] | 229 | 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 | } |