package bullet.impl; import java.util.List; import java.util.Objects; import javax.inject.Provider; import javax.inject.Qualifier; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import com.google.auto.common.AnnotationMirrors; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; import com.google.common.base.MoreObjects; import com.google.common.base.Optional; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import dagger.Lazy; import dagger.MembersInjector; import dagger.Subcomponent; @AutoValue abstract class ComponentMethodDescriptor { enum ComponentMethodKind { SIMPLE_PROVISION, PROVIDER_OR_LAZY, SIMPLE_MEMBERS_INJECTION, MEMBERS_INJECTOR, } abstract ComponentMethodKind kind(); abstract DeclaredType type(); abstract String name(); @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj == null || obj.getClass() != this.getClass()) { return false; } ComponentMethodDescriptor other = (ComponentMethodDescriptor) obj; return Objects.equals(this.kind(), other.kind()) && MoreTypes.equivalence().equivalent(this.type(), other.type()) && Objects.equals(this.name(), other.name()); } @Override public int hashCode() { return Objects.hash( this.kind(), MoreTypes.equivalence().wrap(this.type()), this.name()); } @Override public String toString() { switch (this.kind()) { case SIMPLE_PROVISION: return MethodSpec.methodBuilder(this.name()) .returns(TypeName.get(this.type())) .build() .toString(); case PROVIDER_OR_LAZY: return MethodSpec.methodBuilder(this.name()) .returns(ParameterizedTypeName.get(ClassName.get(Provider.class), TypeName.get(this.type()))) .build() .toString(); case SIMPLE_MEMBERS_INJECTION: return MethodSpec.methodBuilder(this.name()) .addParameter(TypeName.get(this.type()), "instance") .build() .toString(); case MEMBERS_INJECTOR: return MethodSpec.methodBuilder(this.name()) .returns(ParameterizedTypeName.get(ClassName.get(MembersInjector.class), TypeName.get(this.type()))) .build() .toString(); default: return super.toString(); } } static Optional<ComponentMethodDescriptor> forComponentMethod(Types types, DeclaredType componentElement, ExecutableElement componentMethod) { // Using same algorithm as Dagger's ComponentDescriptor#getDescriptorForComponentMethod ExecutableType resolvedComponentMethod = MoreTypes.asExecutable(types.asMemberOf(componentElement, componentMethod)); TypeMirror returnType = resolvedComponentMethod.getReturnType(); if (returnType.getKind() == TypeKind.DECLARED) { if (MoreTypes.isTypeOf(Provider.class, returnType) || MoreTypes.isTypeOf(Lazy.class, returnType)) { return methodDescriptor( ComponentMethodKind.PROVIDER_OR_LAZY, MoreTypes.asDeclared(MoreTypes.asDeclared(returnType).getTypeArguments().get(0)), componentMethod); } else if (MoreTypes.isTypeOf(MembersInjector.class, returnType)) { return methodDescriptor( ComponentMethodKind.MEMBERS_INJECTOR, MoreTypes.asDeclared(MoreTypes.asDeclared(returnType).getTypeArguments().get(0)), componentMethod); } else if (MoreElements.getAnnotationMirror(types.asElement(returnType), Subcomponent.class).isPresent()) { // Ignore subcomponent methods return Optional.absent(); } } if (resolvedComponentMethod.getParameterTypes().isEmpty() && resolvedComponentMethod.getReturnType().getKind() == TypeKind.DECLARED) { return methodDescriptor( ComponentMethodKind.SIMPLE_PROVISION, MoreTypes.asDeclared(returnType), componentMethod); } List<? extends TypeMirror> parameterTypes = resolvedComponentMethod.getParameterTypes(); if (parameterTypes.size() == 1 && parameterTypes.get(0).getKind() == TypeKind.DECLARED && (returnType.getKind().equals(TypeKind.VOID) || types.isSameType(returnType, parameterTypes.get(0)))) { return methodDescriptor( ComponentMethodKind.SIMPLE_MEMBERS_INJECTION, MoreTypes.asDeclared(parameterTypes.get(0)), componentMethod); } // Let Dagger do the validation return Optional.absent(); } private static Optional<ComponentMethodDescriptor> methodDescriptor( ComponentMethodKind kind, DeclaredType type, ExecutableElement componentMethod) { // ObjectGraph API doesn't allow passing qualifier as input, so ignore those methods. if (hasQualifier(componentMethod)) { return Optional.absent(); } return Optional.<ComponentMethodDescriptor>of(new AutoValue_ComponentMethodDescriptor(kind, type, componentMethod.getSimpleName().toString())); } static boolean hasQualifier(Element e) { return !AnnotationMirrors.getAnnotatedAnnotations(e, Qualifier.class).isEmpty(); } }