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