blob: ded5f6f67ee140ecab3a4f42edc21e5682b5a130 [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;
gak645274e2017-01-04 11:34:17 -080022import static com.google.common.base.Verify.verify;
gak4d860192016-10-13 23:41:00 -070023import static dagger.internal.codegen.ContributionBinding.Kind.INJECTION;
ronshapiro2c9ec492017-05-26 15:03:38 -070024import static dagger.internal.codegen.ContributionBinding.Kind.SYNTHETIC_MULTIBOUND_SET;
dpbffd98f62016-12-20 10:05:16 -080025import static dagger.internal.codegen.Optionals.optionalComparator;
gake55f0742016-07-12 15:36:42 -070026import static dagger.internal.codegen.TypeNames.DOUBLE_CHECK;
ronshapiro2c9ec492017-05-26 15:03:38 -070027import static dagger.internal.codegen.TypeNames.MAP_FACTORY;
28import static dagger.internal.codegen.TypeNames.MAP_OF_PRODUCED_PRODUCER;
29import static dagger.internal.codegen.TypeNames.MAP_OF_PRODUCER_PRODUCER;
30import static dagger.internal.codegen.TypeNames.MAP_PRODUCER;
31import static dagger.internal.codegen.TypeNames.MAP_PROVIDER_FACTORY;
gake55f0742016-07-12 15:36:42 -070032import static dagger.internal.codegen.TypeNames.PROVIDER_OF_LAZY;
ronshapiro2c9ec492017-05-26 15:03:38 -070033import static dagger.internal.codegen.TypeNames.SET_FACTORY;
34import static dagger.internal.codegen.TypeNames.SET_OF_PRODUCED_PRODUCER;
35import static dagger.internal.codegen.TypeNames.SET_PRODUCER;
ronshapiro0b1bcc62017-05-26 11:14:20 -070036import static dagger.internal.codegen.Util.toImmutableList;
dpbffd98f62016-12-20 10:05:16 -080037import static java.util.Comparator.comparing;
gak645274e2017-01-04 11:34:17 -080038import static javax.lang.model.SourceVersion.isName;
gake55f0742016-07-12 15:36:42 -070039
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070040import com.google.common.base.CaseFormat;
ronshapiro732e5022016-01-07 12:10:55 -080041import com.google.common.base.Joiner;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070042import com.google.common.collect.FluentIterable;
sameb0d49be82015-01-22 17:29:48 -080043import com.google.common.collect.ImmutableList;
Christian Edward Gruber2a900692014-07-01 22:28:26 -070044import com.google.common.collect.ImmutableMap;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070045import com.google.common.collect.ImmutableSet;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070046import com.google.common.collect.Iterables;
ronshapiroe05f9212017-08-08 11:22:11 -070047import com.google.common.collect.Maps;
ronshapiro6475f472016-02-02 07:03:10 -080048import com.squareup.javapoet.ClassName;
ronshapiro732e5022016-01-07 12:10:55 -080049import com.squareup.javapoet.CodeBlock;
ronshapiroe05f9212017-08-08 11:22:11 -070050import com.squareup.javapoet.FieldSpec;
ronshapiro6475f472016-02-02 07:03:10 -080051import com.squareup.javapoet.ParameterizedTypeName;
52import com.squareup.javapoet.TypeName;
ronshapiro9540fb22016-01-13 09:46:47 -080053import com.squareup.javapoet.TypeVariableName;
sraube1028b72017-08-28 18:03:22 -070054import dagger.internal.MapFactory;
55import dagger.internal.MapProviderFactory;
ronshapiro2c9ec492017-05-26 15:03:38 -070056import dagger.internal.SetFactory;
57import dagger.producers.Produced;
sraube1028b72017-08-28 18:03:22 -070058import dagger.producers.internal.MapOfProducerProducer;
59import dagger.producers.internal.MapProducer;
ronshapiro2c9ec492017-05-26 15:03:38 -070060import dagger.producers.internal.SetOfProducedProducer;
61import dagger.producers.internal.SetProducer;
Ron Shapiro5f21a802016-09-21 10:23:55 -040062import java.util.Comparator;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070063import java.util.Iterator;
ronshapiro0b1bcc62017-05-26 11:14:20 -070064import java.util.List;
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 =
gak75b9b722016-09-29 15:48:40 -0700129 FluentIterable.from(dependencyRequests).transform(new DependencyVariableNamer()).toSet();
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
sraube1028b72017-08-28 18:03:22 -0700267 /**
268 * The {@link java.util.Map}-of-value factory class name appropriate for map bindings.
269 *
270 * <ul>
271 * <li>{@link MapFactory} for provision bindings.
272 * <li>{@link MapProducer} for production bindings.
273 * </ul>
274 */
ronshapiro2c9ec492017-05-26 15:03:38 -0700275 static ClassName mapFactoryClassName(ContributionBinding binding) {
276 switch (binding.bindingType()) {
ronshapiro6f910262017-08-28 14:56:58 -0700277 case PRODUCTION:
sraube1028b72017-08-28 18:03:22 -0700278 return MapType.from(binding.key()).valuesAreTypeOf(Produced.class)
279 ? MAP_OF_PRODUCED_PRODUCER : MAP_PRODUCER;
280
281 case PROVISION:
282 return MAP_FACTORY;
283
ronshapiro2c9ec492017-05-26 15:03:38 -0700284 default:
sraube1028b72017-08-28 18:03:22 -0700285 throw new AssertionError(binding.toString());
ronshapiro2c9ec492017-05-26 15:03:38 -0700286 }
287 }
288
sraube1028b72017-08-28 18:03:22 -0700289 /**
290 * The {@link java.util.Map}-of-framework factory class name appropriate for map bindings.
291 *
292 * <ul>
293 * <li>{@link MapProviderFactory} for provision bindings.
294 * <li>{@link MapOfProducerProducer} for production bindings.
295 * </ul>
296 */
297 static ClassName frameworkMapFactoryClassName(BindingType bindingType) {
298 return bindingType.equals(BindingType.PRODUCTION)
299 ? MAP_OF_PRODUCER_PRODUCER : MAP_PROVIDER_FACTORY;
300 }
301
dpb6bd55de2015-09-28 07:58:09 -0700302 private static String factoryPrefix(ContributionBinding binding) {
Christian Edward Gruber4aec5ba2014-05-20 19:37:11 -0700303 switch (binding.bindingKind()) {
304 case INJECTION:
Christian Edward Gruber791bd292014-05-20 19:36:41 -0700305 return "";
Christian Edward Gruber791bd292014-05-20 19:36:41 -0700306
dpb6bd55de2015-09-28 07:58:09 -0700307 case PROVISION:
Ron Shapiro5f21a802016-09-21 10:23:55 -0400308 case PRODUCTION:
dpb6bd55de2015-09-28 07:58:09 -0700309 return CaseFormat.LOWER_CAMEL.to(
dpbdd276032016-07-11 09:34:43 -0700310 UPPER_CAMEL, binding.bindingElement().get().getSimpleName().toString());
dpb6bd55de2015-09-28 07:58:09 -0700311
beder72466bc2014-12-08 10:51:05 -0800312 default:
313 throw new IllegalArgumentException();
314 }
315 }
316
ronshapiro9540fb22016-01-13 09:46:47 -0800317 static ImmutableList<TypeVariableName> bindingTypeElementTypeVariableNames(Binding binding) {
gak4d860192016-10-13 23:41:00 -0700318 if (binding instanceof ContributionBinding) {
319 ContributionBinding contributionBinding = (ContributionBinding) binding;
320 if (!contributionBinding.bindingKind().equals(INJECTION)
321 && !contributionBinding.requiresModuleInstance()) {
322 return ImmutableList.of();
323 }
324 }
ronshapiro0b1bcc62017-05-26 11:14:20 -0700325 List<? extends TypeParameterElement> typeParameters =
326 binding.bindingTypeElement().get().getTypeParameters();
327 return typeParameters.stream().map(TypeVariableName::get).collect(toImmutableList());
ronshapiro9540fb22016-01-13 09:46:47 -0800328 }
329
gak645274e2017-01-04 11:34:17 -0800330 /**
331 * Returns a name to be used for variables of the given {@linkplain TypeElement type}. Prefer
332 * semantically meaningful variable names, but if none can be derived, this will produce something
333 * readable.
334 */
335 // TODO(gak): maybe this should be a function of TypeMirrors instead of Elements?
336 static String simpleVariableName(TypeElement typeElement) {
337 String candidateName = UPPER_CAMEL.to(LOWER_CAMEL, typeElement.getSimpleName().toString());
338 String variableName = protectAgainstKeywords(candidateName);
339 verify(isName(variableName), "'%s' was expected to be a valid variable name");
340 return variableName;
341 }
342
343 private static String protectAgainstKeywords(String candidateName) {
344 switch (candidateName) {
345 case "package":
346 return "pkg";
347 case "boolean":
348 return "b";
349 case "double":
350 return "d";
351 case "byte":
352 return "b";
353 case "int":
354 return "i";
355 case "short":
356 return "s";
357 case "char":
358 return "c";
359 case "void":
360 return "v";
361 case "class":
362 return "clazz";
363 case "float":
364 return "f";
365 case "long":
366 return "l";
367 default:
368 return SourceVersion.isKeyword(candidateName) ? candidateName + '_' : candidateName;
369 }
370 }
371
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700372 private SourceFiles() {}
373}