Flexmi (pronounced flex-em-eye) is a reflective textual syntax for EMF models. Flexmi supports an XML-based and a YAML-based flavour and offers features such as fuzzy matching of tags and attributes against Ecore class/feature names, support for embedding EOL expressions in models and for defining and instantiating model element templates. For example, the following XML document (acme.flexmi
):
<?nsuri psl?> <project title="ACME"> <person name="Alice"/> <person name="Bob"/> <task title="Analysis" start="1" dur="3"> <effort person="Alice"/> </task> <task title="Design" start="4" dur="6"> <effort person="Bob"/> </task> <task title="Implementation" start="7" dur="3"> <effort person="Bob" perc="50"/> <effort person="Alice" perc="50"/> </task> </project>
is a valid instance of the Ecore metamodel (in Emfatic) below (psl
stands for Project Scheduling Language):
@namespace(uri="psl", prefix="") package psl; class Project { attr String name; attr String description; val Task[*] tasks; val Person[*] people; } class Task { attr String title; attr int start; attr int duration; val Effort[*] effort; } class Person { attr String name; ref Skill[*] skills; } class Effort { ref Person person; attr int percentage = 100; } class Skill { attr String name; }
psl.emf
in your workspace and place the Emfatic content above in it.psl.ecore
) as shown here.acme.flexmi
and place the XML content above in it.The Flexmi parser uses fuzzy matching to map the tags in the XML document to instances of EClasses in the target metamodel. In Flexmi, attributes and non-containment references are captured using XML attributes. Multiple values can be captured in a single XML attribute as comma-delimited strings as shown below.
<?nsuri psl?> <_> <person name="Alice" skills="Java, HTML"/> <skill name="Java"/> <skill name="HTML"/> </_>
Containment references are captured using XML element containment. If an XML element has attributes, the Flexmi parser will compare its tag against EClass/EReference names expected in the context and choose the best match. For example, when it encounters the <person>
element below, knowing that it is already in the context of Project
it will match the name person
against the names of the containment references of Project
(tasks
, people
) and (all the sub-types of) their types (Person
, Task
) and will decide that the best match for it is Person
.
<?nsuri psl?> <project title="ACME"> <person name="Alice"/> ... </project>
As such, it will create an instance of Person
and will then try to find a suitable containment reference for it (people
). If there were multiple containment references of type Person
in class Project
, we could help the Flexmi parser by either using the name of the target reference instead or person
, or by using an empty container element as follows.
<?nsuri psl?> <project title="ACME"> <people> <person name="Alice"/> </people> </project>
To resolve non-containment references, Flexmi needs target elements to have some kind of ID. If a class has an EAttribute marked as id
, Flexmi will use that to identify its instances, otherwise, it will use the value of the name
attribute, if present. Fully-qualified ID paths, separated by .
are also supported.
XML elements can also be used instead of XML attributes to capture long/multiline EAttributes. For example, we can use a <description>
nested element instead of an attribute as below.
<?nsuri psl?> <project title="ACME"> <description> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </description> </project>
To keep very long values out of Flexmi models altogether, appending an _
to the name of an attribute will instruct the Flexmi parser to look for a file with that name and parse its content as the value of the attribute as shown below.
<?nsuri psl?> <project title="ACME" description_="readme.txt"> </project>
The Flexmi parser uses an implementation of the Hungarian algorithm to decide the best match of XML attribute names to EAttibute/(non-containment) EReference names.
Prepending :
to the name of an attribute instructs the Flexmi parser to interpret its value as an executable EOL expression instead of a literal value. Also, Flexmi supports attaching a :var
or a :global
attribute to XML elements, to declare local/global variables that can be used in EOL expressions. The scope of local variables includes siblings of the element, and their descendants, while global variables can be accessed from anywhere in the model.
For example, in the Flexmi model below, the Design
task is assigned to a local variable named design
, which is then used to compute the value of the start
time of the implementation task.
<?nsuri psl?> <project title="ACME"> <person name="Alice"/> <person name="Bob"/> <task title="Analysis" start="1" dur="3"> <effort person="Alice"/> </task> <task title="Design" start="4" dur="6" :var="design"> <effort person="Bob"/> </task> <task title="Implementation" :start="design.start + design.duration + 1" dur="3"> <effort person="Bob" perc="50"/> <effort person="Alice" perc="50"/> </task> </project>
You can also use :var
/:global
and EOL attributes to refer to model elements without using names/ids as identifiers. For example, in the version, below, Alice
is attached to the local variable name alice
, which is then used in the :person
reference of the second effort of the Implementation
task.
<?nsuri psl?> <project title="ACME"> <person name="Alice" :var="alice"/> <person name="Bob"/> <task title="Analysis" start="1" dur="3"> <effort person="Alice"/> </task> <task title="Design" start="4" dur="6" :var="design"> <effort person="Bob"/> </task> <task title="Implementation" :start="design.start+design.duration+1" dur="3"> <effort person="Bob" perc="50"/> <effort :person="alice" perc="50"/> </task> </project>
Flexmi supports the <?import other.flexmi?>
and <?include other.flexmi?>
processing instructions. import
creates a new resource for other.flexmi
while include
parses the contents of other.flexmi
as if they were embedded in the Flexmi model that contains the include
processing instruction.
Multiple <?nsuri metamodeluri?>
processing instructions can be used in the preamble of a Flexmi model, allowing it to instantiate multiple Ecore metamodels. However, in case of name clashes between them, there's no good way for disambiguation.
If you need to have multiple top-level elements in your model, you can add them under a <_>
root element, which has no other semantics.
Flexmi supports defining reusable templates through the reserved <:template>
XML tag. For example, when designing one-person projects where all tasks take place in sequence, we can omit all the repetitive <effort>
elements that refer to the same person, and we can automate the calculation of the start date of each task using a simpletask
template, as shown below.
<?nsuri psl?> <_> <project title="ACME"> <person name="Alice"/> <simpletask title="Analysis" dur="3"/> <simpletask title="Design" dur="3"/> <simpletask title="Implementation" dur="6"/> </project> <:template name="simpletask"> <content> <task :start="Task.all.indexOf(self).asVar('index') == 0 ? 1 : Task.all.get(index-1).asVar('previous').start + previous.duration"> <effort :person="Person.all.first()"/> </task> </content> </:template> </_>
Flexmi templates also support parameters, which can be used to configure the content they produce when they are invoked. An example is shown below:
<?nsuri psl?> <_> <project title="ACME"> <person name="Alice"/> <design dur="3" person="Alice"/> </project> <:template name="design"> <parameter name="person"/> <content> <task name="Design"> <effort person="${person}"/> </task> </content> </:template> </_>
To further customise the content that Flexmi templates produce, one can use an EGL template that produces XML as the value of the <content>
element of the template, by setting it's language to EGL as shown below. Also Flexmi supports a <:slot>
element in the content of templates, which specifies where any nested elements of the caller should be placed in the produced XML as shown below.
<?nsuri psl?> <_> <project title="ACME"> <person name="Alice"/> <longtask title="Implementation" years="2"> <effort person="Alice"/> </longtask> </project> <:template name="longtask"> <parameter name="years"/> <content language="EGL"> <![CDATA[ <task duration="[%=years.asInteger()*12%]"> <:slot/> </task> ]]> </content> </:template> </_>
Templates can be stored in separate Flexmi files and be imported from different models using Flexmi's <?include ?>
processing instruction.
Flexmi offers and registers an implementation of EMF's Resource interface (FlexmiResource
), and can be used like any other EMF resource implementation. For example, you can add .flexmi
models as regular EMF models to the run configuration of your Epsilon program. An example of using Flexmi from Java follows.
ResourceSet resourceSet = new ResourceSetImpl(); resourceSet.getResourceFactoryRegistry(). getExtensionToFactoryMap().put("flexmi", new FlexmiResourceFactory()); Resource resource = resourceSet.createResource (URI.createFileURI("/../acme.flexmi")); resource.load(null);
You can convert a Flexmi model to standard XMI (with no templates, executable attributes etc.) by right-clicking on it in the Project Explorer view and selecting Generate XMI
.
Converting an XMI model to Flexmi on the other hand is not supported as there's no unique mapping in this direction.
Since Epsilon 2.3.0, Flexmi also supports a YAML flavour. Equivalent YAML representations for the XML-based model at the top of this page are shown below.
!!! info The YAML flavour of Flexmi supports all the features of the XML flavour, including plain and dynamic templates, and executable attributes. Also, it is worth noting that YAML is a superset of JSON.
=== “Indentation-based”
```yaml ?nsuri: psl project: - name: ACME - person: {name: Alice} - person: {name: Bob} - task: - title: Analysis - start: 1 - dur: 3 - effort: {person: Alice} - task: - title: Design - start: 4 - dur: 6 - effort: {person: Bob} - task: - title: Implementation - start: 7 - dur: 3 - effort: {person: Bob, perc: 50} - effort: {person: Alice, perc: 50} ```
=== “Curly brackets-based”
```yaml ?nsuri: psl project: { name: ACME, person: {name: Alice}, person: {name: Bob}, task: { title: Analysis, start: 1, dur: 3, effort: {person: Alice} }, task: { title: Design, start: 4, dur: 6, effort: {person: Bob} }, task: { title: Implementation, start: 7, dur: 3, effort: {person: Bob, perc: 50}, effort: {person: Alice, perc: 50} } } ```
For multi-valued attributes and non-containment references, comma-separated values, or lists of scalars can be used as shown below.
- ?nsuri: psl - person: - name: Alice - skills: Java, HTML # Comma-separated - person: - name: Bob - skills: # List of scalars - Java - HTML - skill: {name: Java} - skill: {name: HTML}
!!! tip “Tabs vs. Spaces” If your YAML-flavoured Flexmi model doesn't parse (i.e. the outline view of the Flexmi editor is empty), you may want to check that you have not accidentally used tabs instead of spaces for indentation.
The Flexmi parser auto-detects whether a file is XML-based or YAML-based and parses it accordingly. As such, you should be able to edit YAML-flavoured *.flexmi
files in the Flexmi editor. Additional examples of YAML-flavoured Flexmi models are available in this test project (look for *.yaml
files).
The YAML flavour requires a script
attribute in the content
of dynamic templates, that holds the EGL script used to dynamically produce the YAML content. The YAML equivalent of the XML-based dynamic template shown above is as follows.
- ?nsuri: psl - project: - title: ACME - person: - name: Alice - longtask: - title: Implementation - years: 2 - effort: - person: Alice - :template: - name: longtask - parameter: - name: years - content: - language: EGL - script: |- # Multi-line EGL script - task: - duration: [%=years.asInteger()*12%] - :slot
Flexmi was originally developed as a quick and dirty way to type in EMF models without having to define an Xtext grammar or adhere to the rigid naming rules of XMI or HUTN. The name is a combination of the word “flexible” and the “XMI” acronym.
resource.save(...)
will do nothing).flexmi
in their name, under the examples folder of Epsilon's Git repository.