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