UPDATE: Improved the explanation so hopefully the problem is clear now :)
Hi all!
I've got a XML in which transitions of a FSM are declared (and events and states offcourse).
Here's a simplified version of the XML which also causes the problem which is described below:
<?xml version="1.0" encoding="UTF-8"?>
<FSM name="MediaPlayer">
<state name="FastForward"/>
<state name="PlayingMediaFile"/>
<transition name="FastForward to FastForward" source="FastForward" target="FastForward">
<trigger name="FastForwardButtonPressed" action="playingSpeed * 2" guard="currentMediaFile.CurrentPosition U+003C currentMediaFile.Lengtha"/>
</transition>
<transition name="FastForward to PlayingMediaFile" source="FastForward" target="PlayingMediaFile">
<trigger name="PlayButtonPressed" action="playingSpeed = 1" guard=""/>
</transition>
<event name="FastForwardButtonPressed"/>
<event name="PlayButtonPressed"/>
</FSM>
My goal is to generate a FSM in C# with XSLT. The implementation of the FSM must be according to state pattern. Because of this I need to create a class for every state. In every class, methods will represent events. Since the classes derive from a abstract class which holds all the methods (events), every class/state needs to implement all the methods(events). Offcourse not all methods are actually implemented because a state not uses all the events. Therefore I choose to implement the non used events with an exception.
For generating the C# code I need to traverse the XML and generate a class for every state. When a class is created, the next step is to generate a method for every event. For now, this is the difficult part. For every method(event) I need to check whether the event exists for the current state I'm evaluating. Therefore I traverse the XML and look for a transition which attribute "source" equals the current evaluated state/class. If found one, I check whether the trigger child is the same as the current event I'm evaluating. If both tests are true the content of "transtion/trigger/@action" and "transtion/trigger/@guard" need to be used as the implementation of the current method/event. If there isn't found a valid transition, it means the event doesn't exist for the state and therefore must be implemented as an exception.
For now I haven't written code for reading the action and guard attributes so there will be no code generated for this. For now I use the text "implement guard/action" when a transition is valid for the current state and event and use the text "throw new NotImplementedException();" when a transition is not found. Or better said, this should be done....
I've written code which almost gets me there:
<xsl:for-each select="//state">
<xsl:variable name="currentStateName" select="@name"/>
public class <xsl:value-of select="@name"/> : <xsl:value-of select="$FsmName"/>States
{
public <xsl:value-of select="@name"/>()
{
Console.WriteLine("In <xsl:value-of select="@name"/>State");
}
<xsl:for-each select="//event">
public override void <xsl:value-of select="@name"/>(MediaPlayer player)
{
<xsl:variable name="currentEventName" select="@name"/>
<xsl:for-each select="//transition">
<xsl:if test="@source = $currentStateName">
<xsl:choose>
<xsl:when test="trigger/@name = $currentEventName">
implement guard/action
</xsl:when>
<xsl:otherwise>
throw new NotImplementedException();
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:for-each>
}
</xsl:for-each>
}
</xsl:for-each>
Now the output of this code is:
public class FastForward : MediaPlayerStates
{
public FastForward()
{
Console.WriteLine("In FastForwardState");
}
public override void FastForwardButtonPressed(MediaPlayer player)
{
implement guard/action
throw new NotImplementedException();
}
public override void PlayButtonPressed(MediaPlayer player)
{
throw new NotImplementedException();
implement guard/action
}
}
public class PlayingMediaFile : MediaPlayerStates
{
public PlayingMediaFile()
{
Console.WriteLine("In PlayingMediaFileState");
}
public override void FastForwardButtonPressed(MediaPlayer player)
{
}
public override void PlayButtonPressed(MediaPlayer player)
{
}
}
As you might understand, this is wrong. In the "FastForward" class are generated wrong. It only needs the "implement guard/action" and not also the "throw new NotImplementedException();". In the "PlayingMediaFile" class nothing gets generated while it needs be filled with "throw new NotImplementedException();" because that class has no events.
public class FastForward : MediaPlayerStates
{
public FastForward()
{
Console.WriteLine("In FastForwardState");
}
public override void FastForwardButtonPressed(MediaPlayer player)
{
implement guard/action
}
public override void PlayButtonPressed(MediaPlayer player)
{
implement guard/action
}
}
public class PlayingMediaFile : MediaPlayerStates
{
public PlayingMediaFile()
{
Console.WriteLine("In PlayingMediaFileState");
}
public override void FastForwardButtonPressed(MediaPlayer player)
{
throw new NotImplementedException();
}
public override void PlayButtonPressed(MediaPlayer player)
{
throw new NotImplementedException();
}
}
Now I understand why it is going wrong which I will try to explain: when looping all the transitions, it encounters two transitions. The first one will evaluate true to the test so it generates the "implement guard/action" text. Now it traverses to the second transition node which evaluates false. Because of the false evaluation, it generates the NotImplementedException(). This happens all for the same event and thus is wrong.
So, as far as I can see, the transition for-each loop needs to break when the test is true. If there wasn't a found a matching transition, the "NotImplementedException" text needs to be generated.
I've tried all kind of things but I can't get it done. Can somebody please help me?
Thanks