Interface vs annotation driven events

Few days ago I was on a crossroad to design and implement simple notification system for one project. I was decided to go with standard interface-driven setup as common approach in Java since beginning. However after some googling I came accros another very interesting solution, EventBus. It’s a simple, annotation-driven event utility that’s part of Google Guava library.

EventBus allows publish-subscribe-style communication between components without requiring the components to explicitly register with one another (and thus be aware of each other). It is designed exclusively to replace traditional Java in-process event distribution using explicit registration. It is not a general-purpose publish-subscribe system, nor is it intended for interprocess communication.

Interface-driven solution
Before dive into EventBus, let me show you how the typical interface-based notification system could look like. First we need to define a simple immutable object that should be transferred using event from producer to consumers (reused also in EventBus example):

public class Message {

    private final String sender;

    private final String subject;

    private final String text;

    public Message(String sender, String subject, String text) {
        this.sender = sender;
        this.subject = subject;
        this.text = text;
    }

    public String getSender() {
        return sender;
    }

    public String getSubject() {
        return subject;
    }

    public String getText() {
        return text;
    }
}

Than we need to create a listener and event that would carry an object of above Message type. Single listener must exists for each event (used very effective technique proposed by Laurent Simon when listener and event are bound to each other within event class):

public interface MessageRecievedEventListener {
    public void messageRecieved(Message msg);
}
public interface SystemEvent<L> {
	public void notify(L listener);
}
public class MessageRecievedEvent implements SystemEvent<MessageRecievedEventListener> {

    private final Message msg;

    public MessageRecievedEvent(Message msg) {
        this.msg = msg;
    }

    @Override
    public void notify(MessageRecievedEventListener listener) {
        listener.messageRecieved(msg);
    }
}

Now when we have an event and corresponding listener, we can create a consumer of above event:

public class MessageReceivedEventConsumer implements MessageRecievedEventListener {

    private static final Logger LOG = LoggerFactory.getLogger(MessageReceivedEventConsumer.class);

    @Override
    public void messageRecieved(Message msg) {
        LOG.info("messageRecieved(), msg: {}", msg);
    }
}

The only thing missing there is dispatcher. Dispatcher is a component responsible for registering consumers to particular event a firing events. After specific event is fired, all registered consumers will receive the exact event fired. In our implementation dispatcher is a black box since it knows nothing about specific event or listener, it works only with SystemEvent interface.

public class SystemEventBus {

    // ReentrantReadWriteLock could be used if synchronization has proven to be a bottleneck
    @SuppressWarnings("rawtypes")
    private final Multimap<Class, Object> eventBusListeners = Multimaps.synchronizedMultimap(HashMultimap.<Class, Object> create());

    public <L> void registerListener(Class<? extends SystemEvent<L>> eventClass, L listener) {
        eventBusListeners.put(eventClass, listener);
    }

    @SuppressWarnings("unchecked")
    public <L> void fireEvent(SystemEvent<L> event) {
        Collection<L> eventListeners = (Collection<L>) eventBusListeners.get(event.getClass());
        for (L listener : eventListeners) {
            event.notify(listener);
        }
    }
}

EDIT 08/2015: Sources including unit test are now available on GitHub.

Advantages:

  • static typing
  • dispatcher is a black box, it knows nothing about particular event or listener interface

Disadvantages:

  • necessary to create listener interface for each event
  • potential collision of method names in listener interfaces (when subscriber implements multiple listener interfaces)

Annotation-driven solution
On the other side, implementation of preceding example using EventBus is much easier. No specific interfaces are required. Listener has to only define public method, marked by Subscribe annotation with one parameter, the event that wants to capture.

public class MessageRecievedEvent {

    private final Message msg;

    public MessageRecievedEvent(Message msg) {
        this.msg = msg;
    }

    public Message getMsg() {
        return msg;
    }
}
public class MessageRecievedEventConsumer {

    private static final Logger LOG = LoggerFactory.getLogger(MessageRecievedEventConsumer.class);

    @Subscribe
    public void messageRecieved(MessageRecievedEvent e) {
        LOG.info("messageRecieved(), msg: {}", e.getMsg());
    }
}

