Can you fire CDI 2.0 events of type AbstractClass<? extends Something>?

289 views Asked by At

(For background, I've read You think you know everything about CDI events… Think again!, so I'm at least mostly familiar with many edge cases in CDI events.)

I'm using Weld 3.0.4.Final as an implementation of CDI 2.0.

I have an AbstractFoo<? extends T> instance that I want to fire as a CDI event. We'll call this the payload. This is all that I know about the payload.

T is defined as T extends MetaData. MetaData is an interface.

For various unimportant reasons I have to fire the payload programmatically, so I do this:

final Event<Object> cdiEventMachinery = beanManager.getEvent();
assert cdiEventMachinery != null;
final TypeLiteral<AbstractFoo<? extends T>> eventTypeLiteral = new TypeLiteral<AbstractFoo<? extends T>>() {
  private static final long serialVersionUID = 1L;
};
final Event<AbstractFoo<? extends T>> broadcaster = 
cdiEventMachinery.select(eventTypeLiteral, someQualifiers);
assert broadcaster != null;

Obviously here broadcaster is now set up to fire my payload. My intention is that these events should be delivered to observers looking for AbstractFoo-or-its-subclasses instances parameterized by any type that extends MetaData.

But when I do broadcaster.fire(payload), I've noticed that observer methods like this one:

private final void onFoo(@ObservesAsync @MatchingQualifier final Foo<? extends MetaDataSubclass> event) {}

…do not get called. (The ? extends MetaDataSubclass seems to be the culprit; if the observed parameter is simply, say, Object then obviously the method is notified.)

Specifically, assuming that:

  • a MatchingQualifier literal is present in someQualifiers, and
  • MetaDataSubclass is a class that extends MetaData, and
  • Foo is a class that extends AbstractFoo

…why doesn't that observer method get called?

To be clear, I'm sure this isn't a bug but is something missing in my understanding. I'd like to find out what I'm missing.

(Crossposted to developer.jboss.org.)

Test Case

Here is a test case using simpler constructs.

private final void collectionExtendsNumber(@Observes final Collection<? extends Number> payload) {
  System.out.println("*** collection extends Number");
}

private final void collectionExtendsInteger(@Observes final Collection<? extends Integer> payload) {
  System.out.println("*** collection extends Integer");
}

private final void collectionInteger(@Observes final Collection<Integer> payload) {
  System.out.println("*** collection Integer");
}

private final void collectionNumber(@Observes final Collection<Number> payload) {
  System.out.println("*** collection Number");
}

@Test
public void testContainerStartup() {

  final SeContainerInitializer initializer = SeContainerInitializer.newInstance();
  initializer.disableDiscovery();
  initializer.addBeanClasses(this.getClass());
  try (final SeContainer container = initializer.initialize()) {
    assertNotNull(container);
    final BeanManager beanManager = container.getBeanManager();
    assertNotNull(beanManager);
    final TypeLiteral<Collection<? extends Number>> literal = new TypeLiteral<Collection<? extends Number>>() {
      private static final long serialVersionUID = 1L;

    };
    final Event<Collection<? extends Number>> broadcaster = beanManager.getEvent().select(literal);
    assertNotNull(broadcaster);
    final Collection<? extends Number> payload = Collections.singleton(Integer.valueOf(1));
    broadcaster.fire(payload);
  }
}

Only the first observer method is invoked. I'd like to understand what is preventing the second from being invoked. I understand that an-unknown-type-that-is-a-Number cannot be assignable to an-unknown-type-that-is-a-Integer, but I'd like some way to pick the "right" observer method for the payload.

1

There are 1 answers

0
Siliarus On

This is defined in the spec by 10.3.1. Assignability of type variables, raw and parameterized types. To be more specific, looking at your test case, you are bumping into this spec sentence:

  • the observed event type parameter is an actual type with identical raw type to the event type parameter, and, if the type is parameterized, the event type parameter is assignable to the observed event type parameter according to these rules, or

Event type parameter (? extends Number) is not assignable to observed event type parameter (? extends Integer in your test case). The situation is the same for your third observer (Collection<Integer>).

The last case with Collection<Number> can look a bit puzzling, but it follows the same rule. You cannot assign <? extends Number> to <Number> because in such a case you may be effectively trying to assign Collection<Integer> or Collection<Double> or Collection<Number> (<? extends Number> can be any of these).

There is a nice example of generics on Oracle site. It shows that given two types, Box<A> and Box<B>, you cannot assume on assignability based on the relation of the "inner" types (A and B).