Introduction to Lesson 6
6.1 Multiplicity
6.2 Constraints
When you first ran the interpreter of Lesson 3 on a larger model, you probably detected inconsistencies: ports or perimeters not connected, or connected too many times. It would be advantageous if these logical errors could be detected as early as possible, preferably at the exact moment when the user makes the inconsistent modification.
This lesson introduces multiplicities and constraints, the GME consistency checking mechanisms.
Whenever an association is defined in the metamodel, a sequence of symbols, such as "0..*", appears at both ends. This is the multiplicity specification, which determines the acceptable number of associations in which an object can participate. The usual format is <min>.. <max> (or <minmax> if the two values are the same). "*" means infinity, so the default value, "0..*" gives practically no limit on the number of associations.
Here is an example from the networking paradigm: Interfaces should connect to at most one network or perimeter. (Unconnected interfaces will be allowed, since router ports sometimes remain temporarily or permanently idle.) To indicate this in the metamodel, change the destination multiplicity of the Connection association from "0..*" to "0..1". To achieve this, right click on the association line on the destination side and change its Cardinality attribute as described before. Also, make sure that each Perimeter is connected to exactly one Network, or to another Perimeter. Set the "dst" multiplicity of the NetworkEquiv association to "1". It is useless to allow Perimeters without associated Networks, so 0 is not permitted in this case.
On the association line, multiplicities are indicated at the opposite end from
the object to which they apply. For example, in the paragraph above, a
multiplicity rule that applies to interfaces is actually indicated at the
"network" side of the connection.
Internally, GME treats multiplicities as a special kind of constraint for which a simple specification mechanism is provided. Generic constraints are more powerful than multiplicites, but are more difficult to specify. As soon as a new version of the paradigm is generated, the necessary constraint settings will automatically take effect. These are "warning" type constraints; they are not "enforced", but violations are reported to the user in the form of a dialog box. Adherence to the constraints is checked in response to the following events:
Multiplicities specify the number of objects that can participate in a particular association, which is a typical consistency rule. However, there are many other plausible situations in which more general validity rules need to be expressed. These rules are provided by a general and functionally rich language, originally defined as part of the standard UML notation: OCL, the Object Constraint Language.
6.2.1 Object Constraint Language (OCL)
GME includes the full OCL 1.4 with some extensions to the original language. OCL is based on predicates, sentences that are either true or false and must evaluate to "true" in order to satisfy the constraint. As an example, study the following constraint:
self.parts( Fuse )->forAll( b : Fuse | b.amps <= 60 );This constraint will be satisfied only if all the "Fuse" children of the object (such as a Fusebox) have an "amps" value not greater than 60. Another constraint, slightly more complex, requires that at least five red or green fuses exist in the fusebox:
self.parts( Fuse )->select( b : Fuse | b.color ) = "red" or b.color = "green" )->size > 5The examples above illustrate the following principal features of this language:
There is a simple mechanical way to convert OCL expressions into grammatically correct natural language predicates, although the resulting sentence may be rather hard to understand. This is mainly because natural languages do not have good "bracketing" mechanisms. For example, "this dwarf is sleepy or hungry and angry" may translate to either "(sleepy or hungry) and angry", or "sleepy or (hungry and angry)" ). For the same reason, converting natural language predicates into OCL is not a straightforward task, althought OCL uses the usual precedence and associativity rules of operators and expressions.
6.2.2 A constraint example
The process of creating a constraint will be illustrated with the networking example. Let us consider the following problem: The modeling environment introduced Perimeters to represent "central" or "backbone" Networks in lower-level diagrams. So far, however, nothing has kept the user of the modeling environment from connecting a Perimeter to a Network in the same diagram. This is clearly not what Perimeters should be used for. Let's see if a constraint will help us solve the problem. The natural language constraint predicate would look something like this:
"For all network equivalence relationships, the parent of the source Perimeter must be a sibling of the destination."
We can simplify the language by avoiding the use of the "sibling" predicate and getting rid of some unnecessary information. The result is:
"For all network equivalence relationships, the parent of the parent of the source must be equal to the parent of the destination."
The predicate now contains a very limited set of functions:
This OCL expression will now be used as a constraint.
- self.attachingConnections( EquivalenceConn )->forAll( c |
- c.connectionPoints( "src" )->theOnly().target().parent().parent() =
c.connectionPoints( "dst" )->theOnly().target().parent()
6.2.3 Designing a constraint
An OCL expression becomes a constraint when it is specified in the metamodel along with some important additional information. First of all, we need to choose which metaobject the constraint should be attached to. Since a constraint usually specifies the configuration of several objects, this is not always a trivial decision, although the complexity of the constraint expression often varies depending on the object to which it is assigned. As a general rule, a constraint should be bound to the object around which the problem is centered. In the networking example, the problem focuses on the equivalence connection (NetworkEquiv) itself. From the perspective of the connection, the OCL expression looks simpler:
self.connectionPoints( "src" )->theOnly().target().parent().parent() =The automatic simplifying of the expression is a good indication that our choice for the attached object is the right one.
self.connectionPoints( "dst" )->theOnly().target().parent()
Next, we must determine when a constraint needs to be checked. Optimally, constraints should be checked on the fly, whenever a possibly relevant change occurs. Since the constraint translates into a constraint-checking algorithm which needs to be run in order to do the test, continuous checking is not possible (or, at the very least, not efficient). Instead, the metamodeler should specify the particular operations during which this constraint could be violated. GME classifies all modifying operations into the following categories.
Description | Event ID | Attribute name |
close event: GME close model | OBJEVENT_CLOSEMODEL | On close model |
The object has been created | OBJEVENT_CREATED | On create |
The object has been destroyed (limited access is available) |
OBJEVENT_DESTROYED | On delete |
A new child added | OBJEVENT_NEWCHILD | On new child |
A child removed/ moved away | OBJEVENT_LOSTCHILD | On lost child |
Object has been moved | OBJEVENT_PARENT | On move |
Subtype, instance created | OBJEVENT_SUBT_INST | On derive |
object has been connected | OBJEVENT_CONNECTED | On connect |
object has been disconnected | OBJEVENT_DISCONNECTED | On disconnect |
Name, etc. has been changed | OBJEVENT_PROPERTIES | On change attribute |
Attribute changed | OBJEVENT_ATTR | On change property |
ref pointer, set member, conn endpoint change | OBJEVENT_RELATION | On change assoc. |
object has been referenced | OBJEVENT_REFERENCED | On refer |
object reference has been released | OBJEVENT_REFRELEASED | On unrefer |
object has been included in set | OBJEVENT_SETINCLUDED | On include in set |
object has been excluded from set | OBJEVENT_SETEXCLUDED | On exclude from set |
Registry changed | OBJEVENT_REGISTRY | N/A |
Basetype relation broken/added (???) | OBJEVENT_BASE | N/A |
Anything under the object "Position" regnode | OBJEVENT_POSITION | N/A |
These events can be selected or deselected in the Attributes dialog for
Constraint objects in the metamodeling environment (Fig 6.2).
According to the list, OBJEVENT_RELATION seems to be the most suitable operation. This event occurs when a new connection is created, or when an existing one is modified. (It is also triggered if any of the target objects is moved in the modeling hierarchy.)
A third piece of information to specify is the scope (or depth) of the constraint. Sometimes the event is not generated for the object that the constraint belongs to, but to a descendant of that object. An example of this situation is when a constraint is attached to a model, which mandates some specific attribute configuration for the children of the model (e.g. "order number is unique for each child"). The events are generated for the children, while the constraint is associated with the model (it could also be associated with the children, but that may result in a clumsier constraint expression).
Depth has 3 possible values:
Our final task is deciding what to do if a constraint check fails. These actions are specified through constraint priority, which also determines the order of constraint tests so that grave violations can be dealt with before the less serious ones. Priorities range from 1 (highest) to 10 (lowest). A priority of 1 means that the constraint is enforced; if a violation takes place, it must be dealt with. Lower priorities are given to "informational" constraints, where the user can ignore the violation if desired.
Generally, a constraint should be informational if it may produce false alarms, or if there are certain situations in which the constraint may be legally violated (for example, a new object with a non-zero multiplicity is created and is not yet connected to anything). Also, constraints fired at the "close model" phase should be strictly informational.
In the networking example, there is no compelling reason to be tolerant; therefore, the highest priority will be specified.
6.2.4 Adding a constraint to the metamodel
We now have enough information to be able to add the first constraint to the networking metamodel (Fig 6.2):
Open a network diagram and update the paradigm, either directly or through XML
export/import. When the File/Register Components... dialog is opened, we see
that the constraint manager has been activated (!). Test the constraint
by attempting to connect Perimeters and Networks in the "right" way and the
"wrong" way. Also, try moving either end of the connection to see if the model
behaves as expected.
This constraint was the first to be added to the model, but it is certainly not the only possible one. Challenge yourself by creating extra constraints that fulfill the following requirements:
<< Previous Lesson | Complete List | Next Lesson >> |