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