CHAPTER 18

Applying Constraints to the UML Diagrams

images

In This Chapter

  • Defining the Object Constraint Language
  • Defining OCL data types
  • Defining OCL statements
  • Creating invariants, preconditions, and post conditions
  • Navigating association
  • Working with collections, messages, and tuples
  • Applying the OCL Standard Library

images

As I mentioned in earlier chapters, you can use a formal language, the Object Constraint Language (OCL), to specify constraints on attributes and associations, and, in fact, on any classifier.

images There was a previous release of the same document under the title “UML 1.4 with Action Semantics.” To my knowledge, the only difference is the addition of the Action Semantics section. The OMG simply thought that the added content warranted a new version number. You will even find references to 1.4 in the OCL 2.0 specification.

A constraint may be specified as an invariant, a precondition, or a post condition. This description of a constraint reveals one very important fact about OCL: OCL is used to specify static requirements.

images There has been work proposed to integrate a dynamic language with OCL, which might be accomplished using a blend of the action semantics described in Chapter 19. But the primary goal of a constraint language, and OCL specifically, is to define the state of the system at a point in time.

A precondition defines the state that a system (or other subject) must assume before the specified action may be taken. Likewise, a post condition defines the state that a subject must be in when the action is completed. An invariant defines a state that must hold true for the life of the subject. Consequently, a constraint cannot have any residual effect on the subject, that is, it cannot alter the subject in any way.

Now, having stressed the static nature of OCL expressions, I have to say that OCL may also be used to specify an operation or action. However, the distinction between OCL and a programming language is that OCL may only specify the result of a behavior—it cannot execute a behavior. Like an operation, the result of every OCL expression is a type. But unlike a method (the implementation of an operation), the expression cannot change the state of the system.

Furthermore, the evaluation of an OCL expression is instantaneous. This means that the states of objects in a model cannot change during evaluation.

Although there is an earlier version of OCL, this chapter covers only OCL 2.0. In the chapters on specific UML diagrams, I go into detail about both UML 1.4 and UML 2.0 so that those who are currently using UML 1.4 can understand what has changed and how to map their existing models to the updated version. However, OCL has yet to be applied to the extent that the diagrams have, and this new version of OCL is a substantial enhancement. The previous specification may be downloaded as “UML 1.5 with Action Semantics” on the OMG Web site, www.omg.org/cgi-bin/doc?ptc/2002-09.

OCL 2.0 provides some substantial enhancements to the existing standard:

  1. OCL 2.0 has a metamodel consistent with MOF 2.0, so that OCL uses and extends the same metamodel used to create other languages such as UML and CWM. This approach supports the OMG objective to provide a standard modeling framework that may
    1. be extended to provide new modeling tools (languages).
    2. ensure interchange of models between vendor products and between problem domains that support the standard. (In fact, the interchange format is defined as part of the OCL specification.)
  2. The OCL specification provides a concrete syntax that implements the principles defined by the abstract syntax (expressed as a metamodel). This relationship is analogous to the one in UML, which provides a diagramming syntax that implements the UML metamodel that also is based on the MOF.
  3. By providing a clear distinction between the abstract syntax and the concrete syntax, OCL 2.0 provides the means to define any number of alternative concrete bodies of syntax using either text or diagrams.
  4. OCL has been expanded beyond simply defining constraints to an object query and expression language. Consequently, any model created using the MOF (such as a UML or CWM model) may be queried using OCL, including the MOF model itself. This is illustrated in the fact that the well-formedness rules for the UML infrastructure and superstructure are defined using OCL statements.
  5. OCL can now invoke behaviors in order to form the content of a query. OCL statements can evaluate operations using the OCLMessage concept, a standard approach to describing a behavior and its result (in the case of synchronous calls). OCLMessage is like a wrapper for an operation. It contains the operation and supports access to the features and use of the operation. (Technically OCLMessage is a template class that is instantiated using an operation or signal as the parameter.)

Defining the Object Constraint Language

OCL 2.0 defines the language at two levels, an abstract syntax (or metamodel), and a concrete syntax, the language actually used in UML models to specify constraints and to make queries. The metamodel defines the OCL concepts and rules for applying the concepts, or abstract syntax, in the same manner that a metamodel defines the diagramming concepts and rules for UML diagrams. The concrete syntax implements the abstract syntax. The concrete syntax provided in the UML 2.0 specification is only one of any number of implementations of the OCL metamodel. The concrete syntax corresponds to the diagramming notations in UML.

Introducing the abstract syntax

Abstract syntax refers to the conceptual level of the language definition for OCL. In UML, the abstract syntax is expressed as a metamodel (2M or Infrastructure) that explains, for example, what a class is, or what an operation is, as opposed to creating a specific class or operation. Specifically, the UML 2M defines a class as a description for “a set of objects that share the same specifications of features, constraints, and semantics.” It explains that a class may be associated with any number of properties (attributes), operations, relationships, and even nested classes. In like manner, as the portion of the metamodel pictured in Figure 18-1 illustrates, the OCL metamodel defines an OCL expression and its relationships to other meta-concepts such as classifiers, loop mechanisms, and variable expressions.

In contrast, the concrete syntax, or model-level syntax, describes a class that represents some real-world entity. For example, the class Person has properties, including name and address; operations, including work, play, and sleep; and an “owns” association with the class Book. The following OCL statements define invariants on the attributes of the class Event. The first example states that the number of performances linked to an Event must be equal to or greater than one, that is, reflect a multiplicity of 1..*. The second example states that the date of the performance must be equal to or greater than the start date of the event, with which the performance is associated.

context Event inv:
self.performance->size() >= 1

context Performance inv:
self.date >= event.startDate

images

Figure 18-1: A portion of the abstract syntax/metamodel.
©OMG 2.0

The RFP for OCL 2.0 requires the new standard to define a clear distinction between the abstract syntax for OCL and any concrete syntax derived from the abstract syntax. The abstract syntax supports the development of additional constraint languages, just as the MOF/UML Infrastructure metamodel supports the development of modeling languages for various specialized domains, such as UML for modeling software and the CWM for modeling data warehouse domains.

The RFP also stated that the new standard needed to be compatible with the UML 2.0 metamodel. Consequently, the abstract syntax uses the same data types and extension mechanisms defined by the MOF/UML Infrastructure metamodel, plus a few new ones of its own.

Finally, the abstract syntax needed to support a true query language, so new concepts have been introduced. For example, the concept of tuple is added to provide the expressiveness of SQL. As a complete query language, OCL may be used to map the transformation of models, an integral part of the OMG MDA strategy for transitioning models from requirements, through design, and on to implementation.

Introducing the concrete syntax

In contrast to the rule-oriented abstract syntax, the concrete syntax applies the rules of the abstract syntax to create expressions that may be evaluated at runtime. The OCL expression is associated with a classifier, and applied to either the classifier itself or to an attribute, operation, or parameter. In each case the constraint is defined in terms of its placement, the contextual classifier, and the self instance of the OCL expression.

  • The placement is the position where the OCL expression is used in the UML model, that is, as an invariant attached to a classifier, a precondition attached to an operation, or a default value attached to a parameter.
  • The contextual classifier defines the namespace in which the expression is evaluated. For example, the contextual classifier of a precondition is the class that is the owner of the operation in which the precondition is defined. This means that all model elements within that class, such as attributes, associations, and operations, may be referenced within the OCL expression.
  • The self instance is the reference to the object that evaluates the expression. It is always an instance of the contextual classifier. This means that evaluation of an OCL expression may result in a different value for every instance of the contextual classifier. Consequently, OCL may be used to evaluate test data.

OCL concrete syntax may be used for a number of different purposes:

  • As a query language
  • To specify invariants on classes and types in the class model
  • To specify a type invariant for Stereotypes and attributes
  • To specify derivation rules for attributes
  • To describe pre- and post conditions on operations and methods
  • To describe guards
  • To specify a target, or target sets, for messages and actions
  • To specify any expression in a UML model, such as those associated with Behaviors, Parameters, ChangeTriggers and TimeTriggers, and Lifelines

