ATL模型转换技术详解

转载请注明出处:http://blog.csdn.net/tyhj_sf/article/details/51965026

因涉及ATL内容较多,本文将持续更新,如果觉得有价值建议关注。

引言

ATL是ATLAS Transformation Language的简称,它是ATLAS研究组开发出来的一种符合OMG的一个QVT提案的模型转换语言。目前ATL已经实现为ADT的一个组成部分,ADT是一个Eclipse插件,他是著名的Eclipse项目GMT的子项目。ATL是基于EMF(Eclipse模型框架)的,其元模型、模型都是用EMF来描述的。从本质上来说,ATL属于基于规则的模型转换语言,其中使用了OCL约束描述语言。
本文根据ATL的官方资料及个人使用经验理解并整理,详细讲解ATL语言的语法、特性及若干使用建议,为学习和使用ATL模型转换技术提供一定的参考。

ATL的数据类型

ATL中定义的数据类型继承关系全图:
这里写图片描述
在详细介绍上图数据类型之前,必须注意每一个OCL表达,包括与每一个数据类型相关的操作及某个类型的实例的上下文。
注意:
(1)self关键词用于引用上下文的实例。
(2)ATL数据类型与OCL的非常相近,但是继承关系与OCL类型关系差别很大,不可相互套用。

OclType operations

OclType类相当于由OCL指定的类型实例的定义,它与特定的OCL操作allInstances()相关。
allInstances() :无参数,返回self的所有实例的集合。
allInstancesFrom(model : String) :用于获取给定元模型包含的self的所有实例的集合。

OclAny operations

这部分描述了所有数据类型的一系列通用操作。
语法结构:
self.operation_name(parameters)

被ATL支持的OCL定义的操作:
比较操作:=, <>;
oclIsUndefined() :判定 self 是否是未定义的,返回布尔值;
oclIsKindOf(t : oclType) :判断self 是否是t类型或t的子类型的数据类型,返回布尔值。
oclIsTypeOf(t : oclType) :判断调用者是否是 t类型的实例,返回布尔值,用法类似Java中的instanceof。
注意:OCL定义的oclIsNew() 和 oclAsType()是不被ATL引擎支持的。
但是,ATL实现了大量的额外操作:
toString() :返回self的字符串表示,某些类型可能返回与此类型不相关的字符串值。
oclType() :返回self的数据类型。
asSequence(), asSet(), asBag() 返回含有self的sequence、set 、bag 。
output(s : String) 向eclipse控制台输出字符,无返回值,仅能用在ATL imperative blocks中。
debug(s : String)返回self的值,并向eclipse控制台输出格式为“s : self_value”的字符串。
refSetValue(name : String, val : oclAny)设置可以用name_value对识别的self的特征,并返回self本身。
refGetValue(name : String) 通过name获得self含有的name_value对的value,并返回value。
refImmediateComposite() 返回self的直接组合,比如self的直接容器。
refInvokeOperation(opName : String, args : Sequence)通过反射方式调用self的名叫opName的操作,并传递给它包含在args中的若干参数。

ATL的 Module数据类型

Module表示当前ATL引擎运行的ATL单元,开发者可以通过变量thisModule获取这个Module类型的单实例。变量thisModule可用于访问ATL module的上下文中声明的helpers和attributes。
Module数据类型提供了操作:resolveTemp(var, target_pattern_name),可以用于指向由源模型通过ATL匹配规则生成的目标模型的任何一个元素。其中,var变量代表源模型元素,target_pattern_name变量代表目标模式元素的名字,即target_pattern_name表示var到目标模型元素的映射关系。

注意:
resolveTemp(var, target_pattern_name)必须在matching phase完成之后才可以调用。
resolveTemp(var, target_pattern_name)操作可以从以下地方调用:

  1. 匹配规则的target pattern 和 do 部分;
  2. 调用的规则的target pattern 和 do
    部分,这里的调用规则指matching phase完成之后的被执行的调用规则。 一个源模型元素不能被超过一个匹配规则匹配。

resolveTemp(var, target_pattern_name)的使用的例子:
第一个转换规则:

rule AtoAnnotedB {
    from
        a : MMA!A
    to
        ann : MMB!Annotation (),
        b : MMB!B (
            annotation <- ann
        )
}

第二个转换规则:
我们假设了ARef有一个引用名为ref指向A元素,BRef有一个引用名为ref指向B元素。

rule ARefToBRef {
    from
        aRef : MMA!ARef
    to
        bRef : MMB!BRef (
            ref <- thisModule.resolveTemp(aRef.ref, 'b')
        )
}

