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

Building and viewing models

Write Flexmi models in XML and YAML, resolve references by name, diagnose loading problems and inspect model diagrams.

3 hours3 sessions8 runnable examplesEpsilon Playground

By the end you can

  • Create a nested Flexmi model from an Emfatic metamodel.
  • Write equivalent small models with the XML and Epsilon 2.8 YAML flavours.
  • Resolve references with fully qualified names and explain ambiguous short names.
  • Spot model-loading warnings and distinguish them from program output.
  • Use Playground diagrams and simple Emfatic annotations to inspect a model.

Before you start

Modules 1 and 2, or a basic grasp of Ecore classes, containment and references. No local installation is needed.

Download all examples of this module (zip)

Contents

Study plan

3 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 the structure
Session 260 min
Link and diagnose
Session 360 min
View and practise
1

Flexmi and the metamodel

Module 2 defined a component-and-connector language in Emfatic. A metamodel names its kinds of elements (Architecture, Component, InPort, OutPort, Connector), the values they hold and the ways they relate. This module is about writing models in that language. A model records particular things: an architecture named Alarm, an OrGate with an output named open, and a connector to another port.

We write models in Flexmi, a compact notation for EMF models. Flexmi has XML and YAML flavours. It uses the metamodel to interpret the text, so a small Flexmi file can create a sizeable object graph. The same model can then be queried, validated or transformed by Epsilon. Here we use a short EOL query to reveal what loaded; Module 4 will teach EOL in depth.

The workflow is: read the metamodel, write the model, run the query, then inspect the model diagram. The output beneath each example was captured during the build on Epsilon 2.8.0. The browser Playground runs the examples too, but it does not show every model-loading warning. We will deliberately encounter one in Section 5.

Key idea

A Flexmi file is a concrete syntax for a model. Its element types, features and relationships come from the metamodel; changing the syntax does not change those concepts.

2

XML nesting and elements

Open the first example and compare its two visible files. components.emf declares Architecture, its containment references components and connectors, and the port classes. alarm.flexmi begins with <?nsuri components?>, which selects that metamodel’s namespace. The <architecture name="Alarm"> element then creates one Architecture instance.

ExampleThe alarm in XML FlexmiOpen in Playground ↗
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;
}
More files: inspect.eol
inspect.eol
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
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
Ports: 7
Connectors: 2

Read the XML from the outside in. Each <component> is nested under <architecture> because the architecture contains its components. Each <inPort> or <outPort> is nested under its component because Component.ports is a containment reference. The two <connector> elements also sit under the architecture. Their source and target attributes are ordinary references, not nested port definitions.

In this model the three components contain seven ports and the architecture contains two connectors. The query prints exactly those counts. Run it, then delete the Siren element and the connector pointing to it. Predict what the counts will be before running it again. You will see that a small textual edit changes the object graph the program reads.

Flexmi matches XML tags to metamodel class and containment-feature names. A <component> inside an architecture becomes a Component; an <inPort> inside a component becomes an InPort. This matching is deliberately flexible, so do not treat the tag spelling as a strict parser check. When a model behaves unexpectedly, inspect its diagram and query the types and values rather than assuming that every typo will raise an error. The Flexmi documentation explains its matching rules.

3

References by name

Containment gives every port a place in the model tree, but connectors need to link ports in different branches. Flexmi resolves an ordinary reference using an element’s identifier. Without an explicit ID attribute, it uses name where available. A fully qualified path starts at the named root and follows named containers down to the target.

The alarm contains two different ports named open: an output on OrGate and an input on AndGate. A bare open would not clearly identify one of them. The first connector therefore uses Alarm.OrGate.open for its source and Alarm.AndGate.open for its target. The query prints each endpoint with its containing component to make the distinction visible.

ExampleResolve full names across componentsOpen in Playground ↗
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>
More files: inspect.eol, components.emf
inspect.eol
for (c in Connector.all) {
  (c.source.eContainer().name + "." + c.source.name + " -> " +
   c.target.eContainer().name + "." + c.target.name).println();
}
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.open
AndGate.sound -> Siren.sound

Trace the second connector the same way: Alarm.AndGate.sound to Alarm.Siren.sound. Its two ports also share a short name. In Epsilon 2.8.0, use full paths beginning at the root for reliable reference resolution. The newer Flexmi documentation describes partial paths introduced in later versions; this series builds and checks examples on 2.8.0.

Pitfall

If you rename a component or port, update every path that names it. The XML nesting creates the element, but a connector’s source or target text must still resolve to that element.

4

YAML in Epsilon 2.8

