Building a digital watch using DSM: Part 3 – code generation for watch models

Steven Kelly and Juha-Pekka Tolvanen, Metacase

January 06, 2015

Steven Kelly and Juha-Pekka Tolvanen, MetacaseJanuary 06, 2015

Editor’s Note: Excerpted from their book Domain Specific Modeling: Enabling Full Code Generation, in "Part 3: Generating either Java or C language code for a logical watch and a watch application" the authors use a fictitious digital watch company to demonstrate the advantages of domain specific modeling by showing how to build a DSM model generator to create digital watch applications.

Part 1 and Part 2 in this series described the domain specific modeling building blocks and procedures needed to build a digital watch design. In this final part, we describe how generators to both the Java and C languages were built for watch models.

In the watch example, the generator goes beyond simply producing Java code corresponding to the models. It also takes care of creating and running build scripts, and ensuring that the domain framework is available. Generation proceeds with the following steps:

  • The batch scripts for compiling and executing the code are generated. As the mechanisms of compilation and execution vary between platforms, the number and content of the generated script files depend on the target platform.
  • The code for the domain framework components is generated. This is an unusual step, since normally any domain framework code files would already be present on the developer’s local disk or referenced from a network share. For the purposes of the Watch example as part of an evaluation package, however, we had to provide the framework code files and make sure they were installed in an appropriate place. The easiest way to do this was simply to include them as textual elements along with the models. This way we ensured that all required components were available at the time of compilation, and we also had control over the inclusion of platform-specific components.
  • The code for logical watches and watch applications is generated. The state machines are implemented by creating a data structure that defines each state, transition, and display function, along with their contents. For each action, the code generator creates a set of commands that are executed when the action is invoked.
  • The generated code is compiled and executed as a test environment for the target platform. Basically, this step requires only the execution of the scripts created during the first step.

Java generator for watch models
How was the code generator implemented then? Each generator in MetaEdit+ is associated with a certain modeling language and can thus operate on models made according to that specific language. To avoid having one huge generator, generators can be broken down into subgenerators and call each other, forming a hierarchy or network. The top level of the Watch DSM solution is the Watch Family modeling language, and this also forms the top level of the generator. The generators at this top level are shown in Figure 10 (a “*” in the name of a subgenerator denotes an individual version for each target platform). Arrows denote calls to subgenerators, and the order of execution is depth first and left to right.


Figure 10: The watch code generator architecture, part 1

At the top is the main generator, Autobuild. The role of Autobuild here is similar to that of the main function in many programming languages: it initiates the whole generation process but does not itself contain anything other than calls to the subgenerators on the next level down. The subgenerators on the next level relate closely to the steps of the Autobuild process presented earlier in this section.

As _JavaComponents just outputs the predefined Java code for the framework components, and _compile and execute * just executes scripts produced during the earlier steps of the generation process, we can concentrate on the more complex subgenerators, _create make for * and _Models.

The basic task of the _create make for * subgenerators is to create the executable scripts that will take care of the compilation and execution of the generated code. As this procedure varies between platforms, there is an individual version of this subgenerator for each supported target platform. If there are any specific platform-related generation requirements, for example creating the HTML files for the browser- based test environment in Fig. 10, they can be integrated into the _create make for * subgenerator.

The responsibility of the _Models and _Model subgenerators is to handle the generation of code for the Watch Models, Logical Watches, and Watch Applications. For each Watch Model, three pieces of code are generated: an applet as the physical implementation of the user interface, a display definition to create the specified user interface components, and the definition of the logical watch.

An example of the code generated for an applet (produced by the _Applet subgenerator) is shown in Listing 1 below. The generated code defines the applet as an extension of the AbstractWatchApplet framework class and adds the initialization for the new class. Values from the model are shown in bold.

Listing 1: Generated code for an applet.

   public class Ace extends AbstractWatchApplet {
      public Ace() {
         master=new Master();
         master.init(this,
           new DisplayX334(),
           new TASTW(master));
      }
   }


The simple generator that produced this is shown in Listing 2 below with the generator code in bold.

