All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dagger.internal.codegen.writing.OptionalFactories Maven / Gradle / Ivy

There is a newer version: 2.9
Show newest version
/*
 * Copyright (C) 2016 The Dagger Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dagger.internal.codegen.writing;

import static androidx.room.compiler.codegen.compat.XConverters.toXPoet;
import static com.google.common.base.CaseFormat.UPPER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
import static com.google.common.base.Verify.verify;
import static com.google.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.base.RequestKinds.requestTypeName;
import static dagger.internal.codegen.writing.ComponentImplementation.FieldSpecKind.ABSENT_OPTIONAL_FIELD;
import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.ABSENT_OPTIONAL_METHOD;
import static dagger.internal.codegen.writing.ComponentImplementation.TypeSpecKind.PRESENT_FACTORY;
import static dagger.internal.codegen.xprocessing.XAnnotationSpecs.Suppression.RAWTYPES;
import static dagger.internal.codegen.xprocessing.XAnnotationSpecs.Suppression.UNCHECKED;
import static dagger.internal.codegen.xprocessing.XAnnotationSpecs.suppressWarnings;
import static dagger.internal.codegen.xprocessing.XFunSpecs.constructorBuilder;
import static dagger.internal.codegen.xprocessing.XFunSpecs.methodBuilder;
import static dagger.internal.codegen.xprocessing.XTypeNames.abstractProducerOf;
import static dagger.internal.codegen.xprocessing.XTypeNames.daggerProviderOf;
import static dagger.internal.codegen.xprocessing.XTypeNames.listenableFutureOf;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.STATIC;

import androidx.room.compiler.codegen.VisibilityModifier;
import androidx.room.compiler.codegen.XCodeBlock;
import androidx.room.compiler.codegen.XFunSpec;
import androidx.room.compiler.codegen.XParameterSpec;
import androidx.room.compiler.codegen.XPropertySpec;
import androidx.room.compiler.codegen.XTypeName;
import androidx.room.compiler.codegen.XTypeSpec;
import com.google.auto.value.AutoValue;
import com.squareup.javapoet.TypeVariableName;
import dagger.internal.codegen.base.OptionalType;
import dagger.internal.codegen.base.OptionalType.OptionalKind;
import dagger.internal.codegen.binding.BindingType;
import dagger.internal.codegen.binding.FrameworkType;
import dagger.internal.codegen.binding.OptionalBinding;
import dagger.internal.codegen.model.RequestKind;
import dagger.internal.codegen.xprocessing.XAnnotationSpecs;
import dagger.internal.codegen.xprocessing.XParameterSpecs;
import dagger.internal.codegen.xprocessing.XPropertySpecs;
import dagger.internal.codegen.xprocessing.XTypeNames;
import dagger.internal.codegen.xprocessing.XTypeSpecs;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import javax.inject.Inject;

/** The nested class and static methods required by the component to implement optional bindings. */
// TODO(dpb): Name members simply if a component uses only one of Guava or JDK Optional.
final class OptionalFactories {
  /** Keeps track of the fields, methods, and classes already added to the generated file. */
  @PerGeneratedFile
  static final class PerGeneratedFileCache {
    /**
     * The factory classes that implement {@code Provider>} or {@code
     * Producer>} for present optional bindings for a given kind of dependency request
     * within the component.
     *
     * 

The key is the {@code Provider>} type. */ private final Map presentFactoryClasses = new TreeMap<>( Comparator.comparing(PresentFactorySpec::valueKind) .thenComparing(PresentFactorySpec::frameworkType) .thenComparing(PresentFactorySpec::optionalKind)); /** * The static methods that return a {@code Provider>} that always returns an absent * value. */ private final Map absentOptionalProviderMethods = new TreeMap<>(); /** * The static fields for {@code Provider>} objects that always return an absent * value. */ private final Map absentOptionalProviderFields = new TreeMap<>(); @Inject PerGeneratedFileCache() {} } private final PerGeneratedFileCache perGeneratedFileCache; private final GeneratedImplementation topLevelImplementation; @Inject OptionalFactories( PerGeneratedFileCache perGeneratedFileCache, @TopLevel GeneratedImplementation topLevelImplementation) { this.perGeneratedFileCache = perGeneratedFileCache; this.topLevelImplementation = topLevelImplementation; } /** * Returns an expression that calls a static method that returns a {@code Provider>} * for absent optional bindings. */ XCodeBlock absentOptionalProvider(OptionalBinding binding) { verify( binding.bindingType().equals(BindingType.PROVISION), "Absent optional bindings should be provisions: %s", binding); OptionalKind optionalKind = OptionalType.from(binding.key()).kind(); return XCodeBlock.of( "%N()", perGeneratedFileCache.absentOptionalProviderMethods.computeIfAbsent( optionalKind, kind -> { XFunSpec method = absentOptionalProviderMethod(kind); topLevelImplementation.addMethod(ABSENT_OPTIONAL_METHOD, method); return method; })); } /** * Creates a method specification for a {@code Provider>} that always returns an * absent value. */ private XFunSpec absentOptionalProviderMethod(OptionalKind optionalKind) { XTypeName typeVariable = toXPoet(TypeVariableName.get("T")); return methodBuilder( String.format( "absent%sProvider", UPPER_UNDERSCORE.to(UPPER_CAMEL, optionalKind.name()))) .addModifiers(PRIVATE, STATIC) .addTypeVariable(typeVariable) .returns(daggerProviderOf(optionalKind.of(typeVariable))) .addJavadoc( "Returns a {@link %T} that returns {@code %L}.", XTypeNames.DAGGER_PROVIDER, optionalKind.absentValueExpression()) .addCode("%L // safe covariant cast\n", XAnnotationSpecs.suppressWarnings(UNCHECKED)) .addStatement( "%1T provider = (%1T) %2N", daggerProviderOf(optionalKind.of(typeVariable)), perGeneratedFileCache.absentOptionalProviderFields.computeIfAbsent( optionalKind, kind -> { XPropertySpec field = absentOptionalProviderField(kind); topLevelImplementation.addField(ABSENT_OPTIONAL_FIELD, field); return field; })) .addStatement("return provider") .build(); } /** * Creates a field specification for a {@code Provider>} that always returns an absent * value. */ private XPropertySpec absentOptionalProviderField(OptionalKind optionalKind) { return XPropertySpecs.builder( String.format("ABSENT_%s_PROVIDER", optionalKind.name()), XTypeNames.DAGGER_PROVIDER, PRIVATE, STATIC, FINAL) .addAnnotation(suppressWarnings(RAWTYPES)) .initializer( "%T.create(%L)", XTypeNames.INSTANCE_FACTORY, optionalKind.absentValueExpression()) .addJavadoc( "A {@link %T} that returns {@code %L}.", XTypeNames.DAGGER_PROVIDER, optionalKind.absentValueExpression()) .build(); } /** Information about the type of a factory for present bindings. */ @AutoValue abstract static class PresentFactorySpec { /** Whether the factory is a {@code Provider} or a {@code Producer}. */ abstract FrameworkType frameworkType(); /** What kind of {@code Optional} is returned. */ abstract OptionalKind optionalKind(); /** The kind of request satisfied by the value of the {@code Optional}. */ abstract RequestKind valueKind(); /** The type variable for the factory class. */ XTypeName typeVariable() { return toXPoet(TypeVariableName.get("T")); } /** The type contained by the {@code Optional}. */ XTypeName valueType() { return requestTypeName(valueKind(), typeVariable()); } /** The type provided or produced by the factory. */ XTypeName optionalType() { return optionalKind().of(valueType()); } /** The type of the factory. */ XTypeName factoryType() { return frameworkType().frameworkClassOf(optionalType()); } /** The type of the delegate provider or producer. */ XTypeName delegateType() { return frameworkType().frameworkClassOf(typeVariable()); } /** Returns the superclass the generated factory should have, if any. */ Optional superclass() { switch (frameworkType()) { case PRODUCER_NODE: // TODO(cgdecker): This probably isn't a big issue for now, but it's possible this // shouldn't be an AbstractProducer: // - As AbstractProducer, it'll only call the delegate's get() method once and then cache // that result (essentially) rather than calling the delegate's get() method each time // its get() method is called (which was what it did before the cancellation change). // - It's not 100% clear to me whether the view-creation methods should return a view of // the same view created by the delegate or if they should just return their own views. return Optional.of(abstractProducerOf(optionalType())); default: return Optional.empty(); } } /** Returns the superinterface the generated factory should have, if any. */ Optional superinterface() { switch (frameworkType()) { case PROVIDER: return Optional.of(factoryType()); default: return Optional.empty(); } } /** Returns the name of the factory method to generate. */ String factoryMethodName() { switch (frameworkType()) { case PROVIDER: return "get"; case PRODUCER_NODE: return "compute"; } throw new AssertionError(frameworkType()); } /** The name of the factory class. */ String factoryClassName() { return new StringBuilder("Present") .append(UPPER_UNDERSCORE.to(UPPER_CAMEL, optionalKind().name())) .append(UPPER_UNDERSCORE.to(UPPER_CAMEL, valueKind().toString())) .append(frameworkType().frameworkClassName().getSimpleName()) .toString(); } private static PresentFactorySpec of(OptionalBinding binding) { return new AutoValue_OptionalFactories_PresentFactorySpec( FrameworkType.forBindingType(binding.bindingType()), OptionalType.from(binding.key()).kind(), getOnlyElement(binding.dependencies()).kind()); } } /** * Returns an expression for an instance of a nested class that implements {@code * Provider>} or {@code Producer>} for a present optional binding, where * {@code T} represents dependency requests of that kind. * *

    *
  • If {@code optionalRequestKind} is {@link RequestKind#INSTANCE}, the class implements * {@code ProviderOrProducer>}. *
  • If {@code optionalRequestKind} is {@link RequestKind#PROVIDER}, the class implements * {@code Provider>>}. *
  • If {@code optionalRequestKind} is {@link RequestKind#LAZY}, the class implements {@code * Provider>>}. *
  • If {@code optionalRequestKind} is {@link RequestKind#PROVIDER_OF_LAZY}, the class * implements {@code Provider>>>}. *
  • If {@code optionalRequestKind} is {@link RequestKind#PRODUCER}, the class implements * {@code Producer>>}. *
  • If {@code optionalRequestKind} is {@link RequestKind#PRODUCED}, the class implements * {@code Producer>>}. *
* * @param delegateFactory an expression for a {@code Provider} or {@code Producer} of the * underlying type */ XCodeBlock presentOptionalFactory(OptionalBinding binding, XCodeBlock delegateFactory) { return XCodeBlock.of( "%N.of(%L)", perGeneratedFileCache.presentFactoryClasses.computeIfAbsent( PresentFactorySpec.of(binding), spec -> { XTypeSpec type = presentOptionalFactoryClass(spec); topLevelImplementation.addType(PRESENT_FACTORY, type); return type; }) .getName(), // SUPPRESS_GET_NAME_CHECK delegateFactory); } private XTypeSpec presentOptionalFactoryClass(PresentFactorySpec spec) { XPropertySpec delegateField = XPropertySpecs.of("delegate", spec.delegateType(), PRIVATE, FINAL); XParameterSpec delegateParameter = XParameterSpecs.of("delegate", delegateField.getType()); XTypeSpecs.Builder factoryClassBuilder = XTypeSpecs.classBuilder(spec.factoryClassName()) .addTypeVariable(spec.typeVariable()) .addModifiers(PRIVATE, STATIC, FINAL) .addJavadoc( "A {@code %T} that uses a delegate {@code %T}.", spec.factoryType(), delegateField.getType()); spec.superclass().ifPresent(factoryClassBuilder::superclass); spec.superinterface().ifPresent(factoryClassBuilder::addSuperinterface); return factoryClassBuilder .addProperty(delegateField) .addFunction( constructorBuilder() .addModifiers(PRIVATE) .addParameter(delegateParameter) .addCode( "this.%N = %T.checkNotNull(%N);", delegateField, XTypeNames.DAGGER_PRECONDITIONS, delegateParameter.getName()) // SUPPRESS_GET_NAME_CHECK .build()) .addFunction(presentOptionalFactoryGetMethod(spec, delegateField)) .addFunction( methodBuilder("of") .addModifiers(PRIVATE, STATIC) .addTypeVariable(spec.typeVariable()) .returns(spec.factoryType()) .addParameter(delegateParameter) .addStatement( "return %L", XCodeBlock.ofNewInstance( topLevelImplementation.name() .nestedClass(spec.factoryClassName()) .parametrizedBy(spec.typeVariable()), "%N", delegateParameter.getName())) // SUPPRESS_GET_NAME_CHECK .build()) .build(); } private XFunSpec presentOptionalFactoryGetMethod( PresentFactorySpec spec, XPropertySpec delegateField) { XFunSpec.Builder getMethodBuilder = XFunSpec.builder( spec.factoryMethodName(), VisibilityModifier.PUBLIC, /* isOpen= */ false, /* isOverride= */ true, /* addJavaNullabilityAnnotation= */ false); switch (spec.frameworkType()) { case PROVIDER: return getMethodBuilder .returns(spec.optionalType()) .addCode( "return %L;", spec.optionalKind() .presentExpression( FrameworkType.PROVIDER.to( spec.valueKind(), XCodeBlock.of("%N", delegateField)))) .build(); case PRODUCER_NODE: getMethodBuilder.returns(listenableFutureOf(spec.optionalType())); switch (spec.valueKind()) { case FUTURE: // return a ListenableFuture>> case PRODUCER: // return a ListenableFuture>> return getMethodBuilder .addCode( "return %T.immediateFuture(%L);", XTypeNames.FUTURES, spec.optionalKind() .presentExpression( FrameworkType.PRODUCER_NODE.to( spec.valueKind(), XCodeBlock.of("%N", delegateField)))) .build(); case INSTANCE: // return a ListenableFuture> return getMethodBuilder .addCode( "return %L;", transformFutureToOptional( spec.optionalKind(), spec.typeVariable(), XCodeBlock.of("%N.get()", delegateField))) .build(); case PRODUCED: // return a ListenableFuture>> return getMethodBuilder .addCode( "return %L;", transformFutureToOptional( spec.optionalKind(), spec.valueType(), XCodeBlock.of( "%T.createFutureProduced(%N.get())", XTypeNames.PRODUCERS, delegateField))) .build(); default: throw new UnsupportedOperationException( spec.factoryType() + " objects are not supported"); } } throw new AssertionError(spec.frameworkType()); } /** * An expression that uses {@link Futures#transform(ListenableFuture, Function, Executor)} to * transform a {@code ListenableFuture} into a {@code * ListenableFuture>}. * * @param inputFuture an expression of type {@code ListenableFuture} */ private static XCodeBlock transformFutureToOptional( OptionalKind optionalKind, XTypeName inputType, XCodeBlock inputFuture) { XTypeName superInterface = XTypeNames.GUAVA_FUNCTION.parametrizedBy(inputType, optionalKind.of(inputType)); return XCodeBlock.of( "%T.transform(%L, %L, %T.directExecutor())", XTypeNames.FUTURES, inputFuture, XTypeSpec.anonymousClassBuilder("") .addSuperinterface(superInterface) .addFunction( XFunSpec.builder( "apply", VisibilityModifier.PUBLIC, /* isOpen= */ false, /* isOverride= */ true, /* addJavaNullabilityAnnotation= */ false) .returns(optionalKind.of(inputType)) .addParameter("input", inputType) .addStatement( "return %L", optionalKind.presentExpression(XCodeBlock.of("input"))) .build()) .build(), XTypeNames.MORE_EXECUTORS); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy