Skip to content

Commit

Permalink
ArC: add support for static disposer methods
Browse files Browse the repository at this point in the history
This commit also fixes a NPE in case of static producer method
declared on a `@Dependent` bean.
  • Loading branch information
Ladicek committed Jan 30, 2023
1 parent a5e4cae commit ae91381
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide
// Invoke the disposer method
// declaringProvider.get(new CreationalContextImpl<>()).dispose()
MethodInfo disposerMethod = bean.getDisposer().getDisposerMethod();
boolean isStatic = Modifier.isStatic(disposerMethod.flags());

ResultHandle declaringProviderSupplierHandle = destroy.readInstanceField(
FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER_SUPPLIER,
Expand All @@ -855,15 +856,21 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide
MethodDescriptors.SUPPLIER_GET, declaringProviderSupplierHandle);
ResultHandle ctxHandle = destroy.newInstance(
MethodDescriptor.ofConstructor(CreationalContextImpl.class, Contextual.class), destroy.loadNull());
ResultHandle declaringProviderInstanceHandle = destroy.invokeInterfaceMethod(
MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle,
ctxHandle);

if (bean.getDeclaringBean().getScope().isNormal()) {
// We need to unwrap the client proxy
ResultHandle declaringProviderInstanceHandle;
if (isStatic) {
// for static disposers, we don't need to resolve this handle
// the `null` will only be used for reflective invocation in case the disposer is private, which is OK
declaringProviderInstanceHandle = destroy.loadNull();
} else {
declaringProviderInstanceHandle = destroy.invokeInterfaceMethod(
MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE,
declaringProviderInstanceHandle);
MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle,
ctxHandle);
if (bean.getDeclaringBean().getScope().isNormal()) {
// We need to unwrap the client proxy
declaringProviderInstanceHandle = destroy.invokeInterfaceMethod(
MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE,
declaringProviderInstanceHandle);
}
}

