Overview of Notifications for Java

Last revised: 08Feb2002 GLG

The principal class is glguerin.notification.Notification, which encapsulates a collection of modalities for notifying the user of some state or condition. It is supported by glguerin.notification.Platform, which encapsulates the names of platform-specific Notification implementations .

This overview is largely derived from the doc-comments for Notification, Platform, and the concrete Mac OS implementations. The overview also explains some of the design rationale behind certain features of Notification, including waitForResponse() and getCapabilities().

Table of Contents

Design Origins

The basic design and capabilities of the Notification class are patterned after the Notification Manager in Mac OS. The Notification Manager is described on-line in HTML and PDF forms.

The Notification class takes an object-oriented approach to notifications. It also abstracts the concepts and capabilities away from any particular implementation, and allows for many alternative implementations to co-exist. The implementations (imps) can be platform-neutral or platform-specific. They can have different combinations of capabilities, depending on how the imp is actually written.

Since the Notification class is abstract, you must select a concrete imp from the ones provided, or write and select your own imp. The Platform class is a simple repository that binds platform identifiers to concrete imps. By default, the "os.name" property is the platform identifier, and the only bindings are for Mac OS (classic) and Mac OS X. There is also a do-nothing imp for all other platforms. The do-nothing imp is concrete, but it doesn't do anything and has no real capabilities. You can add or remove bindings and imps for other platforms, or alternative imps for the Mac platforms.

The Notification class follows the Abstract Factory and Factory Method design patterns in its static methods SetFactory() and MakeOne(). You assign the class name of a concrete Notification imp with Notification.SetFactory(), typically when your program is starting up. Thereafter, you call Notification.MakeOne() to instantiate new Notifications. This allows for easily configurable Notification imps, and puts instantiation control in one place.

About the Notification Class

The purpose of a Notification is to notify the user from a background state. A background state is simply any state where a Dialog might not appear as the front window, or where some other user-interactive mechanism might not be presented in the proper context. Depending on how far you want to expand the definition, a background state could also mean that an otherwise invisible process or processor needs to make something known to a user or other agent. Thus, you could expand notifications to include the state of otherwise headless servers, or remote sensors, or whatever. This expanded definition is left for other implementations.

Depending on the actual implementation, a Notification may include non-intrusive visual indicators, an audible indicator, or an alert with a brief to moderate-length text of your choosing. These are classified as polite, audible, and alerting notifications, respectively. These modalities are not mutually exclusive, and you can mix them as you wish in a single notification, subject to the concrete implementation's capabilities. You can determine the capabilities of a specific Notification with the getCapabilities() method.

Both interactive and non-interactive applications can use Notifications. Non-interactive apps can use Notifications to show status or completion indicators, among other things. Interactive apps can use Notifications asking the user to bring the app to front, or to tell of the completion of a long-running task.

The typical life-cycle of a Notification is instantiation and preparation, followed by posting and rescinding. You can reconfigure Notifications between different postings. Or you can make multiple Notifications and configure them once, then post, rescind, and repost as needed.

Posting a Notification does not block the thread that calls post(), even when it's posting an alerting Notification. Execution returns from post() immediately, and the notifications requested are shortly thereafter presented to the user, still without blocking. At some later time of your choosing, you rescind the Notification, according to the design of your app and your reasons for using a Notification, . When you rescind the Notification, all its presented elements are also removed or rescinded (though certain aspects of this are subject to the implementation).

Notifications are instantiated with the factory method Notification.MakeOne(). First, though, you tell it a class to use with SetFactory(). You can determine which factory to use either by using Platform.selectFactoryName(), or with your own code that selects a class name.

Once you have a concrete Notification instance, you will typically discover its capabilities using the getCapabilities() method. You will often do this only once, in your program's initialization phase, since all the concrete instances of Notification will normally have identical capabilities. You then use the available capabilities to decide how your Notifications will be presented to users, or what configurable options you will make available. The getCapabilities() method returns a java.util.Dictionary whose keys are Strings. The recognized keys are predefined as public static final Strings in Notification. See the API docs for additional details.

Knowing what capabilities a Notification has, your program then configures it using any assortment of the following methods:

Each method returns true if the modality was actually set, or false if it wasn't. This lets your program check each modality to see whether it will really be presented as requested or not.

You will usually configure all your Notifications before you need to post them, rather than on-the-fly at the moment you decide to post. This approach ensures that your program has all the Notification capabilities it needs beforehand, or that users can decide ahead of time exactly how to be informed. When you want to present the Notification, you simply call its post() method. Since post() is non-blocking, control immediately returns to the thread that called post(). A short time later, the Notification's contents will be presented to the user.

After a time interval, or perhaps after detecting a user-response to an alerting Notification, you call the Notification's rescind() method. This removes any remaining elements that might still be presented to the user. Also, if you want to reuse a Notification, you must rescind it first.

Notifications are reusable and reconfigurable. You can't reuse or reconfigure a Notification while it's posted, but you can rescind it and then do so. You can also create multiple Notifications, configure them, then post and rescind them in any order or combination. You are not limited to a single Notification instance, nor to only one Notification posted at a time. Moderation suggests, however, that a cacophony of concurrently posted Notifications is to be avoided.

About the Notification Modalities

You configure a Notification to present certain elements (called modalities) to the user. Some of these modalities are presented only once, no matter how long a Notification remains posted or which implementation or platform is active. For example, an alert is presented only once, and a sound is played only once, regardless of the imp or the platform.