images The concrete syntax still has a couple of unresolved issues. In UML 2.0, pre- and post conditions are viewed as separate entities. OCL considers them parts of a single operation specification. So the mapping of multiple pre- and post conditions in a single operation has yet to be determined.

Frame conditions define a context in which to interpret a constraint, so that unless a constraint is explicitly stated, it is understood that everything else about the context (frame) not explicitly mentioned should remain the same. This ensures that there are no undefined side effects for an operation.

Backward compatibility

The new standard also needed to be backward compatible with OCL 1.4. As an example, collections as defined in UML 1.4 (1.5 with Action Semantics) are always flat, or one-dimensional. UML 2.0 defines collections to allow for nested collections. But this means that existing operations such as collectNested() must still work as specified in the earlier version. To address this requirement, UML 2.0 preserves the old operations with their original meaning, and provides additional operations to cope with nested collections.

Accessing Properties in a UML Diagram

In order to apply OCL to UML, this chapter uses the example Class diagram shown in Figure 18-2. The diagram says that

  • A Venue employs Employees.
  • Those employees report to one another in a hierarchical structure of supervisors and subordinates. Employees may report to more than one supervisor.
  • Employees, while in the role of venue manager, may authorize contracts with agents.
  • A contract authorizes one agent, and is authorized by one venue manager.
  • In order for an agent to be in the system he must have at least one contract, that is, he has been authorized at least once by a venue manager.
  • EmployeeStatus is an enumeration that lists the valid values for the empStatus attribute used in the Employee class definition.
  • ContractStatus is an enumeration that lists the valid values for the status attribute used in the Contract class definition.

OCL statements may be assigned to just about any element seen in Figure 18-2. But in order to assign a constraint we must be able to define the placement of the constraint. The placement is defined in terms of an element of a UML model—the attribute Employee.firstName, the role name agent on the authorizes association, or the operation Agent.getName(), for example.

Every element of a UML model resides within a context. For OCL statements, the context is always expressed as an OCL classifier. The result of an OCL expression is always a data type. Figure 18-3 is the model for all OCL data types defined in the OCL Standard Library.

images

Figure 18-2: Sample Class diagram.

images

Figure 18-3: OCL Standard Library.
©OMG 2.0

The OCL Standard Library includes UML model types under the class OCLAny, as well as OCLMessages and Collections. OCLAny encompasses all OCL primitive types (Real, Boolean, String, and Integer) and all types defined in a UML model. The classes OCLType, OCLState, and OCLModelElement are enumerations that map the parts of a UML model to the OCL syntax so that the elements can be referenced in OCL statements. In other words

  • There is an entry in the OCLModelElement enumeration for every element defined in a UML model. The OCLModelElement value is used as the parameter on the oclInState(…) and oclIsKindOf(…) operations to find out the state of a model element and the type of the model element, respectively.
  • There is an entry in the OCLType enumeration for each class of objects defined in a UML model. A value from this enumeration is returned from the operation oclIsKindOf(ome:OCLModelElement) identifying the type of the model element passed in as the parameter. This is most useful when the actual type is a subtype in a generalization hierarchy and the constraint needs to evaluate the specific subtype.
  • There is an entry in the OCLState enumeration for each state defined in a UML model. A value from this enumeration is returned from the operation oclIsInState(ome:OCLModelElement) identifying the state of the model element passed in as the parameter.

OCLMessage and OCLCollection (and their subclasses) are template classes, designated by the “T” in a box in the top right corner of the class. A template class cannot be instantiated directly. Instead, it requires a parameter, some other type, which defines the contents of the instantiated class. For example, a Set with the parameter Agent is instantiated as a Set that contains only unique instances of Agent objects. A Set with the parameter Contract is instantiated as a Set that contains only unique instances of Contract objects.

The OCL Standard Library is documented fully later in this chapter.

The context for an OCL expression is written using the context keyword and the context classifier name, as the following example shows:

context Employee…
-- this context statement identifies the Employee class
as the context classifier
context Contract…
-- this context statement identifies the Contract class
as the context classifier

Note the use of the double dash (--) for comments.

When a model involves many packages there is a possibility that class names may be duplicated across packages. The following code samples represent two classes with the name Employee defined in two different packages within the TheaterSystem package. In this case, the fully qualified description of a context classifier includes the keyword package and the package path name:

package TheaterSystem::Sales
context Employee…

and

package TheaterSystem::ContractAdministration
context Employee…

where the double colons (::) act as separators for the package names to indicate containment. In this example, the ContractAdministration package is contained in the TheaterSystem package.

The constraint may be applied to the context classifier itself or to one of its properties. Properties include attributes, operations, and association ends.

images Only those operations that do not change the state of the system may be used in OCL statements.

Modeling constraints on attributes

Constraints on attributes are expressed as invariants. Invariant expressions are constructed using references to elements of the model with logical and arithmetic operators. References created within expressions may be reused through the application of let and def statements.

Invariants

An invariant is a rule that must hold true throughout the life of the attribute. In UML, an invariant (constraint) on an attribute is modeled as a property, an expression enclosed in curly braces following the attribute declaration.

Here's an example of a UML attribute declaration:

-startDate:Date {can't overlap any other contracts for
the same agent}

The default value is the current date. The invariant states that the start date of this contract cannot overlap the effective period of any other contracts for the same agent.

Invariants in OCL use the keyword inv. For example:

package TheaterSystem::ContractAdministration
context Employee
inv: firstName->size() >=1 and firstName->size() <=30

The example invariant states that the context classifier is the Employee class found in the ContractAdministration package, within the TheaterSystem package. The invariant further states that the property, an attribute called firstName, must be at least one character and no more than 30 characters in length.

The last line of the example reveals the syntax for invoking features of the OCL elements. The attribute firstName is part of the Employee class. The attribute firstName is a String. The expression firstName.size() uses the predefined operation size() on the String OCL data type. The dot notation is the OCL standard approach for accessing features of an OCL data type. Other features of the OCL String data type include concat, substring, toInteger, and toReal(). All of these features are defined in the OCL Standard Library later in this chapter.

The operators >= and <= are among the set of standard operators for OCL expressions. The complete set of operators is described in Table 18-1.

Table 18-1 OCL Infix Operators

+ addition
subtraction
* multiplication
/ division
< less than
> greater than
<> not equal to
<= less than or equal to
>= greater than or equal to
and both values must be true
or at least one of the expressions must be true
xor only one of the expressions is true

let and def expressions

In the evaluation of an OCL expression, it is often necessary to introduce an interim value used only in the expression. The let expression enables you to define a variable, assign a data type using the colon (:) designation, and even assign an initial value using the equal to (=) operator. Once the variable is defined, it may be used anywhere in the rest of the expression that contains it. When the expression completes, the variable is no longer available for use. In other words, the variable is scoped by the expression in which it is defined. In the following example, lengthOfEmployment is available only within the context Employee invariant expression.

context Employee inv:
let lengthOfEmployment : Integer = <expression> in
if lengthOfEmployment > 365
then <expression>
else <expression>
endif

The def (definition) provides the means to use a variable whenever the context classifier is evaluated. The def expression is defined independent of the places where it may be used. Once the definition is created in the context classifier, it may be used in any expression applied to the same context classifier.

An attribute definition uses the keywords def: attr:

context Agent
def: attr lengthOfEmployment : Integer = <expression>

An operation definition uses the keywords def: oper:

context Agent
def: oper averageSalesPercentage() : Integer

Modeling pre- and post conditions on operations

Constraints may be assigned to operations in two forms:

  • A precondition, which defines the required state of the model before the operation may execute.
  • A post condition, which defines the required state of the model after the operation has completed execution.

A precondition is expressed in terms of the acceptable values of the input parameters and/or the state of the model when the operation is invoked. For example, the operation Employee.setAwardStatus() is invalid if the employee is currently on probation or in disciplinary status. The context of the constraint is the Employee class. The property is the setAwardStatus() operation. The constraint is a precondition, designated with the keyword pre. The following OCL statement captures these requirements:

