From 1051e807cff93a47fa6b09cc660deafea167b4c5 Mon Sep 17 00:00:00 2001 From: Tamino Dauth Date: Mon, 1 Apr 2019 17:49:12 +0200 Subject: [PATCH] Store incoming transitions #24 --- .../api/ActionTransitions.scala | 6 +-- .../de/retest/guistatemachine/api/State.scala | 9 +++- .../guistatemachine/api/impl/StateImpl.scala | 49 +++++++++++++------ .../GuiStateMachinGMLSerializer.scala | 4 +- .../api/impl/GuiStateMachineImplSpec.scala | 36 +++++++++----- .../GuiStateMachineGMLSerializerSpec.scala | 5 -- ...achineJavaObjectStreamSerializerSpec.scala | 12 ++--- 7 files changed, 77 insertions(+), 44 deletions(-) diff --git a/src/main/scala/de/retest/guistatemachine/api/ActionTransitions.scala b/src/main/scala/de/retest/guistatemachine/api/ActionTransitions.scala index b1c9d5f..4e06251 100644 --- a/src/main/scala/de/retest/guistatemachine/api/ActionTransitions.scala +++ b/src/main/scala/de/retest/guistatemachine/api/ActionTransitions.scala @@ -2,11 +2,11 @@ package de.retest.guistatemachine.api /** * Represents transitions for one single symbol which is represented by an `de.retest.surili.commons.actions.Action` to a number of states. - * The corresponding symbol is not stored in this class but in the [[State]] from which the transitions are started. + * The corresponding symbol is not stored in this class but in the [[State]] from which the transitions are started or which the transitions lead to. * - * @param to The states which the transitions lead to. Since it is a NFA, there can be multiple states for the same symbol. + * @param states The states which the transitions lead to or start from. Since it is a NFA, there can be multiple states for the same symbol. * @param executionCounter The number of times all transitions for the action have been executed from the corresponding state. * It does not matter to which state. In the legacy code this was stored as `StateGraph.executionCounter`. */ @SerialVersionUID(1L) -case class ActionTransitions(to: Set[State], executionCounter: Int) extends Serializable +case class ActionTransitions(states: Set[State], executionCounter: Int) extends Serializable diff --git a/src/main/scala/de/retest/guistatemachine/api/State.scala b/src/main/scala/de/retest/guistatemachine/api/State.scala index 11618e8..e2e0ff5 100644 --- a/src/main/scala/de/retest/guistatemachine/api/State.scala +++ b/src/main/scala/de/retest/guistatemachine/api/State.scala @@ -19,7 +19,12 @@ trait State { * Hence, we have a set of states per action. * In the legacy code there was a type called `AmbigueState` but a multimap simplifies the implementation. */ - def getTransitions: Map[Action, ActionTransitions] + def getOutgoingActionTransitions: Map[Action, ActionTransitions] + + /** + * Helper method to retrieve all incoming transitions. + */ + def getIncomingActionTransitions: Map[Action, ActionTransitions] /** * Overriding this method is required to allow the usage of a set of states. @@ -37,7 +42,7 @@ trait State { override def hashCode(): Int = this.getSutState.hashCode() - override def toString: String = s"sutState=$getSutState,transitions=$getTransitions" + override def toString: String = s"sutState=$getSutState,transitions=$getOutgoingActionTransitions" /** * Adds a new transition to the state which is only allowed by calling the methods of [[GuiStateMachine]]. diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala b/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala index 83ccaa3..7140f3f 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala @@ -13,22 +13,43 @@ case class StateImpl(sutState: SutState) extends State with Serializable { * Currently, there is no MultiMap trait for immutable maps in the Scala standard library. * The legacy code used `AmbigueState` here which was more complicated than just a multi map. */ - var transitions = HashMap[Action, ActionTransitions]() + private var outgoingActionTransitions = HashMap[Action, ActionTransitions]() + + /** + * Redundant information but helpful to be retrieved. + */ + private var incomingActionTransitions = HashMap[Action, ActionTransitions]() override def getSutState: SutState = this.synchronized { sutState } - override def getTransitions: Map[Action, ActionTransitions] = this.synchronized { transitions } - - private[api] override def addTransition(a: Action, to: State): Int = this.synchronized { - val old = transitions.get(a) - old match { - case Some(o) => - val updated = ActionTransitions(o.to + to, o.executionCounter + 1) - transitions = transitions + (a -> updated) - updated.executionCounter - - case None => - transitions += (a -> ActionTransitions(Set(to), 1)) - 1 + override def getOutgoingActionTransitions: Map[Action, ActionTransitions] = this.synchronized { outgoingActionTransitions } + override def getIncomingActionTransitions: Map[Action, ActionTransitions] = this.synchronized { incomingActionTransitions } + + private[api] override def addTransition(a: Action, to: State): Int = { + val executionCounter = this.synchronized { + outgoingActionTransitions.get(a) match { + case Some(oldTransitions) => + val updatedTransitions = ActionTransitions(oldTransitions.states + to, oldTransitions.executionCounter + 1) + outgoingActionTransitions = outgoingActionTransitions + (a -> updatedTransitions) + updatedTransitions.executionCounter + + case None => + outgoingActionTransitions += (a -> ActionTransitions(Set(to), 1)) + 1 + } } + + to.synchronized { + val other = to.asInstanceOf[StateImpl] + other.incomingActionTransitions.get(a) match { + case Some(oldTransitions) => + val updatedTransitions = ActionTransitions(oldTransitions.states + this, oldTransitions.executionCounter + 1) + other.incomingActionTransitions = other.incomingActionTransitions + (a -> updatedTransitions) + + case None => + other.incomingActionTransitions += (a -> ActionTransitions(Set(this), 1)) + } + } + + executionCounter } } diff --git a/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachinGMLSerializer.scala b/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachinGMLSerializer.scala index 752b61a..3d16f73 100644 --- a/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachinGMLSerializer.scala +++ b/src/main/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachinGMLSerializer.scala @@ -48,12 +48,12 @@ class GuiStateMachinGMLSerializer(guiStateMachine: GuiStateMachine) extends GuiS allStatesSorted.foreach { x => val fromVertex = x._1 - val allTransitionsSorted = x._2.getTransitions.toSeq.sortWith(hashCodeComparisonOfTuples) + val allTransitionsSorted = x._2.getOutgoingActionTransitions.toSeq.sortWith(hashCodeComparisonOfTuples) allTransitionsSorted foreach { transition => val actionTransitions = transition._2 val action = transition._1 - actionTransitions.to.foreach { toState => + actionTransitions.states.foreach { toState => val toVertex = toState.getSutState val edge = GraphActionEdge(fromVertex, toVertex, action) if (!graph.addEdge(fromVertex, toVertex, edge)) { throw new RuntimeException(s"Failed to add edge $edge") } diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala index ba65245..fc408fe 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImplSpec.scala @@ -44,10 +44,14 @@ class GuiStateMachineImplSpec extends AbstractApiSpec with BeforeAndAfterEach { val s0SutState = createSutState(rootElementA) val s0 = sut.getState(s0SutState) sut.executeAction(initialSutState, action0, s0SutState) - initial.getTransitions.size shouldEqual 1 - initial.getTransitions(action0).to.size shouldEqual 1 - initial.getTransitions(action0).executionCounter shouldEqual 1 - s0.getTransitions.size shouldEqual 0 + initial.getOutgoingActionTransitions.size shouldEqual 1 + initial.getOutgoingActionTransitions(action0).states.size shouldEqual 1 + initial.getOutgoingActionTransitions(action0).executionCounter shouldEqual 1 + initial.getIncomingActionTransitions.size shouldEqual 0 + s0.getOutgoingActionTransitions.size shouldEqual 0 + s0.getIncomingActionTransitions.size shouldEqual 1 + s0.getIncomingActionTransitions(action0).states.size shouldEqual 1 + s0.getIncomingActionTransitions(action0).executionCounter shouldEqual 1 sut.getAllExploredActions.size shouldEqual 1 sut.getActionExecutionTimes.get(action0).isDefined shouldEqual true sut.getActionExecutionTimes(action0) shouldEqual 1 @@ -56,10 +60,14 @@ class GuiStateMachineImplSpec extends AbstractApiSpec with BeforeAndAfterEach { val s1SutState = createSutState(rootElementB) val s1 = sut.getState(s1SutState) sut.executeAction(initialSutState, action0, s1SutState) - initial.getTransitions.size shouldEqual 1 - initial.getTransitions(action0).to.size shouldEqual 2 - initial.getTransitions(action0).executionCounter shouldEqual 2 - s1.getTransitions.size shouldEqual 0 + initial.getOutgoingActionTransitions.size shouldEqual 1 + initial.getOutgoingActionTransitions(action0).states.size shouldEqual 2 + initial.getOutgoingActionTransitions(action0).executionCounter shouldEqual 2 + initial.getIncomingActionTransitions.size shouldEqual 0 + s1.getOutgoingActionTransitions.size shouldEqual 0 + s1.getIncomingActionTransitions.size shouldEqual 1 + s1.getIncomingActionTransitions(action0).states.size shouldEqual 1 + s1.getIncomingActionTransitions(action0).executionCounter shouldEqual 1 sut.getAllExploredActions.size shouldEqual 1 sut.getActionExecutionTimes.get(action0).isDefined shouldEqual true sut.getActionExecutionTimes(action0) shouldEqual 2 @@ -68,10 +76,14 @@ class GuiStateMachineImplSpec extends AbstractApiSpec with BeforeAndAfterEach { val s2SutState = createSutState(rootElementC) val s2 = sut.getState(s2SutState) sut.executeAction(initialSutState, action1, s2SutState) - initial.getTransitions.size shouldEqual 2 - initial.getTransitions(action1).to.size shouldEqual 1 - initial.getTransitions(action1).executionCounter shouldEqual 1 - s2.getTransitions.size shouldEqual 0 + initial.getOutgoingActionTransitions.size shouldEqual 2 + initial.getOutgoingActionTransitions(action1).states.size shouldEqual 1 + initial.getOutgoingActionTransitions(action1).executionCounter shouldEqual 1 + initial.getIncomingActionTransitions.size shouldEqual 0 + s2.getOutgoingActionTransitions.size shouldEqual 0 + s2.getIncomingActionTransitions.size shouldEqual 1 + s2.getIncomingActionTransitions(action1).states.size shouldEqual 1 + s2.getIncomingActionTransitions(action1).executionCounter shouldEqual 1 sut.getAllExploredActions.size shouldEqual 2 sut.getActionExecutionTimes.get(action1).isDefined shouldEqual true sut.getActionExecutionTimes(action1) shouldEqual 1 diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineGMLSerializerSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineGMLSerializerSpec.scala index 029c658..ed3a39f 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineGMLSerializerSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineGMLSerializerSpec.scala @@ -26,16 +26,11 @@ class GuiStateMachineGMLSerializerSpec extends AbstractApiSpec with BeforeAndAft val finalSutState = createSutState(rootElementC) // Create the whole state machine: - val initialState = guiStateMachine.getState(initialSutState) - val finalState = guiStateMachine.getState(finalSutState) guiStateMachine.executeAction(initialSutState, action0, finalSutState) guiStateMachine.executeAction(initialSutState, action1, finalSutState) guiStateMachine.executeAction(finalSutState, action0, initialSutState) guiStateMachine.executeAction(finalSutState, action1, initialSutState) - initialState.getTransitions.size shouldEqual 2 - finalState.getTransitions.size shouldEqual 2 - val filePath = "./target/test_state_machine.gml" val oldFile = new File(filePath) diff --git a/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineJavaObjectStreamSerializerSpec.scala b/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineJavaObjectStreamSerializerSpec.scala index ac3a24c..d031c8c 100644 --- a/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineJavaObjectStreamSerializerSpec.scala +++ b/src/test/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachineJavaObjectStreamSerializerSpec.scala @@ -51,14 +51,14 @@ class GuiStateMachineJavaObjectStreamSerializerSpec extends AbstractApiSpec with val loadedInitialState = guiStateMachine.getAllStates(initialSutState) val loadedFinalState = guiStateMachine.getAllStates(finalSutState) loadedInitialState.getSutState shouldEqual initialSutState - loadedInitialState.getTransitions.size shouldEqual 1 - loadedInitialState.getTransitions.contains(action0) shouldEqual true - val loadedTransition = loadedInitialState.getTransitions(action0) + loadedInitialState.getOutgoingActionTransitions.size shouldEqual 1 + loadedInitialState.getOutgoingActionTransitions.contains(action0) shouldEqual true + val loadedTransition = loadedInitialState.getOutgoingActionTransitions(action0) loadedTransition.executionCounter shouldEqual 1 - loadedTransition.to.size shouldEqual 1 - loadedTransition.to.head shouldEqual loadedFinalState + loadedTransition.states.size shouldEqual 1 + loadedTransition.states.head shouldEqual loadedFinalState loadedFinalState.getSutState shouldEqual finalSutState - loadedFinalState.getTransitions.isEmpty shouldEqual true + loadedFinalState.getOutgoingActionTransitions.isEmpty shouldEqual true } } }