Skip to content

The Reactions Language

Thomas Weber edited this page Dec 15, 2023 · 7 revisions

The Reactions Language

The Reactions language is a domain-specific language (DSL) for defining consistency relations between two metamodels. Consistency relations are specified unidirectionally and change-driven by defining how changes in an instance of one metamodel have to be transformed into changes of instances of another metamodel to restore consistency. For example, the modification of a name of a UML class is transformed into the change of a Java class that corresponds to the UML class.

Language Elements

The reactions language consists of two main constructs, reactions and routines. A reaction specifies, in reaction to which kind of change some consistency repair logic, defined in a routine, has to be executed. A routine specifies which information has to be extracted from the models and how consistency is repaired. For instance, a reaction may specify that in reaction to the modification of a UML class name a routine shall be executed that retrieves the corresponding Java class and updates its name.

Reactions and routines are explicitly separated to allow the reuse of repair logic. A routine can be called by reactions or by other routines. Thus, separating the repair logic (routine) from the decision when to execute it (reaction) increases the reusability.

Reactions

A reaction specifies when some consistency restoration logic shall be executed. It defines the so called trigger, for example, the replacement of an attribute value or the insertion of a reference, and specifies which routine has to be called for restoring consistency.

Several reactions can be specified for the same change without excluding each other. If two reactions are defines for the same change, both will be executed. Currently, the order in which they are executed is undefined and cannot be influenced. Therefore, only a single reaction should be specified for one change delegating to different routines in the correct order to avoid unexpected behavior due to the execution order.

Routines

A routine specifies how some kind of consistency relation between instances of two metamodels can be restored after a change. It consists of a match, a create and an update.

The matcher specifies preconditions on the state of the models to execute the update. For instance, if a UML class is created and a corresponding Java class shall be created, the Java package corresponding to the UML package of the UML class has to be retrieved to place the created Java class in. If it does not exist, the update cannot be executed. Most of the matcher statements are based on retrieving elements from the models which can be also used in the update afterwards. For example, the Java package corresponding to the UML package can be accessed for placing the Java class in within the update.

The create specifies which elements shall be created within this routine. It defines the element type to create and the variable name it should be assigned to.

The update specifies which changes have to be performed in another model to restore consistency. It is realized as a code block which provides access to an API with several operations for deleting elements, adding and removing correspondences and calling other routines. An update should be executed completely or not at all to avoid resulting in inconsistent intermediate states. To achieve this, all preconditions for executing the update should be specified in the matcher.

Correspondences

Consistency between two models is based on relations between elements of these models. For example, a UML class diagram and Java code can be considered consistent if they contain the same classes, having the same names, attributes, operations, associations and so on. Consequently, a class in the UML diagram has a corresponding class in Java code. Therefore, a correspondence model is used, which contains links between model elements that describe the same concept. A single correspondence consists of two model elements that describe the same concept, for example, a UML class and a Java class.

The correspondence model can be queried and modified by reactions. The matcher of a routine provides constructs for retrieving elements from the correspondence model, for example, the Java class corresponding to a UML class. In the update, correspondences can be added or removed between two objects.

Correspondences can also be defined for metaclasses rather than model elements. This can be useful, if elements are needed once per application (singletons). For example, having a Java project, only one corresponding UML model may has to exist. This can be stored in a correspondence to a metaclass of the Java metamodel, e.g. a Package. Adding such a correspondence within a reaction can look as follows: addCorrespondenceBetween(ContainersPackage.Literals.Package, umlModel) whereas ContainersPackage.Literals.Package is the Package-EClass of the Java metamodel and umlModel is a reference to the uml model element.

Syntax

The syntax of the Reactions language is aligned with mainstream programming languages using curly braces for blocks and common syntax for variable declarations. Keywords in the reactinos language do not necessarily consist of a single word but are often sequences of words to increase the readability.

An example showing the creation of a Java class in reaction to the creation of a UML class is shown in the following. Fictive UML and Java metaclasses are used and accessed via uml::* and java::*

reaction CreatedUmlClass {
	after element uml::Class created and inserted as root
	call {
		val umlClass = newValue
		createJavaClass(umlClass)
	}
}

routine createJavaClass(uml:Class umlClass) {
	match {
		val javaPackage = retrieve java::Package corresponding to umlClass.package
	}
	create {
		val javaClass = create java::Class
	}
	update {
		addCorrespondenceBetween(javaClass, umlClass)
		javaClass.name = umlClass.name
		javaPackage.classes += javaClass
	}
}

Statements

Routines

Match

Matches are used to identify, if a consistency relation exists for the given elements which the routine can repair. Therefore, it is especially necessary to investigate the existing correspondences. Therefore, different statements are available:

  • retrieve: Retrieves elements from the correspondence model, with an optional prepended parameter assignment via val NAME =
    • one: For single element retrievals
      • asserted: Asserts that a corresponding element exists and if not throws an exception
      • matching: Looks for a corresponding element and aborts the routine execution if none is found
      • optional: Retrieves a corresponding element if existing, but also proceeds if there is none
    • many: For many element retrievals
  • require abscence of: Requires the abscence of a corresponding element for the specified one
  • check: A generic check statement that has to evaluate to true to proceed
    • asserted: Throws an exception if the check fails
    • matching: Only stops proceeding with that routine if the check fails

Usage

Keywords

