EmPowerTec Logo

This page demonstrates the benefits of using Oclarity for checking OCL expressions that are added to your model.
The OCL specification contains a lot of OCL expressions that can be divided in two categories.

  • Category 1 refers to the sample model (Companies and Employees). These expressions are used to demonstrate the usage of the OCL.
  • Category 2 refers to the OCL and UML metamodel. These expressions are used to define the OCL precisely.

For testing and debugging purposes we have analyzed all expressions from category 1 with our OCL parser and semantic checker. There was a surprising amount of issues detected during this process. To demonstrate the benefits of a comprehensive checker like it is integrated in Oclarity we have listed the results below.

The error messages have been partially reformatted so that they can be better read on this web page.
Each expression contains the following information:

  • The original code as copied from the language specification. If the original code contains an error, the background color is changed to a light red.
  • If Oclarity found an error, the corrected code is showed with a light blue background color.
  • In the case of an error, the error message as issued by Oclarity is displayed and an explanation is given.

Expression 1

Original code

context Company inv:
self.numberOfEmployees > 50

Expression 2

Original code

context c : Company inv:
c.numberOfEmployees > 50

Expression 3

Original code

context Person::income(d : Date) : Integer
post: result = 5000

Expression 4

Original code

context Person::getCurrentSpouse() : Person
pre: self.isMarried = true
body: self.mariages->select( m | m.ended = false ).spouse

Corrected code

context Person::getCurrentSpouse() : Person
pre: self.isMarried = true
body:
self.marriage[wife]->select( m | m.ended = false )->
one(true).husband

Messages

"Unknown identifier 'mariages'. (Context type was 'Person')"

Obviously a typo; the name of the association is "marriage".

"Association 'marriage' of class 'Person' has (a) required
qualifier(s) but no qualifier is used."

The OCL specification requires that a qualifier is used for
recursive associations (associations with the same class at
both ends).

"Unknown identifier 'ended'. (Context type was 'Marriage')"

The example model from the OCL specification does not define
an attribute called 'ended' for class Marriage.

After adding this attribute, we get this error:

"Unknown identifier 'spouse'. (Context type was
'Set(Marriage)')"

To make the body of the defined operation really return a
Person-object, a member of the calculated Set must be returned
(and not the Set itself as in the original expression).

"Unknown identifier 'spouse'. (Context type was 'Marriage')"

No association named "spouse" is defined for class Marriage.
The correct association name is "husband".

"A body expression was specified for a method that is not a
query."

We forgot to mark Person::getCurrentSpouse() as query in the
model.

Expression 5

Original code

context Person::income : Integer
init: parents.income->sum() * 1%
derive: if underAge
then parents.income->sum() * 1%
else job.salary

endif

Corrected code

context Person::income2 : Integer
init: (parents.income2->sum() * 1/100).round()

derive: if underAge
then (parents.income2->sum() * 1/100).round()
else job.salary->sum()
endif

Messages

"unexpected char: % [file: , line: 21, column: 44]"

The usage of the %-character in OCL is nowhere defined in the
specification. We replaced it by the equivalent arithmetic
operation.

"Unknown attribute 'income' in context specification."

It seems as if the authors of the specification wanted to
illustrate init and derive expressions for an attribute.
But the identifier 'income' is already used in class Person
as a method (and therefore not found as an attribute).

After introducing an attribute named 'income2' of type Integer,
the following error is returned:

"Unknown identifier 'parents'."

An association from Person to Person, relating parents and
children is missing. After adding this association, the
following error is returned:

Unknown identifier 'underAge'."

No attribute "underAge" is defined in class Person in the
original model. We added the attribute.

The next error is:

"An initialization expression of type 'Real' was used to
initialize an attribute of type 'Integer'."

A call to round() has to be added, so that the type of the
initialization expression conforms to the declared type.

Finally we get this error:

"The types of the 'then'-part and of the 'else'-part are not
conforming to a common supertype: 'Integer' vs. 'Set(Integer)'."

The association "employer" from Person to Company is of
multiplicity 0..n. This means, that the expression "self.jobs"
returns a Set and not a single instance. Therefore, the sum of
all Jobs has to be calculated.

Expression 6

Original code

context Person inv:
let income : Integer = self.job.salary->sum() in
if isUnemployed then
income < 100
elseincome >= 100
endif

Corrected code

context Person inv:
let income : Integer = self.job.salary->sum() in
if isUnemployed then
income < 100
else income >= 100
endif

Messages

expecting "else", found 'elseincome' [file: , line: 32, column: 9]

This is just a typo.

Expression 7

Original code

context Person
def: income : Integer = self.job.salary->sum()
def: nickname : String = 'Little Red Rooster'
def: hasTitle(t : String) : Boolean = self.job->exists(title = t)

Expression 8

Original code

context Person inv:
self.age > 0

Expression 9

Original code

