collection->select( ... )
The parameter of select has a special syntax that enables one to specify which elements of the collection we want to select. There are three different forms, of which the simplest one is:
collection->select( boolean-expression )
This results in a collection that contains all the elements from collection for which the boolean-expression evaluates to true. To find the result of this expression, for each element in collection the expression boolean-expression is evaluated. If this evaluates to true, the element is included in the result collection, otherwise not. As an example, the following OCL expression specifies that the collection of all the employees older than 50 years is not empty:
context Company
inv:
self.employee->select(age
> 50)->notEmpty()
The self.employee is of type Set(Person). The select takes each person from self.employee and evaluates age > 50 for this person. If this results in true, then the person is in the result Set.
As shown in the previous example, the context for the expression in the select argument is the element of the collection on which the select is invoked. Thus the age property is taken in the context of a person.
In the above example, it is impossible to refer explicitly to the persons themselves; you can only refer to properties of them. To enable to refer to the persons themselves, there is a more general syntax for the select expression:
collection->select( v | boolean-expression-with-v )
The variable v is called the iterator. When the select is evaluated, v iterates over the collection and the boolean-expression-with-v is evaluated for each v. The v is a reference to the object from the collection and can be used to refer to the objects themselves from the collection. The two examples below are identical:
context Company
inv:
self.employee->select(age
> 50)->notEmpty()
context Company
inv:
self.employee->select(p
| p.age > 50)->notEmpty()
The result of the complete select is the collection of persons p for which the p.age > 50 evaluates to True. This amounts to a subset of self.employee.
As a final extension to the select syntax, the expected type of the variable v can be given. The select now is written as:
collection->select( v : Type | boolean-expression-with-v )
The meaning of this is that the objects in collection must be of type Type. The next example is identical to the previous examples:
context Company
inv:
self.employee.select(p
: Person | p.age > 50)->notEmpty()
The compete select syntax now looks like one of:
collection->select( v
: Type | boolean-expression-with-v )
collection->select(
v | boolean-expression-with-v )
collection->select(
boolean-expression )
The reject operation is identical to the select operation, but with reject we get the subset of all the elements of the collection for which the expression evaluates to False. The reject syntax is identical to the select syntax:
collection->reject( v
: Type | boolean-expression-with-v )
collection->reject(
v | boolean-expression-with-v )
collection->reject(
boolean-expression )
As an example, specify that the collection of all the employees who are not married is empty:
context Company
inv:
self.employee->reject(
isMarried )->isEmpty()
The reject operation is available in OCL for convenience, because each reject can be restated as a select with the negated expression. Therefore, the following two expressions are identical:
collection->reject( v
: Type | boolean-expression-with-v )
collection->select(
v : Type | not (boolean-expression-with-v) )
collection->collect(
v : Type | expression-with-v )
collection->collect(
v | expression-with-v )
collection->collect(
expression )
The value of the reject operation is the collection of the results of all the evaluations of expression-with-v.
An example: specify the collection of birthDates for all employees in the context of a company. This can be written in the context of a Company object as one of:
self.employee->collect(
birthDate )
self.employee->collect(
person | person.birthDate )
self.employee->collect(
person : Person | person.birthDate )
An important issue here is that the resulting collection is not a Set, but a Bag. When more than one employee has the same value for birthDate, this value will be an element of the resulting Bag more than once. The Bag resulting from the collect operation always has the same size as the original collection.
It is possible to make a Set from the Bag, by using the asSet property on the Bag. The following expression results in the Set of different birthDates from all employees of a Company:
self.employee->collect( birthDate )->asSet()
Shorthand for Collect
Because navigation through many objects is very common, there is a shorthand notation for the collect that makes the OCL expressions more readable. Instead of
self.employee->collect(birthdate)
we can also write:
self.employee.birthdate
In general, when we apply a property to a collection of Objects, then it will automatically be interpreted as a collect over the members of the collection with the specified property.
For any propertyname that is defined as a property on the objects in a collection, the following two expressions are identical:
collection.propertyname
collection->collect(propertyname)
and so are these if the property is parameterized:
collection.propertyname
(par1, par2, ...)
collection->collect
(propertyname(par1, par2, ...))
collection->forAll( v
: Type | boolean-expression-with-v )
collection->forAll(
v | boolean-expression-with-v )
collection->forAll(
boolean-expression )
This forAll expression results in a Boolean. The result is true if the boolean-expression-with-v is true for all elements of collection. If the boolean-expression-with-v is false for one or more v in collection, then the complete expression evaluates to false. For example, in the context of a company:
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 )
These invariants evaluate to true if the age property of each employee is less or equal to 65.
The forAll operation has an extended variant in which more then one iterator is used. Both iterators will iterate over the complete collection. Effectively this is a forAll on the Cartesian product of the collection with itself.
context Company
inv:
self.employee->forAll(
e1, e2 : Person |
e1 <> e2 implies e1.forename <> e2.forename)
This expression evaluates to true if the forenames of all employees are different. It is semantically equivalent to:
context Company
inv:
self.employee->forAll
(e1 | self.employee->forAll (e2 |
e1 <> e2 implies e1.forename <> e2.forename))
collection->exists( v
: Type | boolean-expression-with-v )
collection->exists(
v | boolean-expression-with-v )
collection->exists(
boolean-expression )
This exists operation results in a Boolean. The result is true if the boolean-expression-with-v is true for at least one element of collection. If the boolean-expression-with-v is false for all v in collection, then the complete expression evaluates to false. For example, in the context of a company:
context Company
inv:
self.employee->exists(
forename = 'Jack' )
context Company
inv:
self.employee->exists(
p | p.forename = 'Jack' )
context Company
inv:
self.employee->exists(
p : Person | p.forename = 'Jack' )
These expressions evaluate to true if the forename property of
at least one employee is equal to `Jack.'
collection->iterate(
elem : Type; acc : Type = <expression> |
expression-with-elem-and-acc )
The variable elem is the iterator, as in the definition of select, forAll, etc. The variable acc is the accumulator. The accumulator gets an initial value <expression>. When the iterate is evaluated, elem iterates over the collection and the expression-with-elem-and-acc is evaluated for each elem. After each evaluation of expression-with-elem-and-acc, its value is assigned to acc. In this way, the value of acc is built up during the iteration of the collection. The collect operation described in terms of iterate will look like:
collection->collect(x
: T | x.property)
-- is identical to:
collection->iterate(x
: T; acc : T2 = Bag{} |
acc->including(x.property))
Or written in Java-like pseudocode the result of the iterate can be calculated as:
iterate(elem : T; acc
: T2 = value)
{
acc
= value;
for(Enumeration
e = collection.elements() ; e.hasMoreElements(); ){
elem = e.nextElement();
acc = <expression-with-elem-and-acc>
}
return
acc;
}
Although the Java pseudo code uses a `next element', the iterate
operation is defined not only for Sequqnce, but for each collection type.
The order of the iteration through the elements in the collection is not
defined for Set and Bag. For a Sequence the order is the order of the elements
in the sequence.