diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index 29682a90070bdb..fa37f4e681a494 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -74,6 +74,7 @@ public class BeanDeployment { private final List beans; private final List interceptors; + private final List decorators; private final List observers; @@ -178,6 +179,7 @@ public class BeanDeployment { this.injectionPoints = new CopyOnWriteArrayList<>(); this.interceptors = new CopyOnWriteArrayList<>(); + this.decorators = new CopyOnWriteArrayList<>(); this.beans = new CopyOnWriteArrayList<>(); this.observers = new CopyOnWriteArrayList<>(); @@ -233,6 +235,7 @@ BeanRegistrar.RegistrationContext registerBeans(List beanRegistra buildContextPut(Key.OBSERVERS.asString(), Collections.unmodifiableList(observers)); this.interceptors.addAll(findInterceptors(injectionPoints)); + this.decorators.addAll(findDecorators(injectionPoints)); this.injectionPoints.addAll(injectionPoints); buildContextPut(Key.INJECTION_POINTS.asString(), Collections.unmodifiableList(this.injectionPoints)); @@ -254,6 +257,10 @@ void init(Consumer bytecodeTransformerConsumer, for (InterceptorInfo interceptor : interceptors) { interceptor.init(errors, bytecodeTransformerConsumer, transformUnproxyableClasses); } + for (DecoratorInfo decorator : decorators) { + decorator.init(errors, bytecodeTransformerConsumer, transformUnproxyableClasses); + } + processErrors(errors); List> allUnusedExclusions = new ArrayList<>(additionalUnusedBeanExclusions); if (unusedExclusions != null) { @@ -391,6 +398,10 @@ public Collection getInterceptors() { return Collections.unmodifiableList(interceptors); } + public Collection getDecorators() { + return Collections.unmodifiableList(decorators); + } + public Collection getStereotypes() { return Collections.unmodifiableCollection(stereotypes.values()); } @@ -1128,6 +1139,33 @@ private List findInterceptors(List injectio return interceptors; } + private List findDecorators(List injectionPoints) { + Map decoratorClasses = new HashMap<>(); + for (AnnotationInstance annotation : beanArchiveIndex.getAnnotations(DotNames.DECORATOR)) { + if (Kind.CLASS.equals(annotation.target().kind())) { + decoratorClasses.put(annotation.target().asClass().name(), annotation.target().asClass()); + } + } + List decorators = new ArrayList<>(); + for (ClassInfo decoratorClass : decoratorClasses.values()) { + if (annotationStore.hasAnnotation(decoratorClass, DotNames.VETOED) || isExcluded(decoratorClass)) { + // Skip vetoed decorators + continue; + } + decorators + .add(Decorators.createDecorator(decoratorClass, this, injectionPointTransformer, annotationStore)); + } + if (LOGGER.isTraceEnabled()) { + for (DecoratorInfo decorator : decorators) { + LOGGER.logf(Level.TRACE, "Created %s", decorator); + } + } + for (DecoratorInfo decorator : decorators) { + injectionPoints.addAll(decorator.getAllInjectionPoints()); + } + return decorators; + } + private void validateBeans(List errors, List validators, Consumer bytecodeTransformerConsumer) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index ec25103b0086f7..e6ff29894500bc 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -8,9 +8,12 @@ import static org.objectweb.asm.Opcodes.ACC_VOLATILE; import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.InjectableDecorator; import io.quarkus.arc.InjectableInterceptor; +import io.quarkus.arc.InjectableReferenceProvider; import io.quarkus.arc.impl.CreationalContextImpl; import io.quarkus.arc.impl.CurrentInjectionPointProvider; +import io.quarkus.arc.impl.DecoratorDelegateProvider; import io.quarkus.arc.impl.InitializedInterceptor; import io.quarkus.arc.processor.BeanInfo.InterceptionInfo; import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; @@ -171,8 +174,8 @@ Collection generateSyntheticBean(BeanInfo bean) { stereotypes = beanCreator.getFieldCreator(FIELD_NAME_STEREOTYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); } - MethodCreator constructor = initConstructor(classOutput, beanCreator, bean, baseName, Collections.emptyMap(), - Collections.emptyMap(), + MethodCreator constructor = initConstructor(classOutput, beanCreator, bean, Collections.emptyMap(), + Collections.emptyMap(), Collections.emptyMap(), annotationLiterals, reflectionRegistration); FieldCreator params = beanCreator.getFieldCreator(FIELD_NAME_PARAMS, Map.class) @@ -217,7 +220,7 @@ Collection generateSyntheticBean(BeanInfo bean) { isApplicationClass, baseName); } implementCreate(classOutput, beanCreator, bean, providerType, baseName, - Collections.emptyMap(), + Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), reflectionRegistration, targetPackage, isApplicationClass); implementGet(bean, beanCreator, providerType, baseName); @@ -290,11 +293,14 @@ Collection generateClassBean(BeanInfo bean, ClassInfo beanClass) { Map injectionPointToProviderSupplierField = new HashMap<>(); Map interceptorToProviderSupplierField = new HashMap<>(); - initMaps(bean, injectionPointToProviderSupplierField, interceptorToProviderSupplierField); - - createProviderFields(beanCreator, bean, injectionPointToProviderSupplierField, interceptorToProviderSupplierField); - createConstructor(classOutput, beanCreator, bean, baseName, injectionPointToProviderSupplierField, - interceptorToProviderSupplierField, + Map decoratorToProviderSupplierField = new HashMap<>(); + initMaps(bean, injectionPointToProviderSupplierField, interceptorToProviderSupplierField, + decoratorToProviderSupplierField); + + createProviderFields(beanCreator, bean, injectionPointToProviderSupplierField, interceptorToProviderSupplierField, + decoratorToProviderSupplierField); + createConstructor(classOutput, beanCreator, bean, injectionPointToProviderSupplierField, + interceptorToProviderSupplierField, decoratorToProviderSupplierField, annotationLiterals, reflectionRegistration); implementGetIdentifier(bean, beanCreator); @@ -307,6 +313,7 @@ Collection generateClassBean(BeanInfo bean, ClassInfo beanClass) { implementCreate(classOutput, beanCreator, bean, providerType, baseName, injectionPointToProviderSupplierField, interceptorToProviderSupplierField, + decoratorToProviderSupplierField, reflectionRegistration, targetPackage, isApplicationClass); implementGet(bean, beanCreator, providerType, baseName); @@ -392,10 +399,11 @@ Collection generateProducerMethodBean(BeanInfo bean, MethodInfo produc Map injectionPointToProviderField = new HashMap<>(); // Producer methods are not intercepted - initMaps(bean, injectionPointToProviderField, null); + initMaps(bean, injectionPointToProviderField, null, null); - createProviderFields(beanCreator, bean, injectionPointToProviderField, Collections.emptyMap()); - createConstructor(classOutput, beanCreator, bean, baseName, injectionPointToProviderField, Collections.emptyMap(), + createProviderFields(beanCreator, bean, injectionPointToProviderField, Collections.emptyMap(), Collections.emptyMap()); + createConstructor(classOutput, beanCreator, bean, injectionPointToProviderField, Collections.emptyMap(), + Collections.emptyMap(), annotationLiterals, reflectionRegistration); implementGetIdentifier(bean, beanCreator); @@ -406,7 +414,7 @@ Collection generateProducerMethodBean(BeanInfo bean, MethodInfo produc } implementCreate(classOutput, beanCreator, bean, providerType, baseName, injectionPointToProviderField, - Collections.emptyMap(), + Collections.emptyMap(), Collections.emptyMap(), reflectionRegistration, targetPackage, isApplicationClass); implementGet(bean, beanCreator, providerType, baseName); @@ -481,8 +489,9 @@ Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producer stereotypes = beanCreator.getFieldCreator(FIELD_NAME_STEREOTYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); } - createProviderFields(beanCreator, bean, Collections.emptyMap(), Collections.emptyMap()); - createConstructor(classOutput, beanCreator, bean, baseName, Collections.emptyMap(), Collections.emptyMap(), + createProviderFields(beanCreator, bean, Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap()); + createConstructor(classOutput, beanCreator, bean, Collections.emptyMap(), Collections.emptyMap(), + Collections.emptyMap(), annotationLiterals, reflectionRegistration); implementGetIdentifier(bean, beanCreator); @@ -492,7 +501,7 @@ Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producer baseName); } implementCreate(classOutput, beanCreator, bean, providerType, baseName, - Collections.emptyMap(), + Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), reflectionRegistration, targetPackage, isApplicationClass); implementGet(bean, beanCreator, providerType, baseName); @@ -525,7 +534,7 @@ Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producer } protected void initMaps(BeanInfo bean, Map injectionPointToProvider, - Map interceptorToProvider) { + Map interceptorToProvider, Map decoratorToProvider) { int providerIdx = 1; for (InjectionPointInfo injectionPoint : bean.getAllInjectionPoints()) { injectionPointToProvider.put(injectionPoint, "injectProviderSupplier" + providerIdx++); @@ -538,11 +547,15 @@ protected void initMaps(BeanInfo bean, Map injection for (InterceptorInfo interceptor : bean.getBoundInterceptors()) { interceptorToProvider.put(interceptor, "interceptorProviderSupplier" + providerIdx++); } + for (DecoratorInfo decorator : bean.getBoundDecorators()) { + decoratorToProvider.put(decorator, "decoratorProviderSupplier" + providerIdx++); + } } protected void createProviderFields(ClassCreator beanCreator, BeanInfo bean, Map injectionPointToProviderSupplier, - Map interceptorToProviderSupplier) { + Map interceptorToProviderSupplier, + Map decoratorToProviderSupplier) { // Declaring bean provider if (bean.isProducerMethod() || bean.isProducerField()) { beanCreator.getFieldCreator(FIELD_NAME_DECLARING_PROVIDER_SUPPLIER, Supplier.class) @@ -556,21 +569,28 @@ protected void createProviderFields(ClassCreator beanCreator, BeanInfo bean, for (String interceptorProvider : interceptorToProviderSupplier.values()) { beanCreator.getFieldCreator(interceptorProvider, Supplier.class).setModifiers(ACC_PRIVATE | ACC_FINAL); } + // Decorators + for (String decoratorProvider : decoratorToProviderSupplier.values()) { + beanCreator.getFieldCreator(decoratorProvider, Supplier.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + } } - protected void createConstructor(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, String baseName, + protected void createConstructor(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, Map injectionPointToProviderField, Map interceptorToProviderField, + Map decoratorToProviderSupplierField, AnnotationLiteralProcessor annotationLiterals, ReflectionRegistration reflectionRegistration) { - initConstructor(classOutput, beanCreator, bean, baseName, injectionPointToProviderField, interceptorToProviderField, + initConstructor(classOutput, beanCreator, bean, injectionPointToProviderField, interceptorToProviderField, + decoratorToProviderSupplierField, annotationLiterals, reflectionRegistration) .returnValue(null); } - protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, String baseName, + protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, Map injectionPointToProviderField, Map interceptorToProviderField, + Map decoratorToProviderSupplierField, AnnotationLiteralProcessor annotationLiterals, ReflectionRegistration reflectionRegistration) { @@ -580,7 +600,7 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be parameterTypes.add(Supplier.class.getName()); } for (InjectionPointInfo injectionPoint : bean.getAllInjectionPoints()) { - if (BuiltinBean.resolve(injectionPoint) == null) { + if (!injectionPoint.isDelegate() && BuiltinBean.resolve(injectionPoint) == null) { parameterTypes.add(Supplier.class.getName()); } } @@ -594,6 +614,9 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be for (int i = 0; i < interceptorToProviderField.size(); i++) { parameterTypes.add(Supplier.class.getName()); } + for (int i = 0; i < decoratorToProviderSupplierField.size(); i++) { + parameterTypes.add(Supplier.class.getName()); + } MethodCreator constructor = beanCreator.getMethodCreator(Methods.INIT, "V", parameterTypes.toArray(new String[0])); // Invoke super() @@ -621,36 +644,49 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be } for (InjectionPointInfo injectionPoint : allInjectionPoints) { // Injection points - BuiltinBean builtinBean = null; - if (injectionPoint.getResolvedBean() == null) { - builtinBean = BuiltinBean.resolve(injectionPoint); - } - if (builtinBean != null) { - builtinBean.getGenerator() - .generate(new GeneratorContext(classOutput, bean.getDeployment(), injectionPoint, beanCreator, - constructor, injectionPointToProviderField.get(injectionPoint), annotationLiterals, bean, - reflectionRegistration, injectionPointAnnotationsPredicate)); + if (injectionPoint.isDelegate()) { + // this.delegateProvider = () -> new DecoratorDelegateProvider(); + ResultHandle delegateProvider = constructor.newInstance( + MethodDescriptor.ofConstructor(DecoratorDelegateProvider.class)); + ResultHandle delegateProviderSupplier = constructor.newInstance( + MethodDescriptors.FIXED_VALUE_SUPPLIER_CONSTRUCTOR, delegateProvider); + constructor.writeInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), injectionPointToProviderField.get(injectionPoint), + Supplier.class.getName()), + constructor.getThis(), + delegateProviderSupplier); } else { - - if (BuiltinScope.DEPENDENT.is(injectionPoint.getResolvedBean().getScope()) && (injectionPoint.getResolvedBean() - .getAllInjectionPoints().stream() - .anyMatch(ip -> BuiltinBean.INJECTION_POINT.hasRawTypeDotName(ip.getRequiredType().name())) - || injectionPoint.getResolvedBean().isSynthetic())) { - // Injection point resolves to a dependent bean that injects InjectionPoint metadata and so we need to wrap the injectable - // reference provider - ResultHandle wrapHandle = wrapCurrentInjectionPoint(classOutput, beanCreator, bean, constructor, - injectionPoint, paramIdx++, tccl, reflectionRegistration); - ResultHandle wrapSupplierHandle = constructor.newInstance( - MethodDescriptors.FIXED_VALUE_SUPPLIER_CONSTRUCTOR, wrapHandle); - constructor.writeInstanceField( - FieldDescriptor.of(beanCreator.getClassName(), injectionPointToProviderField.get(injectionPoint), - Supplier.class.getName()), - constructor.getThis(), wrapSupplierHandle); + if (injectionPoint.getResolvedBean() == null) { + BuiltinBean builtinBean = BuiltinBean.resolve(injectionPoint); + builtinBean.getGenerator() + .generate(new GeneratorContext(classOutput, bean.getDeployment(), injectionPoint, beanCreator, + constructor, injectionPointToProviderField.get(injectionPoint), annotationLiterals, bean, + reflectionRegistration, injectionPointAnnotationsPredicate)); } else { - constructor.writeInstanceField( - FieldDescriptor.of(beanCreator.getClassName(), injectionPointToProviderField.get(injectionPoint), - Supplier.class.getName()), - constructor.getThis(), constructor.getMethodParam(paramIdx++)); + // Not a built-in bean + if (BuiltinScope.DEPENDENT.is(injectionPoint.getResolvedBean().getScope()) + && (injectionPoint.getResolvedBean() + .getAllInjectionPoints().stream() + .anyMatch(ip -> BuiltinBean.INJECTION_POINT.hasRawTypeDotName(ip.getRequiredType().name())) + || injectionPoint.getResolvedBean().isSynthetic())) { + // Injection point resolves to a dependent bean that injects InjectionPoint metadata and so we need to wrap the injectable + // reference provider + ResultHandle wrapHandle = wrapCurrentInjectionPoint(classOutput, beanCreator, bean, constructor, + injectionPoint, paramIdx++, tccl, reflectionRegistration); + ResultHandle wrapSupplierHandle = constructor.newInstance( + MethodDescriptors.FIXED_VALUE_SUPPLIER_CONSTRUCTOR, wrapHandle); + constructor.writeInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), + injectionPointToProviderField.get(injectionPoint), + Supplier.class.getName()), + constructor.getThis(), wrapSupplierHandle); + } else { + constructor.writeInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), + injectionPointToProviderField.get(injectionPoint), + Supplier.class.getName()), + constructor.getThis(), constructor.getMethodParam(paramIdx++)); + } } } } @@ -660,6 +696,12 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be Supplier.class.getName()), constructor.getThis(), constructor.getMethodParam(paramIdx++)); } + for (DecoratorInfo decorator : bean.getBoundDecorators()) { + constructor.writeInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), decoratorToProviderSupplierField.get(decorator), + Supplier.class.getName()), + constructor.getThis(), constructor.getMethodParam(paramIdx++)); + } // Bean types ResultHandle typesHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); @@ -858,6 +900,7 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator String baseName, Map injectionPointToProviderSupplierField, Map interceptorToProviderSupplierField, + Map decoratorToProviderSupplierField, ReflectionRegistration reflectionRegistration, String targetPackage, boolean isApplicationClass) { MethodCreator create = beanCreator.getMethodCreator("create", providerType.descriptorName(), CreationalContext.class) @@ -865,15 +908,16 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator if (bean.isClassBean()) { implementCreateForClassBean(classOutput, beanCreator, bean, providerType, baseName, - injectionPointToProviderSupplierField, interceptorToProviderSupplierField, reflectionRegistration, + injectionPointToProviderSupplierField, interceptorToProviderSupplierField, decoratorToProviderSupplierField, + reflectionRegistration, targetPackage, isApplicationClass, create); } else if (bean.isProducerMethod()) { implementCreateForProducerMethod(classOutput, beanCreator, bean, providerType, baseName, - injectionPointToProviderSupplierField, interceptorToProviderSupplierField, reflectionRegistration, + injectionPointToProviderSupplierField, reflectionRegistration, targetPackage, isApplicationClass, create); } else if (bean.isProducerField()) { implementCreateForProducerField(classOutput, beanCreator, bean, providerType, baseName, - injectionPointToProviderSupplierField, interceptorToProviderSupplierField, reflectionRegistration, + injectionPointToProviderSupplierField, reflectionRegistration, targetPackage, isApplicationClass, create); } else if (bean.isSynthetic()) { bean.getCreatorConsumer().accept(create); @@ -889,6 +933,7 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator private List newProviderHandles(BeanInfo bean, ClassCreator beanCreator, MethodCreator createMethod, Map injectionPointToProviderField, Map interceptorToProviderField, + Map decoratorToProviderSupplierField, Map interceptorToWrap, List transientReferences) { @@ -928,6 +973,15 @@ private List newProviderHandles(BeanInfo bean, ClassCreator beanCr providerHandles.add(interceptorProviderHandle); } } + for (DecoratorInfo decorator : bean.getBoundDecorators()) { + ResultHandle decoratorProviderSupplierHandle = createMethod.readInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), decoratorToProviderSupplierField.get(decorator), + Supplier.class), + createMethod.getThis()); + ResultHandle decoratorProviderHandle = createMethod.invokeInterfaceMethod( + MethodDescriptors.SUPPLIER_GET, decoratorProviderSupplierHandle); + providerHandles.add(decoratorProviderHandle); + } } return providerHandles; } @@ -946,6 +1000,7 @@ private ResultHandle newInstanceHandle(BeanInfo bean, ClassCreator beanCreator, // new SimpleBean_Subclass(foo,ctx,lifecycleInterceptorProvider1) List interceptors = bean.getBoundInterceptors(); + List decorators = bean.getBoundDecorators(); List paramTypes = new ArrayList<>(); List paramHandles = new ArrayList<>(); @@ -954,7 +1009,6 @@ private ResultHandle newInstanceHandle(BeanInfo bean, ClassCreator beanCreator, paramTypes.add(injectionPoints.get(i).getRequiredType().name().toString()); paramHandles.add(providerHandles.get(i)); } - // 2. ctx paramHandles.add(createMethod.getMethodParam(0)); paramTypes.add(CreationalContext.class.getName()); @@ -964,6 +1018,11 @@ private ResultHandle newInstanceHandle(BeanInfo bean, ClassCreator beanCreator, paramTypes.add(InjectableInterceptor.class.getName()); paramHandles.add(providerHandles.get(injectionPoints.size() + i)); } + // 4. decorators + for (int i = 0; i < decorators.size(); i++) { + paramTypes.add(InjectableDecorator.class.getName()); + paramHandles.add(providerHandles.get(injectionPoints.size() + interceptors.size() + i)); + } return creator.newInstance( MethodDescriptor.ofConstructor(SubclassGenerator.generatedName(bean.getProviderType().name(), baseName), @@ -1016,7 +1075,7 @@ private ResultHandle newInstanceHandle(BeanInfo bean, ClassCreator beanCreator, void implementCreateForProducerField(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, ProviderType providerType, String baseName, Map injectionPointToProviderSupplierField, - Map interceptorToProviderSupplierField, ReflectionRegistration reflectionRegistration, + ReflectionRegistration reflectionRegistration, String targetPackage, boolean isApplicationClass, MethodCreator create) { AssignableResultHandle instanceHandle = create.createVariable(DescriptorUtils.extToInt(providerType.className())); @@ -1082,7 +1141,7 @@ void implementCreateForProducerField(ClassOutput classOutput, ClassCreator beanC void implementCreateForProducerMethod(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, ProviderType providerType, String baseName, Map injectionPointToProviderSupplierField, - Map interceptorToProviderSupplierField, ReflectionRegistration reflectionRegistration, + ReflectionRegistration reflectionRegistration, String targetPackage, boolean isApplicationClass, MethodCreator create) { AssignableResultHandle instanceHandle; @@ -1185,6 +1244,7 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat ProviderType providerType, String baseName, Map injectionPointToProviderSupplierField, Map interceptorToProviderSupplierField, + Map decoratorToProviderSupplierField, ReflectionRegistration reflectionRegistration, String targetPackage, boolean isApplicationClass, MethodCreator create) { @@ -1314,7 +1374,7 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat List transientReferences = new ArrayList<>(); List providerHandles = newProviderHandles(bean, beanCreator, create, - injectionPointToProviderSupplierField, interceptorToProviderSupplierField, + injectionPointToProviderSupplierField, interceptorToProviderSupplierField, decoratorToProviderSupplierField, interceptorToWrap, transientReferences); // Forwarding function @@ -1359,7 +1419,7 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat create.assign(instanceHandle, newInstanceHandle(bean, beanCreator, create, create, providerType.className(), baseName, newProviderHandles(bean, beanCreator, create, injectionPointToProviderSupplierField, - interceptorToProviderSupplierField, + interceptorToProviderSupplierField, decoratorToProviderSupplierField, interceptorToWrap, transientReferences), reflectionRegistration, isApplicationClass)); // Destroy injected transient references 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 e4db190374e42c..960b7ae59ccd74 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 @@ -3,12 +3,16 @@ import static io.quarkus.arc.processor.IndexClassLookupUtils.getClassByName; import io.quarkus.arc.processor.Methods.MethodKey; +import io.quarkus.arc.processor.Methods.SubclassSkipPredicate; import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -18,6 +22,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.stream.Collectors; +import javax.enterprise.inject.spi.DefinitionException; import javax.enterprise.inject.spi.DeploymentException; import javax.enterprise.inject.spi.InterceptionType; import org.jboss.jandex.AnnotationInstance; @@ -39,15 +44,15 @@ public class BeanInfo implements InjectionTargetInfo { private final Type providerType; - private final Optional target; + protected final Optional target; private final BeanDeployment beanDeployment; - private final ScopeInfo scope; + protected final ScopeInfo scope; - private final Set types; + protected final Set types; - private final Set qualifiers; + protected final Set qualifiers; private final List injections; @@ -56,6 +61,7 @@ public class BeanInfo implements InjectionTargetInfo { private final DisposerInfo disposer; private final Map interceptedMethods; + private final Map decoratedMethods; private final Map lifecycleInterceptors; @@ -132,6 +138,7 @@ public class BeanInfo implements InjectionTargetInfo { // Identifier must be unique for a specific deployment this.identifier = Hashes.sha1(toString()); this.interceptedMethods = new ConcurrentHashMap<>(); + this.decoratedMethods = new ConcurrentHashMap<>(); this.lifecycleInterceptors = new ConcurrentHashMap<>(); } @@ -196,6 +203,10 @@ public boolean isInterceptor() { return false; } + public boolean isDecorator() { + return false; + } + public BeanInfo getDeclaringBean() { return declaringBean; } @@ -257,6 +268,44 @@ Map getInterceptedMethods() { return interceptedMethods; } + Map getDecoratedMethods() { + return decoratedMethods; + } + + List getInterceptedOrDecoratedMethods() { + Set methods = new HashSet<>(interceptedMethods.keySet()); + methods.addAll(decoratedMethods.keySet()); + List sorted = new ArrayList<>(methods); + Collections.sort(sorted, Comparator.comparing(MethodInfo::toString)); + return sorted; + } + + Set getDecoratedMethods(DecoratorInfo decorator) { + Set decorated = new HashSet<>(); + for (Entry entry : decoratedMethods.entrySet()) { + if (entry.getValue().decorators.contains(decorator)) { + decorated.add(entry.getKey()); + } + } + return decorated; + } + + // Returns a map of method descriptor -> next decorator in the chain + // e.g. foo() -> BravoDecorator + Map getNextDecorators(DecoratorInfo decorator) { + Map next = new HashMap<>(); + for (Entry entry : decoratedMethods.entrySet()) { + List decorators = entry.getValue().decorators; + int index = decorators.indexOf(decorator); + if (index != -1) { + if (index != (decorators.size() - 1)) { + next.put(MethodDescriptor.of(entry.getKey()), decorators.get(index + 1)); + } + } + } + return next; + } + InterceptionInfo getLifecycleInterceptors(InterceptionType interceptionType) { return lifecycleInterceptors.containsKey(interceptionType) ? lifecycleInterceptors.get(interceptionType) : InterceptionInfo.EMPTY; @@ -271,7 +320,8 @@ public boolean hasAroundInvokeInterceptors() { } boolean isSubclassRequired() { - return !interceptedMethods.isEmpty() || lifecycleInterceptors.containsKey(InterceptionType.PRE_DESTROY); + return !interceptedMethods.isEmpty() || !decoratedMethods.isEmpty() + || lifecycleInterceptors.containsKey(InterceptionType.PRE_DESTROY); } boolean hasDefaultDestroy() { @@ -314,6 +364,27 @@ List getBoundInterceptors() { return bound; } + List getBoundDecorators() { + if (decoratedMethods.isEmpty()) { + return Collections.emptyList(); + } + List bound = new ArrayList<>(); + for (DecorationInfo decoration : decoratedMethods.values()) { + for (DecoratorInfo decorator : decoration.decorators) { + if (!bound.contains(decorator)) { + bound.add(decorator); + } + } + } + // Sort by priority (highest goes first) and by bean class + // Highest priority first because the decorators are instantiated in the reverse order, + // i.e. when the subclass constructor is generated the delegate subclass of the first decorator + // (lower priority) needs a reference to the next decorator in the chain (higher priority) + Collections.sort(bound, + Comparator.comparing(DecoratorInfo::getPriority).reversed().thenComparing(DecoratorInfo::getBeanClass)); + return bound; + } + public DisposerInfo getDisposer() { return disposer; } @@ -375,6 +446,10 @@ void init(List errors, Consumer bytecodeTransfor boolean transformUnproxyableClasses) { for (Injection injection : injections) { for (InjectionPointInfo injectionPoint : injection.injectionPoints) { + if (injectionPoint.isDelegate() && !isDecorator()) { + errors.add(new DeploymentException(String.format( + "Only decorators can declare a delegate injection point: %s", this))); + } Beans.resolveInjectionPoint(beanDeployment, this, injectionPoint, errors); } } @@ -382,6 +457,7 @@ void init(List errors, Consumer bytecodeTransfor disposer.init(errors); } interceptedMethods.putAll(initInterceptedMethods(errors, bytecodeTransformerConsumer, transformUnproxyableClasses)); + decoratedMethods.putAll(initDecoratedMethods()); if (errors.isEmpty()) { lifecycleInterceptors.putAll(initLifecycleInterceptors()); } @@ -401,7 +477,7 @@ protected String getType() { private Map initInterceptedMethods(List errors, Consumer bytecodeTransformerConsumer, boolean transformUnproxyableClasses) { - if (!isInterceptor() && isClassBean()) { + if (!isInterceptor() && !isDecorator() && isClassBean()) { Map interceptedMethods = new HashMap<>(); Map> candidates = new HashMap<>(); @@ -435,6 +511,91 @@ private Map initInterceptedMethods(List } } + private Map initDecoratedMethods() { + Collection decorators = beanDeployment.getDecorators(); + if (decorators.isEmpty() || isInterceptor() || isDecorator() || !isClassBean()) { + return Collections.emptyMap(); + } + // A decorator is bound to a bean if the bean is assignable to the delegate injection point + List bound = new LinkedList<>(); + for (DecoratorInfo decorator : decorators) { + if (Beans.matches(this, decorator.getDelegateInjectionPoint().getTypeAndQualifiers())) { + bound.add(decorator); + } + } + // Decorators with the smaller priority values are called first + Collections.sort(bound, Comparator.comparingInt(DecoratorInfo::getPriority).thenComparing(DecoratorInfo::getBeanClass)); + + Map candidates = new HashMap<>(); + addDecoratedMethods(candidates, target.get().asClass(), bound, + new SubclassSkipPredicate(beanDeployment.getAssignabilityCheck()::isAssignableFrom)); + + Map decoratedMethods = new HashMap<>(candidates.size()); + for (Entry entry : candidates.entrySet()) { + decoratedMethods.put(entry.getKey().method, entry.getValue()); + } + return decoratedMethods; + } + + private void addDecoratedMethods(Map decoratedMethods, ClassInfo classInfo, + List boundDecorators, SubclassSkipPredicate skipPredicate) { + skipPredicate.startProcessing(classInfo); + for (MethodInfo method : classInfo.methods()) { + if (skipPredicate.test(method)) { + continue; + } + List matching = findMatchingDecorators(method, boundDecorators); + if (!matching.isEmpty()) { + decoratedMethods.computeIfAbsent(new MethodKey(method), key -> new DecorationInfo(matching)); + } + } + skipPredicate.methodsProcessed(); + if (!classInfo.superName().equals(DotNames.OBJECT)) { + ClassInfo superClassInfo = getClassByName(beanDeployment.getBeanArchiveIndex(), classInfo.superName()); + if (superClassInfo != null) { + addDecoratedMethods(decoratedMethods, superClassInfo, boundDecorators, skipPredicate); + } + } + } + + List findMatchingDecorators(MethodInfo method, List decorators) { + List methodParams = method.parameters(); + 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) { + throw new DefinitionException( + "The class of the decorated type " + decoratedType + " was not found in the index"); + } + for (MethodInfo decoratedMethod : decoratedTypeClass.methods()) { + if (!method.name().equals(decoratedMethod.name())) { + continue; + } + List decoratedMethodParams = decoratedMethod.parameters(); + if (methodParams.size() != decoratedMethodParams.size()) { + continue; + } + // Match the resolved parameter types + boolean matches = true; + decoratedMethodParams = Types.getResolvedParameters(decoratedTypeClass, method, + beanDeployment.getBeanArchiveIndex()); + for (int i = 0; i < methodParams.size(); i++) { + if (!methodParams.get(i).equals(decoratedMethodParams.get(i))) { + matches = false; + } + } + if (matches) { + matching.add(decorator); + } + } + } + } + return matching; + } + private Map initLifecycleInterceptors() { if (!isInterceptor() && isClassBean()) { Map lifecycleInterceptors = new HashMap<>(); @@ -579,6 +740,20 @@ boolean isEmpty() { } + static class DecorationInfo { + + final List decorators; + + public DecorationInfo(List decorators) { + this.decorators = decorators; + } + + boolean isEmpty() { + return decorators.isEmpty(); + } + + } + static class Builder { private ClassInfo implClazz; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java index 30d4a758032629..3cba9e982cd120 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java @@ -160,6 +160,9 @@ public List generateResources(ReflectionRegistration reflectionRegistr InterceptorGenerator interceptorGenerator = new InterceptorGenerator(annotationLiterals, applicationClassPredicate, privateMembers, generateSources, reflectionRegistration, existingClasses, beanToGeneratedName, injectionPointAnnotationsPredicate); + DecoratorGenerator decoratorGenerator = new DecoratorGenerator(annotationLiterals, applicationClassPredicate, + privateMembers, generateSources, reflectionRegistration, existingClasses, beanToGeneratedName, + injectionPointAnnotationsPredicate); SubclassGenerator subclassGenerator = new SubclassGenerator(annotationLiterals, applicationClassPredicate, generateSources, reflectionRegistration, existingClasses); ObserverGenerator observerGenerator = new ObserverGenerator(annotationLiterals, applicationClassPredicate, @@ -175,7 +178,12 @@ public List generateResources(ReflectionRegistration reflectionRegistr resources.add(resource); } } - + // Generate decorators + for (DecoratorInfo decorator : beanDeployment.getDecorators()) { + for (Resource resource : decoratorGenerator.generate(decorator)) { + resources.add(resource); + } + } // Generate beans for (BeanInfo bean : beanDeployment.getBeans()) { for (Resource resource : beanGenerator.generate(bean)) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java index 223b4fc93206d9..1db503483ffcf6 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java @@ -448,6 +448,10 @@ static boolean matchesType(BeanInfo bean, Type requiredType) { static void resolveInjectionPoint(BeanDeployment deployment, InjectionTargetInfo target, InjectionPointInfo injectionPoint, List errors) { + if (injectionPoint.isDelegate()) { + // Skip delegate injection points + return; + } BuiltinBean builtinBean = BuiltinBean.resolve(injectionPoint); if (builtinBean != null) { if (BuiltinBean.INJECTION_POINT.equals(builtinBean) diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java index 21f054215555b8..883f2d9f910ecd 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java @@ -211,6 +211,11 @@ private void processBeans(ClassCreator componentsProvider, MethodCreator getComp beanAdder.addComponent(interceptor); } } + for (BeanInfo decorator : beanDeployment.getDecorators()) { + if (!processed.contains(decorator)) { + beanAdder.addComponent(decorator); + } + } // Make sure the last addBeans() method is closed properly beanAdder.close(); @@ -259,8 +264,11 @@ private Map> initBeanToInjections(BeanDeployment beanDe for (InterceptorInfo interceptor : bean.getBoundInterceptors()) { beanToInjections.computeIfAbsent(interceptor, d -> new ArrayList<>()).add(bean); } + for (DecoratorInfo decorator : bean.getBoundDecorators()) { + beanToInjections.computeIfAbsent(decorator, d -> new ArrayList<>()).add(bean); + } } - // Also process interceptors injection points + // Also process interceptor and decorator injection points for (InterceptorInfo interceptor : beanDeployment.getInterceptors()) { for (Injection injection : interceptor.getInjections()) { for (InjectionPointInfo injectionPoint : injection.injectionPoints) { @@ -271,6 +279,16 @@ private Map> initBeanToInjections(BeanDeployment beanDe } } } + for (DecoratorInfo decorator : beanDeployment.getDecorators()) { + for (Injection injection : decorator.getInjections()) { + for (InjectionPointInfo injectionPoint : injection.injectionPoints) { + if (!injectionPoint.isDelegate() && !BuiltinBean.resolvesTo(injectionPoint)) { + beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), d -> new ArrayList<>()) + .add(decorator); + } + } + } + } // Note that we do not have to process observer injection points because observers are always processed after all beans are ready return beanToInjections; } @@ -515,7 +533,7 @@ void addComponentInternal(BeanInfo bean) { } List injectionPoints = bean.getInjections().stream().flatMap(i -> i.injectionPoints.stream()) - .filter(ip -> !BuiltinBean.resolvesTo(ip)).collect(toList()); + .filter(ip -> !ip.isDelegate() && !BuiltinBean.resolvesTo(ip)).collect(toList()); List params = new ArrayList<>(); List paramTypes = new ArrayList<>(); @@ -564,6 +582,18 @@ void addComponentInternal(BeanInfo bean) { } paramTypes.add(Type.getDescriptor(Supplier.class)); } + for (DecoratorInfo decorator : bean.getBoundDecorators()) { + if (processedBeans.contains(decorator)) { + params.add(addMethod.invokeInterfaceMethod(MethodDescriptors.MAP_GET, + beanIdToBeanHandle, addMethod.load(decorator.getIdentifier()))); + } else { + // Bound decorator was not processed yet - use MapValueSupplier + params.add(addMethod.newInstance( + MethodDescriptors.MAP_VALUE_SUPPLIER_CONSTRUCTOR, + beanIdToBeanHandle, addMethod.load(decorator.getIdentifier()))); + } + paramTypes.add(Type.getDescriptor(Supplier.class)); + } // Foo_Bean bean = new Foo_Bean(bean3) ResultHandle beanInstance = addMethod.newInstance( diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorGenerator.java new file mode 100644 index 00000000000000..1f60236883a6e7 --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorGenerator.java @@ -0,0 +1,361 @@ +package io.quarkus.arc.processor; + +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; + +import io.quarkus.arc.InjectableDecorator; +import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; +import io.quarkus.arc.processor.ResourceOutput.Resource; +import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.DescriptorUtils; +import io.quarkus.gizmo.FieldCreator; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.Predicate; +import java.util.function.Supplier; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type.Kind; +import org.jboss.jandex.TypeVariable; + +public class DecoratorGenerator extends BeanGenerator { + + protected static final String FIELD_NAME_DECORATED_TYPES = "decoratedTypes"; + protected static final String FIELD_NAME_DELEGATE_TYPE = "delegateType"; + static final String ABSTRACT_IMPL_SUFFIX = "_Impl"; + + public DecoratorGenerator(AnnotationLiteralProcessor annotationLiterals, Predicate applicationClassPredicate, + PrivateMembersCollector privateMembers, boolean generateSources, ReflectionRegistration reflectionRegistration, + Set existingClasses, Map beanToGeneratedName, + Predicate injectionPointAnnotationsPredicate) { + super(annotationLiterals, applicationClassPredicate, privateMembers, generateSources, reflectionRegistration, + existingClasses, beanToGeneratedName, injectionPointAnnotationsPredicate); + } + + /** + * + * @param decorator bean + * @return a collection of resources + */ + Collection generate(DecoratorInfo decorator) { + + ProviderType providerType = new ProviderType(decorator.getProviderType()); + ClassInfo decoratorClass = decorator.getTarget().get().asClass(); + String baseName = createBaseName(decoratorClass); + String targetPackage = DotNames.packageName(providerType.name()); + String generatedName = generatedNameFromTarget(targetPackage, baseName, BEAN_SUFFIX); + beanToGeneratedName.put(decorator, generatedName); + if (existingClasses.contains(generatedName)) { + return Collections.emptyList(); + } + + boolean isApplicationClass = applicationClassPredicate.test(decorator.getBeanClass()); + ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, + name -> name.equals(generatedName) ? SpecialType.DECORATOR_BEAN : null, generateSources); + + // MyDecorator_Bean implements InjectableDecorator + ClassCreator decoratorCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .interfaces(InjectableDecorator.class, Supplier.class) + .build(); + + // Generate the implementation class for an abstract decorator + if (decorator.isAbstract()) { + String generatedImplName = generateDecoratorImplementation(baseName, targetPackage, decorator, decoratorClass, + classOutput); + providerType = new ProviderType(org.jboss.jandex.Type.create(DotName.createSimple(generatedImplName), Kind.CLASS)); + } + + // Fields + FieldCreator beanTypes = decoratorCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator decoratedTypes = decoratorCreator.getFieldCreator(FIELD_NAME_DECORATED_TYPES, Set.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + InjectionPointInfo delegateInjectionPoint = decorator.getDelegateInjectionPoint(); + FieldCreator delegateType = decoratorCreator.getFieldCreator(FIELD_NAME_DELEGATE_TYPE, Type.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator delegateQualifiers = null; + if (!delegateInjectionPoint.hasDefaultedQualifier()) { + delegateQualifiers = decoratorCreator.getFieldCreator(FIELD_NAME_QUALIFIERS, Set.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + } + + Map injectionPointToProviderField = new HashMap<>(); + initMaps(decorator, injectionPointToProviderField, Collections.emptyMap(), Collections.emptyMap()); + createProviderFields(decoratorCreator, decorator, injectionPointToProviderField, Collections.emptyMap(), + Collections.emptyMap()); + createConstructor(classOutput, decoratorCreator, decorator, injectionPointToProviderField, + delegateType, delegateQualifiers, decoratedTypes, reflectionRegistration); + + implementGetIdentifier(decorator, decoratorCreator); + implementSupplierGet(decoratorCreator); + implementCreate(classOutput, decoratorCreator, decorator, providerType, baseName, + injectionPointToProviderField, + Collections.emptyMap(), Collections.emptyMap(), + reflectionRegistration, targetPackage, isApplicationClass); + implementGet(decorator, decoratorCreator, providerType, baseName); + implementGetTypes(decoratorCreator, beanTypes.getFieldDescriptor()); + implementGetBeanClass(decorator, decoratorCreator); + // Decorators are always @Dependent and have always default qualifiers + + // InjectableDecorator methods + implementGetDecoratedTypes(decoratorCreator, decoratedTypes.getFieldDescriptor()); + implementGetDelegateType(decoratorCreator, delegateType.getFieldDescriptor()); + implementGetDelegateQualifiers(decoratorCreator, delegateQualifiers); + implementGetPriority(decoratorCreator, decorator); + + decoratorCreator.close(); + + return classOutput.getResources(); + + } + + static String createBaseName(ClassInfo decoratorClass) { + String baseName; + if (decoratorClass.enclosingClass() != null) { + baseName = DotNames.simpleName(decoratorClass.enclosingClass()) + "_" + DotNames.simpleName(decoratorClass); + } else { + baseName = DotNames.simpleName(decoratorClass); + } + return baseName; + } + + private String generateDecoratorImplementation(String baseName, String targetPackage, DecoratorInfo decorator, + ClassInfo decoratorClass, ClassOutput classOutput) { + // MyDecorator_Impl + String generatedImplName = generatedNameFromTarget(targetPackage, baseName, ABSTRACT_IMPL_SUFFIX); + ClassCreator decoratorImplCreator = ClassCreator.builder().classOutput(classOutput).className(generatedImplName) + .superClass(decoratorClass.name().toString()) + .build(); + IndexView index = decorator.getDeployment().getBeanArchiveIndex(); + + FieldCreator delegateField = decoratorImplCreator.getFieldCreator("impl$delegate", + Object.class.getName()); + + // Constructor + MethodInfo decoratorConstructor = decoratorClass.firstMethod(Methods.INIT); + MethodCreator constructor = decoratorImplCreator.getMethodCreator(Methods.INIT, "V", + decoratorConstructor.parameters().toArray()); + // Invoke super() + constructor.invokeSpecialMethod(decoratorConstructor, constructor.getThis()); + // Set the delegate field + constructor.writeInstanceField(delegateField.getFieldDescriptor(), constructor.getThis(), + constructor.invokeStaticMethod(MethodDescriptors.DECORATOR_DELEGATE_PROVIDER_GET)); + constructor.returnValue(null); + + // Find non-decorated methods from all decorated types + Set abstractMethods = new HashSet<>(); + Map bridgeMethods = new HashMap<>(); + for (org.jboss.jandex.Type decoratedType : decorator.getDecoratedTypes()) { + + ClassInfo decoratedTypeClass = index + .getClassByName(decoratedType.name()); + if (decoratedTypeClass == null) { + throw new IllegalStateException("TODO"); + } + + // A decorated type can declare type parameters + // For example Converter should result in a T -> String mapping + List typeParameters = decoratedTypeClass.typeParameters(); + Map resolvedTypeParameters = Collections.emptyMap(); + if (!typeParameters.isEmpty()) { + resolvedTypeParameters = new HashMap<>(); + // The delegate type can be used to infer the parameter types + org.jboss.jandex.Type type = decorator.getDelegateType(); + if (type.kind() == Kind.PARAMETERIZED_TYPE) { + List typeArguments = type.asParameterizedType().arguments(); + for (int i = 0; i < typeParameters.size(); i++) { + resolvedTypeParameters.put(typeParameters.get(i), typeArguments.get(i)); + } + } + } + + for (MethodInfo method : decoratedTypeClass.methods()) { + if (Methods.skipForDelegateSubclass(method)) { + continue; + } + MethodDescriptor methodDescriptor = MethodDescriptor.of(method); + + // Create a resolved descriptor variant if a param contains a type variable + // E.g. ping(T) -> ping(String) + MethodDescriptor resolvedMethodDescriptor; + if (!typeParameters.isEmpty() && (Methods.containsTypeVariableParameter(method) + || Types.containsTypeVariable(method.returnType()))) { + List paramTypes = Types.getResolvedParameters(decoratedTypeClass, + resolvedTypeParameters, method, + index); + org.jboss.jandex.Type returnType = Types.resolveTypeParam(method.returnType(), resolvedTypeParameters, + index); + String[] paramTypesArray = new String[paramTypes.size()]; + for (int i = 0; i < paramTypesArray.length; i++) { + paramTypesArray[i] = DescriptorUtils.typeToString(paramTypes.get(i)); + } + resolvedMethodDescriptor = MethodDescriptor.ofMethod(method.declaringClass().toString(), + method.name(), DescriptorUtils.typeToString(returnType), paramTypesArray); + } else { + resolvedMethodDescriptor = null; + } + + MethodDescriptor abstractDescriptor = methodDescriptor; + for (MethodInfo decoratorMethod : decoratorClass.methods()) { + MethodDescriptor descriptor = MethodDescriptor.of(decoratorMethod); + if (Methods.descriptorMatches(descriptor, methodDescriptor)) { + abstractDescriptor = null; + break; + } + } + + if (abstractDescriptor != null) { + abstractMethods.add(methodDescriptor); + // Bridge method is needed + if (resolvedMethodDescriptor != null) { + bridgeMethods.put(resolvedMethodDescriptor, abstractDescriptor); + } + } + } + } + + for (MethodDescriptor abstractMethod : abstractMethods) { + MethodCreator delegate = decoratorImplCreator.getMethodCreator(abstractMethod) + .setModifiers(ACC_PUBLIC); + // Invoke the method upon the delegate injection point + ResultHandle delegateHandle = delegate.readInstanceField(delegateField.getFieldDescriptor(), + delegate.getThis()); + ResultHandle[] args = new ResultHandle[abstractMethod.getParameterTypes().length]; + for (int i = 0; i < args.length; i++) { + args[i] = delegate.getMethodParam(i); + } + delegate.returnValue(delegate.invokeInterfaceMethod(abstractMethod, delegateHandle, args)); + } + + for (Entry entry : bridgeMethods.entrySet()) { + MethodCreator delegate = decoratorImplCreator.getMethodCreator(entry.getKey()) + .setModifiers(ACC_PUBLIC); + ResultHandle[] args = new ResultHandle[entry.getKey().getParameterTypes().length]; + for (int i = 0; i < args.length; i++) { + args[i] = delegate.getMethodParam(i); + } + delegate.returnValue( + delegate.invokeVirtualMethod( + MethodDescriptor.ofMethod(decoratorImplCreator.getClassName(), entry.getValue().getName(), + entry.getValue().getReturnType(), entry.getValue().getParameterTypes()), + delegate.getThis(), args)); + } + + decoratorImplCreator.close(); + return generatedImplName; + } + + protected void createConstructor(ClassOutput classOutput, ClassCreator creator, DecoratorInfo decorator, + Map injectionPointToProviderField, + FieldCreator delegateType, + FieldCreator delegateQualifiers, + FieldCreator decoratedTypes, + ReflectionRegistration reflectionRegistration) { + + MethodCreator constructor = initConstructor(classOutput, creator, decorator, injectionPointToProviderField, + Collections.emptyMap(), Collections.emptyMap(), annotationLiterals, reflectionRegistration); + + if (delegateQualifiers != null) { + // delegateQualifiers = new HashSet<>() + ResultHandle delegateQualifiersHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (AnnotationInstance delegateQualifier : decorator.getDelegateQualifiers()) { + // Create annotation literal first + ClassInfo delegateQualifierClass = decorator.getDeployment().getInterceptorBinding(delegateQualifier.name()); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, delegateQualifiersHandle, + annotationLiterals.process(constructor, classOutput, delegateQualifierClass, delegateQualifier, + Types.getPackageName(creator.getClassName()))); + } + constructor.writeInstanceField(delegateQualifiers.getFieldDescriptor(), constructor.getThis(), + delegateQualifiersHandle); + } + + // decoratedTypes = new HashSet<>() + ResultHandle decoratedTypesHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + // Get the TCCL - we will use it later + ResultHandle currentThread = constructor + .invokeStaticMethod(MethodDescriptors.THREAD_CURRENT_THREAD); + ResultHandle tccl = constructor.invokeVirtualMethod(MethodDescriptors.THREAD_GET_TCCL, currentThread); + for (org.jboss.jandex.Type decoratedType : decorator.getDecoratedTypes()) { + ResultHandle typeHandle; + try { + typeHandle = Types.getTypeHandle(constructor, decoratedType, tccl); + } catch (IllegalArgumentException e) { + throw new IllegalStateException("Unable to construct the type handle for " + decorator + ": " + e.getMessage()); + } + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, decoratedTypesHandle, typeHandle); + } + constructor.writeInstanceField(decoratedTypes.getFieldDescriptor(), constructor.getThis(), decoratedTypesHandle); + + // delegate type + ResultHandle delegateTypeHandle; + try { + delegateTypeHandle = Types.getTypeHandle(constructor, decorator.getDelegateType(), tccl); + } catch (IllegalArgumentException e) { + throw new IllegalStateException("Unable to construct the type handle for " + decorator + ": " + e.getMessage()); + } + constructor.writeInstanceField(delegateType.getFieldDescriptor(), constructor.getThis(), delegateTypeHandle); + + constructor.returnValue(null); + } + + /** + * + * @see InjectableDecorator#getDecoratedTypes() + */ + protected void implementGetDecoratedTypes(ClassCreator creator, FieldDescriptor decoratedTypes) { + MethodCreator getDecoratedTypes = creator.getMethodCreator("getDecoratedTypes", Set.class).setModifiers(ACC_PUBLIC); + getDecoratedTypes.returnValue(getDecoratedTypes.readInstanceField(decoratedTypes, getDecoratedTypes.getThis())); + } + + /** + * + * @see InjectableDecorator#getDelegateType() + */ + protected void implementGetDelegateType(ClassCreator creator, FieldDescriptor delegateType) { + MethodCreator getDelegateType = creator.getMethodCreator("getDelegateType", Type.class).setModifiers(ACC_PUBLIC); + getDelegateType.returnValue(getDelegateType.readInstanceField(delegateType, getDelegateType.getThis())); + } + + /** + * + * @param creator + * @param qualifiersField + * @see InjectableDecorator#getDelegateQualifiers() + */ + protected void implementGetDelegateQualifiers(ClassCreator creator, FieldCreator qualifiersField) { + if (qualifiersField != null) { + MethodCreator getDelegateQualifiers = creator.getMethodCreator("getDelegateQualifiers", Set.class) + .setModifiers(ACC_PUBLIC); + getDelegateQualifiers + .returnValue(getDelegateQualifiers.readInstanceField(qualifiersField.getFieldDescriptor(), + getDelegateQualifiers.getThis())); + } + } + + /** + * + * @see InjectableDecorator#getPriority() + */ + protected void implementGetPriority(ClassCreator creator, DecoratorInfo decorator) { + MethodCreator getPriority = creator.getMethodCreator("getPriority", int.class).setModifiers(ACC_PUBLIC); + getPriority.returnValue(getPriority.load(decorator.getPriority())); + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorInfo.java new file mode 100644 index 00000000000000..d799919e2b1006 --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorInfo.java @@ -0,0 +1,73 @@ +package io.quarkus.arc.processor; + +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.Type; +import org.jboss.jandex.Type.Kind; + +public class DecoratorInfo extends BeanInfo implements Comparable { + + private final InjectionPointInfo delegateInjectionPoint; + private final Set decoratedTypes; + private final int priority; + + DecoratorInfo(AnnotationTarget target, BeanDeployment beanDeployment, InjectionPointInfo delegateInjectionPoint, + Set decoratedTypes, List injections, int priority) { + super(target, beanDeployment, BuiltinScope.DEPENDENT.getInfo(), + Collections.singleton(Type.create(target.asClass().name(), Kind.CLASS)), new HashSet<>(), injections, + null, null, null, Collections.emptyList(), null, false); + this.priority = priority; + this.delegateInjectionPoint = delegateInjectionPoint; + this.decoratedTypes = decoratedTypes; + } + + public InjectionPointInfo getDelegateInjectionPoint() { + return delegateInjectionPoint; + } + + public Type getDelegateType() { + return delegateInjectionPoint.getRequiredType(); + } + + public ClassInfo getDelegateTypeClass() { + return getDeployment().getBeanArchiveIndex().getClassByName(getDelegateType().name()); + } + + public Set getDelegateQualifiers() { + return delegateInjectionPoint.getRequiredQualifiers(); + } + + public Set getDecoratedTypes() { + return decoratedTypes; + } + + public int getPriority() { + return priority; + } + + public boolean isAbstract() { + return Modifier.isAbstract(target.get().asClass().flags()); + } + + @Override + public boolean isDecorator() { + return true; + } + + @Override + public String toString() { + return "DECORATOR bean [decoratedTypes=" + decoratedTypes + ", target=" + getTarget() + "]"; + } + + @Override + public int compareTo(DecoratorInfo other) { + return getTarget().toString().compareTo(other.getTarget().toString()); + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Decorators.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Decorators.java new file mode 100644 index 00000000000000..b1ec5e0c1dd99b --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Decorators.java @@ -0,0 +1,101 @@ +package io.quarkus.arc.processor; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import javax.enterprise.inject.spi.DefinitionException; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.jboss.logging.Logger; + +final class Decorators { + + static final Logger LOGGER = Logger.getLogger(Decorators.class); + + private Decorators() { + } + + static DecoratorInfo createDecorator(ClassInfo decoratorClass, BeanDeployment beanDeployment, + InjectionPointModifier transformer, AnnotationStore store) { + + // Find the delegate injection point + List delegateInjectionPoints = new LinkedList<>(); + List injections = Injection.forBean(decoratorClass, null, beanDeployment, transformer); + for (Injection injection : injections) { + for (InjectionPointInfo injectionPoint : injection.injectionPoints) { + if (injectionPoint.isDelegate()) { + delegateInjectionPoints.add(injectionPoint); + } + } + } + if (delegateInjectionPoints.isEmpty()) { + throw new DefinitionException("The decorator " + decoratorClass + " has no @Delegate injection point"); + } else if (delegateInjectionPoints.size() > 1) { + throw new DefinitionException( + "The decorator " + decoratorClass + " has multiple @Delegate injection points: " + delegateInjectionPoints); + } + + InjectionPointInfo delegateInjectionPoint = delegateInjectionPoints.get(0); + + Integer priority = 0; + boolean priorityDeclared = false; + for (AnnotationInstance annotation : store.getAnnotations(decoratorClass)) { + if (annotation.name().equals(DotNames.PRIORITY)) { + priority = annotation.value().asInt(); + priorityDeclared = true; + } + } + + // The set includes all bean types which are Java interfaces, except for java.io.Serializable + Set types = Types.getClassBeanTypeClosure(decoratorClass, beanDeployment); + Set decoratedTypes = new HashSet<>(); + for (Type type : types) { + if (type.name().equals(DotNames.SERIALIZABLE)) { + continue; + } + ClassInfo clazz = beanDeployment.getBeanArchiveIndex().getClassByName(type.name()); + if (Modifier.isInterface(clazz.flags())) { + decoratedTypes.add(type); + } + } + if (decoratedTypes.isEmpty()) { + throw new DefinitionException("The decorator " + decoratorClass + " has no decorated type"); + } + + // The delegate type of a decorator must implement or extend every decorated type + Set delegateTypes = Types.getDelegateTypeClosure(delegateInjectionPoint, beanDeployment); + for (Type decoratedType : decoratedTypes) { + if (!delegateTypes.contains(decoratedType)) { + throw new DefinitionException("The delegate type " + delegateInjectionPoint.getRequiredType() + + " does not implement the decorated type: " + decoratedType); + } + } + + if (!priorityDeclared) { + LOGGER.info("The decorator " + decoratorClass + " does not declare any @Priority. " + + "It will be assigned a default priority value of 0."); + } + + if (Modifier.isAbstract(decoratorClass.flags())) { + List abstractMethods = new ArrayList<>(); + for (MethodInfo method : decoratorClass.methods()) { + if (Modifier.isAbstract(method.flags())) { + abstractMethods.add(method); + } + } + if (!abstractMethods.isEmpty()) { + throw new DefinitionException("An abstract decorator " + decoratorClass + + " declares abstract methods: " + abstractMethods); + } + } + + return new DecoratorInfo(decoratorClass, beanDeployment, delegateInjectionPoint, + decoratedTypes, injections, priority); + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java index 3c4909a0b9ffe3..c1bf7ef516b014 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java @@ -5,6 +5,7 @@ import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableInstance; import io.quarkus.arc.impl.ComputingCache; +import java.io.Serializable; import java.lang.annotation.Repeatable; import java.util.Optional; import java.util.OptionalDouble; @@ -13,6 +14,8 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Priority; +import javax.decorator.Decorator; +import javax.decorator.Delegate; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Initialized; import javax.enterprise.context.control.ActivateRequestContext; @@ -103,6 +106,9 @@ public final class DotNames { public static final DotName INITIALIZED = create(Initialized.class); public static final DotName TRANSIENT_REFERENCE = create(TransientReference.class); public static final DotName INVOCATION_CONTEXT = create(InvocationContext.class); + public static final DotName DECORATOR = create(Decorator.class); + public static final DotName DELEGATE = create(Delegate.class); + public static final DotName SERIALIZABLE = create(Serializable.class); public static final DotName BOOLEAN = create(Boolean.class); public static final DotName BYTE = create(Byte.class); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java index d91b57e16469fc..c193a292012724 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java @@ -1,5 +1,7 @@ package io.quarkus.arc.processor; +import static io.quarkus.arc.processor.Annotations.contains; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -38,7 +40,7 @@ static InjectionPointInfo fromField(FieldInfo field, ClassInfo beanClass, BeanDe Type type = resolveType(field.type(), beanClass, field.declaringClass(), beanDeployment); return new InjectionPointInfo(type, transformer.applyTransformers(type, field, qualifiers), field, -1, - Annotations.contains(annotations, DotNames.TRANSIENT_REFERENCE)); + contains(annotations, DotNames.TRANSIENT_REFERENCE), contains(annotations, DotNames.DELEGATE)); } static InjectionPointInfo fromResourceField(FieldInfo field, ClassInfo beanClass, BeanDeployment beanDeployment, @@ -46,7 +48,7 @@ static InjectionPointInfo fromResourceField(FieldInfo field, ClassInfo beanClass Type type = resolveType(field.type(), beanClass, field.declaringClass(), beanDeployment); return new InjectionPointInfo(type, transformer.applyTransformers(type, field, new HashSet<>(field.annotations())), - InjectionPointKind.RESOURCE, field, -1, false); + InjectionPointKind.RESOURCE, field, -1, false, false); } static List fromMethod(MethodInfo method, ClassInfo beanClass, BeanDeployment beanDeployment, @@ -72,32 +74,28 @@ static List fromMethod(MethodInfo method, ClassInfo beanClas Type type = resolveType(paramType, beanClass, method.declaringClass(), beanDeployment); injectionPoints.add(new InjectionPointInfo(type, transformer.applyTransformers(type, method, paramQualifiers), - method, position, Annotations.contains(paramAnnotations, DotNames.TRANSIENT_REFERENCE))); + method, position, contains(paramAnnotations, DotNames.TRANSIENT_REFERENCE), + contains(paramAnnotations, DotNames.DELEGATE))); } return injectionPoints; } private final TypeAndQualifiers typeAndQualifiers; - private final AtomicReference resolvedBean; - private final InjectionPointKind kind; - private final boolean hasDefaultedQualifier; - private final AnnotationTarget target; - private final int position; - private final boolean isTransientReference; + private final boolean isDelegate; InjectionPointInfo(Type requiredType, Set requiredQualifiers, AnnotationTarget target, int position, - boolean isTransientReference) { - this(requiredType, requiredQualifiers, InjectionPointKind.CDI, target, position, isTransientReference); + boolean isTransientReference, boolean isDelegate) { + this(requiredType, requiredQualifiers, InjectionPointKind.CDI, target, position, isTransientReference, isDelegate); } InjectionPointInfo(Type requiredType, Set requiredQualifiers, InjectionPointKind kind, - AnnotationTarget target, int position, boolean isTransientReference) { + AnnotationTarget target, int position, boolean isTransientReference, boolean isDelegate) { this.typeAndQualifiers = new TypeAndQualifiers(requiredType, requiredQualifiers.isEmpty() ? Collections.singleton(AnnotationInstance.create(DotNames.DEFAULT, null, Collections.emptyList())) @@ -108,6 +106,7 @@ static List fromMethod(MethodInfo method, ClassInfo beanClas this.target = target; this.position = position; this.isTransientReference = isTransientReference; + this.isDelegate = isDelegate; } void resolve(BeanInfo bean) { @@ -174,6 +173,14 @@ boolean isDependentTransientReference() { return bean != null && isParam() && BuiltinScope.DEPENDENT.is(bean.getScope()) && isTransientReference; } + public boolean isTransientReference() { + return isTransientReference; + } + + public boolean isDelegate() { + return isDelegate; + } + /** * @return the parameter position or {@code -1} for a field injection point */ diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java index a26da991c58942..364c0fe9081d64 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java @@ -88,18 +88,18 @@ Collection generate(InterceptorInfo interceptor) { .setModifiers(ACC_PRIVATE | ACC_FINAL); Map injectionPointToProviderField = new HashMap<>(); - Map interceptorToProviderField = new HashMap<>(); - initMaps(interceptor, injectionPointToProviderField, interceptorToProviderField); + initMaps(interceptor, injectionPointToProviderField, Collections.emptyMap(), Collections.emptyMap()); - createProviderFields(interceptorCreator, interceptor, injectionPointToProviderField, interceptorToProviderField); - createConstructor(classOutput, interceptorCreator, interceptor, baseName, injectionPointToProviderField, - interceptorToProviderField, bindings.getFieldDescriptor(), reflectionRegistration); + createProviderFields(interceptorCreator, interceptor, injectionPointToProviderField, Collections.emptyMap(), + Collections.emptyMap()); + createConstructor(classOutput, interceptorCreator, interceptor, injectionPointToProviderField, + bindings.getFieldDescriptor(), reflectionRegistration); implementGetIdentifier(interceptor, interceptorCreator); implementSupplierGet(interceptorCreator); implementCreate(classOutput, interceptorCreator, interceptor, providerType, baseName, injectionPointToProviderField, - interceptorToProviderField, + Collections.emptyMap(), Collections.emptyMap(), reflectionRegistration, targetPackage, isApplicationClass); implementGet(interceptor, interceptorCreator, providerType, baseName); implementGetTypes(interceptorCreator, beanTypes.getFieldDescriptor()); @@ -121,13 +121,11 @@ Collection generate(InterceptorInfo interceptor) { } protected void createConstructor(ClassOutput classOutput, ClassCreator creator, InterceptorInfo interceptor, - String baseName, Map injectionPointToProviderField, - Map interceptorToProviderField, FieldDescriptor bindings, - ReflectionRegistration reflectionRegistration) { + FieldDescriptor bindings, ReflectionRegistration reflectionRegistration) { - MethodCreator constructor = initConstructor(classOutput, creator, interceptor, baseName, injectionPointToProviderField, - interceptorToProviderField, annotationLiterals, reflectionRegistration); + MethodCreator constructor = initConstructor(classOutput, creator, interceptor, injectionPointToProviderField, + Collections.emptyMap(), Collections.emptyMap(), annotationLiterals, reflectionRegistration); // Bindings // bindings = new HashSet<>() diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java index 474ad3ef792808..96dd1df92513b1 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java @@ -44,7 +44,7 @@ static InterceptorInfo createInterceptor(ClassInfo interceptorClass, BeanDeploym throw new DefinitionException("Interceptor has no bindings: " + interceptorClass); } if (!priorityDeclared) { - LOGGER.info("An interceptor " + interceptorClass + " does not declare any @Priority. " + + LOGGER.info("The interceptor " + interceptorClass + " does not declare any @Priority. " + "It will be assigned a default priority value of 0."); } return new InterceptorInfo(interceptorClass, beanDeployment, diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java index dc84000db17782..aa40bf2320ff3c 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java @@ -10,6 +10,7 @@ import io.quarkus.arc.InjectableReferenceProvider; import io.quarkus.arc.impl.ClientProxies; import io.quarkus.arc.impl.CreationalContextImpl; +import io.quarkus.arc.impl.DecoratorDelegateProvider; import io.quarkus.arc.impl.FixedValueSupplier; import io.quarkus.arc.impl.InjectableReferenceProviders; import io.quarkus.arc.impl.InterceptedMethodMetadata; @@ -267,6 +268,13 @@ public final class MethodDescriptors { public static final MethodDescriptor CLIENT_PROXIES_GET_DELEGATE = MethodDescriptor.ofMethod(ClientProxies.class, "getDelegate", Object.class, InjectableBean.class); + public static final MethodDescriptor DECORATOR_DELEGATE_PROVIDER_SET = MethodDescriptor + .ofMethod(DecoratorDelegateProvider.class, "set", Object.class, Object.class); + public static final MethodDescriptor DECORATOR_DELEGATE_PROVIDER_UNSET = MethodDescriptor + .ofMethod(DecoratorDelegateProvider.class, "unset", void.class); + public static final MethodDescriptor DECORATOR_DELEGATE_PROVIDER_GET = MethodDescriptor + .ofMethod(DecoratorDelegateProvider.class, "get", Object.class); + private MethodDescriptors() { } 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 94fe82c64c05f4..2fb2ff41100ad3 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 @@ -4,6 +4,7 @@ import io.quarkus.gizmo.DescriptorUtils; import io.quarkus.gizmo.Gizmo; +import io.quarkus.gizmo.MethodDescriptor; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; @@ -134,6 +135,20 @@ private static boolean skipForClientProxy(MethodInfo method, boolean transformUn return false; } + static boolean skipForDelegateSubclass(MethodInfo method) { + if (Modifier.isStatic(method.flags())) { + return true; + } + if (IGNORED_METHODS.contains(method.name())) { + return true; + } + // skip all Object methods + if (method.declaringClass().name().equals(DotNames.OBJECT)) { + return true; + } + return false; + } + static boolean isObjectToString(MethodInfo method) { return method.declaringClass().name().equals(DotNames.OBJECT) && method.name().equals(TO_STRING); } @@ -363,6 +378,39 @@ static DotName toRawType(Type a) { } } + static void addDelegateTypeMethods(IndexView index, ClassInfo delegateTypeClass, List methods) { + if (delegateTypeClass != null) { + for (MethodInfo method : delegateTypeClass.methods()) { + if (skipForDelegateSubclass(method)) { + continue; + } + methods.add(method); + } + // Interfaces + for (Type interfaceType : delegateTypeClass.interfaceTypes()) { + ClassInfo interfaceClassInfo = getClassByName(index, interfaceType.name()); + if (interfaceClassInfo != null) { + addDelegateTypeMethods(index, interfaceClassInfo, methods); + } + } + if (delegateTypeClass.superClassType() != null) { + ClassInfo superClassInfo = getClassByName(index, delegateTypeClass.superName()); + if (superClassInfo != null) { + addDelegateTypeMethods(index, superClassInfo, methods); + } + } + } + } + + static boolean containsTypeVariableParameter(MethodInfo method) { + for (Type param : method.parameters()) { + if (Types.containsTypeVariable(param)) { + return true; + } + } + return false; + } + static class RemoveFinalFromMethod implements BiFunction { private final String classToTransform; @@ -540,4 +588,9 @@ private boolean parametersMatch(MethodInfo method, MethodInfo bridge) { } } + static boolean descriptorMatches(MethodDescriptor d1, MethodDescriptor d2) { + return d1.getName().equals(d2.getName()) + && d1.getDescriptor().equals(d2.getDescriptor()); + } + } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceOutput.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceOutput.java index d03b989f2ba83a..eb6cb182586ae8 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceOutput.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceOutput.java @@ -74,6 +74,7 @@ enum Type { enum SpecialType { BEAN, INTERCEPTOR_BEAN, + DECORATOR_BEAN, OBSERVER; } 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 5591741333d70f..552c9403aeb72f 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 @@ -5,11 +5,14 @@ import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import io.quarkus.arc.ArcUndeclaredThrowableException; +import io.quarkus.arc.InjectableDecorator; import io.quarkus.arc.InjectableInterceptor; import io.quarkus.arc.Subclass; import io.quarkus.arc.impl.InterceptedMethodMetadata; +import io.quarkus.arc.processor.BeanInfo.DecorationInfo; import io.quarkus.arc.processor.BeanInfo.InterceptionInfo; import io.quarkus.arc.processor.ResourceOutput.Resource; +import io.quarkus.gizmo.AssignableResultHandle; import io.quarkus.gizmo.BytecodeCreator; import io.quarkus.gizmo.CatchBlockCreator; import io.quarkus.gizmo.ClassCreator; @@ -45,12 +48,14 @@ import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; +import org.jboss.jandex.Type.Kind; +import org.jboss.jandex.TypeVariable; /** - * - * @author Martin Kouba + * A subclass is generated for any intercepted/decorated bean. */ public class SubclassGenerator extends AbstractGenerator { @@ -91,13 +96,6 @@ public SubclassGenerator(AnnotationLiteralProcessor annotationLiterals, Predicat this.existingClasses = existingClasses; } - /** - * - * @param bean - * @param beanClassName Fully qualified class name - * @param existingClasses - * @return a java file - */ Collection generate(BeanInfo bean, String beanClassName) { ResourceClassOutput classOutput = new ResourceClassOutput(applicationClassPredicate.test(bean.getBeanClass()), @@ -117,7 +115,7 @@ Collection generate(BeanInfo bean, String beanClassName) { .superClass(providerTypeName).interfaces(Subclass.class) .build(); - FieldDescriptor preDestroyField = createConstructor(classOutput, bean, subclass, providerTypeName, + FieldDescriptor preDestroyField = createConstructor(classOutput, bean, subclass, providerType, providerTypeName, reflectionRegistration); createDestroy(classOutput, bean, subclass, preDestroyField); @@ -126,8 +124,7 @@ Collection generate(BeanInfo bean, String beanClassName) { } protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo bean, ClassCreator subclass, - String providerTypeName, - ReflectionRegistration reflectionRegistration) { + Type providerType, String providerTypeName, ReflectionRegistration reflectionRegistration) { // Constructor parameters List parameterTypes = new ArrayList<>(); @@ -146,6 +143,11 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be for (int j = 0; j < boundInterceptors.size(); j++) { parameterTypes.add(InjectableInterceptor.class.getName()); } + // Decorator providers + List boundDecorators = bean.getBoundDecorators(); + for (int j = 0; j < boundDecorators.size(); j++) { + parameterTypes.add(InjectableDecorator.class.getName()); + } MethodCreator constructor = subclass.getMethodCreator(Methods.INIT, "V", parameterTypes.toArray(new String[0])); ResultHandle creationalContextHandle = constructor.getMethodParam(superParamsSize); @@ -177,6 +179,30 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be interceptorInstanceToResultHandle.put(interceptorInfo.getIdentifier(), interceptorInstance); } + Map forwardingMethods = new HashMap<>(); + List interceptedOrDecoratedMethods = bean.getInterceptedOrDecoratedMethods(); + int methodIdx = 1; + for (MethodInfo method : interceptedOrDecoratedMethods) { + forwardingMethods.put(MethodDescriptor.of(method), + createForwardingMethod(subclass, providerTypeName, method, methodIdx)); + } + + // If a decorator is associated: + // 1. Generate the delegate subclass + // 2. Instantiate the decorator instance, add set the corresponding field + Map decoratorToResultHandle; + if (boundDecorators.isEmpty()) { + decoratorToResultHandle = Collections.emptyMap(); + } else { + decoratorToResultHandle = new HashMap<>(); + for (int j = 0; j < boundDecorators.size(); j++) { + processDecorator(boundDecorators.get(j), bean, providerType, providerTypeName, subclass, classOutput, + constructor, boundInterceptors.size() + superParamsSize + j + 1, decoratorToResultHandle, + creationalContextHandle, + forwardingMethods); + } + } + // PreDestroy interceptors FieldCreator preDestroysField = null; InterceptionInfo preDestroys = bean.getLifecycleInterceptors(InterceptionType.PRE_DESTROY); @@ -277,76 +303,378 @@ public ResultHandle apply(List interceptors) { } }; - int methodIdx = 1; - for (Entry entry : bean.getInterceptedMethods().entrySet()) { - String methodId = "m" + methodIdx++; - MethodInfo method = entry.getKey(); - ResultHandle methodIdHandle = constructor.load(methodId); - InterceptionInfo interceptedMethod = entry.getValue(); - - // 1. Interceptor chain - ResultHandle chainHandle = interceptorChains.computeIfAbsent(interceptedMethod.interceptors, interceptorChainsFun); - - // 2. Method method = Reflections.findMethod(org.jboss.weld.arc.test.interceptors.SimpleBean.class,"foo",java.lang.String.class) - ResultHandle[] paramsHandles = new ResultHandle[3]; - paramsHandles[0] = constructor.loadClass(providerTypeName); - paramsHandles[1] = constructor.load(method.name()); - if (!method.parameters().isEmpty()) { - ResultHandle paramsArray = constructor.newArray(Class.class, constructor.load(method.parameters().size())); - for (ListIterator iterator = method.parameters().listIterator(); iterator.hasNext();) { - constructor.writeArrayValue(paramsArray, iterator.nextIndex(), - constructor.loadClass(iterator.next().name().toString())); + methodIdx = 1; + for (MethodInfo method : interceptedOrDecoratedMethods) { + MethodDescriptor methodDescriptor = MethodDescriptor.of(method); + InterceptionInfo interception = bean.getInterceptedMethods().get(method); + DecorationInfo decoration = bean.getDecoratedMethods().get(method); + MethodDescriptor forwardDescriptor = forwardingMethods.get(methodDescriptor); + + if (interception != null) { + String methodId = "m" + methodIdx++; + ResultHandle methodIdHandle = constructor.load(methodId); + + // 1. Interceptor chain + ResultHandle chainHandle = interceptorChains.computeIfAbsent(interception.interceptors, interceptorChainsFun); + + // 2. Method method = Reflections.findMethod(org.jboss.weld.arc.test.interceptors.SimpleBean.class,"foo",java.lang.String.class) + ResultHandle[] paramsHandles = new ResultHandle[3]; + paramsHandles[0] = constructor.loadClass(providerTypeName); + paramsHandles[1] = constructor.load(method.name()); + if (!method.parameters().isEmpty()) { + ResultHandle paramsArray = constructor.newArray(Class.class, constructor.load(method.parameters().size())); + for (ListIterator iterator = method.parameters().listIterator(); iterator.hasNext();) { + constructor.writeArrayValue(paramsArray, iterator.nextIndex(), + constructor.loadClass(iterator.next().name().toString())); + } + paramsHandles[2] = paramsArray; + } else { + paramsHandles[2] = constructor.readStaticField(FieldDescriptors.ANNOTATION_LITERALS_EMPTY_CLASS_ARRAY); } - paramsHandles[2] = paramsArray; + ResultHandle methodHandle = constructor.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_METHOD, + paramsHandles); + + // 3. Interceptor bindings + // Note that we use a shared list if possible + ResultHandle bindingsHandle = bindings.computeIfAbsent( + interception.bindings.stream().map(BindingKey::new).collect(Collectors.toList()), bindingsFun); + + // Now create metadata for the given intercepted method + ResultHandle methodMetadataHandle = constructor.newInstance( + MethodDescriptors.INTERCEPTED_METHOD_METADATA_CONSTRUCTOR, + chainHandle, methodHandle, bindingsHandle); + // metadata.put("m1", new SubclassMethodMetadata(...)) + constructor.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, metadataHandle, methodIdHandle, + methodMetadataHandle); + + // Needed when running on native image + reflectionRegistration.registerMethod(method); + + // Finally create the intercepted method + createInterceptedMethod(classOutput, bean, method, methodId, subclass, providerTypeName, + metadataField.getFieldDescriptor(), forwardDescriptor, + decoration != null ? decoration.decorators.get(0) : null); } else { - paramsHandles[2] = constructor.readStaticField(FieldDescriptors.ANNOTATION_LITERALS_EMPTY_CLASS_ARRAY); - } - ResultHandle methodHandle = constructor.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_METHOD, - paramsHandles); + // Only decorators are applied + MethodCreator decoratedMethod = subclass.getMethodCreator(methodDescriptor); - // 3. Interceptor bindings - // Note that we use a shared list if possible - ResultHandle bindingsHandle = bindings.computeIfAbsent( - interceptedMethod.bindings.stream().map(BindingKey::new).collect(Collectors.toList()), bindingsFun); + ResultHandle[] params = new ResultHandle[method.parameters().size()]; + for (int i = 0; i < method.parameters().size(); ++i) { + params[i] = decoratedMethod.getMethodParam(i); + } - // Now create metadata for the given intercepted method - ResultHandle methodMetadataHandle = constructor.newInstance( - MethodDescriptors.INTERCEPTED_METHOD_METADATA_CONSTRUCTOR, - chainHandle, methodHandle, bindingsHandle); - // metadata.put("m1", new SubclassMethodMetadata(...)) - constructor.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, metadataHandle, methodIdHandle, methodMetadataHandle); + // if(!this.bean == null) return super.foo() + BytecodeCreator notConstructed = decoratedMethod + .ifNull(decoratedMethod.readInstanceField(metadataField.getFieldDescriptor(), + decoratedMethod.getThis())) + .trueBranch(); + if (Modifier.isAbstract(method.flags())) { + notConstructed.throwException(IllegalStateException.class, "Cannot delegate to an abstract method"); + } else { + notConstructed.returnValue( + notConstructed.invokeVirtualMethod(forwardDescriptor, notConstructed.getThis(), params)); + } - // Needed when running on native image - reflectionRegistration.registerMethod(method); + DecoratorInfo firstDecorator = decoration.decorators.get(0); + ResultHandle decoratorInstance = decoratedMethod.readInstanceField(FieldDescriptor.of(subclass.getClassName(), + firstDecorator.getIdentifier(), Object.class.getName()), decoratedMethod.getThis()); - // Finally create the forwarding method - createForwardingMethod(classOutput, bean, method, methodId, subclass, providerTypeName, - metadataField.getFieldDescriptor(), - interceptedMethod); + String declaringClass = firstDecorator.getBeanClass().toString(); + if (firstDecorator.isAbstract()) { + String baseName = DecoratorGenerator.createBaseName(firstDecorator.getTarget().get().asClass()); + String targetPackage = DotNames.packageName(firstDecorator.getProviderType().name()); + declaringClass = generatedNameFromTarget(targetPackage, baseName, DecoratorGenerator.ABSTRACT_IMPL_SUFFIX); + } + MethodDescriptor virtualMethodDescriptor = MethodDescriptor.ofMethod( + declaringClass, methodDescriptor.getName(), + methodDescriptor.getReturnType(), methodDescriptor.getParameterTypes()); + decoratedMethod + .returnValue(decoratedMethod.invokeVirtualMethod(virtualMethodDescriptor, decoratorInstance, params)); + } } - constructor.returnValue(null); return preDestroysField != null ? preDestroysField.getFieldDescriptor() : null; } - private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, MethodInfo method, String methodId, + private void processDecorator(DecoratorInfo decorator, BeanInfo bean, Type providerType, + String providerTypeName, ClassCreator subclass, ClassOutput classOutput, MethodCreator subclassConstructor, + int paramIndex, Map decoratorToResultHandle, + ResultHandle creationalContextHandle, Map forwardingMethods) { + + // First genetare the delegate subclass + // An instance of this subclass is injected in the delegate injection point of a decorator instance + ClassInfo decoratorClass = decorator.getTarget().get().asClass(); + String baseName; + if (decoratorClass.enclosingClass() != null) { + baseName = DotNames.simpleName(decoratorClass.enclosingClass()) + UNDERSCORE + + DotNames.simpleName(decoratorClass); + } else { + baseName = DotNames.simpleName(decoratorClass); + } + // Name: AlphaDecorator_FooBeanId_Delegate_Subclass + String generatedName = generatedName(providerType.name(), + baseName + UNDERSCORE + bean.getIdentifier() + UNDERSCORE + "Delegate"); + + ClassCreator.Builder delegateSubclassBuilder = ClassCreator.builder().classOutput(classOutput) + .className(generatedName); + ClassInfo delegateTypeClass = decorator.getDelegateTypeClass(); + boolean delegateTypeIsInterface = Modifier.isInterface(delegateTypeClass.flags()); + // The subclass implements/extends the delegate type + if (delegateTypeIsInterface) { + delegateSubclassBuilder.interfaces(delegateTypeClass.name().toString()); + } else { + delegateSubclassBuilder.superClass(delegateTypeClass.name().toString()); + } + ClassCreator delegateSubclass = delegateSubclassBuilder.build(); + + Map nextDecorators = bean.getNextDecorators(decorator); + List decoratorParameters = new ArrayList<>(nextDecorators.values()); + Collections.sort(decoratorParameters); + Set decoratedMethods = bean.getDecoratedMethods(decorator); + Set decoratedMethodDescriptors = new HashSet<>(decoratedMethods.size()); + for (MethodInfo m : decoratedMethods) { + decoratedMethodDescriptors.add(MethodDescriptor.of(m)); + } + + List constructorParameterTypes = new ArrayList<>(); + // Fields and constructor + FieldCreator subclassField = null; + if (decoratedMethods.size() != decoratorParameters.size()) { + subclassField = delegateSubclass.getFieldCreator("subclass", subclass.getClassName()) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + constructorParameterTypes.add(subclass.getClassName()); + } + Map nextDecoratorToField = new HashMap<>(); + for (DecoratorInfo nextDecorator : decoratorParameters) { + FieldCreator nextDecoratorField = delegateSubclass + .getFieldCreator(nextDecorator.getIdentifier(), nextDecorator.getBeanClass().toString()) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + constructorParameterTypes.add(nextDecorator.getBeanClass().toString()); + nextDecoratorToField.put(nextDecorator, nextDecoratorField.getFieldDescriptor()); + } + + MethodCreator constructor = delegateSubclass.getMethodCreator(Methods.INIT, "V", + constructorParameterTypes.toArray(new String[0])); + int param = 0; + // Invoke super() + constructor.invokeSpecialMethod(MethodDescriptors.OBJECT_CONSTRUCTOR, constructor.getThis()); + // Set fields + if (subclassField != null) { + constructor.writeInstanceField( + subclassField.getFieldDescriptor(), constructor.getThis(), constructor.getMethodParam(param++)); + } + for (FieldDescriptor field : nextDecoratorToField.values()) { + constructor.writeInstanceField( + field, constructor.getThis(), constructor.getMethodParam(param++)); + } + constructor.returnValue(null); + + IndexView index = bean.getDeployment().getBeanArchiveIndex(); + + // Identify the set of methods that should be delegated + // Note that the delegate subclass must override ALL methods from the delegate type + // This is not enough if the delegate type is parameterized + List methods = new ArrayList<>(); + Methods.addDelegateTypeMethods(index, delegateTypeClass, methods); + + // The delegate type can declare type parameters + // For example @Delegate Converter should result in a T -> String mapping + List typeParameters = delegateTypeClass.typeParameters(); + Map resolvedTypeParameters = Collections.emptyMap(); + if (!typeParameters.isEmpty()) { + resolvedTypeParameters = new HashMap<>(); + // The delegate type can be used to infer the parameter types + Type delegateType = decorator.getDelegateType(); + if (delegateType.kind() == Kind.PARAMETERIZED_TYPE) { + List typeArguments = delegateType.asParameterizedType().arguments(); + for (int i = 0; i < typeParameters.size(); i++) { + resolvedTypeParameters.put(typeParameters.get(i), typeArguments.get(i)); + } + } + } + + for (MethodInfo method : methods) { + if (Methods.skipForDelegateSubclass(method)) { + continue; + } + + MethodDescriptor methodDescriptor = MethodDescriptor.of(method); + MethodCreator forward = delegateSubclass.getMethodCreator(methodDescriptor); + // Exceptions + for (Type exception : method.exceptions()) { + forward.addException(exception.toString()); + } + + ResultHandle ret = null; + ResultHandle delegateTo; + + // Method params + ResultHandle[] params = new ResultHandle[method.parameters().size()]; + for (int i = 0; i < method.parameters().size(); ++i) { + params[i] = forward.getMethodParam(i); + } + + // Create a resolved descriptor variant if a param contains a type variable + // E.g. ping(T) -> ping(String) + MethodDescriptor resolvedMethodDescriptor; + if (typeParameters.isEmpty() + || (!Methods.containsTypeVariableParameter(method) && !Types.containsTypeVariable(method.returnType()))) { + resolvedMethodDescriptor = null; + } else { + List paramTypes = Types.getResolvedParameters(delegateTypeClass, resolvedTypeParameters, method, + index); + Type returnType = Types.resolveTypeParam(method.returnType(), resolvedTypeParameters, index); + String[] paramTypesArray = new String[paramTypes.size()]; + for (int i = 0; i < paramTypesArray.length; i++) { + paramTypesArray[i] = DescriptorUtils.typeToString(paramTypes.get(i)); + } + resolvedMethodDescriptor = MethodDescriptor.ofMethod(method.declaringClass().toString(), + 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(); + break; + } + } + + if (nextDecorator != null && isDecorated(decoratedMethodDescriptors, methodDescriptor, resolvedMethodDescriptor)) { + // 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()); + if (delegateTypeIsInterface) { + ret = forward.invokeInterfaceMethod(methodDescriptor, delegateTo, params); + } else { + MethodDescriptor virtualMethod = MethodDescriptor.ofMethod(providerTypeName, + methodDescriptor.getName(), + methodDescriptor.getReturnType(), + methodDescriptor.getParameterTypes()); + ret = forward.invokeVirtualMethod(virtualMethod, delegateTo, params); + } + + } else { + // This method is not decorated or no next decorator was found in the chain + MethodDescriptor forwardingMethod = 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))) { + forwardingMethod = entry.getValue(); + break; + } + } + if (forwardingMethod != null) { + // Delegate to the subclass forwarding method + delegateTo = forward.readInstanceField(subclassField.getFieldDescriptor(), forward.getThis()); + ret = forward.invokeVirtualMethod(forwardingMethod, delegateTo, params); + } else { + // No forwarding method exists + // Simply delegate to subclass + delegateTo = forward.readInstanceField(subclassField.getFieldDescriptor(), forward.getThis()); + if (Modifier.isInterface(method.declaringClass().flags())) { + ret = forward.invokeInterfaceMethod(methodDescriptor, delegateTo, params); + } else { + ret = forward.invokeVirtualMethod(methodDescriptor, delegateTo, params); + } + } + } + + // Finally write the bytecode + forward.returnValue(ret); + } + delegateSubclass.close(); + + // Now modify the subclass constructor + ResultHandle constructorMethodParam = subclassConstructor + .getMethodParam(paramIndex); + // create instance of each decorator -> InjectableDecorator.get() + ResultHandle creationalContext = subclassConstructor.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, + creationalContextHandle); + + // Create new delegate subclass instance and set the DecoratorDelegateProvider to satisfy the delegate IP + ResultHandle[] paramHandles = new ResultHandle[constructorParameterTypes.size()]; + int paramIdx = 0; + if (subclassField != null) { + paramHandles[paramIdx++] = subclassConstructor.getThis(); + } + for (DecoratorInfo decoratorParameter : decoratorParameters) { + paramHandles[paramIdx++] = decoratorToResultHandle.get(decoratorParameter.getIdentifier()); + } + ResultHandle delegateSubclassInstance = subclassConstructor.newInstance(MethodDescriptor.ofConstructor( + delegateSubclass.getClassName(), constructorParameterTypes.toArray(new String[0])), paramHandles); + subclassConstructor.invokeStaticMethod(MethodDescriptors.DECORATOR_DELEGATE_PROVIDER_SET, delegateSubclassInstance); + + ResultHandle decoratorInstance = subclassConstructor.invokeInterfaceMethod( + MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, constructorMethodParam, creationalContext); + // And unset the delegate IP afterwards + subclassConstructor.invokeStaticMethod(MethodDescriptors.DECORATOR_DELEGATE_PROVIDER_UNSET); + + decoratorToResultHandle.put(decorator.getIdentifier(), decoratorInstance); + + // Store the decorator instance in a field + FieldCreator decoratorField = subclass.getFieldCreator(decorator.getIdentifier(), Object.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + subclassConstructor.writeInstanceField(decoratorField.getFieldDescriptor(), subclassConstructor.getThis(), + decoratorInstance); + } + + private boolean isDecorated(Set decoratedMethodDescriptors, MethodDescriptor original, + MethodDescriptor resolved) { + for (MethodDescriptor decorated : decoratedMethodDescriptors) { + if (Methods.descriptorMatches(decorated, original)) { + return true; + } + } + if (resolved != null) { + for (MethodDescriptor decorated : decoratedMethodDescriptors) { + if (Methods.descriptorMatches(decorated, resolved)) { + return true; + } + } + } + return false; + } + + private MethodDescriptor createForwardingMethod(ClassCreator subclass, String providerTypeName, MethodInfo method, + int index) { + MethodDescriptor methodDescriptor = MethodDescriptor.of(method); + String forwardMethodName = method.name() + "$$superforward" + index; + MethodDescriptor forwardDescriptor = MethodDescriptor.ofMethod(subclass.getClassName(), forwardMethodName, + methodDescriptor.getReturnType(), + methodDescriptor.getParameterTypes()); + MethodCreator forward = subclass.getMethodCreator(forwardDescriptor); + ResultHandle[] params = new ResultHandle[method.parameters().size()]; + for (int i = 0; i < method.parameters().size(); ++i) { + params[i] = forward.getMethodParam(i); + } + MethodDescriptor virtualMethod = MethodDescriptor.ofMethod(providerTypeName, methodDescriptor.getName(), + methodDescriptor.getReturnType(), methodDescriptor.getParameterTypes()); + forward.returnValue(forward.invokeSpecialMethod(virtualMethod, forward.getThis(), params)); + return forwardDescriptor; + } + + private void createInterceptedMethod(ClassOutput classOutput, BeanInfo bean, MethodInfo method, String methodId, ClassCreator subclass, String providerTypeName, FieldDescriptor metadataField, - InterceptionInfo interceptedMethod) { + MethodDescriptor forwardMethod, DecoratorInfo decorator) { MethodDescriptor originalMethodDescriptor = MethodDescriptor.of(method); - MethodCreator forwardMethod = subclass.getMethodCreator(originalMethodDescriptor); + MethodCreator interceptedMethod = subclass.getMethodCreator(originalMethodDescriptor); // Params // Object[] params = new Object[] {p1} - ResultHandle paramsHandle = forwardMethod.newArray(Object.class, forwardMethod.load(method.parameters().size())); + ResultHandle paramsHandle = interceptedMethod.newArray(Object.class, + interceptedMethod.load(method.parameters().size())); for (int i = 0; i < method.parameters().size(); i++) { - forwardMethod.writeArrayValue(paramsHandle, i, forwardMethod.getMethodParam(i)); + interceptedMethod.writeArrayValue(paramsHandle, i, interceptedMethod.getMethodParam(i)); } // if(!this.bean == null) return super.foo() - BytecodeCreator notConstructed = forwardMethod - .ifNull(forwardMethod.readInstanceField(metadataField, forwardMethod.getThis())).trueBranch(); + BytecodeCreator notConstructed = interceptedMethod + .ifNull(interceptedMethod.readInstanceField(metadataField, interceptedMethod.getThis())).trueBranch(); ResultHandle[] params = new ResultHandle[method.parameters().size()]; for (int i = 0; i < method.parameters().size(); ++i) { params[i] = notConstructed.getMethodParam(i); @@ -354,16 +682,18 @@ private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, Meth if (Modifier.isAbstract(method.flags())) { notConstructed.throwException(IllegalStateException.class, "Cannot delegate to an abstract method"); } else { - MethodDescriptor superDescriptor = MethodDescriptor.ofMethod(providerTypeName, method.name(), - method.returnType().name().toString(), - method.parameters().stream().map(p -> p.name().toString()).toArray()); - notConstructed.returnValue( - notConstructed.invokeSpecialMethod(superDescriptor, notConstructed.getThis(), params)); + notConstructed.returnValue(notConstructed.invokeVirtualMethod(forwardMethod, notConstructed.getThis(), params)); + } + + ResultHandle decoratorHandle = null; + if (decorator != null) { + decoratorHandle = interceptedMethod.readInstanceField(FieldDescriptor.of(subclass.getClassName(), + decorator.getIdentifier(), Object.class.getName()), interceptedMethod.getThis()); } // Forwarding function // Function forward = ctx -> super.foo((java.lang.String)ctx.getParameters()[0]) - FunctionCreator func = forwardMethod.createFunction(Function.class); + FunctionCreator func = interceptedMethod.createFunction(Function.class); BytecodeCreator funcBytecode = func.getBytecode(); ResultHandle ctxHandle = funcBytecode.getMethodParam(0); ResultHandle[] superParamHandles = new ResultHandle[method.parameters().size()]; @@ -374,17 +704,34 @@ private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, Meth for (int i = 0; i < superParamHandles.length; i++) { superParamHandles[i] = funcBytecode.readArrayValue(ctxParamsHandle, i); } - ResultHandle superResult = funcBytecode.invokeSpecialMethod( - MethodDescriptor.ofMethod(providerTypeName, method.name(), method.returnType().name().toString(), - method.parameters().stream().map(p -> p.name().toString()).collect(Collectors.toList()) - .toArray(new String[0])), - forwardMethod.getThis(), superParamHandles); - funcBytecode.returnValue(superResult != null ? superResult : funcBytecode.loadNull()); + + // If a decorator is bound then invoke the method upon the decorator instance instead of the generated forwarding method + if (decorator != 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()); + declaringClass = generatedNameFromTarget(targetPackage, baseName, DecoratorGenerator.ABSTRACT_IMPL_SUFFIX); + } + MethodDescriptor methodDescriptor = MethodDescriptor.ofMethod( + declaringClass, originalMethodDescriptor.getName(), + originalMethodDescriptor.getReturnType(), originalMethodDescriptor.getParameterTypes()); + funcBytecode + .returnValue(funcBytecode.invokeVirtualMethod(methodDescriptor, funDecoratorInstance, superParamHandles)); + + } else { + ResultHandle superResult = funcBytecode.invokeVirtualMethod(forwardMethod, interceptedMethod.getThis(), + superParamHandles); + funcBytecode.returnValue(superResult != null ? superResult : funcBytecode.loadNull()); + } + for (Type declaredException : method.exceptions()) { - forwardMethod.addException(declaredException.name().toString()); + interceptedMethod.addException(declaredException.name().toString()); } - TryBlock tryCatch = forwardMethod.tryBlock(); + TryBlock tryCatch = interceptedMethod.tryBlock(); // catch exceptions declared on the original method boolean addCatchRuntimeException = true; boolean addCatchException = true; @@ -422,7 +769,7 @@ private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, Meth tryCatch.readInstanceField(FIELD_METADATA_METHOD, methodMetadataHandle), func.getInstance(), paramsHandle, tryCatch.readInstanceField(FIELD_METADATA_CHAIN, methodMetadataHandle), tryCatch.readInstanceField(FIELD_METADATA_BINDINGS, methodMetadataHandle)); - tryCatch.returnValue(superResult != null ? ret : null); + tryCatch.returnValue(ret); } /** diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java index b7c76850928236..fb631e96379fd1 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java @@ -9,6 +9,7 @@ import io.quarkus.gizmo.BytecodeCreator; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -252,6 +253,39 @@ static Set getClassBeanTypeClosure(ClassInfo classInfo, BeanDeployment bea return restrictBeanTypes(types, beanDeployment.getAnnotations(classInfo)); } + static List getResolvedParameters(ClassInfo classInfo, MethodInfo method, IndexView index) { + return getResolvedParameters(classInfo, Collections.emptyMap(), method, index); + } + + static List getResolvedParameters(ClassInfo classInfo, Map resolvedMap, + MethodInfo method, IndexView index) { + List typeParameters = classInfo.typeParameters(); + // E.g. Foo, T, List + List parameters = method.parameters(); + if (typeParameters.isEmpty()) { + return parameters; + } else { + resolvedMap = buildResolvedMap(typeParameters, typeParameters, + resolvedMap, index); + List resolved = new ArrayList<>(); + for (Type param : parameters) { + switch (param.kind()) { + case ARRAY: + case PRIMITIVE: + case CLASS: + resolved.add(param); + break; + case TYPE_VARIABLE: + case PARAMETERIZED_TYPE: + resolved.add(resolveTypeParam(param, resolvedMap, index)); + default: + break; + } + } + return resolved; + } + } + static Set getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFieldOrMethod, Map resolvedTypeParameters, BeanDeployment beanDeployment, BiConsumer> resolvedTypeVariablesConsumer) { @@ -322,6 +356,32 @@ static Set getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFi return types; } + static Set getDelegateTypeClosure(InjectionPointInfo delegateInjectionPoint, BeanDeployment beanDeployment) { + Set types; + Type delegateType = delegateInjectionPoint.getRequiredType(); + if (delegateType.kind() == Kind.TYPE_VARIABLE + || delegateType.kind() == Kind.PRIMITIVE + || delegateType.kind() == Kind.ARRAY) { + throw new DefinitionException("Illegal delegate type declared:" + delegateInjectionPoint.getTargetInfo()); + } + ClassInfo delegateTypeClass = getClassByName(beanDeployment.getBeanArchiveIndex(), delegateType); + if (delegateTypeClass == null) { + throw new IllegalArgumentException("Delegate type not found in index: " + delegateType); + } + if (Kind.CLASS.equals(delegateType.kind())) { + types = getTypeClosure(delegateTypeClass, delegateInjectionPoint.getTarget(), Collections.emptyMap(), + beanDeployment, null); + } else if (Kind.PARAMETERIZED_TYPE.equals(delegateType.kind())) { + types = getTypeClosure(delegateTypeClass, delegateInjectionPoint.getTarget(), + buildResolvedMap(delegateType.asParameterizedType().arguments(), delegateTypeClass.typeParameters(), + Collections.emptyMap(), beanDeployment.getBeanArchiveIndex()), + beanDeployment, null); + } else { + throw new IllegalArgumentException("Unsupported return type"); + } + return types; + } + static Map> resolvedTypeVariables(ClassInfo classInfo, BeanDeployment beanDeployment) { Map> resolvedTypeVariables = new HashMap<>(); @@ -427,4 +487,17 @@ static boolean isPrimitiveClassName(String className) { return PRIMITIVE_CLASS_NAMES.contains(className); } + static boolean containsTypeVariable(Type type) { + if (type.kind() == Kind.TYPE_VARIABLE) { + return true; + } else if (type.kind() == Kind.PARAMETERIZED_TYPE) { + for (Type arg : type.asParameterizedType().arguments()) { + if (containsTypeVariable(arg)) { + return true; + } + } + } + return false; + } + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java index 82056176b17b23..3279be0fa28601 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java @@ -120,7 +120,8 @@ enum Kind { PRODUCER_FIELD, PRODUCER_METHOD, SYNTHETIC, - INTERCEPTOR; + INTERCEPTOR, + DECORATOR; public static Kind from(String value) { for (Kind kind : values()) { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableDecorator.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableDecorator.java new file mode 100644 index 00000000000000..fd7b2d0662135a --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableDecorator.java @@ -0,0 +1,17 @@ +package io.quarkus.arc; + +import javax.enterprise.inject.spi.Decorator; +import javax.enterprise.inject.spi.Prioritized; + +/** + * Quarkus representation of a decorator bean. + * This interface extends the standard CDI {@link Decorator} interface. + */ +public interface InjectableDecorator extends InjectableBean, Decorator, Prioritized { + + @Override + default Kind getKind() { + return Kind.DECORATOR; + } + +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInterceptor.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInterceptor.java index 8b17f5b850604c..35b374b8fdd3fa 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInterceptor.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInterceptor.java @@ -7,8 +7,6 @@ * Quarkus representation of an interceptor bean. * This interface extends the standard CDI {@link Interceptor} interface. * - * @author Martin Kouba - * * @param */ public interface InjectableInterceptor extends InjectableBean, Interceptor, Prioritized { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Subclass.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Subclass.java index 453e7a29510619..a2e6abab7d84d2 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Subclass.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Subclass.java @@ -1,7 +1,7 @@ package io.quarkus.arc; /** - * A marker interface that represents an intercepted subclass. + * A marker interface that represents an intercepted/decorated subclass. * * @author Martin Kouba */ diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/DecoratorDelegateProvider.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/DecoratorDelegateProvider.java new file mode 100644 index 00000000000000..4c4a851581a4ec --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/DecoratorDelegateProvider.java @@ -0,0 +1,44 @@ +package io.quarkus.arc.impl; + +import io.quarkus.arc.InjectableReferenceProvider; +import javax.enterprise.context.spi.CreationalContext; + +public class DecoratorDelegateProvider implements InjectableReferenceProvider { + + private static final ThreadLocal CURRENT = new ThreadLocal<>(); + + @Override + public Object get(CreationalContext creationalContext) { + return CURRENT.get(); + } + + /** + * Set the current delegate for a non-null parameter, remove the threadlocal for null parameter. + * + * @param delegate + * @return the previous delegate or {@code null} + */ + public static Object set(Object delegate) { + if (delegate != null) { + Object prev = CURRENT.get(); + if (delegate.equals(prev)) { + return delegate; + } else { + CURRENT.set(delegate); + return prev; + } + } else { + CURRENT.remove(); + return null; + } + } + + public static void unset() { + set(null); + } + + public static Object get() { + return CURRENT.get(); + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/SimpleDecoratorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/SimpleDecoratorTest.java new file mode 100644 index 00000000000000..ccdf75e21ed97e --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/SimpleDecoratorTest.java @@ -0,0 +1,78 @@ +package io.quarkus.arc.test.decorators; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; +import javax.annotation.Priority; +import javax.decorator.Decorator; +import javax.decorator.Delegate; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class SimpleDecoratorTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Converter.class, ToUpperCaseConverter.class, + TrimConverterDecorator.class, UnusedConverterDecorator.class); + + @Test + public void testDecoration() { + ToUpperCaseConverter converter = Arc.container().instance(ToUpperCaseConverter.class).get(); + assertEquals("HOLA!", converter.convert(" holA!")); + assertEquals(" HOLA!", converter.convertNoDelegation(" holA!")); + } + + interface Converter { + + T convert(T value); + + } + + @ApplicationScoped + static class ToUpperCaseConverter implements Converter { + + @Override + public String convert(String value) { + return value.toUpperCase(); + } + + public String convertNoDelegation(String value) { + return value.toUpperCase(); + } + + } + + @Priority(1) + @Decorator + static class TrimConverterDecorator implements Converter { + + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(String value) { + return delegate.convert(value.trim()); + } + + } + + @Priority(2) + @Decorator + static class UnusedConverterDecorator implements Converter { + + @Inject + @Delegate + Converter delegate; + + @Override + public Integer convert(Integer value) { + return 0; + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/abstractimpl/AbstractDecoratorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/abstractimpl/AbstractDecoratorTest.java new file mode 100644 index 00000000000000..16ce5bd1c4db26 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/abstractimpl/AbstractDecoratorTest.java @@ -0,0 +1,66 @@ +package io.quarkus.arc.test.decorators.abstractimpl; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; +import javax.annotation.Priority; +import javax.decorator.Decorator; +import javax.decorator.Delegate; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class AbstractDecoratorTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Converter.class, ToUpperCaseConverter.class, + TrimConverterDecorator.class); + + @Test + public void testDecoration() { + ToUpperCaseConverter converter = Arc.container().instance(ToUpperCaseConverter.class).get(); + assertEquals("HELLO", converter.convert(" hello ")); + assertEquals(ToUpperCaseConverter.class.getName(), converter.getId()); + } + + interface Converter { + + T convert(T value); + + U getId(); + + } + + @ApplicationScoped + static class ToUpperCaseConverter implements Converter { + + @Override + public String convert(String value) { + return value.toUpperCase(); + } + + @Override + public String getId() { + return ToUpperCaseConverter.class.getName(); + } + + } + + @Priority(1) + @Decorator + static abstract class TrimConverterDecorator implements Converter { + + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(String value) { + return delegate.convert(value.trim()); + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/abstractimpl/InterceptorAndAbstractDecoratorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/abstractimpl/InterceptorAndAbstractDecoratorTest.java new file mode 100644 index 00000000000000..1ce3ff0bac5dcf --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/abstractimpl/InterceptorAndAbstractDecoratorTest.java @@ -0,0 +1,104 @@ +package io.quarkus.arc.test.decorators.abstractimpl; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Priority; +import javax.decorator.Decorator; +import javax.decorator.Delegate; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InterceptorBinding; +import javax.interceptor.InvocationContext; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class InterceptorAndAbstractDecoratorTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Converter.class, ToUpperCaseConverter.class, + TrimConverterDecorator.class, LoggingInterceptor.class, Logging.class); + + @Test + public void testInterceptionAndDecoration() { + LoggingInterceptor.LOG.set(null); + ToUpperCaseConverter converter = Arc.container().instance(ToUpperCaseConverter.class).get(); + assertEquals("HELLO", converter.convert(" hello ")); + assertEquals("HELLO", LoggingInterceptor.LOG.get()); + assertEquals(ToUpperCaseConverter.class.getName(), converter.getId()); + assertEquals(ToUpperCaseConverter.class.getName(), LoggingInterceptor.LOG.get()); + } + + interface Converter { + + T convert(T value); + + U getId(); + + } + + @Logging + @ApplicationScoped + static class ToUpperCaseConverter implements Converter { + + @Override + public String convert(String value) { + return value.toUpperCase(); + } + + @Override + public String getId() { + return ToUpperCaseConverter.class.getName(); + } + + } + + @Priority(1) + @Decorator + static abstract class TrimConverterDecorator implements Converter { + + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(String value) { + return delegate.convert(value.trim()); + } + + } + + @Target({ TYPE, METHOD }) + @Retention(RUNTIME) + @Documented + @InterceptorBinding + public @interface Logging { + + } + + @Logging + @Priority(10) + @Interceptor + static class LoggingInterceptor { + + static final AtomicReference LOG = new AtomicReference(); + + @AroundInvoke + Object log(InvocationContext ctx) throws Exception { + Object ret = ctx.proceed(); + LOG.set(ret); + return ret; + } + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/generics/GenericsDecoratorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/generics/GenericsDecoratorTest.java new file mode 100644 index 00000000000000..adaa50d2e494bf --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/generics/GenericsDecoratorTest.java @@ -0,0 +1,74 @@ +package io.quarkus.arc.test.decorators.generics; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; +import java.util.List; +import javax.annotation.Priority; +import javax.decorator.Decorator; +import javax.decorator.Delegate; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class GenericsDecoratorTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Converter.class, ToUpperCaseConverter.class, + TrimConverterDecorator.class); + + @Test + public void testDecoration() { + ToUpperCaseConverter converter = Arc.container().instance(ToUpperCaseConverter.class).get(); + assertEquals("HELLO", converter.convert(singletonList(singletonList(" hello ")))); + assertEquals(3, converter.ping(1l)); + } + + interface Converter { + + T convert(List> value); + + R ping(R value); + + } + + @ApplicationScoped + static class ToUpperCaseConverter implements Converter { + + @Override + public String convert(List> value) { + return value.get(0).get(0).toUpperCase(); + } + + @Override + public Long ping(Long value) { + return value + 1; + } + + } + + @Priority(1) + @Decorator + static class TrimConverterDecorator implements Converter { + + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(List> value) { + value = singletonList(singletonList(value.get(0).get(0).trim())); + return delegate.convert(value); + } + + @Override + public Long ping(Long value) { + return delegate.ping(value) + 1; + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/interceptor/InterceptorAndDecoratorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/interceptor/InterceptorAndDecoratorTest.java new file mode 100644 index 00000000000000..f51e0fa8e4c7f1 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/interceptor/InterceptorAndDecoratorTest.java @@ -0,0 +1,101 @@ +package io.quarkus.arc.test.decorators.interceptor; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Priority; +import javax.decorator.Decorator; +import javax.decorator.Delegate; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InterceptorBinding; +import javax.interceptor.InvocationContext; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class InterceptorAndDecoratorTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Converter.class, ToUpperCaseConverter.class, + TrimConverterDecorator.class, LoggingInterceptor.class, Logging.class); + + @Test + public void testInterceptionAndDecoration() { + LoggingInterceptor.LOG.set(null); + ToUpperCaseConverter converter = Arc.container().instance(ToUpperCaseConverter.class).get(); + assertEquals("HOLA!", converter.convert(" holA!")); + assertEquals("HOLA!", LoggingInterceptor.LOG.get()); + assertEquals(" HOLA!", converter.convertNoDelegation(" holA!")); + assertEquals(" HOLA!", LoggingInterceptor.LOG.get()); + } + + interface Converter { + + T convert(T value); + + } + + @Logging + @ApplicationScoped + static class ToUpperCaseConverter implements Converter { + + @Override + public String convert(String value) { + return value.toUpperCase(); + } + + public String convertNoDelegation(String value) { + return value.toUpperCase(); + } + + } + + @Priority(1) + @Decorator + static class TrimConverterDecorator implements Converter { + + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(String value) { + return delegate.convert(value.trim()); + } + + } + + @Target({ TYPE, METHOD }) + @Retention(RUNTIME) + @Documented + @InterceptorBinding + public @interface Logging { + + } + + @Logging + @Priority(10) + @Interceptor + static class LoggingInterceptor { + + static final AtomicReference LOG = new AtomicReference(); + + @AroundInvoke + Object log(InvocationContext ctx) throws Exception { + Object ret = ctx.proceed(); + LOG.set(ret); + return ret; + } + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/priority/AlphaConverterDecorator.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/priority/AlphaConverterDecorator.java new file mode 100644 index 00000000000000..0f67ceafe3836a --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/priority/AlphaConverterDecorator.java @@ -0,0 +1,23 @@ +package io.quarkus.arc.test.decorators.priority; + +import io.quarkus.arc.test.decorators.priority.MultipleDecoratorsTest.Converter; +import javax.annotation.Priority; +import javax.decorator.Decorator; +import javax.decorator.Delegate; +import javax.inject.Inject; + +@Priority(20) +@Decorator +class AlphaConverterDecorator implements Converter { + + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(String value) { + // skip first char + return value.substring(1); + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/priority/BravoConverterDecorator.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/priority/BravoConverterDecorator.java new file mode 100644 index 00000000000000..e76d306bb679c5 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/priority/BravoConverterDecorator.java @@ -0,0 +1,25 @@ +package io.quarkus.arc.test.decorators.priority; + +import io.quarkus.arc.test.decorators.priority.MultipleDecoratorsTest.Converter; +import javax.annotation.Priority; +import javax.decorator.Decorator; +import javax.decorator.Delegate; +import javax.inject.Inject; + +@Priority(2) +@Decorator +class BravoConverterDecorator implements Converter { + + Converter delegate; + + @Inject + public BravoConverterDecorator(@Delegate Converter delegate) { + this.delegate = delegate; + } + + @Override + public String convert(String value) { + return new StringBuilder(delegate.convert(value)).reverse().toString(); + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/priority/MultipleDecoratorsTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/priority/MultipleDecoratorsTest.java new file mode 100644 index 00000000000000..b0d47d8864b297 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/priority/MultipleDecoratorsTest.java @@ -0,0 +1,39 @@ +package io.quarkus.arc.test.decorators.priority; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; +import javax.enterprise.context.ApplicationScoped; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class MultipleDecoratorsTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Converter.class, ToUpperCaseConverter.class, + AlphaConverterDecorator.class, BravoConverterDecorator.class); + + @Test + public void testDecoration() { + ToUpperCaseConverter converter = Arc.container().instance(ToUpperCaseConverter.class).get(); + assertEquals("je", converter.convert("hej")); + } + + interface Converter { + + T convert(T value); + + } + + @ApplicationScoped + static class ToUpperCaseConverter implements Converter { + + @Override + public String convert(String value) { + return value.toUpperCase(); + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/AbstractDecoratorWithAbstractMethodTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/AbstractDecoratorWithAbstractMethodTest.java new file mode 100644 index 00000000000000..5a87e389de51c9 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/AbstractDecoratorWithAbstractMethodTest.java @@ -0,0 +1,52 @@ +package io.quarkus.arc.test.decorators.validation; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.quarkus.arc.test.ArcTestContainer; +import javax.annotation.Priority; +import javax.decorator.Decorator; +import javax.decorator.Delegate; +import javax.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class AbstractDecoratorWithAbstractMethodTest { + + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Converter.class, DecoratorWithAbstractMethod.class).shouldFail().build(); + + @Test + public void testFailure() { + assertNotNull(container.getFailure()); + assertTrue( + container.getFailure().getMessage().contains("declares abstract methods:"), + container.getFailure().getMessage()); + } + + interface Converter { + + T convert(T value); + + } + + @Priority(1) + @Decorator + static abstract class DecoratorWithAbstractMethod implements Converter { + + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(String value) { + return delegate.convert(value.trim()); + } + + // this method is not legal + abstract String anotherConvert(); + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/DelegateDoesNotImplementDecoratedTypeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/DelegateDoesNotImplementDecoratedTypeTest.java new file mode 100644 index 00000000000000..13d41900fea4b4 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/DelegateDoesNotImplementDecoratedTypeTest.java @@ -0,0 +1,55 @@ +package io.quarkus.arc.test.decorators.validation; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.quarkus.arc.test.ArcTestContainer; +import javax.annotation.Priority; +import javax.decorator.Decorator; +import javax.decorator.Delegate; +import javax.enterprise.context.Dependent; +import javax.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class DelegateDoesNotImplementDecoratedTypeTest { + + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Converter.class, TrimConverterDecorator.class, MyBean.class).shouldFail().build(); + + @Test + public void testFailure() { + assertNotNull(container.getFailure()); + assertTrue( + container.getFailure().getMessage().contains("MyBean does not implement the decorated type"), + container.getFailure().getMessage()); + } + + interface Converter { + + T convert(T value); + + } + + @Dependent + static class MyBean { + + } + + @Priority(1) + @Decorator + static class TrimConverterDecorator implements Converter { + + @Inject + @Delegate + MyBean delegate; + + @Override + public String convert(String value) { + return null; + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/MultipleDelegateInjectionPointsTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/MultipleDelegateInjectionPointsTest.java new file mode 100644 index 00000000000000..044c37dea7b959 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/MultipleDelegateInjectionPointsTest.java @@ -0,0 +1,54 @@ +package io.quarkus.arc.test.decorators.validation; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.quarkus.arc.test.ArcTestContainer; +import javax.annotation.Priority; +import javax.decorator.Decorator; +import javax.decorator.Delegate; +import javax.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class MultipleDelegateInjectionPointsTest { + + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Converter.class, DecoratorWithMultipleDelegateInjetionPoints.class).shouldFail().build(); + + @Test + public void testFailure() { + assertNotNull(container.getFailure()); + assertTrue( + container.getFailure().getMessage() + .contains("DecoratorWithMultipleDelegateInjetionPoints has multiple @Delegate injection points"), + container.getFailure().getMessage()); + } + + interface Converter { + + T convert(T value); + + } + + @Priority(1) + @Decorator + static class DecoratorWithMultipleDelegateInjetionPoints implements Converter { + + @Inject + @Delegate + Converter delegate1; + + @Inject + @Delegate + Converter delegate2; + + @Override + public String convert(String value) { + return null; + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/NoDecoratedTypeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/NoDecoratedTypeTest.java new file mode 100644 index 00000000000000..78d7c3b0a02390 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/NoDecoratedTypeTest.java @@ -0,0 +1,48 @@ +package io.quarkus.arc.test.decorators.validation; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.quarkus.arc.test.ArcTestContainer; +import javax.annotation.Priority; +import javax.decorator.Decorator; +import javax.decorator.Delegate; +import javax.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class NoDecoratedTypeTest { + + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Converter.class, DecoratorWithNoDecoratedType.class).shouldFail().build(); + + @Test + public void testFailure() { + assertNotNull(container.getFailure()); + assertTrue( + container.getFailure().getMessage().contains("DecoratorWithNoDecoratedType has no decorated type"), + container.getFailure().getMessage()); + } + + interface Converter { + + T convert(T value); + + } + + @Priority(1) + @Decorator + static class DecoratorWithNoDecoratedType { + + @Inject + @Delegate + Converter delegate; + + public String convert(String value) { + return null; + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/NoDelegateInjectionPointTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/NoDelegateInjectionPointTest.java new file mode 100644 index 00000000000000..70f475cea1ef5e --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/validation/NoDelegateInjectionPointTest.java @@ -0,0 +1,44 @@ +package io.quarkus.arc.test.decorators.validation; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.quarkus.arc.test.ArcTestContainer; +import javax.annotation.Priority; +import javax.decorator.Decorator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class NoDelegateInjectionPointTest { + + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Converter.class, DecoratorWithNoDelegateInjectionPoint.class).shouldFail().build(); + + @Test + public void testFailure() { + assertNotNull(container.getFailure()); + assertTrue( + container.getFailure().getMessage() + .contains("DecoratorWithNoDelegateInjectionPoint has no @Delegate injection point"), + container.getFailure().getMessage()); + } + + interface Converter { + + T convert(T value); + + } + + @Priority(1) + @Decorator + static class DecoratorWithNoDelegateInjectionPoint implements Converter { + + @Override + public String convert(String value) { + return null; + } + + } + +}