The reactions language uses keywords defined in the Xtend language and additionally the following:

  • reaction
  • routine
  • as
  • using
  • qualified
  • name
  • reactions:
  • in
  • to
  • changes
  • execute
  • actions
  • routines
  • and
  • call
  • after
  • element
  • attribute
  • with
  • inserted
  • removed
  • replaced
  • anychange
  • created
  • deleted
  • root
  • from
  • at
  • require
  • absence
  • of
  • corresponding
  • tagged
  • retrieve
  • optional
  • many
  • val
  • check
  • asserted
  • create
  • new
  • update
  • plain

Writing code

The Reactions language uses several Xtend code blocks for defining the logic of a consistency specification. In these code blocks, ordinary Xtend code can be written. Nevertheless, the usable identifiers are restricted because keywords of the Reactions language cannot be used as identifiers anymore. A list of keywords can be found here and especially comprises words like optional, update, and create.

Important: Keywords can be escaped using the ^ character, e.g. EcoreUtil.^create(...).

Modifying Reactions Language

In order to modify the Reactions language you have to modify the xtext file. To be able to use the changed language you first have to generate it with the workflow in the workflow folder and load the plug-in into an inner eclipse instance.

Writing an application

To use the Reactions language for writing an application, you have to start an Eclipse in which the Reactions language is installed, for example, an inner Eclipse from the Eclipse with your Vitruv workspace. Then, you have to create an Eclipse Plug-in project for the application. Within this project, you create a file with the extension .reactions. This file automatically opens with the Reactions language editor. If it does not, the Reactions language is not correctly installed in your Eclipse. After writing reactions in this document, code is automatically generated into the src-gen folder whenever the project is built. In each of the mir.reactions.* subfolders, an *ChangePropagationSpecification* class is created. If you define multiple reactions segments, one such folder with a ChangePropagationSpecification is generated for each of them. You may define a further reactions file that imports all the others, such that you can use the combined ChangePropagationSpecification generated from this reactions file. You should extend this class with an empty, concrete class in a package that is exported by your Plug-in and can then be used in a V-SUM to keep models consistent.

Design Decisions

The Reactions language was formerly called Response language. Some of the documentation still following this naming scheme.

Conceptual Decisions

Reactions

  • Reactions can specify a single element type of whose creation/deletion/insertion/removal to react to
    • Sometime it may seem reasonable to specifiy multiple elements, because one wants to react to any of them but they do not have a common superclass to specify
    • We decided not to allow to define multiple elements because we then have to determine a common supertype which, on the one hand, may just be EObject, which is not very helpful, or, on the other hand, may be ambiguous as both classes implement the same, different metaclasses, so we may pick an arbitrary common supertype, which could be confusing for the user.

Separation of responses and repair routines

  • The original response language allowed the definition of responses and repair routines with the following characteristics:
    • Repair routines define reusable routines for restoring a specific consistency/semantic overlap after a certain kind of modification of its elements
    • Responses define in reaction to which events (changes) a repair routine is called (i.e. when a semantic overlap may get inconsistent due to a change)
    • Responses define an implicit repair routine that can call other reusable repair routine
  • We changed the design as follows:
    • Responses still define in reaction to which events a repair routine is called
    • Responses do not define an implicit repair routine any more but simply delegate to explicit repair routines.
  • Benefits:
    • Reusability
      • Every repair routine is reusable and not potentially hidden as an implicit routine of a response
      • Also reduces the effort for extracting routines later
    • Clear responsibilities
      • Responses and repair routines have completely distinct responsibilites
      • No need to think about which part of a responses belongs to the response and what belongs to its implicit repair routine
    • Expressive naming of accessible model elements
      • In responses, model elements within the change are accessed by its properties like ''change.newValue'' without knowing what ''newValue'' is
      • Repair routines have named arguments, so that the model elements that are used in during repair have expressive names
  • Drawbacks:
    • Verboseness
      • It requires more effort to explicitly define the routine with its inputs and delegate to it from the response (if the repair routine is not reused)
      • More first-level elements blow up responses documents because several routines were hidden in responses before

Concrete Syntax Change Log

To increase the understandability of reactions and their conformance with mainstream programming languages, we performed a major refactoring of the syntax in 10/2016 as described in the following.

Renamed Keywords

  • response renamed to reaction
  • effect renamed to action
  • add for after value replaced
  • add in after inserted or removed
  • renamed root created to root inserted and root deleted to root removed
  • renamed initialized as to and initialize (to avoid usage of Xbase's typecast keyword as)

Minor Changes

  • create action: name of created element first then equals sign (to avoid usage of Xbase's typecast keyword as)
  • for retrieve statement (required or optional): start with val and name of retrieved element, then equals sign, then retrieve
  • reaction name optional
  • new header: reactions ReactionsName in reaction to changes in SourceMM execute actions in TargetMM

Major Changes

  • new change type element, qualified meta class name , (created|deleted) and optionally allow to specify created and or deleted and for list entry and for root changes
    • temporary solution: write explicit create and insert respectively delete and remove and keep to the current way in which the monitor only produces these cases
    • later: add new possibility to have insert without create and remove without delete later
  • optional last statement in a routine: return, variable declaration and assignment possible when routine is called, we have to generate not only correct return type for the method but also final variable declarations and assignments when they are called
  • have a new "execute action block" and only allow calls in "call action block"
  • address metaclasses via Metamodel::Metaclass instead of Metamodel.Metaclass
  • make simple name addressing of metaclasses default, replace keyword using simple names with using qualified names enabling addressing of metaclasses via qualified names, which may be neccessary due to naming conflicts.