3.3 The Code Generator
The basic idea of the code generator within a DSM environment
is simple: it crawls through the models, extracts information from them and
translates it as the code for the target platform. With the models capturing all
static and dynamic logical aspects and a framework providing the required
low-level support, the code generator can produce completely functional and
executable output code.
The MetaEdit+ report generator used to produce the code
also provides a flexible tool to automate integration of the DSM environment
with other tools. An example of this kind of integration within the watch
example is the auto-build mechanism that enables us to automatically compile the
generated code and execute it on a testing environment. This automation results
in major savings of both the effort and time during the testing.
In the watch example, the auto-build proceeds with
following steps:
| 1) | The
scripts for compiling and executing the code are generated. As the mechanism
of automated compilation and execution varies between the platforms, the number
and content of generated script files depend on the target
platform. |
| 2) | The
code for framework components is generated. All framework code has been
included into the watch models and generated from there as needed. This way we
ensure that all required components are available at the time of compilation and
have the control over the inclusion of platform specific
components. |
| 3) | 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. For each action the code generator creates a
set of commands that are executed when the action is
invoked. |
| 4) | 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. |
How the code
generator has been implemented, then? As we have seen before, the code
generators in MetaEdit+ are defined with a dedicated report definition language.
Each report definition is associated with certain graph type and thus can
operate on models made according to that specific graph type. These report
definitions – that could be also referred to as sub-generators – can
be arranged in hierarchical fashion. The top level of the code generator
architecture of the watch example (i.e. the sub-generators associated with
WatchFamily graph type) is presented in
Figure 3-3 (a ‘*’ in the name
of a sub-generator denotes an individual version for each target
platform).

Figure 3-3. The watch code generator architecture, part 1
On
the top, there is a master generator called ‘Autobuild’. The role of
the ‘Autobuild’ is similar to those of the main programs in most
programming languages: it initiates the whole generation process but basically
does not contain anything else but the calls to the sub-generators on the lower
level. The sub-generators on the next level relate closely to those steps of the
auto-build process presented earlier in this chapter. As
‘_JavaComponents’ only outputs the pre-defined Java code for the
framework components and ‘_compile and execute *’ only executes
scripts produced during the earlier steps of the generation process, we can
concentrate more on the more complex sub-generator, ‘_create make for
*’ and ‘_Models’.
The basic task of ‘_create make for *’
sub-generators 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 sub-generator for each
supported target platform. If there are any specific platform-related generation
need like HTML for browser-based test environment in
Figure 3-3, they can be integrated with
the ‘_create make for *’ sub-generator.
The responsibility of the ‘_Models’ and
‘_Model’ sub-generators is to master 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 about the model specific user interface
components and the definition of the logical watch.
An example of the code generated for an applet (produced
by the ‘_Applet’ sub-generator) is shown in
Listing 1. The generated code defines the
applet as an extension of the AbstractWatchApplet and adds the initialization
for the new class.
public class Venturer extends
AbstractWatchApplet
{
public Venturer()
{
master=new Master();
master.init(this, new DisplayX334(),
new TASTW(master));
}
}
Listing 1. Generated code for an applet
The display
definition can be generated in the same vein by the sub-generator
‘_Display’, as shown in
Listing 2. Again, a new concrete display
class is inherited from the AbstractDisplay and the required user interface
components are defined within the class constructor method.
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");
}
}
Listing 2. Generated code for a display
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 sub-generators associated with
WatchApplication graph type) is presented in
Figure 3-4.

