2.22.  Weaving Kermeta code

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

2.22.1. Textual syntax for merging

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:

aspect

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.

2.22.2. Example 1: Simple Class merge

In this sample, writing:

file aspect1.kmt

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
}

base.kmt

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
}

aspect2.kmt

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
}

aspect_sample.kp

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"
	}
[Note] 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]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
}

2.22.3. Example 2: merge with feature redefinition

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.

[Note] 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 void keyword instead.

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
}

2.22.4. Example 3: merge with overload of operation from ecore genmodel

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.

2.22.5. Aspect and inheritance

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.

2.22.6. Aspect and abstract

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
}

2.22.7. Aspect without common base

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.