Recipe12.8.Generating Code from UML Models via XMI


Recipe 12.8. Generating Code from UML Models via XMI

Problem

You want to generate code from Unified Modeling Language (UML) specifications but are unhappy with the results produced by your UML tools' native-code generator.

The State Design Pattern

A state machine is a useful design tool that directly models a software system whose behavior changes over time as various events unfold. A state machine is often depicted as a graph where vertices (nodes) designate specific states and where edges (links) specify transitions between states in response to a stimulus or event.

The state design pattern approaches the construction of a state machine by representing the concrete states by distinct classes that derive from an abstract base State class. A separate StateMachine class represents the state machine as a whole and holds a pointer to a concrete State that represents the current state. Methods in the StateMachine defer to the current state to implement state-specific behavior. Invocation of these methods may cause the concrete state class to transition its owning StateMachine to a new state.


Solution

XMI is a standard XML-based representation of UML that many UML modeling tools (such as Rational Rose) support. Although the stated purpose of XMI is to allow interchange of modeling information between different tools, it also can be the basis for code generation.

Most UML tools support code generation but rarely generate more than a skeleton of the object model. For instance, all UML-based generators I am aware of only consider information in class diagrams.

Here you will generate code for the State Design Pattern[3] that combines information from both a class diagram (Figure 12-1) and a state chart (Figure 12-2).

[3] Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley, 1995).

Figure 12-2. Class diagram representing states


Figure 12-3. State chart for answering machine


The code generator assumes that the designer followed these conventions:

  1. The state context class has the stereotype StateMachine.

  2. The abstract base class of the actual state classes has the stereotype State.

  3. All operations in the state context that forward their implementation to the state classes have the stereotype delegate (indicating that they delegate their responsibility to the state).

  4. All state context operations used by the states to implement state-machine behavior have the stereotype action.

  5. The only exception to (4) is the single operation that should be invoked when the state does not know what to do. This operation has stereotype default and is generally used for error handling.

  6. The UML state chart states use the same names as the concrete classes derived from the State interface (2).

  7. The actions and guards associated with transitions refer to the context and use the exact operation names. An action is the code executed upon a transition and a guard is a condition that must be true for the transition to be chosen.

The next example shows a state machine that implements the basic functionality of an answering machine when you retrieve your messages.

The XMI generated from this simple design cannot be shown in its entirety because it is huge. XMI uses horrendously long and arduous naming conventions. This chapter shows a fragment containing the AnsweringMachineState class to give the flavor of it. However, even this has been elided:

