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

Validating models

Express well-formedness rules in EVL with constraints, critiques, guards and fixes.

3 hours3 sessions8 runnable examplesEpsilon Playground

By the end you can

  • Write EVL constraints and critiques for a model.
  • Use guards, messages and dependencies to report useful results.
  • Explain how fixes are offered in Eclipse and what the Playground shows.

Before you start

Modules 1–4 are recommended. Examples run in the Playground unless noted.

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
Validation rules and feedback
Session 260 min
Guards, dependencies and fixes
Session 360 min
Apply and practise
1

Why validation needs rules

A metamodel says that a connector has an output-port source and an input-port target. It cannot, by that structure alone, decide whether the two ports belong to different components or carry compatible signals. Module 4 could query those facts; this module states which ones a valid architecture must satisfy. Epsilon’s EVL (Epsilon Validation Language) evaluates rules over model elements and reports violations.

EVL uses EOL expressions inside check, guard and message blocks. A context Connector applies its rules to each connector. A constraint reports an error when its check is false; a critique reports a warning for a less serious issue. The examples deliberately include both valid and invalid models. The captured output below each one is the local runner’s readable report. In the Playground, the console stays empty for EVL and the findings appear as notes on the validated model diagram.

2

Constraints and critiques

This alarm has a wiring mistake: one connector runs from AndGate.sound back to AndGate.armed. The NoSelfLoop constraint compares the containing components of its source and target. The Connected critique asks whether any connector targets each input.

ExampleErrors and warnings for bad wiringOpen in Playground ↗
rules.evl
context Connector {
  constraint NoSelfLoop {
    check: self.source.eContainer() <> self.target.eContainer()
    message: "A connector joins " + self.source.eContainer().name + " to itself"
  }
}
context InPort {
  critique Connected {
    check: Connector.all.exists(c | c.target = self)
    message: self.eContainer().name + "." + self.name + " is not connected"
  }
}
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" external="true" type="DIGITAL"/>
    <inPort name="window" external="true" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" external="true" 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.AndGate.armed"/>
</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 {
  attr boolean external;
}
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
Error [NoSelfLoop]: A connector joins AndGate to itself
Warning [Connected]: OrGate.door is not connected
Warning [Connected]: OrGate.window is not connected
Warning [Connected]: Siren.sound is not connected

The error identifies the self-loop. The warnings name inputs without a connector. OrGate.door and OrGate.window are meant to receive signals from outside this model, so those two warnings are false positives for our intended architecture. The failed self-loop also leaves Siren.sound without a connection. A good validation set needs domain distinctions, not just more checks.

Rules should produce useful messages. self.source.eContainer().name names the component of a connector’s source port. A bare message such as “invalid connector” would be harder to act on. Use a critique when a finding deserves review but does not necessarily invalidate the model, and a constraint when a violation makes the model unacceptable for the intended task.

3

Guards and messages

The metamodel in these examples gives InPort an external Boolean. A guard can then limit a rule to internal inputs. guard: not self.external skips the Connected critique for door, window and armed, while still checking the internal open and sound inputs.

ExampleSkip external inputs with a guardOpen in Playground ↗
rules.evl
context Connector {
  constraint NoSelfLoop {
    check: self.source.eContainer() <> self.target.eContainer()
    message: "A connector joins " + self.source.eContainer().name + " to itself"
  }
}
context InPort {
  critique Connected {
    guard: not self.external
    check: Connector.all.exists(c | c.target = self)
    message: self.eContainer().name + "." + self.name + " is not connected"
  }
}
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" external="true" type="DIGITAL"/>
    <inPort name="window" external="true" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" external="true" 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 {
  attr boolean external;
}
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.

The correctly wired alarm satisfies all checks. A guard answers when to check; a check answers what must hold for an applicable element. You can put a guard on one rule or on a whole context. Do not hide an actual error by writing a guard that is too broad. Exercise 4 asks you to compare the guarded and unguarded versions.