基本数据类型

OCL定义了四个基本数据类型:
Boolean :true、false;
对应的逻辑操作:and, or, xor, not;
implies(b : Boolean) :如果self是true且b是false,则返回false,其他情况返回true;
注意:表达式(exp1 and exp2),exp1即使为false,exp2仍然会进行判断。
Integer (例如1、-5、34、25688)和Real (例如1.5、 3.145)
两者都可执行的操作:
操作符:<, >, >=, <=
二元操作符:*, +, -, /, max(), min()
一元操作符:abs()
toString()操作:返回self的字符串值。
注意:一元操作符 –并没有实现,所以例如 -2 要表示成 0-2 形式。
integer可以执行的操作:div(), mod();
real可以执行的操作:
floor(), round(),cos(), sin(), tan(), acos(), asin(),
toDegrees(), toRadians(),
exp(), log(), sqrt()。

String:例如 ’to be or not to be’;
由OCL定义的String相关操作:
size(),
concat(s : String) ,
substring(lower : Integer, upper : Integer),
toInteger() 和 toReal().
由ATL实现的String相关的操作(用法与Java中String方法的相同):
comparison operators: <, >, >=, <=
字符合并操作符:+
toUpper(), toLower(),
toSequence(),
startsWith(s : String), endsWith(s : String),
indexOf(s : String), lastIndexOf(s : String),
split(regex : String),
replaceAll(c1 : String, c2 : String),
regexReplaceAll(regex : String, replacement : String),
对于ATL程序中的查询和调试很有用的两个方法:
writeTo(fileName : String),
println(),

Collection data types

该类型包括:
Set, 无序集合,元素不能重复;
OrderedSet, 有序集合,元素不能重复;
Bag ,无序,元素可重复;
Sequence,有序,元素可重复。

ATL Helpers

Helper类似于Java和C/C++语言中的函数,它可以在ATL转换程序中的不同地方被调用。
定义为:
helper [context context_type]? def : helper_name(parameters) : return_type = exp;
其中,
context表示哪种元素类型可以调用这个helper,为可选项。缺省时,表示helper关联到全局context;
def关键词表示helper定义的开始;
Helper_name表示helper的名字;
Parameters表示参数集,参数声明方式为 parameter_name : parameter_type,
多个参数声明之间用逗号隔开;
return_type 返回值类型;
exp 表达式;
下面是一个helper定义的例子:

helper def : averageLowerThan(s : Sequence(Integer), value : Real) : Boolean = let avg : Real = s->sum()/s->size() in avg < value;

此外,super可以用来调用父类的同名方法,类似于Java中的super用法。

Attributes

atrtibute在特定上下文中相当于常量,定义:
helper [context context_type]? def : attribute_name : return_type = exp;
attribute与helper的唯一区别是,attribute不能带任何参数。
下面是attribute的一个例子:

helper def : getYoungest : MMPerson!Person =
    let allPersons : Sequence(MMPerson!Person) =
        MMPerson!Person.allInstances()->asSequence() in
    allPersons->iterate(p; y : MMPerson!Person = allPersons->first() |
        if p.age < y.age
        then
            p
        else
            y
        endif
    );

Attribute和helper的局限性:
局限一:目前的ATL版本中,Attribute和helper只能通过他们的name和context两个属性来区分不同的attribute和helper,parameter不能用于区分不同的helper。
局限二:ATL引擎不支持在collection类型的context中定义helper。
例如下面这个定义是不行的:
helper context Set(MMPerson!Person) def : getYoungPersons(age : Integer) :
Set(MMPerson!Person) =
self->select(p | p.age < age);

应该定义成这样:
helper def : getYoungPersons(s : Set(MMPerson!Person), age : Integer) :
Set(MMPerson!Person) =
s->select(p | p.age < age);

局限三:ATL库中不能定义attribute。替代方案是,在自定义库中用无参数的helper的定义代替attribute的定义。

ATL Rules

ATL定义了两种转换规则:
matched rule:匹配源模型的模型元素,生成目标模型的模型元素。
called rule:可以调用ATL代码块(ATL imperative block)生成目标模型元素。

ATL imperative code

赋值语句
语法:

target <- exp;

例如:

thisModule.counter <- thisModule.counter + 1;
aPerson.father.age <- aPerson.age + 25;
aPerson.father <- anotherPerson;

if语句
语法:

if(condition) {
    statements1
}
[else {
    statements2
}]?

例子1:

