A technical conference paper is being written (March 2002) to describe the armidale architecture. So, for now the following notes and a commented sample application are provided to help developers understand the overall structure of the armidale system.
armidale applications are simply classes that extend the armidale.api.Application class and provide an implementation for the inherited init() method.
The init() method is the "main" method of an armidale application.
User interfaces are created using the armidale.api.gui.* API. This API provides a set of GUI object factories which create instances of classes that implement specific GUI objects interfaces. For example, the armidale.api.gui.PushButtonFactory creates instances of classes that implement the armidale.api.gui.PushButton interface. The particular implementation returned by a factory depends on the context in which the application is running.
If the application is running in a local context (ie. no internet or intranet involved), then the factory will create an implementation that displays a PushButton on the local machine (using platform specific widgets such as Java Swing).
If, on the other hand, the application is being run by an armidale server in response to a client request via the internet or intranet, the factory will create an implementation that sends messages to the client directing it to display a PushButton on the clients computer.
Application developers do not need to concern themselves with the context in which their applications will be used. They simply write their applications using the armidale API as if they were stand-alone programs.
The armidale GUI API implements a rich set of GUI objects including frames, buttons, lists, images, etc. In most cases, the objects operate in way similar to their corresponding swing widget. One exception is the way in which armidale manages panels.
In armidale, there are a range of panels. Each panel has a fixed layout manager (eg. BorderPanel, BoxPanel, GridPanel, FlowPanel, SplitPanel etc.). Any widget (including other panels) can be located on an armidale panel. This approach contrasts with Swing where the layout of widgets on a panel is managed by a separate layout manager which can be changed at any time.
The armidale server (armidale.api.Server) is a very simple Java program. It listens on a specified port for TCP/IP connections from clients. All clients, including the armidale launcher, make use of the armidale.api.RemoteApplication class to start applications on remote armidale servers. This class is instantiated with the URL of a remote application and any required arguments. When an instance of this class is created, it sends a CONNECT message to the server containing the name of the application the client wants to run and the list of arguments. This application name is in fact the name of a Java class that extends armidale.api.Application, is installed on the server computer, and is visible to the armidale server (ie. on its class path).
When a server receives a CONNECT message from a client, it creates a server context associated with the requesting client that contains details of the connection and an object registry used to track objects distributed across the connection. This context is then used to create an instance of the requested application class. If everything is OK, a CLASS_INFORMATION message is sent back to the client specifying any special requirements the application may have (such as non-standard widgets). If a problem occurs on the server (such as a missing application class), an ERROR message is returned to the client.
When the client receives the CLASS_INFORMATION message it checks that it is able to satisfy the needs of the remote application (eg. availability of any special widgets). If all is well, the client will send a CLIENT_OK message to the server.
When the server receives the CLIENT_OK message, it calls the start() method of the requested application instance (which is a Java Thread). When the application is started, the init() method (described above) is called. The init() method, defined by the application developer, will usually create a user interface and set up callbacks. The application then waits for and processes EVENT messages from the client.
When an application creates a new GUI object such as a PushButton (using the PushButtonFactory), the server implementation of the object sends a CONSTRUCT message to the client. When the client receives a CONSTRUCT message it uses the applicable widget factory to create a instance of the required class within the client context. These client implementations have a unique identifier (registered with the context object registry) and an associated instance of the class within a platform context (eg. swing). These platform context objects display the GUI widgets on the client computer.
When an application modifies an attribute of a GUI object, such as the title of a PushButton, the server implementation of the object sends a SET_ATTRIBUTE message to the client. When the client receives a SET_ATTRIBUTE message, it dispatches it to the applicable object for processing.
Many of the GUI widgets supported by armidale are capable of generating events. PushButtons, for example, are able to generate ACTION events. Events are handled in armidale by callbacks. Callbacks are classes which define methods that are called when an event is generated by a widget. These callback classes are extended by the application programmer to define the required response to events and are registered with a widget of interest.
When a client event occurs (eg. the user clicks a PushButton), an EVENT message is sent to the application running on the server. When the server receives an EVENT message it is dispatched to the associated object for processing. When an object receives an EVENT message it calls the applicable method in each of the callbacks registered with it.
Event messages are only sent from the client to the server if one or more callbacks are registered with the subject widget.
When an application creates an Image object a CONSTRUCT message is sent to the client and processed in the normal way. No image data is involved at this stage. The application can then set the image data by calling the setImageData() or setFile() methods.
When the setImageData() method is used, image data is simply sent to the client and displayed as required.
A more efficient approach is to use the setFile() method which takes the name of a file in the armidale filesystem. This filesystem is simply a directory within the users home directory called .armidale/filesystem and is created during the user installation process. When the setFile() method is used, the name of the file and its date-time stamp are sent to the client. When the client receives such a message, it attempts to open a file with the same name in its local armidale filesystem. If the file exists on the client and has the same date-time stamp, the image is displayed as required.
If the required file does not exist in the client's filesystem, or the date-time stamp is incorrect, then the client will request a copy of the file from the server. The server will then send the file to the client, and it will be saved on the client's armidale filesystem. The contents of the file are then displayed in the normal way.
The above approach implements a simple, but effective image cache.
armidale is able to efficiently display information from very large data structures or models in lists and tables on the client. The technique ensures that only those items that are visible and are needed for smooth scrolling of lists and tables are passed from the server to the client. As a list or table is scrolled by the user, items that are not yet available on the client are displayed as "please Wait...". When the user stops scrolling a request is sent to the server for any items that need to be displayed. When the items arrive at the client the 'please wait..." messages are replaced with the real data.
The armidale system uses TCP/IP to pass messages between clients and servers. Messages use a simple binary format comprising a 32 bit message length and a set of data items. Data items can be primitive type values such as int, byte, float, double etc., or more complex types like String, Color and Font. The armidale.api.context.clientserver.Message class provides an API for constructing and interpreting armidale messages.
The commented source code of a simple application can be found here. In addition, the following armidale classes and packages are a good place to start understanding the operation of armidale.
To learn more about the operation of a typical widget, I suggest you look at the following classes that implement the PushButton widget.You may also need to look at each of their parent classes.