blob: 6eff4dd5476f0477ee2157da15dabe1e0d7e0bde [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;
ronshapiroe05f9212017-08-08 11:22:11 -070028import static dagger.internal.codegen.ConfigurationAnnotations.getNullableType;
29import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding;
30import static dagger.internal.codegen.SourceFiles.membersInjectorNameForType;
31import static dagger.internal.codegen.TypeNames.rawTypeName;
ronshapirodc07ed52017-08-23 08:52:10 -070032import static dagger.internal.codegen.Util.toImmutableList;
ronshapiroe05f9212017-08-08 11:22:11 -070033import 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,
ronshapirodc07ed52017-08-23 08:52:10 -0700139 ClassName requestingClass,
140 Optional<CodeBlock> moduleReference) {
141 ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder();
142 moduleReference.ifPresent(arguments::add);
143 arguments.addAll(
144 injectionMethodArguments(
145 binding.provisionDependencies(), dependencyUsage, requestingClass));
ronshapiroe05f9212017-08-08 11:22:11 -0700146 return callInjectionMethod(
147 create(binding).get().name,
ronshapirodc07ed52017-08-23 08:52:10 -0700148 arguments.build(),
ronshapiroe05f9212017-08-08 11:22:11 -0700149 generatedClassNameForBinding(binding),
150 requestingClass);
151 }
152
153 private static MethodSpec constructorProxy(ExecutableElement constructor) {
154 UniqueNameSet names = new UniqueNameSet();
155 TypeElement enclosingType = MoreElements.asType(constructor.getEnclosingElement());
156 MethodSpec.Builder method =
157 methodBuilder(methodName(constructor))
158 .returns(TypeName.get(enclosingType.asType()))
159 .addModifiers(PUBLIC, STATIC);
160
161 copyTypeParameters(enclosingType, method);
162 copyThrows(constructor, method);
163
164 return method
165 .addStatement(
166 "return new $T($L)", enclosingType, copyParameters(constructor, method, names))
167 .build();
168 }
169
170 /**
171 * Returns {@code true} if injecting an instance of {@code binding} from {@code callingPackage}
172 * requires the use of an injection method.
173 */
174 static boolean requiresInjectionMethod(ProvisionBinding binding, String callingPackage) {
175 ExecutableElement method = MoreElements.asExecutable(binding.bindingElement().get());
176 return !binding.injectionSites().isEmpty()
177 || !isElementAccessibleFrom(method, callingPackage)
178 || method
179 .getParameters()
180 .stream()
181 .map(VariableElement::asType)
182 .anyMatch(type -> !isRawTypeAccessible(type, callingPackage));
183 }
184
185 private static boolean shouldCreateInjectionMethod(ProvisionBinding binding) {
186 return requiresInjectionMethod(binding, "dagger.should.never.exist");
187 }
188
189 /**
190 * Returns the name of the {@code static} method that wraps {@code method}. For methods that are
191 * associated with {@code @Inject} constructors, the method will also inject all {@link
192 * InjectionSite}s.
193 */
194 private static String methodName(ExecutableElement method) {
195 switch (method.getKind()) {
196 case CONSTRUCTOR:
197 return "new" + method.getEnclosingElement().getSimpleName();
198 case METHOD:
199 return "proxy" + LOWER_CAMEL.to(UPPER_CAMEL, method.getSimpleName().toString());
200 default:
201 throw new AssertionError(method);
202 }
203 }
204 }
205
206 /**
207 * A static method that injects one member of an instance of a type. Its first parameter is an
208 * instance of the type to be injected. The remaining parameters match the dependency requests for
209 * the injection site.
210 *
211 * <p>Example:
212 *
213 * <pre><code>
214 * class Foo {
215 * {@literal @Inject} Bar bar;
216 * {@literal @Inject} void setThings(Baz baz, Qux qux) {}
217 * }
218 *
219 * public static injectBar(Foo instance, Bar bar) { … }
220 * public static injectSetThings(Foo instance, Baz baz, Qux qux) { … }
221 * </code></pre>
222 */
223 static final class InjectionSiteMethod {
224 /**
225 * When a type has an inaccessible member from a supertype (e.g. an @Inject field in a parent
226 * that's in a different package), a method in the supertype's package must be generated to give
227 * the subclass's members injector a way to inject it. Each potentially inaccessible member
228 * receives its own method, as the subclass may need to inject them in a different order from
229 * the parent class.
230 */
231 static MethodSpec create(InjectionSite injectionSite) {
232 String methodName = methodName(injectionSite);
233 switch (injectionSite.kind()) {
234 case METHOD:
235 return methodProxy(
236 MoreElements.asExecutable(injectionSite.element()),
237 methodName,
238 ReceiverAccessibility.CAST_IF_NOT_PUBLIC);
239 case FIELD:
240 return fieldProxy(MoreElements.asVariable(injectionSite.element()), methodName);
241 default:
242 throw new AssertionError(injectionSite);
243 }
244 }
245
246 /**
247 * Invokes each of the injection methods for {@code injectionSites}, with the dependencies
248 * transformed using the {@code dependencyUsage} function.
ronshapiro55cd3922017-08-10 09:48:08 -0700249 *
250 * @param instanceType the type of the {@code instance} parameter
ronshapiroe05f9212017-08-08 11:22:11 -0700251 */
252 static CodeBlock invokeAll(
253 ImmutableSet<InjectionSite> injectionSites,
254 ClassName generatedTypeName,
255 CodeBlock instanceCodeBlock,
ronshapiro55cd3922017-08-10 09:48:08 -0700256 TypeMirror instanceType,
257 Types types,
ronshapiroe05f9212017-08-08 11:22:11 -0700258 Function<DependencyRequest, CodeBlock> dependencyUsage) {
259 return injectionSites
260 .stream()
261 .map(
ronshapiro55cd3922017-08-10 09:48:08 -0700262 injectionSite -> {
263 TypeMirror injectSiteType =
264 types.erasure(injectionSite.element().getEnclosingElement().asType());
265
266 // If instance has been declared as Object because it is not accessible from the
267 // component, but the injectionSite is in a supertype of instanceType that is
268 // publicly accessible, the InjectionSiteMethod will request the actual type and not
269 // Object as the first parameter. If so, cast to the supertype which is accessible
270 // from within generatedTypeName
271 CodeBlock maybeCastedInstance =
272 !types.isSubtype(instanceType, injectSiteType)
273 && isTypeAccessibleFrom(injectSiteType, generatedTypeName.packageName())
274 ? CodeBlock.of("($T) $L", injectSiteType, instanceCodeBlock)
275 : instanceCodeBlock;
276 return CodeBlock.of(
277 "$L;",
278 invoke(injectionSite, generatedTypeName, maybeCastedInstance, dependencyUsage));
279 })
ronshapiroe05f9212017-08-08 11:22:11 -0700280 .collect(toConcatenatedCodeBlock());
281 }
282
283 /**
284 * Invokes the injection method for {@code injectionSite}, with the dependencies transformed
285 * using the {@code dependencyUsage} function.
286 */
287 private static CodeBlock invoke(
288 InjectionSite injectionSite,
289 ClassName generatedTypeName,
290 CodeBlock instanceCodeBlock,
291 Function<DependencyRequest, CodeBlock> dependencyUsage) {
292 List<CodeBlock> arguments = new ArrayList<>();
293 arguments.add(instanceCodeBlock);
294 if (!injectionSite.dependencies().isEmpty()) {
295 arguments.addAll(
296 injectionSite
297 .dependencies()
298 .stream()
299 .map(dependencyUsage)
300 .collect(toList()));
301 }
302 return callInjectionMethod(
303 create(injectionSite).name,
304 arguments,
305 membersInjectorNameForType(
306 MoreElements.asType(injectionSite.element().getEnclosingElement())),
307 generatedTypeName);
308 }
309
310 /*
311 * TODO(ronshapiro): this isn't perfect, as collisions could still exist. Some examples:
312 *
313 * - @Inject void members() {} will generate a method that conflicts with the instance
314 * method `injectMembers(T)`
315 * - Adding the index could conflict with another member:
316 * @Inject void a(Object o) {}
317 * @Inject void a(String s) {}
318 * @Inject void a1(String s) {}
319 *
320 * Here, Method a(String) will add the suffix "1", which will conflict with the method
321 * generated for a1(String)
322 * - Members named "members" or "methods" could also conflict with the {@code static} injection
323 * method.
324 */
325 private static String methodName(InjectionSite injectionSite) {
326 int index = injectionSite.indexAmongAtInjectMembersWithSameSimpleName();
327 String indexString = index == 0 ? "" : String.valueOf(index + 1);
328 return "inject"
329 + LOWER_CAMEL.to(UPPER_CAMEL, injectionSite.element().getSimpleName().toString())
330 + indexString;
331 }
332 }
333
334 /**
335 * Returns an argument list suitable for calling an injection method. Down-casts any arguments
336 * that are {@code Object} (or {@code Provider<Object>}) at the caller but not the method.
337 *
338 * @param dependencies the dependencies used by the method
339 * @param dependencyUsage function to apply on each of {@code dependencies} before casting
340 * @param requestingClass the class calling the injection method
341 */
ronshapirodc07ed52017-08-23 08:52:10 -0700342 private static ImmutableList<CodeBlock> injectionMethodArguments(
ronshapiroe05f9212017-08-08 11:22:11 -0700343 ImmutableSet<DependencyRequest> dependencies,
344 Function<DependencyRequest, CodeBlock> dependencyUsage,
345 ClassName requestingClass) {
346 return dependencies.stream()
347 .map(dep -> injectionMethodArgument(dep, dependencyUsage.apply(dep), requestingClass))
ronshapirodc07ed52017-08-23 08:52:10 -0700348 .collect(toImmutableList());
ronshapiroe05f9212017-08-08 11:22:11 -0700349 }
350
351 private static CodeBlock injectionMethodArgument(
352 DependencyRequest dependency, CodeBlock argument, ClassName generatedTypeName) {
353 TypeMirror keyType = dependency.key().type();
354 CodeBlock.Builder codeBlock = CodeBlock.builder();
355 if (!isRawTypeAccessible(keyType, generatedTypeName.packageName())
356 && isTypeAccessibleFrom(keyType, generatedTypeName.packageName())) {
357 if (!dependency.kind().equals(Kind.INSTANCE)) {
358 TypeName usageTypeName = accessibleType(dependency);
359 codeBlock.add("($T) ($T)", usageTypeName, rawTypeName(usageTypeName));
360 } else if (dependency.requestElement().get().asType().getKind().equals(TypeKind.TYPEVAR)) {
361 codeBlock.add("($T)", keyType);
362 }
363 }
364 return codeBlock.add(argument).build();
365 }
366
367 /**
368 * Returns the parameter type for {@code dependency}. If the raw type is not accessible, returns
369 * {@link Object}.
370 */
371 private static TypeName accessibleType(DependencyRequest dependency) {
372 TypeName typeName = dependency.kind().typeName(accessibleType(dependency.key().type()));
373 return dependency.requestsPrimitiveType() ? typeName.unbox() : typeName;
374 }
375
376 /**
377 * Returns the accessible type for {@code type}. If the raw type is not accessible, returns {@link
378 * Object}.
379 */
380 private static TypeName accessibleType(TypeMirror type) {
381 return isRawTypePubliclyAccessible(type) ? TypeName.get(type) : TypeName.OBJECT;
382 }
383
384 private static CodeBlock callInjectionMethod(
385 String methodName,
386 List<CodeBlock> arguments,
387 ClassName enclosingClass,
388 ClassName requestingClass) {
389 CodeBlock.Builder invocation = CodeBlock.builder();
390 if (!enclosingClass.equals(requestingClass)) {
391 invocation.add("$T.", enclosingClass);
392 }
393 return invocation.add("$L($L)", methodName, makeParametersCodeBlock(arguments)).build();
394 }
395
396 private enum ReceiverAccessibility {
397 CAST_IF_NOT_PUBLIC {
398 @Override
399 TypeName parameterType(TypeMirror type) {
400 return accessibleType(type);
401 }
402
403 @Override
404 CodeBlock potentiallyCast(CodeBlock instance, TypeMirror instanceType) {
405 return instanceWithPotentialCast(instance, instanceType);
406 }
407 },
408 IGNORE {
409 @Override
410 TypeName parameterType(TypeMirror type) {
411 return TypeName.get(type);
412 }
413
414 @Override
415 CodeBlock potentiallyCast(CodeBlock instance, TypeMirror instanceType) {
416 return instance;
417 }
418 },
419 ;
420
421 abstract TypeName parameterType(TypeMirror type);
422 abstract CodeBlock potentiallyCast(CodeBlock instance, TypeMirror instanceType);
423 }
424
425 private static CodeBlock instanceWithPotentialCast(CodeBlock instance, TypeMirror instanceType) {
426 return isRawTypePubliclyAccessible(instanceType)
427 ? instance
428 : CodeBlock.of("(($T) $L)", instanceType, instance);
429 }
430
431 private static MethodSpec methodProxy(
432 ExecutableElement method, String methodName, ReceiverAccessibility receiverAccessibility) {
433 TypeElement enclosingType = MoreElements.asType(method.getEnclosingElement());
434 MethodSpec.Builder methodBuilder = methodBuilder(methodName).addModifiers(PUBLIC, STATIC);
435
436 UniqueNameSet nameSet = new UniqueNameSet();
437 if (!method.getModifiers().contains(STATIC)) {
438 methodBuilder.addParameter(
439 receiverAccessibility.parameterType(enclosingType.asType()),
440 nameSet.getUniqueName("instance"));
441 }
442 CodeBlock arguments = copyParameters(method, methodBuilder, nameSet);
443 if (!method.getReturnType().getKind().equals(VOID)) {
444 methodBuilder.returns(TypeName.get(method.getReturnType()));
445 getNullableType(method)
446 .ifPresent(nullableType -> CodeBlocks.addAnnotation(methodBuilder, nullableType));
447 methodBuilder.addCode("return ");
448 }
449 if (method.getModifiers().contains(STATIC)) {
450 methodBuilder.addCode("$T", rawTypeName(TypeName.get(enclosingType.asType())));
451 } else {
452 copyTypeParameters(enclosingType, methodBuilder);
453 // "instance" is guaranteed b/c it was the first name into the UniqueNameSet
454 methodBuilder.addCode(
455 receiverAccessibility.potentiallyCast(CodeBlock.of("instance"), enclosingType.asType()));
456 }
457 copyTypeParameters(method, methodBuilder);
458 copyThrows(method, methodBuilder);
459
460 methodBuilder.addCode(".$N($L);", method.getSimpleName(), arguments);
461 return methodBuilder.build();
462 }
463
464 private static MethodSpec fieldProxy(VariableElement field, String methodName) {
465 TypeElement enclosingType = MoreElements.asType(field.getEnclosingElement());
466 MethodSpec.Builder methodBuilder = methodBuilder(methodName).addModifiers(PUBLIC, STATIC);
467 copyTypeParameters(enclosingType, methodBuilder);
468
469 UniqueNameSet nameSet = new UniqueNameSet();
470 String instanceName = nameSet.getUniqueName("instance");
471 methodBuilder.addParameter(accessibleType(enclosingType.asType()), instanceName);
472 return methodBuilder
473 .addCode(
474 "$L.$L = $L;",
475 instanceWithPotentialCast(CodeBlock.of(instanceName), enclosingType.asType()),
476 field.getSimpleName(),
477 copyParameter(field, methodBuilder, nameSet))
478 .build();
479 }
480
481 private static void copyThrows(ExecutableElement method, MethodSpec.Builder methodBuilder) {
482 for (TypeMirror thrownType : method.getThrownTypes()) {
483 methodBuilder.addException(TypeName.get(thrownType));
484 }
485 }
486
487 private static CodeBlock copyParameters(
488 ExecutableElement method, MethodSpec.Builder methodBuilder, UniqueNameSet nameSet) {
489 ImmutableList.Builder<CodeBlock> argumentsBuilder = ImmutableList.builder();
490 for (VariableElement parameter : method.getParameters()) {
491 argumentsBuilder.add(copyParameter(parameter, methodBuilder, nameSet));
492 }
493 methodBuilder.varargs(method.isVarArgs());
494 return makeParametersCodeBlock(argumentsBuilder.build());
495 }
496
497 private static CodeBlock copyParameter(
498 VariableElement element, MethodSpec.Builder methodBuilder, UniqueNameSet nameSet) {
499 TypeMirror elementType = element.asType();
500 boolean useObject = !isRawTypePubliclyAccessible(elementType);
501 TypeName typeName = useObject ? TypeName.OBJECT : TypeName.get(elementType);
502 String name = nameSet.getUniqueName(element.getSimpleName().toString());
503 ParameterSpec parameter =
504 ParameterSpec.builder(typeName, name).build();
505 methodBuilder.addParameter(parameter);
506 return useObject
507 ? CodeBlock.of("($T) $N", elementType, parameter)
508 : CodeBlock.of("$N", parameter);
509 }
510
511 private static void copyTypeParameters(
512 Parameterizable parameterizable, MethodSpec.Builder methodBuilder) {
513 for (TypeParameterElement typeParameterElement : parameterizable.getTypeParameters()) {
514 methodBuilder.addTypeVariable(TypeVariableName.get(typeParameterElement));
515 }
516 }
517}