<Foundation.Core.Class xmi.>   <Foundation.Core.ModelElement.name>AnsweringMachineState   </Foundation.Core.ModelElement.name>   <Foundation.Core.ModelElement.stereotype>     <Foundation.Extension_Mechanisms.Stereotype xmi.idref="G.22"/>     <!-- state -->   </Foundation.Core.ModelElement.stereotype>   <Foundation.Core.GeneralizableElement.specialization>     <Foundation.Core.Generalization xmi.idref="G.91"/>     <!-- {Connected-&gt;AnsweringMachineState}{3D6782A402EE} -->     <Foundation.Core.Generalization xmi.idref="G.92"/>     <!-- {HandlingMsg-&gt;AnsweringMachineState}{3D6782EF0119} -->     <Foundation.Core.Generalization xmi.idref="G.93"/>     <!-- {Start-&gt;AnsweringMachineState}{3D67B2FD004E} -->     <Foundation.Core.Generalization xmi.idref="G.94"/>     <!-- {GoodBye-&gt;AnsweringMachineState}{3D67B31B02AF} -->   </Foundation.Core.GeneralizableElement.specialization>   <Foundation.Core.Classifier.associationEnd>     <Foundation.Core.AssociationEnd xmi.idref="G.25"/>   </Foundation.Core.Classifier.associationEnd>   <Foundation.Core.Classifier.feature>     <Foundation.Core.Operation xmi.>       <Foundation.Core.ModelElement.name>doCmd</Foundation.Core.ModelElement.name>       <Foundation.Core.ModelElement.visibility xmi.value="public"/>       <Foundation.Core.Feature.ownerScope xmi.value="instance"/>       <Foundation.Core.BehavioralFeature.isQuery xmi.value="false"/>       <Foundation.Core.Operation.specification/>       <Foundation.Core.Operation.isPolymorphic xmi.value="false"/>       <Foundation.Core.Operation.concurrency xmi.value="sequential"/>       <Foundation.Core.ModelElement.stereotype>         <Foundation.Extension_Mechanisms.Stereotype xmi.idref="G.23"/>         <!-- pure -->       </Foundation.Core.ModelElement.stereotype>       <Foundation.Core.BehavioralFeature.parameter>         <Foundation.Core.Parameter xmi.>            <Foundation.Core.ModelElement.name>cntx</Foundation.Core.ModelElement.name>           <Foundation.Core.ModelElement.visibility xmi.value="private"/>           <Foundation.Core.Parameter.defaultValue>             <Foundation.Data_Types.Expression>               <Foundation.Data_Types.Expression.language/>               <Foundation.Data_Types.Expression.body/>             </Foundation.Data_Types.Expression>           </Foundation.Core.Parameter.defaultValue>           <Foundation.Core.Parameter.kind xmi.value="inout"/>           <Foundation.Core.Parameter.type>             <Foundation.Core.Class xmi.idref="S.10001"/>             <!-- AnsweringMachine -->           </Foundation.Core.Parameter.type>         </Foundation.Core.Parameter>         <Foundation.Core.Parameter xmi.>            <Foundation.Core.ModelElement.name>cmd</Foundation.Core.ModelElement.name>           <Foundation.Core.ModelElement.visibility xmi.value="private"/>           <Foundation.Core.Parameter.defaultValue>             <Foundation.Data_Types.Expression>               <Foundation.Data_Types.Expression.language/>               <Foundation.Data_Types.Expression.body/>             </Foundation.Data_Types.Expression>           </Foundation.Core.Parameter.defaultValue>           <Foundation.Core.Parameter.kind xmi.value="inout"/>           <Foundation.Core.Parameter.type>             <Foundation.Core.DataType xmi.idref="G.65"/>             <!-- Command -->           </Foundation.Core.Parameter.type>         </Foundation.Core.Parameter>         <Foundation.Core.Parameter xmi.>           <Foundation.Core.ModelElement.name>doCmd.Return           </Foundation.Core.ModelElement.name>           <Foundation.Core.ModelElement.visibility xmi.value="private"/>           <Foundation.Core.Parameter.defaultValue>             <Foundation.Data_Types.Expression>               <Foundation.Data_Types.Expression.language/>               <Foundation.Data_Types.Expression.body/>             </Foundation.Data_Types.Expression>           </Foundation.Core.Parameter.defaultValue>           <Foundation.Core.Parameter.kind xmi.value="return"/>           <Foundation.Core.Parameter.type>             <Foundation.Core.DataType xmi.idref="G.67"/>             <!-- void -->           </Foundation.Core.Parameter.type>         </Foundation.Core.Parameter>       </Foundation.Core.BehavioralFeature.parameter>     </Foundation.Core.Operation>   </Foundation.Core.Classifier.feature> </Foundation.Core.Class>

