Recipe 12.4. Generating Data Wrappers
Problem
You want to create classes
that wrap the data
contained in each message with a type-safe interface.
Solution
The solution works in two modes. If a message name is provided in a
parameter, then it generates a wrapper only for that message data.
Otherwise, if no message is specified, it generates wrappers for all
messages:
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<!--The message to generate data for. '*' for all -->
<xsl:param name="message" select=" '*' "/>
<!--The directory to generate code -->
<xsl:param name="generationDir" select=" 'src/' "/>
<!--The C++ header extension to use -->
<xsl:param name="headerExt" select=" '.h' "/>
<!--The C++ source extension to use -->
<xsl:param name="sourceExt" select=" '.C' "/>
<!--Key to locate data types by name -->
<xsl:key name="dataTypes" match="Structure" use="Name" />
<xsl:key name="dataTypes" match="Primitive" use="Name" />
<xsl:key name="dataTypes" match="Array" use="Name" />
<xsl:key name="dataTypes" match="Enumeration" use="Name" />
<!-- Top-level template determines which messages to process -->
<xsl:template match="/">
<xsl:choose>
<xsl:when test="$message = '*'">
<xsl:apply-templates select="*/Messages/*"/>
</xsl:when>
<xsl:when test="*/Messages/Message[Name=$message]">
<xsl:apply-templates select="*/Messages/Message[Name=$message]"/>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">No such message name
[<xsl:value-of select="$message"/>]</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- If the messages data type is contained in the repository, then generate data
wrapper header and source file for it -->
<xsl:template match="Message">
<xsl:choose>
<xsl:when test="key('dataTypes',DataTypeName)">
<xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="header"/>
<xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="source"/>
</xsl:when>
<xsl:otherwise>
<xsl:message>Message name [<xsl:value-of select="Name"/>] uses data
[<xsl:value-of select="DataTypeName"/>] that is not defined in the
repository.</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- We only generate headers if a messages data type is a Structure.
The only other typical message data type is XML. We don't generate wrappers for XML
payloads.-->
<xsl:template match="Structure" mode="header">
<xsl:document href="{concat($generationDir,Name,$headerExt)}">
#include <primitives/primitives.h>
class <xsl:value-of select="Name"/>
{
public:<xsl:text>

</xsl:text>
<xsl:for-each select="Members/Member">
<xsl:text> </xsl:text>
<xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="returnType"/>
get_<xsl:value-of select="Name"/>( ) const ;<xsl:text/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:text>
</xsl:text>
private:<xsl:text>

</xsl:text>
<xsl:for-each select="Members/Member">
<xsl:text> </xsl:text>
<xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="data"/> m_
<xsl:value-of select="Name"/> ;<xsl:text/>
<xsl:text>
</xsl:text>
</xsl:for-each>
} ;
</xsl:document>
</xsl:template>
<!-- We only generate source if a messages data type is a Structure. -->
<!-- The only other typical message data type is XML. We don't -->
<!-- generate wrappers for XML payloads. -->
<xsl:template match="Structure" mode="source">
<xsl:document href="{concat($generationDir,Name,$sourceExt)}">
#include "<xsl:value-of select="Name"/><xsl:value-of select="$headerExt"/>"
<xsl:text/>
<xsl:for-each select="Members/Member">
<xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="returnType"/>
<xsl:text> </xsl:text>
<xsl:value-of select="../../Name"/>::get_<xsl:value-of select="Name"/>( ) const
<xsl:text>
</xsl:text>
<xsl:text>{
</xsl:text>
<xsl:text> return m_</xsl:text><xsl:value-of select="Name"/>
<xsl:text>;
</xsl:text>
<xsl:text>}

</xsl:text>
</xsl:for-each>
</xsl:document>
</xsl:template>
<!-- We assume members that are themselves structures are -->
<!-- returned by reference. -->
<xsl:template match="Structure" mode="returnType">
const <xsl:value-of select="Name"/>&<xsl:text/>
</xsl:template>
<!-- We map primitives that can be represented by native C++ types to those native
types. -->
<!-- Otherwise we assume the primitive is externally defined. -->
<xsl:template match="Primitive" mode="returnType">
<xsl:choose>
<xsl:when test="Name='Integer' ">int</xsl:when>
<xsl:when test="Name='Real' ">double</xsl:when>
<xsl:otherwise><xsl:value-of select="Name"/></xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*" mode="returnType">
<xsl:value-of select="Name"/>
</xsl:template>
<xsl:template match="Primitive" mode="data">
<xsl:choose>
<xsl:when test="Name='Integer' ">int</xsl:when>
<xsl:when test="Name='Real' ">double</xsl:when>
<xsl:otherwise><xsl:value-of select="Name"/></xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*" mode="data">
<xsl:value-of select="Name"/>
</xsl:template>
</xsl:stylesheet>
This generator produces only a
get
interface, but you can easily extend it to generate set functions or
other types of functions. Here is a sample generated header file:
#include <primitives/primitives.h>
class AddStockOrderData
{
public:
StkSymbol get_symbol( ) const ;
Shares get_quantity( ) const ;
BuyOrSell get_side( ) const ;
OrderType get_type( ) const ;
double get_price( ) const ;
private:
StkSymbol m_symbol ;
Shares m_quantity ;
BuyOrSell m_side ;
OrderType m_type ;
double m_price ;
} ;
Here is a sample
cpp file:
#include "AddStockOrderData.h"
StkSymbol AddStockOrderData::get_symbol( ) const
{
return m_symbol;
}
Shares AddStockOrderData::get_quantity( ) const
{
return m_quantity;
}
BuyOrSell AddStockOrderData::get_side( ) const
{
return m_side;
}
OrderType AddStockOrderData::get_type( ) const
{
return m_type;
}
double AddStockOrderData::get_price( ) const
{
return m_price;
}
Discussion
This section uses the term wrapper to denote a
class that provides an object-oriented interface to data that is
otherwise just a plain old C struct. I once worked on a project that
hand-coded all our message wrappers. Although the work was tedious,
the result was well worth the effort. Consider a message that
contains prices, quantities, and dates. An integer type might encode
both of these higher-level types. You could easily make a mistake and
substitute one for the other without the compiler noticing. Wrappers
provide a way to put a skin around your message data that converts
low-level representations to class-based primitives such as
Price, Qty, and
Date. An autogenerated wrapper provides this
benefit with less effort.
A message repository and XSLT-based generator allow you to automate
the task of producing wrappers. In practice, wrappers sometimes
contain some smarts, and you might need to store additional metadata
in the repository to get corresponding code-generation smarts. One
common case occurs when message data contains arrays. Often another
field is present that states how many items are actually stored in
the array. If you hand-coded a wrapper function to add an item to
this array, it would need to reference this field to find the next
empty locations and increment it after adding the new data. You could
generate such code only if the repository associated
the array size field with the array field.
|