aPerson.income(aDate).bonus = 300 and
aPerson.income(aDate).result = 5000

Corrected code

context Person::addedMethod1(aPerson : Person, aDate : Date):
OclVoid
inv: aPerson.incomeWithBonus(aDate).bonus = 300 and
aPerson.incomeWithBonus(aDate).result = 5000

Messages

"Unknown identifier 'aPerson'."
"Unknown identifier 'aDate'. (Context type was 'Person')"

As it stands, aPerson and aDate are not valid identifiers within
this code fragment. To make the fragment valid, we add as
context a method that receives appropriate parameters.

"Type 'Person' does not support a matching method for signature
'incomeWithBonus(Date)'".

This seems to be a hole in the OCL language specification.
Output parameters may not be specified in the call to the
respective method but this makes it impossible in the general
case to find the called method.

Expression 10

Original code

context Person::income (d: Date) : Integer
post: result = age * 1000

Expression 12

Original code

context Company inv:
self.stockPrice() > 0

Expression 13

Original code

context Company
inv: self.manager.isUnemployed = false
inv: self.employee->notEmpty()

Expression 14

Original code

context Person inv:
self.employer->size() < 3

Expression 15

Original code

context Person inv:
self.employer->isEmpty()

Expression 16

Original code

context Company inv:
self.manager->size() = 1

Expression 17

Original code

context Company inv:
self.manager.age > 40

Expression 18

Original code

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

Expression 19

Original code

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

Expression 20

Original code

context Company inv:
self.employee->size() <= 50

Expression 21

Original code

context Person inv:
self.job

Corrected code

context Person inv:
self.job->size() <= 2

Messages

"The expression of the invariant is not of type 'Boolean' but of
type 'Set(Job)'."

The type of a constraint expression (inv, pre, post) must be
boolean. The correct version is only an example, stating that
a person may not have more than two jobs.

Expression 22

Original code

context Person inv:
self.employeeRanking[bosses]->sum() > 0

Corrected code

context Person inv:
self.employeeRanking[bosses].score->sum() > 0

Messages

"Element type 'EmployeeRanking' does not support
addition as required by Collection::sum()."

Instances of type EmployeeRanking cannot be summed.
The intention was most probably to sum the score of all rankings.

Expression 23

Original code

context Person inv:
self.employeeRanking[employees]->sum() > 0

Corrected code

context Person inv:
self.employeeRanking[employees].score->sum() > 0

Messages

The same problem as above.

Expression 24

Original code

context Person inv:
self.employeeRanking->sum() > 0 -- INVALID!

Corrected code Messages

"Association 'employeeRanking' of class 'Person' has (a) required
qualifier(s) but no qualifier is used."

This constraint is intentionally wrong.

Expression 25

Original code

context Person inv:
self.job[employer]

Corrected code Messages

"Identifier 'job' uses 1 qualifier(s) but should use 0
qualifier(s)."

The language specification explicitly allows the redundant
specification of the association class although the association is
not recursive. Oclarity currently does not support this
redundancy. It is better style anyway to omit useless information.

Expression 26

Original code

context Job
inv: self.employer.numberOfEmployees >= 1
inv: self.employee.age > 21

Expression 27

Original code

context Bank inv:
self.customer

Corrected code

context Bank inv:
not self.customer->exists(underAge = true)

Messages

"The expression of the invariant is not of type 'Boolean'
but of type 'Set(Person)'."

As it stands, the expression is not a valid invariant.
We have extended it, so that it expresses, that all customer must
be of full age.

Expression 28

Original code

context Bank inv:
self.customer[8764423]

Corrected code

context Bank inv:
self.customer[8764423]->forAll(underAge = false)

Messages

"The expression of the invariant is not of type 'Boolean'
but of type 'Set(Person)'."

As ist stands, the expression is not a valid invariant.
We have extended it, so that it expresses, that customer 8764423
must be of full age.

In addition, the expression "self.customer[8764423]" returns a
Set and not a single instance.

Expression 29

Original code

context Person inv:
Person.allInstances()->forAll(p1, p2 |
p1 <> p2 implies
p1.name <> p2.name)

Corrected code

context Person inv:
Person.allInstances()->forAll(p1, p2 |
p1 <> p2 implies
p1.lastName <> p2.lastName)

Messages

"Unknown identifier 'name'. (Context type was 'Person')"

The properties are called lastName and foreName;
there is no property 'name' defined in class Person.

Expression 30

Original code

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

Expression 31

Original code

context Company::hireEmployee(p : Person)
post: employees = employees@pre->including(p) and
stockprice() = stockprice@pre() + 10

Corrected code

context Company::hireEmployee(p : Person)
post: employee = employee@pre->including(p) and
stockPrice() = stockPrice@pre() + 10

Messages

"Unknown identifier 'employees'."

This is a typo in the example. The association is called "employee"
(without trailing s).

After correcting this, the following error is reported:

