--- SPDX-FileCopyrightText: 2024 The Refinery Authors SPDX-License-Identifier: EPL-2.0 description: Metamodeling in Refinery sidebar_position: 0 --- # Classes and references Refinery supports _metamodeling_ to describe the desired structure of generated models. The metamodeling facilities are inspired by object-oriented software and the [Eclipse Modeling Foundation](https://eclipse.dev/modeling/emf/) (EMF) Core, a lightweight framework for data models. The textual syntax in Refinery for defining metamodels is largely compatible with [Xcore](https://wiki.eclipse.org/Xcore), a textual syntax for EMF metamodels. ## Classes Classes are declared with the `class` keyword. Like in many programming languages, class members are specified between curly braces `{}`. If a class has no members, the declaration may be terminated with a `.` instead. ```refinery % Class with no members. class Region {} % Alternative syntax without curly braces. class State. ``` By default, a _new object_ is added to the partial model to represent the instances of a class. For example, the new objects `Region::new` and `State::new` represent potential instances of the classes `Region` and `State`, respectively: import NewObjectsSimple from './NewObjectsSimple.svg'; As you can see, no new objects represent potential nodes that are instanceof of both `Region` and `State`. In fact, such instances are not permitted at all. Each node must the instance of a _single most-specific class:_ import InvalidInstance from './InvalidInstance.svg'; ### Inheritance Like in object-oriented programming languages, classes may declare _superclasses_ with the `extends` keyword. The inheritance hierarchy may not contain any cycles (a class cannot be a superclass of itself), but _multiple inheritance_ is allowed. Classes that can't be instantiated directly (i.e., a subclass must be instantiated instead) can be marked with the `abstract` keyword. Such classes do not have a _new object,_ since there are no direct instances to represent. ```refinery abstract class CompositeElement. class Region. abstract class Vertex. abstract class RegularState extends Vertex. class State extends RegularState, CompositeElement. ``` Notice that the new object `State::new` is an instance of `CompositeElement`, `Vertex`, `RegularState`, and `State` as well. import NewObjectsWithInheritance from './NewObjectsWithInheritance.svg'; ## References The graph structure of model generated by Refinery is determined by the _references_ of the metamodel, which will appear as labeled edges between nodes (class instances). References are declared as class members by providing the _target type,_ and optional _multiplicity,_ and the name of the reference: ```refinery class Vertex. class Transition { Vertex[1] source Vertex[1] target } ``` import ReferencesSimple from './ReferencesSimple.svg'; You may add the `refers` keyword for compatibility with [Xcore](https://wiki.eclipse.org/Xcore). The following specification is equivalent: ```refinery class Vertex. class Transition { refers Vertex[1] source refers Vertex[1] target } ``` ### Opposite constraints The `opposite` keywords specifies that two references are in an _opposite_ relationship, i.e., if one reference is present in a direction, the other must be present between the same nodes in the opposite direction. ``` class Vertex { Transition[] outgoingTransition opposite source Transition[] incomingTransition opposite target } class Transition { Vertex[1] source opposite outgoingTransition Vertex[1] target opposite incomingTransition } ``` import ReferencesOppositeInstance from './ReferencesOppositeInstance.svg'; Opposites must be declared in pairs: it is a specification error to declare the `opposite` for one direction but not the other. Unlike in EMF, references that are the `opposite` of themselves are also supported. These must always be present in both directions between two nodes. Thus, they correspond to undirected graph edges. ```refinery class Person { Person[] friend opposite friend } ``` import ReferencesOppositeSelf from './ReferencesOppositeSelf.svg'; ### Multiplicity _Multiplicity constrains_ can be provided after the reference type in square braces. They specify how many _outgoing_ references should exist for any given instance of the class. :::info To control the number of _incoming_ references, add an `opposite` reference with multiplicity constraint. ::: A multiplicity constraint is of the form `[n..m]`, where the non-negative integer `n` is the _lower_ bound of outgoing references, and `m` is a positive integer or `*` corresponding to the _upper_ bound of outgoing references. The value of `*` represent a reference with _unbounded_ upper multiplicity. If `n` = `m`, the shorter form `[n]` may be used. The bound `[0..*]` may be abbreviated as `[]`. If the multiplicity constraint is omitted, the bound `[0..1]` is assumed. --- In the following model, the node `v1` satisfies all multiplicity constraints of `outgoingTransition`. The node `v2` violates the lower bound constraint, while `v3` violates the upper bound constraint. All `Transition` instances satisfy the multiplicity constrains associated with `source`. ```refinery class Vertex { Transition[2..3] outgoingTransition opposite source } class Transition { Vertex[1] source opposite outgoingTransition } ``` import MultiplicityConstraintsInstance from './MultiplicityConstraintsInstance.svg'; ### Containment hierarchy To structure models and ensure their connectedness, Refinery supports _containment_ constraints. References may be marked as _containment_ references with the `contains` keyword. Classes that are the _target type_ of at least one _containment_ reference are considered `contained`. An instance of a `contained` class must have exactly 1 incoming containment reference. Instances of classes that are not `contained` must _not_ have any incoming containment references. Containment references have to form a _forest_, i.e., they must not contain any cycles. The _roots_ of the forest are instances of classes that are not `contained`, while `contained` classes for the internal nodes and leaves of the trees. Opposites of _containment_ references have to be marked with the `container` keyword. They must not specify any multiplicity constraint, since the multiplicity is already implied by the containment hierarchy. --- In the following model, the instances of `Region` are the roots of the containment hierarchy. The classes `Vertex` are `Transition` are both considered `contained`. ```refinery class Region { contains Vertex[] vertices opposite region } class Vertex { container Region region opposite vertices contains Transition[] outgoingTransition opposite source Transition[] incomingTransition opposite target } class Transition { container Vertex source opposite outgoingTransition Vertex[1] target opposite incomingTransition } ``` Containment edges are show with **thick** lines: import ContainmentInstance from './ContainmentInstance.svg'; Containment edges form trees, while non-containment references, such as `target`, may point across the containment hierarchy.