Главная » XSLT » Генерация кода из UML-моделей, описанных на языке XMI

0

Задача

Требуется сгенерировать код по спецификациям на языке Unified Modeling Language (UML), но результат, который выдает генератор, встроенный в инст­рументальную программу для работы с UML, вам по каким-то причинам не нра­вится.

ПАТТЕРН ПРОЕКТИРОВАНИЯ «СОСТОЯНИЕ»

Конечный автомат – полезный инструмент проектирования, позволяющий непосредственно моделировать программную систему, поведение который изменяется во времени по мере наступления различных событий. Часто конеч­ные автоматы представляют в виде графов, вершины которых обозначают со­стояния, а ребра – переходы между состояниями в ответ на события.

Паттерн проектирования Состояние (State) описывает подход к построению конечного автомата, в котором конкретные состояния представляются классами, производными от абстрактного класса State. Отдельный класс stateMachine представляет конечный автомат в целом и содержит указатель на объект State, соответствующий текущему состоянию. Методы класса StateMachine делегируют текущему состоянию реализацию поведения в конкретном состоянии. При вызове любого из этих методов класс конкретного состояния может пере­вести владеющий им объект StateMachine в новое состояние.

Решение

XMI – это стандартизованное представление UML-моделей в формате XML, которое поддерживают многие инструменты моделирования (например, Rational Rose). Хотя официально цель XMI – обеспечить обмен информацией о модели между различными инструментами, этот язык можно использовать также и для генерации кода.

Большинство инструментов для работы с UML поддерживают генерацию кода, но редко идут дальше генерации скелета объектной модели. Так, все UML- генераторы, с которыми я знаком, принимают во внимание только информацию из диаграмм классов.

В примере ниже мы сгенерируем код, соответствующий паттерну «Состояние»1, в котором будет использоваться информация из диаграммы классов (см. рис. 12.2) и диаграммы состояний (рис. 12.3).

Генератор кода предполагает, что проектировщик следовал таким согла­шениям:

Рис. 12.2. Диаграмма классов, представляющих состояния

 

Рис. 12.3. Диаграмма состояний автомата, описывающего ответы на сообщения

1.              Класс контекста состояния снабжен стереотипом StateMachine.

2.              Абстрактный класс, которому наследуют классы конкретных состояний, снабжен стереотипом State.

3.              Все операции контекста состояния, которые делегируют свою реализацию классам состояний, снабжены стереотипом delegate.

4.              Все операции контекста состояния, используемые состояниями для реа­лизации поведения конечного автомата, снабжены стереотипом action.

5.              Единственное исключение из правила 4 составляет операция, которая должна вызываться, когда состояние не знает, что делать. Эта операция снабжена стереотипом default и обычно используется для обработки ошибок.

6.              Имена состояний на UML-диаграмме состояний совпадают с именами конкретных классов, производных от интерфейса State (2).

7.              Действия (actions) и стражи (guards), ассоциированные с переходами со­стояний, ссылаются на контекст и пользуются точными именами опера­ций. Действие – это код, исполняемый в момент перехода, а страж – усло­вие, которое должно удовлетворяться, чтобы данный переход был выбран.

В следующем примере показан конечный автомат, который реализует базовую функциональность, требуемую от процесса, обрабатывающего поступающие сообщения.

XMI-код, сгенерированный для этого простого примера, невозможно привести полностью, так как он огромен. В языке XMI применяются до нелепости длинные и трудновоспринимаемые имена. Мы приведем лишь небольшой фрагмент, опи­сывающий класс AnsweringMachineState, чтобы вы могли составить пред­ставление о схеме. Но даже и его пришлось сократить.

<Foundation.Core.Class xmi.id="S.10011">

<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}{3D6782A4 02EE} –> <Foundation.Core.Generalization xmi.idref="G.92"/> <!— {HandlingMsg-&gt;AnsweringMachineState}{3D6782EF0119} —> <Foundation.Core.Generalization xmi.idref="G.9 3"/> <!— {Start-&gt;AnsweringMachineState}{3D67B2FD00 4E} —> <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.id="S.10012"> <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.2 3"/> <!– pure –> </Foundation.Core.ModelElement.stereotype> <Foundation.Core.BehavioralFeature.parameter> <Foundation.Core.Parameter xmi.id="G.7 6">

