Language Workbench Competition 2011

The objective of the language workbench competition is to compare the strengths and weaknesses of various language workbenches - like OOMEGA - based on solutions for a predefined set of cases. The idea for LWC2011 originates from a group of people who met in the preparations for, and during CodeGeneration 2010: Markus Völter, Eelco Visser, Steven Kelly, Angelo Hulshout, Jos Warmer, Pedro J. Molina, Bernhard Merkle and Karsten Thoms. The original case description can be found here: http://www.languageworkbenches.net/LWCTask-1.0.pdf. Here's a link to the competition's website: http://www.languageworkbenches.net.

OOMEGA supports this initiative and participates in the contest. In the following the challenges of the case are documented alongside our solution description.

Installation

Please install OOMEGA and the LWC2011 DSLs in order to test our solution.

How to install OOMEGA

OOMEGA provides plugins for the Eclipse IDE.

First you need to install the current version of Eclipse.

  • Go to the Eclipse download page and download Eclipse Classic 3.6.2 (Helios).
  • Unpack the downloaded archive file in a directory of your choice. The archive will be extracted to a new directory named eclipse. Before unpacking the archive, please ensure that there is no subdirectory named eclipse yet!
  • Start Eclipse by running the eclipse executable in the newly-created eclipse directory.

These are the steps to install OOMEGA.

How to install LWC2011

These are the steps to install the LWC2011 DSLs.

Moreover check out the sources from metaMODELS.org's SVN repository.

Now you can switch to the "OOMEGA" perspective and explore the model repository "org.metamodels.lwc2011.exemplarymodel".

Finally install the project "OOMEGA-Libraries" in your Eclipse workspace by choosing "File >> New >> Example..." and then "OOMEGA >> Libraries". Last but not least it's necessary to adjust the paths in "OOMEGA-Libraries/etc/build.global.properties". Now you can execute the Ant build scripts for code generation and model-to-model transformation in the projects "org.metamodels.lwc2011.m2t" and "org.metamodels.lwc2011.m2m".

Introduction

LWC2011 iteratively imposes requirements in different phases and tasks: the resulting domain-specific languages evolve over time. For example the Entity DSL is introduced in task 0.1, but will be enhanced in task 0.3 and 1.4.

In the meantime we have implemented all functional aspects of the LWC case, thus our Entity DSL implementation covers the whole functionality that is requested in different phases. In order to avoid confusion we are not going to maintain and explain several versions of one and the same DSL, rather we will outline the final solution that covers all requirements. Anyway the documentation will highlight the relevant aspects alongside the tasks of the case description.

One important requirement of LWC2011 is to showcase language modularity and composition. This is exactly what we’ve done. As a result we’ve designed five DSLs that are interrelated:

  • LWC2011_Primitive
    contains four primitive types that are reused in several DSLs, e.g. in LWC2011_Domain and LWC2011_EntityRelationship.
  • LWC2011_Expression
    implements a simple expression language that will be relevant to calculate the age of a person as requested in task 1.5. The concepts of the expression language could have been integrated in LWC2011_Domain, but we wanted to highlight the capability to implement an expression language once and to integrate it in various DSLs.
  • LWC2011_Domain
    is the implementation of the Entity DSL on the basis of primitive types and an expression language defined in other DSLs as explained above.
  • LWC2011_Instance
    is another language to define instances of entities including assignment of values to properties. This DSL is interrelated to LWC2011_Domain.
  • LWC2011_EntityRelationship
    is a simple DSL for designing Entity Relationship models. This metamodel is again based on the primitive types defined in LWC2011_Primitive.
No 3GL code / ~350 lines of code

Finally we’d like to emphasize that our final solution does not contain a single line of 3GL code for designing the language, implementing constraints or setting up text editors. In fact you do only specify abstract and concrete syntaxes of your DSLs. In total you need to code less than 350 lines of code with OOMEGA’s metamodelling language M2L in order to realize the whole functionality of the LWC2011 case.

Here's a screenshot of the OOMEGA language workbench that shows the text editor for editing a domain model:

Let’s start to explore the phases of LWC2011:

Phase 0 - Basics

This phase is intended to demonstrate basic language design, including IDE support (code completion, syntax coloring, outlines, etc).

Task 0.1 - Simple (structural) DSL without any fancy expression language or such

Challenge: Build a simple data definition language to define entities with properties. Properties have a name and a type. It should be possible to use primitive types for properties, as well as other Entities.

entity Person {
  string name
  string firstname
  date bithdate
  Car ownedCar
}

entity Car {
  string make
  string model
}

Solution:

As mentioned in the introduction, our solution distinguishes between three language modules – namely LWC2011_Primitive, LWC2011_Expression and LWC2011_Domain – that form the Entity DSL altogether. In task 0.1 we will introduce the metamodel LWC2011_Primitive and parts of LWC2011_Domain.

The language module LWC2011_Primitive

The language module LWC2011_Primitive is pretty simple: the abstract syntax contains only five concepts.

  • Primitive
    is an abstract attribute concept. Every primitive type refines this concept.
  • P_Boolean
    represents the primitive type "Boolean".
  • P_Date
    represents the primitive type "Date".
  • P_Natural
    represents the primitive type "Natural".
  • P_String
    P_String represents the primitive type "String".

Abstract syntax

Let’s explain the concepts Primitive and P_Date in more detail. The other concepts work similarly.

Here’s the M2L statement for the concept Primitive.

[Primitive!!] refines AttributeObject ::> ;

What does that mean?

  • [Primitive!!]
    means that the concept is called “Primitive”; moreover it is an abstract concept (expressed by the squared brackets) and an attribute concept (expressed by the two exclamation marks).
  • refines AttributeObject
    means that the concept refines the concept “AttributeObject”, which is the common base class for all attribute concepts.
  • ::>
    means that the concept is not complete, i.e. sub concepts can add additional properties.
  • ;
    is the delimiter of any concept definition.

And now the abstract syntax for the concept P_Date:

P_Date!! refines Primitive ::=
  &javaMapping[0..1](Singleton) : C:String := {{ "java.util.Date" }},
  &xmlMapping[0..1](Singleton) : C:String := {{ Date }} ;

It basically says that P_Date is a concrete attribute concept (no squared brackets, again two exclamation marks) that refines the concept Primitive. Moreover there are two inferred properties (javaMapping, xmlMapping) of type String that in each case return a static string value. Those inferred properties are actually not necessary for language design; anyway they will be used later on when code is generated.

Concrete syntax

There are two concrete syntaxes defined for the concept P_Date: the concrete syntax LWC is the default syntax for the whole case study, whereas the syntax SQL is relevant when primitive types are used in the context of Entity Relationship models.

textual concrete syntax LWC {
  syntaxpackage org {
    syntaxpackage metamodels {
      syntaxpackage lwc2011 {
        syntaxpackage primitive {
          <default> P_Date : "date" ;
        }
      }
    }
  }
}

textual concrete syntax SQL {
  syntaxpackage org {
    syntaxpackage metamodels {
      syntaxpackage lwc2011 {
        syntaxpackage primitive {
          P_Date : "DATE" ;
        }
      }
    }
  }
}

The whole metamodel definition in M2L

Finally let’s show the whole language module LWC2011_Primitive. It is represented in M2L – which is OOMEGA’s metamodelling language.

metamodel LWC2011_Primitive
  based on 'Modelling Languages'.'Language Engineering'.'OOMEGA Core' {
  abstract syntax {
    metapackage org {
      metapackage metamodels {
        metapackage lwc2011 {
          metapackage primitive {
            P_Boolean!! refines Primitive ::=
              &javaMapping[0..1](Singleton) : C:String := {{ "boolean" }},
              &xmlMapping[0..1](Singleton) : C:String := {{ Boolean }} ;
            P_Date!! refines Primitive ::=
              &javaMapping[0..1](Singleton) : C:String := {{ "java.util.Date" }},
              &xmlMapping[0..1](Singleton) : C:String := {{ Date }} ;
            P_Natural!! refines Primitive ::=
              &javaMapping[0..1](Singleton) : C:String := {{ int }},
              &xmlMapping[0..1](Singleton) : C:String := {{ Integer }} ;
            P_String!! refines Primitive ::=
              &javaMapping[0..1](Singleton) : C:String := {{ String }},
              &xmlMapping[0..1](Singleton) : C:String := {{ String }} ;
            [Primitive!!] refines AttributeObject ::> ;
          }
        }
      }
    }
  }
  textual concrete syntax LWC {
    syntaxpackage org {
      syntaxpackage metamodels {
        syntaxpackage lwc2011 {
          syntaxpackage primitive {
            <default> P_Natural : "natural" ;
            <default> P_Date : "date" ;
            <default> P_Boolean : "boolean" ;
            <default> P_String : "string" ;
          }
        }
      }
    }
  }
  textual concrete syntax SQL {
    syntaxpackage org {
      syntaxpackage metamodels {
        syntaxpackage lwc2011 {
          syntaxpackage primitive {
            P_Date : "DATE" ;
            P_Boolean : "BOOLEAN" ;
            P_Natural : "INT" ;
            P_String : "CHAR" ;
          }
        }
      }
    }
  }
}

The language module LWC2011_Domain

Now let’s go on with relevant parts of the metamodel LWC2011_Domain.

  • DomainModel
    is the entry point of the Entity DSL: it refines the concept FolderEntry and thus domain models can be filed within folders. A DomainModel contains a set of packages which in turn contain a set of entities. The concept Package will be introduced later as namespaces are requested not until task 1.4. For now you can imagine that a domain model contains a set of entities.
  • Entity
    refines the concept Named – thus it inherits the property “name”. Moreover an entity contains a set of properties.
  • Prop
    is the common base for the concepts Attribute and Association. It also refines the concept Named – thus a property has a name.
  • Attribute
    refines the concept Prop. An attribute has a primitive type. Please note that the concept Primitive of the metamodel LWC2011_Primitive is referenced here.
  • Association
    refines the concept Prop. An association has also a type, but this time the concept Entity is referenced.

Abstract syntax

Let’s explain the abstract syntax of those concepts in more detail:

DomainModel
DomainModel! refines FolderEntry ::=
  packages[0..*](Set) <-> belongsTo : C:Package,
  &entities[0..*](Set) : C:Entity := P:packages.P:entities ;
  • DomainModel! refines FolderEntry ::=
    means that the concept is called “DomainModel”; it is concrete (no squared brackets), a weak entity concept (expressed by one exclamation mark) and complete. Moreover it refines the concept “FolderEntry”. Please note: weak entities are automatically deleted by the database as soon as they are not referenced by a composite object.
  • packages[0..*](Set) <-> belongsTo : C:Package
    defines a composition to a set of packages. It is a bidirectional association and the opposite property within the concept “Package” is called “belongsTo”.
  • &entities[0..*](Set) : C:Entity := P:packages.P:entities
    defines an inferred property – thus the value of the property is calculated at runtime and not persisted in the database. The property is called “entities” and returns a set of “Entity” objects. The implementation of the property is a simple Edge Algebra statement: P:packages.P:entities navigates over two properties, namely “packages” in the concept “DomainModel” and “entities” in the concept “Package”. Please note that this navigation passes a set-based property.
Entity
Entity! refines Named ::>
  &belongsTo[0..1](Singleton) <-> entities : C:Package,
  &usedBy[0..*](Set) <-> type : C:Association,
  properties[0..*](Set) <-> belongsTo : C:Prop,
  &attributes[0..*](Set) : C:Attribute := P:properties projectOn C:org.metamodels.lwc2011.domain.Attribute,
  &associations[0..*](Set) : C:Association := P:properties projectOn C:org.metamodels.lwc2011.domain.Association,
  &context[0..1](Singleton) : C:EntityObject := self ;
  • Entity! refines Named ::>
    is a weak entity concept and refines the concept “Named”.
  • &belongsTo[0..1](Singleton) <-> entities : C:Package
    is the opposite property of the composition “entities” within the concept “Package”.
  • &usedBy[0..*](Set) <-> type : C:Association
    is the opposite side of the “type” property within the concept “Association”. An Entity might be the type of several associations.
  • properties[0..*](Set) <-> belongsTo : C:Prop
    defines the composition to a set of properties. A property might be an attribute or an association.
  • &attributes[0..*](Set) : C:Attribute := P:properties projectOn C:org.metamodels.lwc2011.domain.Attribute
    defines an inferred property that is again calculated via an Edge Algebra statement: it’s a projection on the property “properties” and all objects of type “Attribute”. As a result all properties of type “Attribute” are returned.
  • &associations[0..*](Set) : C:Association := P:properties projectOn C:org.metamodels.lwc2011.domain.Association
    is the equivalent inferred property definition for returning all associations of this entity.
  • &context[0..1](Singleton) : C:EntityObject := self
    is another inferred property that will be used within a context-sensitive domain specification. This will be relevant for task 1.5. The Edge Algebra statement “self” simply returns this object, i.e. the respective entity.
