Home Learn more about OCL Rational Rose OCL-AddIn product description Downloads Buy our products Support information Sitemap Contact Privacy policy  

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.
If you want to experiment with this model and these expressions you might find these files helpful:
original_expressions.txt
correct_expressions.txt
The underlying model for Rational Rose

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) )
        
Page last modified: 2009-12-01 22:07:49