context Employee::setAwardStatus():Boolean
Pre:
status <> EmployeeStatus::PROBATION and
status <> EmployeeStatus::DISCIPLINE

EmployeeStatus is an enumeration, and both probation and discipline are allowed values of the enumeration. The double colon (::) is used to identify a value within the enumeration.

You also can use literals, as in

context Employee::setAwardStatus():Boolean
Pre:
status <> ‘PROBATION’ and
status <> ‘DISCIPLINE’

Note that the line breaks are discretionary. The following expressions are also valid:

context Employee::setAwardStatus():Boolean
Pre: status <> EmployeeStatus::PROBATION and
status <> EmployeeStatus::DISCIPLINE

context Employee::setAwardStatus():Boolean Pre:
status <> EmployeeStatus::PROBATION and
status <> EmployeeStatus::DISCIPLINE

A post condition states what must be true when the operation has completed. This is expressed by testing the result value of the operation and/or the state of the model when the operation has completed. For example, in the following OCL expression the operation setAwardStatus() is supposed to change the value of the attribute to EmployeeStatus::award, so when the operation has completed, the test for the expected change should result in a true value.

context Employee::setAwardStatus():Boolean
post: status = EmployeeStatus::AWARD

The standard also supports the use of constraint names. For pre- and post conditions the name is placed immediately after the keyword and before the colon, as in the following code snippet:

context Employee::setAwardStatus():Boolean
post success: status = EmployeeStatus::AWARD

A post condition may also require that, for the operation to succeed, it must have sent a message to another object. To do this, OCL needs a means to specify operation calls. To express that an operation has been invoked on another object, specify the object type, the ^ (has been sent) symbol, and the operation that must have been sent to the object. For example, when the setAwardStatus() operation has completed it must have sent the message issueAward(e:Employee) to the HumanResources object (the parameter value for the message is the current employee instance):

context Employee
post: HumanResources^issueAward(self)

The post condition reads, “The HumanResources object has been sent (^) the message ‘issueAward’, along with the information about the current employee instance (the self-instance)”.

A post condition may refer to values for properties of an object at two points during the evaluation of the expression: at the start of the operation or upon completion of the operation.

To refer to the value of a property at the start of the operation, postfix (add to the end of) to the property name the keyword ©pre:

context Employee assign(empl:Employee):Boolean
post: nbrOfSubordinates = nbrOfSubs©pre + 1

images Attempting to access a property of an object that has been created or destroyed during execution of the operation results in the type OclUndefined.

Navigating associations

A constraint is often based on the relationships between objects and/or values of attributes of associated objects. Making references to associated objects is referred to as navigating across the association. OCL uses dot notation to trace the ownership of information across a set of objects and their attributes. For example, in the sample Class diagram in Figure 18-2, an agent is associated with the employees who have functioned as venue managers on the contracts that the agent has with the theater company.

When a venue manager needs to look up information about agents with whom he has contracted, the OCL expression simply prefixes the property name with the role name of the end of the association next to the class being referenced, as in associationEndName.address. For example:

context Employee
inv: self.workLocation() = venue.address()

In this example, venue is the role name of the end of the employs association next to the Venue class, as shown in Figure 18-2.

In this second example, the result of the operation currentContract is an object of type Contract. So, the result of currentContract() is used in the OCL expression to gain access to the Contract attribute status:

context Agent terminate():Boolean
post: currentContract().status =
ContractStatus::TERMINATED

If there is no role name on the association end, the type of the object is used. This works just about everywhere except on reflexive associations where the types on both ends are the same. In that case, the role names are required in order to remove the ambiguity from the references.

Navigating across associations has to take into account the multiplicity of the association end. When the maximum multiplicity value is one, the reference is a single object, as in the previous examples. But when the multiplicity allows more than one instance, the result is a Set object containing all of the objects in the relationship, without any duplicates. If the {ordered} constraint is applied to the association end, the result is a Sequence, an ordered group of objects.

Navigating to association classes uses the same basic reasoning. Because there is no role name, the type of the reference is the type of the association class. In Figure 18-4, an agent is associated with the Contract class, an association class describing the relationship between a venue manager and an agent.

images

Figure 18-4: Navigating an association class.

When an agent behavior needs to reference a property of the association class, use the dot notation and write the name of the association class in lower case:

context Agent
inv: contract->size() >= 1

This example states that in order for an agent to exist, he must participate in at least one contract.

images As the examples become more complex, I won't write out the entire OCL expression when trying to illustrate a single point about syntax. I will occasionally use an ellipsis (…) to indicate that I am omitting some information not directly pertinent to the current topic.

If the association class describes a reflexive association, as does the Assignment class in Figure 18-5, then the class name is not enough. The association class may be accessed by employees participating on either end of the association. Without further explanation we cannot know how the employee is participating in the relationship and whether or how they should access the association class. For example, an employee functioning as a subordinate should not be able to query his supervisor's assignments, but a supervisor must be able to query her subordinates' assignments.

images

Figure 18-5: Navigating to an association class on a reflexive association.

To clarify the context, append the role name supervisor in square braces [supervisor] as in the following expression:

context Employee
inv: … self.assignment[supervisor] …

The preceding expression refers to the set of assignments that describe relationships between the context employee and all of the associated supervisors. Use this approach when you want to find out who an employee has been assigned to work for and when he worked for them.

The following expression refers to the set of assignments that describe relationships between the context employee and all of the associated subordinates. Use this approach when you want to find out who is working for the employee and when they worked for her.

context Employee
inv: … self.assignment[subordinate] …

Navigating out of an association class, to either of the classes on the ends of the association, uses the standard dot notation. When the association class Assignment needs to refer to the object playing the subordinate role it uses subordinate.property. When the association class Assignment needs to refer to the object playing the supervisor role it uses supervisor.property.

context Assignment
inv:
… subordinate.firstName …
… supervisor.firstName

To navigate a qualified association, the qualifier value is enclosed in square braces following the type of the referenced object. In the following example, a venue looks up an employee using an employee id value:

context Venue
inv: … self.employee[123456]

If there is more than one qualifier attribute, the values are separated by commas, in the order specified in the UML class model.

context Venue
inv: … self.employee[Pender, Reno]

Also note that OCL does not allow the use of a partially specified qualifier value.

Accessing overridden properties

When using subclasses, it is valid to override an inherited attribute definition or operation. If you need access to the definition in the superclass, use the oclAsType expression to recast the object reference to the type of the superclass. Here's an example:

context PremiumContract
inv: … self.oclIsType(Contract).terms …

This expression accesses the terms property as defined in the superclass Contract instead of the definition provided in the context class called PremiumContract.

Using the Predefined Properties for all Objects

There are several predefined properties that apply to all objects, namely OclType and OclState. OclType contains a reference to the classifier type associated with the context object. OclState contains a reference to the current state of the context object.

These properties can be used to evaluate an object before trying to use it. Standard operations that use these properties provide the means to test the object's type, to test whether it is a subclass of another type of object, to test the current state of the object, and to test whether it was created as part of the current operation. You can even recast the object as one of its subtypes. The operations to test these predefined properties are as follows:

oclIsTypeOf (t : OclType) : Boolean
oclIsKindOf (t : OclType) : Boolean
oclInState (s : OclState) : Boolean
oclIsNew () : Boolean
oclAsType (t : OclType) : instance of OclType

oclIsTypeOf

Use the property oclIsTypeOf to determine whether the current object is the same type as the context object. To do so, test the property value using the corresponding operation oclIsTypeOf(type). For example:

context Agent
inv: self.oclIsTypeOf(Agent ) -- is true
inv: self.oclIsTypeOf(Venue)  -- is false

oclIsKindOf

The oclIsTypeOf example dealt with the direct type of an object. Use the operation oclIsKindOf(t) to determine whether it is either the direct type of an object or one of the supertypes of an object.

