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.
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.
("Architecture: " + Architecture.all.first().name).println();
for (c in Component.all) {
(c.name + ": " + c.ports.size() + " ports").println();
}
More files: alarm.flexmi, components.emf
<?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>
@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;
}
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.
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:
context Connector {
constraint Compatible {
check: self.source.type = self.target.type
message: "Incompatible signal types"
}
}
More files: alarm.flexmi, components.emf
<?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>
@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;
}
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.
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.
Architecture: [%=Architecture.all.first().name%]
Components: [%=Component.all.size()%]
Connectors: [%=Connector.all.size()%]
More files: alarm.flexmi, components.emf
<?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>
@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;
}
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.
rule Component2Summary
transform c : Component {
template: "template.egl"
target: c.name + ".txt"
}
[% var noun = "ports"; if (c.ports.size() = 1) { noun = "port"; } %][%=c.name + ": " + c.ports.size() + " " + noun%]
More files: alarm.flexmi, components.emf
<?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>
@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;
}
--- AndGate.txt ---
AndGate: 3 ports
--- OrGate.txt ---
OrGate: 3 ports
--- Siren.txt ---
Siren: 1 port
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.
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.
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();
}
@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
<?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>
@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;
}
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.
Exercises
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.
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.
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.
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:
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
<?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>
@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;
}
OrGate.open
AndGate.sound
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:
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
<?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>
@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;
}
All constraints are satisfied.
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:
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
<?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>
@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;
}
Model: Alarm
Component: OrGate
Component: AndGate
Component: Siren
Validation: run rules.evl before deployment
Self-check quiz
model ref="M" refer to?Further reading
- Epsilon download and Eclipse installation; Playground exports.
- Ant and Maven execution and the EMC guide.
- Dimitris Kolovos’s Introduction to MDE, EMF and Epsilon public lectures offer further viewing.