Jupe is structured in different layers. This architecture is partially imposed by the used frameworks and has several advantages. It leads to orthogonality, one of our main design goals, because each layer depends at most on two other layers. This in turn encourages extensibility and reusability since some layers can be replaced to fit other needs and others can be reused for the mentioned extensions. Figure 4.1 shows the five layers of Jupe and their connections.
The normal lines signify dependencies. The model layer depends on UML2, whereas the editparts depend on the model as well as on the figures. The dotted lines have special meanings that will be explained.
The source layer can be any library giving access to the source code that is represented in the UML diagrams. It basically provides the possibility to read and write source files and the containing directory structures. In case of the Java support it is the JDT (see 3.1), which gives convenient access to the Java source in the Eclipse workspace.
We mainly used the part of JDT that is build around the interface IJavaElement. It has many derived interfaces where each presents a part of Java source code. Figure 4.2 shows an extract of it.
ICompilationUnit presents classes and interfaces and normally has a corresponding .java file. IPackageFragment is a package, e.g. "org.jupe" and usually has a corresponding directory in the file system. As they are both interfaces they can't be created directly, but using the JavaCore class. It provides create() methods that construct them from a given IFile or IFolder, the representations of files and directories in the Eclipse API. Additionally, JDT provides IMethod to access methods and IField for attributes of a class or interface. Each interface offers getters to obtain the most important properties, like the name or visibility.
The mentioned interfaces are handlers, so they can exist without a corresponding file or directory in the physical file system.
As described in section 3.2, UML2 offers an implementation of the UML metamodel. The public API mostly consists of interfaces for the different parts of UML. We use a relatively small subset of it which is partially presented in figure 4.4. Element is an abstract base interface that is used as common superclass for most other interfaces. The elements of UML2 are structured as composites. They can have children, called members, and usually have a parent element. The root of the composite structure is Model. The Jupe plugin uses exactly one instance of it with the help of a singleton mechanism. Operation describes a method of a class or interface. The other interfaces also present parts of a UML model and are self-explaining.
In order to use UML2 we developed some convenience classes. Firstly, you find UML2ModelFactory to create the different elements. UML2 provides several ways to create them, but none of them works for all elements we use. So we provide for example methods to create new instances of Class in a specified package or in the default package. Moreover, we introduced UML2ModelRoot. It holds the reference to the Model and offers methods to find types using their name, e.g. for packages and classes. Besides, you can use some convenience methods, e.g. to get the absolute name of an element.
As illustrated in figure 4.3, the UML2 model presents only a subset of the source code. The main reason is performance. The Jupe plugin aims to be usable for large projects with more than 100.000 lines of code. Presenting them completely in UML2 when only a small part is actually in class diagrams is obviously no good idea. Apart from synchronization questions (described in chapter 5) this poses the problem of references from elements in UML2 to elements that are not included. E.g. class A is in UML2 and has an attribute of type B which is in the source, in a used library or even part of the programming language. The solution we found is a hidden package. It contains primitive types like boolean and int, as well as all referenced classes and interfaces that are not part of our UML2 model.
UML2 provides a mechanism to react on changes in its elements following the observer pattern. Each element can act as a so-called Notifier that sends messages to listeners which are called Adapters. We are using that functionality to connect the UML2 layer with the model layer without any dependency on it. As described in section 4.3, the instances of model classes 4.1 are bound to a specific diagram, whereas the instances of UML2 are unique. E.g. in a situation with two opened class diagrams containing the same class, there is only one instance of Class, but two instances of the corresponding element in the model. The observer mechanism cares about updating one diagram if the other one has been changed. In consequence, it allows the decoupling from UML2 layer and model layer (and thus orthogonality), as well as the update of several representations of UML2.
The UML2 layer is independent from the diagram type. As the source layer, it can be reused for any other diagram type including the already developed convenience classes.
The model layer is developed for a specific diagram type, class diagrams in our case. We divided it into nodes and connections. All elements are derived from ModelElement. As one node can contain others (e.g. a package usually contains classes), the nodes are structured as a composite with children and parent relations. The root element of this structure is ClassDiagram. The connections are referenced by their source and target nodes.
This layer fulfills three main tasks. Firstly, it stores all necessary graphical information which can't be found in UML2. These are defined in NodeElement as location and size. Additionally properties like colors and so on could be stored here, too.
Moreover, all elements are IPropertySources, an interface defined in the Eclipse API. That means that they support a number of properties, which can be accessed with getters and setters using property IDs. The concept is widely used in Eclipse, which allows us to reuse functionalities based on it. E.g. we use a property table to present the characteristics of elements. Elements define three types of property IDs. The layout properties like location and size, which are stored in the model. Further, the UML characteristics, like the visibility of a class, that are in fact stored in UML2. Each model element has a reference to its corresponding UML2 element, which is asked whenever such a property is used in the model. The third type of properties is not visible to the user. These IDs serve to connect the model layer with the editparts.
Finally, the model layer acts as application model for GEF, so that its adoption is also imposed by the use of GEF. As we will see in section 4.4, each model element has a corresponding editpart. Once again, the layers are connected via the observer pattern. As all model elements are IPropertySources, its listeners have to be IPropertyListeners. The property IDs described above are used to indicate to the editparts the kind of event that has occurred in the model.
The model contains commands. These are working steps like creating elements, adding one element to another, moving nodes, reconnecting connections and so on. The command classes derive from a class in the Eclipse API, which allows us to use its redo/undo functionality.
Figure 4.6 shows an enhanced version of the relationship between source code, UML2 and model layer. It demonstrates the case of two opened class diagrams that have common elements. These common elements are represented by two instances of each element that reference the same UML2 element. Each class diagram corresponds to an instance of ClassDiagram. The figure is not absolutely correct, since normally the union of all models is equal to the UML2 model.
GEF employs a model-view-controller architecture as illustrated in figure 4.7. The model layer has the role of the model that provides all data to display it in the view with the help of the controller. That part is delivered by GEF which also provides interaction with the user. To use GEF and its architecture some conditions have to be accomplished.
First of all, you need editparts. Editparts present an intermediate layer between the model and the figures, that finally display and layout the diagrams. Each model element has a corresponding editpart and figure. Editparts are responsible for creating and deleting figures, as well as adapting them when the model changes. They are created with the help of PartFactory which constructs the correct editpart for a given model element.
Editparts derive from the IPropertyListener interface and add themselves as listeners of their corresponding model element. Thus, they get called when changes in the model occur and can propagate them to the figures.
Another requirement is that so-called edit policies are defined by the editparts. They are used as a reaction on requests from the user, e.g. to create a new element or to reconnect a connection. The policies also serve to fix the layout of the view, as well as its behaviour. Behavioral edit policies describe for example which editparts can be used as container for others, and between which editparts connections can exist. Several edit policies build and execute commands that are defined in the model. Their execution changes the model which again may cause an adaption of the view.
Figures are the third condition for the use of GEF. They build the lowest layer of the Jupe architecture. They are based on Draw2D. Created and controlled by the editparts, they simply draw the diagrams. The figures also form a composite structure with ClassDiagramFigure as root element.
http://jupe.binaervarianz.de