Skip to main content
Version: 0.1.5-SNAPSHOT 🚧

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:

Region::newRegionState::newState

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:

invalidRegionState

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.

Region::newRegionState::newCompositeElementVertexRegularStateState

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
}
Vertex::newVertexTransition::newTransitionsourcetarget

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

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
}
Person::newPersonfriend

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.

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 constraints associated with source.

class Vertex {
Transition[2..3] outgoingTransition opposite source
}
class Transition {
Vertex[1] source opposite outgoingTransition
}
v1VertexoutgoingTransitionsourcet1TransitionsourceoutgoingTransitiont2Transitionv2VertexoutgoingTransition::invalidMultiplicityt3TransitionsourceoutgoingTransitiont4Transitionv3VertexoutgoingTransition::invalidMultiplicitysourceoutgoingTransitiont5TransitionoutgoingTransitiont6TransitionoutgoingTransitiont7TransitionoutgoingTransitionsourcesourcesource

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:

v1Vertexr1Regionregiont1TransitionoutgoingTransitiont2TransitionoutgoingTransitionincomingTransitiont3Transitionverticesv2VertexverticesregionincomingTransitionv3VertexincomingTransitionr2RegionregiontargetverticesoutgoingTransitiontargetsourcetargetsourcesource

Containment edges form must form a forest. In contrast, non-containment references, such as target, may cross the containment hierarchy.