context PremiumContract
inv: self.oclIsKindOf(PremiumContract)
-- true for both contracts and premium contracts

oclInState

Use the operation oclInState(s) to determine the current state of an object. Values for s are the names of the states in the state machine(s) attached to the classifier of object. For nested states, the state names may be combined using the double colon (::). In the example state machine in Figure 18-6, values for s can be Probation, Normal, Probation::AwaitingReview, Probation::NewHire.

The valid OCL expressions based on Figure 18-6 are:

object.oclInState(Probation)
object.oclInState(Normal)
object.oclInstate(Probation::AwaitingReview)
object.oclInState(Probation::NewHire)

images

Figure 18-6: Sample partial state machine for Employee.

If there are multiple state machines attached to the object's classifier, the state name can be prefixed with the name of the state machine containing the state and the double colon (::), as with nested states.

oclIsNew

Use the operation oclIsNew() only in post conditions to determine whether the object was created during the execution, as in

context Contract
Contract(start:Date, end:Date,terms:String)
post:self.oclIsNew()

In this example, the expression tests to see whether the instance of contract was created during the execution of the constructor Contract(start:Date, end:Date, terms:String).

OclAsType

OclAsType allows the expression to recast an object into a more specific type. For example, an expression may be passed a value of type Contract, but the expression needs to evaluate properties unique to PremiumContracts:

context Contract
inv: … self.oclAsType(PremiumContract).terms …

Working with Collections

Many of the results of OCL expressions contain more than a single value. Many of them include lists of objects that OCL calls Collections. OCL defines four types of lists—Collection, Set, Bag, and Sequence—as shown in Figure 18-7.

images

Figure 18-7: OCL Collection classes.

Collection is an abstract type, with the concrete collection types as its subtypes:

  • A Set contains a group of like items (the same type) and may not contain any duplicates. There is no specified order to the elements of the set.
  • A Bag contains a group of like items (the same type) but, unlike a set, may contain duplicates. There is no specified order to the elements of the bag.
  • A Sequence is like a Bag, but the elements are ordered. This feature corresponds to the use of the {ordered} constraint on a group of elements in a UML diagram—instances of a class on the end of an association or multiple values of an attribute, for example.

images The “order” in a Sequence refers to the placement of an item within the sequence, not an ordering based on the values of the elements themselves.

In UML 1.4, a collection in OCL is always flat, that is, a collection can never contain other collections as elements. This restriction is lifted in UML 2.0 to allow collections to contain elements that are also collections. The OCL Standard Library includes specific flatten operations for collections:

  • collectNested(), which is identical to the OCL 1.4 collect, but without flattening applied
  • flatten(), which flattens a nested collection
  • collect(), which is identical to collectNested()->flatten()

Each of these operations is explained later in the “OCL Standard Library” section.

Collection operations

OCL defines a number of operations specifically aimed at addressing the need to facilitate the manipulation of collections:

  • select functions like a filter to pick and choose items from a collection to make a new collection that contains only the items that meet the specified criteria.
  • reject is the opposite of select(), effectively creating a collection of items that do not meet the specified criteria.
  • collect is used to create a new collection from information gleaned from another collection or collections.,
  • forAll allows the specification of a constraint that is applied to every element in a collection.
  • exists determines whether a value is found in at least one member of a collection.
  • iterate accesses each member of a collection, enabling evaluations and queries to be performed on each element.

The properties of a collection are accessed using the arrow symbol (->) between the collection name and the property. For example,

collection->select(…)
agents->select(…)

The parameter of the select, reject, collect, forAll, and exists operations is the same. They take on four forms, three standard forms and a shorthand form.

In this first form, the operation uses the Boolean to evaluate every member of the collection:

collection->operation( boolean-expression )

Here's a sample OCL statement using the select operation:

context Venue
inv: self.employee->
select(status = EmployeeStatus :: PROBATION)

This statement iterates through all the members of the employee collection defined in the venue (through the association with the Employee class). It creates a collection of all employees employed at the current venue, and who are on probation.

In this second form, the operation uses the variable v to hold each member of the collection as it iterates through the members. It then evaluates the Boolean expression against the member held in the variable.

collection->select( v | boolean-expression )

Naming the variable allows named access to the properties of the member. In this example, status is an attribute of an employee.

context Venue
inv: self.employee->select
( e | e.status = EmployeeStatus :: probation)

In the third form, the variable is assigned a type, which must conform to the type of the collection:

collection->select( v : Type | boolean-expression )

The following example defines the type of the variable.

context Venue
inv: self.employee->select
( e : Employee | e.status = EmployeeStatus :: probation)

The iterate operation provides a shorthand form to read through every member of a collection and accumulate information. The result is a value expressed as the accumulator variable in the following syntax. In order to iterate through a collection, the iterate expression needs an iterator, designated as element in the example.

collection->iterate
( element : Type1; accumulator : Type2 = <initial value
expression> | <evaluation expression> )

This expression says, “Iterate through the collection. For each element (of Type1), evaluate the <evaluation expression> and save the result in the accumulator (of Type2), which was initialized using the <initial value expression>.”

For example, the following expression accumulates the number of contracts authorized by Employees at a venue:

Context Venue
inv:
employee->iterate
( e : Employee; contracts : Integer = 0 |
contracts = contracts + e.contract.size())

Creating collections using literals

Collections may be created explicitly by the use of literals. Simply write the name of the collection type followed by the list of comma-separated values enclosed within curly brackets, as illustrated in the following examples:

Set { 1 , 2 , 5 , 88 }
Set { ‘Tom’ , ‘Cathy’, ‘Susan’ }

Sequence { 1, 3, 45, 2, 3 }
Sequence { ‘agent’, ‘premier agaent’ }

Bag {1 , 3 , 4, 3, 5 }

A Sequence may also be defined by specifying a range using the variables Int-expr1 and Int-expr2. The range notation is the same as for multiplicity in UML—minimum..maximum, or more precisely, Int-expr1..Int-expr2. Place the range within the curly braces where you would normally place the series of comma-separated values. For example,

Sequence{ 1..10 }
Sequence{ 1..(maxCount) } -- where maxCount = 10
-- are both identical to
Sequence{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }

Using Messages in OCL

OCL supports access to existing operations. This means that an OCL expression may send messages in the forms of operation calls and signals. To do so, OCL provides three mechanisms:

  • ^: The hasSent symbol. This symbol indicates that the specified object has been sent the specified message.

    images Hereafter, I refer to this symbol (^) as the hasBeenSent symbol because I believe this name is more accurate and helps clarify the meaning of the expressions in which it is used.

  • OclMessage: OclMessage is a sort of container for evaluating messages and providing access to their features.
  • ^^: The enhanced form of the hasSent symbol, which allows access to the set of messages that has been sent. Each message is contained within an OclMessage.

To evaluate whether a message has in fact been sent, specify the target object, the ^ (hasBeenSent symbol), and the message that should have been sent. For example, the expression states that when the agent operation terminate() has completed execution, the current contract for the agent should have been sent the message terminate(). In other words, when the system terminates an agent, it must make certain that the agent's contract is also terminated.

context Agent::terminate()
post: currentContract()^terminate()

Messages may include parameters. When an operation specifies parameters, the values passed in the expression must conform to the parameter types. If the parameter values are unknown prior to evaluation of the expression, use a question mark in place of the parameter, and provide the type as shown in the following sample expression.

First, here are the operation declarations:

Agent     terminate (date : Date, vm : Employee)
Contract  terminate (date : Date)

And here's the OCL expression:

context Agent::terminate(? : Date, ? : Employee)
post: currentContract()^terminate(? : Date)

This example states that the message terminate has been sent to the current contract object, but we don't know, and don't care, what the parameter values were.

The ^^ message operator supports access to a Sequence object that contains the sent messages. Each message in the set is contained within an OclMessage (more on OclMessage in a moment). For example,

context Agent::terminate(? : Date, ? : Employee)
post: contracts^^terminate(? : Date)

