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.
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.
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:
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.
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
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.
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.
OCL concrete syntax may be used for a number of different purposes:
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.
In order to apply OCL to UML, this chapter uses the example Class diagram shown in Figure 18-2. The diagram says that
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.
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
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.
Only those operations that do not change the state of the system may be used in OCL statements.
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.
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.
+ | 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 |
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
Constraints may be assigned to operations in two forms:
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
Attempting to access a property of an object that has been created or destroyed during execution of the operation results in the type OclUndefined.
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.
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.
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.
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.
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.
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
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
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
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)
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.
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 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 …
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.
Collection is an abstract type, with the concrete collection types as its subtypes:
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:
Each of these operations is explained later in the “OCL Standard Library” section.
OCL defines a number of operations specifically aimed at addressing the need to facilitate the manipulation of collections:
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())
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 }
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:
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.
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:
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)
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.
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.
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 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.
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.
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)
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.
post: [email protected]()
Description: Evaluates to true if the self is equal to OclUndefined.
post: result = self.isTypeOf( OclVoid )
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.
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()
Description: Returns true if the OclMessage represents the sending of a UML Signal.
Description: Returns true if the OclMessage represents the sending of a UML Operation call.
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.
Description: oclIsUndefined() evaluates to true if the object is equal to OclUndefined.
post: result = true
This section defines several enumeration types that allow the modeler to refer to elements defined in the UML model.
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)
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)
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)
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.
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.
Description: The value of the addition of self and r.
Description: The value of the subtraction of r from self.
Description: The value of the multiplication of self and r.
Description: The negative value of self.
Description: The value of self divided by r.
Description: The absolute value of self.
post: if self < 0 then result = - self else result = self endif
Description: The largest integer that is less than or equal to self.
post: (result <= self) and (result + 1 > self)
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))
Description: The maximum of self and r.
post: if self >= r then result = self else result = r endif
Description: The minimum of self and r.
post: if self <= r then result = self else result = r endif
Description: True if self is less than r.
Description: True if self is greater than r.
post: result = not (self <= r)
Description: True if self is less than or equal to r.
post: result = ((self = r) or (self < r))
Description: True if self is greater than or equal to r.
post: result = ((self = r) or (self > r))
The standard type Integer represents the mathematical concept of integer. Integer is itself an instance of the metatype Primitive (from UML Core).
Description: The negative value of self.
Description: The value of the addition of self and i.
Description: The value of the subtraction of i from self.
Description: The value of the multiplication of self and i.
Description: The value of self divided by i.
Description: The absolute value of self.
post: if self < 0 then result = - self else result = self endif
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
Description: The result is self modulo i.
post: result = self - (self.div(i) * i)
Description: The maximum of self and i.
post: if self >= i then result = self else result = i endif
Description: The minimum of self and i.
post: if self <= i then result = self else result = i endif
The standard type String represents strings, which can be ASCII or Unicode. String is itself an instance of the metatype Primitive (from UML Core).
Description: The number of characters in self.
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()
Description: Converts self to an Integer value.
Description: Converts self to a Real value.
The standard type Boolean represents the common true/false values. Boolean is itself an instance of the metatype Primitive (from UML Core).
Description: True if either self or b is true.
Description: True if either self or b is true, but not both.
post: (self or b) and not (self = b)
Description: True if both self and b are true.
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)
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 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.
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.
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)
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))
Description: Is self the empty collection?
post: result = ( self->size() = 0 )
Description: Is self not the empty collection?
post: result = ( self->size() <> 0 )
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 )
The Set is the mathematical set. It contains elements without duplicates. Set is itself an instance of the metatype SetType.
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))
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))
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 )
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))
Description: The number of occurrences of object in self.
post: result <= 1
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
Description: A set identical to self. This operation exists for convenience reasons.
post: result = self
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)
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)
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.
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)) )
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))
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)
Description: The number of occurrences of object in self.
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
Description: A bag identical to self. This operation exists for convenience reasons.
post: result = self
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))
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))
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.
Description: The number of occurrences of object in self.
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() )))
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))
Description: The i-th element of sequence.
pre : i >= 1 and i <= self->size()
Description: The index of object obj in the sequence.
pre : self->includes(obj) post : self->at(i) = obj
Description: The first element in self.
post: result = self->at(1)
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 )
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) )
Description: The sequence identical to the object itself. This operation exists for convenience reasons.
post: result = self
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))
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.
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.
The following are standard iterator expressions with source of type Collection(T).
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)
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)
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)
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
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()
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
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()
The following are standard iterator expressions with source of type Set(T).
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)
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)
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 ) )
Following are the standard iterator expressions with source of type Bag(T).
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)
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)
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 ) )
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)
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)
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 ) )
3.137.181.52