diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java index 92838d59b4fb7..6d67a7b13e822 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; @@ -350,23 +351,41 @@ List getInterceptedOrDecoratedMethods() { Set getDecoratedMethods(DecoratorInfo decorator) { Set decorated = new HashSet<>(); for (Entry entry : decoratedMethods.entrySet()) { - if (entry.getValue().decorators.contains(decorator)) { + if (entry.getValue().contains(decorator)) { decorated.add(entry.getKey()); } } return decorated; } + MethodInfo getDecoratedMethod(MethodInfo decoratorMethod, DecoratorInfo decorator) { + for (Entry e : decoratedMethods.entrySet()) { + for (DecoratorMethod dm : e.getValue().decoratorMethods) { + if (dm.decorator.equals(decorator) && dm.method.equals(decoratorMethod)) { + return e.getKey(); + } + } + } + return null; + } + // Returns a map of method descriptor -> next decorator in the chain // e.g. foo() -> BravoDecorator - Map getNextDecorators(DecoratorInfo decorator) { - Map next = new HashMap<>(); + Map getNextDecorators(DecoratorInfo decorator) { + Map next = new HashMap<>(); for (Entry entry : decoratedMethods.entrySet()) { - List decorators = entry.getValue().decorators; - int index = decorators.indexOf(decorator); + List decoratorMethods = entry.getValue().decoratorMethods; + int index = -1; + for (ListIterator it = decoratorMethods.listIterator(); it.hasNext();) { + DecoratorMethod dm = it.next(); + if (dm.decorator.equals(decorator)) { + index = it.previousIndex(); + break; + } + } if (index != -1) { - if (index != (decorators.size() - 1)) { - next.put(MethodDescriptor.of(entry.getKey()), decorators.get(index + 1)); + if (index != (decoratorMethods.size() - 1)) { + next.put(MethodDescriptor.of(entry.getKey()), decoratorMethods.get(index + 1)); } } } @@ -456,9 +475,9 @@ public List getBoundDecorators() { } List bound = new ArrayList<>(); for (DecorationInfo decoration : decoratedMethods.values()) { - for (DecoratorInfo decorator : decoration.decorators) { - if (!bound.contains(decorator)) { - bound.add(decorator); + for (DecoratorMethod dm : decoration.decoratorMethods) { + if (!bound.contains(dm.decorator)) { + bound.add(dm.decorator); } } } @@ -692,7 +711,7 @@ private Map initDecoratedMethods() { beanDeployment.getBeanArchiveIndex(), beanDeployment.getObserverAndProducerMethods(), beanDeployment.getAnnotationStore())); - Map decoratedMethods = new HashMap<>(candidates.size()); + Map decoratedMethods = new HashMap<>(); for (Entry entry : candidates.entrySet()) { decoratedMethods.put(entry.getKey().method, entry.getValue()); } @@ -706,9 +725,10 @@ private void addDecoratedMethods(Map decoratedMethods if (skipPredicate.test(method)) { continue; } - List matching = findMatchingDecorators(method, boundDecorators); - if (!matching.isEmpty()) { - decoratedMethods.computeIfAbsent(new MethodKey(method), key -> new DecorationInfo(matching)); + List matching = findMatchingDecorators(method, boundDecorators); + MethodKey key = new MethodKey(method); + if (!matching.isEmpty() && !decoratedMethods.containsKey(key)) { + decoratedMethods.put(key, new DecorationInfo(matching)); } } skipPredicate.methodsProcessed(); @@ -720,12 +740,11 @@ private void addDecoratedMethods(Map decoratedMethods } } - private List findMatchingDecorators(MethodInfo method, List decorators) { + private List findMatchingDecorators(MethodInfo method, List decorators) { List methodParams = method.parameterTypes(); - List matching = new ArrayList<>(decorators.size()); + List matching = new ArrayList<>(decorators.size()); for (DecoratorInfo decorator : decorators) { for (Type decoratedType : decorator.getDecoratedTypes()) { - // Converter ClassInfo decoratedTypeClass = decorator.getDeployment().getBeanArchiveIndex() .getClassByName(decoratedType.name()); if (decoratedTypeClass == null) { @@ -750,13 +769,20 @@ private List findMatchingDecorators(MethodInfo method, List decorators; + final List decoratorMethods; - public DecorationInfo(List decorators) { - this.decorators = decorators; + public DecorationInfo(List decoratorMethods) { + this.decoratorMethods = decoratorMethods; } boolean isEmpty() { - return decorators.isEmpty(); + return decoratorMethods.isEmpty(); + } + + boolean contains(DecoratorInfo decorator) { + for (DecoratorMethod dm : decoratorMethods) { + if (dm.decorator.equals(decorator)) { + return true; + } + } + return false; + } + + DecoratorMethod firstDecoratorMethod() { + return decoratorMethods.get(0); + } + + } + + static class DecoratorMethod { + + final DecoratorInfo decorator; + final MethodInfo method; + + public DecoratorMethod(DecoratorInfo decorator, MethodInfo method) { + this.decorator = Objects.requireNonNull(decorator); + this.method = Objects.requireNonNull(method); } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolver.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolver.java index 9e30614c94f04..61a672037430f 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolver.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolver.java @@ -85,4 +85,11 @@ default Set resolveBeans(Type requiredType, AnnotationInstance... requ */ boolean hasQualifier(Collection qualifiers, AnnotationInstance requiredQualifier); + /** + * @param requiredTypeArgument + * @param typeArgument + * @return {@code true} if the required type argument matches the given type argument, {@code false} otherwise + */ + boolean matchTypeArguments(Type requiredTypeArgument, Type typeArgument); + } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolverImpl.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolverImpl.java index 860a43c3ce460..2a446d9bf252f 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolverImpl.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolverImpl.java @@ -185,19 +185,19 @@ boolean matchesNoBoxing(Type requiredType, Type beanType) { throw new IllegalArgumentException("Invalid argument combination " + requiredType + "; " + beanType); } for (int i = 0; i < requiredTypeArguments.size(); i++) { - if (!parametersMatch(requiredTypeArguments.get(i), beanTypeArguments.get(i))) { + if (!matchTypeArguments(requiredTypeArguments.get(i), beanTypeArguments.get(i))) { return false; } } return true; } } else if (WILDCARD_TYPE.equals(requiredType.kind())) { - return parametersMatch(requiredType, beanType); + return matchTypeArguments(requiredType, beanType); } return false; } - boolean parametersMatch(Type requiredParameter, Type beanParameter) { + public boolean matchTypeArguments(Type requiredParameter, Type beanParameter) { if (isActualType(requiredParameter) && isActualType(beanParameter)) { /* * the required type parameter and the bean type parameter are actual types with identical raw type, and, if the diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DelegateInjectionPointResolverImpl.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DelegateInjectionPointResolverImpl.java index 8e01c669758b0..ed5bf4b8d854f 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DelegateInjectionPointResolverImpl.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DelegateInjectionPointResolverImpl.java @@ -21,7 +21,7 @@ class DelegateInjectionPointResolverImpl extends BeanResolverImpl { } @Override - boolean parametersMatch(Type delegateType, Type beanParameter) { + public boolean matchTypeArguments(Type delegateType, Type beanParameter) { // this is the same as for bean types if (isActualType(delegateType) && isActualType(beanParameter)) { /* diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java index 5f57163fe681e..d48294834bc68 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java @@ -140,7 +140,7 @@ private static boolean skipForClientProxy(MethodInfo method, boolean transformUn } static boolean skipForDelegateSubclass(MethodInfo method) { - if (Modifier.isStatic(method.flags())) { + if (Modifier.isStatic(method.flags()) || method.isSynthetic() || isDefault(method)) { return true; } if (IGNORED_METHODS.contains(method.name())) { @@ -153,6 +153,12 @@ static boolean skipForDelegateSubclass(MethodInfo method) { return false; } + static boolean isDefault(MethodInfo method) { + // Default methods are public non-abstract instance methods declared in an interface + return ((method.flags() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC) + && method.declaringClass().isInterface(); + } + static boolean isObjectToString(MethodInfo method) { return method.declaringClass().name().equals(DotNames.OBJECT) && method.name().equals(TO_STRING); } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java index 284a76641324b..126e917a878b8 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java @@ -45,6 +45,7 @@ import io.quarkus.arc.Subclass; import io.quarkus.arc.impl.InterceptedMethodMetadata; import io.quarkus.arc.processor.BeanInfo.DecorationInfo; +import io.quarkus.arc.processor.BeanInfo.DecoratorMethod; import io.quarkus.arc.processor.BeanInfo.InterceptionInfo; import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; import io.quarkus.arc.processor.Methods.MethodKey; @@ -419,11 +420,11 @@ public String apply(List keys) { initMetadataMethodFinal.getMethodParam(1), initMetadataMethodFinal.load(bindingKey)); }); - DecoratorInfo decorator = decoration != null ? decoration.decorators.get(0) : null; + DecoratorMethod decoratorMethod = decoration != null ? decoration.firstDecoratorMethod() : null; ResultHandle decoratorHandle = null; - if (decorator != null) { + if (decoratorMethod != null) { decoratorHandle = initMetadataMethod.readInstanceField(FieldDescriptor.of(subclass.getClassName(), - decorator.getIdentifier(), Object.class.getName()), initMetadataMethod.getThis()); + decoratorMethod.decorator.getIdentifier(), Object.class.getName()), initMetadataMethod.getThis()); } // Instantiate the forwarding function @@ -447,19 +448,22 @@ public String apply(List keys) { } // If a decorator is bound then invoke the method upon the decorator instance instead of the generated forwarding method - if (decorator != null) { + if (decoratorMethod != null) { AssignableResultHandle funDecoratorInstance = funcBytecode.createVariable(Object.class); funcBytecode.assign(funDecoratorInstance, decoratorHandle); - String declaringClass = decorator.getBeanClass().toString(); - if (decorator.isAbstract()) { - String baseName = DecoratorGenerator.createBaseName(decorator.getTarget().get().asClass()); - String targetPackage = DotNames.packageName(decorator.getProviderType().name()); + String declaringClass = decoratorMethod.decorator.getBeanClass().toString(); + if (decoratorMethod.decorator.isAbstract()) { + String baseName = DecoratorGenerator + .createBaseName(decoratorMethod.decorator.getTarget().get().asClass()); + String targetPackage = DotNames.packageName(decoratorMethod.decorator.getProviderType().name()); declaringClass = generatedNameFromTarget(targetPackage, baseName, DecoratorGenerator.ABSTRACT_IMPL_SUFFIX); } + // We need to use the decorator method in order to support generic decorators + MethodDescriptor decoratorMethodDescriptor = MethodDescriptor.of(decoratorMethod.method); MethodDescriptor virtualMethodDescriptor = MethodDescriptor.ofMethod(declaringClass, originalMethodDescriptor.getName(), - originalMethodDescriptor.getReturnType(), originalMethodDescriptor.getParameterTypes()); + decoratorMethodDescriptor.getReturnType(), decoratorMethodDescriptor.getParameterTypes()); funcBytecode .returnValue(funcBytecode.invokeVirtualMethod(virtualMethodDescriptor, funDecoratorInstance, superParamHandles)); @@ -501,9 +505,8 @@ public String apply(List keys) { reflectionRegistration.registerMethod(method); // Finally create the intercepted method - createInterceptedMethod(classOutput, bean, method, subclass, providerTypeName, - metadataField, constructedField.getFieldDescriptor(), forwardDescriptor, - decoration != null ? decoration.decorators.get(0) : null); + createInterceptedMethod(bean, method, subclass, providerTypeName, metadataField, + constructedField.getFieldDescriptor(), forwardDescriptor); } else { // Only decorators are applied MethodCreator decoratedMethod = subclass.getMethodCreator(methodDescriptor); @@ -525,7 +528,8 @@ public String apply(List keys) { notConstructed.invokeVirtualMethod(forwardDescriptor, notConstructed.getThis(), params)); } - DecoratorInfo firstDecorator = decoration.decorators.get(0); + DecoratorMethod decoratorMethod = decoration.firstDecoratorMethod(); + DecoratorInfo firstDecorator = decoratorMethod.decorator; ResultHandle decoratorInstance = decoratedMethod.readInstanceField(FieldDescriptor.of(subclass.getClassName(), firstDecorator.getIdentifier(), Object.class.getName()), decoratedMethod.getThis()); @@ -535,9 +539,11 @@ public String apply(List keys) { String targetPackage = DotNames.packageName(firstDecorator.getProviderType().name()); declaringClass = generatedNameFromTarget(targetPackage, baseName, DecoratorGenerator.ABSTRACT_IMPL_SUFFIX); } + // We need to use the decorator method in order to support generic decorators + MethodDescriptor decoratorMethodDescriptor = MethodDescriptor.of(decoratorMethod.method); MethodDescriptor virtualMethodDescriptor = MethodDescriptor.ofMethod( declaringClass, methodDescriptor.getName(), - methodDescriptor.getReturnType(), methodDescriptor.getParameterTypes()); + decoratorMethodDescriptor.getReturnType(), decoratorMethodDescriptor.getParameterTypes()); decoratedMethod .returnValue(decoratedMethod.invokeVirtualMethod(virtualMethodDescriptor, decoratorInstance, params)); } @@ -617,9 +623,12 @@ private void processDecorator(DecoratorInfo decorator, BeanInfo bean, Type provi } ClassCreator delegateSubclass = delegateSubclassBuilder.build(); - Map nextDecorators = bean.getNextDecorators(decorator); - Collection nextDecoratorsValues = nextDecorators.values(); - List decoratorParameters = new ArrayList<>(new HashSet<>(nextDecoratorsValues)); + Map nextDecorators = bean.getNextDecorators(decorator); + Set nextDecoratorsValues = new HashSet<>(); + for (DecoratorMethod decoratorMethod : nextDecorators.values()) { + nextDecoratorsValues.add(decoratorMethod.decorator); + } + List decoratorParameters = new ArrayList<>(nextDecoratorsValues); Collections.sort(decoratorParameters); Set decoratedMethods = bean.getDecoratedMethods(decorator); Set decoratedMethodDescriptors = new HashSet<>(decoratedMethods.size()); @@ -730,36 +739,50 @@ private void processDecorator(DecoratorInfo decorator, BeanInfo bean, Type provi method.name(), DescriptorUtils.typeToString(returnType), paramTypesArray); } - DecoratorInfo nextDecorator = null; - for (Entry entry : nextDecorators.entrySet()) { - if (Methods.descriptorMatches(entry.getKey(), methodDescriptor) || (resolvedMethodDescriptor != null - && Methods.descriptorMatches(entry.getKey(), resolvedMethodDescriptor))) { - nextDecorator = entry.getValue(); + DecoratorMethod nextDecorator = null; + MethodDescriptor nextDecoratorDecorated = null; + for (Entry e : nextDecorators.entrySet()) { + // Find the next decorator for the current delegate type method + if (Methods.descriptorMatches(e.getKey(), methodDescriptor) + || (resolvedMethodDescriptor != null + && Methods.descriptorMatches(e.getKey(), resolvedMethodDescriptor)) + || Methods.descriptorMatches(MethodDescriptor.of(e.getValue().method), methodDescriptor)) { + nextDecorator = e.getValue(); + nextDecoratorDecorated = e.getKey(); break; } } - if (nextDecorator != null && isDecorated(decoratedMethodDescriptors, methodDescriptor, resolvedMethodDescriptor)) { + if (nextDecorator != null + && isDecorated(decoratedMethodDescriptors, methodDescriptor, resolvedMethodDescriptor, + nextDecoratorDecorated)) { // This method is decorated by this decorator and there is a next decorator in the chain // Just delegate to the next decorator - delegateTo = forward.readInstanceField(nextDecoratorToField.get(nextDecorator), forward.getThis()); + delegateTo = forward.readInstanceField(nextDecoratorToField.get(nextDecorator.decorator), forward.getThis()); if (delegateTypeIsInterface) { ret = forward.invokeInterfaceMethod(methodDescriptor, delegateTo, params); } else { MethodDescriptor virtualMethod = MethodDescriptor.ofMethod(providerTypeName, methodDescriptor.getName(), - methodDescriptor.getReturnType(), - methodDescriptor.getParameterTypes()); + nextDecorator.method.returnType(), + nextDecorator.method.parameterTypes()); ret = forward.invokeVirtualMethod(virtualMethod, delegateTo, params); } } else { // This method is not decorated or no next decorator was found in the chain MethodDescriptor forwardingMethod = null; + MethodInfo decoratedMethod = bean.getDecoratedMethod(m.method, decorator); + MethodDescriptor decoratedMethodDescriptor = decoratedMethod != null ? MethodDescriptor.of(decoratedMethod) + : null; for (Entry entry : forwardingMethods.entrySet()) { - // Also try to find the forwarding method for the resolved variant - if (Methods.descriptorMatches(entry.getKey(), methodDescriptor) || (resolvedMethodDescriptor != null - && Methods.descriptorMatches(entry.getKey(), resolvedMethodDescriptor))) { + if (Methods.descriptorMatches(entry.getKey(), methodDescriptor) + // Also try to find the forwarding method for the resolved variant + || (resolvedMethodDescriptor != null + && Methods.descriptorMatches(entry.getKey(), resolvedMethodDescriptor)) + // Finally, try to match the decorated method + || (decoratedMethodDescriptor != null + && Methods.descriptorMatches(entry.getKey(), decoratedMethodDescriptor))) { forwardingMethod = entry.getValue(); break; } @@ -824,19 +847,14 @@ private void processDecorator(DecoratorInfo decorator, BeanInfo bean, Type provi } private boolean isDecorated(Set decoratedMethodDescriptors, MethodDescriptor original, - MethodDescriptor resolved) { + MethodDescriptor resolved, MethodDescriptor nextDecoratorDecorated) { for (MethodDescriptor decorated : decoratedMethodDescriptors) { - if (Methods.descriptorMatches(decorated, original)) { + if (Methods.descriptorMatches(decorated, original) + || (resolved != null && Methods.descriptorMatches(decorated, resolved)) + || Methods.descriptorMatches(decorated, nextDecoratorDecorated)) { return true; } } - if (resolved != null) { - for (MethodDescriptor decorated : decoratedMethodDescriptors) { - if (Methods.descriptorMatches(decorated, resolved)) { - return true; - } - } - } return false; } @@ -858,9 +876,9 @@ private MethodDescriptor createForwardingMethod(ClassCreator subclass, String pr return forwardDescriptor; } - private void createInterceptedMethod(ClassOutput classOutput, BeanInfo bean, MethodInfo method, ClassCreator subclass, + private void createInterceptedMethod(BeanInfo bean, MethodInfo method, ClassCreator subclass, String providerTypeName, FieldDescriptor metadataField, FieldDescriptor constructedField, - MethodDescriptor forwardMethod, DecoratorInfo decorator) { + MethodDescriptor forwardMethod) { MethodDescriptor originalMethodDescriptor = MethodDescriptor.of(method); MethodCreator interceptedMethod = subclass.getMethodCreator(originalMethodDescriptor); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/generics/DecoratorsWithTypeVariablesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/generics/DecoratorsWithTypeVariablesTest.java new file mode 100644 index 0000000000000..e31ef7b890fc0 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/generics/DecoratorsWithTypeVariablesTest.java @@ -0,0 +1,127 @@ +package io.quarkus.arc.test.decorators.generics; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import jakarta.annotation.Priority; +import jakarta.decorator.Decorator; +import jakarta.decorator.Delegate; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class DecoratorsWithTypeVariablesTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(StringFunction.class, + FunctionDecorator.class, StringConsumer.class, ConsumerDecorator.class, StringSupplier.class, + SupplierDecorator.class); + + @Test + public void testFunction() { + StringFunction strFun = Arc.container().instance(StringFunction.class).get(); + assertEquals("FOO", strFun.apply(" foo ")); + } + + @Test + public void testConsumer() { + StringConsumer strConsumer = Arc.container().instance(StringConsumer.class).get(); + strConsumer.accept("baz"); + assertEquals("bar", StringConsumer.CONSUMED.get()); + } + + @Test + public void testSupplier() { + StringSupplier strSupplier = Arc.container().instance(StringSupplier.class).get(); + assertEquals("stringsupplier", strSupplier.get()); + } + + @ApplicationScoped + static class StringSupplier implements Supplier { + + @Override + public String get() { + return StringSupplier.class.getSimpleName(); + } + + } + + @ApplicationScoped + static class StringConsumer implements Consumer { + + static final AtomicReference CONSUMED = new AtomicReference<>(); + + @Override + public void accept(String value) { + CONSUMED.set(value); + } + + } + + @ApplicationScoped + static class StringFunction implements Function { + + public String apply(String val) { + return val.trim(); + } + + } + + @Priority(1) + @Decorator + static class FunctionDecorator implements Function { + + @Inject + @Delegate + Function delegate; + + @SuppressWarnings("unchecked") + @Override + public T apply(T val) { + return (T) delegate.apply(val).toString().toUpperCase(); + } + + } + + @Priority(1) + @Decorator + static class SupplierDecorator implements Supplier { + + @Inject + @Delegate + Supplier delegate; + + @SuppressWarnings("unchecked") + @Override + public T get() { + return (T) delegate.get().toString().toLowerCase(); + } + + } + + @Priority(1) + @Decorator + static class ConsumerDecorator implements Consumer { + + @Inject + @Delegate + Consumer delegate; + + @SuppressWarnings("unchecked") + @Override + public void accept(T val) { + delegate.accept((T) "bar"); + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/generics/MultipleDecoratorsWithTypeVariablesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/generics/MultipleDecoratorsWithTypeVariablesTest.java new file mode 100644 index 0000000000000..36acd2354d31b --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/generics/MultipleDecoratorsWithTypeVariablesTest.java @@ -0,0 +1,173 @@ +package io.quarkus.arc.test.decorators.generics; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import jakarta.annotation.Priority; +import jakarta.decorator.Decorator; +import jakarta.decorator.Delegate; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class MultipleDecoratorsWithTypeVariablesTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(StringFunction.class, + FunctionDecorator1.class, FunctionDecorator2.class, StringConsumer.class, ConsumerDecorator1.class, + ConsumerDecorator2.class, StringSupplier.class, SupplierDecorator1.class, SupplierDecorator2.class); + + @Test + public void testFunction() { + StringFunction strFun = Arc.container().instance(StringFunction.class).get(); + assertEquals("FOOFOO", strFun.apply(" foo ")); + } + + @Test + public void testConsumer() { + StringConsumer strConsumer = Arc.container().instance(StringConsumer.class).get(); + strConsumer.accept("baz"); + assertEquals("barbar", StringConsumer.CONSUMED.get()); + } + + @Test + public void testSupplier() { + StringSupplier strSupplier = Arc.container().instance(StringSupplier.class).get(); + assertEquals("stringsupplier_Foo", strSupplier.get()); + } + + @ApplicationScoped + static class StringSupplier implements Supplier { + + @Override + public String get() { + return StringSupplier.class.getSimpleName(); + } + + } + + @ApplicationScoped + static class StringConsumer implements Consumer { + + static final AtomicReference CONSUMED = new AtomicReference<>(); + + @Override + public void accept(String value) { + CONSUMED.set(value); + } + + } + + @ApplicationScoped + static class StringFunction implements Function { + + public String apply(String val) { + return val.trim(); + } + + } + + @Priority(1) + @Decorator + static class FunctionDecorator1 implements Function { + + @Inject + @Delegate + Function delegate; + + @SuppressWarnings("unchecked") + @Override + public T apply(String val) { + return (T) delegate.apply(val).toString().repeat(2); + } + + } + + @Priority(3) + @Decorator + static class FunctionDecorator2 implements Function { + + @Inject + @Delegate + Function delegate; + + @SuppressWarnings("unchecked") + @Override + public T apply(T val) { + return (T) delegate.apply(val).toString().toUpperCase(); + } + + } + + @Priority(1) + @Decorator + static class SupplierDecorator1 implements Supplier { + + @Inject + @Delegate + Supplier delegate; + + @Override + public String get() { + return delegate.get() + "_Foo"; + } + + } + + @Priority(2) + @Decorator + static class SupplierDecorator2 implements Supplier { + + @Inject + @Delegate + Supplier delegate; + + @SuppressWarnings("unchecked") + @Override + public T get() { + return (T) delegate.get().toString().toLowerCase(); + } + + } + + @Priority(1) + @Decorator + static class ConsumerDecorator1 implements Consumer { + + @Inject + @Delegate + Consumer delegate; + + @SuppressWarnings("unchecked") + @Override + public void accept(T val) { + delegate.accept((T) "bar"); + } + + } + + @Priority(2) + @Decorator + static class ConsumerDecorator2 implements Consumer { + + @Inject + @Delegate + Consumer delegate; + + @Override + public void accept(String val) { + delegate.accept(val + val); + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/generics/SlightlyMoreComplexDecoratorWithTypeVariableTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/generics/SlightlyMoreComplexDecoratorWithTypeVariableTest.java new file mode 100644 index 0000000000000..3c5c228309dfd --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/generics/SlightlyMoreComplexDecoratorWithTypeVariableTest.java @@ -0,0 +1,94 @@ +package io.quarkus.arc.test.decorators.generics; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import jakarta.annotation.Priority; +import jakarta.decorator.Decorator; +import jakarta.decorator.Delegate; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class SlightlyMoreComplexDecoratorWithTypeVariableTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Fun.class, + FunctionDecorator1.class, FunctionDecorator2.class, FunctionDecorator3.class); + + @Test + public void testFunction() { + Fun strFun = Arc.container().instance(Fun.class).get(); + assertEquals(List.of("1", "foo", "bar", "baz"), strFun.apply(1)); + } + + @ApplicationScoped + static class Fun implements Function> { + + public List apply(Integer val) { + return List.of(val.toString()); + } + + } + + @Priority(1) + @Decorator + static class FunctionDecorator1 implements Function> { + + @Inject + @Delegate + Function> delegate; + + @Override + public List apply(T val) { + List list = new ArrayList<>((List) delegate.apply(val)); + list.add("baz"); + return list; + } + + } + + @Priority(2) + @Decorator + static class FunctionDecorator2 implements Function { + + @Inject + @Delegate + Function delegate; + + @SuppressWarnings("unchecked") + @Override + public L apply(T val) { + List list = new ArrayList<>((List) delegate.apply(val)); + list.add("bar"); + return (L) list; + } + + } + + @Priority(3) + @Decorator + static class FunctionDecorator3 implements Function> { + + @Inject + @Delegate + Function> delegate; + + @Override + public List apply(Integer val) { + // this should call Fun#apply() + List list = delegate.apply(val); + return List.of(list.get(0), "foo"); + } + + } + +}