• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.binding;
18 
19 import static androidx.room.compiler.codegen.XTypeNameKt.toJavaPoet;
20 import static androidx.room.compiler.processing.XElementKt.isConstructor;
21 import static com.google.common.base.CaseFormat.LOWER_CAMEL;
22 import static com.google.common.base.CaseFormat.UPPER_CAMEL;
23 import static com.google.common.base.Preconditions.checkArgument;
24 import static com.google.common.base.Verify.verify;
25 import static dagger.internal.codegen.javapoet.TypeNames.DOUBLE_CHECK;
26 import static dagger.internal.codegen.javapoet.TypeNames.PRODUCER;
27 import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER;
28 import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER_OF_LAZY;
29 import static dagger.internal.codegen.model.BindingKind.ASSISTED_INJECTION;
30 import static dagger.internal.codegen.model.BindingKind.INJECTION;
31 import static dagger.internal.codegen.xprocessing.XElements.asExecutable;
32 import static dagger.internal.codegen.xprocessing.XElements.asMethod;
33 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
34 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
35 import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames;
36 import static dagger.internal.codegen.xprocessing.XTypeNames.simpleName;
37 import static javax.lang.model.SourceVersion.isName;
38 
39 import androidx.room.compiler.codegen.XClassName;
40 import androidx.room.compiler.processing.XExecutableElement;
41 import androidx.room.compiler.processing.XFieldElement;
42 import androidx.room.compiler.processing.XMethodElement;
43 import androidx.room.compiler.processing.XTypeElement;
44 import com.google.common.base.Joiner;
45 import com.google.common.collect.ImmutableList;
46 import com.google.common.collect.ImmutableMap;
47 import com.google.common.collect.ImmutableSet;
48 import com.google.common.collect.Iterables;
49 import com.google.common.collect.Maps;
50 import com.squareup.javapoet.ClassName;
51 import com.squareup.javapoet.CodeBlock;
52 import com.squareup.javapoet.FieldSpec;
53 import com.squareup.javapoet.ParameterizedTypeName;
54 import com.squareup.javapoet.TypeName;
55 import com.squareup.javapoet.TypeVariableName;
56 import dagger.internal.codegen.base.MapType;
57 import dagger.internal.codegen.base.SetType;
58 import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite;
59 import dagger.internal.codegen.javapoet.TypeNames;
60 import dagger.internal.codegen.model.DependencyRequest;
61 import dagger.internal.codegen.model.RequestKind;
62 import dagger.internal.codegen.xprocessing.XTypeNames;
63 import javax.inject.Inject;
64 import javax.lang.model.SourceVersion;
65 
66 /** Utilities for generating files. */
67 public final class SourceFiles {
68 
69   private static final Joiner CLASS_FILE_NAME_JOINER = Joiner.on('_');
70 
SourceFiles()71   @Inject SourceFiles() {}
72 
73   /**
74    * Generates names and keys for the factory class fields needed to hold the framework classes for
75    * all of the dependencies of {@code binding}. It is responsible for choosing a name that
76    *
77    * <ul>
78    *   <li>represents all of the dependency requests for this key
79    *   <li>is <i>probably</i> associated with the type being bound
80    *   <li>is unique within the class
81    * </ul>
82    *
83    * @param binding must be an unresolved binding (type parameters must match its type element's)
84    */
85   public static ImmutableMap<DependencyRequest, FrameworkField>
generateBindingFieldsForDependencies(Binding binding)86       generateBindingFieldsForDependencies(Binding binding) {
87     checkArgument(!binding.unresolved().isPresent(), "binding must be unresolved: %s", binding);
88 
89     FrameworkTypeMapper frameworkTypeMapper =
90         FrameworkTypeMapper.forBindingType(binding.bindingType());
91 
92     return Maps.toMap(
93         binding.dependencies(),
94         dependency -> {
95           XClassName frameworkClassName =
96               frameworkTypeMapper.getFrameworkType(dependency.kind()).frameworkClassName();
97           return FrameworkField.create(
98               DependencyVariableNamer.name(dependency),
99               frameworkClassName,
100               dependency.key().type().xprocessing());
101         });
102   }
103 
frameworkTypeUsageStatement( CodeBlock frameworkTypeMemberSelect, RequestKind dependencyKind)104   public CodeBlock frameworkTypeUsageStatement(
105       CodeBlock frameworkTypeMemberSelect, RequestKind dependencyKind) {
106     switch (dependencyKind) {
107       case LAZY:
108         return CodeBlock.of(
109             "$T.lazy($L)",
110             DOUBLE_CHECK,
111             frameworkTypeMemberSelect);
112       case INSTANCE:
113       case FUTURE:
114         return CodeBlock.of("$L.get()", frameworkTypeMemberSelect);
115       case PROVIDER:
116       case PRODUCER:
117         return frameworkTypeMemberSelect;
118       case PROVIDER_OF_LAZY:
119         return CodeBlock.of("$T.create($L)", PROVIDER_OF_LAZY, frameworkTypeMemberSelect);
120       default: // including PRODUCED
121         throw new AssertionError(dependencyKind);
122     }
123   }
124 
125   /**
126    * Returns a mapping of {@link DependencyRequest}s to {@link CodeBlock}s that {@linkplain
127    * #frameworkTypeUsageStatement(CodeBlock, RequestKind) use them}.
128    */
frameworkFieldUsages( ImmutableSet<DependencyRequest> dependencies, ImmutableMap<DependencyRequest, FieldSpec> fields)129   public ImmutableMap<DependencyRequest, CodeBlock> frameworkFieldUsages(
130       ImmutableSet<DependencyRequest> dependencies,
131       ImmutableMap<DependencyRequest, FieldSpec> fields) {
132     return Maps.toMap(
133         dependencies,
134         dep -> frameworkTypeUsageStatement(CodeBlock.of("$N", fields.get(dep)), dep.kind()));
135   }
136 
generatedProxyMethodName(ContributionBinding binding)137   public static String generatedProxyMethodName(ContributionBinding binding) {
138     switch (binding.kind()) {
139       case INJECTION:
140       case ASSISTED_INJECTION:
141         return "newInstance";
142       case PROVISION:
143         XMethodElement method = asMethod(binding.bindingElement().get());
144         String simpleName = getSimpleName(method);
145         // If the simple name is already defined in the factory, prepend "proxy" to the name.
146         return simpleName.contentEquals("get") || simpleName.contentEquals("create")
147             ? "proxy" + LOWER_CAMEL.to(UPPER_CAMEL, simpleName)
148             : simpleName;
149       default:
150         throw new AssertionError("Unexpected binding kind: " + binding);
151     }
152   }
153 
154   /** Returns the generated factory or members injector name for a binding. */
generatedClassNameForBinding(Binding binding)155   public static XClassName generatedClassNameForBinding(Binding binding) {
156     switch (binding.kind()) {
157       case ASSISTED_INJECTION:
158       case INJECTION:
159       case PROVISION:
160       case PRODUCTION:
161         return factoryNameForElement(asExecutable(binding.bindingElement().get()));
162       case ASSISTED_FACTORY:
163         return siblingClassName(asTypeElement(binding.bindingElement().get()), "_Impl");
164       case MEMBERS_INJECTION:
165         return membersInjectorNameForType(
166             ((MembersInjectionBinding) binding).membersInjectedType());
167       default:
168         throw new AssertionError();
169     }
170   }
171 
172   /**
173    * Returns the generated factory name for the given element.
174    *
175    * <p>This method is useful during validation before a {@link Binding} can be created. If a
176    * binding already exists for the given element, prefer to call {@link
177    * #generatedClassNameForBinding(Binding)} instead since this method does not validate that the
178    * given element is actually a binding element or not.
179    */
factoryNameForElement(XExecutableElement element)180   public static XClassName factoryNameForElement(XExecutableElement element) {
181     return elementBasedClassName(element, "Factory");
182   }
183 
184   /**
185    * Calculates an appropriate {@link XClassName} for a generated class that is based on {@code
186    * element}, appending {@code suffix} at the end.
187    *
188    * <p>This will always return a top level class name, even if {@code element}'s enclosing class is
189    * a nested type.
190    */
elementBasedClassName(XExecutableElement element, String suffix)191   public static XClassName elementBasedClassName(XExecutableElement element, String suffix) {
192     XClassName enclosingClassName = element.getEnclosingElement().asClassName();
193     String methodName =
194         isConstructor(element) ? "" : LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(element));
195     return XClassName.Companion.get(
196         enclosingClassName.getPackageName(),
197         classFileName(enclosingClassName) + "_" + methodName + suffix);
198   }
199 
parameterizedGeneratedTypeNameForBinding(Binding binding)200   public static TypeName parameterizedGeneratedTypeNameForBinding(Binding binding) {
201     ClassName className = toJavaPoet(generatedClassNameForBinding(binding));
202     ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding);
203     return typeParameters.isEmpty()
204         ? className
205         : ParameterizedTypeName.get(className, Iterables.toArray(typeParameters, TypeName.class));
206   }
207 
membersInjectorNameForType(XTypeElement typeElement)208   public static XClassName membersInjectorNameForType(XTypeElement typeElement) {
209     return siblingClassName(typeElement, "_MembersInjector");
210   }
211 
memberInjectedFieldSignatureForVariable(XFieldElement field)212   public static String memberInjectedFieldSignatureForVariable(XFieldElement field) {
213     return field.getEnclosingElement().getClassName().canonicalName() + "." + getSimpleName(field);
214   }
215 
216   /*
217    * TODO(ronshapiro): this isn't perfect, as collisions could still exist. Some examples:
218    *
219    *  - @Inject void members() {} will generate a method that conflicts with the instance
220    *    method `injectMembers(T)`
221    *  - Adding the index could conflict with another member:
222    *      @Inject void a(Object o) {}
223    *      @Inject void a(String s) {}
224    *      @Inject void a1(String s) {}
225    *
226    *    Here, Method a(String) will add the suffix "1", which will conflict with the method
227    *    generated for a1(String)
228    *  - Members named "members" or "methods" could also conflict with the {@code static} injection
229    *    method.
230    */
membersInjectorMethodName(InjectionSite injectionSite)231   public static String membersInjectorMethodName(InjectionSite injectionSite) {
232     int index = injectionSite.indexAmongAtInjectMembersWithSameSimpleName();
233     String indexString = index == 0 ? "" : String.valueOf(index + 1);
234     return "inject"
235         + LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(injectionSite.element()))
236         + indexString;
237   }
238 
classFileName(XClassName className)239   public static String classFileName(XClassName className) {
240     return CLASS_FILE_NAME_JOINER.join(className.getSimpleNames());
241   }
242 
generatedMonitoringModuleName(XTypeElement componentElement)243   public static XClassName generatedMonitoringModuleName(XTypeElement componentElement) {
244     return siblingClassName(componentElement, "_MonitoringModule");
245   }
246 
247   // TODO(ronshapiro): when JavaPoet migration is complete, replace the duplicated code
248   // which could use this.
siblingClassName(XTypeElement typeElement, String suffix)249   private static XClassName siblingClassName(XTypeElement typeElement, String suffix) {
250     XClassName className = typeElement.asClassName();
251     return XClassName.Companion.get(className.getPackageName(), classFileName(className) + suffix);
252   }
253 
254   /**
255    * The {@link java.util.Set} factory class name appropriate for set bindings.
256    *
257    * <ul>
258    *   <li>{@link dagger.producers.internal.SetFactory} for provision bindings.
259    *   <li>{@link dagger.producers.internal.SetProducer} for production bindings for {@code Set<T>}.
260    *   <li>{@link dagger.producers.internal.SetOfProducedProducer} for production bindings for
261    *       {@code Set<Produced<T>>}.
262    * </ul>
263    */
setFactoryClassName(MultiboundSetBinding binding)264   public static XClassName setFactoryClassName(MultiboundSetBinding binding) {
265     switch (binding.bindingType()) {
266       case PROVISION:
267         return XTypeNames.SET_FACTORY;
268       case PRODUCTION:
269         SetType setType = SetType.from(binding.key());
270         return setType.elementsAreTypeOf(TypeNames.PRODUCED)
271             ? XTypeNames.SET_OF_PRODUCED_PRODUCER
272             : XTypeNames.SET_PRODUCER;
273       default:
274         throw new IllegalArgumentException(binding.bindingType().toString());
275     }
276   }
277 
278   /** The {@link java.util.Map} factory class name appropriate for map bindings. */
mapFactoryClassName(MultiboundMapBinding binding)279   public static XClassName mapFactoryClassName(MultiboundMapBinding binding) {
280     MapType mapType = MapType.from(binding.key());
281     switch (binding.bindingType()) {
282       case PROVISION:
283         return mapType.valuesAreTypeOf(PROVIDER)
284             ? XTypeNames.MAP_PROVIDER_FACTORY : XTypeNames.MAP_FACTORY;
285       case PRODUCTION:
286         return mapType.valuesAreFrameworkType()
287             ? mapType.valuesAreTypeOf(PRODUCER)
288                 ? XTypeNames.MAP_OF_PRODUCER_PRODUCER
289                 : XTypeNames.MAP_OF_PRODUCED_PRODUCER
290             : XTypeNames.MAP_PRODUCER;
291       default:
292         throw new IllegalArgumentException(binding.bindingType().toString());
293     }
294   }
295 
bindingTypeElementTypeVariableNames( Binding binding)296   public static ImmutableList<TypeVariableName> bindingTypeElementTypeVariableNames(
297       Binding binding) {
298     if (binding instanceof ContributionBinding) {
299       ContributionBinding contributionBinding = (ContributionBinding) binding;
300       if (!(contributionBinding.kind() == INJECTION
301               || contributionBinding.kind() == ASSISTED_INJECTION)
302           && !contributionBinding.requiresModuleInstance()) {
303         return ImmutableList.of();
304       }
305     }
306     return typeVariableNames(binding.bindingTypeElement().get());
307   }
308 
309   /**
310    * Returns a name to be used for variables of the given {@linkplain XTypeElement type}. Prefer
311    * semantically meaningful variable names, but if none can be derived, this will produce something
312    * readable.
313    */
314   // TODO(gak): maybe this should be a function of TypeMirrors instead of Elements?
simpleVariableName(XTypeElement typeElement)315   public static String simpleVariableName(XTypeElement typeElement) {
316     return simpleVariableName(typeElement.asClassName());
317   }
318 
319   /**
320    * Returns a name to be used for variables of the given {@link XClassName}. Prefer semantically
321    * meaningful variable names, but if none can be derived, this will produce something readable.
322    */
simpleVariableName(XClassName className)323   public static String simpleVariableName(XClassName className) {
324     String candidateName = UPPER_CAMEL.to(LOWER_CAMEL, simpleName(className));
325     String variableName = protectAgainstKeywords(candidateName);
326     verify(isName(variableName), "'%s' was expected to be a valid variable name", variableName);
327     return variableName;
328   }
329 
protectAgainstKeywords(String candidateName)330   public static String protectAgainstKeywords(String candidateName) {
331     switch (candidateName) {
332       case "package":
333         return "pkg";
334       case "boolean":
335         return "b";
336       case "double":
337         return "d";
338       case "byte":
339         return "b";
340       case "int":
341         return "i";
342       case "short":
343         return "s";
344       case "char":
345         return "c";
346       case "void":
347         return "v";
348       case "class":
349         return "clazz";
350       case "float":
351         return "f";
352       case "long":
353         return "l";
354       default:
355         return SourceVersion.isKeyword(candidateName) ? candidateName + '_' : candidateName;
356     }
357   }
358 }
359