OCL

The Object Constraint Language

License

Plan

  1. Introduction

  2. Invariants

  3. Model Navigation

  4. Property Definition

  5. Operation Specification

  6. Advanced Topics

  7. Conclusion

  8. Appendix - Language Details

Introduction

What is OCL?

OCL stands for «Object Constraint Language».

OCL is:
  • a OMG standard (see http://www.omg.org/spec/OCL/).

  • a formal and unambiguous language, but easy to use (even for non mathematicians).

  • a complement to UML (and also to MOF, but that is another history).

Why do I need OCL?

Sometimes, the UML lacks precision. Suppose the following class diagram:

family
  • How do you specify that this class only considers people born after 1900?

  • And how do you specify that cycles are not allowed (i.e., that a person cannot be an ancestor of himself)?

What About Comments?

marriage
anna bob carol
  • Comments, expressed in natural languages, are often very useful.

  • But sometimes, they are also ambiguous.

  • Still, comments cannot avoid some situations.

How can OCL add more precision to UML?

  • By adding constraints to modeling elements:

context Person
inv: self.wife->notEmpty() implies self.wife.husband = self and
    self.husband->notEmpty() implies self.husband.wife = self
marriage

Plan

  1. Introduction

  2. Invariants

  3. Model Navigation

  4. Property Definition

  5. Operation Specification

  6. Advanced Topics

  7. Conclusion

  8. Appendix - Language Details

Invariants

Class Invariants

  • A class invariant is a constraint that must be verified by all instances of a class, when in a stable state.

  • The notion of stable state is important: an invariant may be broken during the execution of an operation.

  • It is commonly accepted that an instance is in a stable state between the execution of two public operations.

Invariants: Graphical Notation

  • Invariants can be placed directly on the modeling element, between braces ({}) or on a comment attached to it:

person inv
person inv note

Invariants: Textual Notation

Invariants may also be placed on a separate document. In this case, the notion of context is important.

context Person inv: self.age < 150

context Person inv: age < 150

«Context»

  • Every OCL expression is attached to a specific context: a UML modeling element.

  • The context may be referenced inside the expression using the self keyword.

context Person inv: self.age < 150
context Person inv: self.age > 0
Diagram

Context Properties

  • The context allows the access to some properties from the attached modeling element.

  • In the case of a UML class, this means: attributes, query operations, and states (from attached state machines).

person
context Person
inv:
  self.name.size() > 1 and
  self.age() >= 0 and
  self.oclInState(Single)

Plan

  1. Introduction

  2. Invariants

  3. Model Navigation

  4. Property Definition

  5. Operation Specification

  6. Advanced Topics

  7. Conclusion

  8. Appendix - Language Details

Model Navigation

OCL & UML: Basic Principles

  • OCL expressions have no side effect, they cannot modify the model.

  • A OCL expression refers to the following constituents:

    • Values of basic types: Integer, Real, Boolean, String, UnlimitedNatural;

    • Modeling elements, from the associated UML model;

    • Collections of values or modeling elements.

university

Operation Call Syntax

  • Operation calls on elements and values use dots:

'Nantes'.substring(1,3) = 'Nan'
  • Operation calls on collections use arrows:

{1, 2, 3, 4, 5}->size() = 5

Role Navigation

An OCL expression can navigate through model associations using the opposite role (association end):

univ department
context Department
    -- A department’s university should not be null.
    inv: not self.university.oclIsUndefined()

context University
    -- A university must have at least one department
    inv: self.department->notEmpty()

Multiplicities

course
The type of an expression (its return type) depends on the role’s maximum multiplicity:
  • If equals to 1, it’s a simple element.

  • If > 1, it’s a collection.

context Course
  -- an objet:
  inv: self.instructor.oclInState(Available)

  -- a collection (Set):
  inv: self.is_mastered_by->notEmpty()

Navigation: Special Cases

department professor
wife
  • When there is no role name, the OCL uses the class name (in lower cases).

  • Monovalued (max multiplicity = 1) roles may be navigated as a collection.

context Department inv: self.chef->size() = 1

context Department inv: self.chef.age > 40

context Person inv: self.wife->notEmpty()
    implies self.wife.gender = Gender::female

Navigation through Association-Classes

  • To navigate towards an association-class, OCL uses the association-class’ name, in lower cases.

context Student
inv:
    -- A student average grade is always greater than 4:
    self.grade.value->average() > 4
grade

Navigation through Association-Classes

  • To navigate from class-association, OCL uses role names:

context Grade inv:
    self.students.age() >= 18
    self.follows.hours > 3
grade

Qualified Associations

qualified association
  • To navigate through a qualified association, OCL uses the qualifier name between square brackets:

context University
    -- The name of student 8764423 must be 'Martin'.
    inv: self.students[8764423].name = 'Martin'
  • When the qualifier is not specified, the result is a collection:

context University
    -- There is at least one student named 'Martin':
    inv: self.students->exists(each | each.name = 'Martin')

Plan

  1. Introduction

  2. Invariants

  3. Model Navigation

  4. Property Definition

  5. Operation Specification

  6. Advanced Topics

  7. Conclusion

  8. Appendix - Language Details

Property Definition, Initialization, and Calculation

Property Definition

  • OCL allows the definition of new attributes and new operations, and add them to an existing class.

  • These new properties can be used within other OCL constraints.

Syntax:

context <class-name>
  def: <attr-name> : <type> = <ocl-expression>
  def: <op-name> (<argument-list) : type = <ocl-expression>

Property Definition

  • Useful to decompose complex expressions without overloading the model.

  • Examples:

context Professor
def: students() : Bag(Student) =
    self.teaches.students

context Department
def: students() : Set(Student) =
    self.instructors.teaches.student->asSet()
university

Property Initialization

  • Initial value specification for attributes and roles.

  • The expression type must conform to the attribute or role type.

Syntax:

context <class-name>::<prop-name>: <type>
    init: <ocl-expression>

Example:

context Professor::wage : Integer
    init: 800

Derived Property Specification

course
  • OCL expression defining how a derived property is calculated.

Syntax:

context <class-name>::<role-name>: <type>
    derive:  <ocl-expression>

Examples:

context Professor::service : Integer
    derive: self.teaches.hours->sum()

context Person::single : Boolean
    derive: self.partner->isEmpty()

Query Operation Specification

  • Specification of query operation body.

Example:

context University::instructors() : Set(Professor)
body:
    self.departments.instructors->asSet()

Plan

  1. Introduction

  2. Invariants

  3. Model Navigation

  4. Property Definition

  5. Operation Specification

  6. Advanced Topics

  7. Conclusion

  8. Appendix - Language Details

Operation Specification

Operation Specification

OCL can be used to specify class operations:
  • Approach inspired from Abstract Types.

  • An operation is defined by:

    • A signature;

    • A precondition; and

    • A postcondition.

  • The precondition constraints the operation input set.

  • The postcondition specifies the operation semantics.

Operation Precondition

  • A precondition is a constraint that must be verified before the execution of the operation.

  • Specifies what clients must respect to call the operation.

  • Represented by an OCL expression, preceded by pre:

-- Only professors older than 30 years can be added to the department:
context Department::add(p : Professor) : Integer
    pre old: p.age > 30

Postconditions

  • A postcondition is a constraint that must be verified after the execution of the operation.

  • Specifies what the operation must accomplish.

  • Represented by an OCL expression preceded by the keyword post:

context Student::age() : Integer
post correct: result = (today - birthday).years()
  • The result operator gives access to the operation return value.

Property Values

  • Within a postcondition, there are two available values for each property:

    • Its value before the operation execution.

    • Its value after the operation execution.

context Person::birthday()
    post: age = age@pre + 1

context Professor::raise(v : Integer)
    post: self.wage = self.wage@pre + v
  • The @pre operator gives access to a property’s value before the operation execution.

Previous Values (1/2)

When the @pre value of a property is an object, all the values reached from this objects are new:

a.b@pre.c
        -- the old value of b, say X,
        -- and the new value of c of X

a.b@pre.c@pre
        -- the old value of b , say X,
        -- and the old value of c of X.

Previous Values (2/2)

atpre
a.b@pre.c -- the new value of b1.c,
c3 a.b@pre.c@pre  -- the old value of b1.c,
c1 a.b.c -- the new value of b2.c, c2+`

Plan

  1. Introduction

  2. Invariants

  3. Model Navigation

  4. Property Definition

  5. Operation Specification

  6. Advanced Topics

  7. Conclusion

  8. Appendix - Language Details

Advanced Topics

Tuples, Messages, Constraint Inheritance

Tuples

Definition

Tuple:

A Tuple is a finite sequence of objects or components, where each component is named. The component types are potentially different.

Examples:

Tuple {name:String = 'Martin', age:Integer = 42}
Tuple {name:'Colette', grades:Collection(Integer) = Set{12, 13, 9},
     diploma:String = 'Computer Science'}

Tuple Syntax

  • Types are optionals.

  • The component order is not relevant.

Equivalent expressions:

Tuple {name: String = 'Martin,' age: Integer = 42}
Tuple {name = 'Martin,' age = 42}
Tuple {age = 42, name = 'Martin'}

Tuple Component Initialization

  • OCL expressions can be used to initialize tuple components:

context University def:
statistics : Set(Tuple(dpt : Department, studentNb:Integer,
                               graduated: Set(Student), average: Integer)) =
     department->collect(each |
       Tuple {dpt : Department = each,
           studentNb: Integer = each.students()->size(),
           graduated: Set(Student) = each.students()->select(graduated()),
           average: Integer = each.students()->collect(note)->avg()
          }
      )

Tuple Component Access

  • Component values are accessible through their names, using the dotted notation:

Tuple {name:String='Martin', age:Integer = 42}.age = 42
  • The attribute statistics defined previously can be used within another OCL expression:

context University inv:
     statistics->sortedBy(average)->last().dpt.name = 'Computer Science'
     -- CS department has always the best students.

Messages

OCL expressions can verify that a communication happened, using the «^» (hasSent) operator:

context Subject::hasChanged()
post:  observer^update(12, 14)

Jokers

  • When the arguments are not known, the expression can use the operator «?» (joker):

context Subject::hasChanged() post:  observer^update(? : Integer, ? : Integer)

The «OclMessage» Type

  • The operator «^^» (messages) allows an expression to access a sequence of sent messages:

context Subject::hasChanged()
post: let messages : Sequence(OclMessage) =
            observer^^update(? : Integer, ? : Integer) in
      messages->notEmpty() and
      messages->exists( m | m.i > 0 and m.j >= m.i )

Returned Values

  • The operator OclMessage::result() allows an expression to access an operation return value (signals do not return values).

  • The operator OclMessage::hasReturned() returns true if the operation returned a value.

context Person::giveSalary(amount : Integer)
    post: let message : OclMessage = company^getMoney(amount) in
    message.hasReturned()
    -- getMoney was sent and returned
    and message.result()
    -- the getMoney call returned true

Constraint Inheritance

Liskov substitution principle (LSP)

In an object-oriented program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., an object of type T may be substituted with any object of a subtype S).

Invariant Inheritance

Consequence of the LSP on the invariants:
  • Subclasses always inherit invariants.

  • Subclasses can only reinforce an invariant.

Pre- and Post-Condition Inheritance

Consequences of the LSP on pre and postconditions:
  • A precondition can only be relaxed (contrevariance)

  • A postcondition can only be reinforced (covariance)

Plan

  1. Introduction

  2. Invariants

  3. Model Navigation

  4. Property Definition

  5. Operation Specification

  6. Advanced Topics

  7. Conclusion

  8. Appendix - Language Details

Conclusion

OCL Goals

Design by contracts allows designers to:
  • be more precise.

  • improve documentation.

  • keep design independent from implementation.

  • Identify component’s responsibilities.

OCL Usages

OCL expressions can specify:
  • Class invariants;

  • Class attributes initialization;

  • Class derived attributes;

  • New class properties: attributes and query operations;

  • Class operations pre- and post-conditions;

  • Transition guards;

  • Transition pre and postconditions;

Modeling Advices

  • Keep things simple: the goal of constraints is to improve the quality of a specification, and not to make it more complex.

  • Always combine constraints with natural language: constraints are used to make comments less ambiguous and not to replace them.

  • Use a tool.

Usage

References

Tools

Plan

  1. Introduction

  2. Invariants

  3. Model Navigation

  4. Property Definition

  5. Operation Specification

  6. Advanced Topics

  7. Conclusion

  8. Appendix - Language Details

Appendix

Language Details

Access to Class-level Properties

  • Class-level properties are accessed through double-colons (::)

Class-level attributes
context Professor inv:
    self.wage < Professor::maximumWage
Class-level query operations:
context Professor inv:
    self.age() > Student::minimumAge()

Access to Enumeration Literals and Nested States

  • To avoid name conflicts, enumeration literals are preceded by the enumeration name and double-colons:

context Professor
inv: self.title = Title::full implies self.wage > 10
  • Nested states (from the attached state machine) are preceded by the container state name and double-colons:

context Department::add(p:Professor)
pre:p.oclInState(Unavailable::Holydays)
-- nested states

Basic Types

TypeValues

OclInvalid

invalid

OclVoid

null, invalid

Boolean

true, false

Integer

1, -5, 2, 34, 26524, etc.

Real

1.5, 3.14, etc.

String

'To be or not to be…​'

UnlimitedNatural

0, 1, 2, 42, …​ , *

Collection Types (1/2)

TypeDescriptionObtained fromExamples

Set

unordered set.

Simple navigation

{1, 2, 45, 4}

OrderedSet

ordered set.

Navigation through an ordered association end (labelled with {ordered})

{1, 2, 4, 45}

Collection Types (2/2)

TypeDescriptionObtained fromExamples

Bag

unordered multiset.

Combined navigations

{1, 3, 4, 3}

Sequence

ordered multiset.

Navigation though a ordered association end {ordered}

{1, 3, 3, 5, 7}, {1..10}

Type Conformity Rules

TypeConforms toCondition

Set(T1)

Collection(T2)

If T1 conforms to T2

Sequence(T1)

Collection(T2)

If T1 conforms to T2

Bag(T1)

Collection(T2)

If T1 conforms to T2

OrderedSet(T1)

Collection(T2)

If T1 conforms to T2

Integer

Real

Operations on Basic Types

TypeOperations

Integer

=, *, +, -, /, abs(), div(), mod(), max(), min()

Real

=, *, +, -, /, abs(), floor(), round(), max(), min(), >, <, ⇐, >=, …​

String

=, size(), concat(), substring(), toInteger(), toReal(), toUpperCase(), toLowerCase()

Boolean

or, xor, and, not, implies

UnlimitedNatural

*, +, /

Operations on Collections

OperationsBehavior

isEmpty()

True if the collection is empty.

notEmpty()

Trues if the collection contains at least one element.

size()

Number of elements in the collection.

count(<elem>)

Number of occurrences of <elem> in the collection.

Examples:
{}->isEmpty()
{1}->notEmpty()
{1,2,3,4,5}->size() = 5
{1,2,3,4,5}->count(2) = 1

Iterator Operations on Collections

Complex operations use an iterator (named each by convention), a variable that evaluates to each collection element.

OperationBehavior

select(<boolean-expression>)

Selects (filters) a subset of the collection.

collect(<expression>)

Evaluates an expression for each element in the collection.

Examples:

{1,2,3,4,5}->select(each | each > 3) = {4,5}
{'a','bb','ccc','dd'}->collect(each | each.toUpper()) = {'A','BB','CCC','DD'}

Select and Reject: Syntax

Selects (respectively rejects) the collection subset to which a boolean expression evaluates to true.

Collection(T)->select(elem:T | <bool-expr>) : Collection(T)
  • The element types of the input and the output collections are always the same.

  • The size of the output collection is less than or equal to the size of the input collection.

Select and Reject: Examples

  • Possible syntaxes:

context Department inv:
    -- no iterator
    self.instructors->select(age > 50)->notEmpty()
    self.instructors->reject(age > 23)->isEmpty()

    -- with iterator
    self.instructors->select(each | each.age > 50)->notEmpty()

    -- with typed iterator
    self.instructors->select(each : Professor | each.age > 50)->notEmpty()

Collect: Syntax

Evaluates an expression on each collection element and returns another collection containing the results.

Collection<T1>->collect(<expr>) : Bag<T2>
  • The sizes of the input and the output collection are mandatory the same.

  • The result is a multiset (Bag).

  • If the the result of <expr> is a collection, the result will not be a collection of collections. The result is automatically flattened.

Collect: Examples

Possible syntaxes:

context Department:
    self.instructors->collect(name)
    self.instructors->collect(each | each.name)
    self.instructors->collect(each: Professor | each.name)

    -- Bag to Set conversion:
    self.instructors->collect(name)->asSet()

    -- shortcut:
    self.instructors.name

Property Verification on Collections

OperationBehavior

forAll(<boolean-expression>)

Verifies that all the collection elements respect the expression.

exists(<boolean-expression>)

Verifies that at least the collection elements respect the expression.

Examples:

{1,2,3,4,5}->forAll(each | each > 0 and each < 10)
{1,2,3,4,5}->exists(each | each = 3)

For All: Syntax

Evaluates a Boolean expression on all elements of a collection and returns true if all evaluations return true.

Collection(T)->forAll(elem:T | <bool-expr>) : Boolean

For All: Examples

context Department
inv:
    -- All instructors are associate professors.
    self.instructors->forAll(title = Title::associate)

    self.instructors->forAll(each | each.titre = Title::associate)

    self.instructors->forAll(each: Professor | each.title = Title::associate)

For All

Cartesian product:

context Department inv:
    self.instructors->forAll(e1, e2 : Professor |
        e1 <> e2 implies e1.name <> e2.name)

-- equivalent to:
    self.instructors->forAll(e1 | self.instructors->
        forAll(e2 | e1 <> e2 implies e1.name <> e2.name))

Exists

Returns true if a boolean expression is true for at least one collection element.

Syntax:

collection->exists(<boolean-expression>) : Boolean

Example:

context: Department inv:
    self.instructors->exists(each: Professor |
        each.name = 'Martin')

Advanced Operations on Collections

Operation Behavior

collectNested(<exp>)

Similar to collect(), but does not flatten the result if it is a collections of collections.

closure()

Recursively evaluates and expression.

iterate()

Generic operation that applies to any collection.


Collect Nested

Similar to collect(), without flattening collections of collections.

context University
    -- All university instructors, grouped by department:
    self.department->collectNested(instructors)

Collections of collections can be flattened with the flatten() operation:

    Set{Set{1, 2}, Set{3, 4}} ->flatten() = Set{1, 2, 3, 4}

Closures

  • The closure() operation recursively invokes an OCL expression over a source and adds the successive results to the source.

  • The iteration finishes when the expression evaluation returns an empty set.

Syntax:

source->closure(v : <class-name> | <expression-with-v>)

Closure Example

family
context Person
def descendants() : Set(Person) = self.children->closure(children)

Iterate

Generic iterator operation on collections.

Syntax:

Collection(<T>)->iterate(<elm>: <T>; answer: T = <value> |
    <expr-with-elm-and-response>)

Examples:

context Department inv:
    self.instructors->select(age > 50)->notEmpty()

    -- equivalent expression:
    self.instructors->iterate(each: Professor;
        answer: Set(Professor) = Set {} |
            if each.age > 50 then answer.including(each)
            else answer endif) -> notEmpty()

Other operations on Collections

OperationBehavior

includes(<elem>), excludes(<elem>)

Checks if <elem> belongs (resp. not belongs) to the collection.

includesAll(<coll>), excludesAll(<coll>)

Checks if all elements of <coll> belong (resp. not belong) to the collection.

union(<coll>), intersection(<coll>)

Set operations.

asSet(), asBag(), asSequence()

Type conversion.

including(<elem>), excluding(<elem>)

Creates a new collection that includes (resp. excludes) <elem>

Predefined Properties (1/2)

OperationBehavior

oclIsTypeOf(t : OclType):Boolean

oclIsKindOf(t : OclType):Boolean

oclInState(s : OclState):Boolean

oclIsNew():Boolean

Predefined Properties (1/2)

OperationBehavior

oclIsUndefined():Boolean

oclIsInvalid():Boolean

oclAsType(t : Type):Type

allInstances():Set(T)

Examples:

context University
    inv: self.oclIsTypeOf(University)
    inv: not self.oclIsTypeOf(Department)

Let…​in (alias)

When an OCL sub-expression appears several times on a constraint, it is possible to use an alias to replace if:

Syntax:

let <alias> : <Type> = <ocl-expression> in <expression-with-alias>

Example:

context Person inv:
    let income : Integer = self.job.salary->sum() in
    if isUnemployed then
        income < 100
    else
        income >= 100
    endif
  • Note that this is only an alias, not an assignment.

Thank you for your attention!

Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)

You are free to:
  • Share — copy and redistribute the material in any medium or format

  • Adapt — remix, transform, and build upon the material for any purpose, even commercially.

The licensor cannot revoke these freedoms as long as you follow the license terms.

Under the following terms:
  • Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.

  • NonCommercial — You may not use the material for commercial purposes.

  • ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.

No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.