390 likes | 534 Views
The Object Constraint Language (OCL). Jacques Robin. Outline. What is OCL? Uses of OCL for Model-Driven Development Motivating examples The OCL meta-model OCL Types OCL Expression Context OCL and Inheritance Primitive Type Operators Local Variables Collections and Tuples Messages
E N D
The Object Constraint Language (OCL) Jacques Robin
Outline • What is OCL? • Uses of OCL for Model-Driven Development • Motivating examples • The OCL meta-model • OCL Types • OCL Expression Context • OCL and Inheritance • Primitive Type Operators • Local Variables • Collections and Tuples • Messages • Packages • OCL Constraints vs. UML Constraints • OCL Formal Semantics • OCL vs. Java
What is OCL? • A textual specification language to adorn UML diagrams and make a UML model far more semantically precise, detailed and thus productive (i.e., subject to automated processing leading to code). • Characteristics: • Formal language with well-defined semantics based on set theory and first-order predicate logic, yet free of mathematical notation and thus friendly to mainstream programmers • Object-oriented functional language: constructors syntactically combined using functional nesting and object-oriented navigation in expressions that take objects and/or object collections as parameters and evaluates to an object and/or an object collection as return value • Strongly typed language where all expression and sub-expression has a well-defined type that can be an UML primitive data type, a UML model classifier or a collection of these • Semantics of an expression defined by its type mapping • Declarative language that specifies what properties the software under construction must satisfy, not how it shall satisfy them • Side effect free language that cannot alter model elements, but only specify relations between them (some possibly new but not created by OCL expressions) • Pure specification language that cannot alone execute nor program models but only describe them • Both a constraint and query language for UML models
Uses of OCL for Model-Driven Development: Formalizing Structure • Adorn a class diagram in UML structural models or MOF meta-models to make them formally precise • Specify complex constraints (value, multiplicity, type, etc) between multiple attributes and associations • Disambiguate association cycles • Specifying rules that define derived attributes, associations and classes
Uses of OCL for Model-Driven Development: Formalizing Behavior • Specify query operation bodies • Specify operation pre and post conditions for design by contract and automated test generation • Adorn an interaction diagrams (associated to a class diagram) • Specify objects • Specify message expressions including guards, iteration and condition clauses, return value signature • Specify guards on lifelines of alternates fragments • Adorn a state machine (associated to a class diagram): • Specify trigger and deferred events, guards, state activities • Specify object target and parameter values of transition action, state entry action, state exit action • Specify constraints between the state of an object and its other features (attributes, association ends) • Adorn an activity diagram (associated to a class diagram): • Specify transition guards • Specify object host and parameter values of actions • Specify objects in object flows and their states • Adorn a use case diagram (associated to a class diagram): • Specify the pre and post conditions of each use case
OCL: Motivating Examples • Diagram 1 allows Flight with unlimited number of passengers • No way using UML only to express restriction that the number of passengers is limited to the number of seats of the Airplane used for the Flight • Similarly, diagram 2 allows: • A Person to Mortgage the house of another Person • A Mortgage start date to be after its end date • Two Persons to share same social security number • A Person with insufficient income to Mortgage a house 1 2
OCL: Motivating Examples contextFlightinv:passengers-> size()<=plane.numberOfSeats 1 contextPersoninv:Person::allInstances() -> isUnique(socSecNr) context Person::getMortgage(sum:Money,security:House) pre: self.mortgages.monthlyPayment -> sum() <= self.salary * 0.3 contextMortgageinv:security.owner=borrower inv:startDate<endDate 2
Operation OCL Expression Context
Specifying Initial Property Valuesand Query Operations Initial values: • contextLoyaltyAccount::points: integerinit:0 • contextLoyaltyAccount::transactions : Set(Transaction)init:Set{} Query operations: • contextLoyaltyAccount::getCustomerName(): Stringbody: Membership.card.owner.name • context LoyaltyProgram::getServices(): Set(Services)body: partner.deliveredServices -> asSet()
Defining New Attributesand Operations New attribute: • contextCustomerdef:initial:String =name.substring(1.1) New operation: • contextCustomerCarddef:getTotalPoints(d:Date):Integer=transactions-> select(date.isAfter(d)).points -> sum()
Dot operator • Association navigation: • contextTransactiondef getCustomer():Customer=self.card.owner • Attribute access: • context Transactiondef getCustomerName():String=self.card.owner.name • Abbreviation of collect operator that creates new collection from existing one, for example result of navigating association with plural multiplicity: • contextLoyaltyAccountinv:transactions-> collect(points) ->exists(p:Integer | p=500) • contextLoyaltyAccountinv:transactions.points ->exists(p:Integer | p=500) • Call UML model and OCL built-in operations
Association Navigation Use target class name to navigate roleless association: • context LoyaltyPrograminv:levels->includesAll(Membership.currentLevel) Identify navigation qualifier between []: • contextLoyaltyPrograminv:self.levels[1].name=‘basic’
Generalization Navigation • OCL constraint to limit points earned from single service to 10,000 • Cannot be correctly specified using association navigation: contextProgramPartnerinv totalPoints:deliveredServices.transactions.points-> sum() <10,000 adds both Earning and Burning points • Operator oclIsTypeOf allows hybrid navigation following associations and specialization links contextProgramPartnerinv totalPoints:deliveredServices.transactions-> select(oclIsTypeOf(Earning)).points-> sum() <10,000
Specifying Invariants on Attributes The context of an invariant constraint is a class When it occurs as navigation path prefix, the self keyword can be omitted: • context Customer inv: self.name = ‘Edward’ • context Customer inv: name = ‘Edward’ Invariants can be named: • context Customer inv myInvariant23: self.name = ‘Edward’ • contextLoyaltyAccountinv oneOwner:transaction.card.owner-> asSet() -> size() = 1 In some context self keyword is required: • context Membershipinv: participants.cards.Membership.includes(self)
Specifying Derivation Rules for Attributes • contextCustomerCard::printedName derive:owner.title.concat(‘ ‘).concat(owner.name) • context TransactionReportLine: Stringderiveself.date= transaction.date • ... • context TransactionReportinvdates: lines.date -> forAll(d | d.isBefore(until) and d.isAfter(from)) • ...
Design by Contract: Pre and Postconditions on Operations • A class or interface offers services to other classes through a set of typed public operations • This service can be designed by contract through the specification of the pre-condition and post-condition of each operation • The contract states that: • Whenever a client class invokes an operation in way that satisfies its specified preconditions • The server class or interface guarantees that the execution of the operation will result in the satisfaction of its specified post-conditions • The preconditions thus specify the rights of the server while the post-conditions specify its obligations
Specifying Contractual Interfaces • context LoyaltyAccount::isEmpty(): Booleanpre: -- nonepost: result = (points = 0) Keyword @pre used to refer in post-condition to the value of a property before the execution of the operation: • contextLoyaltyProgram::enroll(c:Customer)pre:c.name<> ‘ ‘post:participants=participants@pre ->including(c) Keyword oclIsNew used to specify creation of a new instance (objects or primitive data): • contextLoyaltyProgram::enrollAndCreateCustomer(n:String,d:Date):Customerpost:result.oclIsNew() and result.name=nand result.dateOfBirth =d and participant -> includes(result) oclIsNew only specifies that the operation created the new instance, but not how it did it which cannot be expressed in OCL
Disambiguating Association Cycles, Optional Multiplicity and Or Constraints • Cycles of association with plural multiplicity are often underconstrained by class diagrams, thus not reflecting actual modeled domain semantics • In the real world a person cannot use the house of another person as security for a mortgage • context Personinv: mortgages.security.owner -> forall(onwner:Person | owner= self) • UML ‘or’ constraint can mean: • either that a Person can either manage a Project or perform a Project but not both • or that a Project is managed by one Person or performed by one Person but not both • OCL invariant disambiguating between these two interpretations: • contextPersoninv:managedProject-> isEmpty() orperformedProject-> isEmpty() • contextProjectinv:projectLeader-> isEmpty() or projectMember -> isEmpty()
OCL Types • Value Types: • UML primitive types (including user-defined enumerations) • OCL collection types (even of user-defined classifiers, since collections are never processed by changing their states, but only partially copied to create new one) • Their instances never change value • ex, Integer instance 1 cannot be changed to instance 2, nor can string instance “Lew Alcindor” be changed to string instance “Kareem Abdul-Jabbar”, nor can enumeration Grade instance A can be changed to enumeration instance C. • Object types: UML classifiers • Their instances can change value, i.e., the Person instance p1 can have its name attribute “Lew Alcindor” changed to “Kareem Abdul-Jabbar” yet remain the same instance p1 • OclAny: • Most generic OCL type, subsuming all others • General reflective operations are defined for this type and inherited by all other OCL types
OCL Types • Primitive data types from UML: boolean, string, integer, unlimitedNatural • Reals • Type conformance rules: • t1 conforms to t2 if t1 <= t2 in type hierarchy • t1 = collection(t2) conforms to t3 = collection(t4) if t2 conforms to t4 • integer <= real • Type casting: • Operation oclAsType(s) can be invoked on an expression of type g to recast it as a type s • s must conform to g • OclVoid: • Undefined value (similar to null values of SQL) • Tested by oclIsUndefined operation of OclAny type
By default, OCL expressions ignore attribute visibility i.e., an expression that access a private attribute from another class is not syntactically rejected OCL constraints are inherited down the classifier hierarchy OCL constraints redefined down the classifier hierarchy must follow substituability principle Invariants and post-condition can only become more restrictive Preconditions can only become less restrictive contextStoveinv:temperature<= 200 contextElectricStoveinv:temperature<=300 context Stove::open() pre: status = StoveState::off post: status = StoveState::off and isOpen context ElectricStove::open() pre: status = StoveState::off andtemperature <= 100 post: isOpen Inheritance and Encapsulation
Local Variables • Let constructor allows creation of aliases for recurring sub-expressions context CustomerCard inv: let correctDate: Boolean = validFrom.isBefore(Date::now) andgoodThru.isAfter(Date::now) in if correctDate then valid = true else valid = false endif • Syntactic sugar that improves constraint legibility
Generic Operators • Operators that apply to expressions of any type • Defined at the top-level of OclAny
Primitive Type Operators • Boolean: host, parameter and return type boolean • Unary: not • Binary: or, and, xor, =, <>, implies • Ternary: if-then-else • Arithmetic: host and parameters integer or real • Comparison (return type boolean): =, <>, <, > <=, >=, • Operations (return type integer or real): +, -, *, /, mod, div, abs, max, min, round, floor • String: host string • Comparison (return type boolean): =, <> • Operation: concat(String), size(), toLower(), toUpper(), substring(n:integer,m:integer)
Collections • Collection constants can be specified in extension: • Set{1, 2, 5, 88}, Set{‘apple’, ‘orange’, ‘strawberry’} • OrderedSet{‘black’, ‘brown’, ‘red’, ‘orange’, ‘yellow’, ‘green’, ‘blue’, ‘purple’} • Sequence{1, 3, 45, 2, 3}, Bag{1, 3, 4, 3, 5} • Sequence of consecutive integers can be specified in intension: • Sequence{1..4} = Sequence{1,2,3,4} • Collection operations are called using -> instead of . • Collection operations have value types: • They do not alter their input only output a new collection which may contain copies of some input elements • Most collections operations return flattened collections • ex, flatten{Set{1,2},Set{3,Set{4,5}}} = Set{1,2,3,4,5} • Operation collectNested must be used to preserve embedded sub-structures • Navigating through several associations with plural multiplicity results in a bag
Syntax of Loop Operators • Loop operators (aka. iterator operations, aka. iterators) • Common characteristics: • They are all hosted by an OCL expression of type collection • They all take an OCL expression as input parameter (called the body of the loop) • They optionally take as second parameter an iterator variable • The return type of the body expression and the type of the iterator variable must conform to the type of the host collection’s elements • Loop operators iterate over the elements of the host collection, applying the body expression to each one of them • Distinguishing characteristics: • How they combine the body expression application into a new result collection
Example of OCL Expressions with Loop Operators • context LoyaltyProgram inv self.Membership.account -> isUnique(acc:LoyaltyAccount | acc.number) • context LoyaltyProgram inv Membership.account -> isUnique(acc | acc.number) • context LoyaltyProgram inv Membership.account -> isUnique(number) • Iterator variable clarifies that number refers to the number attribute of each collection element and not to the number of elements in the collection • Loop expressions with reference to the iterator requires an iterator variable: • context ProgramPartner inv: self.programs.partners -> select(p:ProgramPartner | p.self)
Tuples • Values can be composed in tuples of types fields of heterogeneous types • Tuple {<fieldName>: <TypeExpression> = <ValueExpression>, ...} • Tuple {name: String = ‘John’, age: Integer = 10} Tuple {a: Collection(Integer) = Set{1,2,7}, b: String = ‘foo’} • Tuple types can be specified using same syntax without value expression: • TupleType {<fieldName>: <TypeExpression>, ...} • TupleType {name: String, age = Integer} • Convenient to express constraints on data layers using relational model
Messages • isSent ^ operator in an operation post-condition, specifies which messages (operation calls or signals) must be sent by the execution of the operation • context Subject::hasChanged() post: observer^update(12,14) • ? can be used to specify unknown messages of a given type • context Subject::hasChanged() post: observer^update(? :integer, ? :integer) • In addition to their base types, all messages sent are also instances of the special type OclMessage • Message operator ^^ allows performing pattern matching on sent messages • context Subject::hasChanged() post: let messages: Sequence(OclMessage) = observers ->collect(o | o^^update(? :integer, ? :integer)) in messages -> notEmpty() • Other message operators: • Booleans isSignalSent(), isOperationCall(), hasReturned() • result() returns type of called operation
Packages • OCL expressions can be structured in packages and sub-packages • Package can be specified inside a context specification: context P::S::C inv ... context P::S::C::op() pre: ... post: ... • Or with keyword pair package, endpackage surrounding set of expressions: package P::S context X inv: ... context X::op pre: ... post: ... endpackage
OCL Constraints vs. UML Constraints context: ClassicalGuitar inv: strings-> forAll(s | s.oclIsType(plasticStrings)) context ElectricGuitar inv: strings -> forAll(s \ s.oclIsType(MetalStrings)) context Guitar inv: type = GuitarType::classic implies strings -> forAll(type = StringType::plastic inv: type = GuitarType::classic implies strings -> forAll(type = StringType::plastic context ClassicGuitar inv: strings -> forAll(type = StringType::plastic) context ElectricGuitar inv: strings -> forAll(type = StringType::metal)
OCL Formal Semantics • Set theoretic • Semantic domain for each OCL type defined by its set of possible values • Semantics of an OCL expression defined by the mathematical function mapping the semantic domains of its host and input parameters to its return value
OCL vs. Java • Declarative specification of operation post-conditions in OCL is far more concise than corresponding implementation in mainstream imperative OO language such as Java • This is due mainly to OCL’s powerful collection operators • Example: OCL expressionself.partners -> select(deliveredServices -> forAll(pointsEarned = 0)) • Corresponding Java code: • Iterator it = this.getPartners().iterator(); • Set selectResult = new HashSet(); • while( it.hasNext() ){ • ProgramPartner p = (ProgramPartner) it.next(); • Iterator services = p.getDeliveredServices().iterator(); • boolean forAllresult = true; • while( services.hasNext() ){ • Service s = (Service) services.next(); • forAllResult = forAllResult && (s.getPointsEarned() == 0); • } • if ( forAllResult ){ • selectResult.add(p); • } • } • return result;