This expression results in a Sequence object that contain the messages sent to all the contracts associated with the agent instance during the execution of the terminate operation on the agent.

To gain access to the messages themselves, OCL provides the OclMessage expression. OclMessage is essentially a container that provides some predefined operations that help evaluate the execution of the operation, namely:

  • hasReturned() : Boolean
  • result() : <<The return type of the called operation>>
  • isSignalSent() : Boolean
  • isOperationCall() : Boolean

The complete descriptions for these operations are provided in the OCL Standard Library.

Using OclMessage, you can now gain access to the individual messages returned by the previous expression that used the ^^ message operator. To set up the OclMessage, use the let statement to create a variable of type Sequence to contain the Sequence obtained from the ^^ message operator.

Operation declarations are as follows:

Agent     terminate (date : Date, vm : Employee)
Contract  terminate (date : Date)

And here's the OCL expression:

context Agent::terminate(? : Date, ? : Employee)
post: let messages : Sequence(OclMessage) =
contracts^^terminate(? : Date) in
messages->notEmpty and
messages->forAll(contract |
contract.terminationDate = agent.terminationDate and
contract.terminationVM = agent.terminationVM)

This expression evaluates each message sent to each contract to test whether the date and venue manager attributes have been set properly to coincide with the values in the agent.

The one significant difference between operations and signals in an OCL expression is the fact that an operation has a return and a signal does not. Attempting to use the result of an operation that has not completed execution yields an error. OCL syntax provides the hasReturned() operation to test whether an operation has completed execution. When the result of hasReturned() operation is true, the OCL expression can continue, knowing that the values generated by the operation are accessible. If the result of hasReturned() operation is false, testing the outcome result of the operation is not possible and the OCL expression should terminate.

In the previous OCL expression, the statements messages->notEmpty and following would be references to non-existent values if the operation never complete execution. The addition of the messages.hasReturned() operation prevents the following statements from executing when there are no values to reference.

context Agent::terminate(? : Date, ? : Employee)
post: let messages : Sequence(OclMessage) =
contracts^^terminate(? : Date) in
messages.hasReturned() and
messages->notEmpty and
messages->forAll(contract |
contract.terminationDate = agent.terminationDate and
contract.terminationVM = agent.terminationVM)

Creating and Using Tuples in OCL

A tuple is the definition of a block of data elements, such as a record in a file or a row in a database. Each element, or column, is named and typed. A tuple may be created using literals or assigned values based on expressions.

A tuple is defined in OCL using curly braces around a series of name : type pairs and optional assigned values:

Tuple {name : String = ‘Tom’, age : Integer = 45}

The tuple is only a means to assemble a set of values. The tuple must then be assigned to a variable. The following expression uses a def (definition) expression to create a new attribute called sales within the context of an agent classifier. sales is a Set of tuples. sale is the name of the tuple.

context Agent def:
attr sales : Set
(sale(venue: Venue, performance: Performance,
soldSeats: Integer, perfCommission: Integer)
)
…

This expression defines a Set of tuples containing information about an agent's sales for each performance. Subsequent expressions could define how to set the values for each tuple. From that point on, the sales attribute of Agent would contain the set of tuples and the assigned values.

Using Class-Level Features

All the features described so far apply to instances. But just as in programming, there are times when the application needs to perform queries and tests against the set of instances that make up the class. One such class-scoped feature in OCL is the allInstances() operation. allInstances() returns a set of objects of the type defined by the class. For example, the following expression returns the set of all agent instances that have more than one contract.

context Agent
… self.allInstances()->forAll(contract->size() > 1)…

Once you have the result set, you can use any of the collection operations on that set.

OCL Standard Library

The previous section covers a number of possible applications of OCL expressions in a UML model using a variety of OCL types. The OCL Standard Library defines the complete set of available OCL types used to form OCL expressions. Each type has a set of operations (and sometimes attributes) that is available on objects of that type.

Every OCL statement results in a value of some defined type. Very often the result is a Boolean value, because constraints are most often tests against the condition of an object or set of objects. The allowed OCL types are defined in the OCL Standard Library (refer back to Figure 18-3) and any type defined in a UML model—Venue, Contract, or Agent, for example.

OclAny covers any type defined in a UML model, using OclType, OclState, and OclModelElement to track them. Boolean, String, Real, and Integer support the basic primitive types we've come to expect in data manipulation languages.

The OclMessage and Collection metaclasses are all template classes designated with a T in the top right corner. This means that they cannot be directly instantiated. Instead, they are instantiated by providing a parameter. For example, the Sequence class may be instantiated as a sequence of type Agent (the parameter), such as an ordered list of Agents, which may include duplicates.

The following sections provide detailed descriptions of each of the standard OCL types and their usage.

The OclAny, OclMessage, and OclVoid types

The OclAny type includes not only the subclasses shown in Figure 18-3, but the UML model types as well. In fact, UML model types inherit all of the features of OclAny. The primitives correspond to the primitives defined for most programming languages.

OclMessage defines either a signal or an operation. The features of OclMessage allow access to the properties of an operation, including its result, whether it is a signal or call, and if it is a call, whether it has returned.

OclVoid defines a single instance that contains an undefined value.

OclAny

The type OclAny is the supertype of all types in the UML model and the primitive types in the OCL Standard Library. All classes in a UML model inherit all operations defined on OclAny. To avoid name conflicts between properties in the model and the properties inherited from OclAny, all names on the properties of OclAny start with ocl. One can also use the oclAsType() operation to explicitly refer to the OclAny properties.

In all of the following operation descriptions, the instance of OclAny is called object.

= (object : OclAny) : Boolean

Description: True if self is the same object as object.

post: result = (self = object)

<> (object : OclAny) : Boolean

Description: True if self is a different object from object. not is an Infix operator.

pre: result = not (self = object)

oclIsNew() : Boolean

Description: May only be used in a post condition because it tests to see whether it was created by the expression. oclIsNew() evaluates to true if the self is created during performing the operation, meaning that it didn't exist at precondition time.

oclIsUndefined() : Boolean

Description: Evaluates to true if the self is equal to OclUndefined.

post: result = self.isTypeOf( OclVoid )

OclMessage

This section contains the definition of the standard type OclMessage. As defined in this section, each OCL message type is actually a template type with one parameter. A concrete OCL message type is created by substituting an operation or signal for the parameter. Every OclMessage is fully determined by either the operation, or signal given as parameter. Every type has as attributes the name of the operation or signal, and either all formal parameters of the operation, or all attributes of the signal.

hasReturned() : Boolean

Description: True if the type of template parameter is an operation call, and the called operation has returned a value. This implies that the message has been sent.

post: result() : <<The return type of the called operation>>

Description: Returns the result of the called operation if the type of the template parameter is an operation call and if the called operation has returned a value. Otherwise the result is undefined.

pre: hasReturned()

isSignalSent() : Boolean

Description: Returns true if the OclMessage represents the sending of a UML Signal.

isOperationCall() : Boolean

Description: Returns true if the OclMessage represents the sending of a UML Operation call.

OclVoid

The type OclVoid is a type that conforms to all other types. It has a single instance called OclUndefined. Any property call applied on an undefined type results in OclUndefined, except for the operation oclIsUndefined(), which returns true.

oclIsUndefined() : Boolean

Description: oclIsUndefined() evaluates to true if the object is equal to OclUndefined.

post: result = true

ModelElement types

This section defines several enumeration types that allow the modeler to refer to elements defined in the UML model.

OclModelElement

An OclModelElement is an enumeration. For each element in a UML model there is a corresponding enumeration literal.

= (object : OclModelElementType) : Boolean

Description: True if self is the same object as object.

<> (object : OclModelElementType) : Boolean

Description: True if self is a different object from object.

post: result = not (self = object)

OclType

An OclType is an enumeration. For each Classifier in a UML model there is a corresponding enumeration literal.

= (object : OclType) : Boolean

Description: True if self is the same type as object.