Prop
[Prop!] refines Named ::>
  &belongsTo[0..1](Singleton) <-> properties : C:Entity ;

The concept “Prop” is an abstract weak entity concept and refines “Named”. Thus each property has a name. There’s one association defined which is called “belongsTo”: this is the opposite property of the composition “properties” within the concept “Entity”.

Attribute
Attribute! refines Prop ::>
  type[1..1](Singleton) : C:Primitive ;

“Attribute” is a concrete weak entity concept and refines “Prop”. There is one additional property defined: “type” defines the primitive type of this attribute. Please note that the concept “Primitive” is referenced here, which is defined within the metamodel LWC2011_Primitive.

Association
Association! refines Prop ::>
  &type[1..1](Singleton) <-> usedBy : C:Entity projectOn (P:belongsTo.P:belongsTo.P:entities) union (P:belongsTo.P:belongsTo.P:importedPackages.P:entities),
  &includedContext[0..*](Set) : C:EntityObject := P:belongsTo.P:belongsTo.P:importedPackages ;

“Association” is a concrete weak entity concept and refines “Prop”, too. The additional property “type” defines the entity type of this association. Please note that the property “type” has a context-sensitive domain defined by an Edge Algebra statement. This and the inferred property “includedContext” will be explained in task 1.4 of the LWC2011 case.

Concrete syntax

Apart from the outlined abstract syntax, you must create concrete syntax definitions for the concepts “DomainModel”, “Entity”, “Attribute” and “Association”. You do not need to define a concrete syntax for the concept “Prop”, because it is an abstract concept.

There are two concrete syntaxes defined – namely LWC and DIRECTORY. The concrete syntax LWC is the default syntax for the whole case study, whereas the syntax DIRECTORY is only relevant for the concept “DomainModel”. This is a specialty for integrating custom folder entries into OOMEGA’s standard DIRECTORY syntax.

textual concrete syntax DIRECTORY {
  syntaxpackage org {
    syntaxpackage metamodels {
      syntaxpackage lwc2011 {
        syntaxpackage domain {
          <helper> DomainModel : "domain" _ "model" _ <org.oomega.base.Named> ;
        }
      }
    }
  }
}

textual concrete syntax LWC {
  syntaxpackage org {
    syntaxpackage metamodels {
      syntaxpackage lwc2011 {
        syntaxpackage domain {
          <default> DomainModel : "domain" _ "model" _ <org.oomega.base.Named> _ "{" (nl | P:packages) nl "}" ;
          <default> Entity : "entity" _ <org.oomega.base.Named> _ "{" (nl | P:properties) nl "}" ;
          <default> Attribute : (!P:type) _ <org.oomega.base.Named> ;
          <default> Association : "ref" _ (&P:type) _ <org.oomega.base.Named> ;
        }
      }
    }
  }
}
  • The DIRECTORY syntax of the concept “DomainModel” is a helper syntax. Hence the syntax tab “DIRECTORY” will not be shown when opening the OOMEGA editor for a DomainModel object. The syntax definition says that a DomainModel object starts with the keywords “domain” plus space plus “model” plus space. Then the DIRECTORY syntax of the concept “Named” is included. The DIRECTORY syntax will be finally used to create a domain model within a folder.
  • The LWC syntax is the default syntax. Thus it must be ensured that the whole information that the model is able to carry due to its abstract syntax definition is coded. Let’s have a closer look to the concrete syntax of the DomainModel object. This syntax does not only code the name of the domain model, rather the contained packages are considered as well. (nl | P:packages) means that a new line will be inserted at the beginning of each package declaration.

The whole metamodel definition in M2L

Finally let's show all relevant parts of the language module LWC2011_Domain.

metamodel LWC2011_Domain
  based on LWC2011_Expression, LWC2011_Primitive, 'Modelling Languages'.'Language Engineering'.'OOMEGA Core' {
  abstract syntax {
    metapackage org {
      metapackage metamodels {
        metapackage lwc2011 {
          metapackage "domain" {
            DomainModel! refines FolderEntry ::=
              packages[0..*](Set) <-> belongsTo : C:Package,
              &entities[0..*](Set) : C:Entity := P:packages.P:entities ;
            Entity! refines Named ::>
              &belongsTo[0..1](Singleton) <-> entities : C:Package,
              &usedBy[0..*](Set) <-> type : C:Association,
              properties[0..*](Set) <-> belongsTo : C:Prop,
              &attributes[0..*](Set) : C:Attribute := P:properties projectOn C:org.metamodels.lwc2011.domain.Attribute,
              &associations[0..*](Set) : C:Association := P:properties projectOn C:org.metamodels.lwc2011.domain.Association,
              &context[0..1](Singleton) : C:EntityObject := self ;
            [Prop!] refines Named ::>
              &belongsTo[0..1](Singleton) <-> properties : C:Entity ;
            Attribute! refines Prop ::>
              type[1..1](Singleton) : C:Primitive ;
            Association! refines Prop ::>
              &type[1..1](Singleton) <-> usedBy : C:Entity projectOn (P:belongsTo.P:belongsTo.P:entities) union (P:belongsTo.P:belongsTo.P:importedPackages.P:entities),
              &includedContext[0..*](Set) : C:EntityObject := P:belongsTo.P:belongsTo.P:importedPackages ;
          }
        }
      }
    }
  }
  textual concrete syntax DIRECTORY {
    syntaxpackage org {
      syntaxpackage metamodels {
        syntaxpackage lwc2011 {
          syntaxpackage domain {
            <helper> DomainModel : "domain" _ "model" _ <org.oomega.base.Named> ;
          }
        }
      }
    }
  }
  textual concrete syntax LWC {
    syntaxpackage org {
      syntaxpackage metamodels {
        syntaxpackage lwc2011 {
          syntaxpackage domain {
            <default> DomainModel : "domain" _ "model" _ <org.oomega.base.Named> _ "{" (nl | P:packages) nl "}" ;
            <default> Entity : "entity" _ <org.oomega.base.Named> _ "{" (nl | P:properties) nl "}" ;
            <default> Attribute : (!P:type) _ <org.oomega.base.Named> ;
            <default> Association : "ref" _ (&P:type) _ <org.oomega.base.Named> ;
          }
        }
      }
    }
  }
}

Screenshots of the language workbench

Task 0.2 - Code generation to GPL such as Java, C#, C++ or XML

