blob: d2ebf4ef3041ad7e1c1ccfe55751921544b90428 [file] [log] [blame]
ronshapiroe05f9212017-08-08 11:22:11 -07001/*
2 * Copyright (C) 2017 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.CaseFormat.LOWER_CAMEL;
20import static com.google.common.base.CaseFormat.UPPER_CAMEL;
21import static com.squareup.javapoet.MethodSpec.methodBuilder;
22import static dagger.internal.codegen.Accessibility.isElementAccessibleFrom;
23import static dagger.internal.codegen.Accessibility.isRawTypeAccessible;
24import static dagger.internal.codegen.Accessibility.isRawTypePubliclyAccessible;
25import static dagger.internal.codegen.Accessibility.isTypeAccessibleFrom;
26import static dagger.internal.codegen.CodeBlocks.makeParametersCodeBlock;
27import static dagger.internal.codegen.CodeBlocks.toConcatenatedCodeBlock;
28import static dagger.internal.codegen.CodeBlocks.toParametersCodeBlock;
29import static dagger.internal.codegen.ConfigurationAnnotations.getNullableType;
30import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding;
31import static dagger.internal.codegen.SourceFiles.membersInjectorNameForType;
32import static dagger.internal.codegen.TypeNames.rawTypeName;
33import static java.util.stream.Collectors.toList;
34import static javax.lang.model.element.Modifier.PUBLIC;
35import static javax.lang.model.element.Modifier.STATIC;
36import static javax.lang.model.type.TypeKind.VOID;
37
38import com.google.auto.common.MoreElements;
39import com.google.common.collect.ImmutableList;
40import com.google.common.collect.ImmutableSet;
41import com.squareup.javapoet.ClassName;
42import com.squareup.javapoet.CodeBlock;
43import com.squareup.javapoet.MethodSpec;
44import com.squareup.javapoet.ParameterSpec;
45import com.squareup.javapoet.TypeName;
46import com.squareup.javapoet.TypeVariableName;
47import dagger.internal.codegen.DependencyRequest.Kind;
48import dagger.internal.codegen.MembersInjectionBinding.InjectionSite;
49import java.util.ArrayList;
50import java.util.List;
51import java.util.Optional;
52import java.util.function.Function;
53import javax.lang.model.element.ExecutableElement;
54import javax.lang.model.element.Parameterizable;
55import javax.lang.model.element.TypeElement;
56import javax.lang.model.element.TypeParameterElement;
57import javax.lang.model.element.VariableElement;
58import javax.lang.model.type.TypeKind;
59import javax.lang.model.type.TypeMirror;
ronshapiro55cd3922017-08-10 09:48:08 -070060import javax.lang.model.util.Types;
ronshapiroe05f9212017-08-08 11:22:11 -070061
62/**
63 * Injection methods are static methods that implement provision and/or injection in one step:
64 *
65 * <ul>
66 * <li>methods that invoke {@code @Inject} constructors and do members injection if necessary
67 * <li>methods that call {@code @Provides} module methods
68 * <li>methods that perform members injection
69 * </ul>
70 */
71// TODO(ronshapiro): add examples for each class of injection method
72final class InjectionMethods {
73 /**
74 * A static method that returns an object from a {@code @Provides} method or an {@code @Inject}ed
75 * constructor. Its parameters match the dependency requests for constructor and members
76 * injection.
77 *
78 * <p>For {@code @Provides} methods named "foo", the method name is "proxyFoo". If the
79 * {@code @Provides} method and its raw parameter types are publicly accessible, no method is
80 * necessary and this method returns {@link Optional#empty()}.
81 *
82 * <p>TODO(ronshapiro): At the moment non-static {@code @Provides} methods are not supported.
83 *
84 * <p>Example:
85 *
86 * <pre><code>
87 * abstract class FooModule {
88 * {@literal @Provides} static Foo provideFoo(Bar bar, Baz baz) { … }
89 * }
90 *
91 * public static proxyProvideFoo(Bar bar, Baz baz) { … }
92 * </code></pre>
93 *
94 * <p>For {@code @Inject}ed constructors, the method name is "newFoo". If the constructor and its
95 * raw parameter types are publicly accessible, no method is necessary and this method returns
96 * {@code Optional#empty()}.
97 *
98 * <p>Example:
99 *
100 * <pre><code>
101 * class Foo {
102 * {@literal @Inject} Foo(Bar bar) {}
103 * }
104 *
105 * public static Foo newFoo(Bar bar) { … }
106 * </code></pre>
107 */
108 static final class ProvisionMethod {
109
110 /**
111 * Returns a method that invokes the binding's {@linkplain ProvisionBinding#bindingElement()
112 * constructor} and injects the instance's members, if necessary. If {@link
113 * #shouldCreateInjectionMethod(ProvisionBinding) no method is necessary}, then {@link
114 * Optional#empty()} is returned.
115 */
116 static Optional<MethodSpec> create(ProvisionBinding binding) {
117 if (!shouldCreateInjectionMethod(binding)) {
118 return Optional.empty();
119 }
120 ExecutableElement element = MoreElements.asExecutable(binding.bindingElement().get());
121 switch (element.getKind()) {
122 case CONSTRUCTOR:
123 return Optional.of(constructorProxy(element));
124 case METHOD:
125 return Optional.of(
126 methodProxy(element, methodName(element), ReceiverAccessibility.IGNORE));
127 default:
128 throw new AssertionError(element);
129 }
130 }
131
132 /**
133 * Invokes the injection method for {@code binding}, with the dependencies transformed with the
134 * {@code dependencyUsage} function.
135 */
136 static CodeBlock invoke(
137 ProvisionBinding binding,
138 Function<DependencyRequest, CodeBlock> dependencyUsage,
139 ClassName requestingClass) {
140 return callInjectionMethod(
141 create(binding).get().name,
142 // TODO(dpb): would this be simpler if injectionMethodArguments returned a List?
143 ImmutableList.of(
144 injectionMethodArguments(
145 binding.provisionDependencies(), dependencyUsage, requestingClass)),
146 generatedClassNameForBinding(binding),
147 requestingClass);
148 }
149
150 private static MethodSpec constructorProxy(ExecutableElement constructor) {
151 UniqueNameSet names = new UniqueNameSet();
152 TypeElement enclosingType = MoreElements.asType(constructor.getEnclosingElement());
153 MethodSpec.Builder method =
154 methodBuilder(methodName(constructor))
155 .returns(TypeName.get(enclosingType.asType()))
156 .addModifiers(PUBLIC, STATIC);
157
158 copyTypeParameters(enclosingType, method);
159 copyThrows(constructor, method);
160
161 return method
162 .addStatement(
163 "return new $T($L)", enclosingType, copyParameters(constructor, method, names))
164 .build();
165 }
166
167 /**
168 * Returns {@code true} if injecting an instance of {@code binding} from {@code callingPackage}
169 * requires the use of an injection method.
170 */
171 static boolean requiresInjectionMethod(ProvisionBinding binding, String callingPackage) {
172 ExecutableElement method = MoreElements.asExecutable(binding.bindingElement().get());
173 return !binding.injectionSites().isEmpty()
174 || !isElementAccessibleFrom(method, callingPackage)
175 || method
176 .getParameters()
177 .stream()
178 .map(VariableElement::asType)
179 .anyMatch(type -> !isRawTypeAccessible(type, callingPackage));
180 }
181
182 private static boolean shouldCreateInjectionMethod(ProvisionBinding binding) {
183 return requiresInjectionMethod(binding, "dagger.should.never.exist");
184 }
185
186 /**
187 * Returns the name of the {@code static} method that wraps {@code method}. For methods that are
188 * associated with {@code @Inject} constructors, the method will also inject all {@link
189 * InjectionSite}s.
190 */
191 private static String methodName(ExecutableElement method) {
192 switch (method.getKind()) {
193 case CONSTRUCTOR:
194 return "new" + method.getEnclosingElement().getSimpleName();
195 case METHOD:
196 return "proxy" + LOWER_CAMEL.to(UPPER_CAMEL, method.getSimpleName().toString());
197 default:
198 throw new AssertionError(method);
199 }
200 }
201 }
202
203 /**
204 * A static method that injects one member of an instance of a type. Its first parameter is an
205 * instance of the type to be injected. The remaining parameters match the dependency requests for
206 * the injection site.
207 *
208 * <p>Example:
209 *
210 * <pre><code>
211 * class Foo {
212 * {@literal @Inject} Bar bar;
213 * {@literal @Inject} void setThings(Baz baz, Qux qux) {}
214 * }
215 *
216 * public static injectBar(Foo instance, Bar bar) { … }
217 * public static injectSetThings(Foo instance, Baz baz, Qux qux) { … }
218 * </code></pre>
219 */
220 static final class InjectionSiteMethod {
221 /**
222 * When a type has an inaccessible member from a supertype (e.g. an @Inject field in a parent
223 * that's in a different package), a method in the supertype's package must be generated to give
224 * the subclass's members injector a way to inject it. Each potentially inaccessible member
225 * receives its own method, as the subclass may need to inject them in a different order from
226 * the parent class.
227 */
228 static MethodSpec create(InjectionSite injectionSite) {
229 String methodName = methodName(injectionSite);
230 switch (injectionSite.kind()) {
231 case METHOD:
232 return methodProxy(
233 MoreElements.asExecutable(injectionSite.element()),
234 methodName,
235 ReceiverAccessibility.CAST_IF_NOT_PUBLIC);
236 case FIELD:
237 return fieldProxy(MoreElements.asVariable(injectionSite.element()), methodName);
238 default:
239 throw new AssertionError(injectionSite);
240 }
241 }
242
243 /**
244 * Invokes each of the injection methods for {@code injectionSites}, with the dependencies
245 * transformed using the {@code dependencyUsage} function.
ronshapiro55cd3922017-08-10 09:48:08 -0700246 *
247 * @param instanceType the type of the {@code instance} parameter
ronshapiroe05f9212017-08-08 11:22:11 -0700248 */
249 static CodeBlock invokeAll(
250 ImmutableSet<InjectionSite> injectionSites,
251 ClassName generatedTypeName,
252 CodeBlock instanceCodeBlock,
ronshapiro55cd3922017-08-10 09:48:08 -0700253 TypeMirror instanceType,
254 Types types,
ronshapiroe05f9212017-08-08 11:22:11 -0700255 Function<DependencyRequest, CodeBlock> dependencyUsage) {
256 return injectionSites
257 .stream()
258 .map(
ronshapiro55cd3922017-08-10 09:48:08 -0700259 injectionSite -> {
260 TypeMirror injectSiteType =
261 types.erasure(injectionSite.element().getEnclosingElement().asType());
262
263 // If instance has been declared as Object because it is not accessible from the
264 // component, but the injectionSite is in a supertype of instanceType that is
265 // publicly accessible, the InjectionSiteMethod will request the actual type and not
266 // Object as the first parameter. If so, cast to the supertype which is accessible
267 // from within generatedTypeName
268 CodeBlock maybeCastedInstance =
269 !types.isSubtype(instanceType, injectSiteType)
270 && isTypeAccessibleFrom(injectSiteType, generatedTypeName.packageName())
271 ? CodeBlock.of("($T) $L", injectSiteType, instanceCodeBlock)
272 : instanceCodeBlock;
273 return CodeBlock.of(
274 "$L;",
275 invoke(injectionSite, generatedTypeName, maybeCastedInstance, dependencyUsage));
276 })
ronshapiroe05f9212017-08-08 11:22:11 -0700277 .collect(toConcatenatedCodeBlock());
278 }
279
280 /**
281 * Invokes the injection method for {@code injectionSite}, with the dependencies transformed
282 * using the {@code dependencyUsage} function.
283 */
284 private static CodeBlock invoke(
285 InjectionSite injectionSite,
286 ClassName generatedTypeName,
287 CodeBlock instanceCodeBlock,
288 Function<DependencyRequest, CodeBlock> dependencyUsage) {
289 List<CodeBlock> arguments = new ArrayList<>();
290 arguments.add(instanceCodeBlock);
291 if (!injectionSite.dependencies().isEmpty()) {
292 arguments.addAll(
293 injectionSite
294 .dependencies()
295 .stream()
296 .map(dependencyUsage)
297 .collect(toList()));
298 }
299 return callInjectionMethod(
300 create(injectionSite).name,
301 arguments,
302 membersInjectorNameForType(
303 MoreElements.asType(injectionSite.element().getEnclosingElement())),
304 generatedTypeName);
305 }
306
307 /*
308 * TODO(ronshapiro): this isn't perfect, as collisions could still exist. Some examples:
309 *
310 * - @Inject void members() {} will generate a method that conflicts with the instance
311 * method `injectMembers(T)`
312 * - Adding the index could conflict with another member:
313 * @Inject void a(Object o) {}
314 * @Inject void a(String s) {}
315 * @Inject void a1(String s) {}
316 *
317 * Here, Method a(String) will add the suffix "1", which will conflict with the method
318 * generated for a1(String)
319 * - Members named "members" or "methods" could also conflict with the {@code static} injection
320 * method.
321 */
322 private static String methodName(InjectionSite injectionSite) {
323 int index = injectionSite.indexAmongAtInjectMembersWithSameSimpleName();
324 String indexString = index == 0 ? "" : String.valueOf(index + 1);
325 return "inject"
326 + LOWER_CAMEL.to(UPPER_CAMEL, injectionSite.element().getSimpleName().toString())
327 + indexString;
328 }
329 }
330
331 /**
332 * Returns an argument list suitable for calling an injection method. Down-casts any arguments
333 * that are {@code Object} (or {@code Provider<Object>}) at the caller but not the method.
334 *
335 * @param dependencies the dependencies used by the method
336 * @param dependencyUsage function to apply on each of {@code dependencies} before casting
337 * @param requestingClass the class calling the injection method
338 */
339 private static CodeBlock injectionMethodArguments(
340 ImmutableSet<DependencyRequest> dependencies,
341 Function<DependencyRequest, CodeBlock> dependencyUsage,
342 ClassName requestingClass) {
343 return dependencies.stream()
344 .map(dep -> injectionMethodArgument(dep, dependencyUsage.apply(dep), requestingClass))
345 .collect(toParametersCodeBlock());
346 }
347
348 private static CodeBlock injectionMethodArgument(
349 DependencyRequest dependency, CodeBlock argument, ClassName generatedTypeName) {
350 TypeMirror keyType = dependency.key().type();
351 CodeBlock.Builder codeBlock = CodeBlock.builder();
352 if (!isRawTypeAccessible(keyType, generatedTypeName.packageName())
353 && isTypeAccessibleFrom(keyType, generatedTypeName.packageName())) {
354 if (!dependency.kind().equals(Kind.INSTANCE)) {
355 TypeName usageTypeName = accessibleType(dependency);
356 codeBlock.add("($T) ($T)", usageTypeName, rawTypeName(usageTypeName));
357 } else if (dependency.requestElement().get().asType().getKind().equals(TypeKind.TYPEVAR)) {
358 codeBlock.add("($T)", keyType);
359 }
360 }
361 return codeBlock.add(argument).build();
362 }
363
364 /**
365 * Returns the parameter type for {@code dependency}. If the raw type is not accessible, returns
366 * {@link Object}.
367 */
368 private static TypeName accessibleType(DependencyRequest dependency) {
369 TypeName typeName = dependency.kind().typeName(accessibleType(dependency.key().type()));
370 return dependency.requestsPrimitiveType() ? typeName.unbox() : typeName;
371 }
372
373 /**
374 * Returns the accessible type for {@code type}. If the raw type is not accessible, returns {@link
375 * Object}.
376 */
377 private static TypeName accessibleType(TypeMirror type) {
378 return isRawTypePubliclyAccessible(type) ? TypeName.get(type) : TypeName.OBJECT;
379 }
380
381 private static CodeBlock callInjectionMethod(
382 String methodName,
383 List<CodeBlock> arguments,
384 ClassName enclosingClass,
385 ClassName requestingClass) {
386 CodeBlock.Builder invocation = CodeBlock.builder();
387 if (!enclosingClass.equals(requestingClass)) {
388 invocation.add("$T.", enclosingClass);
389 }
390 return invocation.add("$L($L)", methodName, makeParametersCodeBlock(arguments)).build();
391 }
392
393 private enum ReceiverAccessibility {
394 CAST_IF_NOT_PUBLIC {
395 @Override
396 TypeName parameterType(TypeMirror type) {
397 return accessibleType(type);
398 }
399
400 @Override
401 CodeBlock potentiallyCast(CodeBlock instance, TypeMirror instanceType) {
402 return instanceWithPotentialCast(instance, instanceType);
403 }
404 },
405 IGNORE {
406 @Override
407 TypeName parameterType(TypeMirror type) {
408 return TypeName.get(type);
409 }
410
411 @Override
412 CodeBlock potentiallyCast(CodeBlock instance, TypeMirror instanceType) {
413 return instance;
414 }
415 },
416 ;
417
418 abstract TypeName parameterType(TypeMirror type);
419 abstract CodeBlock potentiallyCast(CodeBlock instance, TypeMirror instanceType);
420 }
421
422 private static CodeBlock instanceWithPotentialCast(CodeBlock instance, TypeMirror instanceType) {
423 return isRawTypePubliclyAccessible(instanceType)
424 ? instance
425 : CodeBlock.of("(($T) $L)", instanceType, instance);
426 }
427
428 private static MethodSpec methodProxy(
429 ExecutableElement method, String methodName, ReceiverAccessibility receiverAccessibility) {
430 TypeElement enclosingType = MoreElements.asType(method.getEnclosingElement());
431 MethodSpec.Builder methodBuilder = methodBuilder(methodName).addModifiers(PUBLIC, STATIC);
432
433 UniqueNameSet nameSet = new UniqueNameSet();
434 if (!method.getModifiers().contains(STATIC)) {
435 methodBuilder.addParameter(
436 receiverAccessibility.parameterType(enclosingType.asType()),
437 nameSet.getUniqueName("instance"));
438 }
439 CodeBlock arguments = copyParameters(method, methodBuilder, nameSet);
440 if (!method.getReturnType().getKind().equals(VOID)) {
441 methodBuilder.returns(TypeName.get(method.getReturnType()));
442 getNullableType(method)
443 .ifPresent(nullableType -> CodeBlocks.addAnnotation(methodBuilder, nullableType));
444 methodBuilder.addCode("return ");
445 }
446 if (method.getModifiers().contains(STATIC)) {
447 methodBuilder.addCode("$T", rawTypeName(TypeName.get(enclosingType.asType())));
448 } else {
449 copyTypeParameters(enclosingType, methodBuilder);
450 // "instance" is guaranteed b/c it was the first name into the UniqueNameSet
451 methodBuilder.addCode(
452 receiverAccessibility.potentiallyCast(CodeBlock.of("instance"), enclosingType.asType()));
453 }
454 copyTypeParameters(method, methodBuilder);
455 copyThrows(method, methodBuilder);
456
457 methodBuilder.addCode(".$N($L);", method.getSimpleName(), arguments);
458 return methodBuilder.build();
459 }
460
461 private static MethodSpec fieldProxy(VariableElement field, String methodName) {
462 TypeElement enclosingType = MoreElements.asType(field.getEnclosingElement());
463 MethodSpec.Builder methodBuilder = methodBuilder(methodName).addModifiers(PUBLIC, STATIC);
464 copyTypeParameters(enclosingType, methodBuilder);
465
466 UniqueNameSet nameSet = new UniqueNameSet();
467 String instanceName = nameSet.getUniqueName("instance");
468 methodBuilder.addParameter(accessibleType(enclosingType.asType()), instanceName);
469 return methodBuilder
470 .addCode(
471 "$L.$L = $L;",
472 instanceWithPotentialCast(CodeBlock.of(instanceName), enclosingType.asType()),
473 field.getSimpleName(),
474 copyParameter(field, methodBuilder, nameSet))
475 .build();
476 }
477
478 private static void copyThrows(ExecutableElement method, MethodSpec.Builder methodBuilder) {
479 for (TypeMirror thrownType : method.getThrownTypes()) {
480 methodBuilder.addException(TypeName.get(thrownType));
481 }
482 }
483
484 private static CodeBlock copyParameters(
485 ExecutableElement method, MethodSpec.Builder methodBuilder, UniqueNameSet nameSet) {
486 ImmutableList.Builder<CodeBlock> argumentsBuilder = ImmutableList.builder();
487 for (VariableElement parameter : method.getParameters()) {
488 argumentsBuilder.add(copyParameter(parameter, methodBuilder, nameSet));
489 }
490 methodBuilder.varargs(method.isVarArgs());
491 return makeParametersCodeBlock(argumentsBuilder.build());
492 }
493
494 private static CodeBlock copyParameter(
495 VariableElement element, MethodSpec.Builder methodBuilder, UniqueNameSet nameSet) {
496 TypeMirror elementType = element.asType();
497 boolean useObject = !isRawTypePubliclyAccessible(elementType);
498 TypeName typeName = useObject ? TypeName.OBJECT : TypeName.get(elementType);
499 String name = nameSet.getUniqueName(element.getSimpleName().toString());
500 ParameterSpec parameter =
501 ParameterSpec.builder(typeName, name).build();
502 methodBuilder.addParameter(parameter);
503 return useObject
504 ? CodeBlock.of("($T) $N", elementType, parameter)
505 : CodeBlock.of("$N", parameter);
506 }
507
508 private static void copyTypeParameters(
509 Parameterizable parameterizable, MethodSpec.Builder methodBuilder) {
510 for (TypeParameterElement typeParameterElement : parameterizable.getTypeParameters()) {
511 methodBuilder.addTypeVariable(TypeVariableName.get(typeParameterElement));
512 }
513 }
514}