Build : 2.1.1-SNAPSHOT 20130925-1232
Abstract
This manual presents the various aspects of the Kermeta language. This is the reference manual for anybody who want to use Kermeta.
Table of Contents
using
keyword syntactic sugar
List of Figures
Kermeta is workbench and an action language dedicated to metamodel engineering.
The action language is a Domain Specific Language (DLS) that fills the gap let by MOF which defines only the structure of meta-models, by adding a way to specify static semantic (similar to OCL) and dynamic semantic (using operational semantic in the operation of the metamodel). Kermeta uses the object-oriented paradigm like Java or Eiffel.
The workbench allows to mashup various languages that, like Kermeta, works at the MOF level. (For example, OCL, QVT, UML profiles, ...). This means that, at design time, a user may decide to use a language adapted for a given task. The workbench will help to combine the various inputs in an efficient runtime.
This document gives beginners an introduction to the Kermeta action language and to the workbench, then it offers a reference of all the aspects of the language, including the textual syntax, the metamodeling features and some more advanced features. Two other chapters present the Kermeta Metamodel and the Kermeta framework .
Important | |
---|---|
Kermeta is an evolving software and despite that we put a lot of attention to this document, it may contain errors (more likely in the code samples). If you find any error or have some information that improves this document, please send it to us using the bug tracker in the forge: http://gforge.inria.fr/tracker/?group_id=32 or using the developer mailing list (kermeta-developers@lists.gforge.inria.fr) |
Tip | |
---|---|
The most update version of this document is available on line from http://www.kermeta.org . |
Kermeta has undergone a major refactorings between its version 1.x and 2.x. They apply both to the internal tooling and also on the language itself. In order to help identify the major changes between these versions, you can look for notes like the following :
New in Kermeta 2 | |
---|---|
Changed in Kermeta 2 | |
---|---|
You'll find the most significant in the following sections :
This chapter aims to help you to have a quick overview of most of the features of Kermeta. Then, it gives the pointers to the detailed sections in the reference chapter. It also gives some small examples that should help you to understand the basis of the concepts without having to jump to the corresponding detailed section.
Text not verified for kermeta 2 | |
---|---|
Kermeta is a metamodeling language which allows describing both the structure and the behavior of models. It has been designed to be compliant with the OMG metamodeling language EMOF (part of the MOF 2.0 specification) and Ecore (from Eclipse). It provides an action language for specifying the behavior of models.
Kermeta is intended to be used as the core language of a model oriented platform. It has been designed to be a common basis to implement Metadata languages, action languages, constraint languages or transformation language.
In a nutshell, Kermeta is :
MOF compliant (EMOF compliant to be precise)
Model oriented
Imperative
Object-Oriented
Aspect-Oriented
Statically Typed (100% typesafe)
In addition to these characteristics, it includes some typically model-oriented concepts like associations, multiplicities or object containment management.
This chapter presents the main features of the Kermeta language. Section 2 presents the general syntax of the language, sections 3 & 4 give details about the object-oriented and model-oriented features of the language and finally section 4 provides information about some extra concepts in Kermeta (including aspect orientation).
With its workbench, it's goal is to provide a support for all Language Driven Engineering activities. It will be typically used to build tools useful to build software. This includes (but is not restricted to): model checkers, simulators, model transformations (any kind of transformations including model weavers or compilers).
Text not verified for kermeta 2 | |
---|---|
Th action language of Kermeta is an imperative language for modeling, with a basic syntax inspired from Eiffel. Code is statically type checked, and execution is made by a compiler in its version 2.x. The previous version of Kermeta (up to 1.x) were executed thanks to an interpreter.
Even if it is not very useful in our context, since it doesn't show the really interresting structures of the language, here is the traditional " Hello world " example you can find in every programming book.
Example 1.1. MainClass.kmt
package helloworld; using kermeta::standard // shorthand for standard types using kermeta::io::StdIO => stdio // shorthand to kermeta::io::StdIO class HelloworldExample { operation sayHello() is do stdio.writeln("Hello world, ...") end }
Example 1.2. hello.kp
KermetaProject "hello" groupId = "my.group" defaultMainClass = "helloworld::HelloworldExample" defaultMainOperation = "sayHello" sources = { require "${project.baseUri}/src/main/kmt/MainClass.kmt" } dependencies = { //default dependency to kermeta framework (try first in eclipse plugin, then look into maven repository) dependency "library.core" ="platform:/plugin/org.kermeta.language.library.core", "mvn:org.kermeta.language/language.library.core/2.0.4" }
Kermeta language includes usual statements like blocks and loops, comments, etc
do // a loop for getting a text from an user var s : kermeta::standard::String from var found : kermeta::standard::Boolean init false until found loop s := stdio.read("Enter a text:\n --> ") if s.size > 0 then found := true else stdio.writeln("ERROR - Empty text!") end end stdio.writeln("\n You entered: " + s) end
All these "classic" imperative features and their syntaxes are described in Chapter 2, Reference . More precisely in
Text not verified for kermeta 2 | |
---|---|
Users of modern programming languages, like Java, would feel easy with object-oriented features in Kermeta: classes, inheritance, exceptions, and even genericity.
// persons who write documents class Writer { attribute name : kermeta::standard::String } // generic concept for every document abstract class Document { reference author : Writer attribute text : kermeta::standard::String } // a "Document" from the real world class Book inherits Document {} // a specialized "Book" class ChildBook inherits Book { attribute minimalAge : kermeta::standard::Integer }
Such classes can be used for verifications:
// a specialized Exception class AgeException inherits kermeta::exceptions::Exception {} abstract class Reader { operation read(book : ChildBook) : Void is abstract } class Child inherits Reader { attribute age : kermeta::standard::Integer operation initialize(age : kermeta::standard::Integer) : Child is do self.age := age result := self // return self so we can chain this call directly after a new end // an action which triggers an Exception operation read(book : ChildBook) : Void is do if age < book.minimalAge then raise AgeException.new end end }
You can get more informations about Kermeta object-oriented features in Chapter 2, Reference . More precisely in
Text not verified for kermeta 2 | |
---|---|
As explained in the Preface and in Architecture , Kermeta extends the MOF. It provides useful means to manipulate models. The support of model introduces the main difference with "more" traditional programming languages.
Association is one of the key concepts when using and defining models. It is obviously part of Kermeta.
MOF defines the concept of "Property" which generalizes the notions of attributes, and associations (composite or not) that you can find in UML. Kermeta syntax also distinguishes these two notions as introduced in Section 2.16, “ Class properties ” .
As a reminder, the
attribute
keyword defines
a link with containment (a composite association)
whereas the
reference
keyword just defines an association. As
you can see, property
declarations are very close to
variable
declarations introduced in
Section 2.6, “
Using
Variables
”
).
Each reference may be explicitly linked to another reference (it
is
the
opposite
concept in MOF terminology – see also
section
Section 2.16.1, “
Attributes
(attribute)
, references
(reference)
”
).
class Library { attribute books : set Book[0..*] } class Book { attribute title : String attribute subtitle : String reference authors : oset Author[1..*]#works } class Author { attribute name : String attribute lastName : String reference works : set Book[0..*]#authors }
If we represent our Kermeta model in a graphical syntax we obtain the following class diagram ( Figure 1.2, “A concrete example : a library” ).
Using Eclipse Modeling Framework (EMF), Kermeta can load and save models done with other EMF tools.
/* Initialize the EMF repository */ var repository : EMFRepository init EMFRepository.new /* Create an EMF Resource, given model and metamodel URIs as String */ var resource : Resource init repository.createResource(myModelURI, itsMetamodelURI) /* Load the resource */ resource.load // get elements from the resource // in this sample, you know that your root element is always a Library, // so you can directly get the first one var aLibrary : Library aLibrary ?= resource.one // note the conditional assignment using the ?=, if not a Library you'll get Void
In the same way, you can serialize a model, or load, change and save an existing model.
Caution | |
---|---|
Your model URI MUST be of the form "platform:/resource/myProject/myModel" or "platform:/plugin/myProject/myModel". Your metamodel URI MUST be of the form "platform:/resource/myProject/myModel" or "platform:/plugin/myProject/myModel" or an URI registered in the EMF registry. |
Caution | |
---|---|
Be aware that you CANNOT load kermeta text files (*.kmt). Only xmi files are allowed to be loaded. Parsing and obtaining a model from a textual syntax is not part of Kermeta. This is the role of other tools (like sintaks). Technically, it is possible to create some Kermeta operation that will hide this step, however, this is not the goal of this manual to explain this procedure. |
Actually, navigating in a model is as simple as using objects in an object-oriented program. However, several features have been added in order to ease this activity.
For example, thanks to the lambda expressions, the collections of the language are easily manipulated using lexical closure (select, collect, each, etc). This applies to all the collections of the language, the one you may define directly but also the one which are used when an Attribute or Reference has a multiplicity greater than 1.
Example (based on the library sample of Section 1.4.1, “Associations : toward a first concrete example of a Kermeta model ” ):
var smithBooks : Set<Book> init Set<Book>.new smithBooks.addAll( lib.books.select{aLibraryBook | aLibraryBook.authors.exists{aBookAuthor | aBookAuthor.lastName == "Smith"}})
In the example above, lib is an instance of Library. It searchs in the books, select the books where the author last name is "Smith".
In order to improve reuse of existing code between metamodel variants, the language introduces the notion of ModelType. It is based on the notion of conformance between two metamodels. This allows to write behavior that is valid for a given metamodel and that will also work for any conformant metamodel.
TODO write a small illustrative example of a simple printer based on a ModelType : a subset of class diagram of UML
Kermeta has been developed, using MDE principles so it also provides its own metamodel (reflectiveley available). Details of Kermeta metamodel is available in Chapter 3, Kermeta Metamodel
You can get more informations about all Kermeta model-oriented features in the Chapter 2, Reference . More precisely in
Since Kermeta is an extension of MOF, a MOF meta-model can conversely be seen as a valid Kermeta program that just declares packages, classes and so on but does nothing. Kermeta can then be used to breath life into this meta-model by incrementally introducing aspects for handling concerns of static semantics, dynamic semantics, or model transformations.
Kermeta allow to keep the concerns seprated at design time and then merge them to build a runable tool. It uses a powerful mecanism of static introduction that works at the model level and at the model element level.
One of the key feature of the workbench is the static composition
operator
require
. This operator allows defining these various
aspects in separate
units and integrating them
automatically into the
meta-model. The
composition
is done statically and the composed model is
typed-checked to ensure
the safe integration of all units.
This
mechanism makes it easy to reuse
existing meta-models or to split
meta-models into reusable pieces.
It allows to compose the
various
files and models that will form
the final tool.
The workbench will seamlessly convert and merge the various files into the final kermeta program. The classic inputs will be ecore files, kmt files or uml profiles files. The finala assembly will depend on the capabilities of the inputs models. For example, a ecore model has a granularity at the class level (ie. it will just add new classes) whereas Kermeta has a granularity at the feature level and thus can add new features to a class(see next section Section 1.5.2, “ Model element static introduction : Aspect-oriented language ” for more details)
TODO add a sample
Text not verified for kermeta 2 | |
---|---|
One of the key features of Kermeta is the static composition
operator
require
allows extending an existing meta-model with new elements
such as
properties, operations,
constraints or classes. This operator
allows defining these various
aspects in separate units and
integrating them automatically into the
meta-model. The composition
is done statically and the
composed model is typed-checked to
ensure
the safe integration of all units.
This mechanism makes
it
easy to reuse existing meta-models or to split
meta-models into
reusable pieces.
It can be
compared to the open class paradigm.
Consequently a
meta-class that identifies a domain concept
can be
extended without editing the meta-model directly. Open classes
in
Kermeta are used to
organize
cross-cutting
concerns separately from the meta-model to which they belong, a key
feature of aspect-oriented
programming. With this mechanism,
Kermeta can support the addition of new
meta-class, new
subclasses,
new methods, new properties, new contracts to
existing meta-model.
The
require
mechanism also provides flexibility. For example,
several
operational semantics could be
defined
in separate units for
a single meta-model and then alternatively
composed depending on
particular
needs. This is the case for instance in the UML
meta-model when
several semantics
variation points
are defined.
Thank to this composition operator, Kermeta can remain a kernel platform to safely integrate all the concerns around a meta-model. This feature has proved to be extremly useful in the context of metamodel engineering.
You'll find more details in the Reference chapter, more precisely in: Section 2.22, “ Weaving Kermeta code ”
Text not verified for kermeta 2 | |
---|---|
Kermeta implements several "less common" or advanced features that helps in many situations.
Typically, lambda expressions/functions is a well known concept, very useful in almost all kermeta code. Even, if you will probably not write your own function, you'll certainly use the existing one defined on collections.
In order to implement and statically type check OCL-like iterators, Kermeta includes some limited functional features by implementing lambda expressions.
This is typically used on
Collection
which
provides operations that uses a function as parameter. For
example :
each
,
select
,
forAll
,
detect
...
Example 1 : the following code will build a collection of names of the operations that start with "test".
var names : Collection<String> names := self.getMetaClass.classDefinition.ownedOperation .select{ op | op.name.indexOf("test") == 0} .collect{ op | op.name }
Example 2 : Providing a time function on Integer
operation times(body : <Integer->Object>) : Void is do from var i : Integer init 0 until i == self loop body(i) i := i + 1 end end
this allows to write code like :
var res : Integer 10.times { i | stdio.writeln(i.toString + ": Hello") } // Say 10 times Hello
See sections " Lambda Expressions and functions " and Lambda Expression for detailed informations.
Kermeta most recent versions embed advanced concepts like Lambda Expressions and functions , Dynamic evaluation of Kermeta expressions , Design by contract (pre, post, inv contraints) or Weaving Kermeta code .
As Kermeta language is implemented upon Eclipse and Java, you can call Java code inside Kermeta code.(see Section 2.24, “Using existing java code in Kermeta ” )
There is some special behavior regarding object comparison or cloning.
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).
Text not verified for kermeta 2 | |
---|---|
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
}
Text not verified for kermeta 2 | |
---|---|
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 }
Text not verified for kermeta 2 | |
---|---|
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.18, “ Objects comparison ” for more details about object comparison. |
Text not verified for kermeta 2 | |
---|---|
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.15, “ Collections ” for functions offering iterator-like scanning.
Text not verified for kermeta 2 | |
---|---|
Kermeta code can be quite large, and invloves many classes in
different metamodels. The main
mechanism 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.22, “ 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.
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.24, “Using existing java code in Kermeta ” for more details.
Kermeta reuse some of the protocols provided by Eclipse.
Relative path. You can use a path relative to the current file using the normal syntax (./ , ../, etc)
Eclipse workspace relative path.
In Eclipse, you can use a path
relative to the user's workspace. In
that case the path looks
like
platform:/resource/
your_project_name
/
path_to_the_file
Eclipse plugin relative path.
In Eclipse, you can use a path that search in
the installed
plugins. In that case the path looks
like
platform:/plugin/
plugin_name
/
path_to_the_file
Eclipse workspace or plugin relative path.
When deploying Kermeta code, it is
sometime interesting to search
in both the user's workspace
and in the
plugins. In that case the
path looks like
platform:/lookup/
plugin_or_project_name
/
path_to_the_file
Note | |
---|---|
This platform:/lookup/ is available in Kermeta only, and only in the require statement. Be careful, using the current version (Kermeta 1.3.1) since a deployed plugin using this protocol may be modified by the user's workspace content! |
Registered Ecore.
A variant of requiring an ecore file is to require a
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 because you can be sure that you
require the very
same
model as
Eclipse 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.
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 mechanisms 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.22, “ 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.
Text not verified for kermeta 2 | |
---|---|
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.15, “ Collections ” and Section 2.16, “ 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.
Text not verified for kermeta 2 | |
---|---|
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.14, “
using
keyword syntactic sugar
”
), 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. |
Text not verified for kermeta 2 | |
---|---|
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 neither does Kermeta.
However, you can easily add an
init
or
myInitialize
operation that will
take as many parameters as you want. Even
better, you can use a
Builder or a
Factory design pattern.
aspect class ClassFromMyEcore { operation init(myParam : String) : ClassFromMyEcore is do // assign myParam to some attribute self.name := myParam // return self for easier use result := self end // now you can create and initialize this class in one line // var foo : ClassFromMyEcore init ClassFromMyEcore.new.init("foo") }
Text not verified for kermeta 2 | |
---|---|
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 : conditional assignment or asType
The conditional 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 conditionaly 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))
In complement you can use the
isVoid
operation (available on every object) to test the result of a
conditional cast. Or you may use
the
isKindOf
or the
isInstanceOf
operations to test if the calling object has the same type of the
given class before
the
assignment/asType. The
isKindOf
operation returns
true
if the calling object
has exactly the same type than the given
class. The
isInstanceOf
operation returns
true
if the calling object has the exact type or one of its super type
than the given class.
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
Text not verified for kermeta 2 | |
---|---|
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.23, “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 : kermeta::exceptions::Exception excep := kermeta::exceptions::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 : kermeta::exceptions::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. |
Text not verified for kermeta 2 | |
---|---|
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 you can tell Kermeta that you will create/load/manipulate
instances of these metaclasses.
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 : Set<Object> init self.initialize("./test.cs", "./cs.ecore") instances.each{ o | if (o == void) then stdio.writeln("Void object!") else stdio.writeln("---------------------------------") stdio.writeln("Objet : " + o.getMetaClass.typeDefinition.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 } }
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.
To save a model, simply add the model elements in a Resource then call the save operation. All model elements contained by these added elements will also be saved in the Resource.
You can split your model in several files by using several resources, but you need to make sure that they all belong to the same Repository. Otherwise, you'll get at best a Dangling exception, or worse create inconsistent files.
Text not verified for kermeta 2 | |
---|---|
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 { // ... }
Changed in Kermeta 2 | |
---|---|
While coding in Kermeta you regularly need to refer to class definitions. For example to indicate the type of a paramzeter or a variable or to initialize an instance of it (ie. call new).
If the definition you want to use isn't defined in the package where you need to use it, the normal way to acces it is to use the full qualified name.
Example 2.1. use of full qualified name
... var s : kermeta::standard::Integer var e : kermeta::exceptions::NotImplementedException init kermeta::exceptions::NotImplementedException.new ...
Like the
import
statement in Java, Kermeta allows to defines shorcuts which allow
you to specify classes from
other packages that can be referenced
without qualifying them with their full qualified name.
The
keyword
used for that is
using
. It supports various syntax option in order to haheve either a fine
or coarse grain resolution.
Example 2.2. shorthand giving access to the full content of a package
using kermeta::standard::* // direct access to all definitions in Package kermeta::standard using kermeta::exceptions::* // direct access to all definitions in Package kermeta::exceptions ... // allows to write : var s : Integer var e : NotImplementedException init NotImplementedException.new
Note | |
---|---|
For support of retro compatibilty with kermeta 1, the 2 following syntaxes have the same effect. However the second one raises a deprecation warning inviting you to use the first syntax. using kermeta::standard::* // direct access to all definition of Package using kermeta::standard // retro compatibility supported, exactly the same as above // (should raise a deprecation warning) |
Example 2.3. shorthand giving access to a single definition with renaming
using kermeta::standard::Integer => MyInteger // renaming using kermeta::exceptions::NotImplementedException => NIException // renaming ... // allows to write : var s : MyInteger var e : NIException init NIException.new
Example 2.4. shorthand giving access to a single definition with direct renaming
using kermeta::standard::{Integer} // same as using kermeta::standard::Integer => Integer using kermeta::exceptions::{NotImplementedException} // same as kermeta::exceptions::NotImplementedException => NotImplementedException ... // allows to write : var s : Integer var e : NotImplementedException init NotImplementedException.new
Example 2.5. Other examples of renaming and shortcuts
using kermeta::standard::Integer => standard::Integer // with renaming for direct access using kermeta::{standard::Integer} // shortcut : same as above using kermeta::standard::Integer => ks::Integer // another renaming using kermeta::exceptions::* => ke::* // global renaming of a package ... // allows to write : var i1 : standard::Integer var i2 : ks::Integer // note that i1 and i2 are actually of the same type kermeta::standard::Integer var e : ke::NotImplementedException init ke::NotImplementedException.new
Note | |
---|---|
The
|
Be careful when declaring your
using
statements if there is a risk of conflicting names. In such
situation,
the type resolver will
first look into the current
package, then try
each
using
in the order they are defined
and choose the first type definition
that exists.
Text not verified for kermeta 2 | |
---|---|
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 since 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.19, “ Lambda Expressions and functions ” ). These ones are very useful for model navigation.
TODO
aCollection.each { e | /* do something with each element e of this collection */ }
TODO
aBoolean := aCollection.forAll { e | /* put here a condition */ } // return true if the condition is true for all elements in the collection.
TODO
aCollection2 := aCollection.select { e |
/* put here a condition that returns true for elements that must be included in the resulting Collection */
}
TODO
aCollection2 := aCollection.reject { e |
/* put here a condition that returns true for elements that must be exclude in the resulting Collection */
}
TODO
// 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 */
}
TODO
anObject := aCollection.detect { e | /* a condition */} // returns an element (usually the first) that fulfills the condition.
Function exists returns true if at least one element fulfills the last boolean expression defined into the block. For instance:
aBoolean := aCollection.exists { e | /* a condition */} // returns true if at least one element fulfills the condition.
If several boolean condition are defined into the block of function exists , the last condition is used for the test. For instance, the following function exists returns true at the first iteration:
aBoolean := aCollection.exists { e | false true }
Instructions can be defined into the block of function exists . For instance, the following code displays the current object at each iteration:
aBoolean := aCollection.exists { e |
stdio.writeln(e.toString)
/* a condition */
}
If no boolean expression is defined, value Void is returned, as illstrated in the following example. Such a use of function exists is not adviced since it corresponds to the use of function each .
aBoolean := aCollection.exists { e | stdio.writeln(e.toString) }/* The value of aBoolean is Void */
Text not verified for kermeta 2 | |
---|---|
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 items with the same value in your collection, you can use the collection modifiers as defined in Section 2.15, “ 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
|
Note | |
---|---|
For the same reason,
|
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 // aB.c2 == aA.c1 == "Robert"
Note | |
---|---|
The assignment into a
|
New in Kermeta 2 | |
---|---|
It is possible to declare a class as a singleton. The singleton
works like
companion object
in scala or is similar to a static class in java.
Which means that
only a single instance of the
class exists in memory.
Access to this instance is done directly via the class name.
As any class, the singleton supports operations, attributes, references...
Example 2.6. StdIO singleton
For example, StdIO is now defined as a singleton like this :
singleton StdIO { /** * Writeln the object to standard output */ operation writeln(object : String) : Void is do // code for printing on console end // other operations in StdIO }
It can be directly used like this :
kermeta::io::StdIO.writeln("my message")
Caution | |
---|---|
Be careful when deploying a code that use singleton in environment that uses multiple classpath. For example in OSGI and eclipse, you can (on purpose or not) create several classpathes that aren't shared and create several instances of the same singleton. |
Text not verified for kermeta 2 | |
---|---|
Kermeta users could be confused about
==
operator and
equals
method. Unlike, Java they do the
same and redefining the equals to
compare the content also
affects the
==
.
To compare the identity of two objects you must compare the identifier of each object : using their oid
The equals method behaves the same way as in java. This means that you can overwrite it, if you want to compare the contents of two objects of the same class.
@mainClass "root::Main" @mainOperation "main" package root; require kermeta using kermeta::standard using kermeta::kunit class A { reference x : Integer method equals(compared : Object) : Boolean is do var castedCompared : A castedCompared ?= compared result := x.equals (castedCompared.x) end } class Main inherits kermeta::kunit::TestCase{ operation main() : Void is do var a1 : A init A.new var a2 : A init A.new a1.x := 10 a2.x := 20 assert( not a1.equals(a2) ) // objects are different with all methods assert( not a1 == a2 ) assert ( a1.oid == a2.oid ) a2.x := 10 assert ( a1.equals(a2) ) // objects becomes equals assert ( a1 == a2 ) // also with == assert ( a1.oid == a2.oid ) // but they physically are still different end }
In Kermeta language, there is a notion of primitive type. Here is the list of primitive types :
Boolean
Integer
String
Character
Real
We do not want two Integer objects with the same value to be different. What we want is to use == operator to compare values of primitive types. These classes simply redefine the equals. Then we can write this code :
var i1 : Integer init 10 var i2 : Integer init 10 assert (i1.equals(i2)) var i3 : Integer init 10 var i4 : Integer init 10 assert (i3 == i4)
The method
equals
exists for the collections. The generic behavior
is the following :
for two collections a and b,
if all elements of a
are contained in b
and vice-versa, then it returns true, false
otherwise.
This behavior
is little bit different for ordered
collection which takes into
account the order of
elements. Have a
look
to the following pieces of
code :
operation test_try1() : Void is do var os : OrderedSet<Integer> init OrderedSet<Integer>.new os.add(1) os.add(2) os.add(3) os.add(4) os.add(5) os.add(6) var os2 : OrderedSet<Integer> init OrderedSet<Integer>.new os2.add(1) os2.add(2) os2.add(3) os2.add(4) os2.add(5) os2.add(6) assert (os.equals(os2) ) end operation test_try2() : Void is do var os : OrderedSet<Integer> init OrderedSet<Integer>.new os.add(1) os.add(2) os.add(3) os.add(4) os.add(5) os.add(6) var os2 : OrderedSet<Integer> init OrderedSet<Integer>.new os2.add(1) os2.add(2) os2.add(3) os2.add(4) os2.add(5) assert ( not os.equals(os2) ) end /** * An ordered set takes care about the order. */ operation test_try3() : Void is do var os : OrderedSet<Integer> init OrderedSet<Integer>.new os.add(1) os.add(2) os.add(3) os.add(4) os.add(5) os.add(6) var os2 : OrderedSet<Integer> init OrderedSet<Integer>.new os2.add(4) os2.add(6) os2.add(3) os2.add(1) os2.add(5) os2.add(2) assert ( not os.equals(os2) ) end /** * A set does not care about the order. */ operation test_try4() : Void is do var os : Set<Integer> init Set<Integer>.new os.add(1) os.add(2) os.add(3) os.add(4) os.add(5) os.add(6) var os2 : Set<Integer> init Set<Integer>.new os2.add(4) os2.add(6) os2.add(3) os2.add(1) os2.add(5) os2.add(2) assert ( os.equals(os2) ) end
Kermeta proposes an implementation of lambda expressions, that is useful for example when implementing OCL-like functions.
Definition of the type of the function:
Kermeta has a special type called "function type" that can be used in several places.
This type can be used in all the location a type is expected, in operation parameter, return type of an operation, variables, attributes, references. The most typical use is in the parameter of an operation.
// function type that takes one parameter as input <TYPE->RETURN_TYPE> // function type that takes several input parameters [TYPE_1, TYPE_2, ...]->RETURN_TYPE> // replace here the ... with as many type you wish // function type that takes one input parameter of type Void can be called without parameter <Void->RETURN_TYPE>
When an operation has only one parameter of type "function", then a special shortcut syntax allows to ommit the perenthesis when calling this operation.
Examples of definition and call of operations that use only one function as parameter:
Operation with a function parameter that uses one input:
operation operationWithOneLambdaParameter ( lambda : <INPUT_TYPE->RETURN_TYPE> ) : Void is do var foo : INPUT_TYPE init INPUT_TYPE.new lambda(foo) // will call the lambda with foo) end [...] // example of call // calling with the full syntax operationWithOneLambdaParameter({ i : INPUT_TYPE | /* do something on i and return something of kind RETURN_TYPE */}) // calling with the shortcut syntax : the type of i and the parenthesis can be ommitted operationWithOneLambdaParameter{ i | /* do something on i and return something of kind RETURN_TYPE */}
Operation with a function parameter that uses several inputs:
// function with several parameters, (the example use 2 parameters but you can use more than 2 ...) operation operationWithSeveralLambdaParameters ( mylambda : <INPUT_TYPE1, INPUT_TYPE2->RETURN_TYPE> ) : Void is do var foo : INPUT_TYPE1 init INPUT_TYPE1.new var bar : INPUT_TYPE2 init INPUT_TYPE2.new mylambda(foo, bar) // will call the lambda end [...] // example of call // calling with the full syntax operationWithSeveralLambdaParameters({ x, y : INPUT_TYPE | /* do something on x and y and return something of kind RETURN_TYPE */}) // calling with the shortcut syntax : the type of i and the parenthesis can be ommitted operationWithSeveralLambdaParameters{ x, y | /* do something on x and y and return something of kind RETURN_TYPE */}
New in Kermeta 2 | |
---|---|
It is now possible to ommit the variable when calling an
operation that uses a single lambda
parameter with
|
Operation with a function parameter that uses a single input of
type
Void
:
// With 0 parameter operation operationWithSeveralLambdaParameters ( lambda : <Void->RETURN_TYPE> ) : Void is do lambda(void) // will call the lambda lambda() // void can be ommitted to call the lambda, however the parenthesis are still mandatory end [...] // example of call // calling with the full syntax operationWithSeveralLambdaParameters({ void : Void | /* do something and return something of kind RETURN_TYPE */}) // calling with the shortcut syntax : the type, the variable and the parenthesis can be ommitted operationWithSeveralLambdaParameters{ | /* do something and return something of kind RETURN_TYPE */}
The operation
indexedEach
and
forAllCpl
on
Collection
are examples of such lambda with several input parameters. (See
section bellow).
Using the full syntax you can create operations that use parameters of mixed type including function type:
operation operationWithMixedParameters ( f : <INPUT_TYPE->RETURN_TYPE>, otherParam : String ) : Void is do var foo : INPUT_TYPE init INPUT_TYPE.new f(foo) // will call the lambda // do something with otherParam end [...] // example of call // only the full syntax is allowed operationWithSeveralLambdaParameters({ x : INPUT_TYPE | /* do something on x and return something of kind RETURN_TYPE */}, "hellow world")
Declaring and defining an "anonymous" function inside a variable for reuse.
var f1 : <TYPE -> RETURN_TYPE> f1 := { var_name : TYPE | /* SOME_CODE_WITH_RETURN_TYPE_RESULT */ } var f2 : <[ TYPE_1, TYPE_2, TYPE_3 ] -> RETURN_TYPE> f2 := { var_name_1 : TYPE_1, var_name_2 : TYPE_2, var_name_3 : TYPE_3 | /* SOME_CODE_WITH_RETURN_TYPE_RESULT */ }
Tip | |
---|---|
You can also use function type in reference or attribute to store them. However, since ecore doesn't know about them, you'll have to define them on the kermeta side and thus cannot be serialized in a model. |
In the following sections, you will find many examples of declarations, definitions, and uses of functions.
The collections in Kermeta implement several functions based on lambda expression. These ones are very useful for model navigation.
Example 1: closure definition for collection iterator-like in Kermeta
aCollection.each { e | /* do something with each element e of this collection */ }
See Section 2.15, “ Collections ” for other existing functions on collections.
Example 2: another useful function that is defined on Integer : the function times
10.times { i | stdio.writeln(i.toString) } // prints 0 to 9
Notice that you can also write some complex code in the function, using internal variables, etc. In such a case, the last statement evaluation will be the returned result of the lambda expression (provided it is declared to return something)
Example 3 : "complex"" code in a lambda expression
aCollection.each { e | stdio.writeln("I am complex code!" stdio.writeln("Element : " + e.toString) var i : Integer init 5 i := i + 132458 }
Example 4 : postponing parameter evaluation in the andThen operation
The operation andThen and orElse of Boolean use the ability of functions to postpone the parameter evaluation. This allows to implement the expected behavior without adding new construct to the language.
// this kind of code allows to avoid a cast exception on the second test if cl.isInstanceOf(NamedElement).andThen{| cl.asType(NamedElement).name == "foo"} then // do something... end
Example 5 : using operation that use a function with several inputs
aCollection.indexedEach { e, eachContext | stdio.write("element "+ eachContext.index.toString + ": " + e.toString) if(!eachContext.isLast) then stdio.writeln(",") end }
aCollectionOfNamedElement.forAllCpl{s1,s2| (s1.name==s2.name).implies(s1==s2)}
You can also define your own functions, by declaring an operation, with a parameter as a function using the syntax described in Section 2.19.1, “ Syntax ” .
Example : definition of functions for collections
abstract class Collection<G> { /** * runs func on each element of the collection */ operation each(func : <G -> Object>) : Void is do from var it : Iterator<G> init iterator until it.isOff loop func(it.next) end end
/** * checks that the condition is true on all the element of the collection * returns true if the collection is empty */ operation forAll(func : <G -> Boolean>) : Boolean is do var test : Boolean init true from var it : Iterator<G> init iterator until it.isOff loop test := test and func(it.next) end result := test end }
You can also define lambda expression as variable. This can be useful if you don't want to ( or can't) modify the class.
A basic lambda expression
With one Integer argument and returning an Integer.
var aLambdaExp : <Integer->Integer> var aLambdaResult : Integer aLambdaExp := { i : Integer | i.plus(4) } // aLambdaResult equals 7 aLambdaResult := aLambdaExp(3)
A lambda expression with several parameters
var aLambdaExp : <[Integer, Integer]->Integer> var aLambdaResult : Integer aLambdaExp := { i : Integer, j : Integer | i * j } // aLambdaResult equals 12 aLambdaResult := aLambdaExp(3, 4)
A lambda expression on a collection
var init_set : Set<Integer> := Set<Integer>.new init_set.add(32) init_set.add(23) init_set.add(41) // This sequence equals : [320, 230, 410] var sequence : Sequence<Integer> := init_set.collect { element | element*10}
The code within the function can be as complex as you want, using internal variables, etc.
var factoExp : <Integer->Integer> factoExp := { n : Integer | var fact : Integer := 1 from var x : Integer := 1 until x > n loop fact := fact * x x:=x+1 end fact // return fact as the result of the function //shorter alternative ;-)... if n<=1 then 1 else factoExp(n -1) * n end } var cnkExp : <[Integer, Integer]->Integer> cnkExp := { n : Integer, k : Integer | factoExp(n) / (factoExp(k) * factoExp(n-k)) }
When starting to use the lambda expression you can define some
useful design patterns. One of
them is the ability to propose an
each
that traverses all the owned element of a given metamodel. For some
usage, this can be an
interresting alternative to the visitor
design pattern.
Let's have the following metamodel:
class NamedElement { attribute name : String } class A inherits NamedElement { attribute ownedAs : A[0..*] } class B inherits A { attribute ownedBs : B[0..*] } class C inherits A { attribute ownedBs : B[0..*] attribute ownedC : C }
It would be nice to be able to call a simple lambda on all the elements directly owned by an element. This can be done by adding an operation using the following pattern:
aspect class NamedElement { operation eachOwnedElement(func : <NamedElement -> Void>) : Void is do // String attributes are value, so they aren't really "owned" end } aspect class A inherits NamedElement { operation eachOwnedElement(func : <NamedElement -> Void>) : Void is do super[NamedElement](func) self.ownedAs.each{ e | func(e) } end } aspect class B inherits A { operation eachOwnedElement(func : <NamedElement -> Void>) : Void is do super[A](func) // makes sure to call func on attributes inherited from A self.ownedBs.each{ e | func(e) } end } aspect class C inherits A { operation eachOwnedElement(func : <NamedElement -> Void>) : Void is do super[A](func) // makes sure to call func on attributes inherited from A self.ownedBs.each{ e | func(e) } if(not ownedC.isVoid) then func(ownedC) end end }
Then a call like the following will ensure to traverse all the directly owned elements.
var c : C := // initialize it with a complex model :-) // write the name of all the elements directly contained by c c.eachOwnedElement{ aNamedElement | stdio.writeln(aNamedElement.name) } // collect all the elements directly contained by c var ownedElements : Sequence<Object> := Sequence<Object>.new c.eachOwnedElement{ aNamedElement | ownedElements.add(aNamedElement) } stdio.writeln("ownedElements.size = "+ ownedElements.size.toString)
It is then easy to extend this pattern to traverse the directly and indirectly owned elements.
aspect class NamedElement { operation eachAllOwnedElement(func : <NamedElement -> Void>) : Void is do eachOwnedElement{e| func(e) e.eachAllOwnedElement{child|func(child)} } end }
This new operation can be called in the same easy way:
// write the name of all the elements directly or indirectly contained by c c.eachAllOwnedElement{ aNamedElement | stdio.writeln(aNamedElement.name) } // collect all the elements directly or indirectly contained by c var allownedElements : Sequence<Object> := Sequence<Object>.new c.eachAllOwnedElement{ aNamedElement | allownedElements.add(aNamedElement) }
Tip | |
---|---|
Like any pattern, this task can be automated by using a model transformation. The Ecore MDK offers such transformation that generates this pattern for a given Ecore model. |
Text not verified for kermeta 2 | |
---|---|
The dynamic expression are currently not implemented in Kermeta 2. However, there are some plan to reimplement them in the future. |
Kermeta allow you to evaluate dynamically a Kermeta Expression with a specific context.
Example 1 : my first dynamic expression
var de : DynamicExpression init DynamicExpression.new de.initializeDefaults de.formalParameters.put("a", String) de.parse("stdio.writeln(a)") var params : Hashtable<String, Object> init Hashtable<String, Object>.new params.put("a", "hello world!") de.execute(void, params)
If you want to dynamically evaluate more than one statement, you will have to surround your set of statements with "do.. end" block:
Example 2 : yet another example
// let's get previous example and modify it // [...] (carriage return is not necessary inside the block) de.parse("do stdio.writeln(a) stdio.writeln("another stdio writeln ... ") end") // [...]
Example 3 : another more complex sample of dynamic expressions :
package testDynamicExpression; using kermeta::interpreter using kermeta::utils using kermeta::standard class TestMain { operation TestToto() is do stdio.writeln("J'ai essayé de lancer testtoto!") end operation testDynExp() is do var dynExpr : DynamicExpression init DynamicExpression.new dynExpr.initializeDefaults() self.getMetaClass.ownedOperation.select{op| not( op.name.indexOf("Test")==-1) and op.name.indexOf("All")==-1} .collect{op|op.name}.each{opName| stdio.writeln("execution de "+opName) dynExpr.initializeDefaults dynExpr.parse("testDynamicExpression ::TestMain.new." +opName) dynExpr.execute(void,Hashtable<String,Object>.new) } end }
Caution | |
---|---|
You cannot use "self" inside a dynamic expression |
Text not verified for kermeta 2 | |
---|---|
In Kermeta, a contract is specified by "pre" and "post" conditions and by the "invariant" constraint too.
A pre or a post condition is a boolean expression that may be a simple equality checking or a lambda expression. The Kermeta interpreter evaluates the body content like a boolean result
The "pre condition" are declared just before the "is" of the operation body
operation opName(c : String) : String // Declaration of the pre-condition pre notVoidInput is do c != void and c != "" end // Declaration of the post-condition post notVoidOutput is result != void and result != "" is do // operation body end
Tip | |
---|---|
If the body contains only one expression, the block declaration "do ... end" is not mandatory. If your block contains several instructions, the latest one will be evaluated as a boolean expression. |
An invariant constraint is declared anywhere in a ClassDefinition block.
An invariant declaration is a boolean expression that may be a simple equality checking or a lambda expression. The Kermeta interpreter evaluates the body content like a boolean result.
A very simple example :
class className { ... // Declaration of the invariant : deterministicTransition inv nameOfTheInvariant is do self.name != "" end ... }
Tip | |
---|---|
If the body contains only one instruction, the block declaration "do ... end" is not mandatory. // Declaration of the invariant : deterministicTransition inv nameOfTheInvariant is self.name != "" |
A lambda expression can be used into an invariant declaration:
// Declaration of the invariant : deterministicTransition inv deterministicTransition is do self.outgoingTransition.forAll{tr1 | self.outgoingTransition.forAll{ tr2 | ( tr2.input==tr1.input ) == (tr1==tr2) } } end
The activation of the checking of the pre - post conditions depends of the run configuration, see the Kermeta UI user guide for more information.
If the boolean statement is evaluated to "false" then the pre
or
post condition is violated and
an exception
ConstraintViolatedPre
or
ConstraintViolatedPost
is raised.
In order to check the well-formedness rules of a model
element,
there are two methods in
Kermeta. The first-one :
checkInvariants
, consists to check only the
current model element and the
second-one :
checkAllInvariants
, checks recursively the
element being a containment link with the
checked element.
theModelElement.checkInvariants
The checkAllInvariants operation is a recursive method which checks all the elements having a containment relation by transitivity with the checked element.
checkAllInvariants is used especially to check the validity of a model fragment or a complete model.
theModelElement.checkAllInvariants
If the boolean statement is evaluated to
"
false
" then the invariant constraint is violated
and an exception
ConstraintViolatedInv
is raised.
This exception can be handled by a
rescue
call.
// Call the invariant verification do theModelElement.checkInvariants rescue (err : ConstraintViolatedInv) stdio.writeln(err.toString) stdio.write(err.message) end
Text not verified for kermeta 2 | |
---|---|
Since version 0.4.1, it is possible to write Kermeta code using a simplified Aspect Oriented approach.
Technically, you can declare classes as "aspects" that will contribute features (attributes, references, properties, operations, constraints) to an existing classes. In such situation, the definition of two classes that have the same qualified name will be merged into a single class at run time.
This is a great help when you want to separate the concerns into several files. For example, you may have one file containing the structural part of your metamodel, one file containing the constraints for a given purpose and another file containing the operation and special extension to the metamodel for an interpreter.
As a general guideline, this allow to reopen existing structure in order to add new element. It is not possible to remove or change an existing information. For example, you cannot trnasform an abstract class into a concrete class.
Obviously, the merge will be successful only if there is no conflict between all the declared features.
The merge is driven by the qualified name of the element to merge. Two classes will be merged if they have exactly the same qualified name (packages names + class name)
The merge is allowed only if you add the following keyword:
This keyword is placed on a class, it indicates that this class is an aspect of another one. This allows to complement a class with the features of the aspect class.
In this sample, writing:
package pack; // this is the first aspect of class A aspect class A { attribute decorations : Decoration[0..*] } // this aspect also declares a new class, visible only when requiring this aspect class Decoration{ attribute val : kermeta::standard::String }
package pack; // this is the base class class A { attribute name : kermeta::standard::String // operation in this context have access only to the base's features operation getName() : kermeta::standard::String is do result := name end }
package pack; // is this context we have access to all the features of class A : from base, aspect1 and aspect2 aspect class A { attribute id : kermeta::standard::Integer operation getFullSpecification() : kermeta::standard::String is do result := name result := "(" + id.toString + ")" decorations.each{ decoration | result := result + "<" + decoration.val+ ">" } end }
KermetaProject "aspect_sample" groupId = "my.group" defaultMainClass = "sample::AspectExample" defaultMainOperation = "main" sources = { require "${project.baseUri}/base.kmt" require "${project.baseUri}/aspect1.kmt" require "${project.baseUri}/aspect2.kmt" } dependencies = { //default dependency to kermeta framework (try first in eclipse plugin, then look into maven repository) dependency "library.core" ="platform:/plugin/org.kermeta.language.library.core", "mvn:org.kermeta.language/language.library.core/LATEST" }
New in Kermeta 2 | |
---|---|
In kermeta 1, the require statements were at the file level, now they apply at the project level. This means that the contibution of an aspect will be visible in all the project. It is still possible to manage the visibility of aspects using project dependencies. It is often useful to separate some set of files into projects. |
Tip | |
---|---|
In the previous example we aspectize a *.kmt, we could choose to aspectize the corresponding *.ecore file. In fact, the both formats are available: *.kmt or *.ecore. |
from the point of view of the project, the 3 kmt files are equivalent to write a single kmt file containing :
package pack; class A { attribute name : kermeta::standard::String attribute decorations : Decoration[0..*] attribute id : kermeta::standard::Integer // operation in this context have access only to the base's features operation getName() : kermeta::standard::String is do result := name end operation getFullSpecification() : kermeta::standard::String is do result := name result := "(" + id.toString + ")" decorations.each{ decoration | result := result + "<" + decoration.val+ ">" } end } class Decoration{ attribute val : kermeta::standard::String }
This sample shows that if the signature are strictly equivalent, then you can redefine the same structural feature (attribute, reference) in several aspect class.
In addition, if an operation can be extended to addpre or post conditions to a given operation.
New in Kermeta 2 | |
---|---|
In kermeta 1, and abstract operation was used to define an
operation with no body as a support
for operation extensions. In
kermeta 2 we suppose that if the developper has defined the
operation abstract then his intents is that the operation must be
overriden via a sub class. So
now, the abstract keyword cannot be
used for that. We use the
|
Currently, (v1.0.0) the derived properties cannot be redefined in several aspect classes.
aspect class A { //the feature is equivalent to the one define in the base class, so this is legal attribute name : kermeta::standard::String // the operation has the same signature and has no body (ie. void) operation getQualifiedName() : kermeta::standard::String post resultNotVoid is not result == Void is void }
package pack; class A { attribute name : kermeta::standard::String operation getQualifiedName() : kermeta::standard::String is do result := name end }
In ecore, it is possible to define some operation with body. this
is acheived either via the
generated NOT
comment in the generated java file or via a specific annotation in
the ecore file (Please refer
to EMF documentation and book for
details on that). By default, kermeta will reuse these
operations
and allows to call this code. However, in some situations, you may
want to overload
this code by your own in kermeta. This can be done
as follow:
package pack;
require kermeta
aspect class A {
@overloadable "true"
operation getQualifiedName() : kermeta::standard::String
post notVoid is not result == Void
is abstract
}
package pack;
require kermeta
aspect class A {
@overloadable "true"
operation getQualifiedName() : kermeta::standard::String is do
raise kermeta::exceptions::NotImplementedException.new
end
}
class A { attribute name : kermeta::standard::String operation getQualifiedName() : kermeta::standard::String is do result := name end }
In this sample, the first declaration of the operation is adds a post condition to the operation. As it is abstract, it isn't in conflict with the other declarations.
The second declaration, has a body. It implements a kind of default behavior for the operation that will be used if no other body is declared for this operation. this is useful when converting ecore models into Kermeta for example.
The last definition is the one that will be used in this context.
If you define some inheritance in one of your aspects, all the aspects that require this aspect will have access to it. You don't need to redefine it. However, if it helps to clarify the code to write it again, it will behave the same way.
In Kermeta textual syntax, the keyword aspect must be placed before the keyword abstract.
Example :
package pack; require kermeta aspect abstract class A { // add something }
A special case of the use of AOP in Kermeta is having a ClassDefinition A defined in 2 separately modeling units (*.kmt) with the keyword aspect. But the two modeling units does not require another kmt or ecore file containing the base definition of A. Now if there is a new Modeling Unit that requires the two previous one the available definition of A is the composition of the 2 defined aspects.
Text not verified for kermeta 2 | |
---|---|
Warning | |
---|---|
In current version of Kermeta(v1.1.0), ModelType is still a prototype. Any help, feedback, contribution is welcome to help us to finalize it. |
Kermeta is clearly a model oriented language, one of its first concept is a model element, because they are the manipulated objects. However, we often need to organise them and manipulate them as a set : a model. A common way to do that is to simply use a set, and a resource (used for load and save) is just a set. This approach is not type safe enough.
So, Kermeta implements a clear notion of model type, in order to be able to typecheck this kind of model. A model type is simply the set of the type of all allowed model elements.
In addition, it introduce a mechanism of conformance between modeltype that helps to reuse code from one metamodel to another.
A model type is simply a set of metaclasses. Expressed in Kermeta, this is a collection of Kermeta classes.
package aMetamodel; // let's define some normal classes class C1 { attribute name : String attribute aC2 : C2#aC1 } class C2 { reference aC1 : C1#aC2 } // then defining a modeltype is just listing the classes of this modeltype // here, the model type is composed of two metaclasses, C1 and C2 modeltype MainMT { aMetamodel::C1, aMetamodel::C2}
Tip | |
---|---|
You can use all classic way to define classes, in kmt, but you can also require an ecore file that will contain your class definition (see require section ???) |
Now that you have defined a ModelType, you can use it to declare model variables.
var aMainMT : MainMT init MainMT.new
But, your model is still empty. As its contents is a
(constrained)
Set you simply have to fill it
with your model element
using
typechecked operations like
add
, or using
filtered operation like
addCompatible
and
addAllCompatible
(with this one you can pass any
collection, only compatible objects
will be added to the
model).
// create a model element var newC1 : aMetamodel::C1 init aMetamodel::C1.new() newC1.name := "hello" // then add it to the model aMainMT.add(newC1)
As a model type is based on "normal" class definitions, loading and saving models mostly relies on the normal way to load resource (see Section 2.12, “ Loading and saving models ” )
A simple approach for loading is to load with the classical
approach, then to use the operations
addAllCompatible
with the content of the resource
or
resourceToModel
with the resource. That will
fill the model with compatible objects.
// load a resource with model elements var res : EMFRepository init EMFRepository.new var resource : Resource init res.createResource(modelFileName, ecoreMetamodelFileName) resource.load() // then add them to the model aMainMT.addAllCompatible(resource.contents) // alternative // aMainMT.resourceToModel(resource)
TODO sample of saving (how to put root objects of the model into a resource)
TODO explain how Modeltype helps to reuse existing code from a metamodel to another
Model types use a notion of conformance that allows to determine if a metamodel is conformant to another. The goal is to be able to write code that will be valid not only for models of a given metamodel, but will be valid for models of conformant metamodels.
In order to be flexible, the conformity between metamodel is based on the properties offered by the metaclasses. It ignores the name of the metaclasses.
As a sample, let's define a mini metamodel and its corresponding model type :
Note | |
---|---|
We could also define a modeltype directly on top of the previous metamodel (and restrain it to some of its metaclasses), but the sample would have been less general and wouldn't have illustrated the fact that the name of metaclasses are ignored. modeltype MiniMT_2 { aMetamodel::C1 } |
Warning | |
---|---|
The current implementation works only when there is no ambiguity, if there is ambiguity between two metamodels, then they are considered as not conformant. A future version, may eventually introduce a binding mechanism that would allows to remove those ambiguity. (See Jim Steel Phd thesis for more details ???) |
Text not verified for kermeta 2 | |
---|---|
If you have existing code that you want to run in a Kermeta program, you can use one of those two mechanisms : extern call or seamless require.
The extern call is currently the more robust approach as it is used internally by Kermeta to implement some part of its framework. It also helps to clearly specify the border between java and Kermeta.
The seamless java require is more straitghforward to use (no wrapper to write) but is still in prototype (v0.4.2) and still have several limitations.
The extern allows you to call a java static method from Kermeta. But, to do that, you will have firstly to create a Java wrapper, that will be able to manipulate correctly the Java objects, and secondly to add this wrapper in your java global classpath.
Then, from this method you can access all your java libraries. One task of the static method will be to convert the basic types like Integer or String.
You'll need to refer to the Javadoc of the interpreter in order to know how to access the internal RuntimeObject of Kermeta.
Example 1 : sample of Kermeta code using extern (io.kmt):
/** * An implementation of a StdIO class in Kermeta using existing Java: standard * input/output */ class StdIO { /** * write the object to standard output */ operation write(object : Object) : Void is do result ?= extern fr::irisa::triskell::kermeta::runtime::basetypes::StdIO.write(object) end /** * writeln the object to standard output */ operation writeln(object : Object) : Void is do result ?= extern fr::irisa::triskell::kermeta::runtime::basetypes::StdIO.writeln(object) end /** * read an object from standard input */ operation read(prompt : String) : String is do result ?= extern fr::irisa::triskell::kermeta::runtime::basetypes::StdIO.read(prompt) end }
Example 2 : sample of Java code ("wrapper") called by the Kermeta extern:
/** Implementation of input and output methods */ public class StdIO{ // Implementation of method write called as : // extern fr::irisa::triskell::kermeta::runtime::basetypes::Io.write(output) public static RuntimeObject write(RuntimeObject output) { output.getFactory().getKermetaIOStream().print(output.getData().get("StringValue")); return output.getFactory().getMemory().voidINSTANCE; } // Implementation of method writeln called as : // extern fr::irisa::triskell::kermeta::runtime::basetypes::Io.writeln(output) public static RuntimeObject writeln(RuntimeObject output) { write(output); output.getFactory().getKermetaIOStream().print("\n"); return output.getFactory().getMemory().voidINSTANCE; } // Implementation of method writeln called as : // extern fr::irisa::triskell::kermeta::runtime::basetypes::Io.read(output) public static RuntimeObject read(RuntimeObject prompt) { java.lang.String input = null; // We also have our own String wrapper if (String.getValue(prompt).length()>0) prompt.getFactory().getKermetaIOStream().print(String.getValue(prompt)); // FIXME : dirty cast.. read returns a String or could return smthg else? input = (java.lang.String)prompt.getFactory().getKermetaIOStream().read( String.getValue(prompt)); RuntimeObject result = String.create(input, prompt.getFactory()); return result; }
Tip | |
---|---|
This method is used to implement Kermeta framework. You'll find much more code samples of extern call in its sources. |
Warning | |
---|---|
This feature is still a prototype and still have many limitations. The first version is already available but any help is welcome to help us to improve it. |
The basic principle, is to simply require your jar file.
require "yourjar.jar"
Then Kermeta automatically retreive the class definition it contains to be used from Kermeta code. However, as java and Kermeta have different language constraints, some adaptation are automatically made.
If there is several operations with the same name (as this is legal in Java but not in Kermeta) they are renamed. (Please use the outline to find the new name of the operation you want)
Java contructors are generated as "initialize" operation.
When creating a new java object from Kermeta, a call to new is not enough, you also need to call one of the "initialize" operation in order to correctly create it.
In order to get the standard library direclty from your running
java, you can
require java_rt_jar
but as this
library is really big and as you probably don't need all
java from
Kermeta ;-) then
you must use the includeFilter and
excludeFilter.
Sample using java.io from java Standard library
require kermeta require java_rt_jar includeFilter ("java::io") // the filter ensure we don't get all java using java::io using kermeta::kunit class testRequireJava inherits kermeta::kunit::TestCase { operation main() : Void is do var tr : TestRunner init TestRunner.new tr.run(testRequireJava) tr.printTestResult end operation testmain() : Void is do // create and initialize a File with the String var f : File init File.new.initialize_String("c:/temp/test.txt") var f2 : File // create and initialize a FileWriter with the File var fwriter : FileWriter init FileWriter.new.initialize_File(f) if (f.exists) then stdio.writeln(f.toString + " already exists") else stdio.writeln(f.createNewFile.toString) end fwriter.write_String("Hello world") fwriter.close stdio.writeln("file written") stdio.writeln(fwriter.toString) stdio.writeln(f.getPath) stdio.writeln(f.separator) f2 := f stdio.writeln(f2.createNewFile.toString) stdio.writeln((f2.equals(f)).toString) assert( f2.equals(f)) var fwriter2 : FileWriter fwriter2 := fwriter stdio.writeln((fwriter2.equals(fwriter)).toString) assert( fwriter2.equals(fwriter)) stdio.writeln(f.getPath) stdio.writeln(f.toString) stdio.writeln("End") end }
includeFilter
and
excludeFilter
accept a comma separated list of
qualified name.
includeFilter
adds only elements
whose qualified name start with one of the list.
excludeFilter
removes elements whose qualified name
start with one of the list. If
you use a combinaison of
includeFilter
and
excludeFilter
,
then the
includeFilter
is applied before the
excludeFilter
(that'll remove element from the
included one.
Currently known limitations: no support for java5 generics (they are ignored), requiring very big jar like the full java library end up with out of memory error (you need to use the includefilter and excludeFilter), some bugs with some primitives types (double/float)
Text not verified for kermeta 2 | |
---|---|
As we saw in previous sections, class properties can be defined as
attribute or reference. An
attribute cannot be shared between two or
more objects whereas a reference can be. Let's consider
a class
"
Family
" with a property "
father
"
(of type "
Person
") defined as an attribute. In the
following example, we defined two
objects of type
Family
and we want to define the
father
attribute of the second with the
father
of the first. To do that, we need to clone the
object Person which
represents the father of
"
family1
" because, as said in
Section 2.16.6, “Assignment behavior for attribute (and reference)”
, it could not
be shared between the two objects, by definition of
attribute (in
"technical" words,
an object cannot be contained by 2
containers).
class Person { attribute name : String }
class Family { attribute father : Person } class Main { operation run() is do var family1 : Family init Family.new var p1 : Person init Person.new p1.name := "Robert" family1.father := p1 var family2 : Family init Family.new // ERROR 1 : this assigns p1 to family2.father, which // is already owned by family1.father, so it unsets family1.father // family2.father := p1 // ERROR 2 : this assigns family1.father's value to family2.father, // so it unsets family1.father // family2.father := family1.father // This is correct! family2.father keeps its value family2.father := Person.clone(p1) end }
The "
clone
" method creates a copy of the object
that it receives as input. If
it is a complex object, a deep
clone is
performed for each attribute
of its meta-class and a shallow clone
is
performed for each
reference.
Caution | |
---|---|
Reminder : be very careful with the use of the assignment operator on object. Most of the time, you need to use the "clone" feature. Using assignment on attributes break the previous link between objects. So, In the previous example, p1 has no more name after the assignment !There is one exception to this behavior : when the type of attributes are DataType, i.e, in Kermeta, String, Integer, Boolean, the assignment behaves as if those entities were defined as references. |
Text not verified for kermeta 2 | |
---|---|
As Kermeta is designed to be used in a model driven environment, its structure is given as a model. This section presents the metamodel of Kermeta which corresponds to the abstract syntax of Kermeta.
This metamodel may be useful for many use cases. For example, you can use it to manipulate your Kermeta code for analysis or even generate some Kermeta code. This may be useful to understand how Kermeta works too.
Note | |
---|---|
All the code samples in this section are for illustration of the given concepts. |
The goal of the Kermeta language is to provide an action language for MOF models. The idea is to start from MOF, which provides the structure of the language, and to add an action model. Using the MOF to define the structure of the Kermeta language has an important impact on the language. In fact, as MOF concepts are Object-Oriented concepts, Kermeta includes most of the classical Object-Oriented mechanisms. Yet, MOF only defines structures, and the operational semantic corresponding to MOF concepts has to be defined in Kermeta. For instance MOF does not provide a semantic for behavior inheritance (concepts like method redefinition, abstract method, etc does not have any sense in the MOF).
Text not verified for kermeta 2 | |
---|---|
Kermeta has been designed to be fully compatible with the OMG standard meta-data language EMOF. The metamodel of Kermeta is divided into two packages :
structure which corresponds to EMOF
behavior which corresponds to the actions. This section gives an overview of these two packages and their relationships.
Thanks to this reuse, Kermeta is fully compatible with EMOF. This is useful in the promotion of Kermeta as a metalanguage.
The link between structure and behavior is made through the
property
body
of class
Operation
which allows to define the behavior of an operation using a Kermeta
expression.
A more detailed description of the architecture of Kermeta is presented in next sections.
Text not verified for kermeta 2 | |
---|---|
This figure presents the main classes of the structure package. To design this package, we started from EMOF and completed it to build the Kermeta language. The choice of EMOF is motivated by two main reasons : firstly it is standardized by the OMG and secondly is is well-supported by tools such as Eclipse/EMF.
As MOF is not initially designed to be executable, several
concepts
have to be completed and
slightly modified to build an
executable
language. The first and most important modification is to
add
the
ability to define the behavior of operations. To achieve this we
define an action language
in the package
behavior
of Kermeta. The class
hierarchy of the package behavior is presented
on
???
. In practice, Kermeta expressions have
been designed by adding model
modification capabilities
(like assignment
of properties for
instance) to OCL expressions.
This represents the static part of the metamodel.
As a reminder, the structure of Kermeta is derived from EMOF from the OMG. During the build process, we merge and connect it with the behavior part.
So all the meaning of those metaclasses are very close to those described in the OMG standard specification.
This figure presents the element in the structure package that have a name. Kermeta relies on in various situation (For example in the typechecking process). The containment hierarchy is also presented since it is used in the identification process.
Class is a bit special, since its name is a derived property.
This figure presents the global view of all the metaclasses involved in Kermeta type system.
Basically, you can notice the split between Type and TypeDefinion needed in order to handle generics.
The containment is also represented here.
Please note that there is both
Type
and
TypeDefinition
. This is needed
because of the support of the generics. For example
in
var mycoll : Collection<String>
mycoll is a VariableDecl which point to a
Class
whose typeDefinition is the ClassDefinition
Collection
and has a TypeVariableBinding that lead to String.
TODO add an object diagram or a figure to illustrate.
TODO : if time : provide a set of small diagrams that focus of some elements of the type system.
This figure presents the global view of the main metaclasses involved in Kermeta ModelType.
This figure presents the view of the main metaclasses used by the ModelType adaptation.
Text not verified for kermeta 2 | |
---|---|
Kermeta provides basic control structures : block, conditional branch, loop, and exception handling. Here there an excerpt of the Meta-model describing control structures. Each basic control structures derives from the Expression concept.
In the following example, the type of super[ParentClass](element) is CallSuperOperation :
class ParentClass { operation op(element : Integer) : Integer is do result := element + 1 end } class ChildClass inherits ParentClass { method op(element : Integer) : Integer is do result := super[ParentClass](element) end }
The type of callvar , below, is CallVariable:
var myvar : Integer var callvar : Integer init 4 // myvar := callvar
A special case, when calling a lambda expression : the type of lf in the assignment of res, is CallVariable.
var lf : <Integer->Integer> var res : Integerlf := function { i : Integer | i.plus(1) } // The type of lf, below, is CallVariable res := lf(4)
The type of result is CallResult
operation op() : Integer is do result := 61 end
The type of self is a SelfExpression!
The type of attr in the body of the operation myoperation is CallFeature (a callfeature on self ), and so is the type of myoperation(4) (a callfeature on a ).
class A { attribute attr : Integer operation myoperation(param : Integer) : Integer is do result := self.attr + param end } class B { operation anotheroperation() : Integer is do var a : A result := a.myoperation(4) end }
In the following example, thetarget is of type CallExpression and thevalue is of type Expression .
var num : Numeric var thetarget : Integer var thevalue : Integer // assignment : thetarget->target, thevalue->value thetarget := thevalue // casting : a is casted into the type of num which is Numeric. num ?= a
var i : Integer i := 5 // 5 is a IntegerLiteral var s : String s := "I am a string" // "I am a string" is a StringLiteral
New in Kermeta 2 | |
---|---|
In Kermeta 2, in order to delegate the resolution work to the tool,
it use a specific internal
structure based on
Unresolved
metaclass. The resolver will transform every
Unresolved
into the final real
ModelElement. This allow to
build transformations that generate valid km
models without the difficulty of dealing with the
framework default types and the resolving of km
model splitted in several files.
All Kermeta 2 internal transformations like kmt2km, ecore2km,
qvt2km use this mechanism.
Text not verified for kermeta 2 | |
---|---|
In Kermeta, you can view its metamodel by several ways.
First, the ecore file kermeta_java.ecore is available in the lib folder of Kermeta main plugin.It is used when saving a Kermeta program in XMI ("Compile to XMI" function on *.kmt files). You can then load Kermeta program as a model, typically to transform it.
Warning | |
---|---|
You should not try to execute operations on Kermeta models you've just dynamically created unless you froze it. This is a feature which has not been completly tested. |
Another typical way to access to a Kermeta model and Kermeta
metamodel is to use the reflection.
All objects inherits from Object
that defines the
getMetaClass
operation. This use is
used in one of the samples of
Section 2.20, “
Dynamic evaluation of
Kermeta expressions
”
(when it selects an
operation to be executed).
At last, the parse method on dynamic expression presented in section Dynamic evaluation of Kermeta expressions can give you some way to access a Kermeta model as it parses a Kermeta text and provides the corresponding model.
Text not verified for kermeta 2 | |
---|---|
Kermeta is bundled with a framework that provides the base functionalities for metamodel engineering.
Tip | |
---|---|
When you write
|
Currently, it provides the following packages :
Packages in Kermeta framework
kermeta::interpreter
This package defines some classes related to Kermeta interpreter and also to Kermeta surface syntax. Some uses of this package are illustrated in Section 2.20, “ Dynamic evaluation of Kermeta expressions ” .
kermeta::persistence
This package defines the notion required to serialize and deserialize models. This is illustrated in Section 2.12, “ Loading and saving models ” and in the EMF tutorial document ( http://www.kermeta.org/documents/emfTutorial/ )
kermeta::kunit
This is a basic support for unitary tests (like Junit but for Kermeta).
kermeta::language::structure
It contains the classes of Kermeta structure. Note that it slightly differs from the kermeta_java.ecore due to an inheritance to the classes in Kermeta::reflexion. However, it proposes the very same functionalities.
kermeta::language::behavior
It contains the classes for the behavior of Kermeta.
kermeta::exceptions
It defines various exceptions that you can use in Kermeta. Inheriting from kermeta::exceptions::Exception allows to give more information to the end user because it will also provide a stack trace (which would not be available otherwise).
kermeta::io
This package provides basic support for input/output with Kermeta. It is voluntary minimal because that not the main role of Kermeta to provide such primitives. In the future, this package may even disappear and be replaced by the ability to directly call Java libraries.
kermeta::reflection
This package contains all the abstract classes needed for the reflexivity of Kermeta. The concrete implementation are in kermeta::standard or kermeta::structure.
kermeta::utils
This package defines various tool classes that cannot be part of the core of the language, but are important enough to be part of the framework.
kermeta::standard
This package defines all the basic objects needed for a typical Kermeta application. For example, it defines data types, collections, etc.
Note | |
---|---|
For more details about the content of these packages, please look at the generated documentation available on line: http://www.kermeta.org/docs/KermetaFramework/framework.km.html |
Text not verified for kermeta 2 | |
---|---|
The following keywords are used by Kermeta textual syntax.
Note | |
---|---|
If you want to name some class or property using those names,
you'll
need to escape them using the
~ (tilda).Ex:
|
Keyword |
Usage |
---|---|
@pre |
Represents self before the call to this operation in the scope of a post condition. See Section 2.21.1.1, “pre – post conditions syntax” |
abstract |
Modifier for class or operation |
alias |
Definition of a primitive type |
and |
Boolean expression |
aspect |
Modifier for class. Indicates that the class reopen another class. See Section 2.22, “ Weaving Kermeta code ” |
attribute |
Definition of an attribute |
bag | |
class |
Definition of a class |
do |
Beginning of a block |
else |
Else part of a conditional instruction |
end |
End of a block |
enumeration |
Definition of an enumeration |
extern |
Call of a java static operation |
false |
Boolean literal |
from |
Loop instruction |
function |
Declares a local function |
getter |
Declaration of a property getter |
if |
Conditional instruction |
inherits |
Declartion of the super classes of the class |
init |
Initialization of a variable. In Kermeta 2, this can be replaced by
|
inv |
Declaration of an invariant. See Section 2.21.1.2, “Invariant constraint syntax” |
is | part of the declaration of an operation or method. See Section 2.8.2, “Defining operations” |
loop |
Loop instruction |
method |
Redefinition of an operation. See Section 2.8.2, “Defining operations” |
modeltype |
Definition of a modeltype. |
not |
Boolean expression |
operation |
Declaration of an operation. See Section 2.8.2, “Defining operations” |
or |
Boolean expression |
oset | |
package |
Declaration of package |
post |
Declaration of a postcondition. See Section 2.21.1.1, “pre – post conditions syntax” |
pre |
Declaration of a precondition. See Section 2.21.1.1, “pre – post conditions syntax” |
property |
Declaration of a derived property |
raise |
Throw an exception |
raises |
Declares the exception that an operation can throw |
readonly |
Modifier for properties |
reference |
Declaration of a reference |
rescue |
Catch an excetion |
result |
Special variable used for the return value of an operation. See Section 2.8.2, “Defining operations” |
setter |
Declaration of property setter |
self |
Special varaible representing this instances |
set | |
seq | |
singleton |
Definition of a singleton class. See Section 2.17, “ Singleton class ” |
super |
Call to the super operation |
then |
Then part of a conditional instruction |
true |
Boolean literal |
until |
Loop condition |
using |
Shortcut used to avoid to write the full qualified name |
value |
Special variable used in getter and setter to represent the actual value of the property |
var |
Declaration of a variable |
void |
Void literal |
Text not verified for kermeta 2 | |
---|---|
This annex collect the use of known tags. They are generally used as an extension on Kermeta in these typically uses :
Note | |
---|---|
TODO : finish this by looking into kermeta2ecore transformation |
Kown tags :
aspect
Possible values. "true", case insensitive, any other value is considered as "false".
Context. Class
Use description. Old way to indicate that a Class must be reopened as an aspect of another Class and then merging its features. We now prefer to use the dedicated keyword.
Use sample.
@aspect "true"
class MyClas {
// ...
}
ecoreUri
Possible values. An nsURI.
Context. Package
Use description. Indicates the nsUri for the given package when it is transformed in Ecore.
Use sample.
@ecoreUri "http://myPackage/1.0/"
package myPackage;
mainClass
Possible values. Qualified name of a class in the Kermeta program.
Context. ModelingUnit
Use description. Indicates a preferred class from which the run configuration should try to start the Kermeta program. This helps to create a default run configuration. Must be used with the mainOperation tag.
Use sample.
@mainClass "myPackage::MyMainClass" @mainOperation "main" package myPackage; require kermeta class MyMainClass { operation main() : Void is do // TODO: implement 'main' operation end }
mainOperation
Possible values. name of an operation in the main class.
Context. ModelingUnit
Use description. Indicates a preferred operation from which the run configuration should try to start the Kermeta program. This helps to create a default run configuration. Must be used with the mainClass tag. The operation must : either have no parameter, or have string parameters.
Use sample.
@mainClass "myPackage::MyMainClass" @mainOperation "main" package myPackage; require kermeta class MyMainClass { operation main() : Void is do // TODO: implement 'main' operation end }
Text not verified for kermeta 2 | |
---|---|
Kermeta natively integrates Ecore defintions, however it uses an internal mapping in order to import ecore model as Kermeta models. In the other way round a Kermeta model can be translated into a pure ecore model. (for example in the compiler)
This annex indicates how Kermeta models are translated into Ecore models and vice versa.
Note | |
---|---|
TODO : finish this by looking into kermeta2ecore transformation and the compiler specifications. |
Kermeta concept |
Ecore concept |
Notes |
---|---|---|
Class |
EClass | |
Constraint (invariant) |
EAnnotation(source="kermeta.inv") + DetailEntry | |
Constraint (precondition|postcondition) |
EAnnotation(source="kermeta.pre"|"kermeta.post") + DetailEntry | |
Constraint (postcondition) |
EAnnotation(source="") + DetailEntry | |
Operation |
EOperation | |
Tag (general case) |
EAnnotation(source="kermeta") + DetailEntry |
Most Tags are translated into an EAnnotation with
source="kermeta" and one DetailEntry with
key=
|
Tag (applied on Constraint) |
EAnnotation(source="kermeta.inv.doc") + DetailEntry |
EAnnotation.references points to the EAnnotation
representing the
Constraints. DetailEntry
with key=
|