Flexmi can also read YAML. The next example describes the same alarm with the same metamodel and runs the same query as the XML example. Compare the captured outputs: the architecture, component, port and connector counts are identical.

ExampleThe same alarm in Epsilon 2.8 YAMLOpen in Playground ↗
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:
  - name: AndGate
  - inPort: {name: open, type: DIGITAL}
  - inPort: {name: armed, type: DIGITAL}
  - outPort: {name: sound, type: DIGITAL}
- component:
  - name: Siren
  - inPort: {name: sound, type: DIGITAL}
- connector: {source: Alarm.OrGate.open, target: Alarm.AndGate.open}
- connector: {source: Alarm.AndGate.sound, target: Alarm.Siren.sound}
More files: inspect.eol, components.emf
inspect.eol
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
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
Ports: 7
Connectors: 2

Notice the 2.8 syntax. ?nsuri: components selects the metamodel. Beneath architecture:, a sequence of entries records the root’s properties and contained elements. An entry such as - component: introduces a component; entries indented below it set that component’s name and add its ports. A connector can be written compactly as - connector: {source: ..., target: ...}. Indentation matters, so use spaces consistently.

The current Flexmi documentation shows a revised YAML mapping for Epsilon 2.9 and later, with $nsuri. This series’ runner uses 2.8, and the live Playground check confirms that these examples run with the legacy form. Follow the legacy YAML reference when editing them. Copying a 2.9 example into a 2.8 environment may not work as intended.

For this small architecture, XML makes the tree easy to scan. YAML can be convenient when most lines are names and values. Both are concrete syntaxes for the same abstract structure; choose the one that your team can review and maintain reliably.

5

Warnings and conformance

The next model has a one-letter mistake in the second connector’s target: Alarm.Siren.sond instead of Alarm.Siren.sound. Its Siren.sound port exists, but the text names no such element as sond.

ExampleAn unresolved port referenceOpen in Playground ↗
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.sond"/>
</architecture>
More files: inspect.eol, components.emf
inspect.eol
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
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 warning (line 17): Could not resolve target Alarm.Siren.sond for reference target (target)
Architecture: Alarm
Components: 3
Ports: 7
Connectors: 2

Read the captured output in order. The local runner reports a model warning naming the line and the unresolved target, then the EOL query prints the same counts as the valid alarm. The connector itself still exists, so the query counts two connectors, even though the second has no target. Counting elements alone cannot prove that the wiring is sound.

In the Playground, the console shows the four count lines without the model warning. Switch the model panel to its diagram: the broken connector lacks an arrow to Siren.sound. The diagram reveals the missing link. Section 6 explains how to change the diagram’s presentation.

Other mistakes need different checks. A Flexmi tag may be matched fuzzily to a class or feature even when spelled badly; an XML attribute that no metamodel class declares is reported by this series’ local runner, but the Playground does not display that warning. A bound such as Component[+] is a declaration, yet simply running a query is not a complete validity check, as Module 2 showed. When a rule matters, inspect the model and add a deliberate check. Module 5 will use EVL to express domain rules such as connected inputs and compatible signal types.

Tip

Debug from the model outward: check the metamodel name, then the nesting, then the values, then every non-containment reference path. Compare the diagram with the text and read any warnings before trusting a program’s plausible output.

6

Diagrams and syntax

A model is not its XML or YAML file. Those files are ways to write it. The Playground can also draw the loaded model. By default, it shows an object diagram with model elements and references. Emfatic annotations can choose a more focused graphical presentation without changing the model’s concepts or its EOL query results.

The example below uses @node(label="name") on classes whose instances should appear as named nodes, and @edge(source="source", target="target") on Connector so its instances appear as edges between ports. The Flexmi file is the same valid alarm as before; only the Emfatic presentation annotations differ.

ExampleChoose diagram nodes and edgesOpen in Playground ↗
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;
}

@node(label="name")
class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
@node(label="name")
class InPort extends Port {}
@node(label="name")
class OutPort extends Port {}

@edge(source="source", target="target")
class Connector {
  ref OutPort source;
  ref InPort target;
}
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>
More files: inspect.eol
inspect.eol
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
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
Ports: 7
Connectors: 2

Open it in the Playground and switch the model panel to diagram view. Find the arrows made from the two connectors. Then switch back to text and remove the @edge annotation. The connectors are still in the model and the EOL counts do not change, but the diagram uses a different presentation. These annotations are a Playground feature; the local runner reads the metamodel and model, but its output check does not verify diagram appearance. The graphical-syntax annotation guide describes the available options.

You can also open a diagram of the metamodel in its panel. That diagram shows classes and references, not the alarm’s particular components and ports. Keep the levels distinct: the metamodel diagram explains the language; the model diagram shows one instance of that language. XML, YAML and diagrams are different concrete views of the model’s abstract structure.