Challenge: Generate Java Beans (or some equivalent data structure in C#, Scala, etc.) with setters, getters and fields for the properties.

Solution:

Code can be generated with OOMEGA's generator or Eclipse Xpand. Here we'll demonstrate the usage of Xpand. Let's generate a Java bean for each entity with setters, getters and fields for the properties. Basically this can be done in three simple steps:

  • Code a Xpand template
  • Create a workflow
  • Execute the workflow within an Ant script

Let's show the Xpand template first:

«IMPORT org::oomega::m2t::mwe::plugin»
«IMPORT org::oomega::base»
«IMPORT org::metamodels::lwc2011::primitive»
«IMPORT org::metamodels::lwc2011::domain»

«DEFINE Root FOR OOMEGAModel»
«EXPAND Root FOREACH entities»
«ENDDEFINE»

«DEFINE Root FOR Entity»
«FILE belongsTo.path+"/"+name+".java"»package «belongsTo.name»;

«EXPAND Import FOREACH belongsTo.importedPackages»

public class «name» {
  «EXPAND Declaration FOREACH properties»
  «EXPAND Getter FOREACH properties»
  «EXPAND Setter FOREACH properties»
}
«ENDFILE»
«ENDDEFINE»

«DEFINE Import FOR org::metamodels::lwc2011::domain::Package»import «name».*;«ENDDEFINE»

«DEFINE Declaration FOR org::metamodels::lwc2011::domain::Derived»«ENDDEFINE»
«DEFINE Getter FOR org::metamodels::lwc2011::domain::Derived»«ENDDEFINE»
«DEFINE Setter FOR org::metamodels::lwc2011::domain::Derived»«ENDDEFINE»

«DEFINE Declaration FOR org::metamodels::lwc2011::domain::Prop»
  private «EXPAND JavaName FOR type» «name»;«ENDDEFINE»

«DEFINE Getter FOR org::metamodels::lwc2011::domain::Prop»
  public «EXPAND JavaName FOR type» get«name.toFirstUpper()»() {
    return «name»;
  }«ENDDEFINE»

«DEFINE Setter FOR org::metamodels::lwc2011::domain::Prop»
  public void set«name.toFirstUpper()»(«EXPAND JavaName FOR type» «name») {
    this.«name» = «name»;
  }«ENDDEFINE»

«DEFINE JavaName FOR Entity»«name»«ENDDEFINE»
«DEFINE JavaName FOR Primitive»«javaMapping»«ENDDEFINE»

«DEFINE Root FOR EntityObject»
«ENDDEFINE»

The workflow definition looks as follows:

<workflow>
  <!-- component is used to instantiate the model. The model is put in the modelSlot 'model'. -->
  <component class="org.oomega.m2t.mwe.plugin.TextEOCWorkflowComponent">
    <metamodelFile value="../org.metamodels.lwc2011.exemplarymodel/.metamodel.sdf" />
    <modelFile value="../org.metamodels.lwc2011.exemplarymodel/.model.txt" />
    <modelSlot value="model" />
  </component>

  <!-- run the Java generator -->
  <component id="generator" class="org.eclipse.xpand2.Generator" skipOnErrors="true">

    <!-- the metamodel is JavaBeans based -->
    <metaModel class="org.eclipse.xtend.type.impl.java.JavaMetaModel">
      <typeStrategy class="org.eclipse.xtend.type.impl.java.beans.JavaBeansStrategy"/>
    </metaModel>

    <fileEncoding value="UTF-8" />

    <!-- call Define Root in template Java.xpt for our model put in the modelslot 'model' -->
    <expand value="Java::Root FOR model" />

    <outlet path="build/temp/generate" />
  </component>
</workflow>

Please notice that OOMEGA offers various workflow components to load a model. In this case the TextEOCWorkflowComponent has been used in order to load the model from our text-based model repository.

Finally you need to execute the workflow within your Ant script.

<workflow file="src/xpand/workflow.mwe" fork="true">
  <classpath>
    <pathelement path="src/xpand" />
    <pathelement path="src/xpand/xpt" />
    <fileset dir="../org.metamodels.${metamodel.name}.plugin/lib" includes="${metamodel.name}.jar" />
    <fileset dir="${oomega.home}/lib" includes="oomega-core.jar" />
    <fileset dir="${eclise.home}/plugins" includes="com.ibm.icu_*.jar" />
    <fileset dir="${eclipse.plugins.home}"
             includes="org.antlr.runtime_*.jar,
                       org.apache.commons.cli_*.jar,
                       org.apache.commons.logging_*.jar,
                       org.eclipse.emf.common_*.jar,
                       org.eclipse.emf.ecore_*.jar,
                       org.eclipse.emf.mwe.core_1.*.jar,
                       org.eclipse.emf.mwe2.runtime_1.*.jar,
                       org.eclipse.xpand_1.*.jar,
                       org.eclipse.xtend_1.*.jar,
                       org.oomega.m2t.mwe.plugin_*.jar"
    />				
  </classpath>
</workflow>

This is a simple domain model as shown in the LWC2011 case description.

domain model "0.1-domain" {
  package "org.metamodels.lwc2011" {
    entity Person {
      string name
      string firstname
      date birthdate
      ref Car ownedCar
    }
    entity Car {
      string make
      string model
    }
  }
}

According to this model, Xpand will generate two files: Person.java and Car.java.

  • Person.java
    package org.metamodels.lwc2011;
    
    public class Person {
      
      private String firstname;
      private String name;
      private java.util.Date birthdate;
      private Car ownedCar;
    	
      public String getFirstname() {
        return firstname;
      }
      public String getName() {
        return name;
      }
      public java.util.Date getBirthdate() {
        return birthdate;
      }
      public Car getOwnedCar() {
        return ownedCar;
      }
      
      public void setFirstname(String firstname) {
        this.firstname = firstname;
      }
      public void setName(String name) {
        this.name = name;
      }
      public void setBirthdate(java.util.Date birthdate) {
        this.birthdate = birthdate;
      }
      public void setOwnedCar(Car ownedCar) {
        this.ownedCar = ownedCar;
      }
    }
    
  • Car.java
    package org.metamodels.lwc2011;
    
    public class Car {
      
      private String make;
      private String model;
      
      public String getMake() {
        return make;
      }
      public String getModel() {
        return model;
      }
      
      public void setMake(String make) {
        this.make = make;
      }
      public void setModel(String model) {
        this.model = model;
      }
    }
    

Task 0.3 - Simple constraint checks such as name-uniqueness

Challenge: For example, check the name uniqueness of the properties in the entities.

Solution:

Actually, the name uniqueness of the properties in the entities is already built into the example of the previous chapters. You do not have to change the metamodel to achieve that kind of name-uniqueness. Anyway – let’s explain why this is already the case.

Please notice that the concept Prop refines the concept Named. Thus Prop inherits the attribute name from its super concept Named.

[Prop!] refines Named ::>
  &belongsTo[0..1](Singleton) <-> properties : C:Entity

[Named] refines EntityObject ::>
  (PK)name[1..1](Singleton) : C:String,
  (K)alternativeName[0..*](Set) : C:String

Please notice the (PK) statement at the attribute name. This statement tags the attribute name to serve as a primary local key.

Primary local keys must be unique within the set of all sister nodes in the composition tree. This means in our example: an Entity mustn’t contain multiple properties with identical names. On the other hand it’s possible to create two Prop objects with identical names as long as they are not part of the same Entity.

Task 0.4 - Show how to break down a (large) model into several parts, while still cross-referencing between the parts

Challenge: For example, put the Car and Person entities into different files, while still having the Person -> Car reference work.

Solution:

That’s a very interesting requirement: and we’ve got an individual solution to this problem. First let’s discuss the problem in-depth. I think the problem can be broken down into two separate requirements: view and persistence.

  • View: If you’ve got a large model you want to be able to view only parts of it. E.g. you do not want to open a single file with thousands of entities; rather you’d like to pick a specific entity and edit/view the contents of this entity solely.
  • Persistence: A large model shouldn’t be stored in a single text file for several reasons. To mention just one of those: you don’t want to wait until the parser has read millions of lines before being able to view/edit the contents of a single entity.

If text is the storage back-end for models, it’s reasonable to partition the model into several text files in order to solve both: the view and persistence aspect. But if the storage back-end for models is a database, the persistence aspect is already solved in a very professional way: the overall model is partitioned into a huge number of very small items that can be loaded from and stored to the database individually. An object database perfectly covers the persistence aspect of the aforementioned requirement.

OOMEGA’s storage back-end for models is a database. The persistence aspect is solved. Now let’s have a look at the view aspect.

As already mentioned a model is partitioned into many objects; those objects are interconnected/associated so you can think of a model in terms of an object graph. Typically each object of that graph is uniquely located within an object tree and there may be cross-references to other objects in that tree. Please have a look at the metamodel defined in chapter 0.1. The object hierarchy of your model is implicitly defined by using compositions and attributes whereas associations lead to cross-references in your model.

You can browse the composition tree in OOMEGA’s Model Navigator. Moreover you can open the editor for each and every entity in your database by double-clicking on the respective item in the tree. Then you will see only that part of the model you are actually interested in. So you might pick an entity in the model navigator and edit its contents in the respective editor.

Models are partioned by default

As a result your models are – by default – partitioned into little pieces. You can always decide which object to view or edit. And finally, you do not have to decide how to partition your model up-front, i.e. when you design your domain-specific language.

Let’s explain the last feature: Think of a little more complicated DSL where entities are arranged into packages. With OOMEGA it’s up to the user whether to pick the package and to view/edit the package with all contained entities at once or to open a single entity and edit its contents solely. Other solutions might force the DSL designer to take a preliminary decision where to partition the model. The designer might have chosen to store each entity in a separate file. The modeller now has no chance to view/edit the entities of a package all at once.

Phase 1 - Advanced

This phase demonstrates advanced features not necessarily available to the same extent in every language workbench.

Task 1.1 - Show the integration of several languages

Challenge: Define a second language to define instances of the entities, including assignment of values to the properties. This should ideally really be a second language that integrates with the first one, not just "more syntax" in the same grammar. We want to showcase language modularity and composition here.

Person p = {
  name = "Voelter"
  firstname = "Markus"
  birthdate = 14.02.1927
  ownedCar = c
}

Car c = {
  make = "VW"
  model = "Touran"
}

Solution:

As mentioned in the introduction, we’ve created a separate language module – namely LWC2011_Instance – to define instances of entities, including assignment of values to properties. Let’s have a closer look to that metamodel.

The language module LWC2011_Instance

The language module LWC2011_Instance contains the following concepts:

  • InstanceModel
    is the entry point of the Instance DSL: it refines the concept FolderEntry and thus instance models can be filed within folders. An InstanceModel contains a set of instances.
  • Instance
    refines the concept Named – thus it inherits the property “name”. Moreover an instance refers to its entity type and contains a set of property/value pairs.
  • PropertyValue
    represents a property/value pair.
  • Value
    represents a concrete value for a property of an instance. It is an abstract attribute concept; every primitive value refines this concept.
  • V_Boolean, V_Date, V_Natural and V_String
    those concepts represent the respective primitive values; they refine the concept Value.
  • V_Ref
    refines the concept Value as well. It represents a reference to another instance and is used in the context of associations.

Abstract syntax

Let’s explain the abstract syntax of those concepts in more detail:

InstanceModel
InstanceModel! refines FolderEntry ::=
  &basedOn[0..1](Singleton) : C:DomainModel,
  instances[0..*](Set) <-> belongsTo : C:Instance ;
  • InstanceModel! refines FolderEntry ::=
    is a weak entity concept and refines the concept “FolderEntry”.
  • &basedOn[0..1](Singleton) : C:DomainModel
    defines an association to a domain model. Please note that the concept “DomainModel” is referenced here, which is defined within the metamodel LWC2011_Domain.
  • instances[0..*](Set) <-> belongsTo : C:Instance
    defines the composition to a set of instances.
Instance
Instance! refines Named ::>
  &belongsTo[0..1](Singleton) <-> instances : C:InstanceModel,
  &entity[0..1](Singleton) : C:Entity projectOn P:belongsTo.P:basedOn.P:entities,
  propertyValues[0..*](Set) <-> belongsTo : C:PropertyValue,
  &includedContext[0..*](Set) : C:EntityObject := P:belongsTo.P:basedOn ;
  • Instance! refines Named ::>
    is a weak entity concept and refines the concept Named.
  • &belongsTo[0..1](Singleton) <-> instances : C:InstanceModel
    is the opposite property of the composition “instances” within the concept “InstanceModel”.
  • &entity[0..1](Singleton) : C:Entity projectOn P:belongsTo.P:basedOn.P:entities
    is an association to an Entity and represents the type of the instance. Please notice the context sensitive domain C:Entity projectOn P:belongsTo.P:basedOn.P:entities. This instance belongs to an instance model and this in turn is based on a domain model. The context sensitive domain ensures that you can only create instances of entities that are contained in that domain model.
  • propertyValues[0..*](Set) <-> belongsTo : C:PropertyValue
    defines the composition to a set of property/value pairs.
  • &includedContext[0..*](Set) : C:EntityObject := P:belongsTo.P:basedOn
    is an inferred property with special semantics. An instance belongs to an instance model and this in turn is based on a domain model. The “includedContext” property ensures that this domain model is included into the context of instances: as soon as an instance references an object of that domain model (e.g. an entity), the compound key can be shortened by omitting the compound key of the domain model. Normally a reference to an entity would be coded like this: <Domain Model Name>.<Package Name>.<Entity Name>. Due to the “includedContext” property this will be automatically shortened to: <Package Name>.<Entity Name>.
PropertyValue
PropertyValue! refines EntityObject ::>
  &belongsTo[0..1](Singleton) <-> propertyValues : C:Instance,
  (PK)&property[0..1](Singleton) : C:Prop projectOn P:belongsTo.P:entity.P:properties,
  val[1..1](Singleton) : C:Value,
  &includedContext[0..1](Singleton) : C:EntityObject := P:belongsTo.P:entity
  • PropertyValue! refines EntityObject ::>
    is a weak entity concept. It represents a property/value pair.
  • &belongsTo[0..1](Singleton) <-> propertyValues : C:Instance
    is the opposite property of the composition “propertyValues” within the concept “Instance”.
  • (PK)&property[0..1](Singleton) : C:Prop projectOn P:belongsTo.P:entity.P:properties
    defines the association to a property of the domain model: this is the key of the key/value pair. Please note the context sensitive domain again: that’s the implementation of a feature request in task 1.4. Accordingly we will explain this below. Moreover the association is tagged as primary local key: thus a property assignment of one and the same property can occur only once within an instance.
  • val[1..1](Singleton) : C:Value
    defines the attribute of type Value: this is the value of the key/value pair.
  • &includedContext[0..1](Singleton) : C:EntityObject := P:belongsTo.P:entity
    is an inferred property with special semantics. The “includedContext” property ensures that the type of the instance is included into the context of property/value pairs. Thus the reference to the property can be shortened in the text editor: it’s sufficient to code the name of the property, i.e. the compound key of the entity can be omitted.
Value
[Value!!] refines AttributeObject ::> ;
  • [Value!!] refines AttributeObject ::> ;
    is an abstract attribute concept. Every primitive value refines this concept.
V_Boolean, V_Date, V_Natural and V_String
V_Boolean!! refines Value ::>
  booleanValue[1..1](Singleton) : C:Boolean ;

V_Date!! refines Value ::>
  dateValue[1..1](Singleton) : C:Date ;

V_Natural!! refines Value ::>
  naturalValue[1..1](Singleton) : C:Natural ;

V_String!! refines Value ::>
  stringValue[1..1](Singleton) : C:String ;

Let’s only explain the concept V_Date, because the others work similarly.

  • V_Date!! refines Value ::>
    is a concrete attribute concept and refines the concept Value.
  • dateValue[1..1](Singleton) : C:Date
    carries the actual value; it’s an attribute of type Date. The concept Date is defined in the package “org.oomega.base”.
V_Ref
V_Ref!! refines Value ::>
  &refValue[0..1](Singleton) : C:Instance ;
  • V_Ref!! refines Value ::>
    is a concrete attribute concept and again refines the concept Value.
  • &refValue[0..1](Singleton) : C:Instance
    carries the actual reference to another instance; it’s an association to an object of type Instance.

Concrete syntax

Apart from the outlined abstract syntax, you must create concrete syntax definitions for the concepts “InstanceModel”, “Instance”, “PropertyValue”, “V_Boolean”, “V_Date”, “V_Natural”, “V_String” and “V_Ref”. You do not need to define a concrete syntax for the concept “Value”, because it is an abstract concept.

There are two concrete syntaxes defined – namely LWC and DIRECTORY. The concrete syntax LWC is the default syntax for the whole case study, whereas the syntax DIRECTORY is only relevant for the concept “InstanceModel”. This is a specialty for integrating custom folder entries into OOMEGA’s standard DIRECTORY syntax.

textual concrete syntax DIRECTORY {
  syntaxpackage org {
    syntaxpackage metamodels {
      syntaxpackage lwc2011 {
        syntaxpackage instance {
          <helper> InstanceModel : "instance" _ "model" _ <org.oomega.base.Named> ;
        }
      }
    }
  }
}

textual concrete syntax LWC {
  syntaxpackage org {
    syntaxpackage metamodels {
      syntaxpackage lwc2011 {
        syntaxpackage instance {
          <default> InstanceModel : "instance" _ "model" _ <org.oomega.base.Named> _ ("based" _ "on" _ || &P:basedOn / "," || _) "{" (nl | P:instances) nl "}" ;
          <default> PropertyValue : (&P:property) _ "=" _ (P:val) ;
          <default> Instance : (&P:entity) _ <org.oomega.base.Named> _ "=" _ "{" (nl | P:propertyValues) nl "}" ;
          syntaxpackage values {
            <default> V_Ref : (&P:refValue) ;
            <default> V_Date : "[" (P:dateValue) "]" ;
            <default> V_String : (P:stringValue[QUOTED]) ;
            <default> V_Natural : (P:naturalValue) ;
            <default> V_Boolean : (P:booleanValue) ;
          }
        }
      }
    }
  }
}
  • The DIRECTORY syntax of the concept “InstanceModel” is a helper syntax. Hence the syntax tab “DIRECTORY” will not be shown when opening the OOMEGA editor for an InstanceModel object. The syntax definition says that an InstanceModel object starts with the keywords “instance” plus space plus “model” plus space. Then the DIRECTORY syntax of the concept “Named” is included. The DIRECTORY syntax will be finally used to create an instance model within a folder.
  • The LWC syntax is the default syntax. Thus it must be ensured that the whole information that the model is able to carry due to its abstract syntax definition is coded. Let’s have a closer look to the concrete syntax of the InstanceModel object. This syntax does not only code the name of the instance model, rather the referenced domain model and the contained instances are considered as well.

The whole metamodel definition in M2L

Finally let's show all relevant parts of the language module LWC2011_Instance.

metamodel LWC2011_Instance
  based on LWC2011_Domain, 'Modelling Languages'.'Language Engineering'.'OOMEGA Core' {
  abstract syntax {
    metapackage org {
      metapackage metamodels {
        metapackage lwc2011 {
          metapackage "instance" {
            Instance! refines Named ::>
              &belongsTo[0..1](Singleton) <-> instances : C:InstanceModel,
              &entity[0..1](Singleton) : C:Entity projectOn P:belongsTo.P:basedOn.P:entities,
              propertyValues[0..*](Set) <-> belongsTo : C:PropertyValue,
              &includedContext[0..*](Set) : C:EntityObject := P:belongsTo.P:basedOn ;
            InstanceModel! refines FolderEntry ::=
              &basedOn[0..1](Singleton) : C:DomainModel,
              instances[0..*](Set) <-> belongsTo : C:Instance ;
            PropertyValue! refines EntityObject ::>
              &belongsTo[0..1](Singleton) <-> propertyValues : C:Instance,
              (PK)&property[0..1](Singleton) : C:Prop projectOn P:belongsTo.P:entity.P:properties,
              val[1..1](Singleton) : C:Value,
              &includedContext[0..1](Singleton) : C:EntityObject := P:belongsTo.P:entity
            [Value!!] refines AttributeObject ::> ;
            metapackage values {
              V_Boolean!! refines Value ::>
                booleanValue[1..1](Singleton) : C:Boolean ;
              V_Date!! refines Value ::>
                dateValue[1..1](Singleton) : C:Date ;
              V_Natural!! refines Value ::>
                naturalValue[1..1](Singleton) : C:Natural ;
              V_Ref!! refines Value ::>
                &refValue[0..1](Singleton) : C:Instance ;
              V_String!! refines Value ::>
                stringValue[1..1](Singleton) : C:String ;
            }
          }
        }
      }
    }
  }
  textual concrete syntax DIRECTORY {
    syntaxpackage org {
      syntaxpackage metamodels {
        syntaxpackage lwc2011 {
          syntaxpackage instance {
            <helper> InstanceModel : "instance" _ "model" _ <org.oomega.base.Named> ;
          }
        }
      }
    }
  }
  textual concrete syntax LWC {
    syntaxpackage org {
      syntaxpackage metamodels {
        syntaxpackage lwc2011 {
          syntaxpackage instance {
            <default> InstanceModel : "instance" _ "model" _ <org.oomega.base.Named> _ ("based" _ "on" _ || &P:basedOn / "," || _) "{" (nl | P:instances) nl "}" ;
            <default> PropertyValue : (&P:property) _ "=" _ (P:val) ;
            <default> Instance : (&P:entity) _ <org.oomega.base.Named> _ "=" _ "{" (nl | P:propertyValues) nl "}" ;
            syntaxpackage values {
              <default> V_Ref : (&P:refValue) ;
              <default> V_Date : "[" (P:dateValue) "]" ;
              <default> V_String : (P:stringValue[QUOTED]) ;
              <default> V_Natural : (P:naturalValue) ;
              <default> V_Boolean : (P:booleanValue) ;
            }
          }
        }
      }
    }
  }
}

Screenshots of the language workbench

Task 1.2 - Demonstrate how to implement runtime type systems

Challenge: The initialization values in the instance-DSL must be of the same type as the types of the properties.

Solution:

So far you got to know several possibilities to constrain the validity of models. Let’s summarize them:

  • Primary Local Keys
    must be unique within the set of all sister nodes in the composition tree.
  • Context Sensitive Domains
    further constrain the domain of a property with respective Edge Algebra statements – beyond the traditional type safety.
  • Weak Entity Concepts and Attribute Concepts
    must be referenced by exactly one composite object.
  • Multiplicities
    define lower and upper limits for the number of referenced objects in the context of a property.
  • Abstract Concepts
    can’t be instantiated – i.e. corresponding objects must not exist in the database.

All those constraints are guaranteed by OOMEGA’s data stores. A database commit is not possible as long as one of those constraints is violated. Moreover you can define additional constraints for concepts. An additional constraint must hold for every instance of the respective concept. Again - the Edge Algebra is used to define additional constraints.

1 additional constraint for implementing the runtime type system

The implementation of the runtime type system can be achieved with only one additional constraint for the concept PropertyValue in the language module LWC2011_Instance.

Let’s show the PropertyValue concept definition including the additional constraint typeSafety at the end.

PropertyValue! refines EntityObject ::>
  &belongsTo[0..1](Singleton) <-> propertyValues : C:Instance,
  (PK)&property[0..1](Singleton) : C:Prop projectOn P:belongsTo.P:entity.P:properties,
  val[1..1](Singleton) : C:Value,
  &includedContext[0..1](Singleton) : C:EntityObject := P:belongsTo.P:entity
  where typeSafety["Type of the value must match the property's type"] = 
    (P:property consistsOf C:org.metamodels.lwc2011.domain.Association => 
      (P:val consistsOf C:org.metamodels.lwc2011.instance.values.V_Ref & 
      (P:property.P:type) = (P:val.P:refValue.P:entity))) 
    &
    (P:property consistsOf C:org.metamodels.lwc2011.domain.Attribute =>
      (((P:property.P:type) consistsOf C:org.metamodels.lwc2011.primitive.P_Boolean => P:val consistsOf C:org.metamodels.lwc2011.instance.values.V_Boolean) &
      ((P:property.P:type) consistsOf C:org.metamodels.lwc2011.primitive.P_Date => P:val consistsOf C:org.metamodels.lwc2011.instance.values.V_Date) & 
      ((P:property.P:type) consistsOf C:org.metamodels.lwc2011.primitive.P_Natural => P:val consistsOf C:org.metamodels.lwc2011.instance.values.V_Natural) & 
      ((P:property.P:type) consistsOf C:org.metamodels.lwc2011.primitive.P_String => P:val consistsOf C:org.metamodels.lwc2011.instance.values.V_String))) ;

Let’s explain some Edge Algebra operators first:

  • PropertyEdging (P:)
    is an edge. It returns all objects that are contained/referenced in a specific property of this object.
  • ConceptEdging (C:)
    is an edge. It returns all instances of a specific concept, including instances of sub concepts.
  • Navigation (.)
    is an edge. It allows navigation over multiple edges.
  • Implies (=>)
    is a predicate. “A => B” means the following: if A evaluates to “true”, B must also evaluate to “true”.
  • ConsistsOf (consistsOf)
    is a predicate. “A consistsOf B” means – in a set-based context – that A must be a subset of B. In our example B is always a ConceptEdging operator. As a result you can read all occurrences of the consistsOf operator like the Java “instanceof” operator.
  • And (&)
    is a predicate. “A & B” means that A and B must evaluate to “true”.
  • EdgeEqual (=)
    is a predicate. “A = B” returns true, if A is equal to B.

Now let’s discuss the additional constraint typeSafety. You can read it as follows:

(P:property consistsOf C:org.metamodels.lwc2011.domain.Association => 
  (P:val consistsOf C:org.metamodels.lwc2011.instance.values.V_Ref & 
  (P:property.P:type) = (P:val.P:refValue.P:entity)))

If the referenced property is an association, the value must be an instance of V_Ref and the type of the referenced property must be equal to the type of the referenced instance.

(P:property consistsOf C:org.metamodels.lwc2011.domain.Attribute =>
  (((P:property.P:type) consistsOf C:org.metamodels.lwc2011.primitive.P_Boolean => P:val consistsOf C:org.metamodels.lwc2011.instance.values.V_Boolean) &
  ((P:property.P:type) consistsOf C:org.metamodels.lwc2011.primitive.P_Date => P:val consistsOf C:org.metamodels.lwc2011.instance.values.V_Date) & 
  ((P:property.P:type) consistsOf C:org.metamodels.lwc2011.primitive.P_Natural => P:val consistsOf C:org.metamodels.lwc2011.instance.values.V_Natural) & 
  ((P:property.P:type) consistsOf C:org.metamodels.lwc2011.primitive.P_String => P:val consistsOf C:org.metamodels.lwc2011.instance.values.V_String)))

If the referenced property is an attribute, then check the following:

  • If the type of the referenced property is P_Boolean, the value must be an instance of V_Boolean.
  • If the type of the referenced property is P_Date, the value must be an instance of V_Date.
  • If the type of the referenced property is P_Natural, the value must be an instance of V_Natural.
  • If the type of the referenced property is P_String, the value must be an instance of V_String.

Please note that additional constraints have an error message that will be shown in Eclipse as long as the constraint is violated. In our example the message is: "Type of the value must match the property's type".

Task 1.3 - Show how to do a model-to-model transformation

Challenge: Define an ER-meta model (Database, Table, Column) and transform the entity model into an instance of this ER meta model.

Solution:

As mentioned in the introduction, we’ve created a separate DSL for entity relationship models, namely LWC2011_EntityRelationship.

The language module LWC2011_EntityRelationship

The language module LWC2011_EntityRelationship contains the following concepts:

  • Database
    is the entry point of the Entity Relationship DSL: it refines the concept FolderEntry and thus databases can be filed within folders. A Database contains a set of tables.
  • Table
    refines the concept Named – thus it inherits the property “name”. Moreover a table contains a set of columns and a set of foreign keys.
  • Column
    refines the concept Named – thus it inherits the property “name”. Moreover a column has a type, potentially a length restriction and a primary key flag.
  • ForeignKey
    references a column of its table (foreign key column) as well as a foreign table and a column of that foreign table.

Abstract syntax

Let’s explain the abstract syntax of those concepts in more detail:

Database
Database! refines FolderEntry ::=
  tables[0..*](Set) <-> belongsTo : C:Table ;
  • Database! refines FolderEntry ::=
    is a weak entity concept and refines the concept “FolderEntry”.
  • tables[0..*](Set) <-> belongsTo : C:Table
    defines the composition to a set of tables.
Table
Table! refines Named ::>
  &belongsTo[0..1](Singleton) <-> tables : C:Database,
  columns[1..*](Set) <-> belongsTo : C:Column,
  foreignKeys[0..*](Set) <-> belongsTo : C:ForeignKey ;
  • Table! refines Named ::>
    is a weak entity concept and refines the concept Named.
  • &belongsTo[0..1](Singleton) <-> tables : C:Database
    is the opposite property of the composition “tables” within the concept “Database”.
  • columns[1..*](Set) <-> belongsTo : C:Column
    defines the composition to a set of columns.
  • foreignKeys[0..*](Set) <-> belongsTo : C:ForeignKey
    defines the composition to a set of foreign keys.
Column
Column! refines Named ::>
  &belongsTo[0..1](Singleton) <-> columns : C:Table,
  type[1..1](Singleton) : C:Primitive,
  length[0..1](Singleton) : C:Natural,
  primaryKey[1..1](Singleton) : C:Boolean
  where lengthRestriction["Length restriction is only valid for String and Natural"] = (P:type consistsOf C:org.metamodels.lwc2011.primitive.P_Boolean v P:type consistsOf C:org.metamodels.lwc2011.primitive.P_Date) => empty? P:length,
  where primaryKeyRestriction["Exactly one primary key must be defined"] = |((P:belongsTo.P:columns) projectOn select (bool)P:primaryKey)| = 1 ;
  • Column! refines Named ::>
    is a weak entity concept and refines the concept Named.
  • &belongsTo[0..1](Singleton) <-> columns : C:Table
    is the opposite property of the composition “columns” within the concept “Table”.
  • type[1..1](Singleton) : C:Primitive
    defines the primitive type of this column. Please note that the concept “Primitive” is referenced here, which is defined within the metamodel LWC2011_Primitive.
  • length[0..1](Singleton) : C:Natural
    is a length restriction that can be applied to the primitive types String and Natural.
  • primaryKey[1..1](Singleton) : C:Boolean
    is a flag that marks a column as a primary key column.
  • where lengthRestriction["Length restriction is only valid for String and Natural"] = (P:type consistsOf C:org.metamodels.lwc2011.primitive.P_Boolean v P:type consistsOf C:org.metamodels.lwc2011.primitive.P_Date) => empty? P:length
    is an additional constraint that ensures that the length restriction is only applied to the primitive types String and Natural.
  • where primaryKeyRestriction["Exactly one primary key must be defined"] = |((P:belongsTo.P:columns) projectOn select (bool)P:primaryKey)| = 1
    is an additional constraint that ensures that there is exactly one primary key column within a table.
ForeignKey
ForeignKey! refines EntityObject ::>
  &belongsTo[0..1](Singleton) <-> foreignKeys : C:Table,
  &fkColumn[1..1](Singleton) : C:Column projectOn P:belongsTo.P:columns,
  &referencedTable[1..1](Singleton) : C:Table projectOn P:belongsTo.P:belongsTo.P:tables,
  &referencedColumn[1..1](Singleton) : C:Column projectOn P:referencedTable.P:columns ;
  • ForeignKey! refines EntityObject ::>
    is a weak entity concept.
  • &belongsTo[0..1](Singleton) <-> foreignKeys : C:Table
    is the opposite property of the composition “foreignKeys” within the concept “Table”.
  • &fkColumn[1..1](Singleton) : C:Column projectOn P:belongsTo.P:columns
    defines an association to a column and represents the foreign key column. Please notice the context sensitive domain C:Column projectOn P:belongsTo.P:columns. It ensures that a column of this table is referenced here.
  • &referencedTable[1..1](Singleton) : C:Table projectOn P:belongsTo.P:belongsTo.P:tables
    defines an association to a table and represents the referenced table. Please notice the context sensitive domain C:Table projectOn P:belongsTo.P:belongsTo.P:tables. It ensures that a table of this database is referenced here.
  • &referencedColumn[1..1](Singleton) : C:Column projectOn P:referencedTable.P:columns
    defines an association to a column and represents a column of the referenced table (typically a primary key column). Please notice the context sensitive domain C:Column projectOn P:referencedTable.P:columns. It ensures that a column of the referenced table is referenced here.

Concrete syntax

Apart from the outlined abstract syntax, you must create concrete syntax definitions for the concepts “Database”, “Table”, “Column” and “ForeignKey”.

Multiple concrete syntaxes

It is possible to define multiple concrete syntaxes for one and the same abstract syntax. Every concrete syntax definition only bridges the gap between the abstract syntax and a concrete textual representation of the model.

This time there are three concrete syntaxes defined – namely DIRECTORY, LWC and SQL.

textual concrete syntax DIRECTORY {
  syntaxpackage org {
    syntaxpackage metamodels {
      syntaxpackage lwc2011 {
        syntaxpackage entityrelationship {
          <helper> Database : "database" _ <org.oomega.base.Named> ;
        }
      }
    }
  }
}

textual concrete syntax LWC {
  syntaxpackage org {
    syntaxpackage metamodels {
      syntaxpackage lwc2011 {
        syntaxpackage entityrelationship {
          <default> Database : "database" _ <org.oomega.base.Named> _ "{" (nl | P:tables) nl "}" ;
          <default> Table : "table" _ <org.oomega.base.Named> _ "{" (nl | P:columns / ",") ("," || nl | P:foreignKeys / ",") nl "}" ;
          <default> Column : ((bool)P:primaryKey ? "pk" _ : "") (!P:type) ("(" | P:length | ")") _ <org.oomega.base.Named> ;
          <default> ForeignKey : "fk" _ (&P:fkColumn) _ "references" _ (&P:referencedTable) ("(" | &P:referencedColumn | ")") ;
        }
      }
    }
  }
}

textual concrete syntax SQL {
  syntaxpackage org {
    syntaxpackage metamodels {
      syntaxpackage lwc2011 {
        syntaxpackage entityrelationship {
          <readonly> Database : "CREATE" _ "DATABASE" _ <org.oomega.base.Named> (nl | P:tables) ;
          <readonly> Table : "CREATE" _ "TABLE" _ <org.oomega.base.Named> nl "(" (nl | P:columns / ",") ("," || nl | P:foreignKeys / ",") nl ")" ;
          <readonly> Column : <org.oomega.base.Named> _ (P:type) ("(" | P:length | ")") ((bool)P:primaryKey ? _ "PRIMARY" _ "KEY" : "") ;
          <readonly> ForeignKey : "FOREIGN" _ "KEY" ("(" | &P:fkColumn | ")") _ "REFERENCES" _ (&P:referencedTable) ("(" | P:referencedColumn.P:name | ")") ;
        }
      }
    }
  }
}
  • The DIRECTORY syntax is only needed for the concept “Database”, because this is the concept that refines FolderEntry.
  • The LWC syntax is the default syntax. Thus it must be ensured that the whole information that the model is able to carry due to its abstract syntax definition is coded.
  • The SQL syntax is another syntax definition that codes the model in SQL/DDL. Thus you could copy & paste the SQL script to an appropriate database management console. Please notice that the SQL syntax is defined to be read-only. Hence it is impossible to edit the model within the SQL syntax tab.

The whole metamodel definition in M2L

Finally let’s show the whole language module LWC2011_EntityRelationship.

metamodel LWC2011_EntityRelationship
  based on LWC2011_Primitive, 'Modelling Languages'.'Language Engineering'.'OOMEGA Core' {
  abstract syntax {
    metapackage org {
      metapackage metamodels {
        metapackage lwc2011 {
          metapackage entityrelationship {
            Column! refines Named ::>
              &belongsTo[0..1](Singleton) <-> columns : C:Table,
              type[1..1](Singleton) : C:Primitive,
              length[0..1](Singleton) : C:Natural,
              primaryKey[1..1](Singleton) : C:Boolean
              where lengthRestriction["Length restriction is only valid for String and Natural"] = (P:type consistsOf C:org.metamodels.lwc2011.primitive.P_Boolean v P:type consistsOf C:org.metamodels.lwc2011.primitive.P_Date) => empty? P:length,
              where primaryKeyRestriction["Exactly one primary key must be defined"] = |((P:belongsTo.P:columns) projectOn select (bool)P:primaryKey)| = 1 ;
            Database! refines FolderEntry ::=
              tables[0..*](Set) <-> belongsTo : C:Table ;
            ForeignKey! refines EntityObject ::>
              &belongsTo[0..1](Singleton) <-> foreignKeys : C:Table,
              &fkColumn[1..1](Singleton) : C:Column projectOn P:belongsTo.P:columns,
              &referencedTable[1..1](Singleton) : C:Table projectOn P:belongsTo.P:belongsTo.P:tables,
              &referencedColumn[1..1](Singleton) : C:Column projectOn P:referencedTable.P:columns ;
            Table! refines Named ::>
              &belongsTo[0..1](Singleton) <-> tables : C:Database,
              columns[1..*](Set) <-> belongsTo : C:Column,
              foreignKeys[0..*](Set) <-> belongsTo : C:ForeignKey ;
          }
        }
      }
    }
  }
  textual concrete syntax DIRECTORY {
    syntaxpackage org {
      syntaxpackage metamodels {
        syntaxpackage lwc2011 {
          syntaxpackage entityrelationship {
            <helper> Database : "database" _ <org.oomega.base.Named> ;
          }
        }
      }
    }
  }
  textual concrete syntax LWC {
    syntaxpackage org {
      syntaxpackage metamodels {
        syntaxpackage lwc2011 {
          syntaxpackage entityrelationship {
            <default> Database : "database" _ <org.oomega.base.Named> _ "{" (nl | P:tables) nl "}" ;
            <default> Table : "table" _ <org.oomega.base.Named> _ "{" (nl | P:columns / ",") ("," || nl | P:foreignKeys / ",") nl "}" ;
            <default> Column : ((bool)P:primaryKey ? "pk" _ : "") (!P:type) ("(" | P:length | ")") _ <org.oomega.base.Named> ;
            <default> ForeignKey : "fk" _ (&P:fkColumn) _ "references" _ (&P:referencedTable) ("(" | &P:referencedColumn | ")") ;
          }
        }
      }
    }
  }
  textual concrete syntax SQL {
    syntaxpackage org {
      syntaxpackage metamodels {
        syntaxpackage lwc2011 {
          syntaxpackage entityrelationship {
            <readonly> Database : "CREATE" _ "DATABASE" _ <org.oomega.base.Named> (nl | P:tables) ;
            <readonly> Table : "CREATE" _ "TABLE" _ <org.oomega.base.Named> nl "(" (nl | P:columns / ",") ("," || nl | P:foreignKeys / ",") nl ")" ;
            <readonly> Column : <org.oomega.base.Named> _ (P:type) ("(" | P:length | ")") ((bool)P:primaryKey ? _ "PRIMARY" _ "KEY" : "") ;
            <readonly> ForeignKey : "FOREIGN" _ "KEY" ("(" | &P:fkColumn | ")") _ "REFERENCES" _ (&P:referencedTable) ("(" | P:referencedColumn.P:name | ")") ;
          }
        }
      }
    }
  }
}

Model-to-model transformation with ATL (ATLAS Transformation Language)

OOMEGA supports ATL for model-to-model transformations. Let’s transform domain models into entity relationship models. Basically this can be done in two simple steps:

  • Code an ATL template
  • Execute the ATL transformation within an Ant script

Let’s show the ATL template first:

module Domain2EntityRelationship;
create OUT : lwcMetamodel from IN : lwcMetamodel;

rule ModelFolder {
  from
    s : lwcMetamodel!ModelFolder
  to
    t : lwcMetamodel!ModelFolder (
      name <- s.name,
      entry <- s.entry
    )
}

rule DomainModel2Database {
  from
    domainModel : lwcMetamodel!DomainModel
  to
    database : lwcMetamodel!Database (
      name <- domainModel.name,
      tables <- domainModel.entities
    )
}

rule Entity2Table {
  from
    entity : lwcMetamodel!Entity
  to
    table : lwcMetamodel!Table (
      name <- entity.name,
      columns <- thisModule.PrimaryKeyColumn(entity),
      columns <- entity.attributes->collect(attr | thisModule.Attribute2Column(attr)),
      columns <- entity.associations->collect(asso | thisModule.Association2Column(asso)),
      foreignKeys <- entity.associations->collect(asso | thisModule.Association2ForeignKey(asso))
    )
}

unique lazy rule Attribute2Column {
  from
    attribute : lwcMetamodel!Attribute
  to
    column : lwcMetamodel!Column (
      name <- attribute.name,
      type <- attribute.type,
      primaryKey <- false
    )
}

unique lazy rule Association2Column {
  from
    association : lwcMetamodel!Association
  to
    column : lwcMetamodel!Column (
      name <- association.name,
      type <- thisModule.P_Natural(),
      primaryKey <- false
    )
}

unique lazy rule Association2ForeignKey {
  from
    association : lwcMetamodel!Association
  to
    foreignKey : lwcMetamodel!ForeignKey (
      fkColumn <- thisModule.Association2Column(association),
      referencedTable <- association.type,
      referencedColumn <- thisModule.PrimaryKeyColumn(association.type)
    )
}

unique lazy rule PrimaryKeyColumn {
  from
    entity : lwcMetamodel!Entity
  to
    primaryKeyColumn : lwcMetamodel!Column (
      name <- 'oid',
      type <- thisModule.P_Natural(),
      primaryKey <- true
    )
}

rule P_Natural() {
  to
    out : lwcMetamodel!P_Natural
  do {
    out;
  }
}

Here’s the Ant script that executes the ATL transformation:

<project name="org.metamodels.lwc2011.m2m" default="build" basedir="..">
  
  <delete dir="build" />
  <mkdir dir="build/output" />
  
  <property name="atl.launcher" value="OOMEGA-specific VM" />
  <property name="eoc.dir" value="${basedir}/../org.metamodels.lwc2011.exemplarymodel/" />
  
  <target name="build">
    <oomega-atl.loadModel 
      name="lwcMetamodel" 
      metamodel="%OOMEGA" 
      path="../org.metamodels.lwc2011.exemplarymodel/.metamodel.sdf"
    />
    <oomega-atl.loadModel
      name="lwcDomainModel" 
      metamodel="lwcMetamodel" 
      uri="oomega-text:${eoc.dir}/.metamodel.sdf;${eoc.dir}/.model.txt" 
      classpath="../org.metamodels.lwc2011.plugin/lib/lwc2011.jar" 
    />
    
    <oomega-atl.launch path="atl/Domain2EntityRelationship.asm" classpath="../org.metamodels.lwc2011.plugin/lib/lwc2011.jar">
      <inmodel name="IN" model="lwcDomainModel" />
      <outmodel name="OUT" model="lwcEntityRelationshipModel" metamodel="lwcMetamodel" />
    </oomega-atl.launch>
    
    <oomega-atl.saveModel 
      model="lwcEntityRelationshipModel" 
      uri="oomega-text:${eoc.dir}/.metamodel.sdf;${basedir}/build/output/model.txt" 
      classpath="../org.metamodels.lwc2011.plugin/lib/lwc2011.jar" />
  </target>
  
</project>

This is a simple domain model as shown in the LWC2011 case description.

domain model "0.1-domain" {
  package "org.metamodels.lwc2011" {
    entity Person {
      string name
      string firstname
      date birthdate
      ref Car ownedCar
    }
    entity Car {
      string make
      string model
    }
  }
}

According to this model, ATL will generate the following entity relationship model:

database "0.1-domain" {
  table Car {
    pk natural oid,
    string make,
    string model
  }
  table Person {
    pk natural oid,
    string firstname,
    string name,
    date birthdate,
    natural ownedCar,
    fk ownedCar references Car(Car.oid)
  }
}

Screenshots of the language workbench

Task 1.4 - Some kind of visibility/namespaces/scoping for references

Challenge: Integrate namespaces/packages into the entity DSL

package p1 {
  import p2
  entity Person {
    string name
    string firstname
    date birthdate
    Car ownedCar
  }
}

package p2 {
  entity Car {
    string make
    string model
  }
}

and make sure in the instance DSL you can only assign values to the properties of the respective entity, i.e. make sure that writing

Car c = {
  birthdate = …
}

is illegal.

Solution:

Now the time has come to discuss the concept Package of the language module LWC2011_Domain. As mentioned in the introduction we’re not maintaining several versions of one and the same DSL, thus the concept Package was already part of LWC2011_Domain in earlier chapters of this documentation. Anyway – we didn’t show the details of that concept yet. Let’s do that now.

First of all let’s show the context of a package.

DomainModel! refines FolderEntry ::=
  packages[0..*](Set) <-> belongsTo : C:Package,
  ...

A DomainModel contains a set of packages.

Entity! refines Named ::>
  &belongsTo[0..1](Singleton) <-> entities : C:Package,
  ...

An Entity belongs to a package.

Abstract syntax of the concept Package

Package! refines Named ::>
  &belongsTo[0..1](Singleton) <-> packages : C:DomainModel,
  entities[0..*](Set) <-> belongsTo : C:Entity,
  &importedPackages[0..*](Set) : C:Package,
  &path[0..1](Singleton) : C:String := Java: "return getName().replace('.', '/');
  "
  • Package! refines Named ::>
    is a weak entity concept and refines the concept “Named”.
  • &belongsTo[0..1](Singleton) <-> packages : C:DomainModel
    is the opposite property of the composition “packages” within the concept “DomainModel”.
  • entities[0..*](Set) <-> belongsTo : C:Entity
    defines the composition to a set of entities.
  • &importedPackages[0..*](Set) : C:Package
    defines an association to a set of imported packages.
  • &path[0..1](Singleton) : C:String := Java: "return getName().replace('.', '/');"
    defines an inferred property that is – this time – calculated in Java. The package path is returned by replacing dots of package names with slashes. This is used in the context of code generation within the Xpand template for generating Java classes (cp. task 0.2).

Concrete syntax of the concept Package

There is only one concrete syntax defined for the concept Package: the LWC syntax is the default syntax for the whole case study.

textual concrete syntax LWC {
  syntaxpackage org {
    syntaxpackage metamodels {
      syntaxpackage lwc2011 {
        syntaxpackage domain {
          <default> Package : "package" _ <org.oomega.base.Named> _ "{" (nl "import" _ || &P:importedPackages / "," _) (nl | P:entities) nl "}" ;
        }
      }
    }
  }
}

Visibility and scoping

Now namespaces are supported, entities are created within packages and a package can import other packages. Finally we’d like to implement the features visibility and scoping.

  • Visibility: An entity can reference another entity only if the other entity is either part of the same package or part of an imported package.
  • Scoping: A package import leads to a scope expansion: if an entity of an imported package is referenced, then the package prefix can be omitted in the reference.
2 lines of code

Fortunately the implementation of the features visibility and scoping takes only two lines of code.

Let’s recall the abstract syntax of the concept Association:

Association! refines Prop ::>
  &type[1..1](Singleton) <-> usedBy : C:Entity projectOn (P:belongsTo.P:belongsTo.P:entities) union (P:belongsTo.P:belongsTo.P:importedPackages.P:entities),
  &includedContext[0..*](Set) : C:EntityObject := P:belongsTo.P:belongsTo.P:importedPackages ;
  • &type[1..1](Singleton) <-> usedBy : C:Entity projectOn (P:belongsTo.P:belongsTo.P:entities) union (P:belongsTo.P:belongsTo.P:importedPackages.P:entities)
    The context sensitive domain of the “type” property realizes the visibility feature. An association belongs to an entity, which in turn belongs to a package. A package contains a set of entities and refers to a set of imported packages that in turn contain some more entities. Thus an association cannot reference entities of foreign packages that have not been imported.
  • &includedContext[0..*](Set) : C:EntityObject := P:belongsTo.P:belongsTo.P:importedPackages
    The inferred property “includedContext” implements the scoping feature: all imported packages are included into the scope of an association. Thus the package prefix of referenced entities can be omitted when coding an association.

Constrain property/value assignments within instances according to the type of the instance

It shall be ensured that in the Instance DSL you can only assign values to the properties of the respective entity. This can be realized with a context sensitive domain. Let’s show the relevant part of the concept PropertyValue.

PropertyValue! refines EntityObject ::>
  (PK)&property[0..1](Singleton) : C:Prop projectOn P:belongsTo.P:entity.P:properties,
  ...

Here you can see that the property “property” of the concept PropertyValue is constrained by the context sensitive domain C:Prop projectOn P:belongsTo.P:entity.P:properties. A PropertyValue object belongs to an instance, the instance has an entity type and that entity defines a set of properties. The Edge Algebra statement ensures that the referenced property within a property/value assignment is a property that is defined within the type of the instance. That’s it.

Screenshots of the language workbench

Task 1.5 - Integrating manually written code (again in Java, C# or C++)

Challenge: Integrate derived attributes to entities.

entity Person {
  string name
  string firstname
  date birthdate
  Car ownedCar
  derived int age // somehow somewhere implement the code
                  // to calculate age
}

Note that if you want, you can also define or reuse an expression language that allows defining the algorithm for calculating the age directly in the model. Ideally, you will show both (manually written 3GL code as well as an expression language).

Solution:

We have decided to implement a simple expression language that allows defining the algorithm for calculating the age directly in the model. The expression language does not contain many operators; rather it is very specific to solve exactly the problem “calculating the age of a person”. It shall be possible to model the following:

entity Person {
  date birthdate
  derived natural age = YearOf((Today - P(birthdate)))
}

The basic idea is to calculate the difference between the current date and the person’s birthdate. The result of that subtraction is again a date; now we just have to extract the year of that calculated date, which is already the age of the person.

In order to realize this we need to provide four operators, namely Today, DateSubtraction, YearOf and DateProperty. Let’s introduce the metamodel LWC2011_Expression that contains most of those operators.

The language module LWC2011_Expression

Please note that the language module LWC2011_Expression defines some more concepts – not only the aforementioned operators. The design of the expression language is geared to the four primitive types introduced in the language module LWC2011_Primitive. According to this there are some additional abstract concepts defined.

The language module LWC2011_Expression contains the following concepts:

  • Expression
    is an abstract attribute concept. Every expression refines this concept.
  • BooleanExp, DateExp, NaturalExp and StringExp
    are abstract attribute concepts and refine the concept Expression. Those concepts constitute the (return) type of an expression.
  • Today
    is a concrete attribute concept and refines the concept DateExp. It “returns” the current date.
  • DateSubtraction
    is a concrete attribute concept and refines the concept DateExp. It “calculates” the difference between two dates.
  • YearOf
    is a concrete attribute concept and refines the concept NaturalExp. It “returns” the year of a date.

Abstract syntax

Let’s explain the abstract syntax of those concepts in more detail:

Expression
[Expression!!] refines AttributeObject ::>
  &context[0..1](Singleton) : C:EntityObject := P:parent.P:context ;
  • [Expression!!] refines AttributeObject ::>
    is an abstract attribute concept.
  • &context[0..1](Singleton) : C:EntityObject := P:parent.P:context
    is an inferred property that will be used within a context-sensitive domain specification. This will be explained later in this chapter. The Edge Algebra statement returns the “context” property of the parent/composite object.
BooleanExp, DateExp, NaturalExp and StringExp
[BooleanExp!!] refines Expression ::> ;
[DateExp!!] refines Expression ::> ;
[NaturalExp!!] refines Expression ::> ;
[StringExp!!] refines Expression ::> ;

Let’s only explain the concept DateExp, because the others work similarly.

  • [DateExp!!] refines Expression ::>
    is an abstract attribute concept and refines the concept “Expression”.
Today
"Today"!! refines DateExp ::> ;
  • "Today"!! refines DateExp ::>
    is a concrete attribute concept and refines the concept “DateExp”.
DateSubtraction
DateSubtraction!! refines DateExp ::>
  leftOperand[1..1](Singleton) : C:DateExp,
  rightOperand[1..1](Singleton) : C:DateExp ;
  • DateSubtraction!! refines DateExp ::>
    is a concrete attribute concept and refines the concept “DateExp”.
  • leftOperand[1..1](Singleton) : C:DateExp
    defines an attribute of type DateExp: this is the left operand of the DateSubtraction operator.
  • rightOperand[1..1](Singleton) : C:DateExp
    defines an attribute of type DateExp: this is the right operand of the DateSubtraction operator.
YearOf
"YearOf"!! refines NaturalExp ::>
  dateExp[1..1](Singleton) : C:DateExp ;
  • "YearOf"!! refines NaturalExp ::>
    is a concrete attribute concept and refines the concept “NaturalExp”.
  • dateExp[1..1](Singleton) : C:DateExp
    defines an attribute of type DateExp: this is the operand of the YearOf operator.

Concrete syntax

Apart from the outlined abstract syntax, you must create concrete syntax definitions for the concepts “Today”, “DateSubtraction” and “YearOf”. You do not need to define a concrete syntax for abstract concepts.

There is only one concrete syntax defined for those concepts: the LWC syntax is the default syntax for the whole case study.

textual concrete syntax LWC {
  syntaxpackage org {
    syntaxpackage metamodels {
      syntaxpackage lwc2011 {
        syntaxpackage expression {
          syntaxpackage dateexp {
            <default> Today : "Today" ;
            <default> DateSubtraction : "(" (P:leftOperand) _ "-" _ (P:rightOperand) ")" ;
          }
          syntaxpackage naturalexp {
            <default> YearOf : "YearOf" "(" (P:dateExp) ")" ;
          }
        }
      }
    }
  }
}

The whole metamodel definition in M2L

Finally let’s show the whole language module LWC2011_Expression.

metamodel LWC2011_Expression
  based on 'Modelling Languages'.'Language Engineering'.'OOMEGA Core' {
  abstract syntax {
    metapackage org {
      metapackage metamodels {
        metapackage lwc2011 {
          metapackage expression {
            [Expression!!] refines AttributeObject ::>
              &context[0..1](Singleton) : C:EntityObject := P:parent.P:context ;
            metapackage booleanexp {
              [BooleanExp!!] refines Expression ::> ;
            }
            metapackage dateexp {
              [DateExp!!] refines Expression ::> ;
              DateSubtraction!! refines DateExp ::>
                leftOperand[1..1](Singleton) : C:DateExp,
                rightOperand[1..1](Singleton) : C:DateExp ;
              "Today"!! refines DateExp ::> ;
            }
            metapackage naturalexp {
              [NaturalExp!!] refines Expression ::> ;
              "YearOf"!! refines NaturalExp ::>
                dateExp[1..1](Singleton) : C:DateExp ;
            }
            metapackage stringexp {
              [StringExp!!] refines Expression ::> ;
            }
          }
        }
      }
    }
  }
  textual concrete syntax LWC {
    syntaxpackage org {
      syntaxpackage metamodels {
        syntaxpackage lwc2011 {
          syntaxpackage expression {
            syntaxpackage dateexp {
              <default> Today : "Today" ;
              <default> DateSubtraction : "(" (P:leftOperand) _ "-" _ (P:rightOperand) ")" ;
            }
            syntaxpackage naturalexp {
              <default> YearOf : "YearOf" "(" (P:dateExp) ")" ;
            }
          }
        }
      }
    }
  }
}

Enhancement of the language module LWC2011_Domain

You might have noticed that we haven’t implemented the concept DateProperty yet. Please recall what we want to model:

entity Person {
  date birthdate
  derived natural age = YearOf((Today - P(birthdate)))
}

Please note the statement P(birthdate) within the expression that calculates the age of a person: the attribute “birthdate” is referenced here! But be careful: the concept Attribute is part of the metamodel LWC2011_Domain. Thus if the DateProperty operator was included into the metamodel LWC2011_Expression, we would introduce a dependency from LWC2011_Expression to the metamodel LWC2011_Domain. This is not our intended design. Moreover it isn’t even possible to implement that because the metamodel LWC2011_Domain is dependent on LWC2011_Expression (expressions are used within the domain model to specify derived attributes) and circular metamodel dependencies are prohibited in M2L.

So what can we do? Actually the solution to the problem is to “move” the DateProperty operator into the language module LWC2011_Domain. You can view this as an integration point between the independent expression language and its concrete adoption into the metamodel LWC2011_Domain.

Apart from the DateProperty operator, we need to provide another concept, namely Derived, in order to specify derived attributes within the domain model.

Abstract syntax

Let’s explain the abstract syntax of the concepts DateProperty and Derived.

DateProperty
DateProperty!! refines DateExp ::>
  &attr[1..1](Singleton) : C:Attribute projectOn P:context.P:attributes
  where typeSafety["The referenced attribute must be of type Date"] = (P:attr.P:type) consistsOf C:org.metamodels.lwc2011.primitive.P_Date ;
  • DateProperty!! refines DateExp ::>
    is a concrete attribute concept and refines the concept “DateExp”.
  • &attr[1..1](Singleton) : C:Attribute projectOn P:context.P:attributes
    defines an association to the referenced attribute. Please note the context sensitive domain C:Attribute projectOn P:context.P:attributes: DateProperty expressions (that are always part of derived attributes) must not reference attributes of other entities. Be careful: the inferred property “context” is recursively called starting from the DateProperty expression until the derived attribute and finally the entity is reached. Please have a look at the inferred properties with the name “context” in the concepts Expression, Derived and Entity.
  • where typeSafety["The referenced attribute must be of type Date"] = (P:attr.P:type) consistsOf C:org.metamodels.lwc2011.primitive.P_Date
    defines an additional constraint that ensures that the type of the referenced attribute is always a Date; the DateProperty operator can only refer to attributes of type Date.
Derived
Derived! refines Prop ::>
  type[1..1](Singleton) : C:Primitive,
  expression[1..1](Singleton) : C:Expression,
  &context[0..1](Singleton) : C:EntityObject := P:parent.P:context
  where typeSafety["Return type must match the expression's type"] = 
    (P:type consistsOf C: org.metamodels.lwc2011.primitive.P_Boolean => P:expression consistsOf C:org.metamodels.lwc2011.expression.booleanexp.BooleanExp) &
    (P:type consistsOf C:org.metamodels.lwc2011.primitive.P_Date => P:expression consistsOf C:org.metamodels.lwc2011.expression.dateexp.DateExp) &
    (P:type consistsOf C:org.metamodels.lwc2011.primitive.P_Natural => P:expression consistsOf C:org.metamodels.lwc2011.expression.naturalexp.NaturalExp) &
    (P:type consistsOf C:org.metamodels.lwc2011.primitive.P_String => P:expression consistsOf C:org.metamodels.lwc2011.expression.stringexp.StringExp) ;
  • Derived! refines Prop ::>
    is a concrete weak entity concept and refines the concept “Prop”.
  • type[1..1](Singleton) : C:Primitive
    defines an attribute of type Primitive: it’s the primitive return type of the derived attribute. Please note that the concept “Primitive” is referenced here, which is defined within the metamodel LWC2011_Primitive.
  • expression[1..1](Singleton) : C:Expression
    defines an attribute of type Expression. Please note that the concept “Expression” is referenced here, which is defined within the metamodel LWC2011_Expression.
  • &context[0..1](Singleton) : C:EntityObject := P:parent.P:context
    is an inferred property that is used within the aforementioned context-sensitive domain specification of the property “attr” in the concept DateProperty. The Edge Algebra statement returns the “context” property of the parent/composite object.

Now let’s discuss the additional constraint typeSafety. You can read it as follows:

where typeSafety["Return type must match the expression's type"] = 
  (P:type consistsOf C: org.metamodels.lwc2011.primitive.P_Boolean => P:expression consistsOf C:org.metamodels.lwc2011.expression.booleanexp.BooleanExp) &
  (P:type consistsOf C:org.metamodels.lwc2011.primitive.P_Date => P:expression consistsOf C:org.metamodels.lwc2011.expression.dateexp.DateExp) &
  (P:type consistsOf C:org.metamodels.lwc2011.primitive.P_Natural => P:expression consistsOf C:org.metamodels.lwc2011.expression.naturalexp.NaturalExp) &
  (P:type consistsOf C:org.metamodels.lwc2011.primitive.P_String => P:expression consistsOf C:org.metamodels.lwc2011.expression.stringexp.StringExp) ;
  • If the type of the derived attribute is P_Boolean, the expression must be an instance of BooleanExp.
  • If the type of the derived attribute is P_Date, the expression must be an instance of DateExp.
  • If the type of the derived attribute is P_Natural, the expression must be an instance of NaturalExp.
  • If the type of the derived attribute is P_String, the expression must be an instance of StringExp.

Concrete syntax

Apart from the outlined abstract syntax, you must create concrete syntax definitions for those concepts. There is only one concrete syntax defined: the LWC syntax is the default syntax for the whole case study.

textual concrete syntax LWC {
  syntaxpackage org {
    syntaxpackage metamodels {
      syntaxpackage lwc2011 {
        syntaxpackage domain {
          <default> Derived : "derived" _ (P:type) _ <org.oomega.base.Named> _ "=" _ (P:expression) ;
          syntaxpackage expression {
            syntaxpackage dateexp {
              <default> DateProperty : "P" "(" (&P:attr) ")" ;
            }
          }
        }
      }
    }
  }
}

Now you are able to model the derived attribute "age" for calculating the age of a person with a simple expression language integrated into your modelling language.

Screenshots of the language workbench

Task 1.6 - Multiple generators

Challenge: Generate some kind of XML structure from the entity model.

Solution:

It’s absolutely no problem to execute multiple generators for one and the same model. Let’s enhance the Xpand based code generator from task 0.2 in order to generate some kind of XML structure from the domain model. This can be done in two simple steps:

  • Code another Xpand template
  • Enhance the workflow

Let's show the Xpand template first:

«IMPORT org::oomega::m2t::mwe::plugin»
«IMPORT org::oomega::base»
«IMPORT org::metamodels::lwc2011::domain»

«DEFINE Root FOR OOMEGAModel»
«EXPAND Root FOREACH entities»
«ENDDEFINE»

«DEFINE Root FOR Package»
«FILE name+".xml"»<?xml version="1.0"?>

<package name="«name»">«EXPAND XML FOREACH importedPackages»«EXPAND XML FOREACH entities»
</package>
«ENDFILE»
«ENDDEFINE»

«DEFINE XML FOR org::metamodels::lwc2011::domain::Package»
  <import package="«name»" />«ENDDEFINE»

«DEFINE XML FOR org::metamodels::lwc2011::domain::Entity»
  <entity name="«name»">«EXPAND XML FOREACH properties»
  </entity>«ENDDEFINE»

«DEFINE XML FOR org::metamodels::lwc2011::domain::Attribute»
    <attribute name="«name»" type="«type.xmlMapping»" />«ENDDEFINE»
«DEFINE XML FOR org::metamodels::lwc2011::domain::Association»
    <association name="«name»" type="«type.name»" />«ENDDEFINE»
«DEFINE XML FOR org::metamodels::lwc2011::domain::Derived»«ENDDEFINE»

«DEFINE Root FOR EntityObject»
«ENDDEFINE»

The original workflow definition is enhanced by another generator component:

<!-- run the XML generator -->
<component id="generator" class="org.eclipse.xpand2.Generator" skipOnErrors="true">

  <!-- the metamodel is JavaBeans based -->
  <metaModel class="org.eclipse.xtend.type.impl.java.JavaMetaModel">
    <typeStrategy class="org.eclipse.xtend.type.impl.java.beans.JavaBeansStrategy"/>
  </metaModel>

  <fileEncoding value="UTF-8" />

  <!-- call Define Root in template XML.xpt for our model put in the modelslot 'model' -->
  <expand value="XML::Root FOR model" />

  <outlet path="build/temp/generate" />
</component>

Let’s recall the simple domain model as shown in the LWC2011 case description.

domain model "0.1-domain" {
  package "org.metamodels.lwc2011" {
    entity Person {
      string name
      string firstname
      date birthdate
      ref Car ownedCar
    }
    entity Car {
      string make
      string model
    }
  }
}

According to this model, Xpand will now generate three files: the original Java files Person.java and Car.java and additionally the XML file org.metamodels.lwc2011.xml.

<?xml version="1.0"?>

<package name="org.metamodels.lwc2011">
  <entity name="Car">
    <attribute name="make" type="String" />
    <attribute name="model" type="String" />
  </entity>
  <entity name="Person">
    <attribute name="firstname" type="String" />
    <attribute name="name" type="String" />
    <attribute name="birthdate" type="Date" />
    <association name="ownedCar" type="Car" />
  </entity>
</package>

Phase 2 - Non-Functional

Phase 2 is intended to show a couple of non-functional properties of the LWB.

At the beginning of phase 2, we would like to introduce the spectrum of supported storage back-ends and mechanisms as they are essential when considering non-functional aspects of the OOMEGA solution.

First of all it’s important to understand that OOMEGA has standardized the access to every storage back-end mentioned below. There’s one and the same API implemented for every persistence mechanism. As a result it’s effectively possible to interchange storage back-ends without changing a single line of code: a promise often given by standards but rarely kept by implementations. The API is very comfortable – you can think of an object-oriented persistency API supporting CRUD operations, object navigation, transaction management, nested transactions, observer pattern, object-oriented query language (based on the Edge Algebra), cascading delete concept and others.

Secondly it’s worth to mention that the “configuration” of each and every storage back-end is nothing else than specifying a metamodel – more precisely defining the abstract syntax. The abstract syntax definition in that sense is the definition of a data structure where traditionally E/R diagrams, SQL/DDL statements, class diagrams, Java objects with annotations, or some other notation has been used. As you’ve seen in previous phases of this document our metamodelling language M2L is a very compact and precise tool to define the data structure of a language or system: a considerable amount of constraints are supported by M2L that cannot be expressed by traditional approaches, thus it’s possible to define the set of valid models more precisely with M2L. The bottom line: your metamodel is used to configure various storage back-ends; there’s no additional input required.

Now let’s discuss the available storage back-ends. In general there are two flavours:

  • MemoryDB: single-user object database solely based on main memory interaction.
    OOMEGA has implemented an object database that is solely based on main memory interaction: the whole database content is read from a file into main memory; database queries and write accesses are handled within main memory and finally a commit leads to a dump of the whole content back to secondary storage. MemoryDB is suitable for single-user access; it cannot be used in multi-user environments.
  • Third-party databases: bindings to third-party databases (including multi-user support and to-bin/from-bin transfer of main memory to secondary storage at run-time).
    OOMEGA has implemented bindings to third-party databases like Versant Object Database. Memory management, interaction with secondary storage and multi-user support is solved by the respective third-party database whereas the client/server protocol, the locking scheme (supporting a highly interactive multi-user synchronisation) and many other aspects are still covered by OOMEGA.

Related to language engineering and modeling environments what’s the intended use of those storage back-ends?

  • MemoryDB: The purpose of OOMEGA’s MemoryDB is to use it on the client, i.e. within the Eclipse workbench of a single developer/modeller. Database contents are stored in a file; the interaction with other developers is handled by a concurrent versioning system like Apache Subversion (SVN).
  • Third-party databases: Due to OOMEGA’s third-party database bindings multi-user interaction is directly supported within the modelling environment of the language workbench. The advantage of this approach is its ability to handle large-scale models. Moreover concurrent editing of one and the same model is enabled thanks to our highly-interactive multi-user synchronisation mechanism.

Now you know the general difference between the two flavours of storage back-ends. The following table gives a review over concrete implementations:

MemoryDB Third-party databases
SDF - Structured Data Format db4objects
XML - Extensible Markup Language Hibernate
Text - according to your default concrete syntax specification Versant Object Database

Thus OOMEGA supports relational databases via Hibernate and two object-oriented databases, namely db4objects and Versant Object Database. Moreover there are three different file types supported by OOMEGA’s MemoryDB.

  • SDF – Structured Data Format
    SDF is a very efficient binary codec for models. The advantage of this approach is efficiency. The disadvantage: it’s not human-readable and thus DIFF/MERGE mechanisms of Apache Subversion cannot be applied.
  • XML – Extensible Markup Language
    OOMEGA provides a generic coder/decoder of models to and from XML. The XML format is quite nice to read and the standard XML parser is pretty fast. You can use DIFF/MERGE mechanisms of Apache Subversion, but then you still need to interact with a XML representation of your models.
  • Text – according to your default concrete syntax specification
    Database contents are stored in terms of your concrete syntax specification. This approach is optimal for sharing your models with Apache Subversion, because your default concrete syntax will apply in the context of DIFF/MERGE operations. On the other hand: the OOMEGA parser is designed for incremental parsing of small pieces; it’s not the fastest alternative to load a huge model from its textual representation.

With that background knowledge in mind, let’s start to explore the tasks of phase 2:

Task 2.1 - How to evolve the DSL without breaking existing models

Before answering the challenge „How to evolve the DSL without breaking existing models” we should recapitulate that a DSL consists of an abstract and one or more concrete syntax specifications. Thus we should rephrase the original question as follows:

  • How to evolve concrete syntaxes of a DSL without breaking existing models?
  • How to evolve the abstract syntax of a DSL without breaking existing models?

How to evolve concrete syntaxes of a DSL without breaking existing models?

Altering concrete syntaxes at runtime

In fact you can alter concrete syntaxes of a DSL at runtime – even if your model repository already contains instances of your DSL. As soon as you change the concrete syntax of your metamodel, the respective models will be immediately updated by the model repository.

Technically speaking there’s not even an update of the model required, because the textual representation of a model is just a projection of an object graph to text and thus – by changing the concrete syntax – the projection rules are changed and interpreted accordingly. As a result you cannot break existing models when evolving concrete syntaxes of a DSL.

How to evolve the abstract syntax of a DSL without breaking existing models?

But what happens if you change the abstract syntax of a DSL? According to our vision and internal roadmap: you can do this at runtime, too! Unfortunately this is not yet possible and we’re not going to explain (our competitors) in detail how this might work in the near future. Anyway – stay tuned and you will observe how this feature will be included in the upcoming versions of our language workbench.

At the moment you need to migrate existing models when changing the abstract syntax of a DSL. In fact the migration of a model can be implemented with ATL – it’s just a model-to-model transformation. Moreover you can use our Java API to programmatically access the contents of a model repository and store those contents into another repository with some other structure.

Task 2.2 - How to work with the models efficiently in the team

As mentioned in the introduction there are two flavours of storage back-ends: OOMEGA’s MemoryDB and third-party database bindings. The former can be used in single-user environments and/or when the contents of a model repository shall be shared with a concurrent versioning system like Apache Subversion. The purpose of third-party database bindings is to support concurrent and highly-interactive modelling in multi-user environments.

MemoryDB and Apache Subversion

As you know from the introduction, a model repository that is based on OOMEGA’s MemoryDB can store its contents to various file types, namely SDF, XML and Text. Subject to the degree of concurrent modification, you should choose the appropriate format:

  • SDF – Structured Data Format
    SDF is the appropriate format, if your model is shared between several team members but not concurrently modified. Only one team member is editing the model at a certain point in time and acquires a lock of the model file in Apache Subversion in order to do that.
  • XML – Extensible Markup Language
    XML is the best choice, if your model is shared between several team members and only concurrently modified in rare cases. Most of the time there’s no concurrent editing of the model. But if it happens, the DIFF/MERGE mechanisms of Apache Subversion can be used to resolve a conflict on the basis of a human-readable XML representation.
  • Text – according to your default concrete syntax specification
    if your models will be often concurrently modified by several team members, then you should opt for storing and sharing your models according to your default concrete syntax specification: this will substantially facilitate DIFF/MERGE operations with Apache Subversion.

Finally it’s worth to mention that you can switch the storage back-end at any time. So the decision can be revised and adjusted to your possibly shifting project requirements.

Third-party database bindings

Third-party database bindings in conjunction with OOMEGA’s client/server protocol and locking scheme are best-suited for multi-user team environments – they lead to a highly-interactive modelling experience. Let’s explain our locking scheme that is similar but not identical to the well-known optimistic locking scheme:

  • Users can edit model elements concurrently – even if they change exactly the same objects; there are no read/write locks acquired at the server when reading or changing some contents.
  • Local changes to the model won’t be visible to other users as long as those changes are not committed to the database (transaction isolation).
  • As soon as local changes are committed to the database (the modeller hits the Save button in the Eclipse workbench), concurrent transactions of other users will be instantly notified by those changes.

In contrast to traditional optimistic locking we do not wait until a transaction tries to commit and then might fail due to some changes made by concurrent transactions not yet seen. Rather an ongoing transaction is immediately notified as soon as some other transaction has committed something to the database that’s in the relevant scope of the ongoing transaction.

Highly-interactive modelling experience

As a result you will notice the changes of other team members online – text passages might be changed by others, graphical boxes may arise or might be moved, etc. in your Eclipse editors. As stated earlier: a highly-interactive modelling experience.

Task 2.3 - Demonstrate Scalability of the tools

Scalability of a language workbench means the following:

  • Large-scale models
    the language workbench must be able to handle large-scale models with possibly millions of objects in the model repository.
  • High degree of concurrent modelling
    the language workbench must allow a high degree of concurrent modelling with possibly hundreds or thousands of transactions editing the contents of the model repository concurrently.

OOMEGA meets those challenges: we provide bindings to high-performance third-party databases.

Versant Object Database is best-suited for that purpose. Please note that object navigation is heavily used within language engineering and thus modelling is the ideal candidate for unleashing the power and performance benefit of an object-oriented database.

Phase 3 - Freestyle

Every LWB has its own special "cool features". In phase 3 we want the participants to show off these features.

Here we’d like to highlight the features provided by the OOMEGA language workbench: we think it’s more important that you understand the general facts of our tool rather to present even more features on a very detailed level. The latter has been done throughout earlier phases of the solution description. Let’s summarize the most important facts:

  • Eclipse-based solution & Support for ATL and Xpand
    OOMEGA is based on the Eclipse IDE; it supports ATL and Xpand for model-to-model and model-to-text transformations.
  • Scannerless parser
    OOMEGA has implemented its own scannerless parser; we do not rely on ANTLR or some other parser generator.
  • Text editors with syntax highlighting, linking and incremental parsing
    All you need to do is to specify abstract and concrete syntaxes of your domain-specific languages; OOMEGA will instantly provide a comfortable text editor with syntax highlighting, linking and incremental parsing. No programming effort required!
  • Object-oriented database back-end
    You can edit your models with an Eclipse-based text editor. Anyway, the storage back-end is always a database: when you type some text in the editor, objects are instantly created in the context of a database transaction. As soon as you press the Eclipse “Save” button, your database transaction is committed.
  • Various implementations of object-oriented storage back-ends
    There are various implementations of OOMEGA’s persistency API available. You might choose to store and share your models with a relational or object-oriented database. But you can also use file-based storage back-ends on the basis of our binary codec SDF, our XML support or your textual concrete syntax definition. Irrespective of your concrete choice, the editors interact with the models on the basis of our object-oriented database API.
  • Clear separation of abstract and concrete syntaxes
    We strictly distinguish between abstract and concrete syntax definitions. The abstract syntax defines the structure of the model whereas the concrete syntax only bridges the gap between this structure and a concrete textual representation of the model. Therefore it’s easy to define several concrete syntaxes for one and the same abstract syntax.
  • Graphical or form-based editors
    It’s possible to combine text editors with graphical or form-based editors. You can embed those special editors in additional syntax tabs. The observer pattern of OOMEGA’s API will ensure that changes made in the text editor will instantly update your graphical or form-based editors, and vice versa.
  • M2L and the Edge Algebra
    Our metamodelling language M2L and its foundation – the Edge Algebra – allow very accurate abstract syntax specifications. It’s a very powerful and compact language. Please remember: we were able to implement the whole functionality of the LWC2011 case in less than 350 lines of code and didn’t require a single line of additional 3GL code for designing the language, implementing constraints or setting up text editors.
  • Various techniques for defining constraints
    M2L and the Edge Algebra provide various techniques for constraining your models: primary local keys, context sensitive domains, weak entity and attribute concepts, multiplicities, abstract concepts, and additional constraints. All these constraints are guaranteed by the storage back-end, thus it’s impossible to commit an invalid state of a model. Due to the advanced metamodelling concepts you can very accurately specify the range of valid models – far beyond traditional data modelling techniques.
  • Multi-user support via online interaction or CVS/SVN repository
    OOMEGA supports multi-user environments via online interaction on top of database back-ends. We have invented and implemented a highly interactive multi-user synchronisation mechanism that is similar but not identical to the well-known optimistic locking scheme: you will instantly receive committed updates from other users in your language workbench. Moreover you might choose to share your models with a standard CVS or SVN repository.

Acknowledgement

Thanks for reading our solution description of the Language Workbench Competition 2011!

We are pleased about any support: Please share this document with others in the community, blog and twitter about us, help us to improve the software and finally don’t hesitate to contact us. We look forward to hearing from you!

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.