dagger.internal.codegen.binding.SourceFiles Maven / Gradle / Ivy
Show all versions of dagger-compiler Show documentation
/*
* Copyright (C) 2014 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.binding;
import static androidx.room.compiler.processing.XElementKt.isConstructor;
import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_CAMEL;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verify;
import static dagger.internal.codegen.javapoet.TypeNames.DOUBLE_CHECK;
import static dagger.internal.codegen.javapoet.TypeNames.MAP_FACTORY;
import static dagger.internal.codegen.javapoet.TypeNames.MAP_OF_PRODUCED_PRODUCER;
import static dagger.internal.codegen.javapoet.TypeNames.MAP_OF_PRODUCER_PRODUCER;
import static dagger.internal.codegen.javapoet.TypeNames.MAP_PRODUCER;
import static dagger.internal.codegen.javapoet.TypeNames.MAP_PROVIDER_FACTORY;
import static dagger.internal.codegen.javapoet.TypeNames.PRODUCER;
import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER;
import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER_OF_LAZY;
import static dagger.internal.codegen.javapoet.TypeNames.SET_FACTORY;
import static dagger.internal.codegen.javapoet.TypeNames.SET_OF_PRODUCED_PRODUCER;
import static dagger.internal.codegen.javapoet.TypeNames.SET_PRODUCER;
import static dagger.internal.codegen.model.BindingKind.ASSISTED_INJECTION;
import static dagger.internal.codegen.model.BindingKind.INJECTION;
import static dagger.internal.codegen.model.BindingKind.MULTIBOUND_MAP;
import static dagger.internal.codegen.model.BindingKind.MULTIBOUND_SET;
import static dagger.internal.codegen.xprocessing.XElements.asExecutable;
import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames;
import static javax.lang.model.SourceVersion.isName;
import androidx.room.compiler.processing.XExecutableElement;
import androidx.room.compiler.processing.XFieldElement;
import androidx.room.compiler.processing.XTypeElement;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeVariableName;
import dagger.internal.codegen.base.MapType;
import dagger.internal.codegen.base.SetType;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.internal.codegen.model.DependencyRequest;
import dagger.internal.codegen.model.RequestKind;
import javax.inject.Inject;
import javax.lang.model.SourceVersion;
/** Utilities for generating files. */
public final class SourceFiles {
private static final Joiner CLASS_FILE_NAME_JOINER = Joiner.on('_');
@Inject SourceFiles() {}
/**
* Generates names and keys for the factory class fields needed to hold the framework classes for
* all of the dependencies of {@code binding}. It is responsible for choosing a name that
*
*
* - represents all of the dependency requests for this key
*
- is probably associated with the type being bound
*
- is unique within the class
*
*
* @param binding must be an unresolved binding (type parameters must match its type element's)
*/
public static ImmutableMap
generateBindingFieldsForDependencies(Binding binding) {
checkArgument(!binding.unresolved().isPresent(), "binding must be unresolved: %s", binding);
FrameworkTypeMapper frameworkTypeMapper =
FrameworkTypeMapper.forBindingType(binding.bindingType());
return Maps.toMap(
binding.dependencies(),
dependency -> {
ClassName frameworkClassName =
frameworkTypeMapper.getFrameworkType(dependency.kind()).frameworkClassName();
// Remap factory fields back to javax.inject.Provider to maintain backwards compatibility
// for now. In a future release, we should change this to Dagger Provider. This will still
// be a breaking change, but keeping compatibility for a while should reduce the
// likelihood of breakages as it would require components built at much older versions
// using factories built at newer versions to break.
if (frameworkClassName.equals(TypeNames.DAGGER_PROVIDER)) {
frameworkClassName = TypeNames.PROVIDER;
}
return FrameworkField.create(
ParameterizedTypeName.get(
frameworkClassName,
dependency.key().type().xprocessing().getTypeName()),
DependencyVariableNamer.name(dependency));
});
}
public CodeBlock frameworkTypeUsageStatement(
CodeBlock frameworkTypeMemberSelect, RequestKind dependencyKind) {
switch (dependencyKind) {
case LAZY:
return CodeBlock.of(
"$T.lazy($L)",
DOUBLE_CHECK,
frameworkTypeMemberSelect);
case INSTANCE:
case FUTURE:
return CodeBlock.of("$L.get()", frameworkTypeMemberSelect);
case PROVIDER:
case PRODUCER:
return frameworkTypeMemberSelect;
case PROVIDER_OF_LAZY:
return CodeBlock.of("$T.create($L)", PROVIDER_OF_LAZY, frameworkTypeMemberSelect);
default: // including PRODUCED
throw new AssertionError(dependencyKind);
}
}
/**
* Returns a mapping of {@link DependencyRequest}s to {@link CodeBlock}s that {@linkplain
* #frameworkTypeUsageStatement(CodeBlock, RequestKind) use them}.
*/
public ImmutableMap frameworkFieldUsages(
ImmutableSet dependencies,
ImmutableMap fields) {
return Maps.toMap(
dependencies,
dep -> frameworkTypeUsageStatement(CodeBlock.of("$N", fields.get(dep)), dep.kind()));
}
/** Returns the generated factory or members injector name for a binding. */
public static ClassName generatedClassNameForBinding(Binding binding) {
switch (binding.bindingType()) {
case PROVISION:
case PRODUCTION:
ContributionBinding contribution = (ContributionBinding) binding;
switch (contribution.kind()) {
case ASSISTED_INJECTION:
case INJECTION:
case PROVISION:
case PRODUCTION:
return factoryNameForElement(asExecutable(binding.bindingElement().get()));
case ASSISTED_FACTORY:
return siblingClassName(asTypeElement(binding.bindingElement().get()), "_Impl");
default:
throw new AssertionError();
}
case MEMBERS_INJECTION:
return membersInjectorNameForType(
((MembersInjectionBinding) binding).membersInjectedType());
}
throw new AssertionError();
}
/**
* Returns the generated factory name for the given element.
*
* This method is useful during validation before a {@link Binding} can be created. If a
* binding already exists for the given element, prefer to call {@link
* #generatedClassNameForBinding(Binding)} instead since this method does not validate that the
* given element is actually a binding element or not.
*/
public static ClassName factoryNameForElement(XExecutableElement element) {
return elementBasedClassName(element, "Factory");
}
/**
* Calculates an appropriate {@link ClassName} for a generated class that is based on {@code
* element}, appending {@code suffix} at the end.
*
*
This will always return a {@linkplain ClassName#topLevelClassName() top level class name},
* even if {@code element}'s enclosing class is a nested type.
*/
public static ClassName elementBasedClassName(XExecutableElement element, String suffix) {
ClassName enclosingClassName = element.getEnclosingElement().getClassName();
String methodName =
isConstructor(element) ? "" : LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(element));
return ClassName.get(
enclosingClassName.packageName(),
classFileName(enclosingClassName) + "_" + methodName + suffix);
}
public static TypeName parameterizedGeneratedTypeNameForBinding(Binding binding) {
ClassName className = generatedClassNameForBinding(binding);
ImmutableList typeParameters = bindingTypeElementTypeVariableNames(binding);
return typeParameters.isEmpty()
? className
: ParameterizedTypeName.get(className, Iterables.toArray(typeParameters, TypeName.class));
}
public static ClassName membersInjectorNameForType(XTypeElement typeElement) {
return siblingClassName(typeElement, "_MembersInjector");
}
public static String memberInjectedFieldSignatureForVariable(XFieldElement field) {
return field.getEnclosingElement().getClassName().canonicalName() + "." + getSimpleName(field);
}
public static String classFileName(ClassName className) {
return CLASS_FILE_NAME_JOINER.join(className.simpleNames());
}
public static ClassName generatedMonitoringModuleName(XTypeElement componentElement) {
return siblingClassName(componentElement, "_MonitoringModule");
}
// TODO(ronshapiro): when JavaPoet migration is complete, replace the duplicated code
// which could use this.
private static ClassName siblingClassName(XTypeElement typeElement, String suffix) {
ClassName className = typeElement.getClassName();
return className.topLevelClassName().peerClass(classFileName(className) + suffix);
}
/**
* The {@link java.util.Set} factory class name appropriate for set bindings.
*
*
* - {@link dagger.producers.internal.SetFactory} for provision bindings.
*
- {@link dagger.producers.internal.SetProducer} for production bindings for {@code Set
}.
* - {@link dagger.producers.internal.SetOfProducedProducer} for production bindings for
* {@code Set
>}.
*
*/
public static ClassName setFactoryClassName(ContributionBinding binding) {
checkArgument(binding.kind().equals(MULTIBOUND_SET));
if (binding.bindingType().equals(BindingType.PROVISION)) {
return SET_FACTORY;
} else {
SetType setType = SetType.from(binding.key());
return setType.elementsAreTypeOf(TypeNames.PRODUCED)
? SET_OF_PRODUCED_PRODUCER
: SET_PRODUCER;
}
}
/** The {@link java.util.Map} factory class name appropriate for map bindings. */
public static ClassName mapFactoryClassName(ContributionBinding binding) {
checkState(binding.kind().equals(MULTIBOUND_MAP), binding.kind());
MapType mapType = MapType.from(binding.key());
switch (binding.bindingType()) {
case PROVISION:
return mapType.valuesAreTypeOf(PROVIDER) ? MAP_PROVIDER_FACTORY : MAP_FACTORY;
case PRODUCTION:
return mapType.valuesAreFrameworkType()
? mapType.valuesAreTypeOf(PRODUCER)
? MAP_OF_PRODUCER_PRODUCER
: MAP_OF_PRODUCED_PRODUCER
: MAP_PRODUCER;
default:
throw new IllegalArgumentException(binding.bindingType().toString());
}
}
public static ImmutableList bindingTypeElementTypeVariableNames(
Binding binding) {
if (binding instanceof ContributionBinding) {
ContributionBinding contributionBinding = (ContributionBinding) binding;
if (!(contributionBinding.kind() == INJECTION
|| contributionBinding.kind() == ASSISTED_INJECTION)
&& !contributionBinding.requiresModuleInstance()) {
return ImmutableList.of();
}
}
return typeVariableNames(binding.bindingTypeElement().get());
}
/**
* Returns a name to be used for variables of the given {@linkplain XTypeElement type}. Prefer
* semantically meaningful variable names, but if none can be derived, this will produce something
* readable.
*/
// TODO(gak): maybe this should be a function of TypeMirrors instead of Elements?
public static String simpleVariableName(XTypeElement typeElement) {
return simpleVariableName(typeElement.getClassName());
}
/**
* Returns a name to be used for variables of the given {@linkplain ClassName}. Prefer
* semantically meaningful variable names, but if none can be derived, this will produce something
* readable.
*/
public static String simpleVariableName(ClassName className) {
String candidateName = UPPER_CAMEL.to(LOWER_CAMEL, className.simpleName());
String variableName = protectAgainstKeywords(candidateName);
verify(isName(variableName), "'%s' was expected to be a valid variable name", variableName);
return variableName;
}
public static String protectAgainstKeywords(String candidateName) {
switch (candidateName) {
case "package":
return "pkg";
case "boolean":
return "b";
case "double":
return "d";
case "byte":
return "b";
case "int":
return "i";
case "short":
return "s";
case "char":
return "c";
case "void":
return "v";
case "class":
return "clazz";
case "float":
return "f";
case "long":
return "l";
default:
return SourceVersion.isKeyword(candidateName) ? candidateName + '_' : candidateName;
}
}
}