Introduction
The Object Constraint Language (OCL) is a formal language used
to express constraints in an unambiguous way. Expressions written in OCL can
return single values and collections of objects, but they do not alter the
state of the objects.
With Delphis ECO II framework,
you can use OCL throughout the process of designing your application. OCL is
available to:
- Specify the values of derived attributes of classes.
- Specify the values of derived association ends.
- Specify constraints for your objects.
- Configure ECO handles, specifying the values or objects to which
the handles refer.
- Evaluate dynamically built OCL queries and expressions at runtime.
- Translate OCL queries to SQL to be efficiently executed in the
database.
This article presents a brief introduction to OCL. It shows
some of the common operations you can perform, but is not meant to be an
exhaustive reference. The official OCL specification in PDF form can be found
at the Object
Management Group (OMG) website (http://www.omg.org/cgi-bin/doc?formal/03-03-13).
Establishing the Context of an OCL Expression
OCL expressions are evaluated in the context of a data type.
Typically this type is a class declared in the model. Within the expression,
the keyword self refers to the
instance of that data type. For example, if the context is a class called Person, and the expression is
self.Name
then self
refers to an instance of the Person
class. This OCL expression returns the value of the Name attribute of the Person
instance.
Knowing the context of the expression is particularly
important when configuring ECO handles. For more information on this topic see
the Delphi online help topic Working with ECO Handles.
Predefined OCL Types
The OCL Specification includes
four predefined, basic data types. These are:
- Integer
- Real
- Boolean
- String
In addition to these intrinsic types, OCL expressions can
refer to any data type defined in the model. These additional types can be
either values or objects.
The OCL specification includes basic collection types:
- Set: Unordered, no duplicates
- Sequence: Ordered, no duplicates
- Bag: Unordered, duplicates allowed
Basic Anatomy of an OCL Expression
An OCL expression consists of a context, and navigation from
that context to the target value you are interested in. For example, consider
the following class diagram:
If the context is Teacher, the following are valid OCL
expressions:
|
self.firstName
|
Results in a value of type string
|
|
self.lastName
|
Results in a value of type string
|
|
self.classRoomAssignment
|
Results in a value of type string
|
|
self.Courses
|
Results in a collection of Course
objects (because the multiplicity of the association is 1..*)
|
|
self.hireDate
|
Results in a DateTime object
|
|
self.Courses.numberOfStudents
|
Results in a collection of integer values (one for each
course)
|
|
self.Courses.numberOfStudents->sum
|
Results in a value of type integer (the total amount of
students in the courses of the teacher)
|
If the context is Course,
the expression
self.Instructor
results in a single instance of a Teacher (because the multiplicity of that association is
1). If the multiplicity had been 0..1, the result would have been either an
instance of Teacher, or a null
value.
ECO deals with navigation on NULL values differently than you
might expect. In the context of a Course,
the expression
self.Instructor.firstName
is valid and returns the name of the
instructor if there is one and an empty string if
the Course has no instructor.
Expressions are evaluated from left to right to get the type
and value of the result.
Association Classes
You use the dot operator to navigate to an association
class, the same way as navigating to an attribute or association end. The
difference is that you follow the dot with the name of the association class,
starting with a lowercase letter.
Suppose the association above had an association class of
type Room. If the context is Teacher, you could navigate to the association class with the expression
self.room
This expression would result in a collection of all the room
assignments for all the courses to which the teacher is assigned.
Similarly, you can navigate from an association class to
either end of the association. Starting with a context of the Room class, the expressions
self.instructor
self.courses
are valid. Navigating from an association class always
results in a single object. Later this article will show how to add an
association class to a relationship.
Operations on Types
In OCL, types themselves have predefined operations that can
be performed on them. Operations are defined on both value types, and on
collections. If the operation is specified on a basic type, use the dot to
continue the expression. If the operation is specified on a collection, use the
-> operator to continue the
expression.
For example, substring
is a predefined OCL operation defined for the string
type. The following expression
self.firstName.substring(start,stop)
returns the number of characters in the firstName attribute.
However, in the expression
self.Courses->size()
self.Courses
results in a collection of all Course
objects. The arrow operator is used instead of the dot. The expression returns
the number of elements in the collection.
Some operations take parameters. When this is the case, the
parameter list is enclosed in parentheses. For operations that take no
parameters, such as size, the
OCL specification calls for parentheses with an empty argument list. However,
in ECO, you can omit the empty parentheses.
Operations on Basic Types
The following table shows some
of the operations defined by the OCL specification for the basic types.
|
Integer
|
- =, +, -, /, *
- abs()
- div(i: Integer)
- mod(i: Integer)
- max(i: Integer)
- min(i: Integer)
|
|
Real
|
- =, <>, <, >, <=, >=
- +, -, /, *
- abs()
- floor()
- round()
- max(r: Real)
- min(r: Real)
|
|
Boolean
|
|
|
String
|
- =
- length()
Note: The OCL specification defines the size() operation on strings. ECO uses the name length().
- toUpper()
- toLower()
- subString(low: Integer, high: Integer)
- concat(s: String)
Note: In ECO, the operator + is defined as a string concatenator.
|
For all types, the operators +, , *, / <, >,
<>, <=, >=, div, mod, and, or, xor, may be written using infix
notation. The following two expressions are equivalent:
a.+(b)
a + b
Perhaps the most common operation performed on a type is the
allInstances operation. The allInstances operation retrieves a collection of all instances of the given type. The expression
Teacher.allInstances()
will result in a collection of all Teacher objects in the ECO space.
Operations on Meta Types
The following table shows other operations that are defined
for all types.
|
typeName
|
Returns the name of the type.
|
|
attributes
|
Returns the set of attributes of the type.
|
|
associationEnds
|
Returns the set of navigable association ends.
|
|
supertypes
|
Returns the set of all direct supertypes.
|
|
allSuperTypes
|
Returns the entire set of supertypes.
|
|
allSubClasses
|
Returns the set of all subclasses defined on the type.
|
Other type-related operations
|
oclIsKindOf(aType)
|
Returns true if the value is of the specified type or one
of its subtypes.
|
|
oclIsTypeOf(aType)
|
Returns true if the value is of the specified type
exactly.
|
|
oclAsType(aType)
|
Returns the same value, but typed as the specified type. A runtime exception is thrown if the
typecast fails.
|
Operations on Collections
The following table shows some
of the common operations performed on collections. It is not an exhaustive
list.
|
size()
|
Returns the number of elements in the collection.
|
|
includes(object)
|
Returns true
if the collection contains the given object.
|
|
excludes(object)
|
Returns true
if the collection does not include the given object.
|
|
count(object)
|
Returns the number of times object
occurs in the collection.
|
|
isEmpty()
|
Returns true
if the collection is empty.
|
|
notEmpty()
|
Returns true
if the collection is not empty.
|
Iterators
There is a special construct in OCL called iterators. Iterators are defined for all collections, and behave
different from normal operations. For example:
Teacher.allInstances()->select(courses->size() > 2)
The above expression will iterate over all the teacher objects, and return a new collection with the teachers that fulfill
the condition in the select statement. Normally in an expression it is possible to omit the self keyword since this is implicit. Inside an iteration the implicit variable is the loop-variable. The
courses in the example will be applied to an implicit variable of the type Teacher (regardless of the context of the expression). The following table
shows the different iterators in OCL.
select(booleanexpr) |
Returns the collection of elements that yields true. |
reject(booleanexpr) |
Returns the collection of elements that yields false. |
orderBy(anyexpr) |
Returns the same set of elements but ordered according to the anyexpr. |
orderDescending(anyexpr) |
Reverse order compared to previous. |
forAll(booleanexpr) |
Returns true if all of the objects in the collection yields true. |
exists(booleanexpr) |
Returns true if one of the object in the collection yields true . |
collect |
Returns a collection if the values returned by the iteration expression. |
iterate |
Generic iteration in the OCL specification , but not implemented in ECO. |
Iterators can be nested:
Teacher.allInstances()->select(courses->exists(numberOfStudents > 2))
It is also possible to make the implicit iterator variable explicit:
Teacher.allInstances()->select(t | t.courses->size() > 2)
Here the variable t is introduced and used to reference the loop variable.
Using OCL with ECO
Derived Attributes
The first place you are likely to encounter OCL is on the
class diagram. Attributes you create on a class can be derived by expressing a
computation with OCL.
To create an attribute with a derived value, set the
attributes derived property to true in the Object Inspector. Note that the names of derived attributes are automatically prefixed with a slash (/). This is a UML convention. Also note that setting the derived
property to true causes the persistence
property to be set to transient,
meaning the attribute will not be stored in the database.
Next, enter the OCL expression in the Derivation OCL property. Unfortunately
the OCL Expression Editor is not available on the Class Diagram, so you will
have to write the expression by hand.
For example, to add a derived attribute to the Teacher class, you could do the following:
- Right-click the Teacher class and choose Add Attribute.
- Change the attributes name to fullName.
- Set the type of the attribute to String.
- Set the derived property to true.
Notice that Delphi changes the attribute name to /fullName, and
sets the persistent property to transient.
- Enter the following OCL expression in the Derivation OCL property:
firstName + + lastName
The expression you use to calculate the value of a derived
attribute must result in value of type string (in this case, since the
attribute is declared as a string attribute); it cannot result in an object, or
a collection.
Derived Association Ends
You can also use OCL to derive association ends. Here,
unlike the case of derived attributes, the expression must result in a single
object or a collection of objects, depending on the multiplicity of the
association end.
If it is derived, the association end must be derived from another association. To create an association
end that restricts the teachers course assignments to those courses containing
more than 30 students, do the following:
- Add a new association between the Teacher
and Course classes.
Drag from the Course class to
the Teacher class.
- Select the association on the Class Diagram.
- Set the associations derived
property to true.
- Change the names of the association and association ends (double-click the name-labels
in the diagram) according to the diagram below.
- In the Model View tree, expand the Teacher
node and select the CrowdedCourses subnode.
In this example we are setting the
OCL expression for only one of the association ends, but either or both ends
can have an OCL expression associated with it.
- In the Derivation OCL property enter the expression
self.Courses->select(numberOfStudents> 30)
- Set the Multiplicity
of CrowdedCourses to 0..*.
- In the Model View tree, expand the Course
node and select the subnode for the Teacher association end.
- Set the Navigable property of the Teacher association end to false.
The Class Diagram should look similar to the following:
More on Association Classes
Earlier you saw how to navigate to an association class in
an OCL expression. Here is how you create an association class on the Class
Diagram.
- Add a new ECO class to the diagram.
For this example, the class is
called Room.
- Click the association to which you want to connect the class.
- Click the property editor ellipses for the Association
Class property in the Object Inspector.
The Choose Class to Instantiate
dialog box opens.
- Expand the tree and select the Room class.
- Click OK.
The following screen shot shows the Teacher and Course
classes with an association class called Room. Notice the name of the association has changed to that of the association class.
Configuring ECO Handles with OCL Expressions
The next place you are going to encounter OCL is designing
your applications user interface. Here, the use of OCL is not an option. You
must use OCL expressions to configure the ECO handles you place on a form. The
handles OCL expression determines those objects or values the handle will
represent at runtime. You then bind the handles to .NET controls that you place
on the form. This subject is covered in the Delphi online help topic, Working
with ECO Handles.
On the WinForms designer, you will use the OCL Expression Editor
to quickly create the expressions you need. You access the OCL Expression
Editor by clicking the expression handles Expression
property editor ellipses in the Object Inspector. The editor shows you exactly
what is available at any given step in building the expression. The syntax of
the expression is checked dynamically as you build it. You can also type the
expression manually if you wish.
The screen shot below shows the OCL Expression Editor as it
opens when first configuring an ECO ExpressionHandle
(that is connected to a ReferenceHandle
component via the RootHandle
property). The StaticValueType property of the root handle has been set to the Teacher class.
In the right-hand pane the editor shows the available
attributes, types, and operations, based on the current context (Teacher). You can build the expression by double-clicking the item you want in the right-hand pane. The expression
appears in the left-hand pane. For more information on the OCL Expression
Editor, see the Delphi online help topic, Using the OCL Expression Editor.
To bind the expression handle to the association end called Courses, double-click self.Courses in the list. The next
screen shot shows how the expression is built in the left-hand pane. Notice the
list in the right-hand pane has changed to reflect those operations that are
valid, given the current expression.
At runtime, you must set the ReferenceHandle
to refer to a Teacher object in
the ECO space. When you do this, the connected expression handle will bind to
the Courses taught by that Teacher, as dictated by the association. You could accomplish this with the following Delphi code:
// rhRoot is a default component on every ECO Winform
Winform.rhRoot.SetElement(aTeacher.AsIObject());
Using the Object Inspector, you bind a Winform controls DataSource property to the expression handle. The control will then update itself automatically when a different Teacher object is assigned to rhRoot.
Summary
This article has shown the basics of constructing an OCL
expression, and presented some common ways to use OCL in an ECO application.
More information on ECO handles and OCL is available in the following Delphi
online help topics:
- Overview of the ECO Framework
- Working with ECO Handles
- Using the OCL Expression Editor
- Configuring an OclVariables Component
- Adding Columns and Nestings to an ECO Handle