Ran Wei/MDE Series
中文
Model-Driven Engineering with Eclipse Epsilon — Ran Wei

Generating code and text

Generate Java, HTML and diagrams from models with EGL templates and EGX rules.

4 hours4 sessions8 runnable examplesEpsilon Playground

By the end you can

  • Use EGL static and dynamic sections to generate text.
  • Use EGX to create one output file per model element.
  • Explain how protected regions affect regeneration.

Before you start

Modules 1–5 are recommended. Examples run in the Playground unless noted.

Download all examples of this module (zip)

Contents

Study plan

4 hours

Times are estimates and include running the examples. Progress is saved in this browser and shared by both language editions.

Session 160 min
Write an EGL template
Session 260 min
Generate several formats with EGX
Session 360 min
Protected regions and output review
Session 460 min
Practice
1

From models to text

A model can serve as one source for several outputs. The alarm’s component names and connections can become a parts list, an HTML report, a Graphviz diagram description or Java source. Model-to-text generation derives those outputs from the model so that a change can be reflected by regenerating. It does not make the output automatically correct: a generator must escape data, use valid identifiers and run only on models whose required properties have been checked.

Epsilon uses EGL (Epsilon Generation Language) for templates and EGX to coordinate template runs and target files. Both use the EOL navigation you learned in Module 4. In this module, the captured output is the generated text. For EGX, the local runner also names each generated file. Start with the small alarm; then try changing the model and rerunning a template.

2

Write an EGL template

An EGL template mixes text with dynamic regions. Text outside [% ... %] is copied unchanged. A block [% ... %] runs EOL statements, while [%= expression %] inserts an expression’s value. In the first example, a loop visits each component and writes one line with its port count.

ExampleA plain-text parts listOpen in Playground ↗
generate.egl
Architecture: [%=Architecture.all.first().name%]
[% for (c in Component.all) { %]
- [%=c.name%]: ports=[%=c.ports.size()%]
[% } %]
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" type="DIGITAL"/>
    <inPort name="window" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="DIGITAL"/>
  </component>
  <connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
  <connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
Architecture: Alarm
- OrGate: ports=3
- AndGate: ports=3
- Siren: ports=1

The template has a fixed heading and a dynamic list. Architecture.all.first().name supplies the architecture name. If you add a component to the Flexmi model, another line appears without editing the template. The generated file is a derived artefact: keep the model and template as the sources of truth, then regenerate the artefact after changes.

Key idea

Static template text supplies the output’s shape. Dynamic EOL expressions supply values taken from the model. Both parts are needed for a readable generated document.

3

Generate several formats

EGL is not limited to one target language. This template uses HTML tags as its static text and inserts component names and counts into table cells.

ExampleAn HTML status tableOpen in Playground ↗
generate.egl
<h2>[%=Architecture.all.first().name%]</h2>
<table>
<tr><th>Component</th><th>Inputs</th><th>Outputs</th></tr>
[% for (c in Component.all) { %]
<tr><td>[%=c.name%]</td><td>[%=c.ports.select(p | p.isTypeOf(InPort)).size()%]</td><td>[%=c.ports.select(p | p.isTypeOf(OutPort)).size()%]</td></tr>
[% } %]
</table>
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" type="DIGITAL"/>
    <inPort name="window" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="DIGITAL"/>
  </component>
  <connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
  <connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
<h2>Alarm</h2>
<table>
<tr><th>Component</th><th>Inputs</th><th>Outputs</th></tr>
<tr><td>OrGate</td><td>2</td><td>1</td></tr>
<tr><td>AndGate</td><td>2</td><td>1</td></tr>
<tr><td>Siren</td><td>1</td><td>0</td></tr>
</table>

The Playground may render the HTML; the captured block shows the exact generated markup. In a real generator, escape names before inserting them into HTML. The teaching model uses simple names, so the example focuses on the EGL structure rather than a complete HTML escaping library.

The next template produces Graphviz DOT, a textual graph description. Each component becomes a node and each connector becomes an arrow between the components that own its endpoint ports.

ExampleA Graphviz connection graphOpen in Playground ↗
generate.egl
digraph Alarm {
[% for (c in Component.all) { %]
  "[%=c.name%]";
[% } %]
[% for (wire in Connector.all) { %]
  "[%=wire.source.eContainer().name%]" -> "[%=wire.target.eContainer().name%]";
[% } %]
}
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" type="DIGITAL"/>
    <inPort name="window" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="DIGITAL"/>
  </component>
  <connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
  <connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
digraph Alarm {
  "OrGate";
  "AndGate";
  "Siren";
  "OrGate" -> "AndGate";
  "AndGate" -> "Siren";
}

DOT is another concrete output, not another model in the MDE sense used here. It omits port details: both the alarm model and an altered one with different port names could generate the same component-level graph. Decide deliberately which information the target artefact needs. A generated diagram can be helpful for review, but a text preview also makes the exact edge data easy to inspect.

4

One file per element with EGX

A single EGL template naturally makes one stream of text. EGX runs templates for selected model elements and chooses where each result goes. The rule below runs once per Component, passes that component as c to template.egl, and names the output file from c.name.

ExampleOne Java file per componentOpen in Playground ↗
generate.egx
rule Component2Java
  transform c : Component {
  template: "template.egl"
  target: c.name + ".java"
}
template.egl
public final class [%=c.name%] {
  public static final int INPUTS = [%=c.ports.select(p | p.isTypeOf(InPort)).size()%];
  public static final int OUTPUTS = [%=c.ports.select(p | p.isTypeOf(OutPort)).size()%];
}
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" type="DIGITAL"/>
    <inPort name="window" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="DIGITAL"/>
  </component>
  <connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
  <connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
--- AndGate.java ---
public final class AndGate {
  public static final int INPUTS = 2;
  public static final int OUTPUTS = 1;
}
--- OrGate.java ---
public final class OrGate {
  public static final int INPUTS = 2;
  public static final int OUTPUTS = 1;
}
--- Siren.java ---
public final class Siren {
  public static final int INPUTS = 1;
  public static final int OUTPUTS = 0;
}

The captured output has three file headers and three Java classes. template identifies the EGL template; target computes a file name. The template uses EOL’s select to count input and output ports. For a production generator, validate that every component name is a safe, unique Java identifier and escape any text inserted into generated source. Here the fixed example names meet those conditions.

EGX rules can also have guards, parameters and additional workflow controls. The EGX reference describes them. Keep decisions about which files to produce in EGX and decisions about what each file contains in EGL; that makes each part easier to change.

5

Protected regions

Regeneration would normally overwrite hand edits in a generated file. EGL can mark a protected region so that a file-generating workflow may preserve a marked section from an existing destination. The example emits a region named manual-note using out.preserve.

ExampleMark a protected regionOpen in Playground ↗
generate.egl
public final class AlarmNotes {
[%=out.preserve("/*", "*/", "manual-note", true, "  // Add project-specific notes here")%]
}
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" type="DIGITAL"/>
    <inPort name="window" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="DIGITAL"/>
  </component>
  <connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
  <connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
public final class AlarmNotes {
/* protected region manual-note on begin */
  // Add project-specific notes here
/* protected region manual-note end */
}

The captured output demonstrates the markers and initial content. It does not demonstrate a merge with an existing file: this lesson’s local runner processes the template without an earlier destination to merge, and the Playground is not an Eclipse file-generation workflow. To test preservation in a local project, generate to a file, edit only the marked region, then regenerate to the same destination with merging enabled. Keep generated and hand-written responsibilities clear; protected regions are not a substitute for reviewing changes to the model or template. The EGL merge documentation explains the marker and merge behaviour.

6

Review generated artefacts

Before using generated text, ask what it assumes. The Java generator assumes names make valid class identifiers. The HTML generator assumes names can be inserted safely into markup. The Graphviz generator ignores ports and signal types. None of the templates alone verifies that a connector is sound; Module 5’s EVL rules should check that first when those assumptions affect the output.

