FractalGUI is a graphical editor for Fractal component configurations.
Code structure
FractalGUI is organized in many packages. The main package is the model
package, which defines a model for Fractal component configurations. All other
packages are based directly or indirectly on it. The repository
package defines an API for repositories of Fractal configurations, and provides
a basic implementation of this API. The other packages contain the code that
implement FractalGUI's features. Each package implements a feature, and is made
of three optional sub packages called model, view and control (in reference to
the Model View Controller design pattern).
- The model package defines interfaces such as
Configuration,
Component,
Interface or
Binding, which
represent the main concepts of the Fractal model. It also provides two
implementations of these interfaces, one for normal components, and one for
shared components. Finally it defines a
Factory interface,
and an implementation of this interface, which can be used to create objects
that implement the above interfaces.
- The repository package contains two sub packages:
- The api sub package defines an API for configuration
repositories.
- The lib sub package provides an implementation of this API, based
on the Fractal ADL, and one file based storage (one configuration per file).
Future versions of FractalGUI may provide of implementations of this API, for
example based on a database storage.
- The clipboard package provides a clipboard feature:
- The model sub package defines a model for the clipboard, and
provides an implementation of this model.
- The control sub package provides very simple Swing actions to
control the clipboard model. These actions can be added to menus or
toolbars.
- The dialog package provides a configuration view based on text
fields and tables:
- The model sub package implements text field and table models, as
required by Swing, which are based on a configuration model.
- The view sub package implements the view itself, i.e. the code to
assemble the various Swing components that make up this view.
- The control sub package provides a controller that modify the
dialog model, in reaction to events produced by the dialog view.
- The graph package provides a configuration view based on a graph
representation of components:
- The model sub package implements a model for component graphs. The
main role of this model is to assign a location and a size to each component of
a configuration.
- The view sub package implements the view itself, i.e. it contains
the code to draw a configuration as a graph of components (represented by boxes)
and bindings (represented by lines).
- The control sub package provides controllers that modify the
graph model, in reaction to (mouse) events produced by the graph view.
- The history package provides a navigation history manager:
- The model sub package defines a model for the history, and provides
an implementation of this model based on two lists.
- The control sub package provides very simple Swing actions to
control the history model. These actions can be added to menus or toolbars.
- The menu package implements actions of the FractalGUI menus.
- The control sub package sub package provides very simple Swing
actions to control the configuration model. These actions can be added to menus
or toolbars.
- The selection package provides a model for the selection.
- The model sub package defines a model for the selection, which can
be a component or an interface, and provides an implementation of this
model.
- The status package provides a view that displays the status of
components and interfaces.
- The view sub package implements a view, based on a JPanel, that
displays details about the status of the component or interface above which the
mouse cursor currently is, in the graph view.
- The toolbar package provides a configuration view based on a
toolbar.
- The view sub package provides a trivial sub class of the JToolbar
class, that just change the insets of the toolbar's buttons.
- The tree package provides a configuration view based on a JTree:
- The model sub package implements a tree model, as required by
Swing, which is based on a configuration model and a selection model.
- The view sub package provides some tree renderer classes to
personalize the default JTree view.
- The undo package provides an undo and redo manager:
- The model sub package defines a model for undo and redo, and
provides an implementation of this model based on two logs. These logs contain
a list of changes that where made on the configuration, instead of the complete
successive states of this configuration (in order to save memory).
- The control sub package provides very simple Swing actions to
control the undo and redo model. These actions can be added to menus or
toolbars.
Runtime structure
Overview
FractalGUI is a Fractal application, i.e., it is made of Fractal components. The
root component of FractalGUI is a composite component that corresponds to the
main window of FractalGUI. This composite component contains the configuration
and selection model components, and two composite components that correspond to
the menu bar and to the content pane of the main window. The menu bar component
contains sub components that correspond to the menus. These menu components
contains sub components that correspond to the menu items. The content pane
component is also composed of several levels of sub components. And all these
components are Fractal components. The best way to discover FractalGUI's
architecture is to use FractalGUI itself to browse its own configuration (for
this you just have to open, with FractalGUI, the fractalgui-editor.fgl
file, in the examples/fractalgui directory).
Architectural patterns
The main architectural pattern used in FractalGUI is the Model View Controller
(MVC) pattern. Since there are many small variations of this pattern, we
describe here the precise MVC pattern used in FractalGUI (see Figure 1).
Figure 1: the Model View Controller pattern.
The basic MVC pattern used in FractalGUI involves three Fractal components:
- the Model component provides a model interface M, and can be bound to zero
or more model listener components, i.e. components that provide a MListener
interface.
- the View provides a graphical view of the model. The view component listens
to the model, in order to redraws itself each time the model changes. The view
component is therefore a model listener component that has a binding to the
M interface provided by the model component, in order to read the content of
this model (the view must not modify the model directly). The view component
can be bound to zero or more view listener components, i.e. components that
provide a VListener interface.
- the Controller component listens to the events produced by the view
component, and reacts to these events by modifying the model. The controller
component therefore provides a VListener interface, and has a binding to the
M interface provided by the model component, in order to read and modify this
model.
A model component M1 can be synchronized with another model component M2 by
using the Observer pattern (see Figure 2):
- the M1 component provides the M2Listener interface, in order to listen to
the M2 model.
- the M2 component provides the M1Listener interface, in order to listen to
the M1 model.
Figure 2: synchronization of two model components.
When M1 is modified, for example by a controller component for M1, M2 is
notified through its M1Listener interface. It can then update its own state,
which will notify its listeners, such as a view on the M2 model, but also the
M1 component. At this stage the M1 component must ignore this notification,
otherwise an infinite notification loop will occur between M1 and M2.
Symetrically, when M2 is modified, for example by a controller component for
M2, M1 is notified through its M2Listener interface. It can then update its own
state, which will notify its listeners, such as a view on the M1 model, but also
the M2 component. At this stage the M2 component must ignore this notification,
otherwise an infinite notification loop will occur between M1 and M2.
This synchronization pattern is used in many places in FractalGUI, sometimes
with a bidirectional synchronization, sometimes with a unidirectional
synchronization (i.e. M1 updates itself when M2 changes, but M2 does not modify
itself when M1 changes). For example:
- the tree model required by the JTree view is synchronized with a
configuration model.
- the table models required by the JTable views (included in the dialog
view) are synchronized with configuration model.
- the undo model listens to a configuration model, in order to update its
internal logs each time the configuration changes. The undo model also updates
the configuration model when a change is undone. But the configuration model
does not listen to the undo model: this is a unidirectional
synchronization.
- the history model listens to a configuration model, in order to update its
internal state each time the root component of the configuration changes. The
history model also updates the configuration model when a go next or go previous
operation is performed. But the configuration model does not listen to the
history model: this is a unidirectional synchronization.
This synchronization pattern can also be used for view components. For
example the status view component listens to the graph view component, in order
to update itself each time the mouse moves inside the graph view.
The benefit of using these architectural patterns is modularity: for example
a model, a view or a controller component can be replaced with another one,
whole features (such as the undo or history managers) can be removed without
any impact on others, and so on. The FractalGUI browser illustrates this
modularity: by reusing some components of the FractalGUI editor, and by
configuring and assembling them in a different way than in the FractalGUI
editor, one can obtain the FractalGUI browser, which, as its name implies, only
allows configuration browsing, but not configuration editing.
Configuration model
This section gives some details about the configuration model and its
implementation, by using the Fractal HelloWorld example. The HelloWorld
configuration, depicted in Figure 3, is represented in the FractalGUI model
with the objects and references depicted in Figure 4.
Figure 3: the HelloWorld configuration
Figure 4: FractalGUI's model of HelloWorld configuration
Each component is represented by a
Component object,
each client interface by a
ClientInterface
object, each server interface by a
ServerInterface
object, and each binding by a
Binding object. The
configuration itself is represented by a
Configuration
object that references the root component of the configuration. Each component
stores the list of its sub components, the list of external server interfaces,
and the list of its external client interfaces (in addition to various
attributes such as the component's name or type). Each interface has a reference
to its complementary interface (both external and internal interfaces are always
represented in the FractalGUI model, even for primitive components). Each client
interface has a reference to a binding object (if this interface is bound),
which itself has a reference to a server interface.
The model also contains all the inverse references: each component has a
reference to its "owner" configuration, and a reference to its parent component;
each interface has a reference to its owner component, each binding has a
reference to the client interface that references it, and each server interface
has the references of all the binding objects that reference it. This redundancy
is useful to easily navigate from one element of the model to any other. It is
also useful to undo some changes in the model. For example, if the client
component is "deleted" (which also "deletes" the bindings to and from this
component), only three references are really removed from the model (namely the
reference to the binding object on the left, the inverse reference from the
server interface of the server component to the other binding object, and the
reference from the root component to the client component - see Figure 5).
This deletion can then easily be undone (including the deletion of the two
binginds), by reconstructing the removed references from the redundant ones
(that were left unchanged during the deletion).
Figure 5: effect of the deletion of the client component
Although references between the elements of the model are redundant, the
state of these elements is not redundant. For example the name of an
interface, which is equal to the name of its complementary interface, is stored
only in one of these two interfaces, namely in the external one. In other words
the getName (resp. setName) method of an internal interface
just calls the same method on the complementary interface, which itself returns
(resp. modifies) the name stored in this external interface. The consistency
of the two names is therefore always automatically ensured: there is no need to
manually update both names when the name of one of the interface is changed.
This non redundancy principle is also applied to ensure the consistency of
collection interfaces (see Figure 6). A collection of interfaces is
represented by a master collection interface and several slave collection
interfaces (and by the complementary interfaces of the master and slave
interfaces). The external master collection interface contains the list of its
external slave interfaces, and each external slave interface contains a
reference ot its external master collection interface (this is an application of
the reference redundancy principle; note however that the internal master and
slave collection interfaces do not contain master and slave references to
each other). The prefix of the names of the slave collection interfaces, which
is equal to the name of the master collection interface, is stored only in the
external master collection interface.
Figure 6: representation of collection interfaces
This master-slave pattern is also applied to ensure the consistency of shared
components (see Figure 7). Indeed a shared component, i.e. a component that
belongs to several parent components, in represented in FractalGUI's model by
several Component
objects, one per parent component of the shared component (therefore, in
FractalGUI, by construction, any
Component object
has only one parent). In order to ensure the consistency of all these instances,
the shared component's state is stored in only one of them, called the master
component. All other instances are slave components of the master component. As
for collection interfaces, the master component stores the list of its slave
components, and each slave component has a reference to its master component.
Each slave component has its own
Interface objects,
but only for external interfaces. Each of these interfaces references the
corresponding interface of the master component, which is called its master
interface (warning: this is not the same as the master collection
interface). Indeed the state of these interfaces in not stored in these
interfaces, but in the master interface (except the owner reference; in fact
this owner reference is the reason why slave components must have their own
interface objects).
Figure 7: representation of shared components
The BasicXXX classes, in the model package, implement
the interfaces of FractalGUI's model for normal components and interfaces,
including collection interfaces. External, internal, master and slave collection
interfaces are all implemented by the same
BasicClientInterface
and
BasicServerInterface
classes, which provide one constructor per interface type (i.e. external,
internal or slave collection). The SharedXXX classes, as their
name imply, implement the interfaces of FractalGUI's model for shared
components, and for their interfaces.