Building a digital watch using DSM: Part 3 – code generation for watch models - Embedded.com

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

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.C generator for watch models
Since the choice of Java and thehighly specific state machine framework of the Java generator were not afamiliar approach to most embedded developers, we later built a Cgenerator. The generator, shown in Listing 5 , is significantlyshorter than that for Java and follows a completely different approach.The application’s States and Buttons are turned into enums, and thestate machine is generated as two-level nested switch-case statements.The outer level has a case for each state the application is in, and theinner level for that state specifies the actions and transition foreach button that may be pressed in that state.

Listing 5: The C generator for watch applications


 

This generator produces the familiar nested switch–cases used in much embedded software. The results for Stopwatch are shown in Listing 6 .

Listing 6: The generated C code for the Stopwatch application




The domain framework
Fromthe point of view of the DSM environment, the domain framework consistsof everything below the code generator: the hardware, operating system,programming languages and software tools, libraries, and any additionalcomponents or code on top of these. However, in order to understand theset of requirements for the framework to meet the needs of a completeDSM environment, we have to separate the domain-specific parts of theframework from the general platform- related parts.

In manycases, the demarcation between the platform and the domain-specific partof the framework remains unclear. For example, the version of Java inwhich the watch example was originally written did not contain anyuseful service to handle timed events such as alarms. Thus, weimplemented such a service ourselves as part of our domain framework.The more recent versions of Java, however, do provide a similarmechanism, meaning that it could be part of the platform if the watchimplementation only needed to support more recent versions of Java.

Withoutany deep theoretical discussion about what is the border betweenframework and platform, we shall use the following definitions here: Theplatform is considered to include the hardware, operating system(Windows or Linux here), Java programming language with AWT classes, andenvironment to test our generated code (e.g., a web browser with Javaruntime).

The domain framework consists of any additionalcomponents or code that is required to support code generation on top ofthis platform. The architecture of the watch domain framework — asdefined in this way — is shown in Figure 12 (solid line arrowsindicate a specialization relationship, while dotted line arrowsindicate inclusion relationships between the elements).

Thedomain architecture of the watch example consists of three levels. Onthe lowest level, we have those Java classes that are needed tointerface with the target platform. The middle level is the core of theframework, providing the basic building blocks for watch models in theform of abstract superclass “templates.” The top level provides theinterface of the framework with the models by defining the expected codegeneration output, which complies with the code and the templatesprovided by the other levels.

Figure 12: The watch domain framework

Thereare two kinds of classes on the lowest level of our framework. METimeand Alarm were implemented to raise the level of abstraction on the codelevel by hiding platform complexity. For example, the implementation ofalarm services utilizes a fairly complex thread-based Javaimplementation. To hide this complexity, class Alarm implements a simpleservice interface for setting and stopping alarms, and all referencesfrom the code generator to alarms are defined using this interface.Similarly, METime makes up for the shortcomings of the implementation ofdate and time functions in the Java version used. During the codegeneration, when we need to set an alarm or apply an arithmeticoperation on a time unit, the code generator produces a simple dispatchcall to the services provided by these two classes.

The otherclasses on the lowest level, AbstractWatchApplet and WatchCanvas,provide us with an important mechanism that insulates the watcharchitecture from platform-dependent user interface issues. For eachsupported target platform, there is an individual version of both ofthese classes, and it is their responsibility to ensure that there isonly one kind of target template the code generator needs to interfacewith. Initially, there was only one target platform: Java applets in aweb browser.

On top of the platform interface and utilizing itsservices is the core of the framework. The core is responsible forimplementing the counterparts for the logical structures presented bythe models. The abstract definitions of watch applications, logicalwatches, and displays can be found here (the classes AbstractWatchApplication and AbstractDisplay). When the code generatorencounters one of these elements in the models, it creates a concretesubclass of the corresponding abstract class.

Unlike the platforminterface or the core levels, the model interface level no longerincludes any predefined classes. Instead, it is more like a set of rulesor an API of what kind of generator output is expected when concreteversions of AbstractWatchApplet, AbstractWatchApplication, orAbstractDisplay are created.

Improvement due to using DSM
Comparingthe Watch models to the tangle of code that is normally found whensimilar embedded systems are hand coded, it is clear that the DSMsolution helped build systems better here than is normal in theindustry.

However, looking at the generated applications, it isequally clear that the code produced is shorter and in some ways simplerthan would normally be written by hand. How much then is theimprovement due to DSM actually only due to the greater attention spenton developing a good framework?