A different rule checks signal compatibility. The next model makes Siren.sound analog while the output feeding it is digital. Both ports are legal instances of the metamodel, and the connector is structurally well-typed; the domain rule still rejects the pairing.

ExampleReject a signal-type mismatchOpen in Playground ↗
rules.evl
context Connector {
  constraint CompatibleTypes {
    check: self.source.type = self.target.type
    message: self.source.eContainer().name + "." + self.source.name +
      " and " + self.target.eContainer().name + "." + self.target.name +
      " have different signal types"
  }
}
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" external="true" type="DIGITAL"/>
    <inPort name="window" external="true" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" external="true" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="ANALOG"/>
  </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 {
  attr boolean external;
}
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
Error [CompatibleTypes]: AndGate.sound and Siren.sound have different signal types

This is why validation is separate from basic loading. The model can be represented and queried, but a downstream generator should refuse to assume the connection is meaningful until the rule passes.

4

Dependencies and fixes

Sometimes one rule is meaningful only after another passes. In this draft model, one component has a two-letter name and another has no name. HasName checks the prerequisite; LongEnough checks length only if self.satisfies("HasName"). The prerequisite is marked @lazy, so it runs when requested by the dependent rule.

ExampleDepend on a prerequisite ruleOpen in Playground ↗
rules.evl
context Component {
  @lazy
  constraint HasName {
    check: self.name.isDefined() and self.name <> ""
    message: "A component has no name"
  }
  constraint LongEnough {
    guard: self.satisfies("HasName")
    check: self.name.length() >= 3
    message: self.name + " is shorter than three characters"
  }
}
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Draft">
  <component name="AB"/>
  <component name=""/>
</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 {
  attr boolean external;
}
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
Error [LongEnough]: AB is shorter than three characters
Error [HasName]: A component has no name

The output contains one message for the short name and one for the missing name. It does not try to calculate the length of a missing name. Dependencies make the report focused and avoid errors inside checks. The EVL reference also describes satisfiesAll and satisfiesOne for several prerequisites.

EVL rules may offer a fix with a title and EOL do block. The next critique spots a lowercase siren and offers a specific rename. The local runner reports the critique but does not apply the fix. In an Eclipse validation workflow, a user can select a proposed fix; the Playground’s validated diagram shows the finding but does not provide that interactive Eclipse fix workflow.

ExampleOffer a repair for a naming critiqueOpen in Playground ↗
rules.evl
context Component {
  critique Capitalised {
    check: self.name.matches("[A-Z].*")
    message: self.name + " should start with a capital letter"
    fix {
      title: "Rename siren to Siren"
      do { self.name = "Siren"; }
    }
  }
}
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Draft">
  <component name="siren"/>
</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 {
  attr boolean external;
}
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
Warning [Capitalised]: siren should start with a capital letter

A fix is executable code, so its author must ensure that it really repairs the problem. This one is intentionally specific to the siren exercise; it would be a poor general fix for every lowercase component. Do not assume that merely declaring a fix changes the model during validation.

5

Run and inspect EVL

For each example, first read the Flexmi model to predict which elements should fail. Then run it in the Playground and switch to the validated model diagram. Compare its notes with the captured text under the lesson example. The two surfaces present the same kind of result differently: the local runner prints Error [RuleName] and Warning [RuleName]; the Playground attaches findings to affected model elements.

If a model-loading warning appears in the captured output, investigate it before interpreting EVL results. A reference that never resolved may make an EVL expression fail for the wrong reason. This module’s validation examples have resolved references; the deliberate failures are domain-rule violations, apart from the name-only draft model, which has no connectors.

6

Design a validation set

Write rules around decisions the project must make. For this architecture, a practical first set might require distinct components at connector ends, compatible signal types, and an incoming connection for every internal input. Give each rule a stable name and an actionable message. Mark advisory rules as critiques. Use guards to define applicability and dependencies when one check needs another to pass.

