Skip to content

Commit 05d4be0

Browse files
committed
Allow contravariant wildcards in Event and covariant wildcards in Instance
1 parent 2a4c263 commit 05d4be0

18 files changed

Lines changed: 368 additions & 3 deletions

impl/src/main/java/org/jboss/weld/bean/builtin/AbstractFacade.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.lang.annotation.Annotation;
2121
import java.lang.reflect.ParameterizedType;
2222
import java.lang.reflect.Type;
23+
import java.lang.reflect.WildcardType;
2324
import java.util.Set;
2425

2526
import jakarta.enterprise.context.spi.CreationalContext;
@@ -40,12 +41,23 @@ public abstract class AbstractFacade<T, X> {
4041
protected static Type getFacadeType(InjectionPoint injectionPoint) {
4142
Type genericType = injectionPoint.getType();
4243
if (genericType instanceof ParameterizedType) {
43-
return ((ParameterizedType) genericType).getActualTypeArguments()[0];
44+
Type typeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
45+
if (typeArgument instanceof WildcardType) {
46+
return getWildcardBound((WildcardType) typeArgument);
47+
}
48+
return typeArgument;
4449
} else {
4550
throw new IllegalStateException(BeanLogger.LOG.typeParameterMustBeConcrete(injectionPoint));
4651
}
4752
}
4853

54+
private static Type getWildcardBound(WildcardType wildcard) {
55+
if (wildcard.getLowerBounds().length > 0) {
56+
return wildcard.getLowerBounds()[0];
57+
}
58+
return wildcard.getUpperBounds()[0];
59+
}
60+
4961
private final BeanManagerImpl beanManager;
5062
private final InjectionPoint injectionPoint;
5163
// The CreationalContext used to create the facade which was injected.

impl/src/main/java/org/jboss/weld/bootstrap/Validator.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -864,13 +864,46 @@ private static void checkFacadeInjectionPoint(InjectionPoint injectionPoint, Cla
864864
Formats.formatAsStackTraceElement(injectionPoint));
865865
}
866866
if (parameterizedType.getActualTypeArguments()[0] instanceof WildcardType) {
867-
throw ValidatorLogger.LOG.injectionPointHasWildcard(injectionPoint,
868-
Formats.formatAsStackTraceElement(injectionPoint));
867+
WildcardType wildcard = (WildcardType) parameterizedType.getActualTypeArguments()[0];
868+
if (!isAllowedWildcard(wildcard, type)) {
869+
throw ValidatorLogger.LOG.injectionPointHasWildcard(injectionPoint,
870+
Formats.formatAsStackTraceElement(injectionPoint));
871+
}
872+
}
873+
} else if (type.equals(Event.class) && parameterizedType.getRawType().equals(Instance.class)) {
874+
// check for wildcard in Event injected via Instance -> @Inject Instance<Event<?>>
875+
Type instanceTypeArgument = parameterizedType.getActualTypeArguments()[0];
876+
if (instanceTypeArgument instanceof ParameterizedType
877+
&& ((ParameterizedType) instanceTypeArgument).getRawType().equals(Event.class)
878+
&& ((ParameterizedType) instanceTypeArgument).getActualTypeArguments()[0] instanceof WildcardType) {
879+
WildcardType nestedWildcard = (WildcardType) ((ParameterizedType) instanceTypeArgument)
880+
.getActualTypeArguments()[0];
881+
if (!isAllowedWildcard(nestedWildcard, Event.class)) {
882+
throw ValidatorLogger.LOG.injectionPointHasWildcard(injectionPoint,
883+
Formats.formatAsStackTraceElement(injectionPoint));
884+
}
869885
}
870886
}
871887
}
872888
}
873889

