When creating your own graphical modeler using Eclipse Sirius Desktop, you need to tell Sirius:
- how to interpret your domain models so they can be displayed,
- and how your domain elements must be modified when the end-user invokes edition tools (e.g. direct edit, element creation or deletion, etc.).
This is done using a combination of interpreted expressions (in general using AQL) and model operations (for the tool's behavior) that can be configured directly inside the modeler’s configuration (the .odesign file).
For example, on the first screenshot below the interpreted expressions (always presented with a yellow background in the properties view) configure an edge mapping by telling Sirius how to navigate inside the user’s model to find the model objects to be represented as edges (computed from the model with the Semantic Candidate Expression
) and which elements it should connect on the diagram (computed with the Source Finder Expression
and the Target Finder Expression
).
In the second screenshot, another AQL expression is used to configure a Move
operation as part of the definition of the behavior of a Delete
tool.
You can go a long way just using only these mechanisms, but for more advanced modelers these tools can become limited compared to the power of a full-blown programming language like Java. Fortunately, Sirius makes it very easy to invoke your own Java code from inside your odesign file, and thus greatly expands the range of things your modelers can do. Java services are plain Java methods that follow a specific calling convention and which, once they are registered in your modeler, can be invoked transparently from any AQL expression.
For example Flow Designer defines the following simple Java service:
public int sizeFromCapacity(Processor p) {
return Double.valueOf(Math.sqrt(p.getCapacity()) * 2).intValue();
}
and then uses it in the Size Computation Expression
in some elements’ style configuration with aql:self.sizeFromCapacity()
. The result is that the size of the processor elements on a diagram is recomputed by invoking this Java code on each diagram refresh, and thus always adapts to the changes in their capacity attribute.
Typical use-cases for Java services include:
- complex navigations or computations on your models needed for displaying them, which would be difficult or impossible to perform in AQL. For example Ecore Tools uses a Java service to compute the labels for
EOperations
, which includes the operation’s name, return type, arguments, etc. While it would be possible to do it with AQL, the resulting expression would be very large, difficult to read and to maintain; - complex model transformations that can not be expressed using the basic model operations available in the odesign for tools;
- reuse of common patterns and expressions that you need to use consistently in different places in your modeler (see this example in UML Designer which ensures consistent computation of the labels of all elements);
- improved performances, by replacing complex AQL expressions which are invoked very often by an equivalent but more efficient Java method;
- integration with Java APIs (from Eclipse, Sirius, or any Java library) or external systems.
Java services can be used anywhere you can place an AQL expression inside your odesign. In the odesign editor, they can be easily identified by the yellow background on the corresponding text fields in the properties view (see the screenshots above). This includes things like Semantic Candidate Expression
used to find the model elements to display, preconditions or conditional styles, etc. Inside a tool’s body, if you need to call a Java service to perform the required model transformation, the idiom is to use the Change Context
operation with an expression that invokes your service. For example to support the direct edition (the possibility for the user to change the label directly from the diagram) of two different attributes (name and capacity) of a Flow element, Flow Designer delegates the tool’s implementation to a custom Java service like this:
Writing and using a Java service
To create your own Java services:
- Create a Java class inside your modeler project. The class should be public and have a default constructor (implicit or explicit) with no arguments.
- Add your service as public methods following the required calling convention (see below).
- Register the class inside your odesign file using a Java Extension element with the fully qualified name of the class.
- Inside your odesign, invoke your service from an AQL expression like any of the standard AQL services.
If you have used the Viewpoint Specification Project wizard to create your modeler, you are actually already set up, as it automatically creates and registers an example service ready to be filled in:
Of course, if you end up having lots of services, you can split them into different classes. Just make sure they are all registered in the odesign.
The rules for writing a service method are simple:
- The class containing the method must be public and have a default constructor which takes no argument.
- The class can contain many public methods (static or not), each of which will be visible as a service if they follow the right conventions.
- A service method must take at least one parameter, which should be an EMF type (i.e. EObject or a subtype).
- A service method may take more parameters, which can be: strings, numbers, EMF objects (i.e. EObject or a sub-type), Java collections of EMF types (note that array types are not supported).
- A service method must return a value, which can have the same kind of types as parameters. If your service only implements a side-effect you can simply
return self
as in the example above.
Once written and properly registered on a Viewpoint element in your odesign model, you can invoke your Java services from any interpreted expression inside that viewpoint.
- The type of the first argument of a service method determines the values on which the service can be invoked. For example the
sizeFromCapacity
service method above takes an instance ofProcessor
(or a subtype) as the first argument. - If the Java service method takes more than one argument, they must be passed as parameters to the invocation. For example UML Designer defines a service with a signature of
List<Relationship> getRelationShipOrigin(Element element, boolean origin)
. Inside the odesign it is invoked asaql:self.oclAsType(uml::Element).getRelationShipOrigin(true)
. - Finally, the value returned by a service call can be used as any other value inside an expression, including by chaining successive calls, using them inside “lambdas”, etc:
aql:self.findAllByName(‘exchange’)->collect(e | e.swapDirection())
.
There are a few things to be aware of when using Java services inside a Sirius modeler:
- Services invoked from expressions used to refresh a representation should not perform any modification on the model. They should also be fast as they will be called often, possibly a large number of times on each refresh.
- Services used in tools however can of course modify the models (that’s the point of a tool), but they should not have external side effects (like doing some IO). During the execution of a tool, if an error occurs the model itself will be reverted to its original state before the tool application, but external side effects can not be rolled back.
In this article, we showed that it’s very easy to extend the capabilities of a Sirius modeler by calling into Java code. This is very easy to do and opens up a lot of possibilities, in particular on the behavior of the tools. To go further, you can follow the Advanced Tutorial which includes a section with step by step instructions to add a Java service to the sample modeler, refer to the reference documentation for more details, or study more advanced use cases in Ecore Tools, UML Designer or even Capella to get concrete examples to take inspiration from.
If you have any questions, do not hesitate to ask on the Sirius forum and we will do our best to guide you.