Collection Operations

OCL defines many operations on the collection types. These operations are specifically meant to enable a flexible and powerful way of projecting new collections from existing ones. The different constructs are described in the following sections.
 

Select and Reject Operations

Sometimes an expression using operations and navigations results in a collection, while we are interested only in a special subset of the collection. OCL has special constructs to specify a selection from a specific collection. These are the select and reject operations. The select specifies a subset of a collection. A select is an operation on a collection and is specified using the arrow-syntax:

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

Collect Operation

As shown in the previous section, the select and reject operations always result in a sub-collection of the original collection. When we want to specify a collection which is derived from some other collection, but which contains different objects from the original collection (i.e., it is not a sub-collection), we can use a collect operation. The collect operation uses the same syntax as the select and reject and is written as one of:

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

ForAll Operation

Many times a constraint is needed on all elements of a collection. The forAll operation in OCL allows specifying a Boolean expression, which must hold for all objects in a collection:

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

Exists Operation

Many times one needs to know whether there is at least one element in a collection for which a constraint holds. The exists operation in OCL allows you to specify a Boolean expression which must hold for at least one object in a collection:

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.'
 

Iterate Operation

The iterate operation is slightly more complicated, but is very generic. The operations reject, select, forAll, exists, collect, can all be described in terms of iterate. An accumulation builds one value by iterating over a collection.

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.