Code generation

YAKINDU Statechart Tools include code generators for Java, C, and C++ out of the box. The code generators are following a „code-only” approach: They are generating all the code that is needed and do not rely on any additional runtime libraries. The generated code provides a well-defined application programming interface and can be integrated easily with any client code. In this tutorial we will generate Java code for a sample statechart modeling the handling of a phone call, the CallHandling example.

The CallHandling example

The state machine handling a phone call works as follows:

  • After startup, the system is in an idle state and is waiting for incoming calls.
  • When a call comes in, the user can either accept the call or dismiss it.
  • If the users accepts the call and opens a connection, the system is tracking the duration of the call and is waiting for the user to hang up.
  • After hanging up, the system displays the total time of call and then returns to its idle state.

The complete statechart model is shown below:

The CallHandling statechart model

The CallHandling statechart model

In order to eventually obtain the CallHandling example in the form of executable code, we have to create the model in the statechart editor first, followed by creating a suitable generator model, followed by executing the code generator to create the source code we need.

Creating the statechart model

In the previous section we have seen how to work with the statechart editor. So let’s create a new project now and use the statechart editor to create the CallHandling statechart model as outlined above.

Since we are going to generate Java code, we should use a Java project to host the statechart model. Use File → New → Java Project and follow the New Java Project wizard to create one.

In addition to what we have learned above already, there is one new concept: interfaces.

Creating interfaces

Statecharts can describe very complex interactions between a multitude of actors and an even bigger multitude of events these actors can receive or trigger. It is therefore good practice to structure such events and associate them with their respective actors. For this purpose YAKINDU Statecharts Tools provides the concept of so-called interfaces.

In the CallHandling example, we have two actors: the user and the phone. Let’s model their communication as two interfaces:

  • The Phone interface provides a single incoming event named incoming_call.
  • The User interface comprises two incoming events: accept_call and dismiss_call.

We have to enter the respective definitions in textual form into the statechart editor. Here’s how the interface definitions look like:

interface User:
    in event accept_call
    in event dismiss_call

interface Phone:
    var duration : integer
    in event incoming_call

As you can see, the Phone interface also has an integer variable duration which will track the duration of the call. The interface definitions above have to go into the statechart editor’s definition block on the left-hand side of the statechart editor.

If everything went well, any error markers in the model are gone. Your model should look like the one in the screenshot below:

The CallHandling statechart modeled in the statechart editor

The CallHandling statechart modeled in the statechart editor

Creating a generator model

For code generation, YAKINDU Statechart Tools use a textual generator model called SGen. The generator model holds key parameters for the code generation process and allows for the latter’s customization.

The first step to code generation is to create a new SGen model. Right-click on the model folder in the project explorer and select New → Code generator model from the context menu.

Selecting "New → Code generator model" in the context menu

Selecting New → Code generator model in the context menu

The YAKINDU generator model wizard opens. Change the File name to CallHandling.sgen, then click Next >.

Selecting a filename for the generator model

Selecting a filename for the generator model

From the Generator drop-down menu at the top, select YAKINDU SCT Java Code Generator.

In the statechart tree beneath that menu, check the CallHandling.sct model, then click Finish.

Selecting generator type and statechart model

Selecting generator type and statechart model

Now the wizard creates the default SGen model for Java code generation and opens it in an SGen editor. The project explorer on the left-hand side shows the new model file CallHandling.sgen.

The generator model

The generator model

Here’s the generator model once again as plain text:

GeneratorModel for yakindu::java {

    statechart CallHandling {

        feature Outlet {
            targetProject = "CallHandling"
            targetFolder = "src-gen"
            libraryTargetFolder = "src"
        }
    }
}

Let’s have a closer look at the listing above:

  • yakindu::java is the unique ID of the Java code generator.
  • The statechart CallHandling { … } block references the statechart model we want to generate code for.
  • The feature Outlet { … } block specifies where the generated code artifacts are to be placed:
    • The parameters targetProject and targetFolder define the Eclipse project CallHandling as destination for generated code and within that project the src-gen folder.
    • However, certain source code modules are generated only once and won’t change with the underlying statechart. By default, these source code artifacts are generated into the folder specified by the libraryTargetFolder option which by default is src. You can change the folder name or even remove the libraryTargetFolder option at all. In the latter case the „library” components are generated into the targetFolder.

A statechart reference may contain various configuration features. You will learn more about them later.

