Contents
- Introduction
- Optimum Route Application
- Critical Path Application
- Adding Constraints to the CP Application
- Summary
Introduction
Armed with the knowledge of using Eclipse, EMF, GMF and XMF in to develop rich applications, this tutorial describes how this technology arsenal can be used to develop more complex applications. These applications center around the graph structure of nodes and edges commonly found in a wide spectrum of applications. It is often useful to ask questions or to analyze properties of graph structures. The first application in this tutorial calculates the optimum route between two nodes in a graph. The second application identifies the critical path between two nodes in a graph assuming that each edge is given a weighting, this type of application is often found in project planning software. This application also shows how XMF can be used to add constraints to a graph model to analyze whether key properties are observed.To top.
Optimum Route Application
The application presented in this first section calculates the
optimum route through a graph of nodes and edges. This type of
application has many uses including, for example, train journey
planning and calculating which route involves the least number of
station stops. The screenshot below shows the diagram editor for
a configuration of train stations which are represented as nodes and
train station connections which are represented as edges (note that
these are not accurate to reality - please do not use it as a basis for
travel plans!!)
This route application supports the user in indicating their
boarding station and their destination station via menus on the station
nodes. The user is then able to ask the application which route
between the two stations involves the least number of stops. The
screenshot below shows the result of this analysis when the start
station is York and the
destination station is Hull.
The EMF model for the optimum route application is shown below, this
is used as a basis for the GMF editor (show above) which supports the
diagramming of nodes and edges. The EMF model is also used to generate
the code stubs for writing the XMF processing, one of these is
partially shown in the lower editor below. The process for
generating XMF code stubs from EMF models is described here.
As described in the introductory tutorial here, the relationship
between Eclipse code written in Java and XMF code is service
based. This involves XMF offering parameterized services which
can then be invoked in Java code. The route application offers
three services setStart and setTerminal node and getPath which calculates the
optimum path. The first two services simply involve making a
record of the start and terminal node in a known place.
@Service setStart(node)
Root::start := node
end;
The getPath service is
more involved.
@Service getPath(graph)
// Define a local recursive operation that
// calculates a path ending in the terminal
// node...
@Letrec findPath(path) =
// If the path ends with the terminal node
// then produce the path as the result of
// findPath...
if path->last = terminal
then @Result path end
else
// Otherwise, select an edge such that the
// source of the edge is the last node in
// the path and the target of the edge
// has not been visited before...
@Select edge
// If a subsequent selection ever fails
// then control returns here and tries
// another edge...
from graph.getEdges()
when edge.getSource() = path->last() and
not path->includes(edge.getTarget())
do
// Add the edge and the target node to the
// path and call findPath again...
findPath(path + Seq{edge,edge.getTarget()})
end
end
in
// Get a path...
let path = @Start findPath(Seq{start}) end
in
// Display the path in a text dialog...
xmf.textArea("info",pathLabel(path),displayPath(path))
end
end
end;
The services are invoked in a menu handler, the invocation of the setStart service is shown
below.
public void setStartNode(Node node) {
getMachine().invokeService("setStart", new Object[] { node }, new ClientResult() {
public void result(Object value) {
}
public void error(String message) {
Shell shell = Display.getCurrent().getActiveShell();
MessageDialog.openError(shell, "Error", message);
}
},getClassLoader());
}
- A handler ClientResult which displays an error if something goes wrong during the execution of the service.
- A java class loader enabling the manipulation of Java classes and
objects in the XMF world. Typically the appropriate class loader
can be found via the plugin's Activator:
public ClassLoader getClassLoader() {
if (classloader == null)
classloader = new ClassLoader() {
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> c = Activator.getDefault().getBundle().loadClass(name);
return c;
}
};
return classloader;
}
To top.
Critical Path Application
Critical Path (CP) Applications have many uses including
project planning and knowledge base systems. As in the previous
application, they are based on a graph shaped structure although in
this case they have a numeric value assigned to an edge which
represents a weighting. The higher the value of this weighting,
the more it costs to navigate the edge. For example, in project
planning applications the edge represents time to complete a particular
task. The screenshot below shows a graph in the CP application
describing a software development project which consists of five
projects each node represents a stage in the project, for example the
completion of feature E, and each edge represents the transition to
reaching a stage with some associated time taken, for example the
transition from B to E takes 5 units. Some of the five features,
such as A and B, can be developed in parallel; but there are
dependencies between other features, D can only be developed on
completion of feature A.
The screenshot below shows the same diagram, but this time the critical path has been calculated and marked, and features are annotated with their start and end time. The critical path is the most expensive route through the graph, in a project this is the path which needs to be analyzed if the project length needs to be shortened by reducing linear dependencies or simply the effort required on specific components of the project.
The EMF model for the above application is shown below.
Once the EMF model has been exported as an XMF model, the classes
can be extended with operations. For example:
Note that the operations getStartEvent() and getEventEvent() are
implemented by EMF in Java. Java-defined methods can be called from
XOCL defined operations. In this way the XOCL defined class Activity
can be viewed as extending the Java defined class for Activity.
Events are also extended:
All of the application code is added to the XOCL classes in this
way. For documentation purposes, the rest of the tutorial will show
individual definitions as:
context C
// some definition
The CPM tool provides a service called getPath that calculates the critical path for the CPM network. This service is to be offered as a menu item from the GMF-generated CPM network editor. To do this we include a popup menu item in the plugin:
<extension point="org.eclipse.ui.popupMenus">
<objectContribution id="ID" objectClass="cpm.diagram.edit.parts.CPM_ModelEditPart">
<action id="getPath" label="Get Path" menubarPath="additions" class="cpm.xmf.HandleAction" enablesFor="1">
</action>
</objectContribution>
</extension>
public void run(IAction action) {
Object selected = selection.getFirstElement();
if(selected instanceof GraphicalEditPart) {
GraphicalEditPart gep = (GraphicalEditPart)selected;
View view = (View)gep.getModel();
EObject eobject = view.getElement();
if (action.getId().equals("getPath"))
calculatePath((CPM_Model)eobject);
}
}
@Service getPath(model)
model.performPasses()
end;
@Operation performPasses()
self.forwardPass();
self.backwardPass();
self.showCriticalPath()
end
The class CPM_Model requires three operations in order to calculate
and display the critical path. First it performs a forward pass that
calculates the earliest start dates for the events. Then it performs a
pass that calculates the lastest end dates for the events. Finally it
calculates the critical path and displays it by adding labels to
each of the activities on the path.
All of the CPM_Model critical path operations require a collection
of helper-operations. We will show some key features of the
implementation. The complete source code can be downloaded here.
context CPM_Model
@Operation forwardPass()
// Start off at the roots and visit each event setting
// the earliest start time...
let roots = self.roots() then
visited = roots->asSet
in
// While there are some unvisited events...
@While visited <> self.getEvents()->asSeq->asSet do
@For event in self.getEvents() do
// If all the predecessors of the selected event
// have been visited...
if self.predecessors(event)->forAll(n |
visited->includes(n)) and
not visited->includes(event)
then
// Calculate the earliest time that the event could
// occur based on the duration of the activities...
let preds = self.predecessors(event);
earliest = 0
in @For pred in preds do
@For activity in self.activitiesBetween(pred,event) do
earliest := earliest.max(pred.getEarliestStart() + activity.getDuration())
end
end;
// Set the earliest start time...
event.setEarliestStart(earliest);
// Mark this event as having been visited...
visited := visited->including(event)
end
end
end
end
end
end
The critical path is calculated by selecting and marking activities where the activities join events that have 0 slack. The smount of slack in an event determines the time over which it can occur. An event with 0 slack cannot be moved and therefore is critical to the plan. A sequence of events with 0 slack is a critical path.
To calculate a critical path, we simply select a sequence of joined up activities that start and end with events with 0 slack. Once selected, each activity is marked by changing its label as shown below:
context CPM_ModelTo top.
@Operation showCriticalPath()
let path = @Start self.calculateCriticalPath() end
in @For activity in self.getActivities() do
activity.setLabel("")
end;
@For activity in path do
activity.setLabel("critical")
end
end
end
context CPM_Model
@Operation calculateCriticalPath()
@Select event from self.roots() do
self.critical(event,Seq{})
end
end
context CPM_Model
@Operation critical(event,path)
if self.terminals()->includes(event)
then @Result path end
else
@Select activity from self.getActivities()
when activity.getStartEvent() = event and
activity.isCritical()
do self.critical(activity.getEndEvent(),path + Seq{activity})
end
end
end
Adding
Constraints to the CP Application
A CPM network must be well formed in order to support the critical path
calculations described above. The labels on events must be unique and
the network should have only one terminal event (the end of the
project). XMF allows you to easily add constraints to classes and to
check that the constraints hold. This section describes how to add
constraints to the CPM network and to define a service that performs
the check and displays the results.The CPM_Model can be extended with constraints as follows:
context CPM_model
@Constraint UniqueEventLabels
// Since ->asSet will remove any duplicates, the following
// expression checks that there are no duplicate label names...
self.events().label->size = self.events().label->asSet->size
fail
// A fail-clause in a constraint is used to calculate a
// message-string that is used to report the reason when
// the constraint is not satisfied...
let names = self.events().label then
duplicateNames = names->asSet->select(n | names->select(nn | nn = n)->size > 1)
in formats("The following names are duplicated: ~{,~;~S~}",Seq{duplicateNames})
end
end
context CPM_Model
@Constraint UniqueTerminalEvent
self.terminals()->size <= 1
fail
formats("Can only have a single terminal event: ~{,~;~S~}",Seq{self.terminals().label})
end
<extension point="org.eclipse.ui.popupMenus">
<objectContribution id="ID" objectClass="cpm.diagram.edit.parts.CPM_ModelEditPart">
<action id="check" label="Check" menubarPath="additions" class="actions.HandleAction" enablesFor="1">
</action>
</objectContribution>
</extension>
@Service check(model)
// Each EMFClass can be requested to 'eclipseClassify' an
// EMF object. This will run any constraints that have been
// defined for the class and report the failures using the
// Problem View in Eclipse...
CPM_Model.eclipseClassify(model)
end;
No problems are reported because the constraints are all satisfied. Now supposing that netowkr is modified so that one of the event labels is duplicated. Re-checking the constraints produces the following problems:
Adding an extra terminal node causes the following problem to be added when the network is re-checked:
Summary
The tutorial has described how more complex applications can be
developed using XMF and Eclipse based technologies using two graph
based examples. The final
tutorial in this series shows how a complex and realistic testing
framework is developed using the same approach.