444 lines
18 KiB
Java
444 lines
18 KiB
Java
/*
|
|
* 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 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 com.squareup.javapoet.MethodSpec.constructorBuilder;
|
|
import static com.squareup.javapoet.MethodSpec.methodBuilder;
|
|
import static com.squareup.javapoet.TypeSpec.anonymousClassBuilder;
|
|
import static com.squareup.javapoet.TypeSpec.classBuilder;
|
|
import static dagger.internal.codegen.base.RequestKinds.requestTypeName;
|
|
import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES;
|
|
import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED;
|
|
import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER;
|
|
import static dagger.internal.codegen.javapoet.TypeNames.abstractProducerOf;
|
|
import static dagger.internal.codegen.javapoet.TypeNames.listenableFutureOf;
|
|
import static dagger.internal.codegen.javapoet.TypeNames.providerOf;
|
|
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 javax.lang.model.element.Modifier.FINAL;
|
|
import static javax.lang.model.element.Modifier.PRIVATE;
|
|
import static javax.lang.model.element.Modifier.PUBLIC;
|
|
import static javax.lang.model.element.Modifier.STATIC;
|
|
|
|
import com.google.auto.value.AutoValue;
|
|
import com.google.common.base.Function;
|
|
import com.google.common.util.concurrent.Futures;
|
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
import com.google.common.util.concurrent.MoreExecutors;
|
|
import com.squareup.javapoet.ClassName;
|
|
import com.squareup.javapoet.CodeBlock;
|
|
import com.squareup.javapoet.FieldSpec;
|
|
import com.squareup.javapoet.MethodSpec;
|
|
import com.squareup.javapoet.ParameterSpec;
|
|
import com.squareup.javapoet.ParameterizedTypeName;
|
|
import com.squareup.javapoet.TypeName;
|
|
import com.squareup.javapoet.TypeSpec;
|
|
import com.squareup.javapoet.TypeVariableName;
|
|
import dagger.internal.InstanceFactory;
|
|
import dagger.internal.Preconditions;
|
|
import dagger.internal.codegen.base.OptionalType;
|
|
import dagger.internal.codegen.base.OptionalType.OptionalKind;
|
|
import dagger.internal.codegen.binding.BindingType;
|
|
import dagger.internal.codegen.binding.ContributionBinding;
|
|
import dagger.internal.codegen.binding.FrameworkType;
|
|
import dagger.internal.codegen.javapoet.AnnotationSpecs;
|
|
import dagger.model.RequestKind;
|
|
import dagger.producers.Producer;
|
|
import dagger.producers.internal.Producers;
|
|
import java.util.Comparator;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.TreeMap;
|
|
import java.util.concurrent.Executor;
|
|
import javax.inject.Inject;
|
|
import javax.inject.Provider;
|
|
|
|
/** 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.
|
|
@PerGeneratedFile
|
|
final class OptionalFactories {
|
|
private final ComponentImplementation componentImplementation;
|
|
|
|
@Inject OptionalFactories(@TopLevel ComponentImplementation componentImplementation) {
|
|
this.componentImplementation = componentImplementation;
|
|
}
|
|
|
|
/**
|
|
* The factory classes that implement {@code Provider<Optional<T>>} or {@code
|
|
* Producer<Optional<T>>} for present optional bindings for a given kind of dependency request
|
|
* within the component.
|
|
*
|
|
* <p>The key is the {@code Provider<Optional<T>>} type.
|
|
*/
|
|
private final Map<PresentFactorySpec, TypeSpec> presentFactoryClasses =
|
|
new TreeMap<>(
|
|
Comparator.comparing(PresentFactorySpec::valueKind)
|
|
.thenComparing(PresentFactorySpec::frameworkType)
|
|
.thenComparing(PresentFactorySpec::optionalKind));
|
|
|
|
/**
|
|
* The static methods that return a {@code Provider<Optional<T>>} that always returns an absent
|
|
* value.
|
|
*/
|
|
private final Map<OptionalKind, MethodSpec> absentOptionalProviderMethods = new TreeMap<>();
|
|
|
|
/**
|
|
* The static fields for {@code Provider<Optional<T>>} objects that always return an absent value.
|
|
*/
|
|
private final Map<OptionalKind, FieldSpec> absentOptionalProviderFields = new TreeMap<>();
|
|
|
|
/**
|
|
* Returns an expression that calls a static method that returns a {@code Provider<Optional<T>>}
|
|
* for absent optional bindings.
|
|
*/
|
|
CodeBlock absentOptionalProvider(ContributionBinding binding) {
|
|
verify(
|
|
binding.bindingType().equals(BindingType.PROVISION),
|
|
"Absent optional bindings should be provisions: %s",
|
|
binding);
|
|
OptionalKind optionalKind = OptionalType.from(binding.key()).kind();
|
|
return CodeBlock.of(
|
|
"$N()",
|
|
absentOptionalProviderMethods.computeIfAbsent(
|
|
optionalKind,
|
|
kind -> {
|
|
MethodSpec method = absentOptionalProviderMethod(kind);
|
|
componentImplementation.addMethod(ABSENT_OPTIONAL_METHOD, method);
|
|
return method;
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Creates a method specification for a {@code Provider<Optional<T>>} that always returns an
|
|
* absent value.
|
|
*/
|
|
private MethodSpec absentOptionalProviderMethod(OptionalKind optionalKind) {
|
|
TypeVariableName typeVariable = TypeVariableName.get("T");
|
|
return methodBuilder(
|
|
String.format(
|
|
"absent%sProvider", UPPER_UNDERSCORE.to(UPPER_CAMEL, optionalKind.name())))
|
|
.addModifiers(PRIVATE, STATIC)
|
|
.addTypeVariable(typeVariable)
|
|
.returns(providerOf(optionalKind.of(typeVariable)))
|
|
.addJavadoc(
|
|
"Returns a {@link $T} that returns {@code $L}.",
|
|
Provider.class,
|
|
optionalKind.absentValueExpression())
|
|
.addCode("$L // safe covariant cast\n", AnnotationSpecs.suppressWarnings(UNCHECKED))
|
|
.addCode(
|
|
"$1T provider = ($1T) $2N;",
|
|
providerOf(optionalKind.of(typeVariable)),
|
|
absentOptionalProviderFields.computeIfAbsent(
|
|
optionalKind,
|
|
kind -> {
|
|
FieldSpec field = absentOptionalProviderField(kind);
|
|
componentImplementation.addField(ABSENT_OPTIONAL_FIELD, field);
|
|
return field;
|
|
}))
|
|
.addCode("return provider;")
|
|
.build();
|
|
}
|
|
|
|
/**
|
|
* Creates a field specification for a {@code Provider<Optional<T>>} that always returns an absent
|
|
* value.
|
|
*/
|
|
private FieldSpec absentOptionalProviderField(OptionalKind optionalKind) {
|
|
return FieldSpec.builder(
|
|
PROVIDER,
|
|
String.format("ABSENT_%s_PROVIDER", optionalKind.name()),
|
|
PRIVATE,
|
|
STATIC,
|
|
FINAL)
|
|
.addAnnotation(AnnotationSpecs.suppressWarnings(RAWTYPES))
|
|
.initializer("$T.create($L)", InstanceFactory.class, optionalKind.absentValueExpression())
|
|
.addJavadoc(
|
|
"A {@link $T} that returns {@code $L}.",
|
|
Provider.class,
|
|
optionalKind.absentValueExpression())
|
|
.build();
|
|
}
|
|
|
|
/** Information about the type of a factory for present bindings. */
|
|
@AutoValue
|
|
abstract static class PresentFactorySpec {
|
|
/** Whether the factory is a {@link Provider} or a {@link 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. */
|
|
TypeVariableName typeVariable() {
|
|
return TypeVariableName.get("T");
|
|
}
|
|
|
|
/** The type contained by the {@code Optional}. */
|
|
TypeName valueType() {
|
|
return requestTypeName(valueKind(), typeVariable());
|
|
}
|
|
|
|
/** The type provided or produced by the factory. */
|
|
ParameterizedTypeName optionalType() {
|
|
return optionalKind().of(valueType());
|
|
}
|
|
|
|
/** The type of the factory. */
|
|
ParameterizedTypeName factoryType() {
|
|
return frameworkType().frameworkClassOf(optionalType());
|
|
}
|
|
|
|
/** The type of the delegate provider or producer. */
|
|
ParameterizedTypeName delegateType() {
|
|
return frameworkType().frameworkClassOf(typeVariable());
|
|
}
|
|
|
|
/** Returns the superclass the generated factory should have, if any. */
|
|
Optional<ParameterizedTypeName> 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<ParameterizedTypeName> 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().frameworkClass().getSimpleName())
|
|
.toString();
|
|
}
|
|
|
|
private static PresentFactorySpec of(ContributionBinding 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<Optional<T>>} or {@code Producer<Optional<T>>} for a present optional binding, where
|
|
* {@code T} represents dependency requests of that kind.
|
|
*
|
|
* <ul>
|
|
* <li>If {@code optionalRequestKind} is {@link RequestKind#INSTANCE}, the class implements
|
|
* {@code ProviderOrProducer<Optional<T>>}.
|
|
* <li>If {@code optionalRequestKind} is {@link RequestKind#PROVIDER}, the class implements
|
|
* {@code Provider<Optional<Provider<T>>>}.
|
|
* <li>If {@code optionalRequestKind} is {@link RequestKind#LAZY}, the class implements {@code
|
|
* Provider<Optional<Lazy<T>>>}.
|
|
* <li>If {@code optionalRequestKind} is {@link RequestKind#PROVIDER_OF_LAZY}, the class
|
|
* implements {@code Provider<Optional<Provider<Lazy<T>>>>}.
|
|
* <li>If {@code optionalRequestKind} is {@link RequestKind#PRODUCER}, the class implements
|
|
* {@code Producer<Optional<Producer<T>>>}.
|
|
* <li>If {@code optionalRequestKind} is {@link RequestKind#PRODUCED}, the class implements
|
|
* {@code Producer<Optional<Produced<T>>>}.
|
|
* </ul>
|
|
*
|
|
* @param delegateFactory an expression for a {@link Provider} or {@link Producer} of the
|
|
* underlying type
|
|
*/
|
|
CodeBlock presentOptionalFactory(ContributionBinding binding, CodeBlock delegateFactory) {
|
|
return CodeBlock.of(
|
|
"$N.of($L)",
|
|
presentFactoryClasses.computeIfAbsent(
|
|
PresentFactorySpec.of(binding),
|
|
spec -> {
|
|
TypeSpec type = presentOptionalFactoryClass(spec);
|
|
componentImplementation.addType(PRESENT_FACTORY, type);
|
|
return type;
|
|
}),
|
|
delegateFactory);
|
|
}
|
|
|
|
private TypeSpec presentOptionalFactoryClass(PresentFactorySpec spec) {
|
|
FieldSpec delegateField =
|
|
FieldSpec.builder(spec.delegateType(), "delegate", PRIVATE, FINAL).build();
|
|
ParameterSpec delegateParameter = ParameterSpec.builder(delegateField.type, "delegate").build();
|
|
TypeSpec.Builder factoryClassBuilder =
|
|
classBuilder(spec.factoryClassName())
|
|
.addTypeVariable(spec.typeVariable())
|
|
.addModifiers(PRIVATE, STATIC, FINAL)
|
|
.addJavadoc(
|
|
"A {@code $T} that uses a delegate {@code $T}.",
|
|
spec.factoryType(),
|
|
delegateField.type);
|
|
|
|
spec.superclass().ifPresent(factoryClassBuilder::superclass);
|
|
spec.superinterface().ifPresent(factoryClassBuilder::addSuperinterface);
|
|
|
|
return factoryClassBuilder
|
|
.addField(delegateField)
|
|
.addMethod(
|
|
constructorBuilder()
|
|
.addModifiers(PRIVATE)
|
|
.addParameter(delegateParameter)
|
|
.addCode(
|
|
"this.$N = $T.checkNotNull($N);",
|
|
delegateField,
|
|
Preconditions.class,
|
|
delegateParameter)
|
|
.build())
|
|
.addMethod(presentOptionalFactoryGetMethod(spec, delegateField))
|
|
.addMethod(
|
|
methodBuilder("of")
|
|
.addModifiers(PRIVATE, STATIC)
|
|
.addTypeVariable(spec.typeVariable())
|
|
.returns(spec.factoryType())
|
|
.addParameter(delegateParameter)
|
|
.addCode(
|
|
"return new $L<$T>($N);",
|
|
spec.factoryClassName(),
|
|
spec.typeVariable(),
|
|
delegateParameter)
|
|
.build())
|
|
.build();
|
|
}
|
|
|
|
private MethodSpec presentOptionalFactoryGetMethod(
|
|
PresentFactorySpec spec, FieldSpec delegateField) {
|
|
MethodSpec.Builder getMethodBuilder =
|
|
methodBuilder(spec.factoryMethodName()).addAnnotation(Override.class).addModifiers(PUBLIC);
|
|
|
|
switch (spec.frameworkType()) {
|
|
case PROVIDER:
|
|
return getMethodBuilder
|
|
.returns(spec.optionalType())
|
|
.addCode(
|
|
"return $L;",
|
|
spec.optionalKind()
|
|
.presentExpression(
|
|
FrameworkType.PROVIDER.to(
|
|
spec.valueKind(), CodeBlock.of("$N", delegateField))))
|
|
.build();
|
|
|
|
case PRODUCER_NODE:
|
|
getMethodBuilder.returns(listenableFutureOf(spec.optionalType()));
|
|
|
|
switch (spec.valueKind()) {
|
|
case FUTURE: // return a ListenableFuture<Optional<ListenableFuture<T>>>
|
|
case PRODUCER: // return a ListenableFuture<Optional<Producer<T>>>
|
|
return getMethodBuilder
|
|
.addCode(
|
|
"return $T.immediateFuture($L);",
|
|
Futures.class,
|
|
spec.optionalKind()
|
|
.presentExpression(
|
|
FrameworkType.PRODUCER_NODE.to(
|
|
spec.valueKind(), CodeBlock.of("$N", delegateField))))
|
|
.build();
|
|
|
|
case INSTANCE: // return a ListenableFuture<Optional<T>>
|
|
return getMethodBuilder
|
|
.addCode(
|
|
"return $L;",
|
|
transformFutureToOptional(
|
|
spec.optionalKind(),
|
|
spec.typeVariable(),
|
|
CodeBlock.of("$N.get()", delegateField)))
|
|
.build();
|
|
|
|
case PRODUCED: // return a ListenableFuture<Optional<Produced<T>>>
|
|
return getMethodBuilder
|
|
.addCode(
|
|
"return $L;",
|
|
transformFutureToOptional(
|
|
spec.optionalKind(),
|
|
spec.valueType(),
|
|
CodeBlock.of(
|
|
"$T.createFutureProduced($N.get())", Producers.class, 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<inputType>} into a {@code
|
|
* ListenableFuture<Optional<inputType>>}.
|
|
*
|
|
* @param inputFuture an expression of type {@code ListenableFuture<inputType>}
|
|
*/
|
|
private static CodeBlock transformFutureToOptional(
|
|
OptionalKind optionalKind, TypeName inputType, CodeBlock inputFuture) {
|
|
return CodeBlock.of(
|
|
"$T.transform($L, $L, $T.directExecutor())",
|
|
Futures.class,
|
|
inputFuture,
|
|
anonymousClassBuilder("")
|
|
.addSuperinterface(
|
|
ParameterizedTypeName.get(
|
|
ClassName.get(Function.class), inputType, optionalKind.of(inputType)))
|
|
.addMethod(
|
|
methodBuilder("apply")
|
|
.addAnnotation(Override.class)
|
|
.addModifiers(PUBLIC)
|
|
.returns(optionalKind.of(inputType))
|
|
.addParameter(inputType, "input")
|
|
.addCode("return $L;", optionalKind.presentExpression(CodeBlock.of("input")))
|
|
.build())
|
|
.build(),
|
|
MoreExecutors.class);
|
|
}
|
|
}
|