|
|
|
|
|
|
|
|
|
|
|
|
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.
Each expression contains the following information:
original_expressions.txt correct_expressions.txt The underlying model for Rational Rose Expression 1Original code
context Company inv:
self.numberOfEmployees > 50
Expression 2Original code
context c : Company inv:
c.numberOfEmployees > 50
Expression 3Original code
context Person::income(d : Date) : Integer
post: result = 5000
Expression 4Original 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 5Original 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 6Original 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 7Original 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 8Original code
context Person inv:
self.age > 0
Expression 9Original 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 10Original code
context Person::income (d: Date) : Integer
post: result = age * 1000
Expression 12Original code
context Company inv:
self.stockPrice() > 0
Expression 13Original code
context Company
inv: self.manager.isUnemployed = false
inv: self.employee->notEmpty()
Expression 14Original code
context Person inv:
self.employer->size() < 3
Expression 15Original code
context Person inv:
self.employer->isEmpty()
Expression 16Original code
context Company inv:
self.manager->size() = 1
Expression 17Original code
context Company inv:
self.manager.age > 40
Expression 18Original code
context Person inv:
self.wife->notEmpty() implies self.wife.gender = Gender::female
Expression 19Original code
context Person inv:
self.wife->notEmpty() implies self.wife.age >= 18 and
self.husband->notEmpty() implies self.husband.age >= 18
Expression 20Original code
context Company inv:
self.employee->size() <= 50
Expression 21Original 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 22Original 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 23Original 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 24Original 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 25Original 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 26Original code
context Job
inv: self.employer.numberOfEmployees >= 1
inv: self.employee.age > 21
Expression 27Original 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 28Original 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 29Original 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 30Original code
context Person::birthdayHappens()
post: age = age@pre + 1
Expression 31Original 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 32Original 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 33Original code
context Person inv:
statistics->sortedBy(totalSalary)->
last().wellpaidEmployees->includes(self)
Expression 34Original code
context Company inv:
self.employee->select(age > 50)->notEmpty()
Expression 35Original code
context Company inv:
self.employee->select(age > 50)->notEmpty()
context Company inv:
self.employee->select(p | p.age > 50)->notEmpty()
Expression 36Original code
context Company inv:
self.employee.select(p : Person | p.age > 50)->notEmpty()
Expression 37Original code
context Company inv:
self.employee->reject( isMarried )->isEmpty()
Expression 38Original 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 39Original 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 40Original 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 41Original 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 42Original 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 43Original 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 44Original 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 45Original 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) )
|