<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.id="G.77">

<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.id="G.7 8">

<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>

Показанная ниже таблица стилей (довольно длинная) преобразует этот XMI-код в реализацию конечного автомата на C++, устроенную в соответствии с паттерном «Состояние». Только от полного отчаяния с прибегнул к XML-компонентам, что­бы сократить длинные XMI-имена. Вообще говоря, я не рекомендую такую прак­тику, но в данном случае это был единственный способ уместить XSLT-код на пе­чатной странице. Заодно устраняется «шум», свойственный схеме XMI DTD. Обратите внимание на повсеместное применение ключей, объясняющееся тем, что в XMI активно используются перекрестные ссылки в форме атрибутов xmi.id и xmi.idref.

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE xslt [

<!–=================================================– >

<!– Конструкции XMI, применяемые на верхнем уровне –> <!–=================================================– >

<!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">

<!–=================================================– >

<!– Сокращения для основных элементов XMI, представляющих –>

<!– наибольший интерес для данной таблицы стилей. –> <!–=================================================– >

<!–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">

<!– Класс UML –>

<!ENTITY CLASS "&FC;.Class">

<!–The name of some UML entity — >

<!ENTITY NAME "&FC;.ModelElement.name">

<!– Операция — >

<!ENTITY OP "&FC;.Operation">

<!– Параметр — >

<!ENTITY PARAM "&FC;.Parameter">

<!ENTITY PARAM2 "&FC;.BehavioralFeature.parameter/&PARAM;"> <!– Тип данных параметра –>

<!ENTITY PARAMTYPE "&PARAM;.type/&FC;.DataType"> <!– Стереотип UML — >

<!ENTITY STEREOTYPE "&FC;.ModelElement.stereotype/&FX;.Stereotype"> <!– Отношение наследования –>

<!ENTITY SUPERTYPE "&FC;.Generalization.supertype">

<!ENTITY GENERALIZATION "&FC;.GeneralizableElement. generalization/&FC;.

Generalization">

<!–=================================================– >

<!– Форматирование                               –>

<!–=================================================– >

<!– Служит для управления отступами –>

<!ENTITY INDENT "     ">

<!ENTITY INDENT2 "&INDENT;&INDENT;">

]>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/>

<!– Индексировать классы по идентификатору id –> <xsl:key name="classKey" match="&CLASS;" use="@xmi.id"/> <xsl:key name="classKey" match="&CLASS;" use="&NAME;"/>

<!— Индексировать стереотипы по имени и атрибуту xmi.id —>

<xsl:key name="stereotypeKey" match="&FX;.Stereotype" use="@xmi.id"/>

<xsl:key name="stereotypeKey" match="&FX;.Stereotype" use="&NAME;"/>

<!– Индексировать типы данных по id –>

<xsl:key name="dataTypeKey" match="&FC;.DataType" use="@xmi.id"/>

<!– Индексировать ассоциации по оконечным классам –>

<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"/>

<!– Индексировать обобщения по id –>

<xsl:key name="generalizationKey" match="&FC;.Generalization" use="@xmi.id"/>

<!– Индексировать состояния и псевдосостояния одним индексом по id <xsl:key name="stateKey" match="&SM;.SimpleState" use="@xmi.id"/> <xsl:key name="stateKey" match="&PSEUDOSTATE;" use="@xmi.id"/>

<!– Индексировать переходы состояний по id –>

<xsl:key name="transKey" match="&SM;.Transition" use="@xmi.id"/> <!– Индексировать события перехода по id –>

<xsl:key name="eventsKey" match="&SM;.SignalEvent" use="@xmi.id"/>

<!– XMI-идентификатор стереотипов, применяемых для кодирования –> <!– паттерна Состояние на языке 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"/>

<!— Для генерации исходного файла за пять шагов используются режимы –> <xsl:template match="/">

