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; 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.DaggerStreams.toImmutableList; 25 import static dagger.internal.codegen.DaggerStreams.toImmutableSet; 26 import static dagger.internal.codegen.Optionals.optionalComparator; 27 import static dagger.internal.codegen.javapoet.TypeNames.DOUBLE_CHECK; 28 import static dagger.internal.codegen.javapoet.TypeNames.MAP_FACTORY; 29 import static dagger.internal.codegen.javapoet.TypeNames.MAP_OF_PRODUCED_PRODUCER; 30 import static dagger.internal.codegen.javapoet.TypeNames.MAP_OF_PRODUCER_PRODUCER; 31 import static dagger.internal.codegen.javapoet.TypeNames.MAP_PRODUCER; 32 import static dagger.internal.codegen.javapoet.TypeNames.MAP_PROVIDER_FACTORY; 33 import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER_OF_LAZY; 34 import static dagger.internal.codegen.javapoet.TypeNames.SET_FACTORY; 35 import static dagger.internal.codegen.javapoet.TypeNames.SET_OF_PRODUCED_PRODUCER; 36 import static dagger.internal.codegen.javapoet.TypeNames.SET_PRODUCER; 37 import static dagger.model.BindingKind.INJECTION; 38 import static dagger.model.BindingKind.MULTIBOUND_MAP; 39 import static dagger.model.BindingKind.MULTIBOUND_SET; 40 import static java.util.Comparator.comparing; 41 import static javax.lang.model.SourceVersion.isName; 42 43 import com.google.auto.common.MoreElements; 44 import com.google.common.base.CaseFormat; 45 import com.google.common.base.Joiner; 46 import com.google.common.collect.ImmutableList; 47 import com.google.common.collect.ImmutableMap; 48 import com.google.common.collect.ImmutableSet; 49 import com.google.common.collect.Iterables; 50 import com.google.common.collect.Maps; 51 import com.squareup.javapoet.ClassName; 52 import com.squareup.javapoet.CodeBlock; 53 import com.squareup.javapoet.FieldSpec; 54 import com.squareup.javapoet.ParameterizedTypeName; 55 import com.squareup.javapoet.TypeName; 56 import com.squareup.javapoet.TypeVariableName; 57 import dagger.internal.SetFactory; 58 import dagger.model.DependencyRequest; 59 import dagger.model.Key; 60 import dagger.model.RequestKind; 61 import dagger.producers.Produced; 62 import dagger.producers.Producer; 63 import dagger.producers.internal.SetOfProducedProducer; 64 import dagger.producers.internal.SetProducer; 65 import java.util.Comparator; 66 import java.util.Iterator; 67 import java.util.List; 68 import javax.inject.Provider; 69 import javax.lang.model.SourceVersion; 70 import javax.lang.model.element.Element; 71 import javax.lang.model.element.ElementKind; 72 import javax.lang.model.element.ExecutableElement; 73 import javax.lang.model.element.TypeElement; 74 import javax.lang.model.element.TypeParameterElement; 75 76 /** 77 * Utilities for generating files. 78 */ 79 class SourceFiles { 80 81 private static final Joiner CLASS_FILE_NAME_JOINER = Joiner.on('_'); 82 83 /** 84 * Sorts {@link DependencyRequest} instances in an order likely to reflect their logical 85 * importance. 86 */ 87 static final Comparator<DependencyRequest> DEPENDENCY_ORDERING = 88 // put fields before parameters 89 comparing( 90 (DependencyRequest request) -> request.requestElement().map(Element::getKind), 91 optionalComparator()) 92 // order by dependency kind 93 .thenComparing(DependencyRequest::kind) 94 // then sort by name 95 .thenComparing( 96 request -> 97 request.requestElement().map(element -> element.getSimpleName().toString()), 98 optionalComparator()); 99 100 /** 101 * 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 * 104 * <ul> 105 * <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 108 * </ul> 109 * 110 * @param binding must be an unresolved binding (type parameters must match its type element's) 111 */ generateBindingFieldsForDependencies( Binding binding)112 static ImmutableMap<Key, FrameworkField> generateBindingFieldsForDependencies( 113 Binding binding) { 114 checkArgument(!binding.unresolved().isPresent(), "binding must be unresolved: %s", binding); 115 116 ImmutableMap.Builder<Key, FrameworkField> bindingFields = ImmutableMap.builder(); 117 for (Binding.DependencyAssociation dependencyAssociation : binding.dependencyAssociations()) { 118 FrameworkDependency frameworkDependency = dependencyAssociation.frameworkDependency(); 119 bindingFields.put( 120 frameworkDependency.key(), 121 FrameworkField.create( 122 ClassName.get(frameworkDependency.frameworkClass()), 123 TypeName.get(frameworkDependency.key().type()), 124 fieldNameForDependency(dependencyAssociation.dependencyRequests()))); 125 } 126 return bindingFields.build(); 127 } 128 fieldNameForDependency(ImmutableSet<DependencyRequest> dependencyRequests)129 private static String fieldNameForDependency(ImmutableSet<DependencyRequest> dependencyRequests) { 130 // collect together all of the names that we would want to call the provider 131 ImmutableSet<String> dependencyNames = 132 dependencyRequests.stream().map(DependencyVariableNamer::name).collect(toImmutableSet()); 133 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 frameworkTypeUsageStatement( CodeBlock frameworkTypeMemberSelect, RequestKind dependencyKind)152 static CodeBlock frameworkTypeUsageStatement( 153 CodeBlock frameworkTypeMemberSelect, RequestKind dependencyKind) { 154 switch (dependencyKind) { 155 case LAZY: 156 return CodeBlock.of("$T.lazy($L)", DOUBLE_CHECK, frameworkTypeMemberSelect); 157 case INSTANCE: 158 case FUTURE: 159 return CodeBlock.of("$L.get()", frameworkTypeMemberSelect); 160 case PROVIDER: 161 case PRODUCER: 162 return frameworkTypeMemberSelect; 163 case PROVIDER_OF_LAZY: 164 return CodeBlock.of("$T.create($L)", PROVIDER_OF_LAZY, frameworkTypeMemberSelect); 165 default: // including PRODUCED 166 throw new AssertionError(dependencyKind); 167 } 168 } 169 170 /** 171 * Returns a mapping of {@link DependencyRequest}s to {@link CodeBlock}s that {@linkplain 172 * #frameworkTypeUsageStatement(CodeBlock, RequestKind) use them}. 173 */ frameworkFieldUsages( ImmutableSet<DependencyRequest> dependencies, ImmutableMap<Key, FieldSpec> fields)174 static ImmutableMap<DependencyRequest, CodeBlock> frameworkFieldUsages( 175 ImmutableSet<DependencyRequest> dependencies, ImmutableMap<Key, FieldSpec> fields) { 176 return Maps.toMap( 177 dependencies, 178 dep -> 179 frameworkTypeUsageStatement(CodeBlock.of("$N", fields.get(dep.key())), dep.kind())); 180 } 181 182 /** 183 * Returns the generated factory or members injector name for a binding. 184 */ generatedClassNameForBinding(Binding binding)185 static ClassName generatedClassNameForBinding(Binding binding) { 186 switch (binding.bindingType()) { 187 case PROVISION: 188 case PRODUCTION: 189 ContributionBinding contribution = (ContributionBinding) binding; 190 switch (contribution.kind()) { 191 case INJECTION: 192 case PROVISION: 193 case PRODUCTION: 194 return elementBasedClassName( 195 MoreElements.asExecutable(binding.bindingElement().get()), "Factory"); 196 197 default: 198 throw new AssertionError(); 199 } 200 201 case MEMBERS_INJECTION: 202 return membersInjectorNameForType( 203 ((MembersInjectionBinding) binding).membersInjectedType()); 204 } 205 throw new AssertionError(); 206 } 207 208 /** 209 * Calculates an appropriate {@link ClassName} for a generated class that is based on {@code 210 * element}, appending {@code suffix} at the end. 211 * 212 * <p>This will always return a {@linkplain ClassName#topLevelClassName() top level class name}, 213 * even if {@code element}'s enclosing class is a nested type. 214 */ elementBasedClassName(ExecutableElement element, String suffix)215 static ClassName elementBasedClassName(ExecutableElement element, String suffix) { 216 ClassName enclosingClassName = 217 ClassName.get(MoreElements.asType(element.getEnclosingElement())); 218 String methodName = 219 element.getKind().equals(ElementKind.CONSTRUCTOR) 220 ? "" 221 : LOWER_CAMEL.to(UPPER_CAMEL, element.getSimpleName().toString()); 222 return ClassName.get( 223 enclosingClassName.packageName(), 224 classFileName(enclosingClassName) + "_" + methodName + suffix); 225 } 226 parameterizedGeneratedTypeNameForBinding(Binding binding)227 static TypeName parameterizedGeneratedTypeNameForBinding(Binding binding) { 228 ClassName className = generatedClassNameForBinding(binding); 229 ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding); 230 return typeParameters.isEmpty() 231 ? className 232 : ParameterizedTypeName.get(className, Iterables.toArray(typeParameters, TypeName.class)); 233 } 234 membersInjectorNameForType(TypeElement typeElement)235 static ClassName membersInjectorNameForType(TypeElement typeElement) { 236 return siblingClassName(typeElement, "_MembersInjector"); 237 } 238 classFileName(ClassName className)239 static String classFileName(ClassName className) { 240 return CLASS_FILE_NAME_JOINER.join(className.simpleNames()); 241 } 242 generatedMonitoringModuleName( TypeElement componentElement)243 static ClassName generatedMonitoringModuleName( 244 TypeElement componentElement) { 245 return siblingClassName(componentElement, "_MonitoringModule"); 246 } 247 248 // TODO(ronshapiro): when JavaPoet migration is complete, replace the duplicated code 249 // which could use this. siblingClassName(TypeElement typeElement, String suffix)250 private static ClassName siblingClassName(TypeElement typeElement, String suffix) { 251 ClassName className = ClassName.get(typeElement); 252 return className.topLevelClassName().peerClass(classFileName(className) + suffix); 253 } 254 255 /** 256 * The {@link java.util.Set} factory class name appropriate for set bindings. 257 * 258 * <ul> 259 * <li>{@link SetFactory} for provision bindings. 260 * <li>{@link SetProducer} for production bindings for {@code Set<T>}. 261 * <li>{@link SetOfProducedProducer} for production bindings for {@code Set<Produced<T>>}. 262 * </ul> 263 */ setFactoryClassName(ContributionBinding binding)264 static ClassName setFactoryClassName(ContributionBinding binding) { 265 checkArgument(binding.kind().equals(MULTIBOUND_SET)); 266 if (binding.bindingType().equals(BindingType.PROVISION)) { 267 return SET_FACTORY; 268 } else { 269 SetType setType = SetType.from(binding.key()); 270 return setType.elementsAreTypeOf(Produced.class) ? SET_OF_PRODUCED_PRODUCER : SET_PRODUCER; 271 } 272 } 273 274 /** The {@link java.util.Map} factory class name appropriate for map bindings. */ mapFactoryClassName(ContributionBinding binding)275 static ClassName mapFactoryClassName(ContributionBinding binding) { 276 checkState(binding.kind().equals(MULTIBOUND_MAP), binding.kind()); 277 MapType mapType = MapType.from(binding.key()); 278 switch (binding.bindingType()) { 279 case PROVISION: 280 return mapType.valuesAreTypeOf(Provider.class) ? MAP_PROVIDER_FACTORY : MAP_FACTORY; 281 case PRODUCTION: 282 return mapType.valuesAreFrameworkType() 283 ? mapType.valuesAreTypeOf(Producer.class) 284 ? MAP_OF_PRODUCER_PRODUCER 285 : MAP_OF_PRODUCED_PRODUCER 286 : MAP_PRODUCER; 287 default: 288 throw new IllegalArgumentException(binding.bindingType().toString()); 289 } 290 } 291 bindingTypeElementTypeVariableNames(Binding binding)292 static ImmutableList<TypeVariableName> bindingTypeElementTypeVariableNames(Binding binding) { 293 if (binding instanceof ContributionBinding) { 294 ContributionBinding contributionBinding = (ContributionBinding) binding; 295 if (!contributionBinding.kind().equals(INJECTION) 296 && !contributionBinding.requiresModuleInstance()) { 297 return ImmutableList.of(); 298 } 299 } 300 List<? extends TypeParameterElement> typeParameters = 301 binding.bindingTypeElement().get().getTypeParameters(); 302 return typeParameters.stream().map(TypeVariableName::get).collect(toImmutableList()); 303 } 304 305 /** 306 * Returns a name to be used for variables of the given {@linkplain TypeElement type}. Prefer 307 * semantically meaningful variable names, but if none can be derived, this will produce something 308 * readable. 309 */ 310 // TODO(gak): maybe this should be a function of TypeMirrors instead of Elements? simpleVariableName(TypeElement typeElement)311 static String simpleVariableName(TypeElement typeElement) { 312 String candidateName = UPPER_CAMEL.to(LOWER_CAMEL, typeElement.getSimpleName().toString()); 313 String variableName = protectAgainstKeywords(candidateName); 314 verify(isName(variableName), "'%s' was expected to be a valid variable name"); 315 return variableName; 316 } 317 protectAgainstKeywords(String candidateName)318 static String protectAgainstKeywords(String candidateName) { 319 switch (candidateName) { 320 case "package": 321 return "pkg"; 322 case "boolean": 323 return "b"; 324 case "double": 325 return "d"; 326 case "byte": 327 return "b"; 328 case "int": 329 return "i"; 330 case "short": 331 return "s"; 332 case "char": 333 return "c"; 334 case "void": 335 return "v"; 336 case "class": 337 return "clazz"; 338 case "float": 339 return "f"; 340 case "long": 341 return "l"; 342 default: 343 return SourceVersion.isKeyword(candidateName) ? candidateName + '_' : candidateName; 344 } 345 } 346 SourceFiles()347 private SourceFiles() {} 348 } 349