if(aPerson.gender = #male) {
    thisModule.menNb <- thisModule.menNb + 1;
    thisModule.men->including(aPerson);
}

例子2:

if(aPerson.gender = #male) {
    thisModule.fullName <- 'Mr. ' + aPerson.name + ' ' + aPerson.surname;
}
else {
    if(aPerson.isSingle) {
        thisModule.fullName <- 'Miss ' + aPerson.name;
        thisModule.surname <- aPerson.surname;
    }
    else {
        thisModule.fullName <- 'Mrs. ' + aPerson.name;
        thisModule.surname <- aPerson.marriedTo.surname;
    }
    thisModule.fullName <- thisModule.fullName + ' ' + thisModule.surname;
}

for语句
语法:

for(iterator in collection) {
    statements
}

例子:

for(p in MMPerson!Person.allInstances()) {
    if(p.gender = #male)
        thisModule.men->including(aPerson);
    else
        thisModule.women->including(aPerson);
}

注意:在if和for语句块中时不能定义变量的。
在if和for语句块中可以使用的变量包括:
(1) if和for所在的matched rule中的源模型和目标模型元素;
(2) if和for所在的called rule中的目标模型元素;
(3) matched rule和called rule中定义的局部变量;
(4) ATL模块上下文中定义的属性。

Matched Rules

语法:

rule rule_name {
    from
        in_var : in_type [in model_name]? [(
            condition
        )]?
    [using {
        var1 : var_type1 = init_exp1;
        ...
        varn : var_typen = init_expn;
    }]?
    to
        out_var1 : out_type1 [in model_name]? (
            bindings1
        ),
        out_var2 : distinct out_type2 foreach(e in collection)(
            bindings2
        ),
        ...
        out_varn : out_typen [in model_name]? (
            bindingsn
        )
    [do {
        statements
    }]?
}

注意:
(1) rule_name是rule的识别符,在ATL转换中必须是唯一的;
(2) 在rule中,from和to语句块必须有,using和do语句块为可选项。

下面对matched rule各部分分别讲解

from语句

作用:声明输入的源模型元素,及其初始化。
例子1:

from
    p : MMPerson!Person (
        p.name = 'Smith'
    )

注意下面的例子2到例子3是等价的。
例子2:

from
    p : MMPerson!Person (
        true
    )

例子3:

from
    p : MMPerson!Person

注意,若干个输入模型来自同一个元模型时,可以采用如下方式:
一个转换的header:

create ... from IN1 : MMPerson, IN2 : MMPerson;

只使用IN2时,可以这样:

from 
       p : MMPerson!Person in IN2

局部变量定义在using语句块中,如下例子:

from
    c : GeometricElement!Circle
using {
    pi : Real = 3.14;
    area : Real = pi * c.radius.square();
}
to语句

作用:定义目标模型元素,及其与源模型元素的关系。
注意,生成的目标模型元素必须初始化,形式如下:

feature_name <- exp

其中,feature_name为元素名,exp为表达式。
下面是Biblio元模型上下文中定义的ATL rule例子:

rule Journal2Book {
    from
        j : Biblio!Journal
    to
        b : Biblio!Book (
            title <- j.title + '_(' + j.vol + '):' + j.num,
            authors <- j.articles
                    ->collect(e | e.authors)->flatten()->asSet()
            chapters <- j.articles,
            pagesNb <- j.articles->collect(e | e.pagesNb)->sum()
        )
}

Journal模型转换成了Book模型,Book模型含有title、authors、chapters、pagesNb元素,注意它们的赋值。

关于元素初始化,有三种情况需要考虑的情况:
(1)引用了当前rule生成的目标模型元素

例子:

rule Case1 {
    from
        i : MM_A!ClassA
    to
        o_1 : MM_B!Class1 (
            linkToClass2 <- o_2
        ),
        o_2 : MM_B!Class2 (
            ...
        )
}

(2)引用了另外的rule中的默认模型元素

例子:

rule Case2_R1 {
    from
        i : MM_A!ClassA
    to
        o_1 : MM_B!Class1 (
            linkToClass2 <- i.linkToClassB
        )
}
rule Case2_R2 {
    from
        i : MM_A!ClassB
    to
        o_1 : MM_B!Class2 (
            ...
        ),
        ...
}

(3)引用了另外的rule中的非默认模型元素

例子:

rule Case3_R1 {
    from
        i : MM_A!ClassA
    to
        o_1 : MM_B!Class1 (
            linkToClass2 <- thisModule.resolveTemp(i.linkToClassB, 'o_n')
        )
}
rule Case3_R2 {
    from
        in : MM_A!ClassB
    to
        o_1 : MM_B!Class3 (
            ...
        ),
        ...
        o_n : MM_B!Class2 (
            ...
        ),
        ...
}

注意,当若干个输出模型来自同一个目标元模型时,可以使用in关键字来区分是哪一个输出模型。例如:
一个转换的header:

create OUT1 : MM_B, OUT2 : MM_B from ... ;

如果在模型OUT2中创建元素,则使用in像这样:

to
    o : MM_B!Class2 in OUT2
do语句

作用:在目标元模型生成之后(即执行do 语句后),可以执行一段ATL语句,这段语句可以用于初始化一些模型元素或者修改一些模型元素的特征。
例子:

helper def : id : Integer = 0;
...
rule Journal2Book {
    from
        j : Biblio!Journal
    to
        b : Biblio!Book (
            ...
        )
    do {
        thisModule.id <- thisModule.id + 1;
        b.id <- thisModule.id;
    }
}

在上述例子中,先是在ATL模型的上下文中定义了id变量,并初始化为0。do语句块中以递增方式生成了每一模型元素的统一id。

Lazy Rules

给出一个lazy rule的例子:

lazy rule getCross {
   from
     i: ecore!EObject
   to 
     rel: metamodel!Relationship (
     )
 }

我们可以从matched rule中调用它:

rule Example {
   from 
     s : ecore!EObject
   to 
     t : metamodel!Node (
       name <- s.toString(),
       edges <- thisModule.getCross(s)
     )
 }

也可以多次调用lazy rule:

rule Example {
   from 
     s : ecore!EObject
   to 
     t : metamodel!Node (
       name <- s.toString(),
       edges <- ecore!EClass.allInstancesFrom('yourmodel')->collect(e | thisModule.getCross(e))
     )
  }

Called rules

语法:

[entrypoint | endpoint]? rule rule_name(parameters){
    [using {
        var1 : var_type1 = init_exp1;
        ...
        varn : var_typen = init_expn;
    }]?
    [to
        out_var1 : out_type1 (
            bindings1
        ),
        out_var2 : distinct out_type2 foreach(e in collection)(
            bindings2
        ),
        ...
        out_varn : out_typen (
            bindingsn
        )]?
    [do {
        statements
    }]?
}

注意:
(1) called rule 通过rule_name来识别,在一个ATL转换中是唯一的并且不能与helper的名字冲突;
(2) entrypoint called rule在模块初始化之后,转换开始之前的地方首先被调用执行。相当于Java程序中的main函数;endpoint called rule在转换结束之后的地方被调用执行。
(3) called rule可以像helper一样拥有参数。
(4) using中声明和初始化的局部变量在using、to、do语句块中都是可用的。
(5) using、to、do语句的用法与在matched rule中的用法一致。

使用范例:

helper def: metamodel : KM3!Metamodel = OclUndefined;
...
entrypoint rule Metamodel() {
    to
        t : KM3!Metamodel
    do {
        thisModule.metamodel <- t;
    }
}

在上面的例子中,在helper的变量声明和初始化完成后,Metamodel作为entrypoint rule,是第一个被执行的rule。

Rule的继承

继承需要两个关键词:abstract 和extends。父规则前用abstract,子规则用extends继承父规则。
例子:

abstract rule A {
   from [fromA]
   using [usingA]
   to [toA]
   do [doA]
 }
 rule B extends A {
   from [fromB]
   using [usingB]
   to [toB]
   do [doB]
 }
 rule C extends B {
   from [fromC]
   using [usingC]
   to [toC]
   do [doC]
 }

ATL将上述例子编译后,是等效为这样的:

rule B {
   from [fromB]
   using [usingB]
   to [toA.bindings union toB.bindings]
   do [doB]
 }
 rule C {
   from [fromC]
   using [usingC]
   to [toA.bindings union toB.bindings union toC.bindings]
   do [doC]
 }

注意仔细分析下面的实例:

module Copy;
 create OUT : MM from IN : MM;

 rule CopyDataType extends CopyClassifier {
   from
     s : MM!DataType
   to
     t : MM!DataType
 }

 rule CopyEnumeration extends CopyClassifier {
   from
     s : MM!Enumeration
   to
     t : MM!Enumeration (
       literals <- s.literals
     )
 }

 rule CopyParameter extends CopyTypedElement {
   from
     s : MM!Parameter
   to
     t : MM!Parameter
 }

 rule CopyReference extends CopyStructuralFeature {
   from
     s : MM!Reference
   to
     t : MM!Reference (
       isContainer <- s.isContainer,
       opposite <- s.opposite
     )
 }

 rule CopyTypedElement extends CopyModelElement {
   from
     s : MM!TypedElement
   to
     t : MM!TypedElement (
       lower <- s.lower,
       upper <- s.upper,
       isOrdered <- s.isOrdered,
       isUnique <- s.isUnique,
       type <- s.type
     )
 }

 rule CopyOperation extends CopyTypedElement {
   from
     s : MM!Operation
   to
     t : MM!Operation (
       parameters <- s.parameters
     )
 }

 rule CopyAttribute extends CopyStructuralFeature {
   from
     s : MM!Attribute
   to
     t : MM!Attribute
 }

 rule CopyEnumLiteral extends CopyModelElement {
   from
     s : MM!EnumLiteral
   to
     t : MM!EnumLiteral
 }

 rule CopyPackage extends CopyModelElement {
   from
     s : MM!Package
   to
     t : MM!Package (
       contents <- s.contents
     )
 }

 rule CopyClass extends CopyClassifier {
   from
     s : MM!Class
   to
     t : MM!Class (
       isAbstract <- s.isAbstract,
       supertypes <- s.supertypes,
       structuralFeatures <- s.structuralFeatures,
       operations <- s.operations
     )
 }

 rule CopyClassifier extends CopyModelElement {
   from
     s : MM!Classifier
   to
     t : MM!Classifier
 }

 abstract rule CopyModelElement extends CopyLocatedElement {
   from
     s : MM!ModelElement
   to
     t : MM!ModelElement (
       name <- s.name
     )
 }

 rule CopyMetamodel extends CopyLocatedElement {
   from
     s : MM!Metamodel
   to
     t : MM!Metamodel (
       contents <- s.contents
     )
 }

 abstract rule CopyLocatedElement {
   from
     s : MM!LocatedElement
   to
     t : MM!LocatedElement (
       location <- s.location
     )
 }

 rule CopyStructuralFeature extends CopyTypedElement {
   from
     s : MM!StructuralFeature
   to
     t : MM!StructuralFeature (
       subsetOf <- s.subsetOf,
       derivedFrom <- s.derivedFrom
     )
 }

由上面的例子可以发现一些规律:
(1) rule不支持多继承,但是一个abstract rule可以被多个rule继承;
(2) 被继承的父rule必须带abstract,一个abstract rule可以继承另一个abstract rule;

三种rule的可以被使用的次数:
(1) Matched rules 每次匹配只能使用一次;
(2) Lazy rules 在每一次match时,可以被另外的rules引用多次;
(3) Unique lazy rules只能被另外的rules引用一次。

编写ATL转换规则的若干建议:

(1) 在保证转换结构尽量简单的情况下,优先使用standard rules,其次考虑使用unique lazy rules,必要时使用lazy rules;
(2) 除非必要,否则尽量只使用resolveTemp;
(3) 优先使用iterators (比如select, collect)。
(4) Action blocks, Lazy rules,多输入元素,迭代输出模式等这些ATL标准模式的高级特征不再被官方支持了,因此尽量不要使用。

ATL语言中的所有关键词

(1) 常量关键词:true, false;
(2) 类型关键词:Bag, Set, OrderedSet, Sequence, Tuple, Integer, Real, Boolean, String, TupleType, Map;
(3) 语言关键词:not, and, or, xor, implies, module, create, from, uses, helper, def, context, rule, using, derived, to, mapsTo, distinct, foreach, in, do, if, then, else, endif, let, library, query, for, div, refining, entrypoint;
注意,有时候,模型元素名可能恰好是ATL关键词,此时用双引号可以将关键词变成普通字符串。如下例子:

rule AtoB {
       from
               a : MM!A
       to
               b : MM!B (
                  "from" <- a.source        
               )
}

ATL编程若干问题与建议

(1) 在ATL中,输入模型元素至多被匹配一次,目前这个限制还不能在编译时间检查。输入模型元素多次匹配问题出现在继承关系中,如下例子:
这里写图片描述
ruleB规则继承ruleA规则,当通过ruleB匹配模型元素时,就会发生多次匹配问题。这个问题可以通过如下方法解决:
rule ruleA {
from
a : MM!A (
a.oclIsTypeOf(MM!A)
)

函数oclIsTypeOf()在这里是用来测试a 是否是输入参数MM!A的实例。

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值