Listing 2: Code generator for the applet (generator code in bold)

   'public class ' :Model name; ' extends AbstractWatchApplet {
      public ' :Model name; '() {
         master=new Master();
         master.init(this,
            new Display' :Display:Display name; '(),
            new ' :LogicalWatch:Application name; '(master));
      }
   }'


The display definition can be generated in the same vein by the subgenerator _Display, as shown in Listing 3 below. Again, a new concrete display class is created, inheriting from AbstractDisplay, and the required user interface components are defined within the class constructor method.

Listing 9.3 Generated code for a display

   public class DisplayX334 extends AbstractDisplay
   {
      public DisplayX334()
      {
         icons.addElement(new Icon("alarm"));
         icons.addElement(new Icon("stopwatch"));
         icons.addElement(new Icon("timer"));

         times.addElement(new Zone("Zone1"));
         times.addElement(new Zone("Zone2"));
         times.addElement(new Zone("Zone3"));

         buttons.addElement("Mode");
         buttons.addElement("Set");
         buttons.addElement("Up");
         buttons.addElement("Down");
      }
   }


However, to understand how the code for a logical watch and a watch application is generated, we need to explore the code generator architecture further. The lower level of the architecture (i.e., the subgenerators associated with the Watch Application modeling language) is shown in Figure 11 below.


Figure 11: The watch code generator architecture, part 2.

The subgenerators “JavaFile” (which is the same as in Figure 10) and “Java” take care of the most critical part of the generation process: the generation of the state machine implementations. To support the possibility to invoke a state machine from within another state machine in a hierarchical fashion, a recursive structure was implemented in the _JavaFile subgenerator. During the generation, when a reference to a lower-level state machine is encountered, the _JavaFile subgenerator will dive to that level and call itself from there.

The task of the _Java subgenerator is to generate the final Java implementation for the Logical Watch and Watch Application state machines. An example of such code can be found in Listing 4,  which shows the implementation of the Stopwatch application.

Listing 4: The generated code for Stopwatch application

 
 
 

For a comprehensive understanding of the _Java subgenerator output, let us study the generated code line by line. As before, a new concrete Watch Application class is derived from the AbstractWatch Application class (line 1). From here on, the responsibility for the generated code is distributed among the _Variables, _StateData, _TransitionData, _StateDisplayData, _Actions, and _DisplayFns subgenerators.

The _Variables and _getSet subgenerators are responsible for declaring the identifiers for actions and display functions to be used later within the switch-case structure (lines 3–7). They also define the variables used (lines 9–10) and the implementations of their accessing methods (lines 12–30). A short return back to the _Java subgenerator produces lines 32–34, followed by the state (lines 35–38) and state transition definitions (lines 40–45) generated by the _StateData and _TransitionData subgenerators. The _StateDisplayData and _StateDisplayDataContent subgenerators then provide the display function definitions (lines 47 and 48), while the basic method definition and opening of the switch statement in lines 51–54 again come from the _Java subgenerator.

The generation of the code for the actions triggered during the state transitions (lines 55–65) is a good example of how to be creative in integrating the code generator and the modeling language. On the modeling language level, each action is modeled with a relationship type of its own. When the code for them is generated, the _Actions subgenerator first provides the master structure for each action definition: a case statement using the unique ID of the Action. It then follows the relationships for each part of the action and executes the subgenerator bearing the same name as the current action relationship (either _Icon, _Roll, _Alarm, or _Set). The subgenerator produces an appropriate line of code for this part of the action, for example turning an icon on in line 60. This implementation not only reduces the generator complexity but also provides a flexible way to extend the watch modeling language later if new kinds of actions are needed.

Finally, the _DisplayFns and _calcValue subgenerators produce the calculations required by the display functions (lines 66–69). The _calcValue subgenerator -- which is also used by the _Alarm and _Set subgenerators -- provides the basic template for all arithmetic operations within the code generator.

The generation of the simple middle-level Watch Applications such as TASTW proceeds in the same way. As the middle-level models are simpler than the applications they contain -- for example, they have no actions -- the resulting code is also simpler. In order to support the references to the lower-level state machines (i.e., to the Watch Applications of which the middle-level model is composed), the definitions of these decomposition structures must be generated. This is taken care of by the _Decompositions subgenerator.

< Previous
Page 1 of 3
Next >

Loading comments...