diff --git a/src/test/java/dagger/sheath/CtxWrapper.java b/src/test/java/dagger/sheath/CtxWrapper.java index c31b7fc22..5a2ba31e3 100644 --- a/src/test/java/dagger/sheath/CtxWrapper.java +++ b/src/test/java/dagger/sheath/CtxWrapper.java @@ -1,12 +1,14 @@ package dagger.sheath; import dagger.sheath.binding.BindingMethod; +import dagger.sheath.reflection.Signature; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.reflect.FieldUtils; import javax.inject.Provider; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.Objects; import java.util.function.Consumer; @@ -20,19 +22,68 @@ public CtxWrapper(Object ctx) { } public Object get(Class clazz) { - try { + return Signature.of(clazz); + } - // binding by available providers - final var provider = this.findProviderFor(clazz); - if (provider != null) { - log.debug("status=beanSolved, from=Provider, beanClass={}", clazz); - return provider.getValue(); + public Object get(Signature signature) { + { + final var found = findUsingProvider(signature.getClazz()); + if (found != null) { + log.debug("status=foundUsingProvider"); + return found; + } + } + + { + final var found = findUsingBindingMethods(signature.getClazz()); + if (found != null) { + log.debug("status=foundUsingBindingMethods"); + return found; + } + } + + { + final var found = findUsingCtx(signature); + if (found != null) { + log.debug("status=foundByUsingCtx"); + return found; + } + } + log.debug("status=notFound, class={}", signature); + return null; + + // todo procurar a classe que o obj grah impl estende ou a interface que ele implementa + // Pegar a anotação @Component e pegar os modulos + // andar pelos metodos de cada modulo procurando pelo método que retorna o tipo da interface desejada + // e que tenha @Binds , provides nao serve como ele pode receber um tipo pra internamente montar o + // tipo retornado mas daih nao da obter a instancia + + } + + private Object findUsingCtx(Signature signature) { + try { + final var method = MethodUtils + .getAllMethods(this.getCtxClass()) + .stream() + .filter(it -> isAssignable(it, signature)) + .findFirst(); + if (method.isPresent()) { + return MethodUtils.invoke(method.get(), this.ctx, true); } } catch (Throwable e) { - log.warn("status=failedToFindByProvider, msg={}", e.getMessage()); + log.warn("status=failedToFindByMethodOnCtx, msg={}", e.getMessage()); } + return null; + } + + private static boolean isAssignable(Method m, Signature sig) { + final var mSig = Signature.ofMethodReturnType(m); + final var assignable = mSig.isSameOrInheritFrom(sig) && m.getParameterTypes().length == 0; + log.trace("status=comparing, assignable={}, mSig={}, sig={}", assignable, mSig, sig); + return assignable; + } - // find by binding methods + private Object findUsingBindingMethods(Class clazz) { try { final var bindingMethod = BindingMethod.findBindingMethod(this); if (bindingMethod == null) { @@ -44,29 +95,21 @@ public Object get(Class clazz) { } catch (Throwable e) { log.warn("status=failedToFindByBinding, msg={}", e.getMessage()); } + return null; + } - // find by ctx obj methods + private Object findUsingProvider(Class clazz) { try { - final var method = MethodUtils - .getAllMethods(this.getCtxClass()) - .stream() - .filter(it -> it.getReturnType().isAssignableFrom(clazz) && it.getParameterTypes().length == 0) - .findFirst(); - if (method.isPresent()) { - return MethodUtils.invoke(method.get(), this.ctx, true); + final var provider = this.findProviderFor(clazz); + if (provider != null) { + log.debug("status=beanSolved, from=Provider, beanClass={}", clazz); + return provider.getValue(); } + return null; } catch (Throwable e) { - log.warn("status=failedToFindByMethodOnCtx, msg={}", e.getMessage()); + log.warn("status=failedToFindByProvider, msg={}", e.getMessage()); + return null; } - - return null; - - // todo procurar a classe que o obj grah impl estende ou a interface que ele implementa - // Pegar a anotação @Component e pegar os modulos - // andar pelos metodos de cada modulo procurando pelo método que retorna o tipo da interface desejada - // e que tenha @Binds , provides nao serve como ele pode receber um tipo pra internamente montar o - // tipo retornado mas daih nao da obter a instancia - } public Object getCtx() { @@ -126,4 +169,5 @@ static Field findFirstProviderFieldWithType(final Class clazz, Class wante public Class getCtxClass() { return this.ctx.getClass(); } + } diff --git a/src/test/java/dagger/sheath/junit/DaggerExtension.java b/src/test/java/dagger/sheath/junit/DaggerExtension.java index 40a4b61bf..21eccd57d 100644 --- a/src/test/java/dagger/sheath/junit/DaggerExtension.java +++ b/src/test/java/dagger/sheath/junit/DaggerExtension.java @@ -6,7 +6,9 @@ import dagger.sheath.InjectSpy; import dagger.sheath.NopSupplier; import dagger.sheath.ProviderWrapper; +import dagger.sheath.reflection.Signature; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.commons.lang3.reflect.MethodUtils; import org.junit.jupiter.api.extension.AfterAllCallback; @@ -64,6 +66,7 @@ public void afterAll(ExtensionContext context) throws Exception { @Override public void beforeEach(ExtensionContext context) throws Exception { + log.debug("status=beforeEach"); injectMocksAndSpies(context); injectFields(context); resetMocks(context); @@ -143,11 +146,11 @@ private static void inject( try { log.debug("status=injectMockSpyInField, field={} {}", field.getType().getSimpleName(), field.getName()); ctxWrapper.initializeWithOrThrows(field.getType(), initializer); - final var mock = ctxWrapper.get(field.getType()); + final var mock = ctxWrapper.get(Signature.of(field)); if (!validator.test(mock)) { throw new IllegalStateException(String.format("Mock/Stub didn't work for type: %s", field.getType())); } - FieldUtils.writeField(field, instance, mock, true); + writeTo(instance, field, mock); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } @@ -179,14 +182,23 @@ Optional findAnnotation(final ExtensionContext context static void injectFields(ExtensionContext context) throws IllegalAccessException { final var ctx = findCtxWrapper(context); final var testInstances = context.getRequiredTestInstances().getAllInstances(); - for (Object instance : testInstances) { - final var fields = FieldUtils.getFieldsListWithAnnotation(instance.getClass(), Inject.class); + for (Object testInstance : testInstances) { + final var fields = FieldUtils.getFieldsListWithAnnotation(testInstance.getClass(), Inject.class); for (Field field : fields) { - FieldUtils.writeField(field, instance, ctx.get(field.getType()), true); + final var foundInstance = ctx.get(Signature.of(field)); + writeTo(testInstance, field, foundInstance); } } } + private static void writeTo(Object testInstance, Field field, Object foundInstance) throws IllegalAccessException { + FieldUtils.writeField(field, testInstance, foundInstance, true); + log.debug( + "status=written, testClass={}, field={}, value={}, classToFind={}, generic={}", + field.getName(), ClassUtils.getSimpleName(testInstance), foundInstance, field.getType(), field.getGenericType() + ); + } + static CtxWrapper findCtxWrapper(ExtensionContext context) { return context.getStore(DAGGER).get(DAGGER_CTX_WRAPPER, CtxWrapper.class); } diff --git a/src/test/java/dagger/sheath/reflection/Signature.java b/src/test/java/dagger/sheath/reflection/Signature.java new file mode 100644 index 000000000..407bc123d --- /dev/null +++ b/src/test/java/dagger/sheath/reflection/Signature.java @@ -0,0 +1,89 @@ +package dagger.sheath.reflection; + +import com.google.common.reflect.TypeToken; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.apache.commons.lang3.ObjectUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +@Value +@Builder +@EqualsAndHashCode(of = "typeArguments") +public class Signature { + + private Class clazz; + private Type[] typeArguments; + + + public boolean isSameOrInheritFrom(Signature sig) { + return this.clazz.isAssignableFrom(sig.getClazz()) && this.areTypeArgumentsSameOrInheritFrom(sig); + } + + boolean areTypeArgumentsSameOrInheritFrom(Signature sig) { + if (ObjectUtils.allNull(this.typeArguments, sig.typeArguments)) { + return true; + } + if (this.typeArguments == null && sig.typeArguments != null) { + return true; + } + if (sig.typeArguments == null) { + return false; + } + if (this.typeArguments.length != sig.typeArguments.length) { + return false; + } + for (int i = 0; i < this.typeArguments.length; i++) { + if (!this.isTypeArgumentSameOrInheritFrom(sig, i)) { + return false; + } + } + return true; + } + + private boolean isTypeArgumentSameOrInheritFrom(Signature sig, int i) { + final var type = TypeToken.of(this.typeArguments[i]).getRawType(); + final var otherType = TypeToken.of(sig.typeArguments[i]).getRawType(); + return type.isAssignableFrom(otherType); + } + + public String getFirstTypeArgumentName() { + if (this.typeArguments != null && this.typeArguments.length > 0) { + return this.typeArguments[0].getTypeName(); + } + return null; + } + + public static Signature of(Field f) { + return Signature + .builder() + .clazz(f.getType()) + .typeArguments(findTypeArguments(f.getGenericType())) + .build(); + } + + public static Signature of(Type type) { + return Signature + .builder() + .clazz(TypeToken.of(type).getRawType()) + .typeArguments(findTypeArguments(type)) + .build(); + } + + public static Signature ofMethodReturnType(Method m) { + return of(m.getGenericReturnType()); + } + + private static Type[] findTypeArguments(Type type) { + if (type instanceof ParameterizedType) { + return ((ParameterizedType) type).getActualTypeArguments(); + } + return null; + } + + +} diff --git a/src/test/java/dagger/sheath/reflection/SignatureTest.java b/src/test/java/dagger/sheath/reflection/SignatureTest.java new file mode 100644 index 000000000..9ab8b46a8 --- /dev/null +++ b/src/test/java/dagger/sheath/reflection/SignatureTest.java @@ -0,0 +1,130 @@ +package dagger.sheath.reflection; + +import dagger.sheath.templates.SignatureTemplates; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SignatureTest { + + @Test + void mustConvertFieldToSignature(){ + final var sig = fieldToSignature(Car.class, "passengers"); + assertEquals("Signature(clazz=interface java.util.List, typeArguments=[class java.lang.String])", sig.toString()); + } + + @Test + void mustGetCorrectTypeFromField(){ + final var signature = fieldToSignature(Car.class, "passengers"); + assertEquals(List.class, signature.getClazz()); + assertEquals("java.lang.String", signature.getFirstTypeArgumentName()); + + } + + @Test + void mustMatchFieldsWithSameTypeAndGenerics(){ + + final var passengers = fieldToSignature(Car.class, "passengers"); + final var accessories = fieldToSignature(Car.class, "accessories"); + + assertEquals(passengers, accessories); + + } + + @Test + void fieldsWithDifferentGenericCantMatch(){ + + final var passengers = fieldToSignature(Car.class, "passengers"); + final var accessories = fieldToSignature(Car.class, "tripsKms"); + + assertNotEquals(passengers, accessories); + + } + + @Test + void mustMatchFieldsWithCompatibleTypes(){ + + final var ancestor = SignatureTemplates.listOfNumber(); + final var impl = SignatureTemplates.listOfInteger(); + + assertTrue(ancestor.isSameOrInheritFrom(impl)); + + } + + + @Test + void differentNumberOfTypeArgumentsMustNotMatch(){ + + final var ancestor = SignatureTemplates.pairOfString(); + final var impl = SignatureTemplates.listOfString(); + + assertFalse(ancestor.areTypeArgumentsSameOrInheritFrom(impl)); + + } + + @Test + void fieldsWithIncompatibleTypesArgumentsMustNotMatch(){ + + final var ancestor = SignatureTemplates.listOfNumber(); + final var impl = SignatureTemplates.listOfString(); + + assertFalse(ancestor.isSameOrInheritFrom(impl)); + + } + + @Test + void fieldsWithMoreThanOneTypeArgumentAndIncompatibleTypesArgumentsMustNotMatch(){ + + final var ancestor = SignatureTemplates.pairOfString(); + final var impl = SignatureTemplates.pairOfStringAndInteger(); + + assertFalse(ancestor.isSameOrInheritFrom(impl)); + + } + + @Test + void whenImplHasNotTypeArgumentsTheyCantMatch(){ + + final var ancestor = SignatureTemplates.pairOfString(); + final var impl = SignatureTemplates.pair(); + + assertFalse(ancestor.isSameOrInheritFrom(impl)); + + } + + @Test + void ancestorsWithoutTypeArgumentsSpecificationMustMatch(){ + + final var ancestor = SignatureTemplates.list(); + final var impl = SignatureTemplates.listOfString(); + + assertTrue(ancestor.isSameOrInheritFrom(impl)); + + } + + @Test + void mustParseMethodReturnTypeWithTypeArguments(){ + final var method = SignatureTemplates.ofMethodIteratorList(); + + final var sig = Signature.ofMethodReturnType(method); + + assertEquals("Signature(clazz=interface java.util.Iterator, typeArguments=[E])", sig.toString()); + } + + private static Signature fieldToSignature(final Class clazz, final String fieldName) { + return Signature.of(FieldUtils.getField(clazz, fieldName, true)); + } + + static class Car { + List passengers = new ArrayList<>(); + List accessories = new ArrayList<>(); + List tripsKms = new ArrayList<>(); + } +} diff --git a/src/test/java/dagger/sheath/templates/SignatureTemplates.java b/src/test/java/dagger/sheath/templates/SignatureTemplates.java new file mode 100644 index 000000000..35dac0370 --- /dev/null +++ b/src/test/java/dagger/sheath/templates/SignatureTemplates.java @@ -0,0 +1,43 @@ +package dagger.sheath.templates; + +import com.fasterxml.jackson.core.type.TypeReference; +import dagger.sheath.reflection.Signature; +import org.apache.commons.lang3.reflect.MethodUtils; +import org.graalvm.collections.Pair; + +import java.lang.reflect.Method; +import java.util.List; + +public class SignatureTemplates { + public static Signature listOfNumber() { + return Signature.of(new TypeReference>() {}.getType()); + } + + public static Signature listOfInteger() { + return Signature.of(new TypeReference>() {}.getType()); + } + + public static Signature listOfString() { + return Signature.of(new TypeReference>() {}.getType()); + } + + public static Signature list() { + return Signature.of(new TypeReference() {}.getType()); + } + + public static Signature pairOfString() { + return Signature.of(new TypeReference>() {}.getType()); + } + + public static Signature pair() { + return Signature.of(new TypeReference() {}.getType()); + } + + public static Signature pairOfStringAndInteger() { + return Signature.of(new TypeReference>() {}.getType()); + } + + public static Method ofMethodIteratorList() { + return MethodUtils.getMatchingMethod(List.class, "iterator"); + } +}