Article
Understanding OCL
Learn the fundamental concepts of the Object Constraint Language.
Overview
The Object Constraint Language (https://www.omg.org/spec/OCL/) is a declarative language for querying and constraining models. It provides a precise, formal way to express constraints and queries that cannot be expressed using UML diagrams alone.
OCL is based on the OMG OCL 2.4 specification (https://www.omg.org/spec/OCL/2.4/PDF) and provides a side-effect-free expression language for navigating models and specifying constraints.
Core Characteristics
Declarative
OCL is a declarative language. You specify what you want to compute, not how to compute it. The OCL engine determines the execution strategy.
Side-Effect Free
OCL expressions cannot modify the model. They can only query the current state. This ensures that evaluating an expression does not change the system being queried.
Typed
Every OCL expression has a type. The type system ensures that operations are used correctly and helps catch errors at compile time.
OCL in Model-Driven Engineering
OCL plays a critical role in Model-Driven Engineering (MDE):
-
Constraints: Express invariants that must hold for model instances
-
Queries: Navigate and query model structures
-
Derivation Rules: Compute derived attribute values
-
Guards: Specify conditions for state transitions
-
Pre/Post Conditions: Define operation contracts
Relationship with AQL
AQL (Acceleo Query Language) is based on OCL but provides a simplified, more accessible syntax. Many AQL operations directly correspond to OCL operations. The swift-ecore ecosystem supports both OCL and AQL.
Operation Categories
Collection Operations
Collection operations query and transform collections of elements.
|
Operation |
Description |
Example |
|---|---|---|
|
|
Number of elements |
|
|
|
True if no elements |
|
|
|
True if has elements |
|
|
|
First element |
|
|
|
Last element |
|
|
|
Element at index |
|
Filtering
|
Operation |
Description |
Example |
|---|---|---|
|
|
Keep matching elements |
|
|
|
Remove matching elements |
|
Transformation
|
Operation |
Description |
Example |
|---|---|---|
|
|
Map to new values |
|
|
|
Flatten nested collections |
|
Testing
|
Operation |
Description |
Example |
|---|---|---|
|
|
All elements match |
|
|
|
Any element matches |
|
|
|
Exactly one matches |
|
|
|
Contains element |
|
|
|
Doesn’t contain |
|
Set Operations
|
Operation |
Description |
Example |
|---|---|---|
|
|
Combine collections |
|
|
|
Common elements |
|
|
|
Remove specific element |
|
|
|
Add element |
|
Iteration
|
Operation |
Description |
|---|---|
|
|
Custom iteration with accumulator |
String Operations
String operations manipulate and query text.
|
Operation |
Description |
Example |
|---|---|---|
|
|
Uppercase |
|
|
|
Lowercase |
|
|
|
Concatenate |
|
|
|
Extract portion |
|
|
|
Find position |
|
|
|
Contains substring |
|
|
|
Check prefix |
|
|
|
Check suffix |
|
|
|
Replace all |
|
|
|
Remove whitespace |
|
|
|
Character at index |
|
Numeric Operations
Numeric operations perform mathematical computations.
|
Operation |
Description |
Example |
|---|---|---|
|
|
Absolute value |
|
|
|
Floor |
|
|
|
Ceiling |
|
|
|
Round |
|
|
|
Maximum value |
|
|
|
Minimum value |
|
|
|
Exponentiation |
|
Arithmetic
|
Operation |
Description |
Example |
|---|---|---|
|
|
Addition |
|
|
|
Subtraction |
|
|
|
Multiplication |
|
|
|
Division |
|
|
|
Modulo |
|
Comparison Operations
Comparison operations compare values.
|
Operation |
Description |
Example |
|---|---|---|
|
|
Equal |
|
|
|
Not equal |
|
|
|
Less than |
|
|
|
Less or equal |
|
|
|
Greater than |
|
|
|
Greater or equal |
|
Logical Operations
Logical operations combine boolean values.
|
Operation |
Description |
Example |
|---|---|---|
|
|
Logical AND |
|
|
|
Logical OR |
|
|
|
Logical NOT |
|
|
|
Implication |
|
Type Operations
Type operations check and cast types at runtime.
|
Operation |
Description |
Example |
|---|---|---|
|
|
Exact type match |
|
|
|
Instance of type or subtype |
|
|
|
Cast to type |
|
|
|
Get the type name |
|
|
|
Is null/undefined |
|
Example Patterns
Filtering Model Elements
import ECore
import OCL
// Find all abstract classes in a package
let abstractClasses = try OCL.select(package.eClassifiers) { classifier in
guard let eClass = classifier as? EClass else {
return false
}
return eClass.abstract
}
// Find classes with specific attributes
let classesWithId = try OCL.select(package.eClassifiers) { classifier in
guard let eClass = classifier as? EClass else {
return false
}
return try OCL.exists(eClass.eAttributes) { $0.isID }
}
Computing Derived Values
import ECore
import OCL
// Count total attributes across all classes
let totalAttributes = try OCL.collect(package.eClassifiers) { classifier in
guard let eClass = classifier as? EClass else {
return 0
}
return try OCL.size(eClass.eAttributes)
}.reduce(0, +)
// Collect all attribute names
let allAttributeNames = try OCL.collect(package.eClassifiers) { classifier in
guard let eClass = classifier as? EClass else {
return []
}
return try OCL.collect(eClass.eAttributes) { $0.name }
}.flatMap { $0 }
Validation Constraints
import ECore
import OCL
// Check that all classes have unique names
let hasUniqueNames = try OCL.forAll(package.eClassifiers) { classifier1 in
try OCL.one(package.eClassifiers) { classifier2 in
classifier1.name == classifier2.name
}
}
// Verify all references have opposites where required
let validReferences = try OCL.forAll(package.eClassifiers) { classifier in
guard let eClass = classifier as? EClass else {
return true
}
return try OCL.forAll(eClass.eReferences) { reference in
!reference.eOpposite.oclIsUndefined() || !reference.containment
}
}
Error Handling
OCL operations throw OCLError when they encounter invalid conditions:
do {
let result = try OCL.first([]) // Empty collection
} catch OCLError.invalidOperation(let message) {
print("Invalid operation: \(message)")
} catch OCLError.typeError(let message) {
print("Type error: \(message)")
} catch {
print("Unexpected error: \(error)")
}
Best Practices
-
Use meaningful predicates: Write clear, readable conditions in select/reject operations
-
Check for null: Use
oclIsUndefined()before navigating potentially null references -
Prefer declarative operations: Use select/collect/forAll instead of manual iteration
-
Handle errors appropriately: Catch OCLError and provide meaningful error messages
-
Keep expressions simple: Break complex queries into smaller, reusable operations
OCL vs AQL
OCL and AQL are closely related:
-
AQL provides simplified syntax (
col->select(e | e > 5)) -
OCL uses function-based syntax (
select(col) { $0 > 5 }) -
Both provide the same semantic operations
-
AQL is preferred for template-based code generation (MTL)
-
OCL is preferred for programmatic model querying
Next Steps
-
Explore Getting Started with OCL for practical examples
-
See OCLUnaryMethod, OCLBinaryMethod, and OCLTernaryMethod for complete operation listings
-
Review the OCL source files for detailed function documentation
See Also
Related Documentation
Essentials
Learn how to use OCL operations in your Swift projects.