<!– Объявляем интерфейс состояния –> <xsl:apply-templates mode="stateInterfaceDecl"

select="&MODEL;/&ELEM;/&CLASS;[&STEREOTYPE;/@xmi.idref =

$STATE_MACH]"/>

<!– Объявляем конкретные состояния –> <xsl:apply-templates mode="concreteStatesDecl"

select="&MODEL;/&ELEM;/&CLASS;[not(&STEREOTYPE;)]"/>

<!– Объявляем класс контекста состояния –> <xsl:apply-templates mode="stateDecl"

select="&MODEL;/&ELEM;/&CLASS;[&STEREOTYPE;/@xmi.idref =

$STATE_MACH]"/>

<!– Реализуем состояния –>

<xsl:apply-templates mode="concreteStatesImpl" select="&MODEL;/&ELEM;"/>

<!– Реализуем контекст –>

<xsl:apply-templates mode="stateContextImpl"

select="&MODEL;/&ELEM;/&CLASS;[&STEREOTYPE;/@xmi.idref =

$STATE_MACH]"/>

</xsl:template>

<!– ОБЪЯВЛЕНИЕ КОНТЕКСТА СОСТОЯНИЯ —> <xsl:template match="&CLASS;" mode="stateDecl">

<!– Найти класс, ассоциированный с контекстом этого состояния, –> <!– в котором реализовано состояние. Это будет тип переменной-члена, —> <!– указывающей на текущее состояние конечного автомата –> <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>

<!– Объявление кoнстpyктopa –> <xsl:text>&INDENT;</xsl:text>

<xsl:value-of select="$className"/>::<xsl:value-of select="$className"/> <xsl:text>();&#xa;&#xa;</xsl:text>

<!– Снабженные стеpеoтипoм delegate oпеpaции делегиpyют выполнение –> <!– текущему состоянию –> <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>

<!— Опеpaции, не снабженные стеpеoтипoм delegate, — это все пpoчие –> <!– oпеpaции контекста, кoтopые может вызывать состояние –> <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>

<!– ОБЪЯВЛЕНИЕ КОНКРЕТНЫХ СОСТОЯНИЙ —>

<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>

<!– Конструкторы синглетных классов должны быть защищенными –>

<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>

<!– Шаблоны, используемые для объявления классов, содержащих –> <!– только открытые члены –>

<xsl:template match="&CLASS;" mode="declare"> <xsl:text>class&#x2 0;</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>&#x2 0;</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>

<!– Игнорировать лишние текстовые узлы –> <xsl:template match="text()" mode="declare"/>

<!— ОБЪЯВЛЕНИЕ ИНТЕРФЕЙСА СОСТОЯНИЯ –>

<xsl:template match="&CLASS;" mode="stateInterfaceDecl">

<xsl:text>//Опережающие объявления&#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"/> <!– Найти класс, ассоциированный с контекстом состояния –> <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>

<!– Игнорировать лишние текстовые узлы –> <xsl:template match="text()" mode="stateInterfaceDecl"/>

<!– РЕАЛИЗАЦИЯ КОНТЕКСТА СОСТОЯНИЯ –> <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>//Конструктор&#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;//Инициализировать конечный автомат в начальном состоянии &#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>&#x2 0;</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>&#x2 0;</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: Реализовать это действие&#xa;</xsl:text> <xsl:text>}&#xa;&#xa;</xsl:text> </xsl:for-each> </xsl:template>

<xsl:template match="text()" mode="stateContextImpl"/> <!– РЕАЛИЗАЦИЯ КОНКРЕТНЫХ СОСТОЯНИЙ —>

<xsl:template match="&CLASS;" mode="concreteStatesImpl"> <xsl:variable name="classId" select="@xmi.id"/> <xsl:variable name="stateMachine"

select="/&STATEMACH;[&SMC;/&CLASS;/@xmi.idref =

$classId]"/>

<!– Для каждого состояния автомата сгенерировать его реализацию –> <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>

<!– Игнорировать лишние текстовые узлы –> <xsl:template match="text()" mode="concreteStatesImpl"/>