A useful generation workflow has three steps: validate the model, generate deterministically, then inspect or test the artefacts. The examples here make the inputs and outputs small enough to compare by eye. In a real project, include generated files in a build and test the code or documents in their target environment. Module 9 shows how to run Epsilon outside the Playground.

7

Exercises

Exercise 1★★★conceptual5 min

Static or dynamic? In an HTML table template, classify <tr><td>, [%=c.name%] and [% for (c in Component.all) { %] as static text, value insertion or executable template code.

Show solution

<tr><td> is static text copied to the output. [%=c.name%] inserts a value from the current component. The for block is executable EOL code controlling how often subsequent text is emitted.

Exercise 2★★★conceptual5 min

What is lost? The Graphviz example creates one node per component and one edge per connector. Name two facts in the source model that the generated graph does not record.

Show solution

It omits individual port names and their DIGITAL/ANALOG types. It also does not say which exact ports a connector joins. The generator intentionally selects a component-level view.

Exercise 3★★★conceptual5 min

Who owns each decision? In the Java example, which file decides the output file name, and which file decides the class body? What should be checked before using the generated classes?

Show solution

The EGX rule computes c.name + ".java"; template.egl writes the class body. Validate unique, legal class names and the model rules the generated code assumes. Compile or otherwise test the produced Java in a real workflow.

Exercise 4★★★coding10 min

Compact HTML. Make an EGL template that produces an unordered list with one item per component and its port count.

Show solution

The loop surrounds one static <li> line, while expressions insert its values:

SolutionGenerate a compact HTML listOpen in Playground ↗
generate.egl
<ul>
[% for (c in Component.all) { %]
<li>[%=c.name%] (ports: [%=c.ports.size()%])</li>
[% } %]
</ul>
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" type="DIGITAL"/>
    <inPort name="window" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="DIGITAL"/>
  </component>
  <connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
  <connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
<ul>
<li>OrGate (ports: 3)</li>
<li>AndGate (ports: 3)</li>
<li>Siren (ports: 1)</li>
</ul>
Exercise 5★★★coding10 min

One summary per component. Write an EGX rule that invokes template.egl once for each component and writes <name>.txt. Each file should report that component’s port count.

Show solution

The rule chooses targets; the template produces the contents. The local output shows three separate files:

SolutionGenerate one text file per componentOpen in Playground ↗
generate.egx
rule Component2Summary
  transform c : Component {
  template: "template.egl"
  target: c.name + ".txt"
}
template.egl
[%=c.name%]: ports=[%=c.ports.size()%].
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" type="DIGITAL"/>
    <inPort name="window" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="DIGITAL"/>
  </component>
  <connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
  <connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
--- AndGate.txt ---
AndGate: ports=3.
--- OrGate.txt ---
OrGate: ports=3.
--- Siren.txt ---
Siren: ports=1.
Exercise 6★★★coding10 min

Label the edges. Modify the DOT template so each connector arrow has a label naming its source and target ports.

Show solution

The added label uses wire.source.name and wire.target.name:

SolutionLabel Graphviz edgesOpen in Playground ↗
generate.egl
digraph Alarm {
[% for (wire in Connector.all) { %]
  "[%=wire.source.eContainer().name%]" -> "[%=wire.target.eContainer().name%]" [label="[%=wire.source.name%] to [%=wire.target.name%]"];
[% } %]
}
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" type="DIGITAL"/>
    <inPort name="window" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="DIGITAL"/>
  </component>
  <connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
  <connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
digraph Alarm {
  "OrGate" -> "AndGate" [label="open to open"];
  "AndGate" -> "Siren" [label="sound to sound"];
}
8

Self-check quiz

1
What happens to text outside EGL markers?
2
What does [%=c.name%] do?
3
What is EGX responsible for in the Java example?
4
Where is the body of each generated Java class defined?
5
What does the DOT example omit?
6
Does the protected-region example prove that an edited destination was preserved?
7
Which step should precede generation when output assumes valid connections?
9

Further reading