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.
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.
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.
<?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;
}
More files: inspect.eol
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
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.
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.
<?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
for (c in Connector.all) {
(c.source.eContainer().name + "." + c.source.name + " -> " +
c.target.eContainer().name + "." + c.target.name).println();
}
@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.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.
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.
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.
?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
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
@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
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.
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.
<?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
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
@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 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.
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.
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.
@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;
}
<?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
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
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.
Exercises
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.
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.
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.
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:
<?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
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
@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: Thermostat
Components: 3
Ports: 4
Connectors: 2
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:
?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
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
@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: Thermostat
Components: 3
Ports: 4
Connectors: 2
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:
<?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
for (c in Connector.all) {
(c.source.eContainer().name + "." + c.source.name + " -> " +
c.target.eContainer().name + "." + c.target.name).println();
}
@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.open
AndGate.sound -> Siren.sound
Self-check quiz
nsuri instruction identifies the metamodel namespace.<outPort> nested inside <component>?target="Alarm.Siren.sound" do?@edge(source="source", target="target") change in the diagram example?Further reading
- The Flexmi documentation explains XML nesting, fuzzy tag matching and reference resolution.
- The legacy Flexmi YAML reference documents the Epsilon 2.8 syntax used here.
- The Playground graphical-syntax annotation guide describes
@node,@edgeand diagram options. - The Epsilon Playground guide explains model and metamodel diagrams and the browser controls.