7

Exercises

Exercise 1★★★conceptual4 min

Types or instances? In the XML example, classify Component, OrGate, OutPort, and Alarm.OrGate.open as metamodel types or model elements. Which model element is an instance of OutPort?

Show solution

Component and OutPort are metamodel types. OrGate and Alarm.OrGate.open are model elements. Alarm.OrGate.open is an instance of OutPort; OrGate is an instance of Component.

Exercise 2★★★conceptual4 min

Read the tree. In the XML alarm, who contains AndGate, its armed input, and the second connector? Does the connector contain either port it connects?

Show solution

Alarm contains AndGate and the second connector. AndGate contains armed. The connector merely refers to its source and target ports; it contains neither of them.

Exercise 3★★★conceptual4 min

Two ports named open. Write the full paths for the first connector’s source and target. Why is open alone a poor choice here?

Show solution

The source is Alarm.OrGate.open; the target is Alarm.AndGate.open. Both ports have the short name open, so it does not say which one is intended. Full paths distinguish them and work in Epsilon 2.8.0.

Exercise 4★★★coding6 min

Model a thermostat in XML. Use the same metamodel to make a Thermostat architecture. Sensor.reading connects to Filter.raw; Filter.smoothed connects to Display.value. Make all four ports ANALOG, then run the count query. Predict its four lines first.

Show solution

The architecture has three components, four ports and two connectors. The full model and captured output are below:

SolutionBuild the thermostat in XMLOpen in Playground ↗
alarm.flexmi
<?nsuri components?>
<architecture name="Thermostat">
  <component name="Sensor">
    <outPort name="reading" type="ANALOG"/>
  </component>
  <component name="Filter">
    <inPort name="raw" type="ANALOG"/>
    <outPort name="smoothed" type="ANALOG"/>
  </component>
  <component name="Display">
    <inPort name="value" type="ANALOG"/>
  </component>
  <connector source="Thermostat.Sensor.reading" target="Thermostat.Filter.raw"/>
  <connector source="Thermostat.Filter.smoothed" target="Thermostat.Display.value"/>
</architecture>
More files: inspect.eol, components.emf
inspect.eol
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
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: Thermostat
Components: 3
Ports: 4
Connectors: 2
Exercise 5★★★coding7 min

Translate the thermostat to YAML. Express the previous solution in the Epsilon 2.8 YAML flavour. Keep the same nesting and full connector paths. Does the count query change?

Show solution

Only the concrete syntax changes; the query and counts stay the same. The ?nsuri line and sequence entries use the legacy 2.8 mapping:

SolutionBuild the thermostat in YAMLOpen in Playground ↗
alarm.flexmi
?nsuri: components
architecture:
- name: Thermostat
- component:
  - name: Sensor
  - outPort: {name: reading, type: ANALOG}
- component:
  - name: Filter
  - inPort: {name: raw, type: ANALOG}
  - outPort: {name: smoothed, type: ANALOG}
- component:
  - name: Display
  - inPort: {name: value, type: ANALOG}
- connector: {source: Thermostat.Sensor.reading, target: Thermostat.Filter.raw}
- connector: {source: Thermostat.Filter.smoothed, target: Thermostat.Display.value}
More files: inspect.eol, components.emf
inspect.eol
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
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: Thermostat
Components: 3
Ports: 4
Connectors: 2
Exercise 6★★★coding5 min

Repair and inspect. Open the broken-reference example. Find the unresolved target in its Flexmi text, fix it and run again. Compare the captured output with the repaired solution and check the second connector in the Playground’s model diagram.

Show solution

Change Alarm.Siren.sond to Alarm.Siren.sound. The local warning disappears and both connector endpoints resolve. This query prints the actual endpoint names so the repaired relationship is visible:

SolutionRepair the missing targetOpen in Playground ↗
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>
More files: inspect.eol, components.emf
inspect.eol
for (c in Connector.all) {
  (c.source.eContainer().name + "." + c.source.name + " -> " +
   c.target.eContainer().name + "." + c.target.name).println();
}
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.open
AndGate.sound -> Siren.sound
8

Self-check quiz

1
What selects the Emfatic package for an XML Flexmi model?
2
Why is <outPort> nested inside <component>?
3
What does a connector’s target="Alarm.Siren.sound" do?
4
Which YAML namespace line belongs to the Epsilon 2.8 examples here?
5
What does the broken-reference example’s count of two connectors prove?
6
What does @edge(source="source", target="target") change in the diagram example?
7
What does a metamodel diagram show?
9

Further reading