blob: 42fd91006b2f2aeff0cf9f0c056ed21f38d2052e [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;
ronshapiro6475f472016-02-02 07:03:10 -080047import com.squareup.javapoet.ClassName;
ronshapiro732e5022016-01-07 12:10:55 -080048import com.squareup.javapoet.CodeBlock;
ronshapiro6475f472016-02-02 07:03:10 -080049import com.squareup.javapoet.ParameterizedTypeName;
50import com.squareup.javapoet.TypeName;
ronshapiro9540fb22016-01-13 09:46:47 -080051import com.squareup.javapoet.TypeVariableName;
ronshapiro2c9ec492017-05-26 15:03:38 -070052import dagger.internal.MapFactory;
53import dagger.internal.MapProviderFactory;
54import dagger.internal.SetFactory;
55import dagger.producers.Produced;
56import dagger.producers.internal.MapOfProducerProducer;
57import dagger.producers.internal.MapProducer;
58import dagger.producers.internal.SetOfProducedProducer;
59import dagger.producers.internal.SetProducer;
Ron Shapiro5f21a802016-09-21 10:23:55 -040060import java.util.Comparator;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070061import java.util.Iterator;
ronshapiro0b1bcc62017-05-26 11:14:20 -070062import java.util.List;
gak645274e2017-01-04 11:34:17 -080063import javax.lang.model.SourceVersion;
gak66241102016-09-13 12:23:00 -070064import javax.lang.model.element.Element;
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070065import javax.lang.model.element.TypeElement;
ronshapiro9540fb22016-01-13 09:46:47 -080066import javax.lang.model.element.TypeParameterElement;
Gregory Kick03c3c2d2014-06-18 17:05:47 -070067
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070068/**
69 * Utilities for generating files.
70 *
71 * @author Gregory Kick
72 * @since 2.0
73 */
74class SourceFiles {
ronshapiro732e5022016-01-07 12:10:55 -080075
ronshapiro6475f472016-02-02 07:03:10 -080076 private static final Joiner CLASS_FILE_NAME_JOINER = Joiner.on('_');
ronshapiro732e5022016-01-07 12:10:55 -080077
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070078 /**
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070079 * Sorts {@link DependencyRequest} instances in an order likely to reflect their logical
80 * importance.
81 */
Ron Shapiro5f21a802016-09-21 10:23:55 -040082 static final Comparator<DependencyRequest> DEPENDENCY_ORDERING =
83 // put fields before parameters
dpbffd98f62016-12-20 10:05:16 -080084 comparing(
85 (DependencyRequest request) -> request.requestElement().map(Element::getKind),
Ron Shapiro5f21a802016-09-21 10:23:55 -040086 optionalComparator())
87 // order by dependency kind
88 .thenComparing(DependencyRequest::kind)
89 // then sort by name
90 .thenComparing(
dpbffd98f62016-12-20 10:05:16 -080091 request ->
92 request.requestElement().map(element -> element.getSimpleName().toString()),
Ron Shapiro5f21a802016-09-21 10:23:55 -040093 optionalComparator());
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070094
sameb2ea676a2015-01-15 10:20:24 -080095 /**
dpb57198802015-12-17 12:18:15 -080096 * Generates names and keys for the factory class fields needed to hold the framework classes for
97 * all of the dependencies of {@code binding}. It is responsible for choosing a name that
98 *
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -070099 * <ul>
dpb57198802015-12-17 12:18:15 -0800100 * <li>represents all of the dependency requests for this key
101 * <li>is <i>probably</i> associated with the type being bound
102 * <li>is unique within the class
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700103 * </ul>
Ron Shapiro5f21a802016-09-21 10:23:55 -0400104 *
dpb57198802015-12-17 12:18:15 -0800105 * @param binding must be an unresolved binding (type parameters must match its type element's)
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700106 */
gak59d2c992015-01-14 14:33:46 -0800107 static ImmutableMap<BindingKey, FrameworkField> generateBindingFieldsForDependencies(
dpbf6be7d62016-01-22 11:43:21 -0800108 Binding binding) {
109 checkArgument(!binding.unresolved().isPresent(), "binding must be unresolved: %s", binding);
110
gak59d2c992015-01-14 14:33:46 -0800111 ImmutableMap.Builder<BindingKey, FrameworkField> bindingFields = ImmutableMap.builder();
gak75b9b722016-09-29 15:48:40 -0700112 for (Binding.DependencyAssociation dependencyAssociation : binding.dependencyAssociations()) {
113 FrameworkDependency frameworkDependency = dependencyAssociation.frameworkDependency();
dpbf6be7d62016-01-22 11:43:21 -0800114 bindingFields.put(
115 frameworkDependency.bindingKey(),
dpbd289ddc2016-04-28 13:09:11 -0700116 FrameworkField.create(
117 ClassName.get(frameworkDependency.frameworkClass()),
118 TypeName.get(frameworkDependency.bindingKey().key().type()),
gak75b9b722016-09-29 15:48:40 -0700119 fieldNameForDependency(dependencyAssociation.dependencyRequests())));
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700120 }
beder3e950f32014-12-19 17:48:47 -0800121 return bindingFields.build();
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700122 }
123
gak75b9b722016-09-29 15:48:40 -0700124 private static String fieldNameForDependency(ImmutableSet<DependencyRequest> dependencyRequests) {
dpbf6be7d62016-01-22 11:43:21 -0800125 // collect together all of the names that we would want to call the provider
126 ImmutableSet<String> dependencyNames =
gak75b9b722016-09-29 15:48:40 -0700127 FluentIterable.from(dependencyRequests).transform(new DependencyVariableNamer()).toSet();
dpbf6be7d62016-01-22 11:43:21 -0800128
129 if (dependencyNames.size() == 1) {
130 // if there's only one name, great! use it!
131 return Iterables.getOnlyElement(dependencyNames);
132 } else {
133 // in the event that a field is being used for a bunch of deps with different names,
134 // add all the names together with "And"s in the middle. E.g.: stringAndS
135 Iterator<String> namesIterator = dependencyNames.iterator();
136 String first = namesIterator.next();
137 StringBuilder compositeNameBuilder = new StringBuilder(first);
138 while (namesIterator.hasNext()) {
139 compositeNameBuilder
140 .append("And")
141 .append(CaseFormat.LOWER_CAMEL.to(UPPER_CAMEL, namesIterator.next()));
142 }
143 return compositeNameBuilder.toString();
144 }
145 }
146
ronshapiro732e5022016-01-07 12:10:55 -0800147 static CodeBlock frameworkTypeUsageStatement(
148 CodeBlock frameworkTypeMemberSelect, DependencyRequest.Kind dependencyKind) {
149 switch (dependencyKind) {
150 case LAZY:
ronshapirob9158d82016-04-13 11:00:56 -0700151 return CodeBlock.of("$T.lazy($L)", DOUBLE_CHECK, frameworkTypeMemberSelect);
ronshapiro732e5022016-01-07 12:10:55 -0800152 case INSTANCE:
153 case FUTURE:
ronshapirob9158d82016-04-13 11:00:56 -0700154 return CodeBlock.of("$L.get()", frameworkTypeMemberSelect);
ronshapiro732e5022016-01-07 12:10:55 -0800155 case PROVIDER:
156 case PRODUCER:
157 case MEMBERS_INJECTOR:
dpbd8d950a2016-08-30 09:24:37 -0700158 return frameworkTypeMemberSelect;
dpbc9bd6d82016-04-19 07:25:06 -0700159 case PROVIDER_OF_LAZY:
160 return CodeBlock.of("$T.create($L)", PROVIDER_OF_LAZY, frameworkTypeMemberSelect);
dpbd8d950a2016-08-30 09:24:37 -0700161 default: // including PRODUCED
162 throw new AssertionError(dependencyKind);
ronshapiro732e5022016-01-07 12:10:55 -0800163 }
164 }
165
dpb6bd55de2015-09-28 07:58:09 -0700166 /**
167 * Returns the generated factory or members injector name for a binding.
168 */
169 static ClassName generatedClassNameForBinding(Binding binding) {
170 switch (binding.bindingType()) {
Christian Edward Gruber4aec5ba2014-05-20 19:37:11 -0700171 case PROVISION:
dpb6bd55de2015-09-28 07:58:09 -0700172 case PRODUCTION:
173 ContributionBinding contribution = (ContributionBinding) binding;
dpbdd276032016-07-11 09:34:43 -0700174 checkArgument(contribution.bindingTypeElement().isPresent());
175 ClassName enclosingClassName = ClassName.get(contribution.bindingTypeElement().get());
dpb6bd55de2015-09-28 07:58:09 -0700176 switch (contribution.bindingKind()) {
177 case INJECTION:
178 case PROVISION:
Ron Shapiro5f21a802016-09-21 10:23:55 -0400179 case PRODUCTION:
dpb6bd55de2015-09-28 07:58:09 -0700180 return enclosingClassName
181 .topLevelClassName()
ronshapiro6475f472016-02-02 07:03:10 -0800182 .peerClass(
ronshapiro23527f62016-04-20 10:33:08 -0700183 classFileName(enclosingClassName)
dpb6bd55de2015-09-28 07:58:09 -0700184 + "_"
185 + factoryPrefix(contribution)
186 + "Factory");
187
188 default:
189 throw new AssertionError();
190 }
191
192 case MEMBERS_INJECTION:
dpb4955ef92016-04-29 12:31:14 -0700193 return membersInjectorNameForType(
194 ((MembersInjectionBinding) binding).membersInjectedType());
dpb6bd55de2015-09-28 07:58:09 -0700195
Christian Edward Gruber4aec5ba2014-05-20 19:37:11 -0700196 default:
197 throw new AssertionError();
198 }
199 }
Christian Edward Gruber791bd292014-05-20 19:36:41 -0700200
gak4d860192016-10-13 23:41:00 -0700201 static TypeName parameterizedGeneratedTypeNameForBinding(Binding binding) {
ronshapiro6475f472016-02-02 07:03:10 -0800202 ClassName className = generatedClassNameForBinding(binding);
gak4d860192016-10-13 23:41:00 -0700203 ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding);
204 return typeParameters.isEmpty()
205 ? className
206 : ParameterizedTypeName.get(className, Iterables.toArray(typeParameters, TypeName.class));
Christian Edward Gruber10876b02014-06-01 11:24:55 -0700207 }
208
ronshapiro6475f472016-02-02 07:03:10 -0800209 static ClassName membersInjectorNameForType(TypeElement typeElement) {
ronshapiroa296c102016-01-14 12:33:42 -0800210 return siblingClassName(typeElement, "_MembersInjector");
ronshapiro732e5022016-01-07 12:10:55 -0800211 }
212
ronshapiro6475f472016-02-02 07:03:10 -0800213 static String classFileName(ClassName className) {
ronshapiro732e5022016-01-07 12:10:55 -0800214 return CLASS_FILE_NAME_JOINER.join(className.simpleNames());
215 }
216
ronshapiro6475f472016-02-02 07:03:10 -0800217 static ClassName generatedMonitoringModuleName(
ronshapiroa296c102016-01-14 12:33:42 -0800218 TypeElement componentElement) {
219 return siblingClassName(componentElement, "_MonitoringModule");
220 }
221
beder893f7702016-02-17 05:22:50 -0800222 static ClassName generatedProductionExecutorModuleName(TypeElement componentElement) {
223 return siblingClassName(componentElement, "_ProductionExecutorModule");
224 }
225
ronshapiro6475f472016-02-02 07:03:10 -0800226 // TODO(ronshapiro): when JavaPoet migration is complete, replace the duplicated code
227 // which could use this.
228 private static ClassName siblingClassName(TypeElement typeElement, String suffix) {
229 ClassName className = ClassName.get(typeElement);
ronshapiro23527f62016-04-20 10:33:08 -0700230 return className.topLevelClassName().peerClass(classFileName(className) + suffix);
beder0e6f2482015-10-23 08:37:55 -0700231 }
232
ronshapiro2c9ec492017-05-26 15:03:38 -0700233 /**
234 * The {@link java.util.Set} factory class name appropriate for set bindings.
235 *
236 * <ul>
237 * <li>{@link SetFactory} for provision bindings.
238 * <li>{@link SetProducer} for production bindings for {@code Set<T>}.
239 * <li>{@link SetOfProducedProducer} for production bindings for {@code Set<Produced<T>>}.
240 * </ul>
241 */
242 static ClassName setFactoryClassName(ContributionBinding binding) {
243 checkArgument(binding.bindingKind().equals(SYNTHETIC_MULTIBOUND_SET));
244 if (binding.bindingType().equals(BindingType.PROVISION)) {
245 return SET_FACTORY;
246 } else {
247 SetType setType = SetType.from(binding.key());
248 return setType.elementsAreTypeOf(Produced.class) ? SET_OF_PRODUCED_PRODUCER : SET_PRODUCER;
249 }
250 }
251
252 /**
253 * The {@link java.util.Map}-of-value factory class name appropriate for map bindings.
254 *
255 * <ul>
256 * <li>{@link MapFactory} for provision bindings.
257 * <li>{@link MapProducer} for production bindings.
258 * </ul>
259 */
260 static ClassName mapFactoryClassName(ContributionBinding binding) {
261 switch (binding.bindingType()) {
262 case PRODUCTION:
263 return MapType.from(binding.key()).valuesAreTypeOf(Produced.class)
264 ? MAP_OF_PRODUCED_PRODUCER : MAP_PRODUCER;
265
266 case PROVISION:
267 return MAP_FACTORY;
268
269 default:
270 throw new AssertionError(binding.toString());
271 }
272 }
273
274 /**
275 * The {@link java.util.Map}-of-framework factory class name appropriate for map bindings.
276 *
277 * <ul>
278 * <li>{@link MapProviderFactory} for provision bindings.
279 * <li>{@link MapOfProducerProducer} for production bindings.
280 * </ul>
281 */
282 static ClassName frameworkMapFactoryClassName(BindingType bindingType) {
283 return bindingType.equals(BindingType.PRODUCTION)
284 ? MAP_OF_PRODUCER_PRODUCER : MAP_PROVIDER_FACTORY;
285 }
286
dpb6bd55de2015-09-28 07:58:09 -0700287 private static String factoryPrefix(ContributionBinding binding) {
Christian Edward Gruber4aec5ba2014-05-20 19:37:11 -0700288 switch (binding.bindingKind()) {
289 case INJECTION:
Christian Edward Gruber791bd292014-05-20 19:36:41 -0700290 return "";
Christian Edward Gruber791bd292014-05-20 19:36:41 -0700291
dpb6bd55de2015-09-28 07:58:09 -0700292 case PROVISION:
Ron Shapiro5f21a802016-09-21 10:23:55 -0400293 case PRODUCTION:
dpb6bd55de2015-09-28 07:58:09 -0700294 return CaseFormat.LOWER_CAMEL.to(
dpbdd276032016-07-11 09:34:43 -0700295 UPPER_CAMEL, binding.bindingElement().get().getSimpleName().toString());
dpb6bd55de2015-09-28 07:58:09 -0700296
beder72466bc2014-12-08 10:51:05 -0800297 default:
298 throw new IllegalArgumentException();
299 }
300 }
301
ronshapiro9540fb22016-01-13 09:46:47 -0800302 static ImmutableList<TypeVariableName> bindingTypeElementTypeVariableNames(Binding binding) {
gak4d860192016-10-13 23:41:00 -0700303 if (binding instanceof ContributionBinding) {
304 ContributionBinding contributionBinding = (ContributionBinding) binding;
305 if (!contributionBinding.bindingKind().equals(INJECTION)
306 && !contributionBinding.requiresModuleInstance()) {
307 return ImmutableList.of();
308 }
309 }
ronshapiro0b1bcc62017-05-26 11:14:20 -0700310 List<? extends TypeParameterElement> typeParameters =
311 binding.bindingTypeElement().get().getTypeParameters();
312 return typeParameters.stream().map(TypeVariableName::get).collect(toImmutableList());
ronshapiro9540fb22016-01-13 09:46:47 -0800313 }
314
gak645274e2017-01-04 11:34:17 -0800315 /**
316 * Returns a name to be used for variables of the given {@linkplain TypeElement type}. Prefer
317 * semantically meaningful variable names, but if none can be derived, this will produce something
318 * readable.
319 */
320 // TODO(gak): maybe this should be a function of TypeMirrors instead of Elements?
321 static String simpleVariableName(TypeElement typeElement) {
322 String candidateName = UPPER_CAMEL.to(LOWER_CAMEL, typeElement.getSimpleName().toString());
323 String variableName = protectAgainstKeywords(candidateName);
324 verify(isName(variableName), "'%s' was expected to be a valid variable name");
325 return variableName;
326 }
327
328 private static String protectAgainstKeywords(String candidateName) {
329 switch (candidateName) {
330 case "package":
331 return "pkg";
332 case "boolean":
333 return "b";
334 case "double":
335 return "d";
336 case "byte":
337 return "b";
338 case "int":
339 return "i";
340 case "short":
341 return "s";
342 case "char":
343 return "c";
344 case "void":
345 return "v";
346 case "class":
347 return "clazz";
348 case "float":
349 return "f";
350 case "long":
351 return "l";
352 default:
353 return SourceVersion.isKeyword(candidateName) ? candidateName + '_' : candidateName;
354 }
355 }
356
Christian Edward Gruberfbb8e4b2014-04-15 14:02:34 -0700357 private SourceFiles() {}
358}