890+
/**
891+
* Event is contravariant so {@code Event<? super X>} is allowed.
892+
* Instance is covariant so {@code Instance<? extends X>} is allowed.
893+
* Unbounded wildcards are rejected for both.
894+
*/
895+
private static boolean isAllowedWildcard(WildcardType wildcard, Class<?> facadeType) {
896+
if (facadeType.equals(Event.class)) {
897+
return wildcard.getLowerBounds().length > 0;
898+
}
899+
if (facadeType.equals(Instance.class)) {
900+
Type[] upperBounds = wildcard.getUpperBounds();
901+
return wildcard.getLowerBounds().length == 0
902+
&& upperBounds.length > 0 && !Object.class.equals(upperBounds[0]);
903+
}
904+
return false;
905+
}
906+
874907
public static void checkBeanMetadataInjectionPoint(Object bean, InjectionPoint ip, Type expectedTypeArgument) {
875908
if (!(ip.getType() instanceof ParameterizedType)) {
876909
throw ValidatorLogger.LOG.invalidBeanMetadataInjectionPointType(ip.getType(), ip,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.jboss.weld.tests.event.wildcard.contravariant;
2+
3+
import jakarta.enterprise.context.ApplicationScoped;
4+
import jakarta.enterprise.event.Event;
5+
import jakarta.inject.Inject;
6+
7+
@ApplicationScoped
8+
public class BeanWithContravariantEvent {
9+
10+
@Inject
11+
Event<? super LifecycleEvent<?>> lifecycleEvents;
12+
13+
public void fireEvent(LifecycleEvent<?> event) {
14+
lifecycleEvents.fire(event);
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.jboss.weld.tests.event.wildcard.contravariant;
2+
3+
import jakarta.enterprise.context.ApplicationScoped;
4+
import jakarta.enterprise.event.Event;
5+
import jakarta.inject.Inject;
6+
7+
@ApplicationScoped
8+
public class BeanWithSimpleContravariantEvent {
9+
10+
@Inject
11+
Event<? super Widget> widgetEvents;
12+
13+
public void fireWidget(Widget widget) {
14+
widgetEvents.fire(widget);
15+
}
16+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.jboss.weld.tests.event.wildcard.contravariant;
2+
3+
import static org.junit.Assert.assertTrue;
4+
5+
import org.jboss.arquillian.container.test.api.Deployment;
6+
import org.jboss.arquillian.junit.Arquillian;
7+
import org.jboss.shrinkwrap.api.Archive;
8+
import org.jboss.shrinkwrap.api.BeanArchive;
9+
import org.jboss.shrinkwrap.api.ShrinkWrap;
10+
import org.jboss.weld.test.util.Utils;
11+
import org.junit.Test;
12+
import org.junit.runner.RunWith;
13+
14+
/**
15+
* Verifies that {@code Event<? super X>} injection points are valid and functional.
16+
* {@code Event} is naturally contravariant — you fire subtypes into it — so a
17+
* lower-bounded wildcard is a legitimate use case.
18+
* <p>
19+
* This reproduces the scenario reported by Gavin King where Jakarta Data injects
20+
* {@code Event<? super LifecycleEvent<?>>}.
21+
*
22+
* @see <a href="https://github.com/jakartaee/cdi/issues/888">CDI #888</a>
23+
*/
24+
@RunWith(Arquillian.class)
25+
public class EventContravariantWildcardTest {
26+
27+
@Deployment
28+
public static Archive<?> getDeployment() {
29+
return ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(EventContravariantWildcardTest.class))
30+
.addClasses(BeanWithContravariantEvent.class, LifecycleEvent.class, LifecycleEventObserver.class,
31+
BeanWithSimpleContravariantEvent.class, Widget.class, WidgetObserver.class);
32+
}
33+
34+
@Test
35+
public void testParameterizedContravariantEventWildcard(BeanWithContravariantEvent bean,
36+
LifecycleEventObserver observer) {
37+
bean.fireEvent(new LifecycleEvent<>("test"));
38+
assertTrue("LifecycleEvent should have been observed", observer.isObserved());
39+
}
40+
41+
@Test
42+
public void testSimpleContravariantEventWildcard(BeanWithSimpleContravariantEvent bean,
43+
WidgetObserver observer) {
44+
bean.fireWidget(new Widget("test"));
45+
assertTrue("Widget event should have been observed", observer.isObserved());
46+
}
47+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.jboss.weld.tests.event.wildcard.contravariant;
2+
3+
public class LifecycleEvent<T> {
4+
5+
private final T payload;
6+
7+
public LifecycleEvent(T payload) {
8+
this.payload = payload;
9+
}
10+
11+
public T getPayload() {
12+
return payload;
13+
}
14+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.jboss.weld.tests.event.wildcard.contravariant;
2+
3+
import jakarta.enterprise.context.ApplicationScoped;
4+
import jakarta.enterprise.event.Observes;
5+
6+
@ApplicationScoped
7+
public class LifecycleEventObserver {
8+
9+
private boolean observed = false;
10+
11+
public void onLifecycleEvent(@Observes LifecycleEvent<?> event) {
12+
observed = true;
13+
}
14+
15+
public boolean isObserved() {
16+
return observed;
17+
}
18+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.jboss.weld.tests.event.wildcard.contravariant;
2+
3+
public class Widget {
4+
5+
private final String name;
6+
7+
public Widget(String name) {
8+
this.name = name;
9+
}
10+
11+
public String getName() {
12+
return name;
13+
}
14+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.jboss.weld.tests.event.wildcard.contravariant;
2+
3+
import jakarta.enterprise.context.ApplicationScoped;
4+
import jakarta.enterprise.event.Observes;
5+
6+
@ApplicationScoped
7+
public class WidgetObserver {
8+
9+
private boolean observed = false;
10+
11+
public void onWidget(@Observes Widget event) {
12+
observed = true;
13+
}
14+
15+
public boolean isObserved() {
16+
return observed;
17+
}
18+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.jboss.weld.tests.event.wildcard.covariant;
2+
3+
import jakarta.enterprise.context.ApplicationScoped;
4+
import jakarta.enterprise.event.Event;
5+
import jakarta.inject.Inject;
6+
7+
@ApplicationScoped
8+
public class BeanWithCovariantEvent {
9+
10+
@Inject
11+
Event<? extends Widget> covariantEvent;
12+
}

0 commit comments

Comments
 (0)