context Person
inv:
self.isMarried
If self is a reference to an object, then self.property
is the value of the property property on self.
context Person inv:
self.age > 0
The value of the subexpression self.age is the value of the age
attribute on the particular instance of Person identified by self.
The type of this subexpression is the type of the attribute
age,
which is the standard type Integer.
Using attributes, and operations defined on the basic value types, we
can express calculations etc. over the class model.
For example, a business rule might be "the age of a Person is always
greater than zero." This can be stated by the invariant above.
Attributes may have multiplicities in a UML model. Wheneven the multiplicity
of an attribute is greater than 1, the result type is collection of values.
Collections in OCL are described later in this chapter.
aPerson.income(aDate)
The result of this operation call is a value of the return type of the operation, which is Integer in this example. If the operation has out or in/out parameters, the result of this operation is a tuple containing all out, in/out parameters and the return value. For example, if the income operation would have an out parameter bonus, the result of the above operation call is of type Tuple( bonus: Integer, result: Integer). You can access these values using the names of the out parameters, and the keyword result, for example:
aPerson.income(aDate).bonus
= 300 and
aPerson.income(aDate).result
= 5000
Note that the out parameters need not be included in the operation call. Values for all in or in/out parameters are neccessary.
Defining operations
The operation itself could be defined by a postcondition constraint. This is a constraint that is stereotyped as «postcondition». The object that is returned by the operation can be referred to by result. It takes the following form:
context Person::income
(d: Date) : Integer
post: result
= age * 1000
The right-hand-side of this definition may refer to the operation being defined (i.e., the definition may be recursive) as long as the recursion is not infinite. Inside a pre- or postcondition one can also use the parameters of the operation. The type of result, when the operation has no out or in/out parameters, is the return type of the operation, which is Integer in the above example. When the operation does have out or in/out parameters, the return type is a Tuple as explained above.The postcondition for the income operation with out parameter bonus may take the following form:
context Person::income
(d: Date, bonus: Integer) : Integer
post: result
= Tuple { bonus = ...,
result = .... }
To refer to an operation or a method that doesn't take a parameter, parentheses with an empty argument list are mandatory:
context Company
inv:
self.stockPrice() >
0
object.associationEndName
The value of this expression is the set of objects on the other side of the associationEndName association. If the multiplicity of the association-end has a maximum of one ("0..1" or "1"), then the value of this expression is an object. In the example class diagram, when we start in the context of a Company (i.e., self is an instance of Company), we can write:
context Company
inv: self.manager.isUnemployed
= false
inv: self.employee->notEmpty()
In the first invariant self.manager is a Person, because the multiplicity of the association is one. In the second invariant self.employee will evaluate in a Set of Persons. By default, navigation will result in a Set. When the association on the Class Diagram is adorned with {ordered}, the navigation results in an OrderedSet.
Collections, like Sets, OrderedSets, Bags, and Sequences are predefined types in OCL. They have a large number of predefined operations on them. A property of the collection itself is accessed by using an arrow `->' followed by the name of the property. The following example is in the context of a person:
context Person
inv:
self.employer->size()
< 3
This applies the size property on the Set self.employer, which results in the number of employers of the Person self.
context Person
inv:
self.employer->isEmpty()
This applies the isEmpty property on the Set self.employer.
This evaluates to true if the set of employers is empty and false otherwise
.
Missing AssociationEnd names
When the name of an association-end is missing at one of the ends of an association, the name of the type at the association end tarting with a lowercase character is used as the rolename. If this results in an ambiguity, the rolename is mandatory. This is e.g. the case with unnamed rolenames in reflexive associations. If the rolename is ambiguous, then it cannot be used in OCL.
Navigation over Associations with Multiplicity Zero or One
Because the multiplicity of the role manager is one, self.manager is an object of type Person. Such a single object can be used as a Set as well. It then behaves as if it is a Set containing the single object. The usage as a set is done through the arrow followed by a property of Set. This is shown in the following example:
context Company
inv:
self.manager->size()
= 1
The sub-expression self.manager is used as a Set, because the arrow is used to access the size property on Set. This expression evaluates to true.
context Company
inv:
self.manager->foo
The sub-expression self.manager is used as Set, because the arrow is used to access the foo property on the Set. This expression is incorrect, because foo is not a defined property of Set.
context Company
inv:
self.manager.age > 40
The sub-expression self.manager is used as a Person, because the dot is used to access the age property of Person.
In the case of an optional (0..1 multiplicity) association, this is especially useful to check whether there is an object or not when navigating the association. In the example we can write:
context Person
inv:
self.wife->notEmpty()
implies
self.wife.gender = Gender::female
Combining Properties
Properties can be combined to make more complicated expressions. An important rule is that an OCL expression always valuates to a specific object of a specific type. After obtaining a result, one can always apply another property to the result to get a new result value. Therefore, each OCL expression can be read and evaluated left-to-right.
Following are some invariants that use combined properties on the example class diagram:
context Person
inv:
self.wife->notEmpty()
implies
self.wife.age >= 18 and
self.husband->notEmpty()
implies
self.husband.age >= 18
context Company
inv:
self.employee->size()
<= 50
context Person
inv:
self.job
The sub-expression self.job evaluates to a Set of all the jobs a person has with the companies that are his/her employer. In the case of an association class, there is no explicit rolename in the class diagram. The name job used in this navigation is the name of the association class starting with a lowercase character, similar to the way described in the section "Missing AssociationEnd names" above.
In case of a recursive association, that is an association of a class
with itself, the name of the association class alone is not enough. We
need to distinguish the direction in which the association is navigated
as well as the name of the association class. Take the following model
as an example.
context Person
inv:
self.employeeRanking[bosses]->sum()
> 0
the self.employeeRanking[bosses] evaluates to the set of EmployeeRankings belonging to the collection of bosses. And in the expression
context Person
inv:
self.employeeRanking[employees]->sum()
> 0
the self.employeeRanking[employees] evaluates to the set of EmployeeRankings belonging to the collection of employees. The unqualified use of the association class name is not allowed in such a recursive situation. Thus, the following example is invalid:
context Person
inv:
self.employeeRanking->sum()
> 0 -- INVALID!
In a non-recursive situation, the association class name alone is enough,
although the qualified version is allowed as well.
Therefore, the examples at the start of this section could also be
written as:
context Person
inv:
self.job[employer]
We can navigate from the association class itself to the objects
that participate in the association. This is done using the dot-notation
and the role-names at the association-ends.
context Job
inv: self.employer.numberOfEmployees
>= 1
inv: self.employee.age
> 21
Navigation from an association class to one of the objects on the association
will always deliver exactly one object. This is a result of the definition
of AssociationClass. Therefore, the result of this navigation is exactly
one object, although it can be used as a Set using the arrow (->).
context Bank inv:
self.customer
The next example results in one Person, having account number 8764423.
context Bank inv:
self.customer[8764423]
If there is more than one qualifier attribute, the values are separated
by commas, in the order which is specified in the UML class model. It is
not permissible to partially specify the qualifier attribute values.
Packagename::Typename
This usage of pathnames is transitive and can also be used for packages within packages:
Packagename1::Packagename2::Typename
context B inv:
self.oclAsType(A).p1
-- accesses the p1 property defined in A
self.p1
-- accesses the p1 property defined in B
Figure 3 shows an example where such a construct is needed. In this model fragment there is an ambiguity with the OCL expression on Dependency:
context Dependency
inv:
self.source <> self
This can either mean normal association navigation, which is inherited from ModelElement, or it might also mean navigation through the dotted line as an association class. Both possible navigations use the same role-name, so this is always ambiguous. Using oclAsType() we can distinguish between them with:
context Dependency
inv: self.oclAsType(Dependency).source->isEmpty()
inv: self.oclAsType(ModelElement).source->isEmpty()
oclIsTypeOf (t : OclType)
: Boolean
oclIsKindOf (t : OclType)
: Boolean
oclInState (s : OclState)
: Boolean
oclIsNew ()
: Boolean
oclAsType (t : OclType)
: instance of OclType
The operation is oclIsTypeOf results in true if the type of self and t are the same. For example:
context Person
inv: self.oclIsTypeOf(
Person ) -- is true
inv: self.oclIsTypeOf(
Company) -- is false
The above property deals with the direct type of an object. The oclIsKindOf property determines whether t is either the direct type or one of the supertypes of an object.
The operation oclInState(s) results in true if the object is
in the state s. Values for s are the names of the states
in the statemachine(s) attached to the Classifier of object. For
nested states the statenames can be combined using the double colon `::'
.
object.oclInState(On)
object.oclInState(Off)
object.oclInstate(Off::Standby)
object.oclInState(Off::NoPower)
If there are multiple statemachines attached to the object's classifier, then the statename can be prefixed with the name of the statemachine containing the state and the double colon `::', as with nested states.
The operation oclIsNew evaluates to true if, used in a postcondition,
the object is created during performing the operation. i.e., it didn't
exist at precondition time.
A predefined feature on classes, interfaces and enumerations is allInstances, which results in the Set of all instances of the type in existence at the specific time when the expression is evaluated. If we want to make sure that all instances of Person have unique names, we can write:
context Person
inv:
Person.allInstances()->forAll(p1,
p2 |
p1
<> p2 implies p1.name <> p2.name)
The Person.allInstances() is the set of all persons and is of
type Set(Person). It is the set of all persons that exist in the system
at the time that the expression is evaluated.
The type Collection is predefined in OCL. The Collection type defines a large number of predefined operations to enable the OCL expression author (the modeler) to manipulate collections. Consistent with the definition of OCL as an expression language, collection operations never change collections; isQuery is always true. They may result in a collection, but rather than changing the original collection they project the result into a new one.
Collection is an abstract type, with the concrete collection types as its subtypes. OCL distinguishes three different collection types: Set, Sequence, and Bag. A Set is the mathematical set. It does not contain duplicate elements. A Bag is like a set, which may contain duplicates (i.e., the same element may be in a bag twice or more). A Sequence is like a Bag in which the elements are ordered. Both Bags and Sets have no order defined on them.
Collection Literals
Sets, Sequences, and Bags can be specified by a literal in OCL. Curly brackets surround the elements of the collection, elements in the collection are written within, separated by commas. The type of the collection is written before the curly brackets:
Set { 1 , 2 , 5 , 88
}
Set { 'apple' , 'orange',
'strawberry' }
A Sequence:
Sequence { 1, 3, 45,
2, 3 }
Sequence { 'ape', 'nut'
}
A bag:
Bag {1 , 3 , 4, 3, 5 }
Because of the usefulness of a Sequence of consecutive Integers, there is a separate literal to create them. The elements inside the curly brackets can be replaced by an interval specification, which consists of two expressions of type Integer, Int-expr1 and Int-expr2, separated by `..'. This denotes all the Integers between the values of Int-expr1 and Int-expr2, including the values of Int-expr1 and Int-expr2 themselves:
Sequence{ 1..(6 + 4)
}
Sequence{ 1..10 }
-- are both identical
to
Sequence{ 1, 2, 3, 4,
5, 6, 7, 8, 9, 10 }
The complete list of Collection operations is described in chapter 11 ("The OCL Standard Library").
Collections can be specified by a literal, as described above. The only other way to get a collection is by navigation. To be more precise, the only way to get a Set, OrderedSet, Sequence, or Bag is:
Set {2 , 4, 1 , 5
, 7 , 13, 11, 17 }
OrderedSet {1 , 2, 3
, 5 , 7 , 11, 13, 17 }
Sequence {1 , 2, 3 ,
5 , 7 , 11, 13, 17 }
Bag {1, 2, 3, 2, 1}
context Company
inv:
self.employee
collection1->union(collection2)
Type conformance rules are as follows for the collection types:
Set(Bicycle) conforms
to Set(Transport)
Set(Bicycle) conforms
to Collection(Bicycle)
Set(Bicycle) conforms
to Collection(Transport)
Note that Set(Bicycle) does not conform to Bag(Bicycle), nor the other
way around. They are both subtypes of collection(Bicycle) at the same level
in the hierarchy.
context Person::birthdayHappens()
post: age = age@pre
+ 1
The property age refers to the property of the instance of Person which executes the operation. The property age@pre refers to the value of the property age of the Person that executes the operation, at the start of the operation.
If the property has parameters, the `@pre' is postfixed to the propertyname, before the parameters.
context Company::hireEmployee(p
: Person)
post: employees
= employees@pre->including(p)
and
stockprice() = stockprice@pre() + 10
When the pre-value of a property evaluates to an object, all further properties that are accessed of this object are the new values (upon completion of the operation) of this object. So:
a.b@pre.c
-- takes the old value of property b of a, say x
-- and then the new value of c of x.
a.b@pre.c@pre
-- takes the old value of property b of a, say x
-- and then the old value of c of x.
The `@pre' postfix is allowed only in OCL expressions that are part
of a Postcondition. Asking for a current property of an object that has
been destroyed during execution of the operation results in OclUndefined.
Also, referring to the previous value of an object that has been created
during execution of the operation results in OclUndefined.
Tuple {name: String =
`John', age: Integer = 10}
Tuple {a: Collection(Integer)
= Set{1, 3, 4}, b: String = `foo', c: String = `bar'}
This is also the way to write tuple literals in OCL; they are enclosed in curly brackets, and the parts are separated by commas. The type names are optional, and the order of the parts is unimportant. Thus:
Tuple {name: String =
`John', age: Integer = 10} is equivalent to
Tuple {name = `John',
age = 10} and to
Tuple {age = 10, name
= `John'}
Also, note that the values of the parts may be given by arbitrary OCL expressions, so for example we may write:
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()
}
)
This results in a bag of tuples summarizing the company, number of employees, the best paid employees and total salary costs of each company a person manages.
The parts of a tuple are accessed by their names, using the same dot notation that is used for accessing attributes. Thus:
Tuple {x: Integer = 5, y: String = `hi'}.x = 5
is a true, if somewhat pointless, expression. Using the definition of statistics above, we can write:
context Person
inv:
statistics->sortedBy(totalSalary)->last().wellpaidEmployees->includes(self)
This asserts that a person is one of the best-paid employees of the
company with the highest total salary that he manages.
In this expression, both `totalSalary' and `wellpaidEmployees' are
accessing tuple parts.