dagger.internal.codegen.SourceFiles.java Source code

Java tutorial

Introduction

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

Source

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

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.Verify.verify;
import static dagger.internal.codegen.ContributionBinding.Kind.INJECTION;
import static dagger.internal.codegen.Optionals.optionalComparator;
import static dagger.internal.codegen.TypeNames.DOUBLE_CHECK;
import static dagger.internal.codegen.TypeNames.PROVIDER_OF_LAZY;
import static java.util.Comparator.comparing;
import static javax.lang.model.SourceVersion.isName;

import com.google.common.base.CaseFormat;
import com.google.common.base.Joiner;
import com.google.common.collect.FluentIterable;
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.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeVariableName;
import java.util.Comparator;
import java.util.Iterator;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;

/**
 * Utilities for generating files.
 *
 * @author Gregory Kick
 * @since 2.0
 */
class SourceFiles {

    private static final Joiner CLASS_FILE_NAME_JOINER = Joiner.on('_');

    /**
     * Sorts {@link DependencyRequest} instances in an order likely to reflect their logical
     * importance.
     */
    static final Comparator<DependencyRequest> DEPENDENCY_ORDERING =
            // put fields before parameters
            comparing((DependencyRequest request) -> request.requestElement().map(Element::getKind),
                    optionalComparator())
                            // order by dependency kind
                            .thenComparing(DependencyRequest::kind)
                            // then sort by name
                            .thenComparing(
                                    request -> request.requestElement()
                                            .map(element -> element.getSimpleName().toString()),
                                    optionalComparator());

    /**
     * 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
     *
     * <ul>
     * <li>represents all of the dependency requests for this key
     * <li>is <i>probably</i> associated with the type being bound
     * <li>is unique within the class
     * </ul>
     *
     * @param binding must be an unresolved binding (type parameters must match its type element's)
     */
    static ImmutableMap<BindingKey, FrameworkField> generateBindingFieldsForDependencies(Binding binding) {
        checkArgument(!binding.unresolved().isPresent(), "binding must be unresolved: %s", binding);

        ImmutableMap.Builder<BindingKey, FrameworkField> bindingFields = ImmutableMap.builder();
        for (Binding.DependencyAssociation dependencyAssociation : binding.dependencyAssociations()) {
            FrameworkDependency frameworkDependency = dependencyAssociation.frameworkDependency();
            bindingFields.put(frameworkDependency.bindingKey(),
                    FrameworkField.create(ClassName.get(frameworkDependency.frameworkClass()),
                            TypeName.get(frameworkDependency.bindingKey().key().type()),
                            fieldNameForDependency(dependencyAssociation.dependencyRequests())));
        }
        return bindingFields.build();
    }

    private static String fieldNameForDependency(ImmutableSet<DependencyRequest> dependencyRequests) {
        // collect together all of the names that we would want to call the provider
        ImmutableSet<String> dependencyNames = FluentIterable.from(dependencyRequests)
                .transform(new DependencyVariableNamer()).toSet();

        if (dependencyNames.size() == 1) {
            // if there's only one name, great! use it!
            return Iterables.getOnlyElement(dependencyNames);
        } else {
            // in the event that a field is being used for a bunch of deps with different names,
            // add all the names together with "And"s in the middle. E.g.: stringAndS
            Iterator<String> namesIterator = dependencyNames.iterator();
            String first = namesIterator.next();
            StringBuilder compositeNameBuilder = new StringBuilder(first);
            while (namesIterator.hasNext()) {
                compositeNameBuilder.append("And")
                        .append(CaseFormat.LOWER_CAMEL.to(UPPER_CAMEL, namesIterator.next()));
            }
            return compositeNameBuilder.toString();
        }
    }

