Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.Set;

import jakarta.enterprise.context.spi.CreationalContext;
Expand All @@ -40,12 +41,23 @@ public abstract class AbstractFacade<T, X> {
protected static Type getFacadeType(InjectionPoint injectionPoint) {
Type genericType = injectionPoint.getType();
if (genericType instanceof ParameterizedType) {
return ((ParameterizedType) genericType).getActualTypeArguments()[0];
Type typeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
if (typeArgument instanceof WildcardType) {
return getWildcardBound((WildcardType) typeArgument);
}
return typeArgument;
} else {
throw new IllegalStateException(BeanLogger.LOG.typeParameterMustBeConcrete(injectionPoint));
}
}

private static Type getWildcardBound(WildcardType wildcard) {
if (wildcard.getLowerBounds().length > 0) {
return wildcard.getLowerBounds()[0];
}
return wildcard.getUpperBounds()[0];
}

private final BeanManagerImpl beanManager;
private final InjectionPoint injectionPoint;
// The CreationalContext used to create the facade which was injected.
Expand Down
37 changes: 35 additions & 2 deletions impl/src/main/java/org/jboss/weld/bootstrap/Validator.java
Original file line number Diff line number Diff line change
Expand Up @@ -864,13 +864,46 @@ private static void checkFacadeInjectionPoint(InjectionPoint injectionPoint, Cla
Formats.formatAsStackTraceElement(injectionPoint));
}
if (parameterizedType.getActualTypeArguments()[0] instanceof WildcardType) {
throw ValidatorLogger.LOG.injectionPointHasWildcard(injectionPoint,
Formats.formatAsStackTraceElement(injectionPoint));
WildcardType wildcard = (WildcardType) parameterizedType.getActualTypeArguments()[0];
if (!isAllowedWildcard(wildcard, type)) {
throw ValidatorLogger.LOG.injectionPointHasWildcard(injectionPoint,
Formats.formatAsStackTraceElement(injectionPoint));
}
}
} else if (type.equals(Event.class) && parameterizedType.getRawType().equals(Instance.class)) {
// check for wildcard in Event injected via Instance -> @Inject Instance<Event<?>>
Type instanceTypeArgument = parameterizedType.getActualTypeArguments()[0];
if (instanceTypeArgument instanceof ParameterizedType
&& ((ParameterizedType) instanceTypeArgument).getRawType().equals(Event.class)
&& ((ParameterizedType) instanceTypeArgument).getActualTypeArguments()[0] instanceof WildcardType) {
WildcardType nestedWildcard = (WildcardType) ((ParameterizedType) instanceTypeArgument)
.getActualTypeArguments()[0];
if (!isAllowedWildcard(nestedWildcard, Event.class)) {
throw ValidatorLogger.LOG.injectionPointHasWildcard(injectionPoint,
Formats.formatAsStackTraceElement(injectionPoint));
}
}
}
}
}

/**
* Event is contravariant so {@code Event<? super X>} is allowed.
* Instance is covariant so {@code Instance<? extends X>} is allowed.
* Unbounded wildcards are rejected for both.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it's obvious, but clearly it's not, because you did the exact opposite of what I did :-) I'll have to specify this explicitly.

*/
private static boolean isAllowedWildcard(WildcardType wildcard, Class<?> facadeType) {
if (facadeType.equals(Event.class)) {
return wildcard.getLowerBounds().length > 0;
}
if (facadeType.equals(Instance.class)) {
Type[] upperBounds = wildcard.getUpperBounds();
return wildcard.getLowerBounds().length == 0
&& upperBounds.length > 0 && !Object.class.equals(upperBounds[0]);
}
return false;
}

