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

From Playground to real projects

Install Eclipse Epsilon, run programs from Ant, Maven and Java, and work with models that are not EMF models.

4 hours4 sessions8 runnable examplesEpsilon Playground

By the end you can

  • Choose a local Epsilon setup and export the examples.
  • Explain Ant, Maven and Java execution paths.
  • Identify non-EMF model drivers and next tools to explore.

Before you start

Modules 1–8 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
Beyond the browser: set up Epsilon
Session 260 min
Run with Ant, Maven and Java
Session 360 min
Other model formats and a project workflow
Session 460 min
Practice
1

Beyond the browser

The Playground made the first eight modules easy to run. A project usually also needs versioned files, repeatable builds, local data, generated files on disk, and integration with other tools. Keep the same three inputs—metamodel, model, program—when moving out of the browser. Export a working example before changing its execution environment, then compare the local result with the captured result on this page.

The module’s download bundle contains its example files. The Playground’s own Download button can package the currently open example as an Eclipse Ant, Maven, Gradle, or Java project, with a README; see the Playground download guide. For a local checkout of this tutorial, its Maven runner source shows a tested Java embedding of EOL, EVL, EGL, EGX and ETL against the same files.

2

Set up Eclipse Epsilon

The Epsilon download page describes two routes: choose Epsilon in the Eclipse Installer, or use Help → Install New Software in an existing Eclipse with the stable update site https://download.eclipse.org/epsilon/updates/2.8/. The current stable Epsilon 2.8 line matches this tutorial’s captured runs. Open a new project, extract the bundle, and inspect components.emf, alarm.flexmi and inspect.eol. Configure an EOL run with the model named M, the Flexmi file as its source and components.emf as its metamodel. The model name matters if the program qualifies types or uses named models.

Run the query first in the Playground, then locally. Both should list the alarm and its three components. If local loading fails, check the metamodel namespace, file paths and the full paths used by connector references before changing the EOL code.

ExampleInspect the exported modelOpen in Playground ↗
inspect.eol
("Architecture: " + Architecture.all.first().name).println();
for (c in Component.all) {
  (c.name + ": " + c.ports.size() + " ports").println();
}
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: 3 ports
AndGate: 3 ports
Siren: 1 ports

The example files are small on purpose. Use the output as a known reference while learning Eclipse’s run configuration. The Epsilon getting started guide and the Playground’s exported README give the current UI steps for the installation you use.

3

Run with Ant and Maven

An Ant workflow names the models to load and the programs to run. Epsilon supplies Ant tasks for EOL and other languages. A typical EMF model task loads an Ecore metamodel and a model, then the EOL task refers to the loaded model:

<epsilon.emf.loadModel name="M" modelFile="alarm.xmi" metamodelFile="components.ecore"/>
<epsilon.eol src="inspect.eol"><model ref="M"/></epsilon.eol>

This snippet assumes you have exported the Emfatic and Flexmi inputs as Ecore and XMI. For the tutorial’s .emf and .flexmi files, start from the Playground’s Ant (Eclipse) download, which includes the required setup, or use the complete Epsilon command-line Ant example. A bare two-line task fragment does not install Ant tasks or convert file formats.

For Maven, choose Maven from the Playground download menu and run mvn clean package in the extracted project. The generated pom.xml supplies dependencies and invokes Epsilon tasks. Pin the Epsilon version in a real project; the download page shows Maven coordinates for version 2.8.0. Put validation before generation in the workflow so an invalid model does not quietly produce deliverables. This EVL gate checks connector signal types:

ExampleRun a validation gate locallyOpen in Playground ↗
rules.evl
context Connector {
  constraint Compatible {
    check: self.source.type = self.target.type
    message: "Incompatible signal types"
  }
}
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
All constraints are satisfied.

Inspect the results locally as well as the exit status. A validation finding needs an explicit build policy: report it, fail the build, or allow a documented exception.

4

Run from Java

Choose Java (Maven) from the Playground download menu to obtain a small Java project and the Epsilon API setup for the current example. The sequence in Java is: create a module such as EolModule, parse its program, load a model through an EMC driver, add that model to the module’s repository, execute the module, then dispose of resources. The tutorial runner’s Run.java is a fuller implementation. Its pom.xml pins Epsilon 2.8.0, and the site build uses it to capture every example output.

The next EGL template is a useful embedding check: Java can capture its text, or a build can write it to a file. Compare the values with the model rather than assuming that a completed template emitted the intended data.

