UML 2.6 RTF Avatar
  1. OMG Issue

UMLR — Improving the association direction notation

  • Key: UMLR-290
  • Legacy Issue Number: 19017
  • Status: open  
  • Source: NASA ( Dr. Nicolas F. Rouquette)
  • Summary:

    The UML 2.5 notation for associations in section 11.5.4 states (4th paragraph):

    On a binary Association drawn as a solid line, a solid triangular arrowhead next to or in place of the name of the Association and pointing along the line in the direction of one end indicates that end to be the last in the order of the ends of the Association. The arrow indicates that the Association is to be read as associating the end away from the direction of the arrow with the end to which the arrow is pointing (see Figure 11.27). This notation is for documentation purposes only and has no general semantic interpretation. It is used to capture some application-specific detail of the relationship between the associated Classifiers.

    In practice, the order of association ends is not very useful. Deriving the direction of an association based on association end cardinality, aggregation type and navigability, which is a function of ownership (see Property::isNavigable()) would be more useful.

    I propose the following criteria (written in QVT Operational):

    modeltype uml uses 'http://www.nomagic.com/magicdraw/UML/2.4.1';

    /**

    • @author nicolas.f.rouquette@jpl.nasa.gov
    • October 2013 - UML2.6 Improving the association direction notation.
      */
      transformation AssociationDirectionCheck(in selectedAssociations:uml);
      property associations : Set(uml::Association) = selectedAssociations.rootObjects()[uml::Association];

    main() {
    log('Analyzing ' + associations->size().repr() + ' associations');
    associations->sortedBy(qualifiedName)->forEach(a)

    { var p := a.memberEnd![name='p']; var q := a.memberEnd![name='q']; log('Association ' + a.name + ' : ' + p.type.name + ' -- ' + q.type.name); p.describe('end1'); q.describe('end2'); }

    }

    helper uml::Property::describe(in prefix:String) {
    var a := self.association;
    assert fatal (a.oclIsKindOf(uml::Association));
    var other := a.memberEnd->excluding(self)->any(true);
    var dir := 'n/a';
    if (self.isMemberEndLogicallyDirectedToOtherEnd()) then dir := self.name + '>>' + other.name endif;
    if (other.isMemberEndLogicallyDirectedToOtherEnd()) then dir := other.name + '>>' + self.name endif;
    log(prefix + ': ' + self.namespace.name + '::' + self.name + ' : ' + self.type.name
    + '[' + self.lower.repr() + '..' + (if self.upper < 0 then '*' else self.upper.repr() endif) + ']'
    + '

    {memberEnd#' + a.memberEnd->indexOf(self).repr() + ', aggregation=' + self.aggregation.repr() + ', isNavigable=' + self.isNavigable().toString() + ', direction=' + dir + '}

    ');
    }

    helper uml::Property::isMemberEndLogicallyDirectedToOtherEnd() : Boolean

    { var a := self.association; assert fatal (a.oclIsKindOf(uml::Association)); var other := a.memberEnd->excluding(self)->any(true); var fwdDirByClassOrNav := ((self.owner = a) and (other.owner <> a)) or (not self.isNavigable()) and other.isNavigable(); var fwdDirByComposition := (not self.isComposite) and other.isComposite; var fwdDirByCardinality := (not self.isComposite) and (not other.isComposite) and (self.upper <= 1) and (other.upper < 0 or other.upper > 1); return fwdDirByClassOrNav or ((not fwdDirByClassOrNav) and (fwdDirByComposition or fwdDirByCardinality)); }

    query Boolean::toString() : String

    { if (self) then return 'Y' endif; return 'F'; }

    For a representative set of test cases varying all combinations of association end aggregation type, cardinality, ownership, navigability, member end order,
    the above criteria suffices to determine whether an association with ends p and q is in the forward direction (p>>q) or reverse (q>>p):

    [10/13/13 2:03 PM] Analyzing 22 associations
    [10/13/13 2:03 PM] Association AB'0 : A'0 – B'0
    [10/13/13 2:03 PM] end1: AB'0: : A'0[1..1]

    {memberEnd#1, aggregation=none, isNavigable=F, direction=p>>q}
    [10/13/13 2:03 PM] end2: A'0::q : B'0[1..1] {memberEnd#2, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association AB'1 : A'1 – B'1
    [10/13/13 2:03 PM] end1: AB'1: : A'1[1..1] {memberEnd#1, aggregation=none, isNavigable=F, direction=p>>q}

    [10/13/13 2:03 PM] end2: AB'1::q : B'1[1..1]

    {memberEnd#2, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association AB0 : A0 – B0
    [10/13/13 2:03 PM] end1: AB0: : A0[1..1] {memberEnd#2, aggregation=none, isNavigable=F, direction=p>>q}
    [10/13/13 2:03 PM] end2: A0::q : B0[1..1] {memberEnd#1, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association AB1 : A1 – B1
    [10/13/13 2:03 PM] end1: AB1: : A1[1..1] {memberEnd#2, aggregation=none, isNavigable=F, direction=p>>q}
    [10/13/13 2:03 PM] end2: AB1::q : B1[1..1] {memberEnd#1, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association CD'0 : C'0 – D'0
    [10/13/13 2:03 PM] end1: CD'0: : C'0[0..1] {memberEnd#1, aggregation=none, isNavigable=F, direction=p>>q}
    [10/13/13 2:03 PM] end2: C'0::q : D'0[1..1] {memberEnd#2, aggregation=composite, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association CD'1 : C'1 – D'1
    [10/13/13 2:03 PM] end1: CD'1: : C'1[0..1] {memberEnd#1, aggregation=none, isNavigable=F, direction=p>>q}
    [10/13/13 2:03 PM] end2: CD'1::q : D'1[1..1] {memberEnd#2, aggregation=composite, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association CD0 : C0 – D0
    [10/13/13 2:03 PM] end1: CD0: : C0[0..1] {memberEnd#2, aggregation=none, isNavigable=F, direction=p>>q}
    [10/13/13 2:03 PM] end2: C0::q : D0[1..1] {memberEnd#1, aggregation=composite, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association CD1 : C1 – D1
    [10/13/13 2:03 PM] end1: CD1: : C1[0..1] {memberEnd#2, aggregation=none, isNavigable=F, direction=p>>q}
    [10/13/13 2:03 PM] end2: CD1::q : D1[1..1] {memberEnd#1, aggregation=composite, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association EF'0 : E'0 – F'0
    [10/13/13 2:03 PM] end1: EF'0: : E'0[1..1] {memberEnd#1, aggregation=composite, isNavigable=F, direction=q>>p}
    [10/13/13 2:03 PM] end2: E'0::q : F'0[0..1] {memberEnd#2, aggregation=none, isNavigable=Y, direction=p>>q}

    [10/13/13 2:03 PM] Association EF'1 : E'1 – F'1
    [10/13/13 2:03 PM] end1: EF'1: : E'1[1..1]

    {memberEnd#1, aggregation=composite, isNavigable=F, direction=q>>p}

    [10/13/13 2:03 PM] end2: EF'1::q : F'1[0..1]

    {memberEnd#2, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association EF0 : E0 – F0
    [10/13/13 2:03 PM] end1: EF0: : E0[1..1] {memberEnd#2, aggregation=composite, isNavigable=F, direction=q>>p}
    [10/13/13 2:03 PM] end2: E0::q : F0[0..1] {memberEnd#1, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association EF1 : E1 – F1
    [10/13/13 2:03 PM] end1: EF1: : E1[1..1] {memberEnd#2, aggregation=composite, isNavigable=F, direction=q>>p}
    [10/13/13 2:03 PM] end2: EF1::q : F1[0..1] {memberEnd#1, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association GH'0 : G'0 – H'0
    [10/13/13 2:03 PM] end1: GH'0: : G'0[1..1] {memberEnd#1, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] end2: G'0::q : H'0[1..1] {memberEnd#2, aggregation=none, isNavigable=Y, direction=p>>q}

    [10/13/13 2:03 PM] Association GH'1 : G'1 – H'1
    [10/13/13 2:03 PM] end1: GH'1: : G'1[0..1]

    {memberEnd#1, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] end2: GH'1::q : H'1[0..*] {memberEnd#2, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association GH'2 : G'2 – H'2
    [10/13/13 2:03 PM] end1: H'2: : G'2[0..1] {memberEnd#1, aggregation=none, isNavigable=Y, direction=p>>q}

    [10/13/13 2:03 PM] end2: G'2::q : H'2[0..*]

    {memberEnd#2, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association GH0 : G0 – H0
    [10/13/13 2:03 PM] end1: GH0: : G0[1..1] {memberEnd#2, aggregation=none, isNavigable=Y, direction=p>>q}

    [10/13/13 2:03 PM] end2: G0::q : H0[1..1]

    {memberEnd#1, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association GH1 : G1 – H1
    [10/13/13 2:03 PM] end1: GH1: : G1[0..1] {memberEnd#2, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] end2: GH1::q : H1[0..*] {memberEnd#1, aggregation=none, isNavigable=Y, direction=p>>q}

    [10/13/13 2:03 PM] Association GH2 : G2 – H2
    [10/13/13 2:03 PM] end1: H2: : G2[0..1]

    {memberEnd#2, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] end2: G2::q : H2[0..*] {memberEnd#1, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association IJ'0 : I'0 – J'0
    [10/13/13 2:03 PM] end1: J'0: : I'0[0..1] {memberEnd#1, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] end2: I'0::q : J'0[1..1] {memberEnd#2, aggregation=composite, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association IJ'1 : I'1 – J'1
    [10/13/13 2:03 PM] end1: J'1: : I'1[0..1] {memberEnd#1, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] end2: I'1::q : J'1[0..*] {memberEnd#2, aggregation=none, isNavigable=Y, direction=p>>q}

    [10/13/13 2:03 PM] Association IJ0 : I0 – J0
    [10/13/13 2:03 PM] end1: J0: : I0[0..1]

    {memberEnd#2, aggregation=none, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] end2: I0::q : J0[1..1] {memberEnd#1, aggregation=composite, isNavigable=Y, direction=p>>q}
    [10/13/13 2:03 PM] Association IJ1 : I1 – J1
    [10/13/13 2:03 PM] end1: J1: : I1[0..1] {memberEnd#2, aggregation=none, isNavigable=Y, direction=p>>q}

    [10/13/13 2:03 PM] end2: I1::q : J1[0..*]

    {memberEnd#1, aggregation=none, isNavigable=Y, direction=p>>q}

    To facilitate reviewing this criteria, these associations are shown in the attached class diagram.

  • Reported: UML 2.5 — Sun, 13 Oct 2013 04:00 GMT
  • Updated: Fri, 6 Mar 2015 20:57 GMT