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 (EMF) Core, a lightweight framework for data models. The textual syntax in Refinery for defining metamodels is largely compatible with 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.
% 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:
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:
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.
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.
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:
class Vertex.
class Transition {
Vertex[1] source
Vertex[1] target
}
You may add the refers
keyword for compatibility with Xcore. The following specification is equivalent:
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
}
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.
class Person {
Person[] friend opposite friend
}
Multiplicityβ
Multiplicity constraints 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.
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 constraints associated with source
.
class Vertex {
Transition[2..3] outgoingTransition opposite source
}
class Transition {
Vertex[1] source opposite outgoingTransition
}
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
.
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:
Containment edges form must form a forest.
In contrast, non-containment references, such as target
, may cross the containment hierarchy.