Listener must be registered in EventBus instance in order to be notified when an event is fired.

Message msg = new Message("marlly", "Interface vs annotation driven events", "Post about differences between those event architectures");
MessageRecievedEvent msgEvent = new MessageRecievedEvent(msg);
new EventBus().post(msgEvent);

EDIT 08/2015: Sources including unit test are now available on GitHub.

Advantages:

  • less code
  • no specific listener interface for each event
  • can listen to event supertype and take advantage of inheritance
  • detects events that have attached no listeners

Disadvantages:

  • moot lack of static typing (register and post methods accept Object type as parameter)

Conclusion
If you are already utilizing Google Guava library and are looking for simple notification system, you should definitely use EventBus. For others just add Guava libray and use it too :). It’s really simple and effective way how to handle with events.

No Comments |Tags: , ,

Non-ASCII file names in ZIP archive

Recently one of our clients reports a bug regarding usage of czech national characters in file names within ZIP archive. They just didn’t display correctly. After some analysis I discovered something I never believed is possible nowadays. Windows 7 has no native support for UTF-8 encoded file names in ZIP archive! Common Microsoft, it’s 2011 and support for UTF-8 file name characters is arround at least 5 years (officialy introduced in v6.3.0 of ZIP specification).

The whole problem with file names encoding lies in the fact that ZIP format uses by default IBM PC character encoding set also known as IBM Code Page 437, IBM437 or CP437. Unfortunately this code page restricts storing file name characters to only those within the original MS-DOS range so it’s quite limited. Therefore if you want to use most national characters in file names within ZIP, you have basically two options:

  • Use UTF-8 and set language encoding flag to instruct the processing tool, that characters in file names are encoded in UTF-8
  • Use whatever encoding that’s native to your specific target platform

First Option
With first option you can achieve the best interoperability among operating systems. Downside of this approach is that Windows users have to use some third-party application to handle ZIP archives because compressed folder doesn’t display UTF-8 characters correctly. All well-known ZIP processing tools I tried on Windows (WinZip, WinRAR, 7-Zip) were able to display UTF-8 encoded file names properly. 7-Zip on unix-based systems has also displayed such a file names correctly. Here is the Java code snippet that creates a ZIP archive containig two empty files with slovak national characters in each file name.

ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(new FileOutputStream("/tmp/utf8.zip"));
zipOut.setEncoding("UTF-8");
zipOut.setUseLanguageEncodingFlag(true);
zipOut.putArchiveEntry(new ZipArchiveEntry("1_ľščťžýáíé.txt"));
zipOut.closeArchiveEntry();
zipOut.putArchiveEntry(new ZipArchiveEntry("2_úäôňďúě.txt"));
zipOut.closeArchiveEntry();
zipOut.flush();
zipOut.close();

This example uses Apache Commons Compress library which allow to specify encoding and set language flag. If you are lucky and already using Java 7 released last month, you can utilize classes from java.util.zip package that obtained new constructor to set encoding. In addition, these classes use UTF-8 by default and read/write language encoding flag. On Java versions <= 1.6 just stay with commons-compress library.

Second Option
Second option is way to go when you address only one operating system using specific code page (that’s our customer case and approach I eventually employed). Suppose all your users use Windows with code page 852 (CP852, IBM852 – standard code page used by central european countries). In this case you can generate ZIP archive in almost the same way as above but this time set the encoding to CP852 and omit the encoding flag.

ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(new FileOutputStream("/tmp/cp852.zip"));
zipOut.setEncoding("CP852");
zipOut.putArchiveEntry(new ZipArchiveEntry("1_ľščťžýáíé.txt"));
zipOut.closeArchiveEntry();
zipOut.putArchiveEntry(new ZipArchiveEntry("2_úäôňďúě.txt"));
zipOut.closeArchiveEntry();
zipOut.flush();
zipOut.close();

Every tool on the platform using default code page 852 will display national characters from this ZIP file correctly, including Windows compressed folder tool. In order to find out what code page Windows currently uses simply navigate to the following node in registry:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage

and look for a key with the name OECMP.

And remember, there is no such thing as universal, always-working approach to ZIP file names encoding.

No Comments |Tags: ,