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