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