Before writing any actual MERL code for our exercise we have
to understand what exactly needs to be done. We have to answer the following
questions:
What
design data do we need to extract from the
models?
How
do we navigate to and access the data we
need?
Where
and how do we output the extracted data?
In
our example case, the answer to the first question is simple: we need to get the
names of all Watches and the names of their buttons and applications. Studying
the models will then reveal the answer to the second question: we have to look
at each filled green Watch relationship in turn, getting the Watch’s name.
We can follow its green role line to its Display object on the left to get its
Buttons’ names. We can follow its red role line to its LogicalWatch object
on the right, and from there down into the LogicalWatch’s subgraph to get
the names of the applications there.
If we keep the third question simple for the moment and
stick with plain text output to the screen, we can already write the first
– albeit preliminary – version of our generator:
01 SpecSheet()
02 'SpecSheet for ' id newline newline
03 foreach >Watch
04 { 'Watch: ' id newline
05 do .Display
06 { ' Buttons: '
07 do :Buttons { id ' ' }
08 newline
09 }
10 do .LogicalWatch
11 { ' Apps: '
12 do decompositions
13 { foreach .State [Watch] { id ' ' } }
14 newline
15 }
16 newline
17 }
When studying the MERL code above it is important to
understand the concept of execution context. When the generator is executing, it
always operates in the context provided by the currently accessed model element
and its environment. This context defines and constrains what design data can be
retrieved and which other design elements can be accessed directly at that time.
For example, in the context of a Graph, we can always retrieve the property
values of the Graph itself and navigate to design elements that are directly in
that Graph: objects, relationships, ports and roles. Navigating to an object in
the Graph will change this focus – the context is now that of the object.
From the context of the object we can retrieve its property values, navigate to
roles attached to the object, or navigate to decomposition or explosion
subgraphs associated with it.
Armed with this understanding of the execution context, it
is now easier to decode our example MERL code. After the obligatory generator
header (line 1) we will first print out the heading text for the specification
sheet (line 2). The heading text is a combination of a constant piece of text
(‘SpecSheet for ’) and the name of the WatchFamily graph fetched
with the id command, followed by two
newline characters. It is important to note that the
id command operates on the current
context, here the current graph itself – it will return the value of this
graph’s identifying property.
We continue by looping over all Watch relationships found
in the Graph (line 3). The foreach loop
iterates over each element of the current graph matching the type specified.
Inside this loop the context will be that of the currently accessed Watch
relationship – therefore the id
command on line 4 will now fetch the identifier from the Watch relationship. The
identifier value will be output with a preceding text label and a newline
character.
Lines 5–9 print out the list of button names. To
fetch these names, we need to navigate from the current Watch relationship
through a Display role to the attached Display object. Navigating within a graph
uses a do loop, which can specify a
chain of navigation steps. In our example, we want to follow the Display role
into its Display object, which would look like this:
do ~Display.Display
We can, however, use a handy
shortcut in this case: instead of explicitly stating which role to follow, we
can just say which type of Object we want to end up in (as in line 5). Inside
the do loop (line 6), we are now in the
context of the single Display object attached to this Watch relationship. Here,
we will first output a header for the list (line 6) and then proceed to retrieve
the button names. The buttons are objects contained in a collection property of
the Display object, so in order to collect their names we have to loop over that
collection using the do command (line
7). Within the loop we will then print out the value of the identifying property
of the Button at hand, followed by a blank character as a separator. The
‘}’ character at the end of
line 7 marks the end of this loop, and the
newline command in line 8 adds a line
break after the Button list. Line 9 will then complete the enclosing loop that
fetches the Display objects from the Watch relationships.
At this point we are back in the context of our Watch
relationship, in the top-level loop. The next step is to collect the names of
the Watch’s applications.
10 do .LogicalWatch
11 { ' Apps: '
12 do decompositions
13 { foreach .State [Watch] { id ' ' } }
14 newline
15 }
16 newline
17 }
We start by following the role from the current
Watch relationship to its LogicalWatch object (line 10) and printing a header
string for the application list (line 11). This time, the content that we want
is not in a property of the LogicalWatch, but in a subgraph of it. To follow
this link we use the do decompositions
command (line 12) that will navigate into the associated subgraph. In this
subgraph, we will now loop over all State [Watch] objects, which represent the
applications, and retrieve their names. As before, to loop over graph elements
we use foreach (line 13). Within the
loop the id command will output the
name of the State and we will follow it with a blank character as a separator.
After that we will close both the innermost
foreach loop and the enclosing
do decompositions loop (line 13) and
add a line break (line 14) after the list of application names. Line 15 finishes
the loop over the LogicalWatches, returning to the context of the Watch
relationship.
Line 16 will add an empty line into the output as
separator between Watches before the next loop iteration. When all Watch
relationships have been iterated over, line 17 finally closes the original
foreach loop, and our generator ends
there.
Executing this MERL code will result in the following
output window:
Figure 5–8. Generator ouput for ‘SpecSheet’ generator.
We
have also put together a video lesson showing the execution of this example
generator, highlighting the navigation and how it changes the execution context.
The video can be watched at http://www.metacase.com/webcasts/MERL_Primer_Generator_Context.html.
(In case you can't view the video below, it is also available here.)