Design Overview
Introduction
The purpose of this document is to communicate the overall structure and design patters used in Antidote, the GUI for Ant. This document is a work in progress, as well as a living document, and it is most likely not be in full synchronization with the source code. Therefore, if there is any doubt, view the source ;-)
Overview
The Antidote architecture design aims to provide a high level of modularity and extensibility. Ideally the components of Antidote will be able to be assembled in different configurations to provide the type of application or plug-in desired.
To achieve this modularity, a high level of decoupling is necessary. The standard UI design approach of providing separation of view (presentation) from model (data) is applied, leveraging the built-in Ant data model where possible, as well as the predefined Swing model interfaces. Furthermore, the architecture is highly event driven, whereby modules communicate via a shared communications channel.
To a large extent, the configuration of application modules is driven by localized configuration files, allowing new modules or data views to be added, as well as providing multi-language support.
The diagram below conveys a high altitude view of the
application's structure. As the application grows, new components
will be plugged in to what will be described as the EventBus
Antidote Component Architecture/Event Bus
+---------------+ +----------------+ +-------------+ +-------------+ | | | | | | | | | ActionManager | | EventResponder | | AntModule | | AntModule | | | | | |(ProjectNav) | |(SourceEdit) | +---------------+ +----------------+ +-------------+ +-------------+ | ^ ^ ^ | | | | ActionEvent EventObject AntEvent AntEvent | | | | v v v v /---------------------------------------------------------------------\ / \ < EventBus > \ / \---------------------------------------------------------------------/ | ^ ^ ^ | | | | EventObject ChangeEvent BuildEvent EventObject | | | | v | | v +---------------+ +----------------+ +-------------+ +--------------+ | | | | | | | | | Console | | ProjectProxy | | Ant | | (Your Module)| | | | | | | | | +---------------+ +----------------+ +-------------+ +--------------+
The backbone of the application is the EventBus. Any
component of the application can post events to the
EventBus
. Components that wish to receive events are
called BusMember
s.
The EventBus
will dispatch any object of type
java.util.Event
, which means that Ant BuildEvent
objects, as well as AWTEvent
objects can be posted (if desired). A
new class of events called AntEvent
is defined for Antidote
specific events, which have the additional capability of being
canceled mid-dispatch.
Each BusMember
must provide a BusFilter
instance,
which is the members' means of telling the bus which
events it is interested in. This allows a BusMember
to,
say, only receive AntEvent
objects.
When a BusMember
registers itself with the
EventBus
, it must provide a (so called) interrupt
level which is a integer value defining a relative ordering
for dispatching EventObject
s to BusMember
s. The
purpose of this is to allow certain BusMember
instances
to see an event before others, and in the case of AntEvent
objects, keep the event from propagating onward. The
EventBus
class defines the interrupt level constants
VETOING=1
, MONITORING=5
, and RESPONDING=10
to
help define categories of members. The implied purpose being that:
VETOING
: Listens for certain types of events, and may process them in a non-default manner to determine if the event should be canceled before being dispatched to theRESPONDING
group.MONITORING
: Just listens for events, like a logger or status monitor.RESPONDING
: Process events in a default manner, knowing that the event has passed anyVETOING
members.
Within a specific interrupt level, the order in which members will
receive events is undefined. A BusMember
may be registered
at a level that is +/- of one of the defined levels, as long as it
follows the constraint MONITORING <= interruptLevel <=
MAX_INTERRUPT
.
Actions and ActionManager
Extensive use of the javax.swing.Action
interface is
made for defining the set of menu and tool bar options that are
available. The configuration file action.properties
exists to define what should appear in the menu and toolbar, how
it is displayed, and the Action
command name that is
dispatched when the user invokes that action. A class called
ActionManager
exists for not only processing the
configuration file, but also for dispatching invoked action events
to the EventBus
, and for controlling the enabled state of
an Action
. When a new menu item or toolbar button is
desired, first it is added to the action.properties
file,
and then the code to respond to it is added to the
EventResponder
(see below).
Commands and EventResponder
At some point in the stages of event processing, an event may
require the data model to be modified, or some other task be
performed. The Command
interface is defined to classify
code which performs some task or operation. This is distinct from
an Action
, which is a user request for an operation. A
Command
class is the encapsulation of the operation
itself.
When an Action
generates an ActionEvent
, the
event is posted to the EventBus
which delivers the event
to all interested BusMember
s. It eventually makes it to
the EventResponder
instance (registered at the
RESPONDING
interrupt level), which is responsible for
translating specific events into Command
objects, and
then executing the Command
object. For example, when the
user selects the "Open..." menu option, an ActionEvent
is
generated by the Swing MenuItem
class, which is then
posted to the EventBus
by the ActionManager
. The
ActionEvent
is delivered to the EventResponder
,
which converts the ActionEvent
into a Command
instance. The EventResponder
then calls the method
Command.execute()
to invoke the command (which displays a
dialog for selecting a file to open).
When adding new Action
s or general tasks to the
application, a Command
object should be created to
encapsulate the behavior. This includes most operations which
modify the state of the data model.
The purpose of this encapsulation is to allow the clean separation of making a request, and servicing a request. Due to various conditions in the application state, the actually response to a request may change, as well as who services it. This design approach facilitates that.
Data Model and Views
NB: This part of the architecture is not fleshed out very well. There
needs to be a discussion of the degree to which the Antidote development
should be able to impose changes on the Ant data model, and to what level
that model should be mirrored in the Antidote code base. The coupling
between them should be kept low, and at the same time changes to one should
affect the other minimally. Still, features like property change events and
bean introspection (or BeanInfo) may be needed to be added to the Ant data
model. Right now the data model is encapsulated in the package
org.apache.tools.ant.gui.acs
(where "acs
" stands for "Ant Construction Set").
Application Context
In order to keep the coupling among application modules to a
minimum, a single point of reference is needed for coordination
and data sharing. The class AppContext
is the catch-all
class for containing the application state. Most modules and
Command
classes require an instance of the
AppContext
class. Because all state information in
contained in an AppContext
instance, multiple instances
of Antidote can run inside the same JVM as long as each has it's
own AppContext
. (Interestingly, two instances of the
Antidote could conceivably share an AppContext
instance
through RMI, allowing remote interaction/collaboration.)
Configuration and ResourceManager
Full "i18n" support should be assumed in modern applications,
and all user viewable strings should be defined in a configuration
file. For Antidote this configuration file is
antidote.properties
, which is located (with other UI
resources) in the sub-package "resources".
To aid in the lookup of text properties, as well as other
resources like icons, a class called ResourceManager
is
defined. There are various convenience methods attached to this
class, which will likely grow to make looking up configuration
values as easy as possible.
The organization of configuration properties is based on the
fully qualified path of the class that requires the property. For
example, the "about" box contains a messages, so it looks for the
property "org.apache.tools.ant.gui.About.message
" for the text
message it should display. Therefore, the ResourceManager
method getString()
takes a Class
instance as
well as a String
key. Please see the
ResourceManager
documentation for more information. Given
this support, no user visible strings should appear in the source
code itself.