Activity nodes are connected by edges, which exist in two types:
Notice that object and control flows are not necessarily implemented as signals or objects that are passed between activity elements, this is just an illustration of the semantics. Object flows can be implemented for instance by variables.
Control flows are just a notification about an event, while object flows also carry data objects. To determine if you need an object flow or a control flow, focus on the nodes that they target: If nodes require data (such as input pins of operations), use an object flow. Some building blocks or operations may provide objects that you are not always interested in. For example, a building block provides an exception object, but for you it only counts that an exception happened. Then you may simply ignore that the pin is types and continue with an control flow. From a technical point of view, you could always use object nodes. However, we recommend to use control flows wherever possible to make visible that no data is transferred.
To create an edge, select Add / Edge from the context menu. This tool stays selected, so you can add more than one edge. To change into normal selection mode, choose Select, or press Esc.
The editor will try to guess if the edge should be a control flow or an object flow. Object flows are chosen whenever the downstream nodes require data or the upstream nodes provide data. This strategy is right in most cases. If, however, the editor choses an control flow but you want an object flow or the other way around, you can simply right-click the newly created edge and select Switch to Object Flow (or vice-versa).
You may want to bend points in order to make your diagram more readable. In order to do this, select the edge with your pointer, then drag the bend-point on the middle.
To make a bent line straight do the exact opposite, i.e. drag the middle bend point back to its original position.
Flows are used to connect Reactive Blocks with other Reactive Blocks or Activity Nodes. The pins of a block provide the interface for ingoing and outgoing data of a block. Even so there are different types of pins (starting pin, terminating pin, streaming input pin, streaming output pin) and different types of flows (object and control flows), there is no restriction in connecting flows to pins. Both flow types may be connected to all pin types.
Add guards to activity flows by using the context menu of the flow. During the execution, a decision must not block, that means, at least one branch must have a guard that is true. This can be achieved by always having one 'else'-branch. If several guards are true, an arbitrary one is chosen.
To make a decision dependent on a boolean value provided by an operation, simply use a pair of outgoing branches from a decision node, and add the guards
false. The edges entering and leaving the decision nodes need to be object flows, since they transport the boolean value to the guards.
To decide between more than two branches, you can use literal values. In the example, the operation produces a string value, which is then compared with the literal string values specified by each outgoing branch. To make sure that the decision is non-blocking, you must add an else branch in any case. Literal integers can be specified as well.
In some cases, the downstream branches need some data that is not the same as the data we want to work with in the guards. Below, for instance, shows part of a specification that lets users subscribe to a serivce via SMS. From the incoming SMS we extract the keyword and compare it with the literal string guards on each branch. This means that the operation
extractType provides a String. However, downstream we would like to use the complete SMS again. Therefore, we use an extra variable
currentIncSMS as a temporal buffer to store the SMS, and use set and get operations.
Boolean methods can also be used as guards. These can be used instead of boolean guards and are useful to make your specifications more compact and easier to understand. Moreover, the object flows that are connected to the decision node can be use to other data. See the following figure.
The left and right models have the same behavior. If operation
isStudent() returns true, operation
studentOp(User u) is executed; otherwise, operation
elseOp(User u) is called.
On the left part, operation
isStudent() is used as a guard. Note that, here the flows in and out the decision node contain an object of type
User. In contrast, the incoming and outgoing flows of the decision node on the right part have a boolean object.
This type of guards can only be used with boolean methods with no parameter.