To gain some insight into this,we devised an experiment. Subjects extended the Stopwatch application toadd lap time functionality, first by DSM with code generation, andagain manually by editing the original Stopwatch Java code in the samearchitecture. While the sample size was too small to be statisticallysignificant, the results in Table 1 may still be interesting.

Table 1: Productivity Improvements of DSM Modeling versus Manual Coding

Thenecessary changes to add a lap time function were roughly eight linesof Java code or eight objects in the model. For a senior developer, theproductivity for modeling was over five times that for coding. In thecase of a junior developer, the difference was only four times, but forthe combination of both developers the productivity for modeling wasfive times that for coding.

As is often found, the seniordeveloper was three to four times more productive than the juniordeveloper, and this difference was evident whether modeling or coding.However, the productivity of a junior developer modeling was greaterthan that of a senior developer coding — a tantalizing prospect forproject managers. Imagine all of your developers being 25% moreproductive than your top developer is currently!

To return to ouroriginal question, it is now clear that the main benefit of DSM is inthe difference between modeling and coding. While building a goodframework to support DSM is useful, hand coding on top of even a goodframework is still four to five times slower than DSM.

Extending the DSM solution to new platforms
Initially,there was only one target platform for the Watch applications: Javaapplets in a web browser. With the advent of Java applications on mobilephones another possible platform appeared. While it is still Java, thenew platform, MIDP, differed radically in its user interfacepossibilities. Text fields to show time digit pairs were replaced by abitmap display, and mouse and keyboard input was replaced by simple softkeys and cursor keys.

In addition, the main Applet class wasreplaced by a Midlet class, and MIDP Java did not support reflection,which we had used in the code generated for Actions and DisplayFunctions. Finally, the build process for compiling and packaging a MIDPapplication required a number of extra steps and several new files.

Inspite of these differences, extending the Watch DSM solution to supportthis new platform was possible mainly by simply building versions ofthe AbstractWatchApplet and WatchCanvas classes for the phone MIDPframework. Only minor changes were necessary to the code generation, towork in the more restricted MIDP environment. No changes were necessaryto the modeling language or models. Altogether, the changes took fourman-days: two for the MIDP framework code, half a day for the MIDP buildscript, one for refactoring existing framework code, and half a day foradapting the code generation.

Later, the generators andframework were further extended to take advantage of the MetaEdit+ APIto provide visual tracing of model execution. As the watch applicationran, it would make WebServices calls back to MetaEdit+ when controlpassed to a new state, and MetaEdit+ would highlight that state. Again,these changes only affected the framework and generators.

The DSMsolution thus insulated Secio from changes in the implementationenvironment, offering good support for a family of products across afamily of platforms. The current Watch models are capable of generatingcode for each of the three platforms and on a variety of operatingsystems. Without DSM, there would probably be no hope of maintaining onecode base for all platforms.

Conclusion
Designing andimplementing the first working version of the Watch DSM language withone complete watch model took eight man-days for a team of twodevelopers. Neither developer had prior experience of Java programmingor of building watch software, and there were, of course, nopre-existing in-house watch components.

It took five days todevelop the Java framework, two days for the modeling language, and oneday for the code generator. Another day or two was then spent addingsupport for multiple Watch Families and creating the full set of examplemodels. These times include design, implementation, testing, and basicdocumentation.

Calculating the return on investment for thesetimes is not possible without more information about Secio: what theircurrent code framework was like, how many watch models they wanted toproduce, and so on. An estimate for the latter can be found from thefact that for a real digital watch manufacturer, Casio, their currentportfolio for the United Kingdom alone contains over 220 differentwatches (not including variants differing only in color or materials).

Part 1: Creating a watch modeling language
Part 2: Putting it all together

Read more about DSM

Juha-Pekka Tolvanen (jpt@metacase.com) has been involved in domain-specific languages, codegenerators and related tools since 1991. He works for MetaCase and hasacted as a consultant world-wide for modeling language and codegenerator development. Juha-Pekka holds a Ph.D. in computer science fromthe University of Jyväskylä, Finland.

Steven Kelly ischief technical officer of MetaCase (www.metacase.com) and cofounder ofthe DSM Forum. He is architect and lead developer of MetaEdit+,MetaCase’s DSM tool.

Used with permission from Wiley-IEEE Computer Society Press, Copyright 2014, this article was excerpted from Domain-Specific Modeling: Enabling Full Code Generation by Steven Kelly and Juha-Pekka Tolvanen.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.