A passing EVL run is evidence for the rules you wrote, not proof of every possible property. A rule set can be incomplete. Review example models that should pass and models that should fail, including boundary cases such as external inputs and empty names. Module 6 will generate artefacts from models; validation should run before generation when the output depends on these assumptions.

7

Exercises

Exercise 1★★★conceptual3 min

Constraint or critique? Classify a self-loop connector and an input intended to be external but lacking an internal connector. Explain your severity choices.

Show solution

A self-loop violates the intended wiring rule and is a constraint error. An input without an internal connector may be external, so a warning or guarded critique is more suitable until the model states whether it is external.

Exercise 2★★★conceptual3 min

Guard or check? In guard: not self.external and check: Connector.all.exists(c | c.target = self), which expression decides applicability, and which decides success?

Show solution

The guard decides whether to evaluate the rule for this input. If the guard passes, the check decides whether the input satisfies the rule.

Exercise 3★★★conceptual3 min

Dependency. Why should the length rule depend on HasName? What two findings does the dependency example report?

Show solution

A missing name has no useful length to test. The dependency reports that AB is shorter than three characters and that another component has no name, without a misleading length result for the unnamed component.

Exercise 4★★★coding4 min

Ignore external inputs. Start with the Connected critique from the bad-wiring example. Add a guard so external inputs are skipped, and use the correctly wired alarm. What does EVL report?

Show solution

The guarded check applies only to internal inputs, which are connected in this model:

SolutionGuard the connected-input critiqueOpen in Playground ↗
rules.evl
context Connector {
  constraint NoSelfLoop {
    check: self.source.eContainer() <> self.target.eContainer()
    message: "A connector joins " + self.source.eContainer().name + " to itself"
  }
}
context InPort {
  critique Connected {
    guard: not self.external
    check: Connector.all.exists(c | c.target = self)
    message: self.eContainer().name + "." + self.name + " is not connected"
  }
}
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" external="true" type="DIGITAL"/>
    <inPort name="window" external="true" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" external="true" 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 {
  attr boolean external;
}
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 5★★★coding4 min

Repair a signal type. In the mismatch example, change Siren.sound to DIGITAL and rerun CompatibleTypes.

Show solution

Both ends of each connector now have the same signal type, so the constraint passes:

SolutionRepair the signal typeOpen in Playground ↗
rules.evl
context Connector {
  constraint CompatibleTypes {
    check: self.source.type = self.target.type
    message: self.source.eContainer().name + "." + self.source.name +
      " and " + self.target.eContainer().name + "." + self.target.name +
      " have different signal types"
  }
}
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" external="true" type="DIGITAL"/>
    <inPort name="window" external="true" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" external="true" 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 {
  attr boolean external;
}
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★★★coding4 min

Inspect a fix. In the naming example, predict the name after selecting the fix in an Eclipse workflow. Then edit the Flexmi model to that name and rerun the rule. Does merely declaring the fix repair the original model?

Show solution

The fix’s do block assigns Siren. Declaring it does not run it during validation; the reference model below applies the change explicitly and passes:

SolutionApply the naming repairOpen in Playground ↗
rules.evl
context Component {
  critique Capitalised {
    check: self.name.matches("[A-Z].*")
    message: self.name + " should start with a capital letter"
    fix {
      title: "Rename siren to Siren"
      do { self.name = "Siren"; }
    }
  }
}
More files: alarm.flexmi, components.emf
alarm.flexmi
<?nsuri components?>
<architecture name="Draft">
  <component name="Siren"/>
</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 {
  attr boolean external;
}
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.
8

Self-check quiz

1
What does context Connector select?
2
A failed EVL constraint is reported as what?
3
What is a critique for?
4
What does a guard do?
5
Why use self.satisfies("HasName") before a name-length check?
6
Does declaring an EVL fix automatically change the model during the local build?
7
Where are EVL findings shown in the Playground for these examples?
9

Further reading

  • The EVL reference covers contexts, guards, dependencies, critiques and fixes.
  • The EOL reference explains the expressions used inside EVL rules.