• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.internal.codegen;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.squareup.javapoet.MethodSpec.methodBuilder;
21 import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock;
22 import static dagger.internal.codegen.langmodel.Accessibility.isRawTypePubliclyAccessible;
23 import static javax.lang.model.element.Modifier.PUBLIC;
24 import static javax.lang.model.element.Modifier.STATIC;
25 
26 import com.google.auto.value.AutoValue;
27 import com.google.common.collect.ImmutableList;
28 import com.google.common.collect.ImmutableMap;
29 import com.google.errorprone.annotations.CanIgnoreReturnValue;
30 import com.google.errorprone.annotations.CheckReturnValue;
31 import com.squareup.javapoet.ClassName;
32 import com.squareup.javapoet.CodeBlock;
33 import com.squareup.javapoet.MethodSpec;
34 import com.squareup.javapoet.ParameterSpec;
35 import com.squareup.javapoet.TypeName;
36 import com.squareup.javapoet.TypeVariableName;
37 import dagger.internal.codegen.javapoet.CodeBlocks;
38 import dagger.internal.codegen.langmodel.DaggerElements;
39 import java.util.List;
40 import java.util.Optional;
41 import javax.lang.model.element.ExecutableElement;
42 import javax.lang.model.element.Parameterizable;
43 import javax.lang.model.element.VariableElement;
44 import javax.lang.model.type.DeclaredType;
45 import javax.lang.model.type.TypeMirror;
46 
47 /**
48  * A static method that implements provision and/or injection in one step:
49  *
50  * <ul>
51  *   <li>methods that invoke {@code @Inject} constructors and do members injection if necessary
52  *   <li>methods that call {@code @Provides} module methods
53  *   <li>methods that perform members injection
54  * </ul>
55  *
56  * <p>Note that although this type uses {@code @AutoValue}, it uses instance equality. It uses
57  * {@code @AutoValue} to avoid the boilerplate of writing a correct builder, but is not intended to
58  * actually be a value type.
59  */
60 @AutoValue
61 abstract class InjectionMethod {
name()62   abstract String name();
63 
varargs()64   abstract boolean varargs();
65 
typeVariables()66   abstract ImmutableList<TypeVariableName> typeVariables();
67 
parameters()68   abstract ImmutableMap<ParameterSpec, TypeMirror> parameters();
69 
returnType()70   abstract Optional<TypeMirror> returnType();
71 
nullableAnnotation()72   abstract Optional<DeclaredType> nullableAnnotation();
73 
exceptions()74   abstract ImmutableList<TypeMirror> exceptions();
75 
methodBody()76   abstract CodeBlock methodBody();
77 
enclosingClass()78   abstract ClassName enclosingClass();
79 
toMethodSpec()80   MethodSpec toMethodSpec() {
81     MethodSpec.Builder builder =
82         methodBuilder(name())
83             .addModifiers(PUBLIC, STATIC)
84             .varargs(varargs())
85             .addTypeVariables(typeVariables())
86             .addParameters(parameters().keySet())
87             .addCode(methodBody());
88     returnType().map(TypeName::get).ifPresent(builder::returns);
89     nullableAnnotation()
90         .ifPresent(nullableType -> CodeBlocks.addAnnotation(builder, nullableType));
91     exceptions().stream().map(TypeName::get).forEach(builder::addException);
92     return builder.build();
93   }
94 
invoke(List<CodeBlock> arguments, ClassName requestingClass)95   CodeBlock invoke(List<CodeBlock> arguments, ClassName requestingClass) {
96     checkArgument(arguments.size() == parameters().size());
97     CodeBlock.Builder invocation = CodeBlock.builder();
98     if (!enclosingClass().equals(requestingClass)) {
99       invocation.add("$T.", enclosingClass());
100     }
101     return invocation.add("$L($L)", name(), makeParametersCodeBlock(arguments)).build();
102   }
103 
104   @Override
hashCode()105   public final int hashCode() {
106     return System.identityHashCode(this);
107   }
108 
109   @Override
equals(Object obj)110   public final boolean equals(Object obj) {
111     return this == obj;
112   }
113 
builder(DaggerElements elements)114   static Builder builder(DaggerElements elements) {
115     Builder builder = new AutoValue_InjectionMethod.Builder();
116     builder.elements = elements;
117     builder.varargs(false).exceptions(ImmutableList.of()).nullableAnnotation(Optional.empty());
118     return builder;
119   }
120 
121   @CanIgnoreReturnValue
122   @AutoValue.Builder
123   abstract static class Builder {
124     private final UniqueNameSet parameterNames = new UniqueNameSet();
125     private final CodeBlock.Builder methodBody = CodeBlock.builder();
126     private DaggerElements elements;
127 
parametersBuilder()128     abstract ImmutableMap.Builder<ParameterSpec, TypeMirror> parametersBuilder();
typeVariablesBuilder()129     abstract ImmutableList.Builder<TypeVariableName> typeVariablesBuilder();
name(String name)130     abstract Builder name(String name);
varargs(boolean varargs)131     abstract Builder varargs(boolean varargs);
returnType(TypeMirror returnType)132     abstract Builder returnType(TypeMirror returnType);
exceptions(Iterable<? extends TypeMirror> exceptions)133     abstract Builder exceptions(Iterable<? extends TypeMirror> exceptions);
nullableAnnotation(Optional<DeclaredType> nullableAnnotation)134     abstract Builder nullableAnnotation(Optional<DeclaredType> nullableAnnotation);
methodBody(CodeBlock methodBody)135     abstract Builder methodBody(CodeBlock methodBody);
136 
methodBodyBuilder()137     final CodeBlock.Builder methodBodyBuilder() {
138       return methodBody;
139     }
140 
enclosingClass(ClassName enclosingClass)141     abstract Builder enclosingClass(ClassName enclosingClass);
142 
143     /**
144      * Adds a parameter for the given name and type. If another parameter has already been added
145      * with the same name, the name is disambiguated.
146      */
addParameter(String name, TypeMirror type)147     ParameterSpec addParameter(String name, TypeMirror type) {
148       ParameterSpec parameter =
149           ParameterSpec.builder(TypeName.get(type), parameterNames.getUniqueName(name)).build();
150       parametersBuilder().put(parameter, type);
151       return parameter;
152     }
153 
154     /**
155      * Calls {@link #copyParameter(VariableElement)} for each parameter of of {@code method}, and
156      * concatenates the results of each call, {@link CodeBlocks#makeParametersCodeBlock(Iterable)
157      * separated with commas}.
158      */
copyParameters(ExecutableElement method)159     CodeBlock copyParameters(ExecutableElement method) {
160       ImmutableList.Builder<CodeBlock> argumentsBuilder = ImmutableList.builder();
161       for (VariableElement parameter : method.getParameters()) {
162         argumentsBuilder.add(copyParameter(parameter));
163       }
164       varargs(method.isVarArgs());
165       return makeParametersCodeBlock(argumentsBuilder.build());
166     }
167 
168     /**
169      * Adds {@code parameter} as a parameter of this method, using a publicly accessible version of
170      * the parameter's type. Returns a {@link CodeBlock} of the usage of this parameter within the
171      * injection method's {@link #methodBody()}.
172      */
copyParameter(VariableElement parameter)173     CodeBlock copyParameter(VariableElement parameter) {
174       TypeMirror elementType = parameter.asType();
175       boolean useObject = !isRawTypePubliclyAccessible(elementType);
176       TypeMirror publicType =  useObject ? objectType() : elementType;
177       ParameterSpec parameterSpec = addParameter(parameter.getSimpleName().toString(), publicType);
178       return useObject
179           ? CodeBlock.of("($T) $N", elementType, parameterSpec)
180           : CodeBlock.of("$N", parameterSpec);
181     }
182 
objectType()183     private TypeMirror objectType() {
184       return elements.getTypeElement(Object.class).asType();
185     }
186 
187     /**
188      * Adds each type parameter of {@code parameterizable} as a type parameter of this injection
189      * method.
190      */
copyTypeParameters(Parameterizable parameterizable)191     Builder copyTypeParameters(Parameterizable parameterizable) {
192       parameterizable.getTypeParameters().stream()
193           .map(TypeVariableName::get)
194           .forEach(typeVariablesBuilder()::add);
195       return this;
196     }
197 
copyThrows(ExecutableElement element)198     Builder copyThrows(ExecutableElement element) {
199       exceptions(element.getThrownTypes());
200       return this;
201     }
202 
203     @CheckReturnValue
build()204     final InjectionMethod build() {
205       return methodBody(methodBody.build()).buildInternal();
206     }
207 
buildInternal()208     abstract InjectionMethod buildInternal();
209   }
210 }
211