The following large XSLT example converts the XMI into a C++ implementation of this state machine according to the State Design Pattern. Out of sheer desperation, I use XML entities to abbreviate long XMI names. I don't usually recommend this practice, but in this case, it was the only way to make the XSLT fit reasonably on the page. It also reduces the inherent noise level of the XMI DTD. You will also notice that keys are used extensively because XMI makes heavy use of cross references in the form of xmi.id and xmi.idref attributes:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xslt [   <!--=======================================================-->   <!-- XMI's high-level organization constructs              -->   <!--=======================================================-->   <!ENTITY BE "Behavioral_Elements">   <!ENTITY SM "&BE;.State_Machines">   <!ENTITY SMC "&SM;.StateMachine.context">   <!ENTITY SMT "&SM;.StateMachine.top">   <!ENTITY MM "Model_Management.Model">   <!ENTITY FC "Foundation.Core">   <!ENTITY FX "Foundation.Extension_Mechanisms">   <!--=======================================================-->   <!-- Abbreviations for the basic elements of a XMI         -->   <!-- file that are of most interest to this stylesheet.  -->   <!--=======================================================-->   <!--The model as a whole -->   <!ENTITY MODEL "XMI/XMI.content/&MM;">   <!--Some generic kind of UML element -->   <!ENTITY ELEM "&FC;.Namespace.ownedElement">   <!--Elements of state machines -->    <!ENTITY STATEMACH "&MODEL;/&ELEM;/&SM;.StateMachine">   <!ENTITY STATE "&SM;.CompositeState">   <!ENTITY SUBSTATE "&STATE;.substate">   <!ENTITY PSEUDOSTATE "&SM;.PseudoState">   <!ENTITY PSEUDOSTATE2 "&SMT;/&STATE;/&SUBSTATE;/&PSEUDOSTATE;">   <!ENTITY ACTION "&BE;.Common_Behavior.ActionSequence/&BE;.Common_Behavior.ActionSequence.action">   <!ENTITY GUARD "&SM;.Transition.guard/&SM;.Guard/&SM;.Guard.expression">   <!--The association as a whole -->   <!ENTITY ASSOC "&FC;.Association">   <!--The connection part of the association-->   <!ENTITY CONN "&ASSOC;.connection">   <!--The ends of an association. -->   <!ENTITY END "&ASSOC;End">   <!ENTITY CONNEND "&CONN;/&END;">   <!ENTITY ENDType "&END;.type">   <!-- A UML class -->   <!ENTITY CLASS "&FC;.Class">   <!--The name of some UML entity -->   <!ENTITY NAME "&FC;.ModelElement.name">   <!--A operation -->   <!ENTITY OP "&FC;.Operation">   <!-- A parameter -->   <!ENTITY PARAM "&FC;.Parameter">   <!ENTITY PARAM2 "&FC;.BehavioralFeature.parameter/&PARAM;">   <!-- The data type of a parameter -->   <!ENTITY PARAMTYPE "&PARAM;.type/&FC;.DataType">   <!--A UML sterotype -->   <!ENTITY STEREOTYPE "&FC;.ModelElement.stereotype/&FX;.Stereotype">   <!-- A Supertype relation (inheritance) -->   <!ENTITY SUPERTYPE "&FC;.Generalization.supertype">   <!ENTITY GENERALIZATION "&FC;.GeneralizableElement.generalization/&FC;.Generalization">   <!--=======================================================-->   <!--Formatting                                             -->   <!--=======================================================-->      <!--Used to control code indenting -->   <!ENTITY INDENT "    ">   <!ENTITY INDENT2 "&INDENT;&INDENT;"> ]> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output method="text"/>      <!--Index classes by their id -->   <xsl:key name="classKey" match="&CLASS;" use="@xmi.id"/>   <xsl:key name="classKey" match="&CLASS;" use="&NAME;"/>      <!-- Index Stereoptypes by both name and xmi.id -->   <xsl:key name="stereotypeKey" match="&FX;.Stereotype" use="@xmi.id"/>   <xsl:key name="stereotypeKey" match="&FX;.Stereotype" use="&NAME;"/>      <!--Index data types by their id -->   <xsl:key name="dataTypeKey" match="&FC;.DataType" use="@xmi.id"/>      <!--Index associations by their end classes -->   <xsl:key name="associationKey" match="&ASSOC;" use="&CONNEND;/&ENDType;/&FC;.Interface/@xmi.idref"/>   <xsl:key name="associationKey" match="&ASSOC;" use="&CONNEND;/&ENDType;/&CLASS;/@xmi.idref"/>      <!--Index generalizations by their id -->   <xsl:key name="generalizationKey" match="&FC;.Generalization" use="@xmi.id"/>   <!--Index states and pseudo states together by their id -->   <xsl:key name="stateKey" match="&SM;.SimpleState" use="@xmi.id"/>   <xsl:key name="stateKey" match="&PSEUDOSTATE;" use="@xmi.id"/>      <!--Index state transitions by their id -->   <xsl:key name="transKey" match="&SM;.Transition" use="@xmi.id"/>      <!--Index transitions events by their id -->   <xsl:key name="eventsKey" match="&SM;.SignalEvent" use="@xmi.id"/>      <!-- The xmi ids of stereotypes used to encode State Pattern in UML -->   <xsl:variable name="STATE_MACH"                  select="key('stereotypeKey','StateMachine')/@xmi.id"/>   <xsl:variable name="STATE"                 select="key('stereotypeKey','state')/@xmi.id"/>   <xsl:variable name="DELEGATE"                  select="key('stereotypeKey','delegate')/@xmi.id"/>   <xsl:variable name="ACTION"                  select="key('stereotypeKey','action')/@xmi.id"/>   <xsl:variable name="DEFAULT"                 select="key('stereotypeKey','default')/@xmi.id"/>   <xsl:variable name="PURE"                  select="key('stereotypeKey','pure')/@xmi.id"/>      <!-- We use modes to generate the source in 5 steps -->   <xsl:template match="/">     <!-- Declare state interface -->       <xsl:apply-templates mode="stateInterfaceDecl"           select="&MODEL;/&ELEM;/&CLASS;[&STEREOTYPE;/@xmi.idref =                                                         $STATE_MACH]"/>     <!-- Declare concrete states -->       <xsl:apply-templates mode="concreteStatesDecl"          select="&MODEL;/&ELEM;/&CLASS;[not(&STEREOTYPE;)]"/>     <!-- Declare state context class -->       <xsl:apply-templates mode="stateDecl"            select="&MODEL;/&ELEM;/&CLASS;[&STEREOTYPE;/@xmi.idref =                                                          $STATE_MACH]"/>               <!--Implement states -->       <xsl:apply-templates mode="concreteStatesImpl"          select="&MODEL;/&ELEM;"/>     <!--Implement context -->       <xsl:apply-templates mode="stateContextImpl"          select="&MODEL;/&ELEM;/&CLASS;[&STEREOTYPE;/@xmi.idref =                                     $STATE_MACH]"/>            </xsl:template>      <!-- STATE CONTEXT DECLARATION -->   <xsl:template match="&CLASS;" mode="stateDecl">        <!-- Find the class associated with this state context that -->     <!-- is a state implementation. This is the type of the state -->     <!-- machines corrent state member variable -->     <xsl:variable name="stateImplClass">       <xsl:variable name="stateClassId" select="@xmi.id"/>       <xsl:for-each select="key('associationKey',$stateClassId)">         <xsl:variable name="assocClassId"                        select="&CONNEND;[&ENDType;/*/@xmi.idref !=                                   $stateClassId]/&ENDType;/*/@xmi.idref"/>         <xsl:if test="key('classKey',$assocClassId)/&STEREOTYPE;/@xmi.idref                                                                   = $STATE">           <xsl:value-of select="key('classKey',$assocClassId)/&NAME;"/>         </xsl:if>       </xsl:for-each>     </xsl:variable>     <xsl:variable name="className" select="&NAME;"/>          <xsl:text>&#xa;class </xsl:text>     <xsl:value-of select="$className"/>     <xsl:text>&#xa;{&#xa;public:&#xa;&#xa;</xsl:text>          <!--Ctor decl -->     <xsl:text>&INDENT;</xsl:text>     <xsl:value-of select="$className"/>::<xsl:value-of select="$className"/>     <xsl:text>( );&#xa;&#xa;</xsl:text>         <!-- Delegates are operations that defer to the current state -->     <xsl:apply-templates                         select="*/&OP;[&STEREOTYPE;/@xmi.idref = $DELEGATE]"                         mode="declare"/>     <!-- void changeState(AbstractState& newState) -->     <xsl:text>&INDENT;void changeState(</xsl:text>     <xsl:value-of select="$stateImplClass"/>     <xsl:text>&amp; newSate) ;&#xa;</xsl:text>          <xsl:text>&#xa;&#xa;</xsl:text>     <!-- Non-Delegates are other operations that states -->     <!-- invoke on the context -->     <xsl:apply-templates                select="*/&OP;[&STEREOTYPE;/@xmi.idref != $DELEGATE]"               mode="declare"/>     <xsl:text>&#xa;private:&#xa;&#xa;</xsl:text>     <xsl:text>&INDENT;</xsl:text>     <xsl:value-of select="$stateImplClass"/>     <xsl:text>* m_State ;</xsl:text>     <xsl:text>&#xa;&#xa;} ;&#xa;&#xa;</xsl:text>        </xsl:template>      <!-- CONCRETE STATES DECLARATION -->      <xsl:template match="&CLASS;" mode="concreteStatesDecl">     <xsl:text>class </xsl:text>   <xsl:value-of select="&NAME;"/>     <xsl:call-template name="baseClass"/>     <xsl:text>&#xa;{&#xa;public:&#xa;&#xa;</xsl:text>     <!-- Concrete States are Singletons so we generate an -->     <!-- instance method -->     <xsl:text>&INDENT;static </xsl:text>     <xsl:value-of select="&NAME;"/>     <xsl:text>&amp; instance( ) ;</xsl:text>     <xsl:text>&#xa;&#xa;private:&#xa;&#xa;</xsl:text>     <!-- We protect constructors of Singletons-->     <xsl:text>&INDENT;</xsl:text>     <xsl:value-of select="&NAME;"/>     <xsl:text>( ) {  } &#xa;</xsl:text>     <xsl:text>&INDENT;</xsl:text>     <xsl:value-of select="&NAME;"/>(const <xsl:value-of select="&NAME;"/>     <xsl:text>&amp;) {  } &#xa;</xsl:text>     <xsl:text>&INDENT;void operator =(const </xsl:text>     <xsl:value-of select="&NAME;"/>     <xsl:text>&amp;) {  } &#xa;</xsl:text>     <xsl:apply-templates select="*/&OP;" mode="declare"/>     <xsl:text> } ; </xsl:text>   </xsl:template>      <!-- Templates used to declare classes with all public members -->      <xsl:template match="&CLASS;" mode="declare">     <xsl:text>class&#x20;</xsl:text>     <xsl:value-of select="&NAME;"/>     <xsl:text>&#xa;{&#xa;public:&#xa;&#xa;</xsl:text>     <xsl:apply-templates select="*/&OP;" mode="declare"/>     <xsl:text>&#xa;} ;&#xa;</xsl:text>   </xsl:template>      <xsl:template match="&OP;" mode="declare">     <xsl:variable name="returnTypeId"                             select="&PARAM2;[&PARAM;.kind/@xmi.value =                                                                   'return']/&PARAMTYPE;/@xmi.idref"/>       <xsl:text>&INDENT;</xsl:text>     <xsl:if test="&STEREOTYPE;/@xmi.idref = $PURE">       <xsl:text>virtual </xsl:text>     </xsl:if>     <xsl:value-of select="key('dataTypeKey',$returnTypeId)/&NAME;"/>       <xsl:text>&#x20;</xsl:text>     <xsl:value-of select="&NAME;"/>     <xsl:text>(</xsl:text>     <xsl:call-template name="parameters"/>     <xsl:text>)</xsl:text>     <xsl:if test="&STEREOTYPE;/@xmi.idref = $PURE">       <xsl:text> = 0 </xsl:text>     </xsl:if>     <xsl:text>;&#xa;</xsl:text>   </xsl:template>   <!--Eat extra text nodes  -->    <xsl:template match="text( )" mode="declare"/>      <!-- STATE INTERFACE DECLARATION -->   <xsl:template match="&CLASS;" mode="stateInterfaceDecl">     <xsl:text>//Forward declarations&#xa;</xsl:text>     <xsl:text>class </xsl:text>     <xsl:value-of select="&NAME;"/>     <xsl:text>;&#xa;&#xa;</xsl:text>          <xsl:variable name="stateClassId" select="@xmi.id"/>     <!-- Find the class associated with the state context that -->     <!-- is a state. -->     <xsl:for-each select="key('associationKey',$stateClassId)">       <xsl:variable name="assocClassId"                     select="&CONNEND;[&ENDType;/*/@xmi.idref !=                                     $stateClassId]/&ENDType;/*/@xmi.idref"/>       <xsl:if test="key('classKey',$assocClassId)/&STEREOTYPE;/@xmi.idref =                                                                     $STATE">         <xsl:apply-templates select="key('classKey',$assocClassId)"                               mode="declare"/>       </xsl:if>     </xsl:for-each>     <xsl:text>&#xa;&#xa;</xsl:text>   </xsl:template>      <!--Eat extra text nodes  -->    <xsl:template match="text( )" mode="stateInterfaceDecl"/>      <!-- STATE CONTEXT IMPLEMENTATION -->   <xsl:template match="&CLASS;" mode="stateContextImpl">        <xsl:variable name="stateImplClass">       <xsl:variable name="stateClassId" select="@xmi.id"/>       <xsl:for-each select="key('associationKey',$stateClassId)">         <xsl:variable name="assocClassId"              select="&CONNEND;[&ENDType;/*/@xmi.idref !=                                  $stateClassId]/&ENDType;/*/@xmi.idref"/>         <xsl:if test="key('classKey',$assocClassId)/&STEREOTYPE;/@xmi.idref                                                                   = $STATE">           <xsl:value-of select="key('classKey',$assocClassId)/&NAME;"/>         </xsl:if>       </xsl:for-each>     </xsl:variable>     <xsl:variable name="className" select="&NAME;"/>     <xsl:text>//Constructor&#xa;</xsl:text>     <xsl:value-of select="$className"/>::<xsl:value-of                                           select="$className"/>     <xsl:text>( )&#xa;</xsl:text>     <xsl:text>{&#xa;</xsl:text>     <xsl:text>&INDENT;//Initialize state machine in start state &#xa;</xsl:text>     <xsl:variable name="startStateName">       <xsl:call-template name="getStartState">         <xsl:with-param name="classId" select="@xmi.id"/>       </xsl:call-template>     </xsl:variable>     <xsl:text>&INDENT;m_State = &amp;</xsl:text>     <xsl:value-of select="$startStateName"/>     <xsl:text>::instance( ) ;&#xa;</xsl:text>     <xsl:text>}&#xa;&#xa;</xsl:text>     <!-- void changeState(AbstractState& newState) -->     <xsl:text>void </xsl:text>     <xsl:value-of select="$className"/>     <xsl:text>::changeState(</xsl:text>     <xsl:value-of select="$stateImplClass"/>     <xsl:text>&amp; newState)&#xa;</xsl:text>     <xsl:text>{&#xa;</xsl:text>     <xsl:text>&INDENT;m_State = &amp;newState;</xsl:text>     <xsl:text>&#xa;}&#xa;&#xa;</xsl:text>          <xsl:for-each select="*/&OP;[&STEREOTYPE;/@xmi.idref = $DELEGATE]">       <xsl:variable name="returnTypeId"                               select="&PARAM2;[&PARAM;.kind/@xmi.value =                                         'return']/&PARAMTYPE;/@xmi.idref"/>       <xsl:value-of select="key('dataTypeKey',$returnTypeId)/&NAME;"/>       <xsl:text>&#x20;</xsl:text>       <xsl:value-of select="$className"/>::<xsl:value-of select="&NAME;"/>       <xsl:text>(</xsl:text>       <xsl:call-template name="parameters"/>       <xsl:text>)&#xa;</xsl:text>       <xsl:text>{&#xa;</xsl:text>       <xsl:text>&INDENT;m_State-></xsl:text>       <xsl:value-of select="&NAME;"/>       <xsl:text>(*this, </xsl:text>       <xsl:for-each select="&PARAM2;[&PARAM;.kind/@xmi.value != 'return']">         <xsl:value-of select="&NAME;"/>         <xsl:if test="position( ) != last( )">           <xsl:text>, </xsl:text>         </xsl:if>       </xsl:for-each>       <xsl:text>);&#xa;</xsl:text>       <xsl:text>}&#xa;&#xa;</xsl:text>     </xsl:for-each>          <xsl:for-each select="*/&OP;[&STEREOTYPE;/@xmi.idref != $DELEGATE]">       <xsl:variable name="returnTypeId"                     select="&PARAM2;[&PARAM;.kind/@xmi.value =                                      'return']/&PARAMTYPE;/@xmi.idref"/>       <xsl:value-of select="key('dataTypeKey',$returnTypeId)/&NAME;"/>       <xsl:text>&#x20;</xsl:text>       <xsl:value-of select="$className"/>::<xsl:value-of select="&NAME;"/>       <xsl:text>(</xsl:text>       <xsl:call-template name="parameters"/>       <xsl:text>)&#xa;</xsl:text>       <xsl:text>{&#xa;</xsl:text>       <xsl:text>&INDENT;//!TODO: Implement behavior of this action&#xa;</xsl:text>       <xsl:text>}&#xa;&#xa;</xsl:text>     </xsl:for-each>   </xsl:template>   <xsl:template match="text( )" mode="stateContextImpl"/>   <!-- CONCRETE STATES IMPLEMENTATION -->   <xsl:template match="&CLASS;" mode="concreteStatesImpl">     <xsl:variable name="classId" select="@xmi.id"/>     <xsl:variable name="stateMachine"                   select="/&STATEMACH;[&SMC;/&CLASS;/@xmi.idref =                                                                 $classId]"/>     <!-- For each state in the machine, generate its implementation -->     <xsl:for-each select="$stateMachine//&PSEUDOSTATE; |                                         $stateMachine//&SM;.SimpleState">       <xsl:call-template name="generateState">         <xsl:with-param name="stateClass" select="key('classKey',&NAME;)"/>       </xsl:call-template>     </xsl:for-each>   </xsl:template>      <!--Eat extra text nodes  -->    <xsl:template match="text( )" mode="concreteStatesImpl"/>      <xsl:template name="generateState">     <!--This is a xmi class corresponding the the state -->     <xsl:param name="stateClass"/>     <!-- The current context is some state -->     <xsl:variable name="state" select="."/>     <xsl:variable name="className" select="&NAME;"/>     <xsl:if test="$className != $stateClass/&NAME;">       <xsl:message terminate="yes">State and class do not match!</xsl:message>     </xsl:if>     <xsl:for-each select="$stateClass/*/&OP;">       <xsl:variable name="returnTypeId"                      select="&PARAM2;[&PARAM;.kind/@xmi.value =                                      'return']/&PARAMTYPE;/@xmi.idref"/>       <xsl:value-of select="key('dataTypeKey',$returnTypeId)/&NAME;"/>       <xsl:text>&#x20;</xsl:text>       <xsl:value-of select="$className"/>::<xsl:value-of select="&NAME;"/>       <xsl:text>(</xsl:text>       <xsl:call-template name="parameters"/>       <xsl:text>)&#xa;</xsl:text>       <xsl:text>{&#xa;</xsl:text>       <xsl:for-each             select="$state/&SM;.StateVertex.outgoing/&SM;.Transition">         <xsl:call-template name="generateStateBody">           <xsl:with-param name="transition"             select="key('transKey',@xmi.idref)"/>         </xsl:call-template>       </xsl:for-each>       <xsl:text>&INDENT2;context.errorMsg( ) ;&#xa;</xsl:text>       <xsl:text>}&#xa;&#xa;</xsl:text>     </xsl:for-each>   </xsl:template>         <xsl:template name="generateStateBody">     <xsl:param name="transition"/>     <xsl:text>&INDENT;if (cmd == </xsl:text>     <xsl:variable name="eventId"                             select="$transition/&SM;.Transition.trigger/&SM;.SignalEvent/@xmi.idref"/>     <xsl:value-of select="key('eventsKey',$eventId)/&NAME;"/>     <xsl:if test="$transition/&SM;.Transition.guard">       <xsl:text> &amp;&amp; </xsl:text>       <xsl:value-of      select="$transition/&GUARD;/*/Foundation.Data_Types.Expression.body"/>       <xsl:text>( )</xsl:text>     </xsl:if>     <xsl:text>)&#xa;</xsl:text>     <xsl:text>&INDENT;{&#xa;</xsl:text>     <xsl:text>&INDENT2;</xsl:text>     <xsl:value-of           select="$transition/&SM;.Transition.effect/&ACTION;/*/&NAME;"/>     <xsl:text>( ) ;&#xa;</xsl:text>     <xsl:variable name="targetStateId"                   select="$transition/&SM;.Transition.target/*/@xmi.idref"/>     <xsl:if test="$targetStateId != $transition/@xmi.id"/>       <xsl:text>&INDENT2;cntx.changeState(</xsl:text>       <xsl:value-of select="key('stateKey',$targetStateId)/&NAME;"/>       <xsl:text>::instance( ));&#xa;</xsl:text>         <xsl:text>&INDENT;}&#xa;</xsl:text>     <xsl:text>&INDENT;else&#xa;</xsl:text>   </xsl:template>      <!-- Generate function parameters -->   <xsl:template name="parameters">     <xsl:for-each select="&PARAM2;[&PARAM;.kind/@xmi.value != 'return']">       <xsl:choose>         <xsl:when test="&PARAMTYPE;">           <xsl:value-of                  select="key('dataTypeKey',&PARAMTYPE;/@xmi.idref)/&NAME;"/>         </xsl:when>         <xsl:when test="&PARAM;.type/&CLASS;">           <xsl:value-of                   select="key('classKey',                              &PARAM;.type/&CLASS;/@xmi.idref)/&NAME;"/>           <xsl:text>&amp;</xsl:text>         </xsl:when>       </xsl:choose>       <xsl:text>&#x20;</xsl:text>       <xsl:value-of select="&NAME;"/>       <xsl:if test="position( ) != last( )">         <xsl:text>, </xsl:text>       </xsl:if>     </xsl:for-each>   </xsl:template>      <!-- Generate base classes -->   <xsl:template name="baseClass">     <xsl:if test="&GENERALIZATION;">       <xsl:text> : </xsl:text>       <xsl:for-each select="&GENERALIZATION;">         <xsl:variable name="genAssoc"                        select="key('generalizationKey',@xmi.idref)"/>         <xsl:text>public </xsl:text>         <xsl:value-of       select="key('classKey',                  $genAssoc/&SUPERTYPE;/&CLASS;/@xmi.idref)/&NAME;"/>         <xsl:if test="position( ) != last( )">           <xsl:text>, </xsl:text>         </xsl:if>       </xsl:for-each>     </xsl:if>   </xsl:template>   <xsl:template name="getStartState">     <xsl:param name="classId"/>     <xsl:variable name="stateMachine"                   select="/&STATEMACH;[&SMC;/&CLASS;/@xmi.idref =                                                            $classId]"/>     <xsl:value-of                    select="$stateMachine/&PSEUDOSTATE2;                                           [&PSEUDOSTATE;.kind/@xmi.value = 'initial']/&NAME;"/>   </xsl:template> </xsl:stylesheet>

