blob: 3f0299ee117c5f1ed868156430e32ca82c74b5b6 [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;
gak4d860192016-10-13 23:41:00 -070024import static dagger.internal.codegen.ContributionBinding.Kind.INJECTION;
ronshapirob4499112017-08-30 11:34:31 -070025import static dagger.internal.codegen.ContributionBinding.Kind.SYNTHETIC_MULTIBOUND_MAP;
ronshapiro2c9ec492017-05-26 15:03:38 -070026import static dagger.internal.codegen.ContributionBinding.Kind.SYNTHETIC_MULTIBOUND_SET;
dpbffd98f62016-12-20 10:05:16 -080027import static dagger.internal.codegen.Optionals.optionalComparator;
gake55f0742016-07-12 15:36:42 -070028import static dagger.internal.codegen.TypeNames.DOUBLE_CHECK;
ronshapiro2c9ec492017-05-26 15:03:38 -070029import static dagger.internal.codegen.TypeNames.MAP_FACTORY;
30import static dagger.internal.codegen.TypeNames.MAP_OF_PRODUCED_PRODUCER;
31import static dagger.internal.codegen.TypeNames.MAP_OF_PRODUCER_PRODUCER;
32import static dagger.internal.codegen.TypeNames.MAP_PRODUCER;
33import static dagger.internal.codegen.TypeNames.MAP_PROVIDER_FACTORY;
gake55f0742016-07-12 15:36:42 -070034import static dagger.internal.codegen.TypeNames.PROVIDER_OF_LAZY;
ronshapiro2c9ec492017-05-26 15:03:38 -070035import static dagger.internal.codegen.TypeNames.SET_FACTORY;
36import static dagger.internal.codegen.TypeNames.SET_OF_PRODUCED_PRODUCER;
37import static dagger.internal.codegen.TypeNames.SET_PRODUCER;
ronshapiro0b1bcc62017-05-26 11:14:20 -070038import static dagger.internal.codegen.Util.toImmutableList;
ronshapiro6d1e6ac2017-10-09 09:00:32 -070039import static dagger.internal.codegen.Util.toImmutableSet;
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
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070043import com.google.common.base.CaseFormat;
ronshapiro732e5022016-01-07 12:10:55 -080044import com.google.common.base.Joiner;
sameb0d49be82015-01-22 17:29:48 -080045import com.google.common.collect.ImmutableList;
Christian Edward Gruber2a900692014-07-01 22:28:26 -070046import com.google.common.collect.ImmutableMap;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070047import com.google.common.collect.ImmutableSet;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070048import com.google.common.collect.Iterables;
ronshapiroe05f9212017-08-08 11:22:11 -070049import com.google.common.collect.Maps;
ronshapiro6475f472016-02-02 07:03:10 -080050import com.squareup.javapoet.ClassName;
ronshapiro732e5022016-01-07 12:10:55 -080051import com.squareup.javapoet.CodeBlock;
ronshapiroe05f9212017-08-08 11:22:11 -070052import com.squareup.javapoet.FieldSpec;
ronshapiro6475f472016-02-02 07:03:10 -080053import com.squareup.javapoet.ParameterizedTypeName;
54import com.squareup.javapoet.TypeName;
ronshapiro9540fb22016-01-13 09:46:47 -080055import com.squareup.javapoet.TypeVariableName;
ronshapiro2c9ec492017-05-26 15:03:38 -070056import dagger.internal.SetFactory;
57import dagger.producers.Produced;
ronshapirob4499112017-08-30 11:34:31 -070058import dagger.producers.Producer;
ronshapiro2c9ec492017-05-26 15:03:38 -070059import dagger.producers.internal.SetOfProducedProducer;
60import dagger.producers.internal.SetProducer;
Ron Shapiro5f21a802016-09-21 10:23:55 -040061import java.util.Comparator;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070062import java.util.Iterator;
ronshapiro0b1bcc62017-05-26 11:14:20 -070063import java.util.List;
ronshapirob4499112017-08-30 11:34:31 -070064import javax.inject.Provider;
gak645274e2017-01-04 11:34:17 -080065import javax.lang.model.SourceVersion;
gak66241102016-09-13 12:23:00 -070066import javax.lang.model.element.Element;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070067import javax.lang.model.element.TypeElement;
ronshapiro9540fb22016-01-13 09:46:47 -080068import javax.lang.model.element.TypeParameterElement;
Gregory Kick03c3c2d2014-06-18 17:05:47 -070069
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070070/**
71 * Utilities for generating files.
72 *
73 * @author Gregory Kick
74 * @since 2.0
75 */
76class SourceFiles {
ronshapiro732e5022016-01-07 12:10:55 -080077
ronshapiro6475f472016-02-02 07:03:10 -080078 private static final Joiner CLASS_FILE_NAME_JOINER = Joiner.on('_');
ronshapiro732e5022016-01-07 12:10:55 -080079
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070080 /**
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070081 * Sorts {@link DependencyRequest} instances in an order likely to reflect their logical
82 * importance.
83 */
Ron Shapiro5f21a802016-09-21 10:23:55 -040084 static final Comparator<DependencyRequest> DEPENDENCY_ORDERING =
85 // put fields before parameters
dpbffd98f62016-12-20 10:05:16 -080086 comparing(
87 (DependencyRequest request) -> request.requestElement().map(Element::getKind),
Ron Shapiro5f21a802016-09-21 10:23:55 -040088 optionalComparator())
89 // order by dependency kind
90 .thenComparing(DependencyRequest::kind)
91 // then sort by name
92 .thenComparing(
dpbffd98f62016-12-20 10:05:16 -080093 request ->
94 request.requestElement().map(element -> element.getSimpleName().toString()),
Ron Shapiro5f21a802016-09-21 10:23:55 -040095 optionalComparator());
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070096
sameb2ea676a2015-01-15 10:20:24 -080097 /**
dpb57198802015-12-17 12:18:15 -080098 * Generates names and keys for the factory class fields needed to hold the framework classes for
99 * all of the dependencies of {@code binding}. It is responsible for choosing a name that
100 *
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700101 * <ul>
dpb57198802015-12-17 12:18:15 -0800102 * <li>represents all of the dependency requests for this key
103 * <li>is <i>probably</i> associated with the type being bound
104 * <li>is unique within the class
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700105 * </ul>
Ron Shapiro5f21a802016-09-21 10:23:55 -0400106 *
dpb57198802015-12-17 12:18:15 -0800107 * @param binding must be an unresolved binding (type parameters must match its type element's)
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700108 */
gak59d2c992015-01-14 14:33:46 -0800109 static ImmutableMap<BindingKey, FrameworkField> generateBindingFieldsForDependencies(
dpbf6be7d62016-01-22 11:43:21 -0800110 Binding binding) {
111 checkArgument(!binding.unresolved().isPresent(), "binding must be unresolved: %s", binding);
112
gak59d2c992015-01-14 14:33:46 -0800113 ImmutableMap.Builder<BindingKey, FrameworkField> bindingFields = ImmutableMap.builder();
gak75b9b722016-09-29 15:48:40 -0700114 for (Binding.DependencyAssociation dependencyAssociation : binding.dependencyAssociations()) {
115 FrameworkDependency frameworkDependency = dependencyAssociation.frameworkDependency();
dpbf6be7d62016-01-22 11:43:21 -0800116 bindingFields.put(
117 frameworkDependency.bindingKey(),
dpbd289ddc2016-04-28 13:09:11 -0700118 FrameworkField.create(
119 ClassName.get(frameworkDependency.frameworkClass()),
120 TypeName.get(frameworkDependency.bindingKey().key().type()),
gak75b9b722016-09-29 15:48:40 -0700121 fieldNameForDependency(dependencyAssociation.dependencyRequests())));
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700122 }
beder3e950f32014-12-19 17:48:47 -0800123 return bindingFields.build();
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700124 }
125
gak75b9b722016-09-29 15:48:40 -0700126 private static String fieldNameForDependency(ImmutableSet<DependencyRequest> dependencyRequests) {
dpbf6be7d62016-01-22 11:43:21 -0800127 // collect together all of the names that we would want to call the provider
128 ImmutableSet<String> dependencyNames =
ronshapiro6d1e6ac2017-10-09 09:00:32 -0700129 dependencyRequests.stream().map(DependencyVariableNamer::name).collect(toImmutableSet());
dpbf6be7d62016-01-22 11:43:21 -0800130
131 if (dependencyNames.size() == 1) {
132 // if there's only one name, great! use it!
133 return Iterables.getOnlyElement(dependencyNames);
134 } else {
135 // in the event that a field is being used for a bunch of deps with different names,
136 // add all the names together with "And"s in the middle. E.g.: stringAndS
137 Iterator<String> namesIterator = dependencyNames.iterator();
138 String first = namesIterator.next();
139 StringBuilder compositeNameBuilder = new StringBuilder(first);
140 while (namesIterator.hasNext()) {
141 compositeNameBuilder
142 .append("And")
143 .append(CaseFormat.LOWER_CAMEL.to(UPPER_CAMEL, namesIterator.next()));
144 }
145 return compositeNameBuilder.toString();
146 }
147 }
148
ronshapiro732e5022016-01-07 12:10:55 -0800149 static CodeBlock frameworkTypeUsageStatement(
150 CodeBlock frameworkTypeMemberSelect, DependencyRequest.Kind dependencyKind) {
151 switch (dependencyKind) {
152 case LAZY:
ronshapirob9158d82016-04-13 11:00:56 -0700153 return CodeBlock.of("$T.lazy($L)", DOUBLE_CHECK, frameworkTypeMemberSelect);
ronshapiro732e5022016-01-07 12:10:55 -0800154 case INSTANCE:
155 case FUTURE:
ronshapirob9158d82016-04-13 11:00:56 -0700156 return CodeBlock.of("$L.get()", frameworkTypeMemberSelect);
ronshapiro732e5022016-01-07 12:10:55 -0800157 case PROVIDER:
158 case PRODUCER:
159 case MEMBERS_INJECTOR:
dpbd8d950a2016-08-30 09:24:37 -0700160 return frameworkTypeMemberSelect;
dpbc9bd6d82016-04-19 07:25:06 -0700161 case PROVIDER_OF_LAZY:
162 return CodeBlock.of("$T.create($L)", PROVIDER_OF_LAZY, frameworkTypeMemberSelect);
dpbd8d950a2016-08-30 09:24:37 -0700163 default: // including PRODUCED
164 throw new AssertionError(dependencyKind);
ronshapiro732e5022016-01-07 12:10:55 -0800165 }
166 }
167
dpb6bd55de2015-09-28 07:58:09 -0700168 /**
ronshapiroe05f9212017-08-08 11:22:11 -0700169 * Returns a mapping of {@link DependencyRequest}s to {@link CodeBlock}s that {@linkplain
170 * #frameworkTypeUsageStatement(CodeBlock, DependencyRequest.Kind) use them}.
171 */
172 static ImmutableMap<DependencyRequest, CodeBlock> frameworkFieldUsages(
173 ImmutableSet<DependencyRequest> dependencies, ImmutableMap<BindingKey, FieldSpec> fields) {
174 return Maps.toMap(
175 dependencies,
176 dep ->
177 frameworkTypeUsageStatement(
178 CodeBlock.of("$N", fields.get(dep.bindingKey())), dep.kind()));
179 }
180
181 /**
dpb6bd55de2015-09-28 07:58:09 -0700182 * Returns the generated factory or members injector name for a binding.
183 */
184 static ClassName generatedClassNameForBinding(Binding binding) {
185 switch (binding.bindingType()) {
Christian Edward Gruber4aec5ba2014-05-20 19:37:11 -0700186 case PROVISION:
dpb6bd55de2015-09-28 07:58:09 -0700187 case PRODUCTION:
188 ContributionBinding contribution = (ContributionBinding) binding;
dpbdd276032016-07-11 09:34:43 -0700189 checkArgument(contribution.bindingTypeElement().isPresent());
190 ClassName enclosingClassName = ClassName.get(contribution.bindingTypeElement().get());
dpb6bd55de2015-09-28 07:58:09 -0700191 switch (contribution.bindingKind()) {
192 case INJECTION:
193 case PROVISION:
Ron Shapiro5f21a802016-09-21 10:23:55 -0400194 case PRODUCTION:
dpb6bd55de2015-09-28 07:58:09 -0700195 return enclosingClassName
196 .topLevelClassName()
ronshapiro6475f472016-02-02 07:03:10 -0800197 .peerClass(
ronshapiro23527f62016-04-20 10:33:08 -0700198 classFileName(enclosingClassName)
dpb6bd55de2015-09-28 07:58:09 -0700199 + "_"
200 + factoryPrefix(contribution)
201 + "Factory");
202
203 default:
204 throw new AssertionError();
205 }
206
207 case MEMBERS_INJECTION:
dpb4955ef92016-04-29 12:31:14 -0700208 return membersInjectorNameForType(
209 ((MembersInjectionBinding) binding).membersInjectedType());
dpb6bd55de2015-09-28 07:58:09 -0700210
Christian Edward Gruber4aec5ba2014-05-20 19:37:11 -0700211 default:
212 throw new AssertionError();
213 }
214 }
Christian Edward Gruber791bd292014-05-20 19:36:41 -0700215
gak4d860192016-10-13 23:41:00 -0700216 static TypeName parameterizedGeneratedTypeNameForBinding(Binding binding) {
ronshapiro6475f472016-02-02 07:03:10 -0800217 ClassName className = generatedClassNameForBinding(binding);
gak4d860192016-10-13 23:41:00 -0700218 ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding);
219 return typeParameters.isEmpty()
220 ? className
221 : ParameterizedTypeName.get(className, Iterables.toArray(typeParameters, TypeName.class));
Christian Edward Gruber10876b02014-06-01 11:24:55 -0700222 }
223
ronshapiro6475f472016-02-02 07:03:10 -0800224 static ClassName membersInjectorNameForType(TypeElement typeElement) {
ronshapiroa296c102016-01-14 12:33:42 -0800225 return siblingClassName(typeElement, "_MembersInjector");
ronshapiro732e5022016-01-07 12:10:55 -0800226 }
227
ronshapiro6475f472016-02-02 07:03:10 -0800228 static String classFileName(ClassName className) {
ronshapiro732e5022016-01-07 12:10:55 -0800229 return CLASS_FILE_NAME_JOINER.join(className.simpleNames());
230 }
231
ronshapiro6475f472016-02-02 07:03:10 -0800232 static ClassName generatedMonitoringModuleName(
ronshapiroa296c102016-01-14 12:33:42 -0800233 TypeElement componentElement) {
234 return siblingClassName(componentElement, "_MonitoringModule");
235 }
236
beder893f7702016-02-17 05:22:50 -0800237 static ClassName generatedProductionExecutorModuleName(TypeElement componentElement) {
238 return siblingClassName(componentElement, "_ProductionExecutorModule");
239 }
240
ronshapiro6475f472016-02-02 07:03:10 -0800241 // TODO(ronshapiro): when JavaPoet migration is complete, replace the duplicated code
242 // which could use this.
243 private static ClassName siblingClassName(TypeElement typeElement, String suffix) {
244 ClassName className = ClassName.get(typeElement);
ronshapiro23527f62016-04-20 10:33:08 -0700245 return className.topLevelClassName().peerClass(classFileName(className) + suffix);
beder0e6f2482015-10-23 08:37:55 -0700246 }
247
ronshapiro2c9ec492017-05-26 15:03:38 -0700248 /**
249 * The {@link java.util.Set} factory class name appropriate for set bindings.
250 *
251 * <ul>
252 * <li>{@link SetFactory} for provision bindings.
253 * <li>{@link SetProducer} for production bindings for {@code Set<T>}.
254 * <li>{@link SetOfProducedProducer} for production bindings for {@code Set<Produced<T>>}.
255 * </ul>
256 */
257 static ClassName setFactoryClassName(ContributionBinding binding) {
258 checkArgument(binding.bindingKind().equals(SYNTHETIC_MULTIBOUND_SET));
259 if (binding.bindingType().equals(BindingType.PROVISION)) {
260 return SET_FACTORY;
261 } else {
262 SetType setType = SetType.from(binding.key());
263 return setType.elementsAreTypeOf(Produced.class) ? SET_OF_PRODUCED_PRODUCER : SET_PRODUCER;
264 }
265 }
266
ronshapirob4499112017-08-30 11:34:31 -0700267 /** The {@link java.util.Map} factory class name appropriate for map bindings. */
ronshapiro2c9ec492017-05-26 15:03:38 -0700268 static ClassName mapFactoryClassName(ContributionBinding binding) {
ronshapirob4499112017-08-30 11:34:31 -0700269 checkState(binding.bindingKind().equals(SYNTHETIC_MULTIBOUND_MAP), binding.bindingKind());
270 MapType mapType = MapType.from(binding.key());
ronshapiro2c9ec492017-05-26 15:03:38 -0700271 switch (binding.bindingType()) {
sraube1028b72017-08-28 18:03:22 -0700272 case PROVISION:
ronshapirob4499112017-08-30 11:34:31 -0700273 return mapType.valuesAreTypeOf(Provider.class) ? MAP_PROVIDER_FACTORY : MAP_FACTORY;
274 case PRODUCTION:
275 return mapType.valuesAreFrameworkType()
276 ? mapType.valuesAreTypeOf(Producer.class)
277 ? MAP_OF_PRODUCER_PRODUCER
278 : MAP_OF_PRODUCED_PRODUCER
279 : MAP_PRODUCER;
ronshapiro2c9ec492017-05-26 15:03:38 -0700280 default:
ronshapirob4499112017-08-30 11:34:31 -0700281 throw new IllegalArgumentException(binding.bindingType().toString());
ronshapiro2c9ec492017-05-26 15:03:38 -0700282 }
283 }
284
dpb6bd55de2015-09-28 07:58:09 -0700285 private static String factoryPrefix(ContributionBinding binding) {
Christian Edward Gruber4aec5ba2014-05-20 19:37:11 -0700286 switch (binding.bindingKind()) {
287 case INJECTION:
Christian Edward Gruber791bd292014-05-20 19:36:41 -0700288 return "";
Christian Edward Gruber791bd292014-05-20 19:36:41 -0700289
dpb6bd55de2015-09-28 07:58:09 -0700290 case PROVISION:
Ron Shapiro5f21a802016-09-21 10:23:55 -0400291 case PRODUCTION:
dpb6bd55de2015-09-28 07:58:09 -0700292 return CaseFormat.LOWER_CAMEL.to(
dpbdd276032016-07-11 09:34:43 -0700293 UPPER_CAMEL, binding.bindingElement().get().getSimpleName().toString());
dpb6bd55de2015-09-28 07:58:09 -0700294
beder72466bc2014-12-08 10:51:05 -0800295 default:
296 throw new IllegalArgumentException();
297 }
298 }
299
ronshapiro9540fb22016-01-13 09:46:47 -0800300 static ImmutableList<TypeVariableName> bindingTypeElementTypeVariableNames(Binding binding) {
gak4d860192016-10-13 23:41:00 -0700301 if (binding instanceof ContributionBinding) {
302 ContributionBinding contributionBinding = (ContributionBinding) binding;
303 if (!contributionBinding.bindingKind().equals(INJECTION)
304 && !contributionBinding.requiresModuleInstance()) {
305 return ImmutableList.of();
306 }
307 }
ronshapiro0b1bcc62017-05-26 11:14:20 -0700308 List<? extends TypeParameterElement> typeParameters =
309 binding.bindingTypeElement().get().getTypeParameters();
310 return typeParameters.stream().map(TypeVariableName::get).collect(toImmutableList());
ronshapiro9540fb22016-01-13 09:46:47 -0800311 }
312
gak645274e2017-01-04 11:34:17 -0800313 /**
314 * Returns a name to be used for variables of the given {@linkplain TypeElement type}. Prefer
315 * semantically meaningful variable names, but if none can be derived, this will produce something
316 * readable.
317 */
318 // TODO(gak): maybe this should be a function of TypeMirrors instead of Elements?
319 static String simpleVariableName(TypeElement typeElement) {
320 String candidateName = UPPER_CAMEL.to(LOWER_CAMEL, typeElement.getSimpleName().toString());
321 String variableName = protectAgainstKeywords(candidateName);
322 verify(isName(variableName), "'%s' was expected to be a valid variable name");
323 return variableName;
324 }
325
326 private static String protectAgainstKeywords(String candidateName) {
327 switch (candidateName) {
328 case "package":
329 return "pkg";
330 case "boolean":
331 return "b";
332 case "double":
333 return "d";
334 case "byte":
335 return "b";
336 case "int":
337 return "i";
338 case "short":
339 return "s";
340 case "char":
341 return "c";
342 case "void":
343 return "v";
344 case "class":
345 return "clazz";
346 case "float":
347 return "f";
348 case "long":
349 return "l";
350 default:
351 return SourceVersion.isKeyword(candidateName) ? candidateName + '_' : candidateName;
352 }
353 }
354
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700355 private SourceFiles() {}
356}