Swing and Roundabouts 1M: Emission DTs
Preface
This article might be debunked over time at this permalink
on java.net CVS. Actually corrections have probably already been made
to that copy, during a post-publication review (or two), so maybe click through to there
rather than continue here.
Cos it's so much easier to right-click and commit a subdirectory from Netbeans,
than to edit weblogs using blogowarez. Not to mention uploading accompanying
files one-by-one... So there's another great use for Netbeans/CVS -
to edit and publish blog entries :)
Introduction
"I can't believe it! Reading and writing actually paid off!" Homer Simpson
Swing and Roundabouts 1P: Epoxy DTs
looked at using dynamic proxies to avoid boiler-plate code
to build GUI helper classes whose methods should all run in the EDT, even if they are
invoked from a background thread. For example, a long running task in a SwingWorker thread
might require confirmation dialogs, GUI focus changes and such, which should be invoked
in the EDT.
Another option is eventbus.dev.java.net
by Michael Bushe, which uses the subscribe/publish model, eg. one can publish events
to invoke subscribers inside the EDT. This is more loosely coupled than Swing listeners,
since those need to be attached to specific components.
Since this seems to be an interesting approach that I haven't used before,
let's implement a simplistic subscribe/publish mechanism, and use it
for some dialogs and what-not, to see what it's all about.
Wherez the warez?
If you want the warez before the hows, here is the demo...