Here is a portion of the resulting C++ code:

//Forward declarations class AnsweringMachine;     class AnsweringMachineState { public:         virtual void doCmd(AnsweringMachine& cntx, Command cmd) = 0 ;     } ;         class Connected : public AnsweringMachineState { public:         static Connected& instance( ) ;     private:         Connected( ) {  }      Connected(const Connected&) {  }      void operator =(const Connected&) {  }      void doCmd(AnsweringMachine& cntx, Command cmd);     } ;     class HandlingMsg : public AnsweringMachineState { public:         static HandlingMsg& instance( ) ;     private:         HandlingMsg( ) {  }      HandlingMsg(const HandlingMsg&) {  }      void operator =(const HandlingMsg&) {  }      void doCmd(AnsweringMachine& cntx, Command cmd);     } ;     //!OTHER STATES WERE ELIDED TO SAVE SPACE...      class AnsweringMachine { public:         AnsweringMachine::AnsweringMachine( );         void doCmd(Command cmd);     void changeState(AnsweringMachineState& newSate) ;             bool moreMsgs( );     void playRemaining( );     void playCurrentMsg( );     void advanceAndPlayMsg( );     void deleteMsg( );     void sayGoodBye( );     void errorMsg( );     private:         AnsweringMachineState* m_State ;     } ;     void Connected::doCmd(AnsweringMachine& cntx, Command cmd) {     if (cmd =  = NEXT_MSG && cntx.moreMsgs( ))     {         cntx.playCurrentMsg( ) ;         cntx.changeState(HandlingMsg::instance( ));     }     else     if (cmd =  = NEXT_MSG && !cntx.moreMsgs( ))     {         cntx.playRemaining( ) ;         cntx.changeState(Connected::instance( ));     }     else     if (cmd =  = END)     {         cntx.sayGoodBye( ) ;         cntx.changeState(GoodBye::instance( ));     }     else         context.errorMsg( ) ; }     //!OTHER STATES WERE ELIDED TO SAVE SPACE...      //Constructor AnsweringMachine::AnsweringMachine( ) {     //Initialize state machine in start state      m_State = &Start::instance( ) ; }     void AnsweringMachine::changeState(AnsweringMachineState& newState) {     m_State = &newState; }     void AnsweringMachine::doCmd(Command cmd) {     m_State->doCmd(*this, cmd); }     bool AnsweringMachine::moreMsgs( ) {     //!TODO: Implement behavior of this action }     void AnsweringMachine::playRemaining( ) {     //!TODO: Implement behavior of this action }     void AnsweringMachine::playCurrentMsg( ) {     //!TODO: Implement behavior of this action }     //!OTHER ACTIONS WERE ELIDED TO SAVE SPACE...

