en_US

Based on the provided lecture materials, this guide covers the fundamental concepts, syntax, and practical applications of the Object Constraint Language (OCL) in the context of UML modeling.

Objct Constraint Language (OCL)


1. Introduction to OCL

Why do we need OCL?

While UML diagrams (like class diagrams) are excellent for visualizing structure, they cannot express all relevant aspects of a specification.

  • Natural language is ambiguous.

  • Traditional formal languages are unambiguous but too difficult for average system modelers to read and write.

  • OCL bridges this gap: it is a formal language used to express precise constraints, yet it remains easy to read and write.

Core Characteristics of OCL

  1. Pure Expression Language: Expressions have no side effects. Evaluating an OCL expression returns a value but cannot alter the state of the system. It can, however, describe a state change (e.g., in a post-condition).

  2. Not a Programming Language: You cannot write program logic, flow of control, or invoke processes/operations that alter state.

  3. Typed Language: Every expression has a type. Well-formed expressions must obey type conformance rules. Every UML classifier is an OCL type.

  4. Instantaneous Evaluation: The state of objects cannot change during the evaluation of an OCL expression.


2. Basic Syntax and Semantics

Context and Self

Every OCL expression is evaluated in the context of a specific UML classifier (class, interface, etc.).

  • context: Defines the class the constraint applies to.

  • self: A reserved word referring to the specific instance of the context class being evaluated.

context Person
-- 'self' refers to a specific instance of Person

Comments and Operators

  • Comments: Denoted by -- (e.g., -- this is a comment).

  • Infix Operators: You can use standard math/logic operators (+-=<). Conceptually, a + b is identical to a.+(b).

Accessing Properties

Use dot notation to access attributes or operations. Parentheses are mandatory for operations, even if they have no parameters.

context Person
self.age             -- Accesses the 'age' attribute
self.stockPrice()    -- Calls the 'stockPrice' operation

3. Types and Collections

Basic Predefined Types

OCL includes standard types with specific operations:

  • Boolean: truefalse (Operations: andornotimpliesxor)

  • Integer / Real: Numbers (Operations: +-*/absmaxmindivmod)

  • String: Text (Operations: sizeconcatsubstringtoInteger)

Collections

Collections are abstract types used to hold multiple elements. They are accessed using the -> notation.

  1. Set: Mathematical set (no duplicates, unordered). Set {1, 2, 5}

  2. OrderedSet: No duplicates, but ordered by position. OrderedSet {5, 4, 3}

  3. Bag: Allows duplicates, unordered. Bag {1, 2, 2, 5}

  4. Sequence: Allows duplicates, ordered. Sequence {1, 2, 5, 10}

    • Note: The .. notation creates a sequence of consecutive integers: Sequence {1..5}.

Key Collection Operations

  • Common: size()isEmpty()notEmpty()includes(v)excludes(v)count(v)sum().

  • Iterators (Crucial for OCL):

    • select(expr): Returns elements where expr is true.

    • reject(expr): Returns elements where expr is false.

    • collect(expr): Extracts a specific property from all elements into a new collection.

    • forAll(expr): Returns true if expr is true for all elements.

    • exists(expr): Returns true if expr is true for at least one element.

    • iterate(elem; acc = init | expr): The generic base for all iterators.

Iterator Syntax Variations:

self.employee->select(age > 50)
self.employee->select(p | p.age > 50)
self.employee->select(p: Person | p.age > 50)

4. Navigating UML Models

OCL is heavily used as a navigation language to traverse associations.

Standard Navigation

Navigate using the opposite role name. The return type depends on multiplicity:

  • Multiplicity 1 or 0..1: Returns a single object.

  • Multiplicity *: Returns a Set (or OrderedSet if marked {ordered}).

context Company
self.manager.age > 40               -- manager is 0..1, returns a Person object
self.employee->notEmpty()           -- employee is 0..*, returns a Set

If a role name is missing, use the lowercase name of the class at the other end (e.g., self.bank).

Association Classes

Navigate to an association class using the lowercase name of the association class.

context Person
self.job->size() >= 1  -- 'job' is the association class between Person and Company

Recursive Associations

When an association loops back to the same class (e.g., Marriage between two Persons), you must specify the direction using square brackets [roleName].

context Person
self.marriage[wife]->select(m | m.ended = false)->size() = 1

Qualified Associations

Use qualifier attributes in square brackets to select a specific object.

context Bank
self.customer[12345]  -- Returns the specific Person with accountNo 12345
self.customer         -- Returns a Set of all customers

Re-typing (Casting)

Use oclAsType() to access properties of a subtype or a hidden superclass property.

self.oclAsType(Sub).p       -- Accesses property 'p' defined in Subtype
self.oclAsType(Super).p     -- Accesses property 'p' defined in Superclass

5. Writing Constraints

1. Invariants (inv)

Conditions that must always be true for all instances of a class.