Figure 3-4. The watch code generator architecture, part 2
The
sub-generators ‘_JavaFile’ (which is same as in
Figure 3-3) 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 hierarchical fashion, a recursive
structure was implemented in the ‘_JavaFile’ sub-generator. During
the generation, when a reference to a lower-level state machine is encountered,
the ‘_JavaFile’ sub-generator will dive to that level and call
itself from there.
The task of the ‘_Java’ sub-generator 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 3 that presents the implementation
of the Stopwatch application.
1 public class Stopwatch extends AbstractWatchApplication
2 {
3 static final int a22_3324 = +1; //+1+1+1+1
4 static final int a22_3621 = +1+1; //+1+1+1
5 static final int a22_4857 = +1+1+1; //+1+1
6 static final int d22_4302 = +1+1+1+1; //+1
7 static final int d22_5403 = +1+1+1+1+1; //
8
9 public METime startTime = new METime();
10 public METime stopTime = new METime();
11
12 public METime getstartTime()
13 {
14 return startTime;
15 }
16
17 public void setstartTime(METime t1)
18 {
19 startTime = t1;
20 }
21
22 public METime getstopTime()
23 {
24 return stopTime;
25 }
26
27 public void setstopTime(METime t1)
28 {
29 stopTime = t1;
30 }
31
32 public Stopwatch(Master master)
33 {
34 super(master, "22_1039");
35 addStateOop("Start [Watch]", "22_4743");
36 addStateOop("Running", "22_2650");
37 addStateOop("Stopped", "22_5338");
38 addStateOop("Stop [Watch]", "22_4800");
39
40 addTransition ("Stopped", "Down", a22_3324, "Stopped");
41 addTransition ("Running", "Up", a22_4857, "Stopped");
42 addTransition ("Stopped", "Up", a22_3621, "Running");
43 addTransition ("Stopped", "Mode", 0, "Stop [Watch]");
44 addTransition ("Running", "Mode", 0, "Stop [Watch]");
45 addTransition ("Start [Watch]", "", 0, "Stopped");
46
47 addStateDisplay("Running", -1, METime.SECOND, d22_5403);
48 addStateDisplay("Stopped", -1, METime.SECOND, d22_4302);
49 }
50
51 public Object perform(int methodId)
52 {
53 switch (methodId)
54 {
55 case a22_3324:
56 setstopTime(getstartTime().meMinus(getstartTime()));
57 return null;
58 case a22_3621:
59 setstartTime(getsysTime().meMinus(getstopTime()));
60 iconOn("stopwatch");
61 return null;
62 case a22_4857:
63 setstopTime(getsysTime().meMinus(getstartTime()));
64 iconOff("stopwatch");
65 return null;
66 case d22_4302:
67 return getstopTime();
68 case d22_5403:
69 return getsysTime().meMinus(getstartTime());
70 }
71 return null;
72 }
73 }
Listing 3. The generated code for Stopwatch application
For
the comprehensive understanding of the ‘_Java’ sub-generator output,
let us study the generated code line by line. As previously, a new concrete
watch application class is derived from the AbstractWatchApplication class (line
1). From here on, the responsibility for the generated code is distributed among
the ‘_Variables’, ‘_StateData’,
‘_TransitionData’, ‘_StateDisplayData’,
‘_Actions’ and ‘_DisplayFns’ sub-generators.
The ‘_Variables’ and ‘_getSet’
sub-generators 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’ sub-generator produces the lines 32 – 34,
followed by the state (lines 35 - 38) and state transition definitions (lines 40
– 45) generated by the ‘_StateData’ and
‘_TransitionData’ sub-generators. The
‘_StateDisplayData’ and ‘_StateDisplayDataContent’
sub-generators then provide the display function definitions (lines 47 –
48) while the basic method definition and opening of the switch statement in the
lines 51 – 54 again come from the ‘_Java’
sub-generator.
The generation of the code for the actions triggered
during the state transitions (lines 55 – 65) is a good example of how to
creatively integrate 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’
sub-generator first provides the master structure for each action definition and
then executes the sub-generator bearing the same name as the current action
relationship (either ‘_Icon’, ‘_Roll’,
‘_Alarm’ or ‘_Set’). This implementation not only
reduces the generator complexity but provides also a flexible way to extend the
watch modeling language later if new kinds of actions are needed.
Finally, the ‘_DisplayFns’ and
‘_calcValue’ sub-generators produce the calculations required by the
display functions (lines 66 – 69). The ‘_calcValue’ –
which is used also by the ‘_Alarm’ and ‘_Set’
sub-generators – provides the basic template for all arithmetic operations
within the code generator.
The generation of a logical watch proceeds the same way.
As there are typically no actions related to the state transitions within a
logical watch, they are left off from the generated code as well. More over, in
order to support the references to the lower-level state machines (i.e. to the
watch applications the logical watch is made of), the definitions of these
decomposition structures must be generated. This is taken care by the
‘_Decompositions’ sub-generator.
As we can see, there is no magic within the code
generator, just a carefully designed modularization of the solution and
integration with the modeling language and domain framework. In general, the
rule is to try to keep generation as simple as possible: if anything appears
difficult, consider raising it up into the modeling language, or pushing it down
into the domain framework code.
Having covered the modeling language and the code
generator, we will next discuss issues related to the domain framework
code.