Discussion

You might want to write your own UML code generation for three reasons. First, you might not like the style or code substance generated by the modeling tool's native code generator. Second, the tool might not generate code in the language you require. For instance, I do not know of a UML tool that generates Python.[4] Third, you want to automate the generation of a higher-level design pattern or software service that you use frequently in development. This last case is the motivation behind the solution section's example.

[4] Not that I am a big fan of this curly-bracket-less language, but it has a large contingent of enthusiastic developers.

Building a general-purpose, multi-language code-generation facility on top of XMI and XSLT is an interesting project, but well beyond the scope of a single example. The example's solution is not intended as a production-ready generator, but it is a start. In particular, four important improvements could be made.

First, the generating XSLT should be broken down into small components that generate portions of the target programming language's various constructs. This is similar to what we did in Chapter 9 with SVG generation.

Second, the generating XSLT should understand more UML information, such as access control, rather than hardcoding these decisions.

Third, the generator should use XSLT 1.0 extensions or XSLT 2.0 to generate code into multiple header and source files, rather than one monolithic source.

Fourth, but least important, several code-layout styles could be supported. For example, the C++ code is in the style of Allman, but many (misguided?) C++ programmers prefer K&R (http://www.tuxedo.org/~esr/jargon/html/entry/indent-style.html). Then again, you might just run the output through a separate code beautifier and dispense with all formatting decisions entirely.




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net