K3 User Documentation


Table of Contents

1. Presentation
1.1. Key features
1.1.1. Full java compatibility
1.1.2. Open class mechanism
2. Language Memento
2.1. Main notations for K3.
2.2. Main annotations for K3.
2.3. Project Specific Annotations
2.4. Experimental annotations
2.4.1. Structure Modelling annotations
2.4.2. Design by Contract annotations
3. Language Reference
3.1. Basic
3.1.1. Creating an aspect on a class
3.1.2. Using aspect from another k3/xTend program
3.1.2.1. Disambiguisation
3.1.3. Aspect inheritance (simple inheritance)
3.1.4. Calling a super operation
3.2. Advanced
3.2.1. Extending an aspect (multi inheritance)
3.3. Replacing a method of the base class
4. UI User Guide
4.1. New K3 project wizard
4.1.1. Modeling options
4.1.2. Dependency management option
4.1.3. Templates
4.2. New Example wizard
5. Implementation details
5.1. Import static extension with wildcard
5.2. K3 Active annotations
5.2.1. K3 Active annotations compilation example

List of Figures

1.1. Object Oriented visitor versus Aspect Oriented visitor
3.1. Metamodel used as base for the examples)
3.2. Metamodel used as base for the multi inheritance example
3.3. Multi inheritance example with aspects

Note

The latest version of this document is available online from http://diverse-project.github.io/k3/

Chapter 1. Presentation

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:

  • weaving operational semantics in metamodel definition
  • defining model to model transformations
  • defining model to text transformations

Note

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:

  • Language extension Using high-level operators (e.g. inheritance, merge), language definitions can be manipulated, extended or restrained to create new DSLs.
  • Model polymorphism Melange’s type system supports model polymorphism and substitutability, and the definition of generic transformations that can be applied on models originating from different DSLs.

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.

Note

Actually, many of K3 annotations are not dependent on EMF/ecore and can also work on plain java classe.

1.1. Key features

1.1.1. Full java compatibility

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.

Tip

Despite that Kermeta was first intended to model manipulation, this java integration means that K3AL can be used for any kind of program.

1.1.2. Open class mechanism

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.

Object Oriented visitor versus Aspect Oriented visitor

Figure 1.1. Object Oriented visitor versus Aspect Oriented visitor


Chapter 2. Language Memento

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.

2.1. Main notations for K3.

keyword/syntaxShort description

_self

In an Aspect, _self is a reference to the current object.

import static extension XYZAspect.*

makes all all features defined on XYZAspect available in the current compilation unit (xtend file).

2.2. Main annotations for K3.

All K3 annotations are defined in the package fr.inria.diverse.k3.al.annotationprocessor

AnnotationApplies toShort description

@Aspect

Class

Indicates that the class is an aspect on top of another class.

@Aspect(className=XYZ)
class XYZAspect {}

@OverrideAspectMethod

Method

allows to override a method when an aspect class inherits from another aspect class.

@ReplaceAspectMethod

Method

allows to replace a method that is defined in the base class.

@SynchroField

Field

When a field exists both in the base class and in the aspect, this annotation make sure to synchronize their values when assigning one of the fields.

@Abstract

Method

Used to tag a k3 operation as abstract when initially defined.

@Singleton

Class

The fields defined in the aspect are shared by all instances of the base class.

2.3. Project Specific Annotations

AnnotationProjectApplies toShort description

@Step

GEMOC

Method

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

GEMOC

Method

Used to define possible entry methods of Sequential engine.

@InitializeModel

GEMOC

Method

Used to define a method that initializes the model before starting an engine (Sequential or Concurrent).

@InitializeModel
def public void initialize(List<String> args){}

2.4. Experimental annotations

Annotations that have been coded but need further development in order to be fully usable. Help welcome :-)

2.4.1. Structure Modelling annotations

AnnotationApplies toShort description

@NotAspectProperty

Field

 

@Composition

Field

 

@Containment

Field

 

@Opposite

Field

 

2.4.2. Design by Contract annotations

This annotation set allows to do some design by contract programming.

Know limitation : there is no way to prevent the contracts from doing some side effects on the model.

AnnotationApplies toShort description

@Contracted

Class

Indicates that the class will define contracts.

@Inv

Method

Indicates that this contract must be true for all instances of the base class.

@Pre

Method

 

@Post

Method

 

Chapter 3. Language Reference

3.1. Basic

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.

blog base diagram

Figure 3.1. Metamodel used as base for the examples)


3.1.1. Creating an aspect on a class

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 1

import blogsite.Blog

@Aspect(className=Blog) 2
class BlogAspect {
    def void display (){
         prinln("I am BlogAspect")
    }
}

1