<> (object : OclType) : Boolean

Description: True if self is a different type from object.

post: result = not (self = object)

OclState

An OclState is an enumeration. For each state in a UML model there is a corresponding enumeration literal.

= (object : OclState) : Boolean

Description: True if self is in the same state as object.

<> (object : OclState) : Boolean

Description: True if self is in a different state than object.

post: result = not (self = object)

Primitive types

The primitive types defined in the OCL Standard Library are Real, Integer, String, and Boolean. They are all instances of the metaclass Primitive from the UML core package.

Real

The standard type Real represents the mathematical concept of real. Note that Integer is a subclass of Real, so for each parameter of type Real, you can use an integer as the actual parameter.

+ (r : Real) : Real

Description: The value of the addition of self and r.

- (r : Real) : Real

Description: The value of the subtraction of r from self.

* (r : Real) : Real

Description: The value of the multiplication of self and r.

- : Real

Description: The negative value of self.

/ (r : Real) : Real

Description: The value of self divided by r.

abs() : Real

Description: The absolute value of self.

post: if self < 0 then result = - self
else result = self
endif

floor() : Integer

Description: The largest integer that is less than or equal to self.

post: (result <= self) and (result + 1 > self)

round() : Integer

Description: The integer that is closest to self. When there are two such integers, the largest one.

post: ((self - result).abs() < 0.5) or
((self - result).abs() = 0.5 and (result > self))

max(r : Real) : Real

Description: The maximum of self and r.

post: if self >= r then result = self
else result = r endif

min(r : Real) : Real

Description: The minimum of self and r.

post: if self <= r then result = self
else result = r endif

< (r : Real) : Boolean

Description: True if self is less than r.

> (r : Real) : Boolean

Description: True if self is greater than r.

post: result = not (self <= r)

<= (r : Real) : Boolean

Description: True if self is less than or equal to r.

post: result = ((self = r) or (self < r))

>= (r : Real) : Boolean

Description: True if self is greater than or equal to r.

post: result = ((self = r) or (self > r))

Integer

The standard type Integer represents the mathematical concept of integer. Integer is itself an instance of the metatype Primitive (from UML Core).

- : Integer

Description: The negative value of self.

+ (i : Integer) : Integer

Description: The value of the addition of self and i.

- (i : Integer) : Integer

Description: The value of the subtraction of i from self.

* (i : Integer) : Integer

Description: The value of the multiplication of self and i.

/ (i : Integer) : Real

Description: The value of self divided by i.

abs() : Integer

Description: The absolute value of self.

post: if self < 0
then result = - self
else result = self
endif

div(i : Integer) : Integer

Description: The number of times that i fits completely within self.

pre : i <> 0  post: if self / i >= 0
then result = (self / i).floor()
else result = -((-self/i).floor())
endif

mod(i : Integer) : Integer

Description: The result is self modulo i.

post: result = self - (self.div(i) * i)

max(i : Integer) : Integer

Description: The maximum of self and i.

post: if self >= i then result = self
else result = i
endif

min(i : Integer) : Integer

Description: The minimum of self and i.

post: if self <= i then result = self
else result = i
endif

String

The standard type String represents strings, which can be ASCII or Unicode. String is itself an instance of the metatype Primitive (from UML Core).

size() : Integer

Description: The number of characters in self.

concat(s : String) : String

Description: The concatenation of self and s.

post: result.size() = self.size() + string.size()
post: result.substring(1, self.size() ) = self
post: result.substring(self.size() + 1,
result.size() ) = s

substring(lower : Integer, upper : Integer) : String

Description: The substring of self starting at character number lower, up to and including character number upper. Character numbers run from 1 to self.size().

pre: 1 <= lower
pre: lower <= upper
pre: upper <= self.size()

toInteger() : Integer

Description: Converts self to an Integer value.

toReal() : Real

Description: Converts self to a Real value.

Boolean

The standard type Boolean represents the common true/false values. Boolean is itself an instance of the metatype Primitive (from UML Core).

or (b : Boolean) : Boolean

Description: True if either self or b is true.

xor (b : Boolean) : Boolean

Description: True if either self or b is true, but not both.

post: (self or b) and not (self = b)

and (b : Boolean) : Boolean

Description: True if both self and b are true.

not : Boolean

Description: True if self is false.

post: if self then result = false else result = true endif

implies (b : Boolean) : Boolean

Description: True if self is false, or if self is true and b is true.

post: (not self) or (self and b)

Collection-related types

This section defines the collection types and their operations. Each collection type is actually a template type with one parameter. A concrete collection type is created by substituting a type for the parameter, so Set (Integer) and Bag (Person) are collection types.

Collection

Collection is the abstract supertype of all collection types in the OCL Standard Library. Each occurrence of an object in a collection is called an element. If an object occurs twice in a collection, there are two elements. This section defines the operations of Collections that have identical semantics for all collection subtypes. Some of these operations may be overridden in the subtypes to provide an additional post condition or a more specialized return value.

images The definition of several common operations is different for each subtype. These operations are not mentioned in this section. The semantics of the collection operations is given in the form of a post condition that uses the IterateExp of the IteratorExp construct. The semantics of those constructs is defined in section A (Semantics) of the OCL specification. In several cases the post condition refers to other collection operations, which in turn are defined in terms of the IterateExp or IteratorExp constructs.

size() : Integer

Description: The number of elements in the collection self.

post: result =
self->iterate(elem; acc : Integer = 0 | acc + 1)

includes(object : T) : Boolean

Description: True if object is an element of self; false otherwise.

post: result = (self->count(object) > 0)

excludes(object : T) : Boolean

Description: True if object is not an element of self; false otherwise.

post: result = (self->count(object) = 0)

count(object : T) : Integer

Description: The number of times that object occurs in the collection self.

post: result =
self->iterate( elem; acc : Integer = 0 |
if elem = object then acc + 1 else acc endif)

The following OCL expressions form queries that return true or false:

includesAll(c2 : Collection(T)) : Boolean

Description: Does self contain all the elements of c2 ?

post: result = c2->forAll(elem | self->includes(elem))

excludesAll(c2 : Collection(T)) : Boolean

Description: Does self contain none of the elements of c2 ?

post: result = c2->forAll(elem | self->excludes(elem))

isEmpty() : Boolean

Description: Is self the empty collection?

post: result = ( self->size() = 0 )

notEmpty() : Boolean

Description: Is self not the empty collection?

post: result = ( self->size() <> 0 )

sum() : T

Description: The addition of all elements in self. Elements must be of a type supporting the + operation. The + operation must take one parameter of type T (the parameter type of the collection) and be both associative ((a+b)+c = a+(b+c)), and commutative (a+b = b+a). Integer and Real fulfill this condition.

post: result =
self->iterate( elem; acc : T = 0 | acc + elem )

Set

The Set is the mathematical set. It contains elements without duplicates. Set is itself an instance of the metatype SetType.

union(s : Set(T)) : Set(T)

Description: The union of self and s.

post: result->forAll(elem |
self->includes(elem) or s->includes(elem))
post: self ->forAll(elem | result->includes(elem))
post: s ->forAll(elem | result->includes(elem))

union(bag : Bag(T)) : Bag(T)

Description: The union of self and bag.

post: result->forAll(elem | result->count(elem) =
self->count(elem) + bag->count(elem))
post: self->forAll(elem | result->includes(elem))
post: bag ->forAll(elem | result->includes(elem))

= (s : Set(T)) : Boolean

Description: Evaluates to true if self and s contain the same elements.

post: result = (self->forAll(elem | s->includes(elem)) and
s->forAll(elem | self->includes(elem)) )

intersection(s : Set(T)) : Set(T)

Description: The intersection of self and s (that is, the set of all elements that are in both self and s).

post: result->forAll(elem |
self->includes(elem) and s->includes(elem))
post: self->forAll(elem |
s ->includes(elem) = result->includes(elem))
post: s ->forAll(elem |
self->includes(elem) = result->includes(elem))

