1 /* 2 * Copyright (C) 2020 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.binding; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.collect.Iterables.getOnlyElement; 21 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 22 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 23 import static dagger.internal.codegen.langmodel.DaggerElements.isAnnotationPresent; 24 import static dagger.internal.codegen.xprocessing.XElements.asConstructor; 25 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; 26 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 27 import static kotlin.streams.jdk8.StreamsKt.asStream; 28 29 import androidx.room.compiler.processing.XConstructorElement; 30 import androidx.room.compiler.processing.XConstructorType; 31 import androidx.room.compiler.processing.XElement; 32 import androidx.room.compiler.processing.XHasModifiers; 33 import androidx.room.compiler.processing.XMethodElement; 34 import androidx.room.compiler.processing.XMethodType; 35 import androidx.room.compiler.processing.XType; 36 import androidx.room.compiler.processing.XTypeElement; 37 import androidx.room.compiler.processing.XVariableElement; 38 import com.google.auto.common.MoreElements; 39 import com.google.auto.value.AutoValue; 40 import com.google.auto.value.extension.memoized.Memoized; 41 import com.google.common.collect.ImmutableList; 42 import com.google.common.collect.ImmutableMap; 43 import com.google.common.collect.ImmutableSet; 44 import com.squareup.javapoet.ParameterSpec; 45 import com.squareup.javapoet.TypeName; 46 import dagger.assisted.Assisted; 47 import dagger.assisted.AssistedFactory; 48 import dagger.internal.codegen.javapoet.TypeNames; 49 import dagger.spi.model.BindingKind; 50 import java.util.List; 51 import java.util.Optional; 52 import javax.lang.model.element.VariableElement; 53 54 /** Assisted injection utility methods. */ 55 public final class AssistedInjectionAnnotations { 56 /** Returns the factory method for the given factory {@link XTypeElement}. */ assistedFactoryMethod(XTypeElement factory)57 public static XMethodElement assistedFactoryMethod(XTypeElement factory) { 58 return getOnlyElement(assistedFactoryMethods(factory)); 59 } 60 61 /** Returns the list of abstract factory methods for the given factory {@link XTypeElement}. */ assistedFactoryMethods(XTypeElement factory)62 public static ImmutableSet<XMethodElement> assistedFactoryMethods(XTypeElement factory) { 63 return asStream(factory.getAllNonPrivateInstanceMethods()) 64 .filter(XHasModifiers::isAbstract) 65 .filter(method -> !method.isJavaDefault()) 66 .collect(toImmutableSet()); 67 } 68 69 /** Returns {@code true} if the element uses assisted injection. */ isAssistedInjectionType(XTypeElement typeElement)70 public static boolean isAssistedInjectionType(XTypeElement typeElement) { 71 return assistedInjectedConstructors(typeElement).stream() 72 .anyMatch(constructor -> constructor.hasAnnotation(TypeNames.ASSISTED_INJECT)); 73 } 74 75 /** Returns {@code true} if this binding is an assisted factory. */ isAssistedFactoryType(XElement element)76 public static boolean isAssistedFactoryType(XElement element) { 77 return element.hasAnnotation(TypeNames.ASSISTED_FACTORY); 78 } 79 80 /** 81 * Returns the list of assisted parameters as {@link ParameterSpec}s. 82 * 83 * <p>The type of each parameter will be the resolved type given by the binding key, and the name 84 * of each parameter will be the name given in the {@link 85 * dagger.assisted.AssistedInject}-annotated constructor. 86 */ assistedParameterSpecs(Binding binding)87 public static ImmutableList<ParameterSpec> assistedParameterSpecs(Binding binding) { 88 checkArgument(binding.kind() == BindingKind.ASSISTED_INJECTION); 89 XConstructorElement constructor = asConstructor(binding.bindingElement().get()); 90 XConstructorType constructorType = constructor.asMemberOf(binding.key().type().xprocessing()); 91 return assistedParameterSpecs(constructor.getParameters(), constructorType.getParameterTypes()); 92 } 93 assistedParameterSpecs( List<? extends XVariableElement> paramElements, List<XType> paramTypes)94 private static ImmutableList<ParameterSpec> assistedParameterSpecs( 95 List<? extends XVariableElement> paramElements, List<XType> paramTypes) { 96 ImmutableList.Builder<ParameterSpec> assistedParameterSpecs = ImmutableList.builder(); 97 for (int i = 0; i < paramElements.size(); i++) { 98 XVariableElement paramElement = paramElements.get(i); 99 XType paramType = paramTypes.get(i); 100 if (isAssistedParameter(paramElement)) { 101 assistedParameterSpecs.add( 102 ParameterSpec.builder(paramType.getTypeName(), getSimpleName(paramElement)).build()); 103 } 104 } 105 return assistedParameterSpecs.build(); 106 } 107 108 /** 109 * Returns the list of assisted factory parameters as {@link ParameterSpec}s. 110 * 111 * <p>The type of each parameter will be the resolved type given by the binding key, and the name 112 * of each parameter will be the name given in the {@link 113 * dagger.assisted.AssistedInject}-annotated constructor. 114 */ assistedFactoryParameterSpecs(Binding binding)115 public static ImmutableList<ParameterSpec> assistedFactoryParameterSpecs(Binding binding) { 116 checkArgument(binding.kind() == BindingKind.ASSISTED_FACTORY); 117 118 XTypeElement factory = asTypeElement(binding.bindingElement().get()); 119 AssistedFactoryMetadata metadata = AssistedFactoryMetadata.create(factory.getType()); 120 XMethodType factoryMethodType = 121 metadata.factoryMethod().asMemberOf(binding.key().type().xprocessing()); 122 return assistedParameterSpecs( 123 // Use the order of the parameters from the @AssistedFactory method but use the parameter 124 // names of the @AssistedInject constructor. 125 metadata.assistedFactoryAssistedParameters().stream() 126 .map(metadata.assistedInjectAssistedParametersMap()::get) 127 .collect(toImmutableList()), 128 factoryMethodType.getParameterTypes()); 129 } 130 131 /** Returns the constructors in {@code type} that are annotated with {@link AssistedInject}. */ assistedInjectedConstructors(XTypeElement type)132 public static ImmutableSet<XConstructorElement> assistedInjectedConstructors(XTypeElement type) { 133 return type.getConstructors().stream() 134 .filter(constructor -> constructor.hasAnnotation(TypeNames.ASSISTED_INJECT)) 135 .collect(toImmutableSet()); 136 } 137 assistedParameters(Binding binding)138 public static ImmutableList<XVariableElement> assistedParameters(Binding binding) { 139 return binding.kind() == BindingKind.ASSISTED_INJECTION 140 ? asConstructor(binding.bindingElement().get()).getParameters().stream() 141 .filter(AssistedInjectionAnnotations::isAssistedParameter) 142 .collect(toImmutableList()) 143 : ImmutableList.of(); 144 } 145 146 /** Returns {@code true} if this binding is uses assisted injection. */ isAssistedParameter(XVariableElement param)147 public static boolean isAssistedParameter(XVariableElement param) { 148 return param.hasAnnotation(TypeNames.ASSISTED); 149 } 150 151 /** Returns {@code true} if this binding is uses assisted injection. */ isAssistedParameter(VariableElement param)152 public static boolean isAssistedParameter(VariableElement param) { 153 return isAnnotationPresent(MoreElements.asVariable(param), TypeNames.ASSISTED); 154 } 155 156 /** Metadata about an {@link dagger.assisted.AssistedFactory} annotated type. */ 157 @AutoValue 158 public abstract static class AssistedFactoryMetadata { create(XType factoryType)159 public static AssistedFactoryMetadata create(XType factoryType) { 160 XTypeElement factoryElement = factoryType.getTypeElement(); 161 XMethodElement factoryMethod = assistedFactoryMethod(factoryElement); 162 XMethodType factoryMethodType = factoryMethod.asMemberOf(factoryType); 163 XType assistedInjectType = factoryMethodType.getReturnType(); 164 XTypeElement assistedInjectElement = assistedInjectType.getTypeElement(); 165 return new AutoValue_AssistedInjectionAnnotations_AssistedFactoryMetadata( 166 factoryElement, 167 factoryType, 168 factoryMethod, 169 factoryMethodType, 170 assistedInjectElement, 171 assistedInjectType, 172 AssistedInjectionAnnotations.assistedInjectAssistedParameters(assistedInjectType), 173 AssistedInjectionAnnotations.assistedFactoryAssistedParameters( 174 factoryMethod, factoryMethodType)); 175 } 176 factory()177 public abstract XTypeElement factory(); 178 factoryType()179 public abstract XType factoryType(); 180 factoryMethod()181 public abstract XMethodElement factoryMethod(); 182 factoryMethodType()183 public abstract XMethodType factoryMethodType(); 184 assistedInjectElement()185 public abstract XTypeElement assistedInjectElement(); 186 assistedInjectType()187 public abstract XType assistedInjectType(); 188 assistedInjectAssistedParameters()189 public abstract ImmutableList<AssistedParameter> assistedInjectAssistedParameters(); 190 assistedFactoryAssistedParameters()191 public abstract ImmutableList<AssistedParameter> assistedFactoryAssistedParameters(); 192 193 @Memoized assistedInjectAssistedParametersMap()194 public ImmutableMap<AssistedParameter, XVariableElement> assistedInjectAssistedParametersMap() { 195 ImmutableMap.Builder<AssistedParameter, XVariableElement> builder = ImmutableMap.builder(); 196 for (AssistedParameter assistedParameter : assistedInjectAssistedParameters()) { 197 builder.put(assistedParameter, assistedParameter.element()); 198 } 199 return builder.build(); 200 } 201 202 @Memoized 203 public ImmutableMap<AssistedParameter, XVariableElement> assistedFactoryAssistedParametersMap()204 assistedFactoryAssistedParametersMap() { 205 ImmutableMap.Builder<AssistedParameter, XVariableElement> builder = ImmutableMap.builder(); 206 for (AssistedParameter assistedParameter : assistedFactoryAssistedParameters()) { 207 builder.put(assistedParameter, assistedParameter.element()); 208 } 209 return builder.build(); 210 } 211 } 212 213 /** 214 * Metadata about an {@link Assisted} annotated parameter. 215 * 216 * <p>This parameter can represent an {@link Assisted} annotated parameter from an {@link 217 * AssistedInject} constructor or an {@link AssistedFactory} method. 218 */ 219 @AutoValue 220 public abstract static class AssistedParameter { create(XVariableElement parameter, XType parameterType)221 public static AssistedParameter create(XVariableElement parameter, XType parameterType) { 222 AssistedParameter assistedParameter = 223 new AutoValue_AssistedInjectionAnnotations_AssistedParameter( 224 Optional.ofNullable(parameter.getAnnotation(TypeNames.ASSISTED)) 225 .map(assisted -> assisted.getAsString("value")) 226 .orElse(""), 227 parameterType.getTypeName()); 228 assistedParameter.parameterElement = parameter; 229 assistedParameter.parameterType = parameterType; 230 return assistedParameter; 231 } 232 233 private XVariableElement parameterElement; 234 private XType parameterType; 235 236 /** Returns the string qualifier from the {@link Assisted#value()}. */ qualifier()237 public abstract String qualifier(); 238 239 /** Returns the type annotated with {@link Assisted}. */ typeName()240 abstract TypeName typeName(); 241 242 /** Returns the type annotated with {@link Assisted}. */ type()243 public final XType type() { 244 return parameterType; 245 } 246 element()247 public final XVariableElement element() { 248 return parameterElement; 249 } 250 251 @Override toString()252 public final String toString() { 253 return qualifier().isEmpty() 254 ? String.format("@Assisted %s", type()) 255 : String.format("@Assisted(\"%s\") %s", qualifier(), type()); 256 } 257 } 258 assistedInjectAssistedParameters( XType assistedInjectType)259 public static ImmutableList<AssistedParameter> assistedInjectAssistedParameters( 260 XType assistedInjectType) { 261 // We keep track of the constructor both as an ExecutableElement to access @Assisted 262 // parameters and as an ExecutableType to access the resolved parameter types. 263 XConstructorElement assistedInjectConstructor = 264 getOnlyElement(assistedInjectedConstructors(assistedInjectType.getTypeElement())); 265 XConstructorType assistedInjectConstructorType = 266 assistedInjectConstructor.asMemberOf(assistedInjectType); 267 268 ImmutableList.Builder<AssistedParameter> builder = ImmutableList.builder(); 269 for (int i = 0; i < assistedInjectConstructor.getParameters().size(); i++) { 270 XVariableElement parameter = assistedInjectConstructor.getParameters().get(i); 271 XType parameterType = assistedInjectConstructorType.getParameterTypes().get(i); 272 if (parameter.hasAnnotation(TypeNames.ASSISTED)) { 273 builder.add(AssistedParameter.create(parameter, parameterType)); 274 } 275 } 276 return builder.build(); 277 } 278 assistedFactoryAssistedParameters( XMethodElement factoryMethod, XMethodType factoryMethodType)279 private static ImmutableList<AssistedParameter> assistedFactoryAssistedParameters( 280 XMethodElement factoryMethod, XMethodType factoryMethodType) { 281 ImmutableList.Builder<AssistedParameter> builder = ImmutableList.builder(); 282 for (int i = 0; i < factoryMethod.getParameters().size(); i++) { 283 XVariableElement parameter = factoryMethod.getParameters().get(i); 284 XType parameterType = factoryMethodType.getParameterTypes().get(i); 285 builder.add(AssistedParameter.create(parameter, parameterType)); 286 } 287 return builder.build(); 288 } 289 AssistedInjectionAnnotations()290 private AssistedInjectionAnnotations() {} 291 } 292