Skip to main content

OCL

This document was generated using AI assistance

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.

An OCL invariant in the console, answered with the yes-or-no it asks for

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.

SyntaxMeaning
-- textcomment, 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.

OperatorsCategory
::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
andconjunction
ordisjunction
xorexclusive disjunction
impliesimplication

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

SyntaxTypeMeaning
42Integerinteger literal
1.5Realreal literal
1.5e-2, 1e3Realreal literal with an exponent
*UnlimitedNaturalthe unlimited value
'text'Stringstring literal
true, falseBooleanboolean literals
nullOclVoidthe absent value
invalidOclInvalidthe 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

SyntaxMeaning
archimate::BusinessActora metaclass, qualified by its metamodel package
String, Integer, Real, Boolean, UnlimitedNaturalprimitive types
OclAnythe type every value conforms to
OclVoid, OclInvalidthe 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.

SyntaxReturnsMeaning
a = bBooleanequality
a <> bBooleaninequality
v.oclIsKindOf(T)Booleanv is a T or a subtype of T
v.oclIsTypeOf(T)Booleanv is exactly a T
v.oclAsType(T)Tv seen as a T, so T's features are reachable
v.oclIsUndefined()Booleanv is null or invalid
v.oclIsInvalid()Booleanv is invalid
v.oclAsSet()Set(T)v as a one-element set, empty when v is null
v.toString()Stringa 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.

SyntaxReturnsMeaning
s + tStringconcatenation
s.concat(t)Stringconcatenation
s.size()Integernumber of characters
s.at(i)Stringthe 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)Integerposition of the first t, 0 when absent
s.lastIndexOf(t)Integerposition of the last t, 0 when absent
s.startsWith(t)Booleans begins with t
s.endsWith(t)Booleans ends with t
s.equalsIgnoreCase(t)Booleanequality ignoring case
s.matches(regex)Booleanthe whole of s matches the regular expression
s.replaceAll(regex, r)Stringevery match of regex replaced by r
s.replaceFirst(regex, r)Stringthe first match of regex replaced by r
s.substituteAll(regex, r)Stringevery match of regex replaced by r
s.substituteFirst(regex, r)Stringthe 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()Stringleading and trailing whitespace removed
s.toUpperCase(), s.toUpper()Stringupper case
s.toLowerCase(), s.toLower()Stringlower case
s.toInteger()Integerparsed as an integer
s.toReal()Realparsed as a real
s.toBoolean()Booleanparsed as a boolean
s < t, s <= t, s > t, s >= tBooleanlexicographic 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.

SyntaxReturnsMeaning
a + b, a - b, a * bInteger or Realarithmetic
a / bRealdivision
-aInteger or Realnegation
a.abs()Integer or Realabsolute value
a.max(b), a.min(b)Integer or Reallarger, smaller
a.floor()Integerlargest integer no greater than a
a.round()Integernearest integer
a.div(b)Integerinteger division - 7.div(2) is 3
a.mod(b)Integerremainder - 7.mod(2) is 1
n.toInteger()Integeran UnlimitedNatural as an Integer
a < b, a <= b, a > b, a >= bBooleanordering comparison

* is the unlimited UnlimitedNatural value, and *.toInteger() is invalid.

Boolean operations

SyntaxReturnsMeaning
a and bBooleanconjunction
a or bBooleandisjunction
a xor bBooleantrue when exactly one side is true
not aBooleannegation
a implies bBooleantrue whenever a is false

Collection kinds

TypeOrderedDuplicates
Set(T)nono
OrderedSet(T)yesno
Bag(T)noyes
Sequence(T)yesyes

-> 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.

SyntaxReturnsMeaning
c->size()Integernumber of elements
c->isEmpty()Booleanc has no elements
c->notEmpty()Booleanc has at least one element
c->count(x)Integeroccurrences of x
c->includes(x)Booleanx is an element of c
c->excludes(x)Booleanx is not an element of c
c->includesAll(d)Booleanevery element of d is in c
c->excludesAll(d)Booleanno element of d is in c
c->sum()Integer or Realthe elements added together
c->max(), c->min()Integer or Reallargest, 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 Tthe elements that are a T or a subtype
c->selectByType(T)collection of Tthe elements that are exactly a T
c->including(x)see belowc with x added
c->excluding(x)see belowc with every x removed
c->flatten()see belownested 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.

SyntaxReturnsMeaning
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

SyntaxReturnsMeaning
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 - dSet(T)the elements of c that are not in d
c->symmetricDifference(d)Set(T)the elements in exactly one side

Bag operations

SyntaxReturnsMeaning
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.

SyntaxReturnsMeaning
c->first()element typethe first element
c->last()element typethe last element
c->at(i)element typethe element at position i
c->indexOf(x)Integerthe position of x
c->append(x)same kindc with x added at the end
c->prepend(x)same kindc with x added at the front
c->insertAt(i, x)same kindc 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.

SyntaxReturnsMeaning
c->select(e | body)same kind as cthe elements for which body is true
c->reject(e | body)same kind as cthe 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)Booleanbody is true for every element
c->exists(e | body)Booleanbody is true for at least one element
c->one(e | body)Booleanbody is true for exactly one element
c->any(e | body)element typeone element for which body is true
c->isUnique(e | body)Booleanbody 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)Tfolds 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

SyntaxReturnsMeaning
self.featurethe feature's typethe value of a feature
namethe feature's typethe same, with self left implicit
c.featureBag or Sequencethe 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)Tnarrows v, making T's features reachable
c->selectByKind(T)collection of Tthe 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

SyntaxReturnsMeaning
if cond then a else b endifthe type of a and bconditional; both branches and endif are required
let n : T = value in bodythe type of bodybinds n to value for the whole of body
let a : T = x, b : T = y in bodythe type of bodybinds 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)