/* * Copyright (C) 2018 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; import static com.google.common.base.Preconditions.checkArgument; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; import static dagger.internal.codegen.langmodel.Accessibility.isRawTypePubliclyAccessible; 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.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeVariableName; import dagger.internal.codegen.javapoet.CodeBlocks; import dagger.internal.codegen.langmodel.DaggerElements; import java.util.List; import java.util.Optional; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Parameterizable; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; /** * A static method that implements provision and/or injection in one step: * * * *

Note that although this type uses {@code @AutoValue}, it uses instance equality. It uses * {@code @AutoValue} to avoid the boilerplate of writing a correct builder, but is not intended to * actually be a value type. */ @AutoValue abstract class InjectionMethod { abstract String name(); abstract boolean varargs(); abstract ImmutableList typeVariables(); abstract ImmutableMap parameters(); abstract Optional returnType(); abstract Optional nullableAnnotation(); abstract ImmutableList exceptions(); abstract CodeBlock methodBody(); abstract ClassName enclosingClass(); MethodSpec toMethodSpec() { MethodSpec.Builder builder = methodBuilder(name()) .addModifiers(PUBLIC, STATIC) .varargs(varargs()) .addTypeVariables(typeVariables()) .addParameters(parameters().keySet()) .addCode(methodBody()); returnType().map(TypeName::get).ifPresent(builder::returns); nullableAnnotation() .ifPresent(nullableType -> CodeBlocks.addAnnotation(builder, nullableType)); exceptions().stream().map(TypeName::get).forEach(builder::addException); return builder.build(); } CodeBlock invoke(List arguments, ClassName requestingClass) { checkArgument(arguments.size() == parameters().size()); CodeBlock.Builder invocation = CodeBlock.builder(); if (!enclosingClass().equals(requestingClass)) { invocation.add("$T.", enclosingClass()); } return invocation.add("$L($L)", name(), makeParametersCodeBlock(arguments)).build(); } @Override public final int hashCode() { return System.identityHashCode(this); } @Override public final boolean equals(Object obj) { return this == obj; } static Builder builder(DaggerElements elements) { Builder builder = new AutoValue_InjectionMethod.Builder(); builder.elements = elements; builder.varargs(false).exceptions(ImmutableList.of()).nullableAnnotation(Optional.empty()); return builder; } @CanIgnoreReturnValue @AutoValue.Builder abstract static class Builder { private final UniqueNameSet parameterNames = new UniqueNameSet(); private final CodeBlock.Builder methodBody = CodeBlock.builder(); private DaggerElements elements; abstract ImmutableMap.Builder parametersBuilder(); abstract ImmutableList.Builder typeVariablesBuilder(); abstract Builder name(String name); abstract Builder varargs(boolean varargs); abstract Builder returnType(TypeMirror returnType); abstract Builder exceptions(Iterable exceptions); abstract Builder nullableAnnotation(Optional nullableAnnotation); abstract Builder methodBody(CodeBlock methodBody); final CodeBlock.Builder methodBodyBuilder() { return methodBody; } abstract Builder enclosingClass(ClassName enclosingClass); /** * Adds a parameter for the given name and type. If another parameter has already been added * with the same name, the name is disambiguated. */ ParameterSpec addParameter(String name, TypeMirror type) { ParameterSpec parameter = ParameterSpec.builder(TypeName.get(type), parameterNames.getUniqueName(name)).build(); parametersBuilder().put(parameter, type); return parameter; } /** * Calls {@link #copyParameter(VariableElement)} for each parameter of of {@code method}, and * concatenates the results of each call, {@link CodeBlocks#makeParametersCodeBlock(Iterable) * separated with commas}. */ CodeBlock copyParameters(ExecutableElement method) { ImmutableList.Builder argumentsBuilder = ImmutableList.builder(); for (VariableElement parameter : method.getParameters()) { argumentsBuilder.add(copyParameter(parameter)); } varargs(method.isVarArgs()); return makeParametersCodeBlock(argumentsBuilder.build()); } /** * Adds {@code parameter} as a parameter of this method, using a publicly accessible version of * the parameter's type. Returns a {@link CodeBlock} of the usage of this parameter within the * injection method's {@link #methodBody()}. */ CodeBlock copyParameter(VariableElement parameter) { TypeMirror elementType = parameter.asType(); boolean useObject = !isRawTypePubliclyAccessible(elementType); TypeMirror publicType = useObject ? objectType() : elementType; ParameterSpec parameterSpec = addParameter(parameter.getSimpleName().toString(), publicType); return useObject ? CodeBlock.of("($T) $N", elementType, parameterSpec) : CodeBlock.of("$N", parameterSpec); } private TypeMirror objectType() { return elements.getTypeElement(Object.class).asType(); } /** * Adds each type parameter of {@code parameterizable} as a type parameter of this injection * method. */ Builder copyTypeParameters(Parameterizable parameterizable) { parameterizable.getTypeParameters().stream() .map(TypeVariableName::get) .forEach(typeVariablesBuilder()::add); return this; } Builder copyThrows(ExecutableElement element) { exceptions(element.getThrownTypes()); return this; } @CheckReturnValue final InjectionMethod build() { return methodBody(methodBody.build()).buildInternal(); } abstract InjectionMethod buildInternal(); } }