走出浏览器
Playground 让前八个模块无需安装即可运行。真实项目通常还需要纳入版本管理的文件、可重复的构建、本地数据、磁盘上的生成文件,以及与其他工具的集成。离开浏览器时,仍保留元模型、模型和程序这三项输入。先导出一个能运行的示例,再改变执行环境,并将本地结果与本页记录的结果比较。
本模块的下载包包含示例文件。Playground 自带的 Download 按钮还可把当前示例打包成 Eclipse Ant、Maven、Gradle 或 Java 项目,并附带 README;参见 Playground 下载指南。如果在本教程的本地仓库中工作,Maven 运行器源码展示了针对同一组文件运行 EOL、EVL、EGL、EGX 和 ETL 的 Java 集成。
设置 Eclipse Epsilon
Epsilon 下载页介绍了两种方式:在 Eclipse Installer 中选择 Epsilon,或在现有 Eclipse 中打开 Help → Install New Software,使用稳定版更新站点 https://download.eclipse.org/epsilon/updates/2.8/。当前稳定的 Epsilon 2.8 系列与本教程记录的运行版本一致。新建项目,解压下载包,查看 components.emf、alarm.flexmi 和 inspect.eol。配置 EOL 运行时,将模型命名为 M,以 Flexmi 文件为模型输入、components.emf 为元模型。如果程序通过名称或限定类型访问模型,模型名称尤为重要。
先在 Playground 中运行查询,再在本地运行。两处都应列出警报模型及其三个组件。如果本地加载失败,先检查元模型命名空间、文件路径及连线引用的完整路径,再改动 EOL 程序。
("Architecture: " + Architecture.all.first().name).println();
for (c in Component.all) {
(c.name + ": " + c.ports.size() + " ports").println();
}
其他文件: 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
示例文件刻意保持简短。学习 Eclipse 运行配置时,可以把输出当作已知参照。Epsilon 入门指南和 Playground 导出项目中的 README 提供适合所用安装版本的界面步骤。
用 Ant 与 Maven 运行
Ant 工作流指定要加载的模型及要运行的程序。Epsilon 提供 EOL 等语言的 Ant 任务。典型的 EMF 任务加载 Ecore 元模型和模型,然后 EOL 任务引用该模型:
<epsilon.emf.loadModel name="M" modelFile="alarm.xmi" metamodelFile="components.ecore"/>
<epsilon.eol src="inspect.eol"><model ref="M"/></epsilon.eol>
这段代码假定已把 Emfatic 和 Flexmi 输入导出为 Ecore 和 XMI。若继续使用教程的 .emf 与 .flexmi 文件,请从 Playground 的 Ant (Eclipse) 下载项目开始;它包含必要配置。也可参照完整的 Epsilon 命令行 Ant 示例。只有上面两行任务片段,既不会安装 Ant 任务,也不会转换文件格式。
对于 Maven,在 Playground 的下载菜单选择 Maven,进入解压后的项目执行 mvn clean package。导出的 pom.xml 提供依赖并调用 Epsilon 任务。在真实项目中应固定 Epsilon 版本;下载页列出了 2.8.0 版 Maven 坐标。把验证放在生成之前,避免无效模型悄悄产出交付文件。下面的 EVL 关卡检查连线的信号类型:
context Connector {
constraint Compatible {
check: self.source.type = self.target.type
message: "Incompatible signal types"
}
}
其他文件: 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.
本地运行时,除了退出状态,还要查看验证结果。对于验证发现的问题,构建必须有明确策略:报告问题、使构建失败,或允许有记录的例外。
从 Java 运行
在 Playground 的下载菜单选择 Java (Maven),可得到小型 Java 项目和当前示例的 Epsilon API 配置。Java 中的步骤是:创建如 EolModule 的模块、解析程序、通过 EMC 驱动加载模型、把模型加入模块仓库、执行模块,最后释放资源。教程运行器的 Run.java提供了更完整的实现。它的 pom.xml 固定 Epsilon 2.8.0;网站构建用它捕获每个示例的输出。
下面的 EGL 模板可用于检查集成结果:Java 可捕获其文本,构建也可将其写入文件。应将数值与模型比较,不要仅凭模板完成运行就假定产物正确。
Architecture: [%=Architecture.all.first().name%]
Components: [%=Component.all.size()%]
Connectors: [%=Connector.all.size()%]
其他文件: 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
要生成多个文件,可以让 EGX 对每个匹配元素调用一次 EGL 模板。本地运行时需要选择可写的输出目录;Playground 则在输出面板展示生成文件。
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%]
其他文件: 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
其他模型格式与工具
Epsilon 的 EMC 层让各语言连接不同的模型技术。EMF 是其中一种驱动,并不是模型的唯一形式。EMC 指南列出 CSV、XML、JSON、YAML 等驱动。例如,CSV 驱动指南在 EOL 中使用 Row.all,并在 Ant 中使用 epsilon.csv.loadModel。CSV 行也是可查询的元素,但不遵循这些 Playground 示例所用的组件元模型。移植规则之前,先用小样本确认驱动的类型名、特征名和持久化行为。
项目需求变化后,其他 Epsilon 工具也会派上用场:ECL比较模型,EML合并模型,Flock迁移旧模型,EUnit测试 Epsilon 程序,Picto在 Eclipse 中展示模型视图。这些是后续学习的起点,不是完成结课项目的前提。
规划真实项目
确定哪个模型是权威来源、如何验证、哪些产物会重新生成,以及哪些文件纳入版本管理。在持续集成中保留一个小型端到端示例。如果转换结果还要交给生成器,也应验证目标模型。下面的 ETL 示例重用组件到图的映射:本地工作流可先对源模型运行 EVL,再运行 ETL,检查图模型,最后生成图报告。
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;
}
其他文件: 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
记录工具版本、输入文件和生成产物。决定如何处理人工修改;第 6 模块介绍了受保护区域,但真实项目应先在可丢弃副本上测试重新生成。一个实用的本地里程碑是:通过一条命令或一次 Eclipse 启动,得到与本页相同的组件数量、验证结果和报告文本。
练习
选择下载方式。 Eclipse Ant 工作流应选择哪个 Playground 下载选项?由 Maven 构建、通过 API 调用的 Java 程序又该选哪个?
查看解答
Eclipse 工作流选择 Ant (Eclipse),带有 Maven 依赖的 Java API 项目选择 Java (Maven)。阅读各自导出项目的 README 并检查路径。
安排工作流。 按合理顺序排列模型加载、验证、转换和生成。目标模型应在何处检查?
查看解答
加载源模型,验证源模型,执行转换,检查或验证目标模型,最后生成文本。工作流结束后释放模型。如果源模型验证失败,可在转换前停止。
选择驱动。 团队收到 CSV 库存和 XML 配置。Epsilon 哪一层让 EOL 访问它们?重用组件规则前应检查什么?
查看解答
EMC 提供 CSV 和 XML 模型驱动。检查驱动中的元素及特征名称,以及数据的加载和保存方式;针对 Component 写的规则不会自动适用于 CSV 的 Row 或 XML 节点。
检查导出文件。 编写 EOL 查询,将每个输出端口打印成 组件.端口,以便比较本地运行与 Playground 的结果。
查看解答
查询选择具体的输出端口,并打印其所属组件名称:
for (c in Component.all) {
for (p in c.ports.select(p | p.isTypeOf(OutPort))) {
(c.name + "." + p.name).println();
}
}
其他文件: 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
加入本地关卡。 在生成之前,要求组件名称各不相同。在警报模型上测试约束。
查看解答
警报模型中各组件名称不同,因此这次 EVL 运行通过:
context Component {
constraint UniqueName {
check: Component.all.select(c | c.name = self.name).size() = 1
message: "Duplicate component name: " + self.name
}
}
其他文件: 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.
生成交接清单。 生成简短的文本清单,包含模型名称、组件名称和运行验证的提醒。
查看解答
EGL 模板从模型读取名称,并打印验证步骤:
Model: [%=Architecture.all.first().name%]
[% for (c in Component.all) { %]Component: [%=c.name%]
[% } %]Validation: run rules.evl before deployment
其他文件: 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
自测
model ref="M" 指向什么?延伸阅读
- Epsilon 下载与 Eclipse 安装;Playground 导出项目。
- Ant 与 Maven 执行以及 EMC 指南。
- Dimitris Kolovos 的 Introduction to MDE, EMF and Epsilon 公共讲座提供延伸视频。