1 /* 2 * Copyright (C) 2018 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.writing; 18 19 import static dagger.internal.codegen.writing.ComponentImplementation.FieldSpecKind.FRAMEWORK_FIELD; 20 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 21 import static dagger.internal.codegen.xprocessing.XProcessingEnvs.wrapType; 22 import static javax.lang.model.element.Modifier.PRIVATE; 23 24 import androidx.room.compiler.processing.XProcessingEnv; 25 import androidx.room.compiler.processing.XType; 26 import com.squareup.javapoet.ClassName; 27 import com.squareup.javapoet.CodeBlock; 28 import com.squareup.javapoet.FieldSpec; 29 import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; 30 import dagger.internal.codegen.javapoet.Expression; 31 import dagger.internal.codegen.javapoet.TypeNames; 32 import dagger.internal.codegen.model.RequestKind; 33 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; 34 import java.util.Optional; 35 36 /** 37 * A factory of {@code Producers#entryPointViewOf(Producer, CancellationListener)} of 38 * {@code Producer}s. 39 */ 40 final class ProducerEntryPointView { 41 private final ShardImplementation shardImplementation; 42 private final XProcessingEnv processingEnv; 43 ProducerEntryPointView(ShardImplementation shardImplementation, XProcessingEnv processingEnv)44 ProducerEntryPointView(ShardImplementation shardImplementation, XProcessingEnv processingEnv) { 45 this.shardImplementation = shardImplementation; 46 this.processingEnv = processingEnv; 47 } 48 49 /** 50 * Returns an expression for an {@code Producers#entryPointViewOf(Producer, CancellationListener)} 51 * of a producer if the component method returns a {@code Producer} or {@code ListenableFuture}. 52 * 53 * <p>This is intended to be a replacement implementation for {@link 54 * dagger.internal.codegen.writing.RequestRepresentation#getDependencyExpressionForComponentMethod(ComponentMethodDescriptor, 55 * ComponentImplementation)}, and in cases where {@link Optional#empty()} is returned, callers 56 * should call {@code super.getDependencyExpressionForComponentMethod()}. 57 */ getProducerEntryPointField( RequestRepresentation producerExpression, ComponentMethodDescriptor componentMethod, ClassName requestingClass)58 Optional<Expression> getProducerEntryPointField( 59 RequestRepresentation producerExpression, 60 ComponentMethodDescriptor componentMethod, 61 ClassName requestingClass) { 62 if (shardImplementation.componentDescriptor().isProduction() 63 && (componentMethod.dependencyRequest().get().kind().equals(RequestKind.FUTURE) 64 || componentMethod.dependencyRequest().get().kind().equals(RequestKind.PRODUCER))) { 65 MemberSelect field = createField(producerExpression, componentMethod); 66 return Optional.of( 67 Expression.create(fieldType(componentMethod), field.getExpressionFor(requestingClass))); 68 } else { 69 // If the component isn't a production component, it won't implement CancellationListener and 70 // as such we can't create an entry point. But this binding must also just be a Producer from 71 // Provider anyway in that case, so there shouldn't be an issue. 72 // TODO(b/116855531): Is it really intended that a non-production component can have Producer 73 // entry points? 74 return Optional.empty(); 75 } 76 } 77 createField( RequestRepresentation producerExpression, ComponentMethodDescriptor componentMethod)78 private MemberSelect createField( 79 RequestRepresentation producerExpression, ComponentMethodDescriptor componentMethod) { 80 // TODO(cgdecker): Use a FrameworkFieldInitializer for this? 81 // Though I don't think we need the once-only behavior of that, since I think 82 // getComponentMethodImplementation will only be called once anyway 83 String methodName = getSimpleName(componentMethod.methodElement()); 84 FieldSpec field = 85 FieldSpec.builder( 86 fieldType(componentMethod).getTypeName(), 87 shardImplementation.getUniqueFieldName(methodName + "EntryPoint"), 88 PRIVATE) 89 .build(); 90 shardImplementation.addField(FRAMEWORK_FIELD, field); 91 92 CodeBlock fieldInitialization = 93 CodeBlock.of( 94 "this.$N = $T.entryPointViewOf($L, $L);", 95 field, 96 TypeNames.PRODUCERS, 97 producerExpression.getDependencyExpression(shardImplementation.name()).codeBlock(), 98 // Always pass in the componentShard reference here rather than the owning shard for 99 // this key because this needs to be the root CancellationListener. 100 shardImplementation.isComponentShard() 101 ? "this" 102 : shardImplementation 103 .getComponentImplementation() 104 .getComponentShard() 105 .shardFieldReference()); 106 shardImplementation.addInitialization(fieldInitialization); 107 108 return MemberSelect.localField(shardImplementation, field.name); 109 } 110 111 // TODO(cgdecker): Can we use producerExpression.getDependencyExpression().type() instead of 112 // needing to (re)compute this? fieldType(ComponentMethodDescriptor componentMethod)113 private XType fieldType(ComponentMethodDescriptor componentMethod) { 114 return wrapType( 115 TypeNames.PRODUCER, 116 componentMethod.dependencyRequest().get().key().type().xprocessing(), 117 processingEnv); 118 } 119 } 120