blob: d7ce5355098b31ace2578f3dd3da606aed1f0b1f [file] [log] [blame]
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -07001/*
ronshapiro5dde42d2016-06-17 09:03:35 -07002 * Copyright (C) 2014 The Dagger Authors.
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -07003 *
ronshapiro3a179ec2017-04-14 09:22:17 -07004 * 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
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -07007 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
ronshapiro3a179ec2017-04-14 09:22:17 -070010 * 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.
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070015 */
dpb1b65b6a2016-07-11 12:11:24 -070016
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070017package dagger.internal.codegen;
18
gak645274e2017-01-04 11:34:17 -080019import static com.google.common.base.CaseFormat.LOWER_CAMEL;
gake55f0742016-07-12 15:36:42 -070020import static com.google.common.base.CaseFormat.UPPER_CAMEL;
21import static com.google.common.base.Preconditions.checkArgument;
ronshapirob4499112017-08-30 11:34:31 -070022import static com.google.common.base.Preconditions.checkState;
gak645274e2017-01-04 11:34:17 -080023import static com.google.common.base.Verify.verify;
dpb639bd4d2017-10-24 15:15:17 -070024import static dagger.internal.codegen.DaggerStreams.toImmutableList;
25import static dagger.internal.codegen.DaggerStreams.toImmutableSet;
dpbffd98f62016-12-20 10:05:16 -080026import static dagger.internal.codegen.Optionals.optionalComparator;
gake55f0742016-07-12 15:36:42 -070027import static dagger.internal.codegen.TypeNames.DOUBLE_CHECK;
ronshapiro2c9ec492017-05-26 15:03:38 -070028import static dagger.internal.codegen.TypeNames.MAP_FACTORY;
29import static dagger.internal.codegen.TypeNames.MAP_OF_PRODUCED_PRODUCER;
30import static dagger.internal.codegen.TypeNames.MAP_OF_PRODUCER_PRODUCER;
31import static dagger.internal.codegen.TypeNames.MAP_PRODUCER;
32import static dagger.internal.codegen.TypeNames.MAP_PROVIDER_FACTORY;
gake55f0742016-07-12 15:36:42 -070033import static dagger.internal.codegen.TypeNames.PROVIDER_OF_LAZY;
ronshapiro2c9ec492017-05-26 15:03:38 -070034import static dagger.internal.codegen.TypeNames.SET_FACTORY;
35import static dagger.internal.codegen.TypeNames.SET_OF_PRODUCED_PRODUCER;
36import static dagger.internal.codegen.TypeNames.SET_PRODUCER;
ronshapiro0c4cddf2018-01-04 15:28:51 -080037import static dagger.model.BindingKind.INJECTION;
38import static dagger.model.BindingKind.MULTIBOUND_MAP;
39import static dagger.model.BindingKind.MULTIBOUND_SET;
dpbffd98f62016-12-20 10:05:16 -080040import static java.util.Comparator.comparing;
gak645274e2017-01-04 11:34:17 -080041import static javax.lang.model.SourceVersion.isName;
gake55f0742016-07-12 15:36:42 -070042
ronshapiroebf07422018-03-02 11:41:13 -080043import com.google.auto.common.MoreElements;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070044import com.google.common.base.CaseFormat;
ronshapiro732e5022016-01-07 12:10:55 -080045import com.google.common.base.Joiner;
sameb0d49be82015-01-22 17:29:48 -080046import com.google.common.collect.ImmutableList;
Christian Edward Gruber2a900692014-07-01 22:28:26 -070047import com.google.common.collect.ImmutableMap;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070048import com.google.common.collect.ImmutableSet;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070049import com.google.common.collect.Iterables;
ronshapiroe05f9212017-08-08 11:22:11 -070050import com.google.common.collect.Maps;
ronshapiro6475f472016-02-02 07:03:10 -080051import com.squareup.javapoet.ClassName;
ronshapiro732e5022016-01-07 12:10:55 -080052import com.squareup.javapoet.CodeBlock;
ronshapiroe05f9212017-08-08 11:22:11 -070053import com.squareup.javapoet.FieldSpec;
ronshapiro6475f472016-02-02 07:03:10 -080054import com.squareup.javapoet.ParameterizedTypeName;
55import com.squareup.javapoet.TypeName;
ronshapiro9540fb22016-01-13 09:46:47 -080056import com.squareup.javapoet.TypeVariableName;
ronshapiro2c9ec492017-05-26 15:03:38 -070057import dagger.internal.SetFactory;
ronshapiroa5023ca2018-01-02 12:55:01 -080058import dagger.model.DependencyRequest;
ronshapiro0a277fd2017-12-22 08:30:49 -080059import dagger.model.Key;
ronshapiro120abc62017-12-15 09:57:09 -080060import dagger.model.RequestKind;
ronshapiro2c9ec492017-05-26 15:03:38 -070061import dagger.producers.Produced;
ronshapirob4499112017-08-30 11:34:31 -070062import dagger.producers.Producer;
ronshapiro2c9ec492017-05-26 15:03:38 -070063import dagger.producers.internal.SetOfProducedProducer;
64import dagger.producers.internal.SetProducer;
Ron Shapiro5f21a802016-09-21 10:23:55 -040065import java.util.Comparator;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070066import java.util.Iterator;
ronshapiro0b1bcc62017-05-26 11:14:20 -070067import java.util.List;
ronshapirob4499112017-08-30 11:34:31 -070068import javax.inject.Provider;
gak645274e2017-01-04 11:34:17 -080069import javax.lang.model.SourceVersion;
gak66241102016-09-13 12:23:00 -070070import javax.lang.model.element.Element;
ronshapiroebf07422018-03-02 11:41:13 -080071import javax.lang.model.element.ElementKind;
72import javax.lang.model.element.ExecutableElement;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070073import javax.lang.model.element.TypeElement;
ronshapiro9540fb22016-01-13 09:46:47 -080074import javax.lang.model.element.TypeParameterElement;
Gregory Kick03c3c2d2014-06-18 17:05:47 -070075
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070076/**
77 * Utilities for generating files.
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070078 */
79class SourceFiles {
ronshapiro732e5022016-01-07 12:10:55 -080080
ronshapiro6475f472016-02-02 07:03:10 -080081 private static final Joiner CLASS_FILE_NAME_JOINER = Joiner.on('_');
ronshapiro732e5022016-01-07 12:10:55 -080082
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070083 /**
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070084 * Sorts {@link DependencyRequest} instances in an order likely to reflect their logical
85 * importance.
86 */
Ron Shapiro5f21a802016-09-21 10:23:55 -040087 static final Comparator<DependencyRequest> DEPENDENCY_ORDERING =
88 // put fields before parameters
dpbffd98f62016-12-20 10:05:16 -080089 comparing(
90 (DependencyRequest request) -> request.requestElement().map(Element::getKind),
Ron Shapiro5f21a802016-09-21 10:23:55 -040091 optionalComparator())
92 // order by dependency kind
93 .thenComparing(DependencyRequest::kind)
94 // then sort by name
95 .thenComparing(
dpbffd98f62016-12-20 10:05:16 -080096 request ->
97 request.requestElement().map(element -> element.getSimpleName().toString()),
Ron Shapiro5f21a802016-09-21 10:23:55 -040098 optionalComparator());
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070099
sameb2ea676a2015-01-15 10:20:24 -0800100 /**
dpb57198802015-12-17 12:18:15 -0800101 * Generates names and keys for the factory class fields needed to hold the framework classes for
102 * all of the dependencies of {@code binding}. It is responsible for choosing a name that
103 *
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700104 * <ul>
dpb57198802015-12-17 12:18:15 -0800105 * <li>represents all of the dependency requests for this key
106 * <li>is <i>probably</i> associated with the type being bound
107 * <li>is unique within the class
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700108 * </ul>
Ron Shapiro5f21a802016-09-21 10:23:55 -0400109 *
dpb57198802015-12-17 12:18:15 -0800110 * @param binding must be an unresolved binding (type parameters must match its type element's)
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700111 */
ronshapiro0a277fd2017-12-22 08:30:49 -0800112 static ImmutableMap<Key, FrameworkField> generateBindingFieldsForDependencies(
dpbf6be7d62016-01-22 11:43:21 -0800113 Binding binding) {
114 checkArgument(!binding.unresolved().isPresent(), "binding must be unresolved: %s", binding);
115
ronshapiro0a277fd2017-12-22 08:30:49 -0800116 ImmutableMap.Builder<Key, FrameworkField> bindingFields = ImmutableMap.builder();
gak75b9b722016-09-29 15:48:40 -0700117 for (Binding.DependencyAssociation dependencyAssociation : binding.dependencyAssociations()) {
118 FrameworkDependency frameworkDependency = dependencyAssociation.frameworkDependency();
dpbf6be7d62016-01-22 11:43:21 -0800119 bindingFields.put(
ronshapiro0a277fd2017-12-22 08:30:49 -0800120 frameworkDependency.key(),
dpbd289ddc2016-04-28 13:09:11 -0700121 FrameworkField.create(
122 ClassName.get(frameworkDependency.frameworkClass()),
ronshapiro0a277fd2017-12-22 08:30:49 -0800123 TypeName.get(frameworkDependency.key().type()),
gak75b9b722016-09-29 15:48:40 -0700124 fieldNameForDependency(dependencyAssociation.dependencyRequests())));
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700125 }
beder3e950f32014-12-19 17:48:47 -0800126 return bindingFields.build();
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700127 }
128
gak75b9b722016-09-29 15:48:40 -0700129 private static String fieldNameForDependency(ImmutableSet<DependencyRequest> dependencyRequests) {
dpbf6be7d62016-01-22 11:43:21 -0800130 // collect together all of the names that we would want to call the provider
131 ImmutableSet<String> dependencyNames =
ronshapiro6d1e6ac2017-10-09 09:00:32 -0700132 dependencyRequests.stream().map(DependencyVariableNamer::name).collect(toImmutableSet());
dpbf6be7d62016-01-22 11:43:21 -0800133
134 if (dependencyNames.size() == 1) {
135 // if there's only one name, great! use it!
136 return Iterables.getOnlyElement(dependencyNames);
137 } else {
138 // in the event that a field is being used for a bunch of deps with different names,
139 // add all the names together with "And"s in the middle. E.g.: stringAndS
140 Iterator<String> namesIterator = dependencyNames.iterator();
141 String first = namesIterator.next();
142 StringBuilder compositeNameBuilder = new StringBuilder(first);
143 while (namesIterator.hasNext()) {
144 compositeNameBuilder
145 .append("And")
146 .append(CaseFormat.LOWER_CAMEL.to(UPPER_CAMEL, namesIterator.next()));
147 }
148 return compositeNameBuilder.toString();
149 }
150 }
151
ronshapiro732e5022016-01-07 12:10:55 -0800152 static CodeBlock frameworkTypeUsageStatement(
ronshapiro120abc62017-12-15 09:57:09 -0800153 CodeBlock frameworkTypeMemberSelect, RequestKind dependencyKind) {
ronshapiro732e5022016-01-07 12:10:55 -0800154 switch (dependencyKind) {
155 case LAZY:
ronshapirob9158d82016-04-13 11:00:56 -0700156 return CodeBlock.of("$T.lazy($L)", DOUBLE_CHECK, frameworkTypeMemberSelect);
ronshapiro732e5022016-01-07 12:10:55 -0800157 case INSTANCE:
158 case FUTURE:
ronshapirob9158d82016-04-13 11:00:56 -0700159 return CodeBlock.of("$L.get()", frameworkTypeMemberSelect);
ronshapiro732e5022016-01-07 12:10:55 -0800160 case PROVIDER:
161 case PRODUCER:
dpbd8d950a2016-08-30 09:24:37 -0700162 return frameworkTypeMemberSelect;
dpbc9bd6d82016-04-19 07:25:06 -0700163 case PROVIDER_OF_LAZY:
164 return CodeBlock.of("$T.create($L)", PROVIDER_OF_LAZY, frameworkTypeMemberSelect);
dpbd8d950a2016-08-30 09:24:37 -0700165 default: // including PRODUCED
166 throw new AssertionError(dependencyKind);
ronshapiro732e5022016-01-07 12:10:55 -0800167 }
168 }
169
dpb6bd55de2015-09-28 07:58:09 -0700170 /**
ronshapiroe05f9212017-08-08 11:22:11 -0700171 * Returns a mapping of {@link DependencyRequest}s to {@link CodeBlock}s that {@linkplain
ronshapiro120abc62017-12-15 09:57:09 -0800172 * #frameworkTypeUsageStatement(CodeBlock, RequestKind) use them}.
ronshapiroe05f9212017-08-08 11:22:11 -0700173 */
174 static ImmutableMap<DependencyRequest, CodeBlock> frameworkFieldUsages(
ronshapiro0a277fd2017-12-22 08:30:49 -0800175 ImmutableSet<DependencyRequest> dependencies, ImmutableMap<Key, FieldSpec> fields) {
ronshapiroe05f9212017-08-08 11:22:11 -0700176 return Maps.toMap(
177 dependencies,
178 dep ->
ronshapiro0a277fd2017-12-22 08:30:49 -0800179 frameworkTypeUsageStatement(CodeBlock.of("$N", fields.get(dep.key())), dep.kind()));
ronshapiroe05f9212017-08-08 11:22:11 -0700180 }
181
182 /**
dpb6bd55de2015-09-28 07:58:09 -0700183 * Returns the generated factory or members injector name for a binding.
184 */
185 static ClassName generatedClassNameForBinding(Binding binding) {
186 switch (binding.bindingType()) {
Christian Edward Gruber4aec5ba2014-05-20 19:37:11 -0700187 case PROVISION:
dpb6bd55de2015-09-28 07:58:09 -0700188 case PRODUCTION:
189 ContributionBinding contribution = (ContributionBinding) binding;
ronshapiro0c4cddf2018-01-04 15:28:51 -0800190 switch (contribution.kind()) {
dpb6bd55de2015-09-28 07:58:09 -0700191 case INJECTION:
192 case PROVISION:
Ron Shapiro5f21a802016-09-21 10:23:55 -0400193 case PRODUCTION:
ronshapiroebf07422018-03-02 11:41:13 -0800194 return elementBasedClassName(
195 MoreElements.asExecutable(binding.bindingElement().get()), "Factory");
dpb6bd55de2015-09-28 07:58:09 -0700196
197 default:
198 throw new AssertionError();
199 }
200
201 case MEMBERS_INJECTION:
dpb4955ef92016-04-29 12:31:14 -0700202 return membersInjectorNameForType(
203 ((MembersInjectionBinding) binding).membersInjectedType());
dpb6bd55de2015-09-28 07:58:09 -0700204
Christian Edward Gruber4aec5ba2014-05-20 19:37:11 -0700205 default:
206 throw new AssertionError();
207 }
208 }
Christian Edward Gruber791bd292014-05-20 19:36:41 -0700209
ronshapiroebf07422018-03-02 11:41:13 -0800210 /**
211 * Calculates an appropriate {@link ClassName} for a generated class that is based on {@code
212 * element}, appending {@code suffix} at the end.
213 *
214 * <p>This will always return a {@linkplain ClassName#topLevelClassName() top level class name},
215 * even if {@code element}'s enclosing class is a nested type.
216 */
217 static ClassName elementBasedClassName(ExecutableElement element, String suffix) {
218 ClassName enclosingClassName =
219 ClassName.get(MoreElements.asType(element.getEnclosingElement()));
220 String methodName =
221 element.getKind().equals(ElementKind.CONSTRUCTOR)
222 ? ""
223 : LOWER_CAMEL.to(UPPER_CAMEL, element.getSimpleName().toString());
224 return ClassName.get(
225 enclosingClassName.packageName(),
226 classFileName(enclosingClassName) + "_" + methodName + suffix);
227 }
228
gak4d860192016-10-13 23:41:00 -0700229 static TypeName parameterizedGeneratedTypeNameForBinding(Binding binding) {
ronshapiro6475f472016-02-02 07:03:10 -0800230 ClassName className = generatedClassNameForBinding(binding);
gak4d860192016-10-13 23:41:00 -0700231 ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding);
232 return typeParameters.isEmpty()
233 ? className
234 : ParameterizedTypeName.get(className, Iterables.toArray(typeParameters, TypeName.class));
Christian Edward Gruber10876b02014-06-01 11:24:55 -0700235 }
236
ronshapiro6475f472016-02-02 07:03:10 -0800237 static ClassName membersInjectorNameForType(TypeElement typeElement) {
ronshapiroa296c102016-01-14 12:33:42 -0800238 return siblingClassName(typeElement, "_MembersInjector");
ronshapiro732e5022016-01-07 12:10:55 -0800239 }
240
ronshapiro6475f472016-02-02 07:03:10 -0800241 static String classFileName(ClassName className) {
ronshapiro732e5022016-01-07 12:10:55 -0800242 return CLASS_FILE_NAME_JOINER.join(className.simpleNames());
243 }
244
ronshapiro6475f472016-02-02 07:03:10 -0800245 static ClassName generatedMonitoringModuleName(
ronshapiroa296c102016-01-14 12:33:42 -0800246 TypeElement componentElement) {
247 return siblingClassName(componentElement, "_MonitoringModule");
248 }
249
beder893f7702016-02-17 05:22:50 -0800250 static ClassName generatedProductionExecutorModuleName(TypeElement componentElement) {
251 return siblingClassName(componentElement, "_ProductionExecutorModule");
252 }
253
ronshapiro6475f472016-02-02 07:03:10 -0800254 // TODO(ronshapiro): when JavaPoet migration is complete, replace the duplicated code
255 // which could use this.
256 private static ClassName siblingClassName(TypeElement typeElement, String suffix) {
257 ClassName className = ClassName.get(typeElement);
ronshapiro23527f62016-04-20 10:33:08 -0700258 return className.topLevelClassName().peerClass(classFileName(className) + suffix);
beder0e6f2482015-10-23 08:37:55 -0700259 }
260
ronshapiro2c9ec492017-05-26 15:03:38 -0700261 /**
262 * The {@link java.util.Set} factory class name appropriate for set bindings.
263 *
264 * <ul>
265 * <li>{@link SetFactory} for provision bindings.
266 * <li>{@link SetProducer} for production bindings for {@code Set<T>}.
267 * <li>{@link SetOfProducedProducer} for production bindings for {@code Set<Produced<T>>}.
268 * </ul>
269 */
270 static ClassName setFactoryClassName(ContributionBinding binding) {
ronshapiro0c4cddf2018-01-04 15:28:51 -0800271 checkArgument(binding.kind().equals(MULTIBOUND_SET));
ronshapiro2c9ec492017-05-26 15:03:38 -0700272 if (binding.bindingType().equals(BindingType.PROVISION)) {
273 return SET_FACTORY;
274 } else {
275 SetType setType = SetType.from(binding.key());
276 return setType.elementsAreTypeOf(Produced.class) ? SET_OF_PRODUCED_PRODUCER : SET_PRODUCER;
277 }
278 }
279
ronshapirob4499112017-08-30 11:34:31 -0700280 /** The {@link java.util.Map} factory class name appropriate for map bindings. */
ronshapiro2c9ec492017-05-26 15:03:38 -0700281 static ClassName mapFactoryClassName(ContributionBinding binding) {
ronshapiro0c4cddf2018-01-04 15:28:51 -0800282 checkState(binding.kind().equals(MULTIBOUND_MAP), binding.kind());
ronshapirob4499112017-08-30 11:34:31 -0700283 MapType mapType = MapType.from(binding.key());
ronshapiro2c9ec492017-05-26 15:03:38 -0700284 switch (binding.bindingType()) {
sraube1028b72017-08-28 18:03:22 -0700285 case PROVISION:
ronshapirob4499112017-08-30 11:34:31 -0700286 return mapType.valuesAreTypeOf(Provider.class) ? MAP_PROVIDER_FACTORY : MAP_FACTORY;
287 case PRODUCTION:
288 return mapType.valuesAreFrameworkType()
289 ? mapType.valuesAreTypeOf(Producer.class)
290 ? MAP_OF_PRODUCER_PRODUCER
291 : MAP_OF_PRODUCED_PRODUCER
292 : MAP_PRODUCER;
ronshapiro2c9ec492017-05-26 15:03:38 -0700293 default:
ronshapirob4499112017-08-30 11:34:31 -0700294 throw new IllegalArgumentException(binding.bindingType().toString());
ronshapiro2c9ec492017-05-26 15:03:38 -0700295 }
296 }
297
ronshapiro9540fb22016-01-13 09:46:47 -0800298 static ImmutableList<TypeVariableName> bindingTypeElementTypeVariableNames(Binding binding) {
gak4d860192016-10-13 23:41:00 -0700299 if (binding instanceof ContributionBinding) {
300 ContributionBinding contributionBinding = (ContributionBinding) binding;
ronshapiro0c4cddf2018-01-04 15:28:51 -0800301 if (!contributionBinding.kind().equals(INJECTION)
gak4d860192016-10-13 23:41:00 -0700302 && !contributionBinding.requiresModuleInstance()) {
303 return ImmutableList.of();
304 }
305 }
ronshapiro0b1bcc62017-05-26 11:14:20 -0700306 List<? extends TypeParameterElement> typeParameters =
307 binding.bindingTypeElement().get().getTypeParameters();
308 return typeParameters.stream().map(TypeVariableName::get).collect(toImmutableList());
ronshapiro9540fb22016-01-13 09:46:47 -0800309 }
310
gak645274e2017-01-04 11:34:17 -0800311 /**
312 * Returns a name to be used for variables of the given {@linkplain TypeElement type}. Prefer
313 * semantically meaningful variable names, but if none can be derived, this will produce something
314 * readable.
315 */
316 // TODO(gak): maybe this should be a function of TypeMirrors instead of Elements?
317 static String simpleVariableName(TypeElement typeElement) {
318 String candidateName = UPPER_CAMEL.to(LOWER_CAMEL, typeElement.getSimpleName().toString());
319 String variableName = protectAgainstKeywords(candidateName);
320 verify(isName(variableName), "'%s' was expected to be a valid variable name");
321 return variableName;
322 }
323
bcorso5f0a1432018-04-16 08:51:58 -0700324 static String protectAgainstKeywords(String candidateName) {
gak645274e2017-01-04 11:34:17 -0800325 switch (candidateName) {
326 case "package":
327 return "pkg";
328 case "boolean":
329 return "b";
330 case "double":
331 return "d";
332 case "byte":
333 return "b";
334 case "int":
335 return "i";
336 case "short":
337 return "s";
338 case "char":
339 return "c";
340 case "void":
341 return "v";
342 case "class":
343 return "clazz";
344 case "float":
345 return "f";
346 case "long":
347 return "l";
348 default:
349 return SourceVersion.isKeyword(candidateName) ? candidateName + '_' : candidateName;
350 }
351 }
352
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700353 private SourceFiles() {}
354}