ResultHandle[] referenceHandles = new ResultHandle[disposerMethod.parametersCount()];
Expand Down Expand Up @@ -904,6 +911,8 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide
destroy.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD,
destroy.loadClass(disposerMethod.declaringClass().name().toString()),
destroy.load(disposerMethod.name()), paramTypesArray, declaringProviderInstanceHandle, argsArray);
} else if (isStatic) {
destroy.invokeStaticMethod(MethodDescriptor.of(disposerMethod), referenceHandles);
} else {
destroy.invokeVirtualMethod(MethodDescriptor.of(disposerMethod), declaringProviderInstanceHandle,
referenceHandles);
Expand All @@ -912,8 +921,8 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide
// Destroy @Dependent instances injected into method parameters of a disposer method
destroy.invokeInterfaceMethod(MethodDescriptors.CREATIONAL_CTX_RELEASE, ctxHandle);

// If the declaring bean is @Dependent we must destroy the instance afterwards
if (BuiltinScope.DEPENDENT.is(bean.getDisposer().getDeclaringBean().getScope())) {
// If the declaring bean is @Dependent and the disposer is not static, we must destroy the instance afterwards
if (BuiltinScope.DEPENDENT.is(bean.getDisposer().getDeclaringBean().getScope()) && !isStatic) {
destroy.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle,
declaringProviderInstanceHandle, ctxHandle);
}
Expand All @@ -927,7 +936,7 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide

// Bridge method needed
MethodCreator bridgeDestroy = beanCreator.getMethodCreator("destroy", void.class, Object.class, CreationalContext.class)
.setModifiers(ACC_PUBLIC);
.setModifiers(ACC_PUBLIC | ACC_BRIDGE);
bridgeDestroy.returnValue(bridgeDestroy.invokeVirtualMethod(destroy.getMethodDescriptor(), bridgeDestroy.getThis(),
bridgeDestroy.getMethodParam(0),
bridgeDestroy.getMethodParam(1)));
Expand Down Expand Up @@ -1241,6 +1250,8 @@ void implementCreateForProducerMethod(ClassOutput classOutput, ClassCreator bean
AssignableResultHandle instanceHandle;

MethodInfo producerMethod = bean.getTarget().get().asMethod();
boolean isStatic = Modifier.isStatic(producerMethod.flags());

instanceHandle = create.createVariable(DescriptorUtils.extToInt(providerType.className()));
// instance = declaringProviderSupplier.get().get(new CreationalContextImpl<>()).produce()
ResultHandle ctxHandle = create.newInstance(
Expand All @@ -1252,8 +1263,9 @@ void implementCreateForProducerMethod(ClassOutput classOutput, ClassCreator bean
create.getThis());
ResultHandle declaringProviderHandle = create.invokeInterfaceMethod(
MethodDescriptors.SUPPLIER_GET, declaringProviderSupplierHandle);
if (Modifier.isStatic(producerMethod.flags())) {
// for static producers, we don't need to resolve this this handle
if (isStatic) {
// for static producers, we don't need to resolve this handle
// the `null` will only be used for reflective invocation in case the producer is private, which is OK
declaringProviderInstanceHandle = create.loadNull();
} else {
declaringProviderInstanceHandle = create.invokeInterfaceMethod(
Expand Down Expand Up @@ -1309,7 +1321,7 @@ void implementCreateForProducerMethod(ClassOutput classOutput, ClassCreator bean
argsArray));
} else {
ResultHandle invokeMethodHandle;
if (Modifier.isStatic(producerMethod.flags())) {
if (isStatic) {
invokeMethodHandle = create.invokeStaticMethod(MethodDescriptor.of(producerMethod),
referenceHandles);
} else {
Expand All @@ -1326,8 +1338,8 @@ void implementCreateForProducerMethod(ClassOutput classOutput, ClassCreator bean
bean.getTarget().get().asMethod().name() + "()");
}

// If the declaring bean is @Dependent we must destroy the instance afterwards
if (BuiltinScope.DEPENDENT.is(bean.getDeclaringBean().getScope())) {
// If the declaring bean is @Dependent and the producer is not static, we must destroy the instance afterwards
if (BuiltinScope.DEPENDENT.is(bean.getDeclaringBean().getScope()) && !isStatic) {
create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle,
declaringProviderInstanceHandle, ctxHandle);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package io.quarkus.arc.test.producer.disposer;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

import java.math.BigDecimal;
import java.math.BigInteger;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.arc.Arc;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.arc.test.ArcTestContainer;

public class StaticDisposerTest {
@RegisterExtension
ArcTestContainer container = new ArcTestContainer(Producers.class, Dependency.class);

@Test
public void testProducersDisposers() {
assertState(0, 0, false, false, 0, 0);

InstanceHandle<BigInteger> intHandle = Arc.container().instance(BigInteger.class);
assertEquals(1, intHandle.get().intValue()); // instance producer
assertState(1, 1, false, false, 1, 0);
intHandle.destroy(); // static disposer
assertState(1, 1, false, true, 2, 2);

InstanceHandle<BigDecimal> decHandle = Arc.container().instance(BigDecimal.class);
assertEquals(2, decHandle.get().intValue()); // static producer
assertState(1, 1, false, true, 3, 2);
decHandle.destroy(); // instance disposer
assertState(2, 2, true, true, 4, 4);
}

private void assertState(int expectedInstancesCreated, int expectedInstancesDestroyed,
boolean expectedInstanceDisposerCalled, boolean expectedStaticDisposerCalled,
int expectedDependencyCreated, int expectedDependencyDestroyed) {
assertEquals(expectedInstancesCreated, Producers.instancesCreated);
assertEquals(expectedInstancesDestroyed, Producers.instancesDestroyed);
assertEquals(expectedInstanceDisposerCalled, Producers.instanceDisposerCalled);
assertEquals(expectedStaticDisposerCalled, Producers.staticDisposerCalled);
assertEquals(expectedDependencyCreated, Dependency.created);
assertEquals(expectedDependencyDestroyed, Dependency.destroyed);
}

@Dependent
static class Producers {
static int instancesCreated = 0;
static int instancesDestroyed = 0;

static boolean instanceDisposerCalled = false;
static boolean staticDisposerCalled = false;

@PostConstruct
void created() {
instancesCreated++;
}

@PreDestroy
void destroy() {
instancesDestroyed++;
}

@Produces
@Dependent
BigInteger instanceProducer(Dependency ignored) {
return new BigInteger("1");
}

@Produces
@Dependent
static BigDecimal staticProducer(Dependency ignored) {
return new BigDecimal("2.0");
}

void instanceDisposer(@Disposes BigDecimal disposed, Dependency ignored) {
assertFalse(instanceDisposerCalled);
assertEquals(2, disposed.intValue());
instanceDisposerCalled = true;
}

static void staticDisposer(@Disposes BigInteger disposed, Dependency ignored) {
assertFalse(staticDisposerCalled);
assertEquals(1, disposed.intValue());
staticDisposerCalled = true;
}
}

@Dependent
static class Dependency {
static int created = 0;
static int destroyed = 0;

@PostConstruct
void created() {
created++;
}

@PreDestroy
void destroyed() {
destroyed++;
}
}
}

0 comments on commit ae91381

Please sign in to comment.