ExampleGenerate a local reportOpen in Playground ↗
generate.egl
Architecture: [%=Architecture.all.first().name%]
Components: [%=Component.all.size()%]
Connectors: [%=Connector.all.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
Components: 3
Connectors: 2

For multiple output files, EGX invokes an EGL template once per matching element. The local runtime must choose a writable output directory; the Playground shows the generated files in its output pane.

ExampleCreate one summary per componentOpen in Playground ↗
generate.egx
rule Component2Summary
  transform c : Component {
  template: "template.egl"
  target: c.name + ".txt"
}
template.egl
[% var noun = "ports"; if (c.ports.size() = 1) { noun = "port"; } %][%=c.name + ": " + c.ports.size() + " " + noun%]
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: 3 ports
--- OrGate.txt ---
OrGate: 3 ports
--- Siren.txt ---
Siren: 1 port
5

Other model formats and tools

Epsilon’s EMC layer connects its languages to different model technologies. EMF is one driver, not the definition of a model. The EMC guide lists CSV, XML, JSON, YAML and other drivers. For example, the CSV driver guide uses Row.all in EOL and an epsilon.csv.loadModel Ant task. A CSV row is still an element you can query, but it does not have the component metamodel used in these Playground examples. Choose a driver and test its type names, feature names and persistence behaviour with a tiny sample before porting rules.

When a project changes shape, other Epsilon tools become relevant: ECL compares models, EML merges them, Flock migrates old models, EUnit tests Epsilon programs, and Picto displays model views in Eclipse. These are starting points for later study, not required for the capstone.

6

Plan a real project

Decide which model is authoritative, how it is validated, which outputs are regenerated, and what is checked into version control. Keep a small end-to-end example in continuous integration. If a transformation feeds a generator, validate the target model too. The ETL example below reuses the component-to-graph mapping: a local workflow could run EVL on the source, ETL, checks on the graph, then a graph report.

ExampleTransform before exportingOpen in Playground ↗
transform.etl
rule Architecture2Graph
  transform a : Source!Architecture
  to g : Target!Graph {
  g.name = a.name;
  g.nodes ::= a.components;
  g.edges ::= a.connectors;
}
rule Component2Node
  transform c : Source!Component
  to n : Target!Node {
  n.name = c.name;
}
rule Connector2Edge
  transform c : Source!Connector
  to e : Target!Edge {
  e.source ::= c.source.eContainer();
  e.target ::= c.target.eContainer();
}
graph.emf
@namespace(uri="graph", prefix="graph")
package graph;
class Graph {
  attr String name;
  val Node[*] nodes;
  val Edge[*] edges;
}
class Node { attr String name; }
class Edge {
  attr String label;
  ref Node source;
  ref Node target;
}
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
Graph name="Alarm"
  Node name="OrGate"
  Node name="AndGate"
  Node name="Siren"
  Edge source->OrGate target->AndGate
  Edge source->AndGate target->Siren

Record the tool version and the exact input files with generated outputs. Decide how to handle manual edits; Module 6 introduced protected regions, but a project should test regeneration on a disposable copy before trusting a merge. A useful first local milestone is simple: one command or Eclipse launch produces the same component count, validation result and report text as these pages.

7

Exercises

Exercise 1★★★conceptual5 min

Choose a download. Which Playground download should you choose for an Eclipse Ant workflow? Which for an API-driven Java program built with Maven?

Show solution

Choose Ant (Eclipse) for the Eclipse workflow, and Java (Maven) for a Java API project with Maven dependencies. Read each exported README and check its paths.

Exercise 2★★★conceptual5 min

Order the workflow. Put model loading, validation, transformation and generation in a sensible order. Where would you inspect a transformed target?

Show solution

Load the source, validate it, transform it, inspect or validate the target, then generate final text. Dispose of models after the workflow. A project can stop before transformation when source validation fails.

Exercise 3★★★conceptual5 min

Choose a driver. A team receives a CSV inventory and an XML configuration. Which Epsilon layer lets EOL access them, and what must be checked before reusing a component rule?

Show solution

EMC provides CSV and XML model drivers. Check the driver’s element and feature names and how it loads and stores data; a rule written for Component will not automatically apply to CSV Row elements or XML nodes.

Exercise 4★★★coding10 min

Inspect exported files. Write an EOL query that lists every output port as Component.Port so you can compare a local run with the Playground.

Show solution

The query selects concrete output ports and prints their owning component names:

SolutionList source port namesOpen in Playground ↗
inspect.eol
for (c in Component.all) {
  for (p in c.ports.select(p | p.isTypeOf(OutPort))) {
    (c.name + "." + p.name).println();
  }
}
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
OrGate.open
AndGate.sound
Exercise 5★★★coding10 min

Add a local gate. Require component names to be unique before generation. Test the constraint on the alarm.

Show solution

Each alarm component has a distinct name, so this EVL run passes:

SolutionRequire distinct component namesOpen in Playground ↗
rules.evl
context Component {
  constraint UniqueName {
    check: Component.all.select(c | c.name = self.name).size() = 1
    message: "Duplicate component name: " + self.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
All constraints are satisfied.
Exercise 6★★★coding10 min

Create a handoff manifest. Generate a short text manifest with the model name, component names and a reminder to run validation.

Show solution

The EGL template reads names from the model and prints the validation step:

SolutionEmit a handoff manifestOpen in Playground ↗
generate.egl
Model: [%=Architecture.all.first().name%]
[% for (c in Component.all) { %]Component: [%=c.name%]
[% } %]Validation: run rules.evl before deployment
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
Model: Alarm
Component: OrGate
Component: AndGate
Component: Siren
Validation: run rules.evl before deployment
8

Self-check quiz

1
What should be compared after moving an example out of the Playground?
2
Which download contains a Java class that calls the Epsilon API?
3
What does an Ant model ref="M" refer to?
4
Why pin the Epsilon dependency version?
5
Which layer connects Epsilon languages to CSV and XML models?
6
What does Flock address?
7
What should happen before generating deliverables?
9

Further reading