OCL
Content may include inaccuracies, outdated information, or technical errors. Users are advised to cross-check critical information before implementation.
Object Constraint Language is the standard language for writing invariants. It reads a model and never changes it. It is statically typed: the expression is parsed against the metaclass of the contextual object, so a misspelt feature or a mismatched comparison is reported before the expression runs.
Writing an expression
An OCL body is a single query expression. Feature navigation is a dot, collection operations use an arrow:
self.name <> null
self.outgoingRelationships->size() > 0
The context of the expression is the metaclass of self, so the features of
that type are directly reachable and content assist offers them.
Variables in scope
self is the object the expression is evaluated against, and it is the only
variable bound from outside the expression. In a validation rule it is typed as
the metaclass the rule is declared on. Which object it is depends on where the
expression runs - see The expression console.
self may be left implicit: a name that is not otherwise bound is looked up as
a feature of self, so name and self.name are the same expression.
let introduces further names inside the expression itself, and the type of a
let variable is written out:
let n : String = self.name in n <> null and n.size() > 3
Type tests and conditionals
Types are qualified with the metamodel package, as in
archimate::ServingRelationship. oclIsKindOf(T) tests a type and its
subtypes, oclIsTypeOf(T) the exact type, and oclAsType(T) narrows a value so
the features of the narrower type become reachable.
Conditionals are expressions, and the endif is required:
if self.documentation = null then false else self.documentation.size() > 0 endif
implies is true whenever its left side is false, which is how a check that
does not apply to an object is written.
Examples
Against ArchiMate, where self is an element:
The element carries a name:
self.name <> null and self.name.size() > 0
Every element carries an owner property:
self.properties->exists(p | p.key = 'owner')
An element that is documented must also be named:
self.documentation <> null implies self.name <> null
Nothing serves this element twice:
self.incomingRelationships
->select(r | r.oclIsKindOf(archimate::ServingRelationship))
->isUnique(r | r.source)
Every outgoing realization ends on a business service:
self.outgoingRelationships
->select(r | r.oclIsKindOf(archimate::RealizationRelationship))
->forAll(r | r.target.oclIsKindOf(archimate::BusinessService))
The names of everything this element depends on:
self.outgoingRelationships->collect(r | r.target.name)
An element is either connected or explicitly documented as standalone:
let connected : Boolean =
self.incomingRelationships->notEmpty()
or self.outgoingRelationships->notEmpty()
in connected or self.properties->exists(p | p.key = 'standalone')
Against ArchiMate, where self is the model:
The model states its purpose:
self.purpose <> null and self.purpose.size() > 0
Every top-level folder is named:
self.folders->forAll(f | f.name <> null and f.name.size() > 0)
Further reading
OMG Object Constraint Language specification
Language reference
Expression form
A body is one query expression. There are no statements, no declarations outside
the expression, and no assignment. let and the variables an iterator
introduces are the only ways to name a value.
self is the only variable bound from outside the expression. Where a boolean
is required, an expression whose type is not Boolean is reported as an error
before it runs.
Metaclasses are reachable from the metamodels of the model being queried, and
the ecore package is always reachable, so ecore::EObject and the other Ecore
metaclasses may be named in any expression.
| Syntax | Meaning |
|---|---|
-- text | comment, to the end of the line |
/* text */ | comment, spanning lines |
_'name' | a name that would otherwise clash with a keyword - self._'name' |
Operator precedence
Highest first. Operators on the same row bind left to right.
| Operators | Category |
|---|---|
:: | qualification of a name by its package or type |
. -> | feature navigation, operation call, iterator call |
not, unary - | logical negation, arithmetic negation |
* / | multiplication, division |
+ - | addition, subtraction |
< > <= >= | ordering comparison |
= <> | equality, inequality |
and | conjunction |
or | disjunction |
xor | exclusive disjunction |
implies | implication |
and binds tighter than or, and or binds tighter than xor, so
true or true and false is true and true xor true or true is false. An
if ... endif is a self-contained operand: if true then 1 else 2 endif + 1 is
2.
Literals
| Syntax | Type | Meaning |
|---|---|---|
42 | Integer | integer literal |
1.5 | Real | real literal |
1.5e-2, 1e3 | Real | real literal with an exponent |
* | UnlimitedNatural | the unlimited value |
'text' | String | string literal |
true, false | Boolean | boolean literals |
null | OclVoid | the absent value |
invalid | OclInvalid | the error value |
Set{1, 2, 3} | Set(T) | unordered, no duplicates |
OrderedSet{1, 2} | OrderedSet(T) | ordered, no duplicates |
Bag{1, 1, 2} | Bag(T) | unordered, duplicates kept |
Sequence{1, 2, 3} | Sequence(T) | ordered, duplicates kept |
Sequence{1..5} | Sequence(Integer) | every integer in the range |
Set{} | Set(T) | the empty collection of that kind |
Tuple{a = 1, b = 'x'} | Tuple(...) | tuple, part types inferred |
Tuple{a : Integer = 1} | Tuple(...) | tuple with a declared part type |
A tuple part is read with a dot: Tuple{a = 1, b = 2}.a is 1.
Inside a string literal the escapes are \b, \t, \n, \f, \r, \', \"
and \\. Any other backslash is rejected by the parser, so a backslash that has
to reach a regular expression is written \\. Two string literals written next
to each other join into one, so 'ab' 'cd' is 'abcd'.
Type syntax
| Syntax | Meaning |
|---|---|
archimate::BusinessActor | a metaclass, qualified by its metamodel package |
String, Integer, Real, Boolean, UnlimitedNatural | primitive types |
OclAny | the type every value conforms to |
OclVoid, OclInvalid | the types of null and invalid |
Set(T), OrderedSet(T), Bag(T), Sequence(T) | collection types |
Collection(T) | any of the four collection types |
Tuple(a : Integer, b : String) | a tuple type |
Operations on every value
Available on any single value, whatever its type.
| Syntax | Returns | Meaning |
|---|---|---|
a = b | Boolean | equality |
a <> b | Boolean | inequality |
v.oclIsKindOf(T) | Boolean | v is a T or a subtype of T |
v.oclIsTypeOf(T) | Boolean | v is exactly a T |
v.oclAsType(T) | T | v seen as a T, so T's features are reachable |
v.oclIsUndefined() | Boolean | v is null or invalid |
v.oclIsInvalid() | Boolean | v is invalid |
v.oclAsSet() | Set(T) | v as a one-element set, empty when v is null |
v.toString() | String | a string rendering of v |
T.allInstances() | Set(T) | every instance of T in the model holding self |
allInstances() also accepts the :: form, as in
archimate::BusinessActor::allInstances().
A collection carries =, <>, oclIsUndefined(), oclIsInvalid() and
oclAsSet(); on a collection oclAsSet() wraps the collection itself, so
Set{1, 2}->oclAsSet()->size() is 1. oclIsKindOf, oclIsTypeOf and
oclAsType apply to single values only. To test the type of the elements of a
collection, apply oclIsKindOf inside an iterator, or use selectByKind.
String operations
Positions are 1-based, and substring bounds are inclusive.
| Syntax | Returns | Meaning |
|---|---|---|
s + t | String | concatenation |
s.concat(t) | String | concatenation |
s.size() | Integer | number of characters |
s.at(i) | String | the character at position i - 'abc'.at(1) is 'a' |
s.substring(lower, upper) | String | 'abc'.substring(1, 2) is 'ab' |
s.characters() | Sequence(String) | one entry per character |
s.indexOf(t) | Integer | position of the first t, 0 when absent |
s.lastIndexOf(t) | Integer | position of the last t, 0 when absent |
s.startsWith(t) | Boolean | s begins with t |
s.endsWith(t) | Boolean | s ends with t |
s.equalsIgnoreCase(t) | Boolean | equality ignoring case |
s.matches(regex) | Boolean | the whole of s matches the regular expression |
s.replaceAll(regex, r) | String | every match of regex replaced by r |
s.replaceFirst(regex, r) | String | the first match of regex replaced by r |
s.substituteAll(regex, r) | String | every match of regex replaced by r |
s.substituteFirst(regex, r) | String | the first match of regex replaced by r |
s.tokenize() | Sequence(String) | split on whitespace |
s.tokenize(delimiters) | Sequence(String) | split on any character of delimiters |
s.tokenize(delimiters, keep) | Sequence(String) | split, keeping the delimiters when keep is true |
s.trim() | String | leading and trailing whitespace removed |
s.toUpperCase(), s.toUpper() | String | upper case |
s.toLowerCase(), s.toLower() | String | lower case |
s.toInteger() | Integer | parsed as an integer |
s.toReal() | Real | parsed as a real |
s.toBoolean() | Boolean | parsed as a boolean |
s < t, s <= t, s > t, s >= t | Boolean | lexicographic comparison |
The pattern argument of matches, replaceAll, replaceFirst, substituteAll
and substituteFirst is a regular expression, and its backslashes are doubled
so that the string parser keeps them: 'a.b'.replaceAll('\\.', '+') is 'a+b'.
Number operations
On Integer, Real and UnlimitedNatural. / produces a Real, so 3 / 2
is 1.5.
| Syntax | Returns | Meaning |
|---|---|---|
a + b, a - b, a * b | Integer or Real | arithmetic |
a / b | Real | division |
-a | Integer or Real | negation |
a.abs() | Integer or Real | absolute value |
a.max(b), a.min(b) | Integer or Real | larger, smaller |
a.floor() | Integer | largest integer no greater than a |
a.round() | Integer | nearest integer |
a.div(b) | Integer | integer division - 7.div(2) is 3 |
a.mod(b) | Integer | remainder - 7.mod(2) is 1 |
n.toInteger() | Integer | an UnlimitedNatural as an Integer |
a < b, a <= b, a > b, a >= b | Boolean | ordering comparison |
* is the unlimited UnlimitedNatural value, and *.toInteger() is invalid.
Boolean operations
| Syntax | Returns | Meaning |
|---|---|---|
a and b | Boolean | conjunction |
a or b | Boolean | disjunction |
a xor b | Boolean | true when exactly one side is true |
not a | Boolean | negation |
a implies b | Boolean | true whenever a is false |
Collection kinds
| Type | Ordered | Duplicates |
|---|---|---|
Set(T) | no | no |
OrderedSet(T) | yes | no |
Bag(T) | no | yes |
Sequence(T) | yes | yes |
-> applied to a value that is not a collection treats it as a one-element
collection, so 'abc'->size() is 1 while 'abc'.size() is 3.
Operations on every collection
Available on Set, OrderedSet, Bag and Sequence.
| Syntax | Returns | Meaning |
|---|---|---|
c->size() | Integer | number of elements |
c->isEmpty() | Boolean | c has no elements |
c->notEmpty() | Boolean | c has at least one element |
c->count(x) | Integer | occurrences of x |
c->includes(x) | Boolean | x is an element of c |
c->excludes(x) | Boolean | x is not an element of c |
c->includesAll(d) | Boolean | every element of d is in c |
c->excludesAll(d) | Boolean | no element of d is in c |
c->sum() | Integer or Real | the elements added together |
c->max(), c->min() | Integer or Real | largest, smallest element |
c->product(d) | Set(Tuple(first, second)) | every pairing of an element of c with one of d |
c->selectByKind(T) | collection of T | the elements that are a T or a subtype |
c->selectByType(T) | collection of T | the elements that are exactly a T |
c->including(x) | see below | c with x added |
c->excluding(x) | see below | c with every x removed |
c->flatten() | see below | nested collections merged one level in |
sum(), max() and min() apply to collections of numbers. including(),
excluding() and flatten() give a Set from a Set or an OrderedSet, a
Bag from a Bag, and a Sequence from a Sequence, so
OrderedSet{1, 2}->including(3) is a Set.
Conversions
Available on Set, OrderedSet, Bag and Sequence.
| Syntax | Returns | Meaning |
|---|---|---|
c->asSet() | Set(T) | order and duplicates dropped |
c->asOrderedSet() | OrderedSet(T) | duplicates dropped, order kept |
c->asBag() | Bag(T) | order dropped, duplicates kept |
c->asSequence() | Sequence(T) | order and duplicates kept |
Set and OrderedSet operations
| Syntax | Returns | Meaning |
|---|---|---|
c->union(d) | Set(T) or Bag(T) | every element of either side; a Bag when d is a Bag |
c->intersection(d) | Set(T) | the elements in both sides |
c - d | Set(T) | the elements of c that are not in d |
c->symmetricDifference(d) | Set(T) | the elements in exactly one side |
Bag operations
| Syntax | Returns | Meaning |
|---|---|---|
c->union(d) | Bag(T) | every element of either side, duplicates kept |
c->intersection(d) | Bag(T) | the elements in both sides |
Ordered collection operations
Available on OrderedSet and Sequence. Positions are 1-based.
| Syntax | Returns | Meaning |
|---|---|---|
c->first() | element type | the first element |
c->last() | element type | the last element |
c->at(i) | element type | the element at position i |
c->indexOf(x) | Integer | the position of x |
c->append(x) | same kind | c with x added at the end |
c->prepend(x) | same kind | c with x added at the front |
c->insertAt(i, x) | same kind | c with x inserted at position i |
c->subOrderedSet(lower, upper) | OrderedSet(T) | the elements between the two inclusive positions |
c->subSequence(lower, upper) | Sequence(T) | the elements between the two inclusive positions |
subOrderedSet applies to an OrderedSet, subSequence to a Sequence. A
Sequence unions only with another Sequence, and concatenates rather than
merging: Sequence{1,2}->union(Sequence{3}) is Sequence{1, 2, 3}.
Iterators
The body is separated from the iterator variable by |. The variable's type may
be written out - c->select(e : archimate::Element | ...) - or left to be
inferred.
| Syntax | Returns | Meaning |
|---|---|---|
c->select(e | body) | same kind as c | the elements for which body is true |
c->reject(e | body) | same kind as c | the elements for which body is false |
c->collect(e | body) | Bag(T) or Sequence(T) | body per element, nested results flattened one level |
c->collectNested(e | body) | Bag(T) or Sequence(T) | body per element, without flattening |
c->forAll(e | body) | Boolean | body is true for every element |
c->exists(e | body) | Boolean | body is true for at least one element |
c->one(e | body) | Boolean | body is true for exactly one element |
c->any(e | body) | element type | one element for which body is true |
c->isUnique(e | body) | Boolean | body gives a different value for every element |
c->sortedBy(e | body) | OrderedSet(T) or Sequence(T) | the elements ordered by body |
c->closure(e | body) | Set(T) or OrderedSet(T) | body applied over and over until nothing new is found |
c->iterate(e; acc : T = init | body) | T | folds body over the elements, carrying acc |
The body of select, reject, forAll, exists, one and any must be a
Boolean, and the body of sortedBy must be a type that carries <.
select and reject keep the kind of the source collection. collect and
collectNested give a Bag from a Set or a Bag and a Sequence from a
Sequence or an OrderedSet. sortedBy gives an OrderedSet from a Set or
an OrderedSet, and a Sequence from a Bag or a Sequence. The body of
closure must produce the element type of the source collection, or a
collection of it. closure gives an OrderedSet when the body is an
OrderedSet or a Sequence, and a Set otherwise.
forAll and exists accept two iterator variables, and then range over every
pairing:
self.folders->forAll(f1, f2 | f1 <> f2 implies f1.name <> f2.name)
iterate names an accumulator, its type and its starting value. The accumulator
type is written out:
self.folders->iterate(f; total : Integer = 0 | total + f.elements->size())
Model navigation
| Syntax | Returns | Meaning |
|---|---|---|
self.feature | the feature's type | the value of a feature |
name | the feature's type | the same, with self left implicit |
c.feature | Bag or Sequence | the feature of every element - the same as c->collect(e | e.feature) |
T.allInstances() | Set(T) | every instance of T in the model holding self |
v.oclAsType(T) | T | narrows v, making T's features reachable |
c->selectByKind(T) | collection of T | the elements of c that are a T or a subtype |
c->closure(e | e.feature) | Set(T) or OrderedSet(T) | follows feature transitively |
Every object is reached through the features its metaclass declares. A
container, a containment and an inverse reference are read the same way as any
other feature. In ArchiMate an element reaches the relationships that point at
it through incomingRelationships, and a model reaches its contents through
folders. Where the metamodel declares no feature for a link, allInstances()
with a select finds the objects that refer to self:
archimate::Relationship::allInstances()->select(r | r.target = self)
Navigating a feature of null gives invalid rather than null, so
self.parent.name is invalid when parent is unset. oclIsUndefined() tests
for both, and -> on an unset feature gives an empty collection, so
self.parent->isEmpty() is true.
Conditional and variable declarations
| Syntax | Returns | Meaning |
|---|---|---|
if cond then a else b endif | the type of a and b | conditional; both branches and endif are required |
let n : T = value in body | the type of body | binds n to value for the whole of body |
let a : T = x, b : T = y in body | the type of body | binds several names at once |
A let variable is declared with its type; the type cannot be left out. let
expressions nest, and one may appear inside an iterator body:
self.folders->select(f | let n : String = f.name in n <> null and n.size() > 3)