The reference sections are ordered in four parts: the imperative syntax (1-6), the object-oriented (7-10) and the model-oriented (11-15) features, the advanced concepts (16-21).
![]() | Warning |
|---|---|
Comments are a little bit particular in the Kermeta language because they are not all ignored by the interpreter. There are two kinds of comments: the first (syntax : // foo , /* bar * /) is only associated to the text editor view in which the user edits his Kermeta file, and the second one(syntax : /** foo */ or @tag_name "tag value") is translated into a MOF tag linked to structures described in the Kermeta model. The first one, is a text decoration, the second one is part of the Kermeta model. |
Like in many languages, Kermeta provides two ways to add comments in your models:
simple line comments, i.e text line beginning with //
// This is a single line comment
multi-line comments, i.e text between /* */. Be careful with this notation : the opening syntax must not have juxtaposed stars ("/**"), otherwise it will be considered as a linked comment (see Section 2.1.2, “Linked comments”), i.e a comment that is part of the Kermeta program as a model.
/* This a multi line comment all these lines are comments this line too. */
Kermeta provides a way to define named and unnamed annotations,
that have to be defined just above any model element among
Package, ClassDefinition,
Property, Operation. Such
annotations correspond to MOF tags, and are linked to the elements
which immediately follows.
To define a named annotation, you have to use a special symbol "@", whereas an anonymous annotation has to be written between /** and */
Example 1: you can define an annotation to describe the role of a property
@usage "count the number of ..."
reference myCounter : Integer Example 2: you can document your classes and operation using /** ... */
/** * This is a documentation tag for the class "myClass" */ class MyClass { /** This is a documentation tag for myOperation */ operation myOperation() is do // Unlinked comment end @desc "This is a named annotation for thisOperation" operation thisOperation() is do /* This is an unlinked comment */ end }
Kermeta textual syntax uses several keywords like
class, attribute,
reference, result, etc. (Please
see Appendix A, Language keywords at the end of this
document to get the complete list of Kermeta keywords.)
This doesn't mean you cannot use these words for your own model. Moreover, this is only a textual syntax limitation.
So Kermeta syntax allows you to use the word you want, you simply have to prefix it with a tilda ~.
This example is valid even if we use "class" and "attribute" which are keywords in the language:
class ~class { attribute ~attribute : kermeta::standard::String attribute ~class : kermeta::standard::String }
Priority | Operator | Operand types | Semantic |
|---|---|---|---|
1 | | Numeric String | Add two numeric values Concatenate two strings |
1 | | Numeric | Subtract two numerous values |
2 | | Numeric | Multiply two numeric values |
2 | | Numeric | Divide the first operand by the second |
Notice that most of these arithmetic operators are only defined for numeric primitive types like Integer. Except the + operator which is a concatenation operator for String, they are not applicable on String, Boolean nor collections
Priority | Operators | Operand Types | Semantics |
|---|---|---|---|
3 | | All | True, if op1 value's is the same that op2 value's |
3 | | All | True if op1 value's is different of op2 value's |
3 |
| Numeric | True if op1value's is strictly smaller than op2 value's |
3 |
| Numeric | True if op1 value's is smaller or equals than op2 value's |
3 |
| Numeric | True if op1 value's is strictly greater than op2 value's |
3 |
| Numeric | True if op1 value's is greater or equals than op2 value's |
Priority | Operators | Operands Types | Semantics |
|---|---|---|---|
4 | | Boolean | True if op1 and op2 are evaluated to true |
4 | | Boolean | True if one of the operators is evaluated to true |
4 | | Boolean | True if op is false. |
![]() | Note |
|---|---|
The See Section 2.16, “Objects comparison” for more details about object comparison. |
Kermeta provides a block notion to manage scope of variable.
Instruction of the block have to be inserted between
do and end keywords. Theses two
keywords may be omitted for the conditional and for the loop
structures.
A variable could only be accessed in the block where it was defined and in its sub blocks:
do var v1 : Integer init 3 var v2 : Integer init 2 do var v3 : Integer v3 := v1 + v2
var v2 : Integer // error : v2 is already declared in the upper block end var v4 : Integer init v3 // error : v3 is unknown here end
Kermeta's conditional statement is composed of at least two
elements : a boolean expression and a block that is executed if the
boolean is evaluated to true. You can add a third
element, with the else keyword, that is executed if
the boolean expression is evaluated to
false.
Example 1: if..then..else block
var v1 : Integer init 2 var v2 : String init "blah" if v1 > 5 then v1 := v1-5 if v1 == 2 then v2 := v1 v1 := v2 + v1 else v1 := 0 end
The if statement is an expression (see Chapter 3, Kermeta Metamodel). As any expression in
Kermeta, it can return a value. The return type of the
if statement must be a super type of the values
"returned" by both then and else
blocks (otherwise the type checker will send an error). The values
considered as the result of the evaluation (the "returned" values) of
the if statement are the last evaluated statement
inside then or else block
Example 2: conditional is an expression
var s : String s := if false then "a" else "b" end
Example 3: a more complex conditional
var x : String var y : Integer init 5 x := if y < 3 then stdio.writeln("hello") "a" else "b" "c" end // The String "c" will be the value of x
Here is a sample of a typical loop in Kermeta.
var v1 : Integer init 3 var v2 : Integer init 6 from var i : Integer init 0 until i == 10 loop i := i + 1 end
![]() | Note |
|---|---|
Unlike Java, there is no exit, break or continue function in Kermeta. |
See Section 2.14, “Collections” for functions offering iterator-like scanning.
Kermeta code can be quite large, and invloves many classes in
different metamodels. The main mecanism used to organise your code is
the require statement.
When you need to refer explicitly another entity defined in
another file, you have to use the require
primitive. It allows loading definitions of an other Kermeta file when
file is processed. In the following example, we define a class C which
inherits from the A class previously defined.
// file : MyPackage-part1.kmt package subPackage1; class A { // ... }
// file : MyPackage-part2.kmt package MyPackage; require "MyPackage-part1.kmt" class C inherits subPackage1::A { // ... }
![]() | Note |
|---|---|
In most case, the order of the declaration in not important. The only exception is a very rare situation related to aspects (see Section 2.20, “Weaving kermeta code” ). |
![]() | Note |
|---|---|
You can create cyclic dependencies of files, the environment will deal with that. |
You can require different kind of
entity
Obvioulsy, you can require Kermeta textual
syntax, ie. *.kmt files
You can require Kermeta models, ie. *.km files.
You can require Ecore models, ie. *.ecore files. These models can then be used
as if they were written in Kermeta. In order to add behavior to the
classes defined in .ecore files you may : use the weaving to dynamically weave
behavior to the ecore, or roundtrip using the button action in the
workbench (ecore->kmt->ecore) to statically add the behavior (as
EAnnotations) into the ecore file.
A variant of requiring an ecore file is to require registered EPackage. When Eclipse deploys an
ecore model plugin, it also registers the EPackage using a unique
identifier (nsuri) in order to retreive it quickly. In Kermeta you can
also use this nsuri into the require statement. This approach is
useful becauseyou can be sure that you require the very same model as
eclispe use to load and save models of that sort. For example, instead
of requiring ecore.ecore you may use require
"http://www.eclipse.org/emf/2002/Ecore". This also works for
all other registered metamodels (Your own registered metamodel, UML
metamodel, etc ...). Kermeta user
interface provides a view that display all registered
EPackages and their nsuri.
![]() | Warning |
|---|---|
A special attention must be put when requiring resources. It is not allowed to get several versions of the same class that comes from several required files. Kermeta cannot know which version it must use, so you'll get an error. A typical error, is to require both an ecore file and the registered version of it. They represent two distinct resources in memory. A more clever error, is when the two versions are hidden inside an erroneous ecore file which uses several ways to access some metaclass and actually is not consistent... (ex: you find references to both http://www.eclipse.org/emf/2002/Ecore and platform:/plugin/org.eclipse.emf.ecore/model/Ecore.ecore in the ecore file or one of the dependent files) |
In order to use the seamless java import (still prototype in v0.4.2), you can require a jar file. It will automatically convert the java into a Kermeta representation in order to be able to call java code as if it was written in Kermeta. see Section 2.22, “Using existing java code in Kermeta” for more details.
Kermeta lets you organise your code the way you prefer. There is no predefined organisation of your code, but the language and the workbench proposes various mecanisms and tools to structure it according to your needs and the style you wish for your code. Typically if you use kermeta internal weaver (see Section 2.20, “Weaving kermeta code”), manually transform your ecore into Kermeta, eventually weaving the ecore using the merge button available on the worbench.
For example, you can put all your classes into a single file
(like in Ecore, every classes is in the .ecore file) or you may create
a file for each of them and then use the require to
indicates that they must know each other You can also use an
organisation like java, with a file per class and a directory per
package...
Whenever you need a data locally to a block because it doesn't goes into a class attribute or reference, you can use a local variable.
A variable is defined by a name and by a type. If needed, the name of the variable can be escaped using the tilda (~)
Declaring a variable:
var foo : String // This is a variable declaration
In the following example, we define 3 variables of type integer. The first is initialized with the "14" literal value, the second is initialized with an expression using v1. For the last variable, we use a multiplicity notation to specify an ordered set of integer (see Section 2.14, “Collections” and Section 2.15, “Class properties” for more information on sets).
do var v1 : Integer init 14 var v2 : Integer init 145 * v1 var tab : Integer[0..*] init kermeta::standard::OrderedSet<Integer>.new v1 := v2/v1 end
Be careful to the multiplicity, when you create a variable with
multiplicity, you have to initialize it with a collection. Then to use its
content, you need to use the collection operation like add, addAll,
remove, select, etc. If you use the assignment := it
will replace your collection ...
Kermeta implements a few primitive types. By primitive types, we mean types that are basic to any language, i.e integer, and that have a "literal" value. See below table.
Name | Description | Literal Example |
|---|---|---|
Integer | Represents integer numeric value like 10 or 12. (« int » data type in Java) | 101, 12, 14, -45, -123 |
String | Represents a string of like « helloworld » (« String » data type in Java) | "helloworld", "this is a string !!!" |
Boolean | Represents a true/false value. (« boolean » data type in Java) | true, false |
// Simple datatypes examples var myVar1 : Integer init 10 var myVar2 : Integer var myVar4 : String init "a new string value" var myVar5 : boolean
Kermeta also supports some other primitive types like Float and Character, but they currently don't have a surface syntax for their literals. The way to get them is to use the convertion methods available on String (for both of them) or Integer (for Float).
For example:
var c : Character init "f".elementAt(0) var r : Real init "4.5".toReal
You can define enumerations using the following syntax. Note that each enumeration literal must end with a ";".
enumeration Size { small; normal; big; huge; } You can manipulate enumerated variables and literals with classical operators such as in the following example.
var mySize : Size if ( mySize == Size.small ) then stdio.writeln("This is small !") end
![]() | Note |
|---|---|
Enumeration is a concept of the same level as Class, they must both be defined in a package. |
In Kermeta, you can define your own datatype based on existing types without a hard dependency like inheritance.
This is done using the alias syntax.
Ex:
alias MyString : kermeta::standard::String;In this sample, MyString has all the behavior of kermeta::standard::String but is declared locally. This means that you don't have any dependency to the framework, even to the String in the framework.
Obviously you can reuse existing type names :
alias String : kermeta::standard::String;This will create a new type String in your package with the behavior of String in the framework.
The definition of an alias is different from the use of "using" statement (as defined in Section 2.13.3, “Syntaxic sugars”), when you write
using kermeta::standard
you simply defined a syntactical shortcut allowing you to access any definition in this package from within this file.
Wheras defining an alias allows you to access this new definition from another package if needed.
![]() | Tip |
|---|---|
It is interesting to redefine your own datatype for all the standard type you use in your metamodel, so when you convert the file into ecore in order to have serialisation, you won't have any dependency to framework.ecore (which is the ecore version of the framework where kermeta standard type) This allow a lazy coupling of the type definitions. |
As introduced in the "Hello world" example (see Section 1.2.1, “First Program”), Kermeta is an object-oriented language. Kermeta provides all MOF concepts like properties, attributes, package. In addition, it provides a body to operations and derived properties.
Classes and abstract classes can be defined in a Java-like way. Class definition must be placed into brackets as it is in the following example.
// an empty simple class class myFirstClass { } // a simple abstract class abstract class MyAbstractClass { }
Additionally, for better code robustness, and more user-friendly programming, classes can use the genericity mechanisms (see Section 2.10, “Genericity” for more information).
// This is a parametric class class A<G> { } // This is the type variable binding : G is binded with Integer var a : A<Integer> a := A<Integer>.new
There are some limitations in regards to Java. For example, you cannot define nested classes in Kermeta. Kermeta offers the same structural concepts than MOF language.
Kermeta provides a way to add operational (action) semantics to your metamodels. For that, you can define operations with their body, as in a classical programming language. You can also specify abstract operations (they have no body). Kermeta requires that every class that contains an abstract operation must be declared as an abstract class.
In the following example, we define some operations, based on a visitor pattern implementation
class Inventory { operation visitLibrary(l : Library) is do writeln("Inventory : "); l.books.each(b : Book | b.accept(self)) writeln("----") end operation visitBook(b : Book) is do stdio.write("Book : ", b.title, " by ") b.authors.each(a : Author | a.accept(self)) end operation visitAuthor(a : Author) is do stdio.write(a.name, " ", a.lastName) end } class Library { // ... operation accept(visitor : Inventory) is do visitor.visitLibrary(self) end } class Book { // ... operation accept(visitor : Inventory) is do visitor.visitBook(self) end } class Author { // ... operation accept(visitor : Inventory) is do visitor.visitAuthor(self) end }
In this small example we define an Inventory
class which can go over the library structure and print books
informations. For that, we apply the visitor GoF pattern's on the
library structure defining an accept method in
every library structures.
The special variable result is used to
store the value that will be returned by the operation.
operation getName() : String is do result := self.name end
![]() | Note |
|---|---|
This is different of the
|
When you run a Kermeta program you'll start from an operation. The main operation that you want to run can have any number of parameters, whose types must be only and only String. For operations that are not intended to be run, you can put any type of parameters.
Technically, if you use kermeta inside Eclipse, when you will ask it to run your operation, the interpreter will implicitely instanciate the class containing this operation, and then will call this operation.
Example: 3 different kinds of "runnable" operations
class A{ operation main0() is do // do something end operation main1( arg1 : String) is do // do something with 1rst argument end operation main3( arg1 : String, arg2 : String) is do // do something with 1st and 2nd arguments end } // If you ask to launch main0, kermeta interpereter will create an instance of A and will run main0 on it.
Kermeta doesn't use constructors.
However in some situation, you may need to provide some initialization operation. The best approach is simply to declare an operation which will return self after having done the initialization work.
class A
{
attribute name : String
operation initialize(name : String) : A is do
self.name := name
end
}
// now you can use it easily in one line
var aA : A init A.new.initialize("hello")
Here are some explanation about some design choice of Kermeta classes.
This is because of the compatibility with EMOF and Ecore. In those metalanguages, the user must always be able to create object (except if they are declared abstract), it cannot rely on a action language (since they don't define one). In addition we want that all Meta tool be able to create the object the same way, so, as Ecore doesn't provide constructor with its editors, then Kermeta doesn't too.
As kermeta is Object Oriented, it supports inheritance. Like its basis EMOF, it support both, simple and multiple inheritance.
abstract class Person { attribute name : string attribute lastName : string reference father : Male#children reference mother : Female#children } class Male inherits Person { reference children : oset Person[0..*]#father reference wife : Female[0..1]#husband } class Female inherits Person { reference children : oset Person[0..*]#mother reference husband : Male[0..1]#wife }
In this example, we define a simple model which represent simples family trees. Here, persons are defined by their name and last name. Each person have a father and mother and respectively might have children. The figure representing this example is Figure 2.1, “A simple family tree model”
As Kermeta is strongly typed, you cannot assign or pass something of the wrong type.
For example :
class A {
}
class SubA inherits A {
}
class AnotherSubA inherits A {
}
// ...
var aA : A init SubA.new
var aSubA : SubA
aSubA := aA // doesn't work because not type safe
In this situation you must use one of the following methods : conditionnal assignment or asType
The conditionnal assignment ?= allows to
assign only if the passed object is of the correct type. If not of
the correct type, the assigned value will simply be
Void.
aSubA ?= aA // works
var aAnotherSubA : AnotherSubA
aAnotherSubA ?= aA // works too, but has been assigned Void and not the value ...
This still propose some control on the types and won't accept all kind of cast, for example, you cannot conditionnaly assign if you don't have a common supertype.
class B {
}
// ...
var aB : B
aB ?= aA // doesn't work because not type safe
You can also use the operation asType to
cast your value. This one use a syntax which is shorter in some
situation, since you don't have to create an intermediate variable
before passing the casted value to an operation. However, the
drawback is that, if it fails to cast, then it will raise an
exception.
class C {
operation needASubA(sa : SubA) : Void is do
// do something
end
}
// ... typical code using ?=
var aC : C init C.new
aC.needASubA(aA.asType(SubA))
class Parent { reference children : oset Child[0..*]#parent } class Child { reference parent : Parent#children
} class Male { } class GrandFather inherits Parent, Male { boolean healthy : Boolean }
The above example defines a class "GrandFather" that inherits at the same time the class "Parent", and the class "Male". Its graphical representation is shown in below figure.
In the following sample, when an operation is declared for the
first time (in the parent class Person), it uses the
operation keyword. Then, whenever you override it,
you will have to use the method keyword.
![]() | Note |
|---|---|
In the sample,Person and adopt are abstract, but this has no influence on the operation-method keywords rule, it would have been the same even if Person was not abstract or if adopt had a behavior in this class. |
abstract class Person { attribute name : String reference father : Male#children reference mother : Female#children operation adopt(child : Person) is abstract}
class Male inherits Person { reference wife : Female#husband reference children : oset Person[0..*]#father method adopt(child : Person) is do children.add(child) if not wife.children.contains(child) then child.father := self wife.adopt(child) end end}
class Female inherits Person { reference husband : Male#wife reference children : oset Person[0..*]#mother method adopt(child : Person) is do children.add(child) if not husband.children.contains(child) then child.mother := self husband.adopt(child) end end}
A MOF class can have operations but MOF does not provide any way to describe the behavior of these operations. Furthermore MOF does not provide any semantics neither for operation call nor for operation inheritance and redefinition. This section investigates how, while weaving actions into MOF, MOF semantics can be extended to support behavior definition and extension mechanisms provided by the action language. This implies answering several questions concerning redefinition and dispatch.
MOF does not specify the notion of overriding an operation because from a structural point of view it does not make any sense. To stick to MOF structure one can argue that redefinition should be forbidden in an executable MOF. This is the simplest solution as it also solves the problem of the dynamic dispatch since a simple static binding policy can be used.
However, operation redefinition is one of the key features of Object-Oriented (OO) languages. The OO paradigm has demonstrated that operation redefinition is a useful and powerful mechanism to define the behavior of objects and allow for variability. This would be very convenient to properly model dynamic semantic variation points existing in e.g. UML state-charts. For this reason we believe that an important feature of an executable MOF is to provide a precise behavior redefinition mechanism. The choice of the operation overriding mechanism must take into account the usual problem of redefinition such as method specialization and conflicting redefinitions related to multiple inheritance.
![]() | class A { operation m1() is do // Some behavior end // operation m2 is abstract operation m2() is abstract } class B inherits A { // method m1 inherits operation m1 from A method m1() is do // Behavior redefinition end method m2() is do // Implementation of the abstract method end } |
Table 2.1. Operation redefinition in Kermeta
![]() | Note |
|---|---|
Notice in that sample that method redefinition uses the
|
The issue of choosing semantics for operation overriding has been widely studied for the design of OO languages ( cf. M. Abadi and L. Cardelli, A theory of objects, Springer). However, OO languages have not adopted a unique solution to this problem. In this context, any language that defines an operation overriding mechanism should define precisely the solution it implements.
The simplest approach to overriding is to require that an overriding method has exactly the same signature as the overridden method. That is that both the type of the parameters and the return type of the operation should be invariant among the implementations of an operation. For the sake of simplicity this is the solution we have chosen for the current version of Kermeta.
However, this condition can be relaxed to allow method specialization, i.e. specialization on the types of parameters or/and return type of the operation. On one hand, the return type of the overriding method can be a sub-type of the return type of the overridden method. Method specialization is said to be covariant for the return types. On the other hand, the types of parameters of the overriding method might be super types of the parameters of the overridden methods. Method specialization is thus contravariant for the parameters.
In practice languages can allow method specialization only on the return type (this is the case of Java 1.5) or both on parameters and return type (this is the case of Eiffel). Among these solutions, we may choose a less restrictive policy then strict invariance for future versions of Kermeta in order to improve the static type checking of Kermeta programs.
Overloading is not allowed in Kermeta. This mechanism allows multiple operations taking different types of parameters to be defined with the same name. For each call, depending on the type of the actual parameters, the compiler or interpreter automatically calls the right one. This provides a convenient way for writing operations whose behaviors differ depending on the static type of the parameters. Overloading is extensively used is some functional languages such as Haskell and has been implemented in OO languages such as Java or C#. However it causes numerous problems in an OO context due to inheritance and even multiple inheritance in our case [REF?]. It is not implemented in some OO languages such as Eiffel for this reason, and that is why we chose to exclude overloading from Kermeta.
class A { method m(i : Integer) is do // [...] end method m(s : String) is do // this is not allowed in Kermeta !!! // [...] end }
This is also a classical problem that has been solved in several OO languages. There are mainly two kinds of conflicts when a class inherits features from several super-classes:
Several features with the same name might be inherited from different super classes causing a name clash.
Several implementations of a single operation could be inherited from different super classes.
There are two kinds of solutions to resolve these conflicts. The first one is to have an implicit resolution mechanism which chooses the method to inherit according to an arbitrary policy. The second one is to include in the language constructions that allow the programmer to explicitly resolve conflicts. In Eiffel, for instance, the programmer can rename features in order to avoid name clashes and can select the method to inherit if several redefinition of an operation are inherited from parent classes.
In the current version of Kermeta, we have chosen to include a minimal selection mechanism that allows the user to explicitly select the inherited method to override if several implementations of an operation are inherited. This mechanism does not allow resolving some name clashes and thus reject some ambiguous programs. For the future version of Kermeta we plan to include a more general mechanism such as traits proposed by Schärli et al. In any case we believe the conflict resolution mechanism should be explicit for the programmer.
![]() | class O { operation m() is abstract } class A inherits O { method m() is do // [...] end } class B inherits O { method m() is do // [...] end } class C inherits A, B { // "from" : an explicit selection of the//implementation to inherit method m() from A is do // [...] end } |
Table 2.2. Explicit selection of super operation in Kermeta
One of the core characteristics of Kermeta is to be statically typed. In order to allow static typing of OCL-like expressions, a few modifications had to be made to the EMOF type system (Please refer to paper Weaving Executability into Object-Oriented Meta-Languages by P.A. Muller et al., presented at the Models05 conference).
As a result to these modifications genericity support has been added into Kermeta. Like Eiffel and Java 5, Kermeta supports generic classes and generic operations. This section gives on overview of these concepts in Kermeta.
In Kermeta classes can have a set of type parameters. These type variables can be used in the implementation of the class as any other type. By default a type variable can take as value any type; but a type variable can also be constrained by a type: in that case, this type variable can only be substituted by a sub-type of this type. The following code demonstrates how to create generic classes.
// A class with a type variable G that can be bound with any type
class Queue<G> { reference elements : oset G[*] operation enqueue(e : G) : Void is do elements.add(e) end operation dequeue() : G is do result := elements.first elements.removeAt(0) end }
// A class with a type variable C that can be bound with any sub-type of Comparable
class SortedQueue<C : Comparable> inherits Queue<C> { method enqueue(e : C) : Void is do var i : Integer from i := 0 until i == elements.size or e > elements.elementAt(i) loop i := i + 1 end elements.addAt(i, e) end }
Kermeta operations can contain type parameters. Like type variables for classes these type parameters can be constrained by a super type. However, unlike for classes, for which the bindings to these type parameters are explicit, for operations the actual type to bind to the variable is statically inferred for each call according to the type of the actual parameters.
class Utils { operation max<T : Comparable>(a : T, b : T) : T is do result := if a > b then a else b end end }
![]() | Note |
|---|---|
Notice in that sample that even the "if" is an expression that can return a value that is assigned here to the special variable "result". |
Actually, all types can be used as a parameter of a generic class or generic operation. More, the ModelType, is really useful when combined with generics. see Section 2.21, “Model type”
Kermeta provides also an exception mechanism. You can define a "rescue" block to manage errors occurring during the execution of another block. Exception mechanism is very close to the Java Exception mechanism.
Example 1: a simple exception raising
do var excep : Exception excep := Exception.new stdio.writeln("Throwing an exception ! ") raise excep end
Any block can then rescue exceptions.
Example 2: rescue block
var v1 : Integer init 2 var v2 : Integer init 3 do var v3 : Integer v3 := v1 + v2 rescue (myConstraintError : kermeta::exceptions::ConstraintViolatedInv) // something with myConstraintError // ... rescue (myError : Exception) // something with myError // ... end
![]() | Tip |
|---|---|
do not hesitate to create "do .. end" block into another block if you want to check for an exception only in a part of the code. This also works if you want to rescue code from within a rescue code. |
This section explains how to load and save (deserialize and serialize) an EMF model in Kermeta. For this purpose, we will use the following small example :
![]() | Note |
|---|---|
Loading and saving model has it own tutorial. It provides more information in a step by step approach. See the EMF tutorial at http://www.kermeta.org/documents/emfTutorial/ |
The user will refer to eclipse documentation for the creation of an EMF model from its ECore meta-model. We suggest to use the Wizard samples to create, at the first hand, an Ecore meta-model, and then, at the second hand, instances of this Ecore meta-model, using the generated EMF reflexive editors.
Once you created the ECore meta-model, please check that you
correctly filled the property "Ns URI" of the root
package of the Ecore meta-model, otherwise the resource load will
fail. This NsURI must equal the real path of your metamodel. (You can
modify this property through the Properties View of your
meta-model)
In the current version of the EMF resource loader, you have to prepare your EMF Resource following these rules :
At the top of the source code where you will access your
model, don't forget to add require "your_metamodel.ecore"
so that you can access your metamodel in Kermeta.
Alternatively, you can use the Kermeta version of your
metamodel using require "your_metamodel.kmt" or
require "your_metamodel.km".
In this case, be careful that your kmt required metamodel is strictly equivalent to the ecore version of your metamodel that is used in createResource method.
Then, create a repository and the resource that will contain
the model instance that you want to load. In the following code
example, uri stands for the uri (as relative or
absolute path[1]) of the model instance, and
mm_uri is the uri of the meta-model of the
model instance.
@mainClass "root::TestCSLoading" @mainOperation "main" package root; require kermeta require "cs.ecore" using kermeta::standard using kermeta::persistence class TestCSLoading { operation initialize(uri : String, mm_uri : String) : Set<Object> is do /* Initialize the EMF repository */ var repository : EMFRepository init EMFRepository.new /* Create an EMF Resource */ var resource : Resource init repository.createResource(uri, mm_uri) /* Load the resource */ resource.load /* Get the loaded __root__ instances (a Set<Object>) */ result := resource // a resource is a collection of objects contained end
Once you loaded your EMF resource, you can get its attribute
instances, that contains all the objects that
you created through your EMF generated reflexive editor. Now you
can "visit" your instances, provided you "visit" them according to
their types. In the simplest way, you can make very basic tests to
display your instances content, as in the following example, which
visit the objects of resource instances which types are
cs::Template and
cs::Decision.
operation main() is do
var instances := self.initialize("./test.cs", "./cs.ecore")
from var it : Iterator<Object> init instances.iterator
until it.isOff
loop
var o : Object o := it.next
if (o == void) then stdio.writeln("Void object!")
else
stdio.writeln("---------------------------------")
stdio.writeln("Objet : " + o.getMetaClass.classDefinition.qualifiedName
+ " ( " + o.getMetaClass.typeDefinition.ownedAttribute.size.toString+ "attr.)" )
end
var template : cs::Template // Print instances which type is cs::Template
if (cs::Template.isInstance(o))
then
template ?= o
stdio.writeln(" name : " + template.name)
stdio.writeln(" decision : " + template.decision.toString)
stdio.writeln(" content : " + template.content) stdio.writeln(" referer : " + template.referer.toString)
end
// Print instances which type is cs::Decision
if (cs::Decision.isInstance(o))
then
decision ?= o
stdio.writeln(" name : " + decision.name)
end
end
}If your resource is dependent of other resources and that EMF succed to load it, the Repository that was used to load your resource will automatically load all these dependent resources.
Kermeta provides package to structure models. So you can define packages and sub-package as deep as you want. There are two main ways to do this, as shown in examples 2 and 3 (below). If you want to define some classes in a package you may define them in a specific file and start this file with a package naming directive (followed by a semi-colon) like in the following example[2].
Example 1: 1 file.kmt == 1 package naming directive
// My file package MyNewPackage; class A { // ... }
class B { // ... }
Here, classes A and B are defined in a package called "MyNewPackage". All classes defined in this file are under the scope of this package.
You can also define explicitly sub-packages using braces (see the following example):
Example 2: Defining subpackages using braces
// file : MyPackage-part1.kmt package MyPackage; package subPackage1 { class A { // ... } } package subPackage2 { class B { // ... } }
In this example, we define a main package called "MyPackage" which contains 2 sub-packages "subPackage1" and "subPackage2". The first one contains the A class and the second one the B class.
If you want, you can declare the same package in several files. You can also directly define subpackages in the package naming directive of your file (see example 1 above). In the following example, we define a new sub-package of "MyPackage" called "subPackage3" directly in the file. All features defined in this file will belong to this sub-package.
Example 3: Defining
subpackage using :: syntactic sugar
// file : subPackage3.kmt package MyPackage::subPackage3; class C { // ... }
If a given file, when you want to use a class definition that is
not in the scope of the current package inside which you are working,
you have to provide the full path of this class definition to be able
to use it. However, you can also define shortcuts to make your code
clearer: the using primitive provides such a
shortcut. Taking the previous example, it becomes:
// file : MyPackage-part2.kmt package MyPackage; require "MyPackage-part1.kmt" using subPackage1 // <- "shortcut"! class C inherits A { // ... }
The using statement is a syntactical shorcut, it has no conterpart in kermeta metamodel.
![]() | Note |
|---|---|
The using statement is different from the definition of a local datatype with an alias. (See Local datatype using "alias" subsection ) |
Collections are widely used in kermeta not only because of their usual usage for collecting data in variables but also because they are used to represent class property when the multiplicity is greater than 1.
Collections is one of the concept where the genericity is the most visible sinc it greatly helps to write more robust code.
Kermeta defines some collection data types handling sets of values. The different available collection types are the result of a combination of the constraints unique and ordered.
Unique specifies a collection type that cannot contain doublet
Ordered specifies a collection type where the position of an object inside the collection can be modified.
Name | Description | Constraints | |
|---|---|---|---|
Unique | Ordered | ||
set | Represents an unordered collection of objects with no doublet (Set) | True | False |
oset | Represents an ordered collection of objects with no doublet (OrderedSet) | True | True |
seq | Represents an ordered collection of objects (Sequence) | False | True |
bag | Represents a basic collection of objects (Bag) | False | False |
Another way to define set of objects would have been to use arrays. In fact, Kermeta does not define explicitly the concept of array, but it provides a multiplicity concept which can be used instead. Multiplicities are a way to define a lower and upper bound on a collection. Syntactically, lower and upper bounds are defined between brackets and are separated by two dots. Bounds can be an integer literal value or a star to specify there's no upper bound.
Example 1: how to declare collections
using kermeta::standard // don't need to specify it all the time // This is the simpliest and recommanded way of declaring a collection variable var myColA : Set<Integer> // this is equivalent to saying set Integer[0..*] var myColB : OrderedSet<Integer> // this is equivalent to saying oset Integer[0..*]
// Collection with multiplicities var myCol1 : set Integer[4..6] // At least 4 elements and never more than 6 var myCol3 : seq String[2..*] // At least two strings in the sequence var myCol4 : set String[1..*] // An non empty set var myCol5 : String[1..*] // If you don't specify any keyword, it is an ordered set
There is currently no way to define a collection by extension
like you can do in C or Java. You must initialize your collection
either by calling new
(Kermeta constructor operation) on your collection type, or
initialize by copy.
Example 2: initialize collections
// Example of declaration of variables as Collections. All those syntaxes are valid var myCol1 : set Integer[0..*] init kermeta::standard::Set<Integer>.new // Fill in myCol1 myCol1.add(10) myCol1.add(50) var myCol2 : oset String[0..*] init kermeta::standard::OrderedSet<String>.new var myCol3 : bag Boolean[0..*] init kermeta::standard::Bag<Boolean>.new var myCol4 : seq Integer[0..*] init kermeta::standard::Sequence<Integer>.new // if no keyword specified, and multiplicity is set, it is an OrderedSet
var myCol4 : String[0..*]init kermeta::standard::OrderedSet<String>.new
var myCol1a : seq Integer[0..*] init myCol1 var myCol2a : oset String[0..*] init myCol2 var myCol3a : kermeta::standard::Bag<Boolean> init myCol3 var myCol3a : kermeta::standard::Sequence<Integer> init myCol4
![]() | Note |
|---|---|
Conclusion : in most cases, you don't need to use this special syntax, you can simply use the generic collection names (Set<Something>, OrderedSet<Something>, etc.) available in Kermeta framework. Moreover, lower and upper bounds aren’t checked yet by Kermeta type checker and interpreter. |
The collections in Kermeta implement several functions based on lambda expressions (see Section 2.17, “Lambda Expressions and functions”). These ones are very useful for model navigation.
Example 1: closure definitions for collection manipulation in Kermeta
aCollection.each { e | /* do something with each element e of this collection */ }
aBoolean := aCollection.forAll { e | /* put here a condition */ } // return true if the condition is true for all elements in the collection.
aCollection2 := aCollection.select { e | /* put here a condition that returns true for elements that must be included in the resulting Collection */ }
aCollection2 := aCollection.reject { e | /* put here a condition that returns true for elements that must be exclude in the resulting Collection */ }
// return a new collection which size is the same as in the original // collection, and which element type is the type of the result of the expression. aCollection2 := aCollection.collect { e | /* put here an expression, for example e.name */ }
anObject := aCollection.detect { e | /* a condition */} // returns an element (usually the first) that fulfill the condition.
aBoolean := aCollection.exists { e | /* a condition */} // returns true if at least one element fulfill the condition.
A property of a (meta)class can be expressed in three ways : as a reference, an attribute, or a derived property. In Kermeta, each kind of these properties has a specific behavior and a dedicated syntax, which is attribute (for attribute), reference (for reference), property (for derived property)
References and attributes can have opposite properties. This last concept is explained in the following subsection.
Unlike UML, there is no concept of visibility in kermeta, so every property is visible
We introduce here the 2 first cases, which are relationships between two concrete entities.
attribute: an attribute defines a composition (e.g the black diamond) between two entities. The diamond-ed association end is navigable by definition
class A { attribute x : set X[0..*] } class X {}
![]() | Note |
|---|---|
Composition notion also relates to containment notion. So, there are some restriction about valid model. For example, in an association, only one end can be an attribute, otherwise this means that we have an association with a diamond on both end and we cannot say who is the container of the other. |
reference: a reference defines an association between two entities.
class A { reference x : set X[0..*] } class X {}
Attributes, references and properties underlying collections may eventually be specialized. By default, they are represented by an OrderedSet. If you wish to be more precise and for exemple allow to have several item with the same value in your collection, you can use the collection modifiers as defined in Section 2.14, “Collections”
For example :
class A {
attribute x1 : seq String[0..*] // allows duplicates, ordered
attribute x2 : set String[0..*] // doesn't allow duplicates, not ordered
attribute x3 : bag String[0..*] // allows duplicates, not ordered
attribute x4 : oset String[0..*] // (default if no modifier) doesn't allow duplicates, ordered
} The opposite [property] of a property defines an association
(bidirectional link) between two entities. An opposite is expressed by
a sharp #. In the following example, a is the
opposite property of the entity of type B, and b is
mutually the opposite property of the entity of type A.
![]() | Caution |
|---|---|
A property whose type is a DataType (i.e String, Boolean, Integer) cannot have an opposite! |
Example 1: definition of an attribute, a reference, and an opposite property
This means that a can be accessed from b. Subsequently, if you modify the property b of an instance of A, it will mutually update its opposite property, i.e the property a of the instance of B contained in property b. You can make a test with the following example of code.
Example 2: navigability of opposite properties
var a1 : A init A.new a1.name := "a1" var b1 : B init C.new a1.b := b1 stdio.writeln("b1 opposite : " + b1.a.name) // This prints "a1"!
The following paragraph shows a set of examples of attributes and references.
Example 3: a set of attributes and references, with multiplicities and opposites.
TODO : add an example of code using those classes !! See https://gforge.inria.fr/plugins/scmcvs/cvsweb.php/integration_projects/other/org.openembedd.formations/Kermeta/Basics/docs/FR_formation_Kermeta_1er_niveau.odp?cvsroot=openembedd for such examples (p. 21).
package root;class A1 { attribute b : B1[0..*] } class B1 {}
class A2 {} class B2 { reference a : A2 }
class A3 { reference b : B3#a } class B3 { reference a : A3#b }
class A4 { reference a : A4[0..*] }
class A5 { attribute ab : A5[0..*]#aa reference aa : A5#ab }
class A6 { attribute b : B6#a } class B6 { reference a : A6[1..1]#b }
class A7 { attribute b : B7[0..*]#a } class B7 { reference a : A7#b }
class A8 { attribute b : B8[1..1]#a } class B8 { reference a : A8#b }
class A9 {} class B9 { reference a : A9[1..1] }
![]() | Note |
|---|---|
For every case where the upper bound of a property is upper to 1, the type of the property is
|
In a class definition, a derived property is a property that is derived or calculated, i.e it contains a body, like operations. Usually, such properties are calculated from other properties available from its owning class definition. In practice, you can define the code that you want inside a derived property.
In other words it does not reference to a concrete entity: it is calculated, through the accessor operations getter and setter.
The special parameter value is used in the setter
to get the value passed when calling the setter.
Let's take the following class definitions :
// readonly property : it has no setter class A { attribute period : Real property readonly frequency : Real // property : keyword for derived property getter is do result := 1/period end } // modifiable property : class B { attribute period : Real property frequency : Real getter is do result result := 1/period end setter is do period := 1/value end } // a typical use would be (with aB an instance of class B) var freq : Real fred := aB.frequency // to use the getter aB.frequency := freq + 1 // to use the setter, the period is also updated in the process
![]() | Note |
|---|---|
To understand to role of the |
![]() | Warning |
|---|---|
Since derived properties aims to behave like attributes or references, if the multiplicity is greater than 1, it doesn't make sense to define a setter. This is because it means reassigning the internal collection which is in fact calculated. |
Properties are accessed or modified as classical properties. See next subsection for examples.
Example 1: let's take the example with A6 and B6 :
class A6 { attribute b : B6[0..*]#a } class B6 { reference a : A6#b }
Access | Kermeta expression |
|---|---|
Get the attribute of an instance | var a6 : A6 init A6.new var b6 : OrderedSet<B6> // get the b attribute // Note that as the attribute as a multiplicity >1 it is an OrderedSet b6 := a6.b |
Add an element to a property with multiplicity [m..n], n>1 | var a6 : A6 init A6.new var b6 : B6 init B6.new // add b6 to the attribute b of A. // Note : you don’t have to initialize b! done through A6.new a6.b.add(b6) |
Remove an element from a property | // OrderedSet owns a method that removes an element given its // index in the set. For unordered sets, use "remove" method a6.b.removeAt(0) // Also valid : a6.b.remove(b6) |
Get the opposite of a property | var a6 : A6 init A6.new var b6 : B6 init B6.new a6.b.add(b6) // this assertion is true. Moreover, any instance in a6.b will // have a6 as their opposite since b is a collection assert(b6.a == a6) |
Get the container of a property | var a6 : A6 init A6.new var b6 : B6 init B6.new // add ab6 to the attribute "b" a6.b.add(b6) var a6c : A6 init b6.container() // this assertion is true assert(a6c.equals(a6)) |
It is not different with references that have a [m..1] (m=0 or m=1) multiplicity:
Example 2: the A5 B5 case
class A5 { attribute b : B5#a // no multiplicity means [0..1] } class B5 { reference a : A5[1..1]#b }
Access | Kermeta expression |
|---|---|
Get the attribute of an instance | var a5 : A5 init A5.new var b5 : B5 // get the b attribute b5 := a5.b |
Set a property (with multiplicity [m..1], m≤1) | var a5 : A5 init A5.new var b5 : B5 init B5.new // set b5 to the attribute b of A. a5.b := b5 |
Unset a property | a5.b := void |
Get the opposite of a property | var a5 : A5 init A5.new var b5 : B5 init B5.new a5.b := b5 // this assertion is true. assert(b5.a == a5) |
Get the container of a property | var a5 : A5 init A5.new var b5 : B5 init B5.new // add b5 to the attribute "b" a5.b := b5 var a5c : A5 init b5.container() // this assertion is true assert(a5c.equals(a5)) |
![]() | Note |
|---|---|
Be careful with attributes or references ref with multiplicity greater than 1, they are automatically initialized with a reflective collection (ie. a collection that is aware of an eventual opposite). So, you cannot assign them using := If you wish to change completely its content with the one of another collection, you must use the
|
Attribute and reference have one main behavior difference.
Attribute has a notion of containment that reference hasn't.
This has some implication on the behavior of the assignment because an attribute cannot be owned by more than one object at a time.
There is an exception for attributes which type is a primitive
type (String, Integer, Boolean, Real, Char) : since those types inherit from ValueType,
they are not concerned by the composition, opposite concepts.
In this case, the assignment doesn't impact any value but the assigned one.
Example 1: Assignment behavior for attribute
class A { attribute c1 : C } class B { attribute c2 : C } class C { } aA.c1 := C.new aB.c2 := aA.c1 // now aA.c1 == void !!!
Example 2: Assignment behavior for reference
class A { reference c1 : C } class B { reference c2 : C } class C { } aA.c1 := C.new aB.c2 := aA.c1 // aB.c2 == aA.c1 and aA.c1 keeps its value !!!
Example 3: Assignment behavior for attribute which type is String
class A { reference c1 : String }class B { reference c2 : String }aA.c1 := "Robert"aB.c2 := aA.c1 // a