intersection(bag : Bag(T)) : Set(T)

Description: The intersection of self and bag.

post: result = self->intersection( bag->asSet )

- (s : Set(T)) : Set(T)

Description: The elements of self that are not in s.

post: result->forAll(elem |
self->includes(elem) and s->excludes(elem))
post: self ->forAll(elem |
result->includes(elem) = s->excludes(elem))

including(object : T) : Set(T)

Description: The set containing all elements of self plus object.

post: result->forAll(elem |
self->includes(elem) or (elem = object))
post: self- >forAll(elem | result->includes(elem))
post: result->includes(object)

excluding(object : T) : Set(T)

Description: The set containing all elements of self without object.

post: result->forAll(elem |
self->includes(elem) and (elem <> object))
post: self- >forAll(elem |
result->includes(elem) = (object <> elem))
post: result->excludes(object)

symmetricDifference(s : Set(T)) : Set(T)

Description: The sets containing all the elements that are in self or s, but not in both.

post: result->forAll(elem |
self->includes(elem) xor s->includes(elem))
post: self->forAll(elem |
result->includes(elem) = s ->excludes(elem))
post: s ->forAll(elem |
result->includes(elem) = self->excludes(elem))

count(object : T) : Integer

Description: The number of occurrences of object in self.

post: result <= 1

flatten() : Set(T2)

Description: If the element type is not a collection type, this result is the same self. If the element type is a collection type, the result is the set containing all the elements of self.

post: result =
if self.type.elementType.oclIsKindOf(CollectionType)
then self->iterate(c; acc : Set() = Set{} |
acc->union(c->asSet() ) )
else self
endif

asSet() : Set(T)

Description: A set identical to self. This operation exists for convenience reasons.

post: result = self

asSequence() : Sequence(T)

Description: A Sequence that contains all the elements from self, in undefined order.

post: result->forAll(elem | self->includes(elem))
post: self->forAll(elem | result->count(elem) = 1)

asBag() : Bag(T)

Description: The bag that contains all the elements from self.

post: result->forAll(elem | self->includes(elem))
post: self->forAll(elem | result->count(elem) = 1)

Bag

A bag is a collection with duplicates allowed. That is, one object can be an element of a bag many times. There is no ordering defined on the elements in a bag. Bag is itself an instance of the metatype BagType.

= (bag : Bag(T)) : Boolean

Description: True if self and bag contain the same elements, the same number of times.

post: result =
(self->forAll(elem |
self->count(elem) = bag->count(elem)) and
bag->forAll(elem |
bag->count(elem) = self->count(elem))
)

union(bag : Bag(T)) : Bag(T)

Description: The union of self and bag.

post: result->forAll(elem | result->count(elem) =
self->count(elem) + bag->count(elem))
post: self ->forAll(elem | result->count(elem) =
self->count(elem) + bag->count(elem))
post: bag ->forAll(elem | result->count(elem) =
self->count(elem) + bag->count(elem))

union(set : Set(T)) : Bag(T)

Description: The union of self and set.

post: result->forAll(elem | result->count(elem) =
self->count(elem) + set->count(elem))
post: self ->forAll(elem | result->count(elem) =
self->count(elem) + set->count(elem))
post: set ->forAll(elem | result->count(elem) =
self->count(elem) + set->count(elem))

intersection(bag : Bag(T)) : Bag(T)

Description: The intersection of self and bag.

post: result->forAll(elem |
result->count(elem) =
self->count(elem).min(bag->count(elem)) )
post: self->forAll(elem |
result->count(elem) =
self->count(elem).min(bag->count(elem)) )
post: bag->forAll(elem |
result->count(elem) =
self->count(elem).min(bag->count(elem)) )

intersection(set : Set(T)) : Set(T)

Description: The intersection of self and set.

post: result->forAll(elem|result->count(elem) =
self->count(elem).min(set->count(elem)) )
post: self ->forAll(elem|result->count(elem) =
self->count(elem).min(set->count(elem)) )
post: set ->forAll(elem|result->count(elem) =
self->count(elem).min(set->count(elem)) )

including(object : T) : Bag(T)

Description: The bag containing all elements of self plus object.

post: result->forAll(elem |
if elem = object
then result->count(elem) = self->count(elem) + 1
else result->count(elem) = self->count(elem)
endif)
post: self->forAll(elem |
if elem = object
then result->count(elem) = self->count(elem) + 1
else result->count(elem) = self->count(elem)
endif)

excluding(object : T) : Bag(T)

Description: The bag containing all elements of self apart from all occurrences of object.

post: result->forAll(elem |
if elem = object then result->count(elem) = 0
else result->count(elem) = self->count(elem)
endif)
post: self->forAll(elem |
if elem = object then result->count(elem) = 0
else result->count(elem) = self->count(elem)
endif)

count(object : T) : Integer

Description: The number of occurrences of object in self.

flatten() : Bag(T2)

Description: If the element type is not a collection type, this operation results in the same bag. If the element type is a collection type, the result is a bag containing all the elements of all the collection elements of self.

post: result =
if self.type.elementType.oclIsKindOf(CollectionType)
then self->iterate(c; acc : Bag() = Bag{} |
acc->union(c->asBag() ) )
else self
endif

asBag() : Bag(T)

Description: A bag identical to self. This operation exists for convenience reasons.

post: result = self

asSequence() : Sequence(T)

Description: A sequence that contains all the elements from self, in undefined order.

post: result->forAll(elem | self->count(elem) =
result->count(elem))
post: self ->forAll(elem | self->count(elem) =
result->count(elem))

asSet() : Set(T)

Description: The set containing all the elements from self, with duplicates removed.

post: result->forAll(elem | self ->includes(elem))
post: self ->forAll(elem | result->includes(elem))

Sequence

A sequence is a collection in which the elements are ordered. An element may be part of a sequence more than once. Sequence is itself an instance of the metatype SequenceType.

count(object : T) : Integer

Description: The number of occurrences of object in self.

= (s : Sequence(T)) : Boolean

Description: True if self contains the same elements as s in the same order.

post: result =
Sequence{1..self->size()}->
forAll(index : Integer | self->at(index) = s->at(index))
and
self->size() = s->size()

union (s : Sequence(T)) : Sequence(T)

Description: The sequence consisting of all elements in self, followed by all elements in s.

post: result->size() = self->size() + s->size()
post: Sequence{1..self->size()}->
forAll(index : Integer |
self->at(index) = result->at(index))
post: Sequence{1..s->size()}->
forAll(index : Integer |
s->at(index) = result->at(index + self->size() )))

flatten() : Sequence(T2)

Description: If the element type is not a collection type, this operation results in the same self. If the element type is a collection type, the result is the sequence containing all the elements of all the sequence elements of self. The order of the elements is partial.

post: result =
if self.type.elementType.oclIsKindOf(CollectionType)
then self->iterate(c; acc : Sequence() = Sequence{} |
acc->union(c->asSequence() ) )
else self
endif

append (object: T) : Sequence(T)

Description: The sequence of elements, consisting of all elements of self, followed by object.

post: result->size() = self->size() + 1
post: result->at(result->size() ) = object
post: Sequence{1..self->size() }->
forAll(index : Integer |
result->at(index) = self ->at(index))

prepend(object : T) : Sequence(T)

Description: The sequence consisting of object, followed by all elements in self.

post: result->size = self->size() + 1
post: result->at(1) = object
post: Sequence{1..self->size()}->
forAll(index : Integer |
self->at(index) = result->at(index + 1))

insertAt(index : Integer, object : T) : Sequence(T)

Description: The sequence consisting of self with object inserted at position index.

post: result->size = self->size() + 1
post: result->at(index) = object
post: Sequence{1..(index - 1)}->
forAll(i : Integer | self->at(i) = result->at(i))
post: Sequence{(index + 1).self->size()}->
forAll(i : Integer | self->at(i) = result->at(i + 1))

subSequence(lower : Integer, upper : Integer) : Sequence(T)