Enhancing the generator model by timing capabilities

The default generator model is insufficient yet. The CallHandling statechart model uses after and every expressions. That is, it is dealing with time events, requiring a timer service to trigger them. We can instruct the code generator to provide us with a default timer service implementation by adding the following feature to the generator model:

feature GeneralFeatures {
    TimerService = true
}

Generating Java source code

What do we have to do to actually start the Java source code generation? Nothing!

The generator model is executed by a so-called Eclipse builder. That is, as long as the Project → Build Automatically menu item is checked (which it is by default), the artifacts are generated automatically with each modification of the statechart model or of the generator model.

As you can see in the project explorer, the folder src-gen has been created and populated with the generated Java source code artifacts.

Adding the timer service feature

Adding the timer service feature

Add the generated artifacts to the Java build path by right-clicking on the src-gen folder and selecting Build Path → Use as source folder in the context menu.

Declaring "src-gen" as a source folder

Declaring src-gen as a source folder

If you want to execute your generator model manually, select Generate Code Artifacts from the .sgen file’s context menu in the project explorer.

Integration with client code

Now that we have a generated Java implementation of the CallHandling state machine available, we want to actually use it from some client code. We’ll create that client code in a second.

Creating client code

Let’s establish a new Java class CallHandlingClient and integrate the state machine with it:

  1. Right-click on the src folder.
  2. Select New → Class in the context menu.
  3. Name it CallHandlingClient.
    Creating a class containing the client code
  4. Click Finish.

Next, copy the following code into the created class:

  import org.yakindu.scr.TimerService;
  import org.yakindu.scr.callhandling.CallHandlingStatemachine;

  public class CallHandlingClient {

      public static void main(String[] args) throws InterruptedException {

          // Create the state machine:
          CallHandlingStatemachine sm = new CallHandlingStatemachine();
          sm.setTimer(new TimerService());

          // Initialize the state machine:
          sm.init();

          // Enter the state machine and implicitly activate its "Idle" state:
          sm.enter();

          // Raise an incoming call:
          sm.getSCIPhone().raiseIncoming_call();
          sm.runCycle();

          // Accept the call:
          sm.getSCIUser().raiseAccept_call();
          sm.runCycle();

          // Keep the phone conversation busy for a while:
          for (int i = 0; i < 50; i++) {
              Thread.sleep(200);
              sm.runCycle();
          }

          // Before hang-up, output the duration of the call:
          System.out.println(String.format("The phone call took %d seconds.",
                  sm.getSCIPhone().getDuration()));

          // Hang up the phone:
          sm.getSCIUser().raiseDismiss_call();
          sm.runCycle();
      }
  }

Let’s have a detailed look at this client code:

  • First, this program creates a new instance of the state machine by calling the default CallHandlingStatemachine constructor:
    • CallHandlingStatemachine sm = new CallHandlingStatemachine();
  • Since we are using time events, the state machine implementation requires an implementation of the ITimer interface. And since we added the TimerService feature to the generator model, the code generator creates a default implementation org.yakindu.scr.TimerService that uses the java.util.Timer class. A new instance of the default TimerService is created and set to the state machine:
    • sm.setTimer(new TimerService());
  • The state machine and its internal data structures are initialized:
    • sm.init();
  • After that, sm.enter() enters the state machine and – via its initial state – activates its Idle state.
  • For each interface in the statechart definition block a getter method has been generated, here getSCIPhone() and getSCIUser(). You can access all incoming events and all variables via these interfaces.
    • sm.getSCIPhone().raiseIncoming_call(); raises the incoming_call event, activating the Incoming Call state after the next run cycle has been executed. The latter is triggered by sm.runCycle().
  • sm.getSCIUser().raiseAccept_call() accepts the call or, to be more precise, raises the accept_call event via the User interface. It activates the Active Call state after the next run cycle has been performed by sm.runCycle();.
  • In the for loop, the run cycle is executed periodically every 200 milliseconds. This simulates the duration of the phone call.
  • Before hanging up the phone, let’s find out how much time we spent in the call. sm.getSCIPhone().getDuration() retrieves the call’s duration, which is then formatted and printed to the console.
  • Finally, sm.getSCIUser().raiseDismiss_call() raises the dismiss_call event. It activates the Dismiss Call state after the next run cycle.

Executing the client code

You can execute the client code via Run As → Java Application from the class file’s context menu.