dagger.internal.codegen.SimpleMethodBindingExpression.java Source code

Java tutorial

Introduction

Here is the source code for dagger.internal.codegen.SimpleMethodBindingExpression.java

Source

/*
 * 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;

import static com.google.auto.common.MoreElements.asExecutable;
import static com.google.common.base.Preconditions.checkArgument;
import static dagger.internal.codegen.Accessibility.isTypeAccessibleFrom;
import static dagger.internal.codegen.CodeBlocks.toParametersCodeBlock;
import static dagger.internal.codegen.InjectionMethods.ProvisionMethod.requiresInjectionMethod;
import static dagger.internal.codegen.TypeNames.rawTypeName;

import com.google.auto.common.MoreTypes;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import dagger.internal.codegen.InjectionMethods.ProvisionMethod;
import dagger.model.DependencyRequest;
import java.util.Optional;
import java.util.function.Function;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;

/**
 * A binding expression that invokes methods or constructors directly (without attempting to scope)
 * {@link dagger.model.RequestKind#INSTANCE} requests.
 */
final class SimpleMethodBindingExpression extends SimpleInvocationBindingExpression {
    private final CompilerOptions compilerOptions;
    private final ProvisionBinding provisionBinding;
    private final ComponentBindingExpressions componentBindingExpressions;
    private final MembersInjectionMethods membersInjectionMethods;
    private final ComponentRequirementExpressions componentRequirementExpressions;
    private final DaggerTypes types;
    private final DaggerElements elements;

    SimpleMethodBindingExpression(ResolvedBindings resolvedBindings, CompilerOptions compilerOptions,
            ComponentBindingExpressions componentBindingExpressions,
            MembersInjectionMethods membersInjectionMethods,
            ComponentRequirementExpressions componentRequirementExpressions, DaggerTypes types,
            DaggerElements elements) {
        super(resolvedBindings);
        this.compilerOptions = compilerOptions;
        this.provisionBinding = (ProvisionBinding) resolvedBindings.contributionBinding();
        checkArgument(provisionBinding.implicitDependencies().isEmpty(),
                "framework deps are not currently supported");
        checkArgument(provisionBinding.bindingElement().isPresent());
        this.componentBindingExpressions = componentBindingExpressions;
        this.membersInjectionMethods = membersInjectionMethods;
        this.componentRequirementExpressions = componentRequirementExpressions;
        this.types = types;
        this.elements = elements;
    }

    @Override
    Expression getDependencyExpression(ClassName requestingClass) {
        ImmutableMap<DependencyRequest, Expression> arguments = ImmutableMap.copyOf(Maps
                .asMap(provisionBinding.dependencies(), request -> dependencyArgument(request, requestingClass)));
        Function<DependencyRequest, CodeBlock> argumentsFunction = request -> arguments.get(request).codeBlock();
        return requiresInjectionMethod(provisionBinding, arguments.values().asList(), compilerOptions,
                requestingClass.packageName(), types) ? invokeInjectionMethod(argumentsFunction, requestingClass)
                        : invokeMethod(argumentsFunction, requestingClass);
    }

    private Expression invokeMethod(Function<DependencyRequest, CodeBlock> argumentsFunction,
            ClassName requestingClass) {
        // TODO(dpb): align this with the contents of InlineMethods.create
        CodeBlock arguments = provisionBinding.dependencies().stream().map(argumentsFunction)
                .collect(toParametersCodeBlock());
        ExecutableElement method = asExecutable(provisionBinding.bindingElement().get());
        CodeBlock invocation;
        switch (method.getKind()) {
        case CONSTRUCTOR:
            invocation = CodeBlock.of("new $T($L)", constructorTypeName(requestingClass), arguments);
            break;
        case METHOD:
            CodeBlock module = moduleReference(requestingClass)
                    .orElse(CodeBlock.of("$T", provisionBinding.bindingTypeElement().get()));
            invocation = CodeBlock.of("$L.$L($L)", module, method.getSimpleName(), arguments);
            break;
        default:
            throw new IllegalStateException();
        }

        return Expression.create(simpleMethodReturnType(), invocation);
    }

    private TypeName constructorTypeName(ClassName requestingClass) {
        DeclaredType type = MoreTypes.asDeclared(provisionBinding.key().type());
        TypeName typeName = TypeName.get(type);
        if (type.getTypeArguments().stream()
                .allMatch(t -> isTypeAccessibleFrom(t, requestingClass.packageName()))) {
            return typeName;
        }
        return rawTypeName(typeName);
    }

    private Expression invokeInjectionMethod(Function<DependencyRequest, CodeBlock> argumentsFunction,
            ClassName requestingClass) {
        return injectMembers(ProvisionMethod.invoke(provisionBinding, argumentsFunction, requestingClass,
                moduleReference(requestingClass), compilerOptions, elements));
    }

    private Expression dependencyArgument(DependencyRequest dependency, ClassName requestingClass) {
        return componentBindingExpressions.getDependencyArgumentExpression(dependency, requestingClass);
    }

    private Expression injectMembers(CodeBlock instance) {
        if (provisionBinding.injectionSites().isEmpty()) {
            return Expression.create(simpleMethodReturnType(), instance);
        }
        // Java 7 type inference can't figure out that instance in
        // injectParameterized(Parameterized_Factory.newParameterized()) is Parameterized<T> and not
        // Parameterized<Object>
        if (!MoreTypes.asDeclared(provisionBinding.key().type()).getTypeArguments().isEmpty()) {
            TypeName keyType = TypeName.get(provisionBinding.key().type());
            instance = CodeBlock.of("($T) ($T) $L", keyType, rawTypeName(keyType), instance);
        }

        MethodSpec membersInjectionMethod = membersInjectionMethods.getOrCreate(provisionBinding.key());
        TypeMirror returnType = membersInjectionMethod.returnType.equals(TypeName.OBJECT)
                ? elements.getTypeElement(Object.class).asType()
                : provisionBinding.key().type();
        return Expression.create(returnType, CodeBlock.of("$N($L)", membersInjectionMethod, instance));
    }

    private Optional<CodeBlock> moduleReference(ClassName requestingClass) {
        return provisionBinding.requiresModuleInstance()
                ? provisionBinding.contributingModule().map(Element::asType).map(ComponentRequirement::forModule)
                        .map(module -> componentRequirementExpressions.getExpression(module, requestingClass))
                : Optional.empty();
    }

    private TypeMirror simpleMethodReturnType() {
        return provisionBinding.contributedPrimitiveType().orElse(provisionBinding.key().type());
    }
}