Description: The subsequence of self starting at number lower, up to and including element number upper.

pre : 1 <= lower
pre : lower <= upper
pre : upper <= self->size()
post: result->size() = upper -lower + 1
post: Sequence{lower..upper}->
forAll( index |
result->at(index - lower + 1) = self->at(index))

at(i : Integer) : T

Description: The i-th element of sequence.

pre : i >= 1 and i <= self->size()

indexOf(obj : T) : Integer

Description: The index of object obj in the sequence.

pre : self->includes(obj)
post : self->at(i) = obj

first() : T

Description: The first element in self.

post: result = self->at(1)

last() : T

Description: The last element in self.

post: result = self->at(self->size() )

including(object : T) : Sequence(T)

Description: The sequence containing all elements of self plus object added as the last element.

post: result = self.append(object)

excluding(object : T) : Sequence(T)

Description: The sequence containing all elements of self apart from all occurrences of object. The order of the remaining elements is not changed.

post:result->includes(object) = false
post: result->size() =
self->size() - self->count(object)
post: result =
self->iterate(elem; acc : Sequence(T) = Sequence{}|
if elem = object then acc else acc->append(elem) endif )

asBag() : Bag(T)

Description: The bag containing all the elements from self, including duplicates.

post: result->forAll(elem |
self->count(elem) = result->count(elem) )
post: self->forAll(elem |
self->count(elem) = result->count(elem) )

asSequence() : Sequence(T)

Description: The sequence identical to the object itself. This operation exists for convenience reasons.

post: result = self

asSet() : Set(T)

Description: The set containing all the elements from self, with duplicates removed.

post: result->forAll(elem | self ->includes(elem))
post: self ->forAll(elem | result->includes(elem))

Predefined iterator expressions

This section defines the standard OCL iterator expressions. In the abstract syntax, these are all instances of IteratorExp. Iterator expressions always have a collection expression as their source, and are shown here per source collection type. The semantics of each expression are defined through a mapping from the iterator to the iterate construct, which means it does not need to be defined separately in the semantics sections.

In all the following OCL expressions, the left-hand side of the equals sign is the IteratorExp to be defined, and the right-hand side of the equals sign is an IterateExp. For example,

Employee.agents->exist(name=‘Tom’)

Name is the iterator expression, the variable that is being examined in each member. The value ‘Tom’ is the iterate expression, the value that the expression is searching for in each member variable.

The names source, body, and iterator refer to the role names in the abstract syntax as defined in Table 18-2.

Table 18-2 Iterator Expression

Iterator role Description
Source The source expression of the IteratorExp
Body The body expression of the IteratorExp
Iterator The iterator variable of the IteratorExp
Result The result variable of the IterateExp

images When new iterator expressions are added to the standard library, their mapping to existing constructs should be fully defined. If this is done, the semantics of the new iterator expression are defined.

Collection

The following are standard iterator expressions with source of type Collection(T).

Exists

Description: Results in true if body evaluates to true for at least one element in the source collection.

source->exists(iterators | body) =
source->iterate(iterators; result : Boolean = false |
result or body)

forAll

Description: Results in true if the body expression evaluates to true for each element in the source collection; otherwise, result is false.

source->forAll(iterators | body ) =
source->iterate(iterators; result : Boolean = true |
result and body)

isUnique

Description: Results in true if body evaluates to a different value for each element in the source collection; otherwise, result is false.

source->isUnique(iterators | body) =
source->collect(iterators|body)->forAll(x, y | x <> y)

sortedBy

Description: Results in the Sequence containing all elements of the source collection. The element for which body has the lowest value comes first, and so on. The type of the body expression must have the < operation defined. The < operation must return a Boolean value and must be transitive, that is, if a < b and b < c, then a < c. sortedBy may have at most one iterator variable.

source->sortedBy(iterator | body) =
iterate( iterator ; result : Sequence(T) : Sequence {} |
if result->isEmpty()
then result.append(iterator)
else let position : Integer =
result->select(item | item > iterator)->first()
in
result.insertAt(position, iterator)
endif

any

Description: Returns any element in the source collection for which body evaluates to true. If there is more than one element for which body is true, only one of them is returned. There must be at least one element fulfilling body, otherwise the result of this IteratorExp is OclUndefined. any may have at most one iterator variable.

source->any(iterator | body) =
source->select(iterator | body)->asSequence()->first()

one

Description: Results in true if there is exactly one element in the source collection for which body is true. one may have at most one iterator variable.

source->one(iterator | body) =
source->select(iterator | body)->size() = 1

collect

Description: The collection of elements that results from applying body to every member of the source set. The result is flattened. Notice that this is based on collectNested, which can be of a different type depending on the type of source. collectNested is defined individually for each subclass of CollectionType.

source->collect (iterators | body) =
source->collectNested (iterators | body)->flatten()

Set

The following are standard iterator expressions with source of type Set(T).

select

Description: The subset of set for which expr is true. select may have at most one iterator variable.

source->select(iterator | body) =
source->iterate(iterator; result : Set(T) =
Set{} |
if body
then result->including(iterator)
else result
endif)

reject

Description: The subset of the source set for which body is false. reject may have at most one iterator variable.

source->reject(iterator | body) =
source->select(iterator | not body)

collectNested

Description: The Bag of elements which results from applying body to every member of the source set.

source->collect(iterators | body) =
source->iterate(iterators; result : Bag(body.type) =
Bag{} | result->including(body ) )

Bag

Following are the standard iterator expressions with source of type Bag(T).

select

Description: The sub-bag of the source bag for which body is true. select may have at most one iterator variable.

source->select(iterator | body) =
source->iterate(iterator; result : Bag(T) =
Bag{} |
if body
then result->including(iterator)
else result
endif)

reject

Description: The sub-bag of the source bag for which body is false. reject may have at most one iterator variable.

source->reject(iterator | body) =
source->select(iterator | not body)

collectNested

Description: The Bag of elements that results from applying body to every member of the source bag.

source->collect(iterators | body) =
source->iterate(iterators; result : Bag(body.type) =
Bag{} | result->including(body ) )

Sequence

The following are standard iterator expressions with source of type Sequence(T).

select(expression : OclExpression) : Sequence(T)

Description: The subsequence of the source sequence for which body is true. select may have at most one iterator variable.

source->select(iterator | body) =
source->iterate(iterator; result : Sequence(T) =
Sequence{} |
if body
then result->including(iterator)
else result
endif)

reject

Description: The subsequence of the source sequence for which body is false. reject may have at most one iterator variable.

source->reject(iterator | body) =
source->select(iterator | not body)

collectNested

Description: The Sequence of elements that results from applying body to every member of the source sequence.

source->collect(iterators | body) =
source->iterate(iterators; result : Sequence(body.type) =
Sequence{} | result->append(body ) )

Summary

  • OCL defines an abstract syntax, a metamodel, for languages used to specify constraints.
  • OCL defines a concrete syntax that implements the abstract syntax. This concrete syntax is only one of many that may be created using the OCL metamodel.
  • OCL expressions are static and instantaneous. They cannot alter the state of the system.
  • OCL expressions require placement, a context classifier, and a self-instance.
  • OCL defines expressions and types. The set of valid types and valid expressions on those types are defined in the OCL Standard Library.
  • An invariant is a condition that must hold true for the life of the system.
  • A precondition defines the state of the system when the context operation is invoked.
  • A post condition defines the state of the system at the completion of the context operation.
  • The let expression enables you to create variables that are scoped by the expression in which they are defined.
  • The def (definition) expression enables you to create variables that are scoped by the context classifier in which they are defined.
  • The OCL Standard Library defines three types of Collections: Set, Bag, and Sequence. Collection itself is an abstract superclass.
  • OclMessage is a container for an operation. It allows access to the features of the operation, such as result(), hasReturned, isSignalSent, and isOperationCall.
  • OCL supports access to any type defined in a UML model via the OCL metaclass enumerations OclType, OclState, and OclModelElement.

images

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.181.52