context Company 
inv: self.noEmployees <= 50
inv SME: self.stockPrice() > 0  -- 'SME' is an optional name for the constraint

2. Pre- and Post-conditions (prepost)

Constraints tied to operations.

  • pre: Must be true before execution.

  • post: Must be true after execution. Use result for the return value.

  • @pre: Used in post-conditions to reference the value of a property before the operation started.

context Person::income(): Integer
pre adult: self.age >= 18
post resultOK: result < 5000

context Company::hireEmployee(p: Person)
post: employee = employee@pre->including(p)

3. Body, Initial, and Derived Expressions

  • body: Defines the result of a query operation (can be recursive).

  • init: Defines the initial value of an attribute.

  • derive: Defines a derived attribute.

context Person::income(): Integer
body: self.job.salary->sum()

context Person::isMarried: Boolean
init: false

context Company::noEmployees: Integer
derive: self.employee->size()

6. Advanced OCL Features

Let and Def Expressions

  • let: Defines a local variable within a single expression.

    let totHours: Integer = self.worksOn->collect(hours)->sum() in totHours >= 30
    
  • def: Defines reusable variables or operations attached to a classifier.

    def: hasTitle(t: String): Boolean = self.job->exists(title = t)
    

Undefined Values (3-Valued Logic)

If a sub-expression is undefined (e.g., navigating a null association), the whole expression is undefined. However, Boolean operators have short-circuit exceptions:

  • true or <undefined> = true

  • false and <undefined> = false

  • false implies <undefined> = true

  • <undefined> implies true = true

Class Features & Predefined Object Properties

  • allInstances(): Returns a Set of all existing instances of a class.

    Person.allInstances()->size() <= 100
    
  • Object properties: oclIsTypeOf(Type)oclIsKindOf(Type)oclInState(State)oclIsNew (used in post-conditions).


7. Comprehensive Case Study: The Company Model

To solidify these concepts, here is a collection of integrity constraints applied to a Company database model (Employees, Departments, Projects, Dependents).

Basic Attribute Constraints

-- Age must be >= 18
context Employee inv: self.age() >= 18

-- Hire date must be after birth date
context Employee inv: self.hireDate > self.birthDate

-- Manager start date must be after hire date
context Employee inv: self.manages->notEmpty() implies self.manages.startDate > self.hireDate

Navigation and Relationship Constraints

-- Supervisor must be older and paid more than the employee
context Employee 
inv: self.supervisor->notEmpty() implies self.age() > self.supervisor.age()
inv: self.supervisor->notEmpty() implies self.salary < self.supervisor.salary

-- A supervisor must be hired before their subordinates
context Employee 
inv: self.subordinates->notEmpty() implies self.subordinates->forall(e | e.hireDate > self.hireDate)

-- Department manager must be an employee of that department
context Department 
inv: self.worksFor->includes(self.manages.employee)

-- Project location must be one of the department's locations
context Project 
inv: self.controls.locations->includes(self.location)

-- Employee can only work on projects controlled by their department
context Employee 
inv: self.worksFor.controls->includesAll(self.worksOn.project)

Collection and Iterator Constraints

-- SSN must be a unique identifier across all employees
context Employee 
inv: Employee.allInstances->forAll(e1, e2 | e1 <> e2 implies e1.SSN <> e2.SSN)

-- Dependent name and relationship must be unique per employee
context Employee 
inv: self.dependents->forAll(e1, e2 | 
      e1 <> e2 implies (e1.name <> e2.name or e1.relationship <> e2.relationship))

-- Derived attribute: number of employees in a department
context Department 
inv: self.nbrEmployees = self.worksFor->size()

-- Employee works on at most 4 projects
context Employee 
inv: self.worksOn->size() <= 4

Complex Logic using let and select

-- Total working hours must be between 30 and 50 per week
context Employee 
inv: let totHours: Integer = self.worksOn->collect(hours)->sum() 
     in totHours >= 30 and totHours <= 50

-- At most 2 employees can work < 10 hours on a specific project
context Project 
inv: self.worksOn->select(hours < 10)->size() <= 2

-- Only department managers can work < 5 hours on a project
context Employee 
inv: self.worksOn->select(hours < 5)->notEmpty() implies 
     self.worksFor.manages.employee = self

-- Employees without subordinates must work >= 10 hours on ALL their projects
context Employee 
inv: self.subordinates->isEmpty() implies self.worksOn->forAll(hours >= 10)

-- Department manager must work >= 5 hours on all projects controlled by the dept
context Department 
inv: self.controls->forall(p: Project | 
      self.manages.employee.worksOn->select(hours >= 5)->contains(p))

Preventing Cycles (Recursive Logic)

-- An employee cannot supervise themselves
context Employee inv: self.subordinates->excludes(self)

-- Supervision hierarchy must not be cyclic
context Employee 
def: allSubordinates = self.subordinates->union(
          self.subordinates->collect(e: Employee | e.allSubordinates))
inv: self.allSubordinates->excludes(self)