/* * Copyright (C) 2015 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.Preconditions.checkNotNull; import static dagger.internal.codegen.binding.ContributionBinding.FactoryCreationStrategy.SINGLETON_INSTANCE; import static dagger.internal.codegen.binding.SourceFiles.bindingTypeElementTypeVariableNames; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; import static dagger.internal.codegen.binding.SourceFiles.setFactoryClassName; import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; import static dagger.internal.codegen.javapoet.TypeNames.FACTORY; import static dagger.internal.codegen.javapoet.TypeNames.MAP_FACTORY; import static dagger.internal.codegen.javapoet.TypeNames.PRODUCER; import static dagger.internal.codegen.javapoet.TypeNames.PRODUCERS; import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; import static javax.lang.model.type.TypeKind.DECLARED; import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableList; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.TypeVariableName; import dagger.internal.codegen.base.SetType; import dagger.internal.codegen.binding.BindingType; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.javapoet.CodeBlocks; import java.util.List; import java.util.Optional; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; /** * Represents a {@link com.sun.source.tree.MemberSelectTree} as a {@link CodeBlock}. */ abstract class MemberSelect { /** * Returns a {@link MemberSelect} that accesses the field given by {@code fieldName} owned by * {@code owningClass}. In this context "local" refers to the fact that the field is owned by the * type (or an enclosing type) from which the code block will be used. The returned * {@link MemberSelect} will not be valid for accessing the field from a different class * (regardless of accessibility). */ static MemberSelect localField(ClassName owningClass, String fieldName) { return new LocalField(owningClass, fieldName); } private static final class LocalField extends MemberSelect { final String fieldName; LocalField(ClassName owningClass, String fieldName) { super(owningClass, false); this.fieldName = checkNotNull(fieldName); } @Override CodeBlock getExpressionFor(ClassName usingClass) { return owningClass().equals(usingClass) ? CodeBlock.of("$N", fieldName) : CodeBlock.of("$T.this.$N", owningClass(), fieldName); } } /** * Returns a {@link MemberSelect} that accesses the method given by {@code methodName} owned by * {@code owningClass}. In this context "local" refers to the fact that the method is owned by the * type (or an enclosing type) from which the code block will be used. The returned {@link * MemberSelect} will not be valid for accessing the method from a different class (regardless of * accessibility). */ static MemberSelect localMethod(ClassName owningClass, String methodName) { return new LocalMethod(owningClass, methodName); } private static final class LocalMethod extends MemberSelect { final String methodName; LocalMethod(ClassName owningClass, String methodName) { super(owningClass, false); this.methodName = checkNotNull(methodName); } @Override CodeBlock getExpressionFor(ClassName usingClass) { return owningClass().equals(usingClass) ? CodeBlock.of("$N()", methodName) : CodeBlock.of("$T.this.$N()", owningClass(), methodName); } } /** * If {@code resolvedBindings} is an unscoped provision binding with no factory arguments or a * no-op members injection binding, then we don't need a field to hold its factory. In that case, * this method returns the static member select that returns the factory or no-op members * injector. */ static Optional staticFactoryCreation(ContributionBinding contributionBinding) { if (contributionBinding.factoryCreationStrategy().equals(SINGLETON_INSTANCE) && !contributionBinding.scope().isPresent()) { switch (contributionBinding.kind()) { case MULTIBOUND_MAP: return Optional.of(emptyMapFactory(contributionBinding)); case MULTIBOUND_SET: return Optional.of(emptySetFactory(contributionBinding)); case INJECTION: case PROVISION: TypeMirror keyType = contributionBinding.key().type(); if (keyType.getKind().equals(DECLARED)) { ImmutableList typeVariables = bindingTypeElementTypeVariableNames(contributionBinding); if (!typeVariables.isEmpty()) { List typeArguments = ((DeclaredType) keyType).getTypeArguments(); return Optional.of( MemberSelect.parameterizedFactoryCreateMethod( generatedClassNameForBinding(contributionBinding), typeArguments)); } } // fall through default: return Optional.of( new StaticMethod( generatedClassNameForBinding(contributionBinding), CodeBlock.of("create()"))); } } return Optional.empty(); } /** * Returns a {@link MemberSelect} for the instance of a {@code create()} method on a factory. This * only applies for factories that do not have any dependencies. */ private static MemberSelect parameterizedFactoryCreateMethod( ClassName owningClass, List parameters) { return new ParameterizedStaticMethod( owningClass, ImmutableList.copyOf(parameters), CodeBlock.of("create()"), FACTORY); } private static final class StaticMethod extends MemberSelect { final CodeBlock methodCodeBlock; StaticMethod(ClassName owningClass, CodeBlock methodCodeBlock) { super(owningClass, true); this.methodCodeBlock = checkNotNull(methodCodeBlock); } @Override CodeBlock getExpressionFor(ClassName usingClass) { return owningClass().equals(usingClass) ? methodCodeBlock : CodeBlock.of("$T.$L", owningClass(), methodCodeBlock); } } /** A {@link MemberSelect} for a factory of an empty map. */ private static MemberSelect emptyMapFactory(ContributionBinding contributionBinding) { BindingType bindingType = contributionBinding.bindingType(); ImmutableList typeParameters = ImmutableList.copyOf( MoreTypes.asDeclared(contributionBinding.key().type()).getTypeArguments()); if (bindingType.equals(BindingType.PRODUCTION)) { return new ParameterizedStaticMethod( PRODUCERS, typeParameters, CodeBlock.of("emptyMapProducer()"), PRODUCER); } else { return new ParameterizedStaticMethod( MAP_FACTORY, typeParameters, CodeBlock.of("emptyMapProvider()"), PROVIDER); } } /** * A static member select for an empty set factory. Calls {@link * dagger.internal.SetFactory#empty()}, {@link dagger.producers.internal.SetProducer#empty()}, or * {@link dagger.producers.internal.SetOfProducedProducer#empty()}, depending on the set bindings. */ private static MemberSelect emptySetFactory(ContributionBinding binding) { return new ParameterizedStaticMethod( setFactoryClassName(binding), ImmutableList.of(SetType.from(binding.key()).elementType()), CodeBlock.of("empty()"), FACTORY); } private static final class ParameterizedStaticMethod extends MemberSelect { final ImmutableList typeParameters; final CodeBlock methodCodeBlock; final ClassName rawReturnType; ParameterizedStaticMethod( ClassName owningClass, ImmutableList typeParameters, CodeBlock methodCodeBlock, ClassName rawReturnType) { super(owningClass, true); this.typeParameters = typeParameters; this.methodCodeBlock = methodCodeBlock; this.rawReturnType = rawReturnType; } @Override CodeBlock getExpressionFor(ClassName usingClass) { boolean accessible = true; for (TypeMirror typeParameter : typeParameters) { accessible &= isTypeAccessibleFrom(typeParameter, usingClass.packageName()); } if (accessible) { return CodeBlock.of( "$T.<$L>$L", owningClass(), typeParameters.stream().map(CodeBlocks::type).collect(toParametersCodeBlock()), methodCodeBlock); } else { return CodeBlock.of("(($T) $T.$L)", rawReturnType, owningClass(), methodCodeBlock); } } } private final ClassName owningClass; private final boolean staticMember; MemberSelect(ClassName owningClass, boolean staticMemeber) { this.owningClass = owningClass; this.staticMember = staticMemeber; } /** Returns the class that owns the member being selected. */ ClassName owningClass() { return owningClass; } /** * Returns true if the member being selected is static and does not require an instance of * {@link #owningClass()}. */ boolean staticMember() { return staticMember; } /** * Returns a {@link CodeBlock} suitable for accessing the member from the given {@code * usingClass}. */ abstract CodeBlock getExpressionFor(ClassName usingClass); }