"Type 'Company' does not support a matching method for signature
'stockprice()'."

Again, this is a typo. The method is called Company::stockPrice()
with capital P.

Expression 32

Original code

context Person def:
attr statistics : Set(TupleType(company: Company,
numEmployees: Integer,
wellpaidEmployees: Set(Person),
totalSalary: Integer)) =
managedCompanies->collect(c |
Tuple { company: Company = c,
numEmployees: Integer = c.employee->size(),
wellpaidEmployees: Set(Person) =
c.job->select(salary>10000).employee->asSet(),
totalSalary: Integer = c.job.salary->sum()
}
)

Corrected code

context Person def:
statistics : Bag(TupleType( company: Company,
numEmployees: Integer,
wellpaidEmployees: Set(Person),
totalSalary: Integer)) =
managedCompanies->collect(c |
Tuple { company: Company = c,
numEmployees: Integer = c.employee->size(),
wellpaidEmployees: Set(Person) =
c.job->select(salary>10000).employee->asSet(),
totalSalary: Integer = c.job.salary->sum()
}
)

Messages

"unexpected token: attr [file: , line: 140, column: 9]"

The keyword "attr" is no longer used in the OCL 2.0 language
definition.

"Initialization expression (of type
'Bag(TupleType( company: Company,
numEmployees: Integer,
wellpaidEmployees: Set(Person),
totalSalary: Integer))')
does not conform to declared type (
'Set(TupleType(company: Company,
numEmployees: Integer,
wellpaidEmployees: Set(Person),
totalSalary: Integer))')."

As the OCL specification says wrt the collect()-operation:
"An important issue here is that the resulting collection is
not a Set, but a Bag."

Expression 33

Original code

context Person inv:
statistics->sortedBy(totalSalary)->
last().wellpaidEmployees->includes(self)

Expression 34

Original code

context Company inv:
self.employee->select(age > 50)->notEmpty()

Expression 35

Original code

context Company inv:
self.employee->select(age > 50)->notEmpty()
context Company inv:
self.employee->select(p | p.age > 50)->notEmpty()

Expression 36

Original code

context Company inv:
self.employee.select(p : Person | p.age > 50)->notEmpty()

Expression 37

Original code

context Company inv:
self.employee->reject( isMarried )->isEmpty()

Expression 38

Original code

self.employee->collect( birthDate )
self.employee->collect( person | person.birthDate )
self.employee->collect( person : Person | person.birthDate )
self.employee->collect( birthDate )->asSet()

Corrected code Messages

The expression are just examples for the usage
of collections and as such are no valid OCL expressions
in the sense of constraints or the other expression types
like body, init oder def expressions.

Expression 39

Original code

self.employee->collect(birthdate)
self.employee.birthdate

Corrected code

inv: self.employee->collect(birthDate)->size() > 0
inv: self.employee.birthDate->size() > 0

Messages

"Unknown identifier 'birthdate'. (Context type was 'Set(Person)')"

Obviously a typo.
The attribute "birthDate" is written with capital "d".

Expression 40

Original code

context Company
inv: self.employee->forAll( age <= 65 )
inv: self.employee->forAll( p | p.age <= 65 )
inv: self.employee->forAll( p : Person | p.age <= 65 )

Expression 41

Original code

context Company inv:
self.employee->forAll(e1, e2 : Person |
e1 <> e2 implies
e1.forename <> e2.forename)

Corrected code

context Company inv:
self.employee->forAll(e1, e2 : Person |
e1 <> e2 implies
e1.firstName <> e2.firstName)

Messages

Unknown identifier 'forename'. (Context type was 'Person')"

There is no attribute "forename" within class Person.
Most probably, 'firstName' was meant.

Expression 42

Original code

context Company inv:
self.employee->forAll (e1 | self.employee->forAll (e2 |
e1 <> e2 implies
e1.forename <> e2.forename))

Corrected code

context Company inv:
self.employee->forAll (e1 | self.employee->forAll (e2 |
e1 <> e2 implies
e1.firstName <> e2.firstName))

Messages

The same problem as above.

Expression 43

Original code

context Company inv:
self.employee->exists( forename = 'Jack' )

Corrected code

context Company inv:
self.employee->exists( firstName = 'Jack' )

Messages

The same problem as above.

Expression 44

Original code

context Company inv:
self.employee->exists( p | p.forename = 'Jack' )
context Company inv:
self.employee->exists( p : Person | p.forename = 'Jack' )

Corrected code

context Company inv:
self.employee->exists( p | p.firstName = 'Jack' )
context Company inv:
self.employee->exists( p : Person | p.firstName = 'Jack' )

Messages

The same problem as above.

Expression 45

Original code

context Person inv:
employer->forAll( employee->exists( lastName = name) )
context Person
inv: employer->forAll( employee->exists( p | p.lastName = name) )
inv: employer->forAll( employee->exists( self.lastName = name) )