• 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 com.google.common.base.CaseFormat.LOWER_CAMEL;
20 import static com.google.common.base.CaseFormat.UPPER_CAMEL;
21 import static com.google.common.base.Preconditions.checkArgument;
22 import static com.google.common.base.Preconditions.checkState;
23 import static com.google.common.base.Verify.verify;
24 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
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.PROVIDER_OF_LAZY;
32 import static dagger.internal.codegen.javapoet.TypeNames.SET_FACTORY;
33 import static dagger.internal.codegen.javapoet.TypeNames.SET_OF_PRODUCED_PRODUCER;
34 import static dagger.internal.codegen.javapoet.TypeNames.SET_PRODUCER;
35 import static dagger.model.BindingKind.ASSISTED_INJECTION;
36 import static dagger.model.BindingKind.INJECTION;
37 import static dagger.model.BindingKind.MULTIBOUND_MAP;
38 import static dagger.model.BindingKind.MULTIBOUND_SET;
39 import static javax.lang.model.SourceVersion.isName;
40 
41 import com.google.auto.common.MoreElements;
42 import com.google.common.base.Joiner;
43 import com.google.common.collect.ImmutableList;
44 import com.google.common.collect.ImmutableMap;
45 import com.google.common.collect.ImmutableSet;
46 import com.google.common.collect.Iterables;
47 import com.google.common.collect.Maps;
48 import com.squareup.javapoet.ClassName;
49 import com.squareup.javapoet.CodeBlock;
50 import com.squareup.javapoet.FieldSpec;
51 import com.squareup.javapoet.ParameterizedTypeName;
52 import com.squareup.javapoet.TypeName;
53 import com.squareup.javapoet.TypeVariableName;
54 import dagger.internal.SetFactory;
55 import dagger.internal.codegen.base.MapType;
56 import dagger.internal.codegen.base.SetType;
57 import dagger.model.DependencyRequest;
58 import dagger.model.RequestKind;
59 import dagger.producers.Produced;
60 import dagger.producers.Producer;
61 import dagger.producers.internal.SetOfProducedProducer;
62 import dagger.producers.internal.SetProducer;
63 import java.util.List;
64 import javax.inject.Provider;
65 import javax.lang.model.SourceVersion;
66 import javax.lang.model.element.ElementKind;
67 import javax.lang.model.element.ExecutableElement;
68 import javax.lang.model.element.TypeElement;
69 import javax.lang.model.element.TypeParameterElement;
70 import javax.lang.model.element.VariableElement;
71 
72 /** Utilities for generating files. */
73 public class SourceFiles {
74 
75   private static final Joiner CLASS_FILE_NAME_JOINER = Joiner.on('_');
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             FrameworkField.create(
100                 ClassName.get(
101                     frameworkTypeMapper.getFrameworkType(dependency.kind()).frameworkClass()),
102                 TypeName.get(dependency.key().type()),
103                 DependencyVariableNamer.name(dependency)));
104   }
105 
frameworkTypeUsageStatement( CodeBlock frameworkTypeMemberSelect, RequestKind dependencyKind)106   public static CodeBlock frameworkTypeUsageStatement(
107       CodeBlock frameworkTypeMemberSelect, RequestKind dependencyKind) {
108     switch (dependencyKind) {
109       case LAZY:
110         return CodeBlock.of("$T.lazy($L)", DOUBLE_CHECK, frameworkTypeMemberSelect);
111       case INSTANCE:
112       case FUTURE:
113         return CodeBlock.of("$L.get()", frameworkTypeMemberSelect);
114       case PROVIDER:
115       case PRODUCER:
116         return frameworkTypeMemberSelect;
117       case PROVIDER_OF_LAZY:
118         return CodeBlock.of("$T.create($L)", PROVIDER_OF_LAZY, frameworkTypeMemberSelect);
119       default: // including PRODUCED
120         throw new AssertionError(dependencyKind);
121     }
122   }
123 
124   /**
125    * Returns a mapping of {@link DependencyRequest}s to {@link CodeBlock}s that {@linkplain
126    * #frameworkTypeUsageStatement(CodeBlock, RequestKind) use them}.
127    */
frameworkFieldUsages( ImmutableSet<DependencyRequest> dependencies, ImmutableMap<DependencyRequest, FieldSpec> fields)128   public static ImmutableMap<DependencyRequest, CodeBlock> frameworkFieldUsages(
129       ImmutableSet<DependencyRequest> dependencies,
130       ImmutableMap<DependencyRequest, FieldSpec> fields) {
131     return Maps.toMap(
132         dependencies,
133         dep -> frameworkTypeUsageStatement(CodeBlock.of("$N", fields.get(dep)), dep.kind()));
134   }
135 
136   /** Returns the generated factory or members injector name for a binding. */
generatedClassNameForBinding(Binding binding)137   public static ClassName generatedClassNameForBinding(Binding binding) {
138     switch (binding.bindingType()) {
139       case PROVISION:
140       case PRODUCTION:
141         ContributionBinding contribution = (ContributionBinding) binding;
142         switch (contribution.kind()) {
143           case ASSISTED_INJECTION:
144           case INJECTION:
145           case PROVISION:
146           case PRODUCTION:
147             return elementBasedClassName(
148                 MoreElements.asExecutable(binding.bindingElement().get()), "Factory");
149 
150           case ASSISTED_FACTORY:
151             return siblingClassName(MoreElements.asType(binding.bindingElement().get()), "_Impl");
152 
153           default:
154             throw new AssertionError();
155         }
156 
157       case MEMBERS_INJECTION:
158         return membersInjectorNameForType(
159             ((MembersInjectionBinding) binding).membersInjectedType());
160     }
161     throw new AssertionError();
162   }
163 
164   /**
165    * Calculates an appropriate {@link ClassName} for a generated class that is based on {@code
166    * element}, appending {@code suffix} at the end.
167    *
168    * <p>This will always return a {@linkplain ClassName#topLevelClassName() top level class name},
169    * even if {@code element}'s enclosing class is a nested type.
170    */
elementBasedClassName(ExecutableElement element, String suffix)171   public static ClassName elementBasedClassName(ExecutableElement element, String suffix) {
172     ClassName enclosingClassName =
173         ClassName.get(MoreElements.asType(element.getEnclosingElement()));
174     String methodName =
175         element.getKind().equals(ElementKind.CONSTRUCTOR)
176             ? ""
177             : LOWER_CAMEL.to(UPPER_CAMEL, element.getSimpleName().toString());
178     return ClassName.get(
179         enclosingClassName.packageName(),
180         classFileName(enclosingClassName) + "_" + methodName + suffix);
181   }
182 
parameterizedGeneratedTypeNameForBinding(Binding binding)183   public static TypeName parameterizedGeneratedTypeNameForBinding(Binding binding) {
184     ClassName className = generatedClassNameForBinding(binding);
185     ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding);
186     return typeParameters.isEmpty()
187         ? className
188         : ParameterizedTypeName.get(className, Iterables.toArray(typeParameters, TypeName.class));
189   }
190 
membersInjectorNameForType(TypeElement typeElement)191   public static ClassName membersInjectorNameForType(TypeElement typeElement) {
192     return siblingClassName(typeElement,  "_MembersInjector");
193   }
194 
memberInjectedFieldSignatureForVariable(VariableElement variableElement)195   public static String memberInjectedFieldSignatureForVariable(VariableElement variableElement) {
196     return MoreElements.asType(variableElement.getEnclosingElement()).getQualifiedName()
197         + "."
198         + variableElement.getSimpleName();
199   }
200 
classFileName(ClassName className)201   public static String classFileName(ClassName className) {
202     return CLASS_FILE_NAME_JOINER.join(className.simpleNames());
203   }
204 
generatedMonitoringModuleName(TypeElement componentElement)205   public static ClassName generatedMonitoringModuleName(TypeElement componentElement) {
206     return siblingClassName(componentElement, "_MonitoringModule");
207   }
208 
209   // TODO(ronshapiro): when JavaPoet migration is complete, replace the duplicated code
210   // which could use this.
siblingClassName(TypeElement typeElement, String suffix)211   private static ClassName siblingClassName(TypeElement typeElement, String suffix) {
212     ClassName className = ClassName.get(typeElement);
213     return className.topLevelClassName().peerClass(classFileName(className) + suffix);
214   }
215 
216   /**
217    * The {@link java.util.Set} factory class name appropriate for set bindings.
218    *
219    * <ul>
220    *   <li>{@link SetFactory} for provision bindings.
221    *   <li>{@link SetProducer} for production bindings for {@code Set<T>}.
222    *   <li>{@link SetOfProducedProducer} for production bindings for {@code Set<Produced<T>>}.
223    * </ul>
224    */
setFactoryClassName(ContributionBinding binding)225   public static ClassName setFactoryClassName(ContributionBinding binding) {
226     checkArgument(binding.kind().equals(MULTIBOUND_SET));
227     if (binding.bindingType().equals(BindingType.PROVISION)) {
228       return SET_FACTORY;
229     } else {
230       SetType setType = SetType.from(binding.key());
231       return setType.elementsAreTypeOf(Produced.class) ? SET_OF_PRODUCED_PRODUCER : SET_PRODUCER;
232     }
233   }
234 
235   /** The {@link java.util.Map} factory class name appropriate for map bindings. */
mapFactoryClassName(ContributionBinding binding)236   public static ClassName mapFactoryClassName(ContributionBinding binding) {
237     checkState(binding.kind().equals(MULTIBOUND_MAP), binding.kind());
238     MapType mapType = MapType.from(binding.key());
239     switch (binding.bindingType()) {
240       case PROVISION:
241         return mapType.valuesAreTypeOf(Provider.class) ? MAP_PROVIDER_FACTORY : MAP_FACTORY;
242       case PRODUCTION:
243         return mapType.valuesAreFrameworkType()
244             ? mapType.valuesAreTypeOf(Producer.class)
245                 ? MAP_OF_PRODUCER_PRODUCER
246                 : MAP_OF_PRODUCED_PRODUCER
247             : MAP_PRODUCER;
248       default:
249         throw new IllegalArgumentException(binding.bindingType().toString());
250     }
251   }
252 
bindingTypeElementTypeVariableNames( Binding binding)253   public static ImmutableList<TypeVariableName> bindingTypeElementTypeVariableNames(
254       Binding binding) {
255     if (binding instanceof ContributionBinding) {
256       ContributionBinding contributionBinding = (ContributionBinding) binding;
257       if (!(contributionBinding.kind() == INJECTION
258               || contributionBinding.kind() == ASSISTED_INJECTION)
259           && !contributionBinding.requiresModuleInstance()) {
260         return ImmutableList.of();
261       }
262     }
263     List<? extends TypeParameterElement> typeParameters =
264         binding.bindingTypeElement().get().getTypeParameters();
265     return typeParameters.stream().map(TypeVariableName::get).collect(toImmutableList());
266   }
267 
268   /**
269    * Returns a name to be used for variables of the given {@linkplain TypeElement type}. Prefer
270    * semantically meaningful variable names, but if none can be derived, this will produce something
271    * readable.
272    */
273   // TODO(gak): maybe this should be a function of TypeMirrors instead of Elements?
simpleVariableName(TypeElement typeElement)274   public static String simpleVariableName(TypeElement typeElement) {
275     String candidateName = UPPER_CAMEL.to(LOWER_CAMEL, typeElement.getSimpleName().toString());
276     String variableName = protectAgainstKeywords(candidateName);
277     verify(isName(variableName), "'%s' was expected to be a valid variable name");
278     return variableName;
279   }
280 
protectAgainstKeywords(String candidateName)281   public static String protectAgainstKeywords(String candidateName) {
282     switch (candidateName) {
283       case "package":
284         return "pkg";
285       case "boolean":
286         return "b";
287       case "double":
288         return "d";
289       case "byte":
290         return "b";
291       case "int":
292         return "i";
293       case "short":
294         return "s";
295       case "char":
296         return "c";
297       case "void":
298         return "v";
299       case "class":
300         return "clazz";
301       case "float":
302         return "f";
303       case "long":
304         return "l";
305       default:
306         return SourceVersion.isKeyword(candidateName) ? candidateName + '_' : candidateName;
307     }
308   }
309 
SourceFiles()310   private SourceFiles() {}
311 }
312