Import the annotation processor for Aspect

2

Add an aspect on top of class Blog

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 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 2
         _self.title =  newTitle 3
    }
}

1

this attrbute exists only in the aspect

2

in aspect, attribute defined on aspect is accessed via _self

3

in aspect, attribute defined on the base class is accessed via _self

Warning

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.

3.1.2. Using aspect from another k3/xTend program

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

class Main {
    def static void main(){
      val Blog b1 = new Blog
      b1.display() 2
    }
}

1

This import static extension <myPkg.AspectName>.* allows to make visible the attributes and operations defined on BlogAspect.

2

method display() is visible thanks to the import static extension BlogAspect.*

3.1.2.1. Disambiguisation

If several operations or attributes have similar names, xTend may not find the expected feature. In that case you need to disambiguise the call by accessing the underlying helper class.

Note

To have more details about the precise mecanism of this helper class please refer to the chapter at the end of this document.

@Aspect(className=Blog)
class BlogAspect2 {
    int title = 1 1
}
import static extension blogsite.aspects.BlogAspect2.*

class Main {
    def static void main(){
      val Blog b1 = new Blog
      println( b1.title) 1
      println( BlogAspect2.title(b1) 2
    }
}

1

Might be ambiguous because it reuse the name of an attribute on the base class

1

returns the attribute title of the base object b1.

2

return the attribute title of the companion object for the aspect BlogAspect2.

Tip

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.

3.1.3. Aspect inheritance (simple inheritance)

An Aspect class can inherit fields and methods from another class. The simple inheritance is acheived using the extends keyword from Xtend.

Important

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{ 1

    @OverrideAspectMethod 2
    def String display (){
        returns _self.title + " by " + _self.author
    }
}

1

PostAspect inherits from HasAuthorAspect

2

the OverrideAspectMethod annotionation is required to indicates that the method already exists in the parent aspect class and must be overriden.

3.1.4. Calling a super operation

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() 1
    }
}

1

call the parent display() method

3.2. Advanced

3.2.1. Extending an aspect (multi inheritance)

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.

multi inheritance base diagram

Figure 3.2. Metamodel used as base for the multi inheritance example


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) 1
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] ) 2
class PostAspect extends Parent1Aspect{ 3
   def void someMethod(){
      _self.someParent1Method()
      _self.someParent2Method()
      _self.someParent3Method()
   }
}

1

each parent defines its own aspects.

2

secondary aspect inheritances are added thanks to the with attribute.

3

primary aspect inheritance is declared using the extends keyword like a single inheritance.

multi inheritance base and aspects diagram

Figure 3.3. Multi inheritance example with aspects


Tip

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

Important

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.

3.3. Replacing a method of the base class

K3 uses the @ReplaceAspectMethod annotation to replace a method that is defined in the base class by a method in the aspect.

Chapter 4. UI User Guide

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.

4.1. New K3 project wizard

As K3 can be used in several situations, the wizard allows to create project for the various configurations:

  1. Eclipse plugin
  2. Standard java project with embedded libraries (compiled with eclipse)
  3. Maven java project

and using a modeling framework or not.

new k3 project screenshot

4.1.1. Modeling options

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.

4.1.2. Dependency management option

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.

4.1.3. Templates

when clicking on next in the first page your can acces to the templates

new k3 project templates screenshot

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.

Note

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.

4.2. New Example wizard

K3 comes with a set of running examples that you can use as a starting point.

Retreive them from Eclipse: File > New > Example…​ > then look for the K3 category.

Chapter 5. Implementation details

This section presents how K3 is implemented and which compiling scheme it follows.

Note

Most K3 users would probably not need to read this section. However, understanding this compilation scheme should help integrating K3 code in a larger applications.

Basically, K3 works by combining 2 mains Xtend mecanisms :

5.1. Import static extension with wildcard

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.

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

max(f1,f2) 2

f1.max(f2) 3

1

is the classic way to access the static method (with only a import java.lang.Math )

2

is allowed by java with an import java.lang.Math.* or import java.lang.Math.max

3

is allowed by Xtend’s extension. This pointed notation gives the feeling to call the max method as if it was defined on double.

K3 use this mecanism extensively.

5.2. K3 Active annotations

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:

  • it creates a class with static methods. It acts as the public interface for the aspect.
  • It creates an *AspectProperties class that is the companion object that will store the attributes defined by aspect.
  • It creates an *AspectContext class that defines the mapping between the base object and the companion object.

Tip

The result of the compilation of Xtend and K3 annotations in plain java is visible in the xtend-gen folder.

5.2.1. K3 Active annotations compilation example

Let’s consider the following K3 code that adds a String attribute to the 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"

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.

Note

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

}