Static Imports Rock

I’m really loving Java 5 static imports. Let’s suppose we have an event service that lets you broadcast “events” (actually any type of object) to arbitrary numbers of subscribers:

public interface EventService {
  <T> void publish(String channel, T event);

  ...other methods
}

The channel is any string, allowing you to filter which subscribers receive the event. You can also filter based on the type of event.

Subscribers merely have to implement this interface:

public interface EventSubscriber<T>{
  void onEvent(T event);
}

How might we add subscribers to the event service?

How Not To Do It

When I started this framework, I envisioned several ways to filter on channel name:

  • Exact name match
  • Match on any channel
  • Regular expression match

And a few ways for receivers to filter events based on their type:

  • Exact type match
  • Subtype match

Just with these small lists, you have six possible combinations. Adding six separate methods to the EventService is a poor solution:

public interface EventService {
  <T> void publish(String channel, T event);

  <T> void subscribeByExactNameAndExactClass(String channel,
        Class<T> eventType, EventSubscriber<T> subscriber);

  ...5 other methods for other combinations
}

Not only do you end up with M * N methods, this approach does not offer any extensibility. What if someone wants to filter events based on some other type of pattern matching algorithm?

A Better Solution

Let’s start by stealing (part of) an interface from Guice:

public interface Matcher<T> {
  boolean matches(T object);
}

Now we can use matchers to subscribe:

<T> EventSubscriber<T> subscribe(Matcher<String> channelMatcher,
           Matcher<Object> eventMatcher,
           EventSubscriber<T> subscriber);

We can now provide many different Matcher implementations, and customers can even write their own Matchers. Instead of six methods, we have one, and it offers far more flexibility.

Static Imports Rock

Using the EventService is now pretty cumbersome, but we can simplify the code using a MatcherMaker class along with static imports. I really like that class name, by the way.

Here is one such method:

public static Matcher<String> anyChannel() {
  ...
}

And here is how event subscription code looks:

eventService.subscribe(anyChannel(), exactType(BaseEvent.class), subscriber);

In that example, the anyChannel() and exactType(...) methods are both static methods on the MatcherMaker class.

Other combinations are possible:

eventService.subscribe(exactChannel("A"),
        exactType(Integer.class), anIntSubscriber);
eventService.subscribe(nullChannel(),
        exactType(ProgressEvent.class), aProgressListener);

So you see, the static imports really clean up the code and make it more readable. (I think so, at least)

Get It

All of this is from my new ClassBus project — I hope to release the version 0.2 ZIP distribution in a few days, along with a more formal announcement. For now, it’s all available in Subversion.


Bob Lee Says:

MatcherMaker… wish I had thought of that. :)

Holger Brands Says:

The Glazed Lists project also has the concept of matcher for filter criteria.

Regarding your ClassBus project:
Do you know the EventBus project on java.net?
How does your project compare to it?

Thanks,
Holger

Eric Burke Says:

ClassBus is very similar to EventBus. I’ll provide more details about this when I finish the online docs, but I did take a lot of inspiration from the EventBus project. In the end, I just felt like writing my own.

Dan Lewis Says:

I agree that static imports can be a good thing. It seems to me that when the feature came out in Java 5, there were some “pitchfork” people upset about it. Hopefully one of them will show up and let us know all the downsides.

BTW I like the Matcher interface versus the combinatorial explosion of methods in the API. And just to clarify, there’s no runtime dependency on Guice, right? Just the coincidence of having the same class name as one that’s in Guice.

Dan Lewis Says:

One other thing…I know you are proud of the name, but would the prevailing convention on class naming have MatcherMaker named Makers instead? e.g. java.util.concurrent.Executors.

Eric Burke Says:

My Matcher is a subset of the Guice Matcher, and strictly a copy — I only needed one method. While I compile ClassBus with Guice annotations, Guice JARs are entirely optional for applications using ClassBus.

Eric Burke Says:

@Dan I think to be consistent with Executors you’d probably want something like Matchers, rather than Makers. MatcherMaker is just too good, though. It even has its own theme song. I can’t see myself changing that… :-)

Dan Lewis Says:

I meant to type Matchers actually. Coffee should be kicking in at any time…

Speaking of Guice, don’t you have to reference Guice API’s to bootstrap the dependency injection? In general how should “third party” libraries handle Guice? As an app developer how should I handle libraries that use Guice? Do I care?

j to Says:

Hi Eric,

there is another project https://somnifugijms.dev.java.net/
that handles jms inside a JVM.

I agree though that exposing an event driven api seems to be better than using jms directly in some cases.