(QuiteBusy, Java5, 450k, unsandboxed)
Which looks something like this...
Which looks, and is, very ordinary, except that the dialogs, console and status bar, are
called into action using a message bus, for loose-coupling and what-not.
The code is currently in the quitebusy and quitebusydemo packages on vellum.dev.java.net,
but will move to aptfoundation.dev.java.net at some stage,
and thereafter maybe to quitebusy.dev.java.net one day.
You can check out the vellum project,
and run the same main class as in the JNLP,
ie. quitebusydemo.common.QMessageBusDemo.
Message Bus Interfaces
Our main QMessageBus interface below exposes methods to subscribe and unsubscribe services
for specific message types, and of course also methods to publish messages.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> QMessageBus<Response> {
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>subscribe</b>(Class messageClass, QSubscriber subscriber);
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>subscribeWeakly</b>(Class messageClass, QSubscriber subscriber);
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>subscribeResponder</b>(Class messageClass, QResponder subscriber) <font color=#000099><b>throws</b></font> Throwable;
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>unsubscribe</b>(Class messageClass, QSubscriber subscriber);
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>publish</b>(QMessage message) <font color=#000099><b>throws</b></font> Throwable;
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>publishBackground</b>(QMessage message);
<font color=#000099><b>public</b></font> Response <b>getResponse</b>(QMessage message) <font color=#000099><b>throws</b></font> Throwable;
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>publishException</b>(Throwable exception, String text);
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>publishInfo</b>(Level level, String text);
}
In a minute we'll present an implementation where publishBackground() uses a
background SwingWorker thread outside the EDT, and publish() uses invokeAndWait()
on the EDT.
We parameterise the message bus implementation with a Response type, rather than
let getResponse() return an Object, out of habit more than anything else.
It makes most sense to default the Response to Object,
so as not to limit our options.
Our QMessage interface is just a tag as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> QMessage {
}
Our QSubscriber interface is as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> QSubscriber<Message> {
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>receive</b>(Message message) <font color=#000099><b>throws</b></font> Exception;
}
where a subscriber receives a message, and might throw an exception.
When subscribing a service (to receive messages published via the bus),
we can opt to subscribe "weakly" using subscribeWeakly(), in which case we will use Java's "weak references."
These don't prevent garbage collection (when there are only
weak references to an object). This is handy for subscribers that
might come and go.
Our QResponder interface below is similar to QSubscriber above, except that
our "responder" can return an object.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> QResponder<Message, Response> {
<font color=#000099><b>public</b></font> Response <b>receive</b>(Message message) <font color=#000099><b>throws</b></font> Exception;
}
We subscribe a "responder" service for a given message type using subscribeResponder().
In this case, we can use the getResponse() method to wait for the response,
and return the response object. For example, we might publish a message for a
"confirmation dialog" service to display, where it returns a Boolean object.
This is essentially a loosely-coupled (and thinly-veiled) method invocation.
So it makes sense to have only a single responder
for a given message type.
Incidently, in a future article,
we might try to push our luck with a distributed message bus,
perhaps using RESTful messages, eg. QBasicRestMessage with QRestMessageType of POST, GET, PUT
and DELETE, and the URI and what-not. Those might be serialised into XML,
and sent over HTTP to remote QRestResource responders.
Sounds like too much fun not to try!
Message Bus Implementation Singleton
We implement a QDefaultMessageBus as follows.
In order to support a "logging aspect," we use a proxy object
for the messageBus singleton. This delegates the method invocation to our NLoggingInvoker,
for logging methods, their arguments, and return value (if there is one).
Actually this is just an exercise to reuse our dynamic proxy classes
introduced in Epoxy DTs :)
Rather than getResponse() return an generic object, we use generics
to limit our options to a Boolean response type, at least for our messageBus singleton
below.
We have two subscriber maps, one for weak references to subscribers, and another
for strong references. And we have a responder map too.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QDefaultMessageBus<Response> <font color=#000099><b>implements</b></font> QMessageBus<Response> {
<font color=#000099><b>public</b></font> <font color=#000099><b>static</b></font> <font color=#000099><b>final</b></font> QMessageBus<Boolean> messageBusProxy = (QMessageBus)
NProxyFactory.<b>createProxy</b>(<font color=#000099><b>new</b></font> QDefaultMessageBus(), <font color=#000099><b>new</b></font> NLoggingInvoker());
<font color=#000099><b>public</b></font> <font color=#000099><b>static</b></font> <font color=#000099><b>final</b></font> QDefaultMessageBus<Boolean> messageBus = <font color=#000099><b>new</b></font> QDefaultMessageBus();
QMessageBusConfiguration configuration = <font color=#000099><b>new</b></font> QMessageBusConfiguration();
QPublisher<Response> publisher = <font color=#000099><b>new</b></font> QEdtPublisher();
QSubscriberMap weakSubscriberMap = <font color=#000099><b>new</b></font> QSubscriberMap(QWeakSubscriberList.<font color=#000099><b>class</b></font>);
QSubscriberMap defaultSubscriberMap = <font color=#000099><b>new</b></font> QSubscriberMap(QDefaultSubscriberList.<font color=#000099><b>class</b></font>);
Map<Class, QResponder> responderMap = Collections.<b>synchronizedMap</b>(<font color=#000099><b>new</b></font> HashMap());
<font color=#000099><b>protected</b></font> QDefaultMessageBus() {
}
<font color=#000099><b>public</b></font> <font color=#000099><b>synchronized</b></font> <font color=#000099><b>void</b></font> <b>subscribe</b>(Class messageClass, QSubscriber subscriber) {
QSimpleList subscriberList = defaultSubscriberMap.<b>getSubscriberList</b>(messageClass);
<font color=#000099><b>if</b></font> (subscriberList.<b>contains</b>(subscriber)) {
logger.<b>warning</b>(messageClass, subscriber);
} <font color=#000099><b>else</b></font> {
subscriberList.<b>add</b>(subscriber);
}
}
<font color=#000099><b>public</b></font> <font color=#000099><b>synchronized</b></font> <font color=#000099><b>void</b></font> <b>subscribeWeakly</b>(Class messageClass, QSubscriber subscriber) {
QSimpleList subscriberList = weakSubscriberMap.<b>getSubscriberList</b>(messageClass);
<font color=#000099><b>if</b></font> (subscriberList.<b>contains</b>(subscriber)) {
logger.<b>warning</b>(messageClass, subscriber);
} <font color=#000099><b>else</b></font> {
subscriberList.<b>add</b>(subscriber);
}
}
<font color=#000099><b>public</b></font> <font color=#000099><b>synchronized</b></font> <font color=#000099><b>void</b></font> <b>subscribeResponder</b>(Class messageClass, QResponder responder)
<font color=#000099><b>throws</b></font> Throwable {
<font color=#000099><b>if</b></font> (responderMap.<b>get</b>(messageClass) != <font color=#000099><b>null</b></font>) {
<font color=#000099><b>throw</b></font> <font color=#000099><b>new</b></font> NRuntimeException(
configuration.subscribeResponderExclusiveError, messageClass);
} <font color=#000099><b>else</b></font> {
responderMap.<b>put</b>(messageClass, responder);
}
}
<font color=#000099><b>public</b></font> <font color=#000099><b>synchronized</b></font> <font color=#000099><b>void</b></font> <b>unsubscribe</b>(Class messageClass, QSubscriber subscriber) {
defaultSubscriberMap.<b>getSubscriberList</b>(messageClass).<b>remove</b>(subscriber);
weakSubscriberMap.<b>getSubscriberList</b>(messageClass).<b>remove</b>(subscriber);
responderMap.<b>remove</b>(messageClass);
}
...
}
We now implement methods to publish messages, eg. via invokeAndWait()
in the EDT, or alternatively in the background, eg. using a SwingWorker thread, ie. outside of the EDT.
If our task will take any length of time to execute, then we must use publishBackground()
to avoid blocking the EDT, otherwise the GUI will appear to be "hung" while the long task
is executing.
Services that receive messages via publishBackground() must themselves use
publish() to invoke any GUI services eg. popping up dialogs and such,
to ensure that those run in the EDT. Otherwise they could use EDT-safe
helper classes, as in the prequels Event DTs
and Epoxy DTs.
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>publishBackground</b>(QMessage message) {
<font color=#000099><b>try</b></font> {
<b>publish</b>(message, <font color=#000099><b>true</b></font>, <font color=#000099><b>true</b></font>);
} <font color=#000099><b>catch</b></font> (Throwable e) {
<font color=#000099><b>throw</b></font> <font color=#000099><b>new</b></font> NWrappedRuntimeException(e, <font color=#000099><b>null</b></font>, message);
}
}
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>publish</b>(QMessage message) <font color=#000099><b>throws</b></font> Throwable {
<b>publish</b>(message, <font color=#000099><b>false</b></font>, <font color=#000099><b>true</b></font>);
}
<font color=#000099><b>protected</b></font> <font color=#000099><b>boolean</b></font> <b>publish</b>(QMessage message, <font color=#000099><b>boolean</b></font> background, <font color=#000099><b>boolean</b></font> strict)
<font color=#000099><b>throws</b></font> Throwable {
<font color=#000099><b>if</b></font> (message == <font color=#000099><b>null</b></font>) {
<font color=#000099><b>throw</b></font> <font color=#000099><b>new</b></font> NullPointerException(configuration.nullMessageError);
}
Class messageClass = message.<b>getClass</b>();
<font color=#000099><b>if</b></font> (!hasSubscriber(messageClass)) {
<font color=#000099><b>if</b></font> (strict) {
<font color=#000099><b>throw</b></font> <font color=#000099><b>new</b></font> NRuntimeException(
configuration.publishSubscriberListEmptyError, messageClass);
}
<font color=#000099><b>return</b></font> <font color=#000099><b>false</b></font>;
}
QResponder responder = responderMap.<b>get</b>(messageClass);
<font color=#000099><b>if</b></font> (responder != <font color=#000099><b>null</b></font>) {
publisher.<b>publishResponder</b>(responder, message, background);
}
<b>publish</b>(defaultSubscriberMap, message, background);
<b>publish</b>(weakSubscriberMap, message, background);
<font color=#000099><b>return</b></font> <font color=#000099><b>true</b></font>;
}
<font color=#000099><b>protected</b></font> <font color=#000099><b>boolean</b></font> <b>hasSubscriber</b>(Class messageClass) {
<font color=#000099><b>if</b></font> (responderMap.<b>containsKey</b>(messageClass)) <font color=#000099><b>return</b></font> <font color=#000099><b>true</b></font>;
<font color=#000099><b>if</b></font> (<b>hasSubscriber</b>(defaultSubscriberMap, messageClass)) <font color=#000099><b>return</b></font> <font color=#000099><b>true</b></font>;
<font color=#000099><b>if</b></font> (<b>hasSubscriber</b>(weakSubscriberMap, messageClass)) <font color=#000099><b>return</b></font> <font color=#000099><b>true</b></font>;
<font color=#000099><b>return</b></font> <font color=#000099><b>false</b></font>;
}
<font color=#000099><b>protected</b></font> <font color=#000099><b>boolean</b></font> <b>hasSubscriber</b>(QSubscriberMap subscriberMap, Class messageClass) {
List<QSubscriber> subscriberList = subscriberMap.<b>getSubscriberList</b>(messageClass).<b>getList</b>();
<font color=#000099><b>return</b></font> (subscriberList.<b>size</b>() > 0);
}
In our private publish() message above, if we find that there are no subscribers for our message,
and we are being strict, then we complain, bitterly.
We will introduce QSubscriberMap shortly, where this is essentially an alias as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> QSubscriberMap <font color=#000099><b>extends</b></font> Map<Class, QSubscriberList> {
}
ie. a Map of message types to the List of subscribers for that message type.
QSubscriberList is essentially an alias as follows, with the addition of a
getList() method to provide a copy of its list of subscribers, for thread-safety.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> QSubscriberList <font color=#000099><b>extends</b></font> List<QSubscriber> {
<font color=#000099><b>public</b></font> List<QSubscriber> <b>getList</b>(); <font color=#222222><b>// get thread-safe copy</b></font>
}
We reuse the publish() method below for each QSubscriberMap,
namely weakSubscriberMap and defaultSubscriberMap.
<font color=#000099><b>protected</b></font> <font color=#000099><b>void</b></font> <b>publish</b>(QSubscriberMap subscriberMap, QMessage message, <font color=#000099><b>boolean</b></font> background)
<font color=#000099><b>throws</b></font> Throwable {
Class messageClass = message.<b>getClass</b>();
List<QSubscriber> subscriberList = subscriberMap.<b>getSubscriberList</b>(messageClass).<b>getList</b>();
<font color=#000099><b>for</b></font> (QSubscriber subscriber : subscriberList) {
publisher.<b>publish</b>(subscriber, message, background);
}
}
We use getList() to get a thread-safe copy of the subscriber list, because while we
might be waiting for the subcriber to process the message, another thread might
invoke the subcribe() and/or unsubscribe() methods for the same message type,
which would modify the list.
We delegate to a QPublisher helper as below, to publish the message to a subscriber
or responder.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> QPublisher<Response> {
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>publish</b>(
QSubscriber subscriber, QMessage message, <font color=#000099><b>boolean</b></font> background)
<font color=#000099><b>throws</b></font> Throwable;
<font color=#000099><b>public</b></font> Response <b>publishResponder</b>(
QResponder responder, QMessage message, <font color=#000099><b>boolean</b></font> background)
<font color=#000099><b>throws</b></font> Throwable;
}
Further below we present the QEdtPublisher implementation of the above interface.
This invokes the subscriber's receive() method in the EDT using invokeAndWait(),
or otherwise in a background SwingWorker thread.
Publishing to Responsive Subscribers
We publish to a responder as follows.
<font color=#000099><b>public</b></font> Response <b>getResponse</b>(QMessage message)
<font color=#000099><b>throws</b></font> Throwable {
<font color=#000099><b>return</b></font> <b>publishResponder</b>(message, <font color=#000099><b>false</b></font>);
}
<font color=#000099><b>protected</b></font> Response <b>publishResponder</b>(QMessage message, <font color=#000099><b>boolean</b></font> background)
<font color=#000099><b>throws</b></font> Throwable {
Class messageClass = message.<b>getClass</b>();
QResponder responder = responderMap.<b>get</b>(messageClass);
<font color=#000099><b>if</b></font> (responder == <font color=#000099><b>null</b></font>) {
<font color=#000099><b>throw</b></font> <font color=#000099><b>new</b></font> NRuntimeException(
configuration.publishResponderNoResponderError,
messageClass);
}
<font color=#000099><b>return</b></font> publisher.<b>publishResponder</b>(responder, message, background);
}
where we wait for a response, and return a response object.
So clearly our publishing methods cannot be synchronized,
because they might take a while a execute, eg. if background is false.
Spanglish
Incidently, we "externalise" our error messages and such, into a configuration object as follows.
Then we can readily inject externalised resources into this object, eg. from a resource bundle.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QMessageBusConfiguration {
@ResourceAnnotation()<font color=#666666></font>
String nullMessageError = <font color=#99006b>"Cannot publish a null message"</font>;
@ResourceAnnotation()<font color=#666666></font>
String subscribeResponderExclusiveError = <font color=#99006b>"Not exclusive responder"</font>;
@ResourceAnnotation()<font color=#666666></font>
String publishSubscriberListEmptyError = <font color=#99006b>"No subscribers for message"</font>;
@ResourceAnnotation()<font color=#666666></font>
String publishResponderNoSubscriberError = <font color=#99006b>"No responder for message"</font>;
<font color=#000099><b>public</b></font> QMessageBusConfiguration() {
resourceBundleHelper.<b>configure</b>(<font color=#000099><b>this</b></font>);
}
}
Subscriber Map
QSubscriberMap maintains a map of QSubscriberList, which manages a list of subscribers,
for a given message type.
QSubscriberMap is implemented as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QSubscriberMap <font color=#000099><b>extends</b></font> QValueMap<Class, QSubscriberList> {
<font color=#000099><b>public</b></font> QSubscriberMap(Class subscriberListClass) {
<font color=#000099><b>super</b></font>(subscriberListClass);
}
<font color=#000099><b>public</b></font> QSubscriberList <b>getSubscriberList</b>(Class messageClass) {
<font color=#000099><b>return</b></font> <font color=#000099><b>super</b></font>.<b>getValue</b>(messageClass);
}
}
where we extend QValueMap presented below.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QValueMap<Key, Value> <font color=#000099><b>extends</b></font> HashMap<Key, Value> {
Class valueClass;
<font color=#000099><b>public</b></font> QValueMap(Class valueClass) {
<font color=#000099><b>super</b></font>();
<font color=#000099><b>this</b></font>.valueClass = valueClass;
}
<font color=#000099><b>public</b></font> Value <b>getValue</b>(Key key) {
Value value = <font color=#000099><b>super</b></font>.<b>get</b>(key);
<font color=#000099><b>if</b></font> (value == <font color=#000099><b>null</b></font>) {
<font color=#000099><b>try</b></font> {
value = (Value) valueClass.<b>newInstance</b>();
<font color=#000099><b>super</b></font>.<b>put</b>(key, value);
} <font color=#000099><b>catch</b></font> (Throwable e) {
<font color=#000099><b>throw</b></font> <font color=#000099><b>new</b></font> NWrappedRuntimeException(e, <font color=#000099><b>null</b></font>, valueClass);
}
}
<font color=#000099><b>return</b></font> value;
}
}
where we create a value instance on demand in getValue() using valueClass.newInstance().
Default Subscriber List
QSubscriberList below is just an alias for a basic list.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> QSubscriberList <font color=#000099><b>extends</b></font> QSimpleList<QSubscriber> {
}
where QSimpleList is defined as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> QSimpleList<Element> {
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>add</b>(Element element);
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>remove</b>(Element element);
<font color=#000099><b>public</b></font> <font color=#000099><b>boolean</b></font> <b>contains</b>(Element element);
<font color=#000099><b>public</b></font> <font color=#000099><b>int</b></font> <b>size</b>();
<font color=#000099><b>public</b></font> List<Element> <b>getList</b>();
}
Just to it keep it simple.
We implement QDefaultSubscriberList as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QDefaultSubscriberList <font color=#000099><b>implements</b></font> QSubscriberList {
List<QSubscriber> subscriberList = <font color=#000099><b>new</b></font> ArrayList();
<font color=#000099><b>protected</b></font> QDefaultSubscriberList() {
}
<font color=#000099><b>public</b></font> <font color=#000099><b>synchronized</b></font> <font color=#000099><b>void</b></font> <b>add</b>(QSubscriber subscriber) {
subscriberList.<b>add</b>(subscriber);
}
<font color=#000099><b>public</b></font> <font color=#000099><b>synchronized</b></font> <font color=#000099><b>void</b></font> <b>remove</b>(QSubscriber subscriber) {
subscriberList.<b>remove</b>(subscriber);
}
<font color=#000099><b>public</b></font> <font color=#000099><b>synchronized</b></font> <font color=#000099><b>boolean</b></font> <b>contains</b>(QSubscriber subscriber) {
<font color=#000099><b>return</b></font> subscriberList.<b>contains</b>(subscriber);
}
<font color=#000099><b>public</b></font> <font color=#000099><b>synchronized</b></font> <font color=#000099><b>int</b></font> <b>size</b>() {
<font color=#000099><b>return</b></font> subscriberList.<b>size</b>();
}
<font color=#000099><b>public</b></font> <font color=#000099><b>synchronized</b></font> List<QSubscriber> <b>getList</b>() {
<font color=#000099><b>return</b></font> <font color=#000099><b>new</b></font> ArrayList(subscriberList);
}
}
where we make it thread-safe using synchronized methods.
The getList() method returns a (thread-safe) copy of the list, which we can use to iterate
through our subscribers, eg. in a for loop.
Since we have a single responder per message type,
we create a regular thread-safe map as follows.
Map<Class, QResponder> responderMap = Collections.<b>synchronizedMap</b>(<font color=#000099><b>new</b></font> HashMap());
where the key is the message type.
Weak Reference Subscriber List
In addition to the above default subscriber list, we also implement a subscriber list
that uses weak references, ie. Java's WeakReference wrapper. In this case, our weak reference
to the subscriber will not prevent it being garbage-collected, if there are no
other (strong) references to it in our application.
Actually QWeakSubscriberList is just an alias as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QWeakSubscriberList <font color=#000099><b>extends</b></font> QWeakList<QSubscriber> {
}
where QWeakList is implemented as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QWeakList<Element> <font color=#000099><b>implements</b></font> QSimpleList<Element> {
List<WeakReference> referenceList = <font color=#000099><b>new</b></font> ArrayList();
<font color=#000099><b>public</b></font> QWeakList() {
}
<font color=#000099><b>public</b></font> <font color=#000099><b>synchronized</b></font> <font color=#000099><b>boolean</b></font> <b>contains</b>(Element element) {
<font color=#000099><b>for</b></font> (WeakReference reference : referenceList) {
<font color=#000099><b>if</b></font> (reference.<b>get</b>() == element) {
<font color=#000099><b>return</b></font> <font color=#000099><b>true</b></font>;
}
}
<font color=#000099><b>return</b></font> <font color=#000099><b>false</b></font>;
}
<font color=#000099><b>public</b></font> <font color=#000099><b>synchronized</b></font> <font color=#000099><b>void</b></font> <b>add</b>(Element element) {
referenceList.<b>add</b>(<font color=#000099><b>new</b></font> WeakReference(element));
}
<font color=#000099><b>public</b></font> <font color=#000099><b>synchronized</b></font> <font color=#000099><b>void</b></font> <b>remove</b>(Element element) {
<font color=#000099><b>for</b></font> (WeakReference reference : referenceList) {
<font color=#000099><b>if</b></font> (reference.<b>get</b>() == element) {
referenceList.<b>remove</b>(reference);
<font color=#000099><b>break</b></font>;
}
}
}
<font color=#000099><b>public</b></font> <font color=#000099><b>synchronized</b></font> List<Element> <b>getList</b>() {
List<Element> elementList = <font color=#000099><b>new</b></font> ArrayList();
WeakReference removeReference = <font color=#000099><b>null</b></font>;
<font color=#000099><b>for</b></font> (WeakReference reference : referenceList) {
Element element = (Element) reference.<b>get</b>();
<font color=#000099><b>if</b></font> (element == <font color=#000099><b>null</b></font>) {
removeReference = reference;
} <font color=#000099><b>else</b></font> {
elementList.<b>add</b>(element);
}
}
<font color=#000099><b>if</b></font> (removeReference != <font color=#000099><b>null</b></font>) {
referenceList.<b>remove</b>(removeReference);
}
<font color=#000099><b>return</b></font> elementList;
}
<font color=#000099><b>public</b></font> <font color=#000099><b>int</b></font> <b>size</b>() {
<font color=#000099><b>return</b></font> referenceList.<b>size</b>();
}
}
In the getList() method, we do some incremental house-keeping, ie.
removing a garbage-collected reference, if we find one.
We know that if weakReference.get() returns null, then that subscriber has been
garbage-collected.
If we publish messages to this list more often than we unsubscribe,
which we should do, then it's fine to remove only one garbage-collected
reference in getList() at a time. Nonetheless, this is clearly a case
of premature optimisation (which we hear is a terrible practice). In particular,
we avoid creating a List for possible elements to remove,
when there is typically at most one, and usually none.
Receiving messages
QSubscriber is an interface for receiving messages.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> QSubscriber<Message> {
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>receive</b>(Message message) <font color=#000099><b>throws</b></font> Throwable;
}
Our QResponder interface is similar, except that it returns a response, as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> QResponder<Message, Response> {
<font color=#000099><b>public</b></font> Response <b>receive</b>(Message message) <font color=#000099><b>throws</b></font> Throwable;
}
We allow services to throw an exception. In this case, our message bus needs
an exception handler. We will implement this in a minute, as a dialog popup service
that subscribes to the message bus, to receive exception messages :)
Publishing messages
Our QDefaultMessageBus implementation presented further above delegates to an QPublisher
helper to publish messages to subscribers and responders.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> QPublisher<Response> {
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>publish</b>(
QSubscriber subscriber, QMessage message, <font color=#000099><b>boolean</b></font> background)
<font color=#000099><b>throws</b></font> Throwable;
<font color=#000099><b>public</b></font> Response <b>publishResponder</b>(
QResponder responder, QMessage message, <font color=#000099><b>boolean</b></font> background)
<font color=#000099><b>throws</b></font> Throwable;
}
where QMessage is just a tag as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> QMessage {
}
We can request the message bus to invoke the service in the background, or otherwise
wait for the execution to complete, eg. using invokeAndWait() on the EDT. In the
case of publishResponder(), we can return a response. If publishResponder() is invoked
in the background, then it will return null immediately, and it's response
is not available in this case.
Publishing to the EDT
We implement an QEdtPublisher, which is cognisant of the EDT. If background is true,
we invoke the service in a background SwingWorker thread (which is not the EDT).
Otherwise we invokeAndWait() on the EDT.
<font color=#000099><b>import</b></font> <font color=#000099><b>static</b></font> quitebusy.common.QDefaultMessageBus.*; <font color=#222222><b>// for messageBus singleton</b></font>
<font color=#000099><b>import</b></font> <font color=#000099><b>static</b></font> aptfoundation.common.context.NContext.*; <font color=#222222><b>// for singletons, eg. logger</b></font>
...
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QEdtPublisher<Response> <font color=#000099><b>implements</b></font> QPublisher<Response> {
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>publish</b>(QSubscriber subscriber, QMessage message, <font color=#000099><b>boolean</b></font> background)
<font color=#000099><b>throws</b></font> Throwable {
logger.<b>entering</b>(subscriber, message, background);
<font color=#000099><b>final</b></font> FutureTask task = <font color=#000099><b>new</b></font> FutureTask(<b>createCallable</b>(subscriber, message));
<font color=#000099><b>if</b></font> (!background) <b>invokeAndWait</b>(task);
<b>doInBackground</b>(task);
}
<font color=#000099><b>public</b></font> Response <b>publishResponder</b>(
QResponder responder, QMessage message, <font color=#000099><b>boolean</b></font> background)
<font color=#000099><b>throws</b></font> Throwable {
logger.<b>entering</b>(responder, message, background);
<font color=#000099><b>final</b></font> FutureTask task = <font color=#000099><b>new</b></font> FutureTask(<b>createCallable</b>(responder, message));
<font color=#000099><b>if</b></font> (!background) <font color=#000099><b>return</b></font> (Response) <b>invokeAndWait</b>(task);
<b>doInBackground</b>(task);
<font color=#000099><b>return</b></font> <font color=#000099><b>null</b></font>;
}
<font color=#000099><b>protected</b></font> Object <b>invokeAndWait</b>(<font color=#000099><b>final</b></font> FutureTask task) <font color=#000099><b>throws</b></font> Throwable {
logger.<b>entering</b>();
<font color=#000099><b>if</b></font> (!SwingUtilities.<b>isEventDispatchThread</b>()) {
SwingUtilities.<b>invokeAndWait</b>(task);
} <font color=#000099><b>else</b></font> {
task.<b>run</b>();
}
<font color=#000099><b>try</b></font> {
<font color=#000099><b>return</b></font> task.<b>get</b>();
} <font color=#000099><b>catch</b></font> (InterruptedException ie) {
<font color=#000099><b>throw</b></font> ie;
} <font color=#000099><b>catch</b></font> (ExecutionException ee) {
<font color=#000099><b>throw</b></font> ee.<b>getCause</b>();
}
}
<font color=#000099><b>protected</b></font> <font color=#000099><b>void</b></font> <b>doInBackground</b>(<font color=#000099><b>final</b></font> FutureTask task) {
logger.<b>entering</b>();
SwingWorker worker = <font color=#000099><b>new</b></font> SwingWorker() {
<font color=#000099><b>protected</b></font> Object <b>doInBackground</b>() <font color=#000099><b>throws</b></font> Throwable {
<font color=#000099><b>try</b></font> {
task.<b>run</b>();
task.<b>get</b>();
} <font color=#000099><b>catch</b></font> (Throwable e) {
e.<b>printStackTrace</b>();
messageBus.<b>publishException</b>(e, <font color=#000099><b>null</b></font>);
}
<font color=#000099><b>return</b></font> <font color=#000099><b>null</b></font>;
}
};
worker.<b>execute</b>();
}
<font color=#000099><b>protected</b></font> Callable <b>createCallable</b>(
<font color=#000099><b>final</b></font> QSubscriber subscriber, <font color=#000099><b>final</b></font> QMessage message) {
<font color=#000099><b>return</b></font> <font color=#000099><b>new</b></font> Callable() {
<font color=#000099><b>public</b></font> Object <b>call</b>() <font color=#000099><b>throws</b></font> Throwable {
subscriber.<b>receive</b>(message);
<font color=#000099><b>return</b></font> <font color=#000099><b>null</b></font>;
}
};
}
<font color=#000099><b>protected</b></font> Callable <b>createCallable</b>(
<font color=#000099><b>final</b></font> QResponder subscriber, <font color=#000099><b>final</b></font> QMessage message) {
<font color=#000099><b>return</b></font> <font color=#000099><b>new</b></font> Callable() {
<font color=#000099><b>public</b></font> Object <b>call</b>() <font color=#000099><b>throws</b></font> Throwable {
<font color=#000099><b>return</b></font> subscriber.<b>receive</b>(message);
}
};
}
}
In publishResponder(), if we are gonna wait for a response, ie. via invokeAndWait(),
rather than doInBackground(), then we can return a response value from our service,
eg. a Boolean object from a confirmation dialog service. Otherwise we return null.
In our background SwingWorker, in the event of an error, we publish an exception message
to the message bus. We will go into this further below.
Dialogue
So now let's implement some dialog support. First we need a dialog message
to publish.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QDialogMessage <font color=#000099><b>implements</b></font> QStatusMessage {
Component parentComponent = QApplicationContext.<b>getInstance</b>().<b>getFrame</b>();
<font color=#000099><b>int</b></font> option = JOptionPane.OK_OPTION;
String title;
<font color=#000099><b>public</b></font> QDialogMessage(String text) {
<font color=#000099><b>super</b></font>(text);
}
<font color=#222222><b>... // getters and setters, eg. getParentComponent(), getOption()</b></font>
}
where the QStatusMessage superclass is as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QStatusMessage <font color=#000099><b>implements</b></font> QMessage {
String text;
<font color=#000099><b>public</b></font> QStatusMessage() {
}
<font color=#000099><b>public</b></font> QStatusMessage(String text) {
<font color=#000099><b>this</b></font>.text = text;
}
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>setText</b>(String text) {
<font color=#000099><b>this</b></font>.text = text;
}
<font color=#000099><b>public</b></font> String <b>getText</b>() {
<font color=#000099><b>return</b></font> text;
}
}
Now we can implement a dialog service to receive the above message type.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QDialogService <font color=#000099><b>implements</b></font> QSubscriber<QDialogMessage> {
<font color=#000099><b>public</b></font> <font color=#000099><b>static</b></font> <font color=#000099><b>final</b></font> QDialogService dialogService = <font color=#000099><b>new</b></font> QDialogService();
<font color=#000099><b>private</b></font> QDialogService() {
}
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>receive</b>(QDialogMessage message) <font color=#000099><b>throws</b></font> Throwable {
JOptionPane.<b>showMessageDialog</b>(
message.<b>getParentComponent</b>(),
message.<b>getText</b>());
}
}
where we create a singleton, so that it is impossible to create and subscribe
more than one dialog service by mistake, in which case, multiple dialogs would
popup at the same time.
Two-way Dialogue
We implement a QConfirmationDialogService as follows, to handle confirmation dialogs,
so it is a responder service, returning a Boolean response.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QConfirmationDialogService
<font color=#000099><b>implements</b></font> QResponder<QConfirmationMessage, Boolean> {
...
<font color=#000099><b>public</b></font> Boolean <b>receive</b>(QConfirmationMessage message) <font color=#000099><b>throws</b></font> Throwable {
<font color=#000099><b>int</b></font> result = JOptionPane.<b>showConfirmDialog</b>(
message.<b>getParentComponent</b>(),
message.<b>getText</b>(),
<font color=#000099><b>null</b></font>,
message.<b>getOption</b>());
<font color=#000099><b>return</b></font> result == message.<b>getConfirmationOption</b>();
}
}
where QConfirmationMessage extends QDialogMessage as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QConfirmationMessage <font color=#000099><b>extends</b></font> QDialogMessage {
<font color=#000099><b>int</b></font> option = JOptionPane.OK_CANCEL_OPTION;
<font color=#000099><b>int</b></font> confirmationOption = JOptionPane.OK_OPTION;
<font color=#000099><b>public</b></font> QConfirmationMessage(String message) {
<font color=#000099><b>super</b></font>(message);
<b>setOption</b>(option);
}
<font color=#000099><b>public</b></font> <font color=#000099><b>int</b></font> <b>getConfirmationOption</b>() {
<font color=#000099><b>return</b></font> confirmationOption;
}
}
where our confirmation message has an affirmative confirmationOption set to JOptionPane.OK_OPTION.
We might also want to add support for YES_NO_OPTION, as an alternative.
Kicking the tyres
Let's see if it works, by subscribing an instance of QDialogService to the bus,
and then publishing a QDialogMessage.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QMessageBusDemo {
...
<font color=#000099><b>protected</b></font> <font color=#000099><b>void</b></font> <b>subscribe</b>() {
messageBus.<b>subscribe</b>(QDialogMessage.<font color=#000099><b>class</b></font>, QDialogService.dialogService);
...
}
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>testMessageDialog</b>() {
messageBus.<b>publish</b>(<font color=#000099><b>new</b></font> QDialogMessage(<font color=#99006b>"Hello, Emission DT!"</font>));
}
}
Here again is Web Start demo of this Quite Busy warez.

(QuiteBusy, Java5, 450k, unsandboxed)
Which looks something like this...
Status Bar
Let's implement a QInfoMessage, which we might use for logging, so it has a verbosity Level.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QInfoMessage <font color=#000099><b>extends</b></font> QStatusMessage {
Level level;
<font color=#000099><b>public</b></font> QInfoMessage(Level level, String text) {
<font color=#000099><b>super</b></font>(text);
<font color=#000099><b>this</b></font>.level = level;
}
<font color=#000099><b>public</b></font> Level <b>getLevel</b>() {
<font color=#000099><b>return</b></font> level;
}
}
We can implement a trivial logging service as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QLoggerInfoService <font color=#000099><b>implements</b></font> QSubscriber<QInfoMessage> {
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>receive</b>(QInfoMessage message) <font color=#000099><b>throws</b></font> Throwable {
logger.<b>logp</b>(message.<b>getLevel</b>(), message.<b>getText</b>());
}
}
which uses our static logger. Incidently, we can control the verbosity per
class/package of this static logger via command-line parameters, using some tricks -
see the upcoming Plumber's Hack: At Loggerheads.
Let's implement a status bar service, which updates a JLabel with the info message,
and changes the background color, eg. to highlight a warning message.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QStatusBarInfoService <font color=#000099><b>implements</b></font> QSubscriber<QInfoMessage> {
JPanel labelPanel;
JLabel label;
<font color=#000099><b>public</b></font> QStatusBarInfoService(JLabel label, JPanel labelPanel) {
<font color=#000099><b>this</b></font>.label = label;
<font color=#000099><b>this</b></font>.labelPanel = labelPanel;
}
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>receive</b>(QInfoMessage message) <font color=#000099><b>throws</b></font> Throwable {
label.<b>setText</b>(message.<b>getText</b>());
<font color=#000099><b>if</b></font> (message.<b>getLevel</b>() == Level.SEVERE) {
labelPanel.<b>setBackground</b>(Color.orange);
} <font color=#000099><b>else</b></font> <font color=#000099><b>if</b></font> (message.<b>getLevel</b>() == Level.WARNING) {
labelPanel.<b>setBackground</b>(Color.yellow);
} <font color=#000099><b>else</b></font> {
labelPanel.<b>setBackground</b>(Color.white);
}
}
}
where since a JLabel's background is transparent, we put the JLabel into a JPanel,
and change the background of the JPanel.
We try it out as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QMessageBusDemo {
JLabel statusLabel = <font color=#000099><b>new</b></font> JLabel();
JPanel statusLabelPanel = <font color=#000099><b>new</b></font> JPanel(<font color=#000099><b>new</b></font> GridBagLayout());
...
<font color=#000099><b>protected</b></font> <font color=#000099><b>void</b></font> <b>subscribe</b>() {
statusLabelPanel.<b>add</b>(statusLabel, Gbc.<b>xy</b>(0, 0).<b>insets</b>(4).<b>both</b>());
messageBus.<b>subscribe</b>(QInfoMessage.<font color=#000099><b>class</b></font>,
<font color=#000099><b>new</b></font> QStatusBarInfoService(statusLabel, statusLabelPanel));
...
}
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>testInfo</b>() {
messageBus.<b>publish</b>(<font color=#000099><b>new</b></font> QInfoMessage(Level.INFO, <font color=#99006b>"This is a info message"</font>));
}
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>testWarning</b>() {
messageBus.<b>publish</b>(<font color=#000099><b>new</b></font> QInfoMessage(Level.WARNING, <font color=#99006b>"This is an warning message"</font>));
}
...
}
where Gbc was presented in Grid Bag Grease.
Our message bus can now use this for a publishInfo() method below as below,
which we provide for convenience.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QDefaultMessageBus<Response> <font color=#000099><b>implements</b></font> QMessageBus<Response> {
...
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>publishInfo</b>(Level level, String text) {
QInfoMessage infoMessage = <font color=#000099><b>new</b></font> QInfoMessage(level, text);
<font color=#000099><b>try</b></font> {
<font color=#000099><b>if</b></font> (<b>publish</b>(infoMessage, <font color=#000099><b>false</b></font>, <font color=#000099><b>false</b></font>)) <font color=#000099><b>return</b></font>;
} <font color=#000099><b>catch</b></font> (Throwable e) {
logger.<b>warning</b>(e);
}
logger.<b>logp</b>(level, text);
}
}
where if no service has been subscribed to QInfoMessage, then publish() returns false
(rather than throwing an exception, since our strict parameter is false),
in which case we use our regular logger.
Consoling
We implement a service to append info messages to a JTextPane console as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QTextPaneConsoleInfoService <font color=#000099><b>implements</b></font> QSubscriber<QInfoMessage> {
JTextPane textPane;
<font color=#000099><b>public</b></font> QTextPaneConsoleInfoService(JTextPane textPane) {
<font color=#000099><b>this</b></font>.textPane = textPane;
}
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>receive</b>(QInfoMessage message) <font color=#000099><b>throws</b></font> Throwable {
<font color=#000099><b>if</b></font> (message.<b>getText</b>() == <font color=#000099><b>null</b></font>) <font color=#000099><b>return</b></font>;
String text = textPane.<b>getText</b>();
StringBuffer buffer = <font color=#000099><b>new</b></font> StringBuffer();
Level level = message.<b>getLevel</b>();
<font color=#000099><b>if</b></font> (level != <font color=#000099><b>null</b></font>) buffer.<b>append</b>(level.<b>toString</b>() + <font color=#99006b>": "</font>);
buffer.<b>append</b>(message.<b>getText</b>());
textPane.<b>setText</b>(text + <font color=#99006b>"\n"</font> + buffer.<b>toString</b>());
}
}
which we test as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QMessageBusDemo {
JTextPane consoleTextPane = <font color=#000099><b>new</b></font> JTextPane();
...
<font color=#000099><b>protected</b></font> <font color=#000099><b>void</b></font> <b>subscribe</b>() {
messageBus.<b>subscribe</b>(QInfoMessage.<font color=#000099><b>class</b></font>,
<font color=#000099><b>new</b></font> QTextPaneConsoleInfoService(consoleTextPane));
messageBus.<b>subscribe</b>(QDialogMessage.<font color=#000099><b>class</b></font>,
<font color=#000099><b>new</b></font> QTextPaneConsoleInfoService(consoleTextPane));
...
}
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>testInfo</b>() <font color=#000099><b>throws</b></font> Throwable {
messageBus.<b>publish</b>(<font color=#000099><b>new</b></font> QInfoMessage(Level.WARNING, <font color=#99006b>"This is an warning message"</font>));
messageBus.<b>publishInfo</b>(Level.FINE, <font color=#99006b>"This is one fine message"</font>); <font color=#222222><b>// convenience method</b></font>
}
}
where if we make QDialogMessage extend QInfoMessage as follows, then we can subscribe
our text pane console service to dialog messages too, as above,
so that those are also displayed in our consoleTextPane.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QDialogMessage <font color=#000099><b>extends</b></font> QInfoMessage {
...
<font color=#000099><b>public</b></font> QDialogMessage(String text) {
<font color=#000099><b>super</b></font>(Level.INFO, text);
}
<font color=#000099><b>public</b></font> QDialogMessage(Level level, String text) {
<font color=#000099><b>super</b></font>(level, text);
}
...
}
where we choose our default verbosity to be at the INFO level, for dialog messages.
Exceptional Dialogue
Our QExceptionMessage could extend the above QDialogMessage, to include an exception.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QExceptionMessage <font color=#000099><b>extends</b></font> QDialogMessage {
Exception exception;
<font color=#000099><b>public</b></font> QExceptionMessage(Exception exception, String message) {
<font color=#000099><b>super</b></font>(formatter.<b>formatExceptionMessage</b>(exception, message));
<font color=#000099><b>this</b></font>.exception = exception;
}
<font color=#000099><b>public</b></font> Exception <b>getException</b>() {
<font color=#000099><b>return</b></font> exception;
}
}
Our message bus can now use this message in its publishException() convenience method below,
eg. to publish exceptions that might occur in QEdtPublisher.doInBackground() ie.
thrown by a service we invoked in the background.
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>publishException</b>(Throwable exception, String text) {
QExceptionMessage exceptionMessage = <font color=#000099><b>new</b></font> QExceptionMessage(exception, text);
<font color=#000099><b>try</b></font> {
<font color=#000099><b>if</b></font> (<b>publish</b>(exceptionMessage, <font color=#000099><b>false</b></font>, <font color=#000099><b>false</b></font>)) <font color=#000099><b>return</b></font>;
} <font color=#000099><b>catch</b></font> (Throwable e) {
logger.<b>warning</b>(e);
}
dialogHelper.<b>showExceptionDialog</b>(exception, text);
}
where we revert to our dialogHelper if there are no subscribers for QExceptionMessage.
We can then subscribe a QDialogService instance as follows, eg. to popup exception messages
that might occur in background SwingWorker threads, which will otherwise
be handled by dialogHelper.showExceptionDialog() as above.
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>testException</b>() {
messageBus.<b>subscribe</b>(QExceptionMessage.<font color=#000099><b>class</b></font>, <font color=#000099><b>new</b></font> QDialogService());
messageBus.<b>publishException</b>(
<font color=#000099><b>new</b></font> NullPointerException(<font color=#99006b>"drill too small"</font>),
<font color=#99006b>"error occurred while fixing camera"</font>
));
}
But let's rather implement a QExceptionDialogService, to popup an
exception panel with a "Details" button to show the stack trace.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QExceptionDialogService <font color=#000099><b>implements</b></font> QSubscriber<QExceptionMessage> {
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>receive</b>(QExceptionMessage message) <font color=#000099><b>throws</b></font> Throwable {
QExceptionPanel panel = <font color=#000099><b>new</b></font> QExceptionPanel(
message.<b>getException</b>(), message.<b>getMessageText</b>());
JDialog dialog = panel.<b>createDialog</b>(
QApplicationData.<b>getInstance</b>().<b>getFrame</b>());
dialog.<b>setVisible</b>(<font color=#000099><b>true</b></font>);
}
}
Exceptions Everywhere
If we wanna display exceptions in our status bar, we can make QExceptionMessage
extend QInfoMessage as follows
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QExceptionMessage <font color=#000099><b>extends</b></font> QInfoMessage {
Throwable exception;
<font color=#000099><b>public</b></font> QExceptionMessage(Throwable exception, String text) {
<font color=#000099><b>super</b></font>(Level.SEVERE, text);
<font color=#000099><b>this</b></font>.exception = exception;
}
<font color=#000099><b>public</b></font> Throwable <b>getException</b>() {
<font color=#000099><b>return</b></font> exception;
}
}
Now we can subscribe the status bar service presented further above,
to exception messages as follows. Then, when an exception gets published on the bus,
it will be displayed in our status bar, as well as in an exception panel,
if we have subscribed that service as well.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QMessageBusDemo {
JLabel statusLabel = <font color=#000099><b>new</b></font> JLabel();
JPanel statusLabelPanel = <font color=#000099><b>new</b></font> JPanel(<font color=#000099><b>new</b></font> GridBagLayout());
...
<font color=#000099><b>protected</b></font> <font color=#000099><b>void</b></font> <b>subscribe</b>() {
messageBus.<b>subscribe</b>(QExceptionMessage.<font color=#000099><b>class</b></font>, <font color=#000099><b>new</b></font> QExceptionDialogService());
messageBus.<b>subscribe</b>(QExceptionMessage.<font color=#000099><b>class</b></font>,
<font color=#000099><b>new</b></font> QStatusBarInfoService(statusLabel, statusLabelPanel));
...
}
Of course, we probably wanna log our exceptions and stack traces to our JTextPane console introduced earlier.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QInfoExceptionService <font color=#000099><b>implements</b></font> QSubscriber<QExceptionMessage> {
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>receive</b>(QExceptionMessage message) <font color=#000099><b>throws</b></font> Exception {
String text = formatter.<b>formatExceptionStackTrace</b>(
message.<b>getException</b>(),
message.<b>getText</b>());
messageBus.<b>publishInfo</b>(
message.<b>getLevel</b>(),
text);
}
}
where we publish a QInfoMessage message courtesy of the publishInfo() convenience method,
and that will get picked up by our QTextPaneConsoleInfoService.
So we subscribe to info and exception messages as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QMessageBusDemo {
JTextPane consoleTextPane = <font color=#000099><b>new</b></font> JTextPane();
...
<font color=#000099><b>protected</b></font> <font color=#000099><b>void</b></font> <b>subscribe</b>() {
messageBus.<b>subscribe</b>(QInfoMessage.<font color=#000099><b>class</b></font>, <font color=#000099><b>new</b></font> QStatusBarInfoService(...));
messageBus.<b>subscribe</b>(QInfoMessage.<font color=#000099><b>class</b></font>, <font color=#000099><b>new</b></font> QTextPaneConsoleInfoService(...));
messageBus.<b>subscribe</b>(QExceptionMessage.<font color=#000099><b>class</b></font>, <font color=#000099><b>new</b></font> QStatusBarInfoService(...));
messageBus.<b>subscribe</b>(QExceptionMessage.<font color=#000099><b>class</b></font>, <font color=#000099><b>new</b></font> QInfoExceptionService());
messageBus.<b>subscribe</b>(QExceptionMessage.<font color=#000099><b>class</b></font>, <font color=#000099><b>new</b></font> QExceptionDialogService());
...
}
...
where we subscribe both status bar and console display services, so that when we publishInfo()
and publishException(), those will show in the status bar and console. In the
case of QExceptionMessage, we will popup QExceptionPanel as well, courtesy of
the QExceptionDialogService.
Progressive Dialogue
Finally, let's implement a "progress panel" with a JProgressBar. Then we can implement a
progress service, and a message to publish to it.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QProgressPanel <font color=#000099><b>extends</b></font> JPanel <font color=#000099><b>implements</b></font> ActionListener {
JDialog dialog = <font color=#000099><b>new</b></font> JDialog();
JProgressBar progressBar = <font color=#000099><b>new</b></font> JProgressBar(0, 100);
JLabel messageLabel = <font color=#000099><b>new</b></font> JLabel();
JButton cancelButton = <font color=#000099><b>new</b></font> JButton(configuration.cancelButton);
...
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>setProgress</b>(Integer progress) {
<font color=#000099><b>if</b></font> (progress == <font color=#000099><b>null</b></font>) <font color=#000099><b>return</b></font>;
progressBar.<b>setValue</b>(progress);
progressLabel.<b>setText</b>(formatter.<b>format</b>(configuration.progressFormat, progress));
}
...
}
Let's implement a progress message, for a progress service.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QProgressMessage <font color=#000099><b>extends</b></font> QStatusMessage {
Integer progress;
QProgressStatus status;
String detailText;
<font color=#000099><b>public</b></font> QProgressMessage() {
}
<font color=#222222><b>... // getters and setters</b></font>
}
Now we can implement the service to receive the above progress message.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QProgressDialogService <font color=#000099><b>implements</b></font> QSubscriber<QProgressMessage> {
QProgressPanel panel = <font color=#000099><b>new</b></font> QProgressPanel();
JDialog dialog = panel.<b>createDialog</b>(QApplicationContext.<b>getInstance</b>().<b>getFrame</b>());
QProgressDialogServiceConfiguration configuration =
<font color=#000099><b>new</b></font> QProgressDialogServiceConfiguration();
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>receive</b>(QProgressMessage message) <font color=#000099><b>throws</b></font> Throwable {
panel.<b>setStatus</b>(message.<b>getStatus</b>());
<font color=#000099><b>if</b></font> (panel.<b>isCancelled</b>()) {
<font color=#000099><b>throw</b></font> <font color=#000099><b>new</b></font> QCancelledException(configuration.cancelledByUser);
}
panel.<b>setMessage</b>(message.<b>getText</b>());
panel.<b>setDetailText</b>(message.<b>getDetailText</b>());
panel.<b>setProgress</b>(message.<b>getProgress</b>());
<font color=#000099><b>if</b></font> (!dialog.<b>isVisible</b>()) {
dialog.<b>setVisible</b>(<font color=#000099><b>true</b></font>);
}
}
}
where if the user has cancelled the process, ie. by hitting the "Cancel" button
on the progress panel, then we throw a QCancelledException
when we receive a progress update message.
Let's test it.
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>testProgress</b>() {
messageBus.<b>subscribe</b>(QProgressMessage.<font color=#000099><b>class</b></font>, <font color=#000099><b>new</b></font> QProgressDialogService());
messageBus.<b>subscribe</b>(QProgressDemoMessage.<font color=#000099><b>class</b></font>, <font color=#000099><b>new</b></font> QProgressDemoService());
messageBus.<b>publishBackground</b>(<font color=#000099><b>new</b></font> QProgressDemoMessage());
}
where QProgressDemoService is implemented as below.
Because this is a long task, it must be executed in a SwingWorker thread
to avoid blocking the EDT.
Therefore we must launch this service using publishBackground(), as above. It receives
a QProgressDemoMessage, which is just an empty class tagged with the empty
QMessage interface.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> QProgressDemoService <font color=#000099><b>implements</b></font> Runnable,
QMessage, QSubscriber<QProgressDemoMessage> {
JFrame applicationFrame = QApplicationContext.<b>getInstance</b>().<b>getFrame</b>();
JPanel glassPane = <font color=#000099><b>new</b></font> JPanel();
<font color=#000099><b>public</b></font> QProgressDemoService() {
glassPane.<b>setOpaque</b>(<font color=#000099><b>false</b></font>);
glassPane.<b>setVisible</b>(<font color=#000099><b>false</b></font>);
glassPane.<b>addMouseListener</b>(<font color=#000099><b>new</b></font> MouseAdapter() {});
applicationFrame.<b>setGlassPane</b>(glassPane);
}
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>receive</b>(QProgressDemoMessage message) <font color=#000099><b>throws</b></font> Throwable {
<b>run</b>();
}
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>run</b>() {
<b>setWaitCursor</b>(<font color=#000099><b>true</b></font>);
<font color=#000099><b>try</b></font> {
messageBus.<b>publish</b>(<b>createProgressStatus</b>(QProgressStatus.startedProgressStatus));
<font color=#000099><b>for</b></font> (<font color=#000099><b>int</b></font> i = 80; i < 100; i += 4) {
<font color=#000099><b>try</b></font> {
Thread.<b>sleep</b>(1000);
} <font color=#000099><b>catch</b></font> (Throwable e) {
}
messageBus.<b>publish</b>(<b>createProgress</b>(i));
<font color=#000099><b>if</b></font> (i % 8 == 0) {
messageBus.<b>publish</b>(<b>createProgressMessageText</b>(<font color=#99006b>"Fetching data for the %d-th time"</font>, i));
}
messageBus.<b>publish</b>(<b>createProgressDetail</b>(<font color=#99006b>"Iteration %d"</font>, i));
}
messageBus.<b>publish</b>(<b>createProgressStatus</b>(QProgressStatus.doneProgressStatus));
} <font color=#000099><b>catch</b></font> (QCancelledException e) {
messageBus.<b>publishInfo</b>(Level.INFO, <font color=#99006b>"Our long process was cancelled by the user"</font>);
} <font color=#000099><b>catch</b></font> (Throwable e) {
messageBus.<b>publishException</b>(e, <font color=#99006b>"An error occurred in our long process"</font>);
} finally {
<b>setWaitCursor</b>(<font color=#000099><b>false</b></font>);
}
}
...
}
In our run() method above, we activate the wait cursor using the setWaitCursor() method below,
and are sure to restore it in the finally clause.
<font color=#000099><b>protected</b></font> <font color=#000099><b>void</b></font> <b>setWaitCursor</b>(<font color=#000099><b>final</b></font> <font color=#000099><b>boolean</b></font> waitCursor) {
<font color=#000099><b>try</b></font> {
SwingUtilities.<b>invokeAndWait</b>(<font color=#000099><b>new</b></font> Runnable() {
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>run</b>() {
<font color=#000099><b>if</b></font> (!waitCursor) applicationFrame.<b>setCursor</b>(<font color=#000099><b>null</b></font>);
<font color=#000099><b>else</b></font> applicationFrame.<b>setCursor</b>(
Cursor.<b>getPredefinedCursor</b>(Cursor.WAIT_CURSOR));
glassPane.<b>setVisible</b>(waitCursor);
}
});
} <font color=#000099><b>catch</b></font> (Throwable e) {
<font color=#000099><b>throw</b></font> <font color=#000099><b>new</b></font> NWrappedRuntimeException(e, <font color=#000099><b>null</b></font>);
}
}
where since this service runs outside the EDT, we use invokeAndWait()
in our setWaitCursor() method.
In addition to the wait cursor, we activate a glass pane over our application frame,
to block any mouse events, eg. clicking on buttons. This forces the user to wait
for the process to complete, or otherwise cancel the process using the progress dialog.
In this case, we will catch a QCancelledException in the run() method further above.
We use convenience methods to create instances of QProgressMessage, for example
as follows.
<font color=#000099><b>protected</b></font> QProgressMessage <b>createProgress</b>(<font color=#000099><b>int</b></font> progress) {
QProgressMessage message = <font color=#000099><b>new</b></font> QProgressMessage();
message.<b>setProgress</b>(progress);
<font color=#000099><b>return</b></font> message;
}
where other fields in the progress message are left as null,
eg. in the above case only the progress is set,
so detailText and status are null. It might be clearer to implement
multiple progress messages, eg. QProgressStatusMessage, QProgressDetailMessage
and QProgressPercentageMessage.
Summary
"Mmmm, donuts - is there anything they can't do?" Homer Simpson
As an exercise in understanding the basics of eventbus.dev.java.net,
we implement a simplistic publish/subscribe bus.
EventBus provides a mechanism for the loose-coupling of components.
Swing listeners need to be attached to specific components, whereas subscribers and publishers
only need to know about the Bus, and not each other.
The EventBus mechanism can be used to address EDT issues. Data,
actions and such, can be be moved between EDT and non-EDT
threads using the bus, for execution in the EDT, or in the background,
as appropriate.
Carrying On
This article might be followed up with Quite Busy Userage, to use the message bus
to implement the following user-related services in a loosely-coupled fashion.
- a logon dialog service.
- a service to authentificate a user logon.
- a distributed message bus, eg. to authentificate users on a remote application server.
- a service for populating and enabling menus for users when they logon, and
disabling the same when they logoff. - publishing context-relevant help along the way, ready if asked for by the user.
- publishing artifacts on the bus for configuration by resources,
should those be available in our Resource Bundle, eg. the translation of menu labels,
help messages and such, into the user's preferred language. - publishing artifacts on the bus for configuration by user preferences,
should those be available, eg. the size and position of the application frame,
table columns, split pane dividers and such, which we might save using the Preferences API.
Here is that Web Start demo again...

(QuiteBusy, Java5, 450k, unsandboxed)
Incidently, this QuiteBusy article was written with the help of Quitewriter, which
also performs the the source code highlighting in the above demo, on the fly.
Plumber's Hack 2: Quitewriter has been queued up for publication for some weeks now,
so hopefully i'll get around to final review
and publication of that next week. Followed the week after by QuiteGooey, which was an
exercise towards a minimal GUI framework - the only problem is that it hasn't
turned out to be as minimal as i planned, D'oh! The above demo uses a slimmed down
superclass called QuiteBasicGooey, which is trivial. Still the jar
is unfortunately currently 450k (and growing)
cos it's got Quitewriter and QuiteGooey bundled in there (270 classes and counting).
Apologies for that. I will be expending some effort to split it up at some point, so...
- Login or register to post comments
- Printer-friendly version
- evanx's blog
- 764 reads





