Chapter 2. Reference

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).

2.1. Comments

[Warning]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.

2.1.1. Simple and multi-line text comments

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. */

2.1.2. Linked comments

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
}

2.2. Escaping reserved keywords

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
}

2.3. Operators

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

and

Boolean

True if op1 and op2 are evaluated to true

4

or

Boolean

True if one of the operators is evaluated to true

4

not

Boolean

True if op is false.

[Note]Note

The == and != can be applied to any Object, in this case, the comparison is based on the equals operation defined for the object and thus can be redefined to compare only the values of the objects. To be sure to compare the identity of objects even if they redefine the operation equals, you need to compare their oid (ie. their identifiers).

See Section 2.16, “Objects comparison” for more details about object comparison.

2.4. Statements : block, condition, loop

2.4.1. Block statement

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

2.4.2. Conditional statement

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

2.4.3. Loop Statement

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]Note

Unlike Java, there is no exit, break or continue function in Kermeta.

See Section 2.14, “Collections” for functions offering iterator-like scanning.

2.5. File dependency : structuring code

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.

2.5.1. Require

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]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]Note

You can create cyclic dependencies of files, the environment will deal with that.

2.5.2. Accepted require statements

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]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.

2.5.3. Organizing code

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...

2.6. Using Variables

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 ...

2.7. Basic types

2.7.1. Primitive types

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

2.7.2. Enumeration types

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]Note

Enumeration is a concept of the same level as Class, they must both be defined in a package.

2.7.3. Local datatype using "alias"

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]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.

2.8. Classes and methods

2.8.1. Classes

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.

2.8.2. Defining operations

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.

2.8.2.1. Result

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]Note

This is different of the return in java, since it doesn't end the block. Other instructions can be used after the result assignment.

2.8.2.2. Operations as main entry point.

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.

2.8.3. Initializing classes

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")
        

2.8.4. Rationale

Here are some explanation about some design choice of Kermeta classes.

2.8.4.1. No constructor

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.

2.8.4.2. No operation overloading

In order to simplify multiple inheritance management, Kermeta allows only one operation or property with the same name in a given class.

2.9. Inheritance

As kermeta is Object Oriented, it supports inheritance. Like its basis EMOF, it support both, simple and multiple inheritance.

2.9.1. Using simple 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”

A simple family tree model

Figure 2.1. A simple family tree model

2.9.2. Cast

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

2.9.2.1. Casting using contionnal assignment

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
        	

2.9.2.2. Casting using asType

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))
        	

2.9.2.3. Other useful function when casting

In complement you can use, the isVoid operation (available on every object) to test the result of a conditionnal cast. Or you may use the isKindOf operation to test if the passed object is of the correct type before the assignment/asType.

2.9.3. Using multiple inheritance

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.

: multiple inheritance

Figure 2.2. : multiple inheritance

2.9.4. Overriding behavior with methods

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]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}

2.9.5. Some limitations

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.

2.9.5.1. Operation redefinition

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]Note

Notice in that sample that method redefinition uses the method keyword instead of operation.

2.9.5.2. Operation specialization

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.

2.9.5.3. Operation overloading

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
}

2.9.5.4. Conflicts related to multiple inheritance

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

2.10. Genericity

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.

2.10.1. Generic classes

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
}

2.10.2. Generic operations

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]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".

2.10.3. Type usable with genericity

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”

2.11. Exception handling

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]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.

2.12. Loading and saving models

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 :

cs.ecore sample metamodel

Figure 2.3. cs.ecore sample metamodel

[Note]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/

2.12.1. Prepare a model

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)

2.12.2. Load a model from an EMF Resource

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.

2.12.3. Save a model into an EMF Resource

TODO : give an example.

2.13. Packages

2.13.1. Definition

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.

2.13.2. Use of packages

TODO : example of use inside code (without the "using" primitive)

2.13.3. Syntaxic sugars

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]Note

The using statement is different from the definition of a local datatype with an alias. (See Local datatype using "alias" subsection )

2.14. Collections

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.

2.14.1. Definition and initialization

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

: The kermeta collections

Figure 2.4. : The kermeta collections

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]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.

2.14.2. Some existing useful functions

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.

2.15. Class properties

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

2.15.1. Attributes (attribute), references (reference)

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]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 {}

2.15.2. properties modifiers

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        
}        

2.15.3. Association using opposite properties

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]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.

Attributes and references

Figure 2.5. 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]Note

For every case where the upper bound of a property is upper to 1, the type of the property is OrderedSet . The reader will refer to Section 2.14, “Collections” (except the bag type) to have the available types for a [m..n](n>1) multiplicity property.

2.15.4. Derived properties (property)

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]Note

To understand to role of the value keyword, you can imagine that in this sample the setter syntax is a shortcut syntax of : setter( value : Real) is do ... (even if actually this syntax isn't supported).

[Warning]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.

2.15.5. How to access and control the properties in Kermeta

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]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 clear and addAll operations.

2.15.6. Assignment behavior for attribute (and reference)

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