    static CodeBlock frameworkTypeUsageStatement(CodeBlock frameworkTypeMemberSelect,
            DependencyRequest.Kind 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:
        case MEMBERS_INJECTOR:
            return frameworkTypeMemberSelect;
        case PROVIDER_OF_LAZY:
            return CodeBlock.of("$T.create($L)", PROVIDER_OF_LAZY, frameworkTypeMemberSelect);
        default: // including PRODUCED
            throw new AssertionError(dependencyKind);
        }
    }

    /**
     * Returns the generated factory or members injector name for a binding.
     */
    static ClassName generatedClassNameForBinding(Binding binding) {
        switch (binding.bindingType()) {
        case PROVISION:
        case PRODUCTION:
            ContributionBinding contribution = (ContributionBinding) binding;
            checkArgument(contribution.bindingTypeElement().isPresent());
            ClassName enclosingClassName = ClassName.get(contribution.bindingTypeElement().get());
            switch (contribution.bindingKind()) {
            case INJECTION:
            case PROVISION:
            case PRODUCTION:
                return enclosingClassName.topLevelClassName().peerClass(
                        classFileName(enclosingClassName) + "_" + factoryPrefix(contribution) + "Factory");

            default:
                throw new AssertionError();
            }

        case MEMBERS_INJECTION:
            return membersInjectorNameForType(((MembersInjectionBinding) binding).membersInjectedType());

        default:
            throw new AssertionError();
        }
    }

    static TypeName parameterizedGeneratedTypeNameForBinding(Binding binding) {
        ClassName className = generatedClassNameForBinding(binding);
        ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding);
        return typeParameters.isEmpty() ? className
                : ParameterizedTypeName.get(className, Iterables.toArray(typeParameters, TypeName.class));
    }

    static ClassName membersInjectorNameForType(TypeElement typeElement) {
        return siblingClassName(typeElement, "_MembersInjector");
    }

    static String classFileName(ClassName className) {
        return CLASS_FILE_NAME_JOINER.join(className.simpleNames());
    }

    static ClassName generatedMonitoringModuleName(TypeElement componentElement) {
        return siblingClassName(componentElement, "_MonitoringModule");
    }

    static ClassName generatedProductionExecutorModuleName(TypeElement componentElement) {
        return siblingClassName(componentElement, "_ProductionExecutorModule");
    }

    // TODO(ronshapiro): when JavaPoet migration is complete, replace the duplicated code
    // which could use this.
    private static ClassName siblingClassName(TypeElement typeElement, String suffix) {
        ClassName className = ClassName.get(typeElement);
        return className.topLevelClassName().peerClass(classFileName(className) + suffix);
    }

    private static String factoryPrefix(ContributionBinding binding) {
        switch (binding.bindingKind()) {
        case INJECTION:
            return "";

        case PROVISION:
        case PRODUCTION:
            return CaseFormat.LOWER_CAMEL.to(UPPER_CAMEL,
                    binding.bindingElement().get().getSimpleName().toString());

        default:
            throw new IllegalArgumentException();
        }
    }

    static ImmutableList<TypeVariableName> bindingTypeElementTypeVariableNames(Binding binding) {
        if (binding instanceof ContributionBinding) {
            ContributionBinding contributionBinding = (ContributionBinding) binding;
            if (!contributionBinding.bindingKind().equals(INJECTION)
                    && !contributionBinding.requiresModuleInstance()) {
                return ImmutableList.of();
            }
        }
        ImmutableList.Builder<TypeVariableName> builder = ImmutableList.builder();
        for (TypeParameterElement typeParameter : binding.bindingTypeElement().get().getTypeParameters()) {
            builder.add(TypeVariableName.get(typeParameter));
        }
        return builder.build();
    }

    /**
     * Returns a name to be used for variables of the given {@linkplain TypeElement 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?
    static String simpleVariableName(TypeElement typeElement) {
        String candidateName = UPPER_CAMEL.to(LOWER_CAMEL, typeElement.getSimpleName().toString());
        String variableName = protectAgainstKeywords(candidateName);
        verify(isName(variableName), "'%s' was expected to be a valid variable name");
        return variableName;
    }

    private 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;
        }
    }

    private SourceFiles() {
    }
}