<xsl:template name="generateState">

<!— Это XMI-класс, соответствующий данному состоянию —> <xsl:param name="stateClass"/>

<!– Текущий контекст — это некоторое состояние –> <xsl:variable name="state" select="."/> <xsl:variable name="className" select="&NAME;"/> <xsl:if test="$className != $stateClass/&NAME;">

<xsl:message terminate="yes">Hет соответствия между состоянием и классом!</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>&#x2 0;</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>

<!– Генерируем параметры функций –> <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>&#x2 0;</xsl:text> <xsl:value-of select="&NAME;"/> <xsl:if test="position() != last()">

<xsl:text>, </xsl:text> </xsl:if> </xsl:for-each> </xsl:template>

<!– Генерируем базовые классы –> <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>

Вот фрагмент сгенерированного кода на C++:

//Опережающие объявления 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);

} ;

//!ПРОЧИЕ СОСТОЯНИЯ ОПУЩЕНЫ ДЛЯ ЭКОНОМИИ МЕСТА…

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( ) ;

}

//!ПРОЧИЕ СОСТОЯНИЯ ОПУЩЕНЫ ДЛЯ ЭКОНОМИИ МЕСТА…

//Конструктор

AnsweringMachine::AnsweringMachine( )

{

//Инициализировать конечный автомат в начальном состоянии m_State = &Start::instance( ) ;

}

void AnsweringMachine::changeState(AnsweringMachineState& newState) {

m_State = snewState;

}

void AnsweringMachine::doCmd(Command cmd)

{

m_State->doCmd(*this, cmd);

}

bool AnsweringMachine::moreMsgs( )

{

//!TODO: Реализовать это действие

}

void AnsweringMachine::playRemaining( )

{

//!TODO: Реализовать это действие

}

void AnsweringMachine::playCurrentMsg( )

{

//!TODO: Реализовать это действие

}

//!ПРОЧИЕ ДЕЙСТВИЯ ОПУЩЕНЫ ДЛЯ ЭКОНОМИИ МЕСТА…

Обсуждение

Желание написать собственный генератор код по UML-модели может воз­никнуть в трех случаях. Во-первых, вам не нравится стиль или сам код, создан­ный штатным кодогенератором, входящим в состав инструментальной програм­мы. Во-вторых, инструмент не умеет генерировать код для нужного вам языка. Например, я не знаю инструмента, который генерировал бы код на языке Python1. В-третьих, вы хотите автоматизировать генерацию кода для какого-то высокоуровневого паттерна или программной службы, часто встречающейся в приложении. Именно последняя причина натолкнула нас на идею, реализо­ванную в этом примере.

Создание универсального механизма для генерации кода на различных язы­ках, построенного на базе XMI и XSLT, – это интересный проект, но он выходит далеко за пределы одного примера. Приведенное решение – не окончательный генератор промышленного качества, а лишь подступы к нему. В частности, можно было бы реализовать четыре важных усовершенствования.

Во-первых, таблицу стилей следует разбить на небольшие компоненты, кото­рые генерируют отдельные конструкции на целевом языке. Примерно так мы по­ступили в главе 9, когда генерировали SVG-код.

Во-вторых, таблица стилей должна извлекать из UML-модели больше информа­ции, в частности о контроле доступа, в не «зашивать» в код произвольные допущения.

В-третьих, в генераторе следует применить расширения XSLT 1.0 или XSLT 2.0, чтобы разбить код на несколько заголовочных и исходных файлов, а не помещать все в один монолитный исходный файл.

В-четвертых, хотя это и не так важно, можно было бы поддержать несколько сти­лей оформления кода. Например, я генерировал код на C++ в стиле Олмана (Allman), хотя многие (по недоразумению?) предпочитают стиль Кернигана и Ричи (K&R) (http://www.tuxedo.org/~esr/jargon/html/entry/indent-style.html). Но можно было бы просто пропустить результирующий код через какой-нибудь автономный фор­матер и вообще не задумываться о форматировании.

Мангано Сэл  XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии