Table of Contents
List of Figures
The latest version of this document is available online from http://diverse-project.github.io/k3/
K3 is an action language build on top of the Xtend programming language in order to use it for executable metamodeling.
Its key feature allows to "re-open" classes of an Ecore metamodel and to weave new features or operations in these classes.
The combinaison of K3 and Xtend allows to address a wide range of activities related to model element manipulation. This includes activites such as:
For an even larger range of activities, K3 is natively used by Melange. Melange allows to build a type system for models and enable scenarios such as:
Technically, K3 consists in a set of active annotations that can be used in plain Xtend files to express e.g. aspects, pre/post conditions or invariants, and uses Xbase as the expression language for operations bodies.
Actually, many of K3 annotations are not dependent on EMF/ecore and can also work on plain java classe.
Integrating Kermeta and java code has never been that easy !
K3 action language is built as an extension to Xtend that in turn is able to fully reuse existing java bytecode. Java code can be used from Xtend/K3 and vice versa.
Despite that Kermeta was first intended to model manipulation, this java integration means that K3AL can be used for any kind of program.
As all other versions of Kermeta, K3 Action Language leverages the open-class mechanism. It allows to "re-open" existing meta-classes of a metamodel to insert new features such as attributes, references, or operations. Please note that return types of aspects methods cannot be inferred: they must be explicitly written!
package mypackage import fr.inria.diverse.k3.al.annotationprocessor.Aspect import mypackage.OtherClass import static extension mypackage.OtherClassAspect.* @Aspect(className=OtherClass) class OtherClassAspect { var int foo = 1 def void display (){ prinln("I am OtherClassAspect") // The keyword _self must be used to refer to attributes and operations of the class and aspect. _self.foo = 2 _self.callingOnOperationOfOtherClass() } }
This mechanism serves as the basis for the definition of aspects on metamodels elements, enabling aspect-oriented modeling that helps to model complex software artifacts composed of intertwined and cross-cutting concerns.
Typical use of such construct are the Visitor and Interpreter design patterns which are implemented in a more elegant and readable way with Aspect Oriented languages than with with classical Object Oriented languages.
This section present a brief overview of the keywords, annotations and syntax elements used by K3. The main elements are detailled in the Chapter 3, Language Reference chapter.
keyword/syntax | Short description |
---|---|
_self | In an Aspect, _self is a reference to the current object. |
import static extension XYZAspect.* | Makes all features defined on XYZAspect available in the current compilation unit (xtend file). This allows for natural writing using Object Oriented syntax. (see Section 5.1, “Import static extension with wildcard”) |
All K3 annotations are defined in the package fr.inria.diverse.k3.al.annotationprocessor
Annotation | Applies to | Short description |
---|---|---|
@Aspect |
| Indicates that the class is an aspect on top of another base class. @Aspect(className=XYZ) class XYZAspect {} In addition to the mandatory className attribute, an optionnal with attribute can be used in case of multiple inheritance. |
@OverrideAspectMethod |
| Allows to override a method when an aspect class inherits from another aspect class. |
@ReplaceAspectMethod |
| Allows to replace a method that is defined in the base class by the method that has this annotation. This makes sure that every call to the base class method are redirected to this method. Note: In some situations, this annotation implementation relies on AspectJ code for intercepting some calls. |
@SynchroField |
| When a field exists both in the base class and in the aspect (same name and type), this annotation makes sure to synchronize their values when assigning one of the fields. Note: The field must be public Note: In some situations, this annotation implementation relies on AspectJ code for intercepting some calls. |
@Abstract |
| Used to tag a k3 operation as abstract when initially defined. |
@Singleton |
| The fields defined in the aspect are shared by all instances of the base class. |
Annotation | Project | Applies to | Short description |
---|---|---|---|
@Step |
|
| Requires Modelanimation framework The method is executed in an EMF Transaction in order to allow edition and observation of the changes on the model. |
@Main |
|
| Used to define possible entry methods of Sequential engine. |
@InitializeModel |
|
| Used to define a method that initializes the model before starting an engine (Sequential or Concurrent). @InitializeModel def public void initialize(List<String> args){} |
Annotations that have been coded but need further development in order to be fully usable. Help welcome :-)
Annotation | Applies to | Short description |
---|---|---|
@NotAspectProperty |
| |
@Composition |
| |
@Containment |
| |
@Opposite |
|
This annotation set allows to do some design by contract programming.
Known limitation : there is no way to prevent the contracts from doing some side effects on the model.
Annotation | Applies to | Short description |
---|---|---|
@Contracted |
| Indicates that the class will define contracts. |
@Inv |
| Indicates that this contract must be true for all instances of the base class. |
@Pre |
| |
@Post |
|
To illustrate the use of K3, in the following sections, we will extend a base java structure. so let’s consider the following set of classes of a small ecore representing a Blog.
Defining an aspect on a class is done by adding the annotation Aspect on the class corresponding to the aspect. The annotation parameter className will indicates which is the base class that will be augmented.
package blogsite.aspects import fr.inria.diverse.k3.al.annotationprocessor.Aspect import blogsite.Blog @Aspect(className=Blog) class BlogAspect { def void display (){ prinln("I am BlogAspect") } }
In an aspect you can use the attributes and operations of both the augmented class and the aspect itself.
In aspects, the keyword "this" must not be used. Use the keyword "_self" instead.
@Aspect(className=Blog) class BlogAspect { int blogVersion = 1 def void changeTitle (String newTitle, int newVersion){ // The keyword _self must be used to refer to attributes and operations of the class and aspect. _self.blogVersion = newVersion _self.title = newTitle } }
this attribute exists only in the aspect | |
in aspect, attribute defined on aspect is accessed via _self | |
in aspect, attribute defined on the base class is accessed via _self |
Despite it should be legal, due to a bug in xtend, we do not recommand the following syntax:
@Aspect(className=typeof(OtherClass))
It may raise strange scope issue, especially in case of aspects defined in several packages.
To use the aspect, you need to import the correct classes and indicates to xTend thant some additionnal features can come from the aspect.
This is done using the special import import static extension which enable the use of the additions declared in the aspect.
package blogsite.demo import blogsite.Blog import blogsite.aspects.BlogAspect import static extension blogsite.aspects.BlogAspect.* class Main { def static void main(){ val Blog b1 = new Blog b1.display() } }
This import static extension <myPkg.AspectName>.* allows to make visible the attributes and operations defined on BlogAspect. | |
method display() is visible thanks to the import static extension BlogAspect.* |
If several operations or attributes have similar names, xTend may not find the expected feature. In that case you need to disambiguate the call by accessing the underlying helper class.
To have more details about the precise mechanism of this helper class please refer to the chapter at the end of this document.
@Aspect(className=Blog) class BlogAspect2 { int title = 1 }
import static extension blogsite.aspects.BlogAspect2.* class Main { def static void main(){ val Blog b1 = new Blog println( b1.title) println( BlogAspect2.title(b1) } }
Might be ambiguous because it reuse the name of an attribute on the base class | |
returns the attribute | |
return the attribute |
Be careful when cleaning unused imports while your programs isn’t complete (for example using ctrl+shift+o) because it will also removed unused static extension and will disable the code completion (ctrl+space) for the features of these aspects.
An Aspect class can inherit fields and methods from another class. The simple inheritance is acheived using the extends keyword from Xtend.
However, as for Java, an aspect can directly inherit from only one aspect ! For multiple inheritance, please refer to the advanced section.
package blogsite.aspects import fr.inria.diverse.k3.al.annotationprocessor.Aspect import fr.inria.diverse.k3.al.annotationprocessor.OverrideAspectMethod import blogsite.HasAuthor import blogsite.Post import static extension blogsite.HasAuthor.* import static extension blogsite.PostAspect.* @Aspect(className=HasAuthor) class HasAuthorAspect { def String display (){ returns _self.author } } @Aspect(className=Post) class PostAspect extends HasAuthorAspect{ @OverrideAspectMethod def String display (){ returns _self.title + " by " + _self.author } }
A call to a super operation can be done by writing super_ followed by the name of the operation. Do not forget to use _self to handle elements of the class or aspect.
The previous example could have been written like this:
package blogsite.aspects import fr.inria.diverse.k3.al.annotationprocessor.Aspect import fr.inria.diverse.k3.al.annotationprocessor.OverrideAspectMethod import blogsite.HasAuthor import blogsite.Post import static extension blogsite.HasAuthor.* import static extension blogsite.PostAspect.* @Aspect(className=Post) class PostAspect extends HasAuthorAspect{ @OverrideAspectMethod def String display (){ returns _self.title + " by " + _self.super_display() } }
Even if Java allows only one inheritance via extends. When using EMF, it is legal to inherit from several class. The underlying framework will take care to implement a pattern using Interfaces.
Our aspects must also be able to deal with that situation.
The @Aspect annotation can then take a with attribute to indicate this inheritance.
If the following ecore model and its java implementation, there is a multi-inheritance from Child class to 3 ParentX classes.
then, in order to correctly reflect these inheritances in the aspects, we will define the following.
// [..] import static extension multiparents.Parent1.* import static extension multiparents.Parent2.* import static extension multiparents.Parent3.* import static extension multiparents.Child.* @Aspect(className=Parent1) class Parent1Aspect { def void someParent1Method(){ // [..] } } @Aspect(className=Parent2) class Parent2Aspect { def void someParent2Method(){ // [..] } } @Aspect(className=Parent3) class Parent3Aspect { def void someParent3Method(){ // [..] } } @Aspect(className=Child, with=#[ParentAspect2, ParentAspect3] ) class ChildAspect extends Parent1Aspect{ def void someMethod(){ _self.someParent1Method() _self.someParent2Method() _self.someParent3Method() } }
each parent defines its own aspects. | |
secondary aspect inheritances are added thanks to the with attribute. | |
primary aspect inheritance is declared using the extends keyword like a single inheritance. |
The syntax with=#[ParentAspect2, ParentAspect3]
indicates that we create a list with 2 additional parents (in addition to the primary one that has been defined using the extends keyword). If there is only one additional parent, then the following syntax will also work : with=ParentAspect2
When an ecore model defines an inheritance structure, the best practice is to fully reflect the structure in the aspects. Othewise, the dynamic dispatch may lead to non-intuitive behaviors.
Sometimes the base class already declares some methods but you need to redefine it in your aspects. For this, K3 uses the @ReplaceAspectMethod annotation to provide a replacement method to be used instead of a method defined in the base class.
A typical use case is a method declared in the ecore metamodel like in the following example.
In this example, the System class defines a run method. However, by default in ecore, it has no implementation. (It throws a UnsupportedOperationException
)
Then, if in your code you wish to refine it in an aspect, you probably expect to be able to call it like this:
Unfortunately, even with the import static extension declaration, using this syntax (sm.run
), java/xtend will still
call the method declared on the base class.
To have the desired behavior, two solutions:
FSMAspect.run(sm)
(see Section 3.1.2.1, “Disambiguation”); but this must be done on every call to the run method.Internally, this annotation indicates to create and use an aspectJ pointcut that replaces the calls to the base class method and replace it by a call to the corresponding method in the K3 aspect class.
K3 comes with several wizard and template facilities to help you start your projects.
This can be either by reproducing one of the featured examples or by using the new K3 project wizard.
As K3 can be used in several situations, the wizard allows to create project for the various configurations:
and using a modeling framework or not.
The Modeling environment can also be set to use EMF/Ecore when checking the Use EMF option. In this case it will add the dependencies to the correct jars dependeing on the dependency management Eclipse plugin with Ecore/EMF.
When set to Plug-in the project uses the Eclipse Plugin nature for defining the dependencies. So all the K3 jar and xTend jar will be referenced in the manifest.mf of the project.
When set to Standalone In that mode, all the depencies are copied in a lib folder. This includes K3 jar, but also xTend jar. It is based on the eclipse standard java compiler for defining dependencies. This mode can be useful as a base for small project with few dependencies
When set to Maven In that mode, the project will be a maven project and dependencies will be resolved using maven.
when clicking on next in the first page your can acces to the templates
These templates allows to create typical project structures for various needs.
One of the most useful is the Use Ecore Basic aspect which asks the user for an initial Ecore model (and its project). With this ecore model, it will automatically create an aspect class for each metaclasses in the ecore. It takes care of replicating the inheritance tree in the aspects classes.
Please note that some templates may require a given dependency management or modeling framework. Those templates will not be available if the options on the first page are not compatible with the template.
This section presents how K3 is implemented and which compiling scheme it follows.
Understanding how K3 use Xtend syntax and how K3 compilation scheme changes xtend java code generation should help understand some issues or help integrating K3 code in larger applications.
Basically, K3 works by combining 2 mains Xtend mecanisms :
In Java, Static import is a feature that allows members (fields and methods) defined in a class as public static
to be used in Java code without specifying the class in which the field is defined. The wildcard allow to access all the static members of a class.
Similarily, in Xtend, extension methods allow to add new methods to existing types without modifying them. They are based on a simple syntactic trick: Instead of passing the first argument of an extension method inside the parentheses of a method invocation, the method can be called with the first argument as its receiver - it can be called as if the method was one of the argument type’s members.
When used with a wildcard, the import static extension
allow to apply this to all static methods of a class.
import static extension java.lang.Math.*; // [..] val double f1 = 2.5 val double f2 = 2.9 Math.max(f1,f2) max(f1,f2) f1.max(f2)
is the classic way to access the static method (with only a | |
is allowed by java with an | |
is allowed by Xtend’s |
K3 use this mecanism extensively in order to provide the feeling of writing object oriented call on methods and attributes on aspects which are actually static methods..
import static extension
is enough to contribute operations to a class, however we also often need to contribute fields.
To do so, K3 implement a set of Active annotations. This kind of annoation allows to contribute to Xtend java code generator and rewrite a part of the resulting java code.
The @Aspect annotation allows to generate a set of classes that declares static methods that will be made avialable thanks to the import static extension
. This set of classes implements a pattern that allows to to store the fields defined in the aspects.
For every aspect class:
*AspectProperties
class that is the companion object that will store the attributes defined by aspect.*AspectContext
class that defines the mapping between the base object and the companion object.The result of the compilation of Xtend and K3 annotations in plain java is visible in the xtend-gen
folder.
Let’s consider the following K3 code that adds a String attribute to the java.io.File
class:
import static extension k3project.SampleXMLFileAspect.* @Aspect(className=java.io.File ) class SampleXMLFileAspect { public String contentType = "" } //[..] val f = new File("toto.txt") f.contentType = "txt"
Obviously, this will also work for operations. Adding an attribute is simply a little bit more complex since from xtend point of view, it adds 2 methods: the getter and the setter
It will be compiled in 3 Java classes : SampleXMLFileAspect
, SampleXMLFileAspectFileAspectContext
and SampleXMLFileAspectFileAspectProperties
.
SampleXMLFileAspect
is the public interface that declares all public static
methods that are make available with the import static extension
. A technical part is kept in private.
You can notice the systematic use of _self as the first parameter of the static methods. This allows to access to the object instance from the bodies of aspect methods.
SampleXMLFileAspectFileAspectProperties
is the companion object that is associated to the base object.
SampleXMLFileAspectFileAspectContext
implements the map that allows to get the companion object SampleXMLFileAspectFileAspectProperties
via the _self keyword.
public class SampleXMLFileAspectFileAspectProperties { public String contentType = ""; }
public class SampleXMLFileAspect { public static String contentType(final File _self) { k3project.SampleXMLFileAspectFileAspectProperties _self_ = k3project.SampleXMLFileAspectFileAspectContext.getSelf(_self); Object result = null; result =_privk3_contentType(_self_, _self); return (java.lang.String)result; } public static void contentType(final File _self, final String contentType) { k3project.SampleXMLFileAspectFileAspectProperties _self_ = k3project.SampleXMLFileAspectFileAspectContext.getSelf(_self); _privk3_contentType(_self_, _self,contentType); } protected static String _privk3_contentType(final SampleXMLFileAspectFileAspectProperties _self_, final File _self) { try { for (java.lang.reflect.Method m : _self.getClass().getMethods()) { if (m.getName().equals("getContentType") && m.getParameterTypes().length == 0) { Object ret = m.invoke(_self); if (ret != null) { return (java.lang.String) ret; } } } } catch (Exception e) { // Chut ! } return _self_.contentType; } protected static void _privk3_contentType(final SampleXMLFileAspectFileAspectProperties _self_, final File _self, final String contentType) { _self_.contentType = contentType; try { for (java.lang.reflect.Method m : _self.getClass().getMethods()) { if (m.getName().equals("setContentType") && m.getParameterTypes().length == 1) { m.invoke(_self, contentType); } } } catch (Exception e) { // Chut ! } } }
public getter operation seen from xtend | |
public setter operation seen from xtend | |
real getter code in the privk3 method | |
real setter code in the privk3 method |
public class SampleXMLFileAspectFileAspectContext { public final static SampleXMLFileAspectFileAspectContext INSTANCE = new SampleXMLFileAspectFileAspectContext(); public static SampleXMLFileAspectFileAspectProperties getSelf(final File _self) { if (!INSTANCE.map.containsKey(_self)) INSTANCE.map.put(_self, new k3project.SampleXMLFileAspectFileAspectProperties()); return INSTANCE.map.get(_self); } private Map<File, ModuleAspectFileAspectProperties > map = new java.util.WeakHashMap<java.io.File, k3project.ModuleAspectFileAspectProperties > (); public Map<File, ModuleAspectFileAspectProperties> getMap() { return map; } }