public static void checkBeanMetadataInjectionPoint(Object bean, InjectionPoint ip, Type expectedTypeArgument) {
if (!(ip.getType() instanceof ParameterizedType)) {
throw ValidatorLogger.LOG.invalidBeanMetadataInjectionPointType(ip.getType(), ip,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.jboss.weld.tests.event.wildcard.contravariant;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Event;
import jakarta.inject.Inject;

@ApplicationScoped
public class BeanWithContravariantEvent {

@Inject
Event<? super LifecycleEvent<?>> lifecycleEvents;

public void fireEvent(LifecycleEvent<?> event) {
lifecycleEvents.fire(event);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.jboss.weld.tests.event.wildcard.contravariant;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Event;
import jakarta.inject.Inject;

@ApplicationScoped
public class BeanWithSimpleContravariantEvent {

@Inject
Event<? super Widget> widgetEvents;

public void fireWidget(Widget widget) {
widgetEvents.fire(widget);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.jboss.weld.tests.event.wildcard.contravariant;

import static org.junit.Assert.assertTrue;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.BeanArchive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.weld.test.util.Utils;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
* Verifies that {@code Event<? super X>} injection points are valid and functional.
* {@code Event} is naturally contravariant — you fire subtypes into it — so a
* lower-bounded wildcard is a legitimate use case.
* <p>
* This reproduces the scenario reported by Gavin King where Jakarta Data injects
* {@code Event<? super LifecycleEvent<?>>}.
*
* @see <a href="https://github.com/jakartaee/cdi/issues/888">CDI #888</a>
*/
@RunWith(Arquillian.class)
public class EventContravariantWildcardTest {

@Deployment
public static Archive<?> getDeployment() {
return ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(EventContravariantWildcardTest.class))
.addClasses(BeanWithContravariantEvent.class, LifecycleEvent.class, LifecycleEventObserver.class,
BeanWithSimpleContravariantEvent.class, Widget.class, WidgetObserver.class);
}

@Test
public void testParameterizedContravariantEventWildcard(BeanWithContravariantEvent bean,
LifecycleEventObserver observer) {
bean.fireEvent(new LifecycleEvent<>("test"));
assertTrue("LifecycleEvent should have been observed", observer.isObserved());
}

@Test
public void testSimpleContravariantEventWildcard(BeanWithSimpleContravariantEvent bean,
WidgetObserver observer) {
bean.fireWidget(new Widget("test"));
assertTrue("Widget event should have been observed", observer.isObserved());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.jboss.weld.tests.event.wildcard.contravariant;

public class LifecycleEvent<T> {

private final T payload;

public LifecycleEvent(T payload) {
this.payload = payload;
}

public T getPayload() {
return payload;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.jboss.weld.tests.event.wildcard.contravariant;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;

@ApplicationScoped
public class LifecycleEventObserver {

private boolean observed = false;

public void onLifecycleEvent(@Observes LifecycleEvent<?> event) {
observed = true;
}

public boolean isObserved() {
return observed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.jboss.weld.tests.event.wildcard.contravariant;

public class Widget {

private final String name;

public Widget(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.jboss.weld.tests.event.wildcard.contravariant;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;

@ApplicationScoped
public class WidgetObserver {

private boolean observed = false;

public void onWidget(@Observes Widget event) {
observed = true;
}

public boolean isObserved() {
return observed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.jboss.weld.tests.event.wildcard.covariant;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Event;
import jakarta.inject.Inject;

@ApplicationScoped
public class BeanWithCovariantEvent {

@Inject
Event<? extends Widget> covariantEvent;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.jboss.weld.tests.event.wildcard.covariant;

import jakarta.enterprise.inject.spi.DefinitionException;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.ShouldThrowException;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.BeanArchive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.weld.test.util.Utils;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
* Verifies that {@code Event<? extends X>} injection points are rejected.
* Covariant wildcards on Event are useless because you cannot call
* {@code fire()} on them.
*
* @see <a href="https://github.com/jakartaee/cdi/issues/888">CDI #888</a>
*/
@RunWith(Arquillian.class)
public class EventCovariantWildcardTest {

@Deployment
@ShouldThrowException(DefinitionException.class)
public static Archive<?> getDeployment() {
return ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(EventCovariantWildcardTest.class))
.addClasses(BeanWithCovariantEvent.class, Widget.class);
}

@Test
public void testCovariantEventWildcardRejected() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.jboss.weld.tests.event.wildcard.covariant;

public class Widget {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.jboss.weld.tests.instance.wildcard.contravariant;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;

@ApplicationScoped
public class BeanWithContravariantInstance {

@Inject
Instance<? super Widget> contravariantInstance;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.jboss.weld.tests.instance.wildcard.contravariant;

import jakarta.enterprise.inject.spi.DefinitionException;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.ShouldThrowException;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.BeanArchive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.weld.test.util.Utils;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
* Verifies that {@code Instance<? super X>} injection points are rejected.
* Contravariant wildcards on Instance are useless because Instance is
* naturally covariant.
*
* @see <a href="https://github.com/jakartaee/cdi/issues/888">CDI #888</a>
*/
@RunWith(Arquillian.class)
public class InstanceContravariantWildcardTest {

@Deployment
@ShouldThrowException(DefinitionException.class)
public static Archive<?> getDeployment() {
return ShrinkWrap.create(BeanArchive.class,
Utils.getDeploymentNameAsHash(InstanceContravariantWildcardTest.class))
.addClasses(BeanWithContravariantInstance.class, Widget.class);
}

@Test
public void testContravariantInstanceWildcardRejected() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.jboss.weld.tests.instance.wildcard.contravariant;

public class Widget {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.jboss.weld.tests.instance.wildcard.covariant;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;

@ApplicationScoped
public class BeanWithCovariantInstance {

@Inject
Instance<? extends Widget> covariantInstance;

public boolean isResolvable() {
return covariantInstance.isResolvable();
}

public Widget get() {
return covariantInstance.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.jboss.weld.tests.instance.wildcard.covariant;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.BeanArchive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.weld.test.util.Utils;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
* Verifies that {@code Instance<? extends X>} injection points are valid and
* functional. Instance is naturally covariant so an upper-bounded wildcard is
* a legitimate use case.
*
* @see <a href="https://github.com/jakartaee/cdi/issues/888">CDI #888</a>
*/
@RunWith(Arquillian.class)
public class InstanceCovariantWildcardTest {

@Deployment
public static Archive<?> getDeployment() {
return ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(InstanceCovariantWildcardTest.class))
.addClasses(BeanWithCovariantInstance.class, Widget.class);
}

@Test
public void testCovariantInstanceWildcardDeploys(BeanWithCovariantInstance bean) {
assertTrue("Instance<? extends Widget> should be resolvable", bean.isResolvable());
Widget widget = bean.get();
assertNotNull("Instance.get() should return a Widget", widget);
}
}
Loading
Loading