A polite notification typically consists of two elements: a marker and an icon. These are independent capabilities in the Notification class, though certain combinations may be preferred when a Notification is presented on a specific platform. You should not assume that both capabilities should always be used on all platforms, or that both are always available together.

Icons are identified either by name or by number. The provided Mac OS Classic imp only supports numbered icons, which are stored as small-icon suites in a resource-file. You can refer to icons from multi-sized icon-suites, though Mac OS Classic only uses small-icons. Typically, you will merge the icon resources into the double-clickable app file.

Sounds are also identified either by name or by number. The provided Mac OS Classic imp only supports numbered sounds, which are stored as sound resources (ResType 'snd ') in a resource-file. Typically, you will merge the sound resources into the double-clickable app file.

Alerting Notifications typically have a limited text length and a limited character-set. You can determine both limits using getCapabilities(). Alerts may be modal, semi-modal, or non-modal, depending on the platform and imp. There's currently no capability that tells you what kind you have, so you either have to design for all eventualities or tailor what you do to a specific platform or imp. The latter is a distinctly inferior design decision.

Alerting Notifications may or may not be able to communicate a user response back to the posting app. There is a specific key you can query for this capability. Both Mac OS imps provide this capability, but you should always design and test your apps so they behave reasonably even if the capability is missing.

Only an alerting Notification will be able to signal a response with waitForResponse(). That is, non-alerting Notifications will never return true from waitForResponse(), regardless of platform or imp. In fact, calling waitForResponse() on a non-alerting Notification will throw an IllegalStateException, so you should probably call isAlerting() before you try waitForResponse().

The behavior details of some modalities is implementation-dependent or platform-dependent. For example, on Classic Mac OS the marker is not presented when the posting app is already in front, but the marker IS presented on Mac OS X, regardless of whether the posting app is in front. A single modality may also encompass a wide range of appearances and behaviors across platforms and imps. The marker on Mac OS Classic, for example, is simply a diamond marking the app's menu-item in the upper-right-corner Application menu. On Mac OS X, however, the marker is a bouncing Dock icon. You should keep this in mind especially when you don't provide user-configurable mechanisms for notifications. Some people think bouncing Dock icons are really annoying.

Design Rationale for Notification.getCapabilities()

The need for capability discovery is not new. Mac OS provides it with the Gestalt Manager and other means. Curiously, Java seems a bit light on capability discovery, instead tending to do "least common denominator" feature-sets and often ignoring even the possibility of additional capabilities.

For Notifications to be useful (or even implementable) across platforms, there had to be a way to let an implementation tell a caller what it can and can't do. I toyed with a couple of alternative representations, including a BitSet where each set bit represented a capability. In the end, I chose a java.util.Dictionary approach with named keys, because it was the most flexible and extensible. A BitSet can only represent present/absent distinctions, or at best a collection of boolean properties. It can't represent numbers or names. Also, a Dictionary can provide related parameters, for example the encoding-name and the length-limit of alert text, and Enumerations. You can't do any of that with a BitSet.

Even considering only Mac OS imps, it's a good thing there's a getCapabilities() method. As I discovered during my testing, Mac OS X does not support icons or sounds in Notifications. I've provided an imp (XNotification) that has the features, but they still don't work. Or at least they don't work on 10.1 or earlier. They may work at some point in the future, but Apple isn't saying if or when.

Design Rationale for Notification.waitForResponse()

Experienced Mac OS programmers may wonder why there's no arbitrary callback in the Notification class, since that's the underlying capability provided by the Notification Manager. There are a couple of reasons behind the Java design, but the primary consideration was preventing fatal usage errors. With an arbitrary callback, the implementation code is free to do anything. For example, you could put up a Dialog, call a JDBC function, or retrieve a URL. All of those would certainly result in a deadlock. Taking that into consideration, and also considering what the user-response callback is communicating, I realized there was a safer option.

Consider exactly what the callback is communicating. When the callback procedure is executed, it means exactly one thing: the user responded to an alert. The callback procedure IS ONLY CALLED when there's an alert. It is not called when there isn't an alert. Therefore, you can't reliably use the callback to remove the notification. Besides, if you have an icon, you don't want to remove the notification when the user responds anyway. Doing so would also remove the marker and the icon, thereby defeating the whole purpose of those modalities.

So seeing that the callback procedure is limited, and also wishing to prevent deadlocks, I made a design based on waiting for a response. That is, rather than give programmers an arbitrary callback or a listener model, I gave them a method that they can call from an arbitrary Thread. If they want the Thread to block until a response appears, they call it with an appropriate arg. If they just want to poll the Notification, they call it with a different arg.

Thus, we have waitForResponse(), which takes an argument telling how long it should wait. You can use this one method to poll for a response, to wait indefinitely, or to wait only for a limited time interval. If a user response arrives, then the waiting thread resumes execution. If a response doesn't arrive, then that too is communicated, and a thread can react accordingly. Even if a response arrives before your thread polls or waits, the response result will be there for it to see.

Unfortunately, I still can't prevent deadlock completely. If your code's main thread or its event-listeners call waitForResponse() with a wait interval, then you can still get deadlock because the main AWT or Swing event-delivery thread will block. At least I've given the method a name that tells you it can block. So if you can't afford to have a Thread block, then don't call waitForResponse() with a non-negative arg.

The implementation of waitForResponse() is accomplished with the class glguerin.util.Gate. That makes the capability reuseable, and I do reuse it in NoteToyFrame to control the background thread that does the posting and rescinding. You can also reuse it in your own code, where it makes sense.


To Greg's Home Page
To Greg's Software Page