Skip to content
This repository was archived by the owner on Mar 12, 2020. It is now read-only.

Commit 36985aa

Browse files
committed
Initial hash code identifier support #27
We use a SHA-256 hash based on the binary object stream of serializables. We provide two difference classes for SutStates and Actions which can get more properties in the future.
1 parent 6047cc2 commit 36985aa

16 files changed

+191
-105
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package de.retest.guistatemachine.api
2+
3+
import de.retest.surili.commons.actions.Action
4+
5+
class ActionIdentifier(action: Action) extends HashIdentifier(action)

src/main/scala/de/retest/guistatemachine/api/GuiStateMachine.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ import de.retest.surili.commons.actions.Action
1313
*/
1414
trait GuiStateMachine {
1515

16+
def getState(sutStateIdentifier: SutStateIdentifier): State
17+
1618
/**
1719
* Gets a state identified by the corresponding SUT state.
1820
*
1921
* @param sutState The SUT state which identifies the state.
2022
* @return The state identified by the descriptors. If there has not been any state yet, it will be added.
2123
*/
22-
def getState(sutState: SutState): State
24+
def getState(sutState: SutState): State = getState(new SutStateIdentifier(sutState))
2325

2426
/**
2527
* Executes an action from a state leading to the current state described by descriptors.
@@ -29,26 +31,27 @@ trait GuiStateMachine {
2931
* @param to The state which the execution leads to.
3032
* @return The current state which the transition of a leads to.
3133
*/
32-
def executeAction(from: State, a: Action, to: State): State
34+
def executeAction(from: State, a: ActionIdentifier, to: State): State
35+
def executeAction(from: State, a: Action, to: State): State = executeAction(from, new ActionIdentifier(a), to)
3336
def executeAction(fromSutState: SutState, a: Action, toSutState: SutState): State =
3437
executeAction(getState(fromSutState), a, getState(toSutState))
3538

36-
def getAllStates: Map[SutState, State]
39+
def getAllStates: Map[SutStateIdentifier, State]
3740

3841
/**
3942
* In the legacy code this was only used to show the number of actions which have been explored by Monkey Testing.
4043
*
4144
* @return All actions which have been explored and therefore have a corresponding transition.
4245
*/
43-
def getAllExploredActions: Set[Action]
46+
def getAllExploredActions: Set[ActionIdentifier]
4447

4548
/**
4649
* In the legacy code this was only used to calculate all never explored actions.
4750
* It could be used for the visualization of the NFA to see how often actions are executed.
4851
*
4952
* @return The number of times every explored action has been executed in the NFA. Never explored actions are not part of it.
5053
*/
51-
def getActionExecutionTimes: Map[Action, Int]
54+
def getActionExecutionTimes: Map[ActionIdentifier, Int]
5255

5356
/**
5457
* Clears all states, transitions and never explored actions etc.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package de.retest.guistatemachine.api
2+
3+
import java.io.{ByteArrayOutputStream, ObjectOutputStream, Serializable}
4+
5+
/**
6+
* Storing the whole states and actions as XML takes too much space. Therefore, we only store a hash value. The hash
7+
* should have no collisions to uniquely identify states and actions. Since we use SHA-256 the probability is very low
8+
* that any collisions will occur.
9+
*
10+
* @param serializable The serializable from which a SHA-256 is generated.
11+
*/
12+
@SerialVersionUID(1L)
13+
class HashIdentifier(serializable: Serializable) extends scala.Serializable {
14+
val hash: String = HashIdentifier.sha256Hash(HashIdentifier.serializableToString(serializable))
15+
16+
override def equals(obj: Any): Boolean =
17+
if (!obj.isInstanceOf[HashIdentifier]) {
18+
false
19+
} else {
20+
val other = obj.asInstanceOf[HashIdentifier]
21+
hash.equals(other.hash)
22+
}
23+
24+
override def hashCode(): Int = hash.hashCode
25+
26+
override def toString: String = s"hash=$hash"
27+
28+
}
29+
30+
object HashIdentifier {
31+
def sha256Hash(text: String): String =
32+
String.format("%064x", new java.math.BigInteger(1, java.security.MessageDigest.getInstance("SHA-256").digest(text.getBytes("UTF-8"))))
33+
34+
def serializableToString(serializable: Serializable): String = {
35+
val byteArrayOutputStream = new ByteArrayOutputStream()
36+
val oos = new ObjectOutputStream(byteArrayOutputStream)
37+
try {
38+
oos.writeObject(serializable)
39+
byteArrayOutputStream.toString("UTF-8")
40+
} finally {
41+
oos.close()
42+
}
43+
}
44+
}
Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package de.retest.guistatemachine.api
22

3-
import de.retest.recheck.ui.descriptors.SutState
4-
import de.retest.surili.commons.actions.Action
5-
63
/**
74
* A state should be identified by its corresponding SutState.
85
* It consists of actions which have not been explored yet and transitions to states which build up the state machine.
@@ -12,19 +9,19 @@ trait State {
129
/**
1310
* @return The SutState which identifies this state.
1411
*/
15-
def getSutState: SutState
12+
def getSutStateIdentifier: SutStateIdentifier
1613

1714
/**
1815
* NFA states can lead to different states by consuming the same symbol.
1916
* Hence, we have a set of states per action.
2017
* In the legacy code there was a type called `AmbigueState` but a multimap simplifies the implementation.
2118
*/
22-
def getOutgoingActionTransitions: Map[Action, ActionTransitions]
19+
def getOutgoingActionTransitions: Map[ActionIdentifier, ActionTransitions]
2320

2421
/**
2522
* Helper method to retrieve all incoming transitions.
2623
*/
27-
def getIncomingActionTransitions: Map[Action, ActionTransitions]
24+
def getIncomingActionTransitions: Map[ActionIdentifier, ActionTransitions]
2825

2926
/**
3027
* Overriding this method is required to allow the usage of a set of states.
@@ -34,15 +31,15 @@ trait State {
3431
override def equals(obj: Any): Boolean = {
3532
obj match {
3633
case other: State =>
37-
this.getSutState == other.getSutState
34+
this.getSutStateIdentifier == other.getSutStateIdentifier
3835
case _ =>
3936
super.equals(obj)
4037
}
4138
}
4239

43-
override def hashCode(): Int = this.getSutState.hashCode()
40+
override def hashCode(): Int = this.getSutStateIdentifier.hashCode()
4441

45-
override def toString: String = s"sutState=$getSutState,transitions=$getOutgoingActionTransitions"
42+
override def toString: String = s"sutStateIdentifier=$getSutStateIdentifier"
4643

4744
/**
4845
* Adds a new transition to the state which is only allowed by calling the methods of [[GuiStateMachine]].
@@ -51,5 +48,5 @@ trait State {
5148
* @param to The state which the transition leads t o.
5249
* @return The number of times the action has been executed from this state. The target state does not matter for this number.
5350
*/
54-
private[api] def addTransition(a: Action, to: State): Int
51+
private[api] def addTransition(a: ActionIdentifier, to: State): Int
5552
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package de.retest.guistatemachine.api
2+
3+
import de.retest.recheck.ui.descriptors.SutState
4+
5+
class SutStateIdentifier(sutState: SutState) extends HashIdentifier(sutState)

src/main/scala/de/retest/guistatemachine/api/impl/GuiStateMachineImpl.scala

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package de.retest.guistatemachine.api.impl
22

33
import com.typesafe.scalalogging.Logger
4-
import de.retest.guistatemachine.api.{GuiStateMachine, State}
5-
import de.retest.recheck.ui.descriptors.SutState
6-
import de.retest.surili.commons.actions.Action
4+
import de.retest.guistatemachine.api.{ActionIdentifier, GuiStateMachine, State, SutStateIdentifier}
75

86
import scala.collection.immutable.{HashMap, HashSet}
97

@@ -13,31 +11,31 @@ import scala.collection.immutable.{HashMap, HashSet}
1311
@SerialVersionUID(1L)
1412
class GuiStateMachineImpl extends GuiStateMachine with Serializable {
1513
@transient private val logger = Logger[GuiStateMachineImpl]
16-
private var states = new HashMap[SutState, State]
14+
private var states = new HashMap[SutStateIdentifier, State]
1715

1816
/**
1917
* The legacy code stored execution counters for every action.
2018
*/
21-
private var allExploredActions = new HashSet[Action]
19+
private var allExploredActions = new HashSet[ActionIdentifier]
2220

2321
/**
2422
* `actionExecutionCounter` from the legacy code.
2523
* Stores the total number of executions per action.
2624
*/
27-
private var actionExecutionTimes = new HashMap[Action, Int]
25+
private var actionExecutionTimes = new HashMap[ActionIdentifier, Int]
2826

29-
override def getState(sutState: SutState): State = this.synchronized {
27+
override def getState(sutState: SutStateIdentifier): State = this.synchronized {
3028
if (states.contains(sutState)) {
3129
states(sutState)
3230
} else {
33-
logger.info(s"Create new state from SUT state with hash code ${sutState.hashCode()}")
31+
logger.info(s"Create new state from SUT state $sutState")
3432
val s = StateImpl(sutState)
3533
states += (sutState -> s)
3634
s
3735
}
3836
}
3937

40-
override def executeAction(from: State, a: Action, to: State): State = this.synchronized {
38+
override def executeAction(from: State, a: ActionIdentifier, to: State): State = this.synchronized {
4139
allExploredActions += a
4240
val old = actionExecutionTimes.get(a)
4341
old match {
@@ -48,16 +46,16 @@ class GuiStateMachineImpl extends GuiStateMachine with Serializable {
4846
to
4947
}
5048

51-
override def getAllStates: Map[SutState, State] = this.synchronized { states }
49+
override def getAllStates: Map[SutStateIdentifier, State] = this.synchronized { states }
5250

53-
override def getAllExploredActions: Set[Action] = this.synchronized { allExploredActions }
51+
override def getAllExploredActions: Set[ActionIdentifier] = this.synchronized { allExploredActions }
5452

55-
override def getActionExecutionTimes: Map[Action, Int] = this.synchronized { actionExecutionTimes }
53+
override def getActionExecutionTimes: Map[ActionIdentifier, Int] = this.synchronized { actionExecutionTimes }
5654

5755
override def clear(): Unit = this.synchronized {
58-
states = new HashMap[SutState, State]
59-
allExploredActions = new HashSet[Action]
60-
actionExecutionTimes = new HashMap[Action, Int]
56+
states = new HashMap[SutStateIdentifier, State]
57+
allExploredActions = new HashSet[ActionIdentifier]
58+
actionExecutionTimes = new HashMap[ActionIdentifier, Int]
6159
}
6260

6361
override def assignFrom(other: GuiStateMachine): Unit = this.synchronized {

src/main/scala/de/retest/guistatemachine/api/impl/StateImpl.scala

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,28 @@
11
package de.retest.guistatemachine.api.impl
22

3-
import de.retest.guistatemachine.api.{ActionTransitions, State}
4-
import de.retest.recheck.ui.descriptors.SutState
5-
import de.retest.surili.commons.actions.Action
3+
import de.retest.guistatemachine.api.{ActionIdentifier, ActionTransitions, State, SutStateIdentifier}
64

75
import scala.collection.immutable.HashMap
86

97
@SerialVersionUID(1L)
10-
case class StateImpl(sutState: SutState) extends State with Serializable {
8+
case class StateImpl(sutState: SutStateIdentifier) extends State with Serializable {
119

1210
/**
1311
* Currently, there is no MultiMap trait for immutable maps in the Scala standard library.
1412
* The legacy code used `AmbigueState` here which was more complicated than just a multi map.
1513
*/
16-
private var outgoingActionTransitions = HashMap[Action, ActionTransitions]()
14+
private var outgoingActionTransitions = HashMap[ActionIdentifier, ActionTransitions]()
1715

1816
/**
1917
* Redundant information but helpful to be retrieved.
2018
*/
21-
private var incomingActionTransitions = HashMap[Action, ActionTransitions]()
19+
private var incomingActionTransitions = HashMap[ActionIdentifier, ActionTransitions]()
2220

23-
override def getSutState: SutState = this.synchronized { sutState }
24-
override def getOutgoingActionTransitions: Map[Action, ActionTransitions] = this.synchronized { outgoingActionTransitions }
25-
override def getIncomingActionTransitions: Map[Action, ActionTransitions] = this.synchronized { incomingActionTransitions }
21+
override def getSutStateIdentifier: SutStateIdentifier = this.synchronized { sutState }
22+
override def getOutgoingActionTransitions: Map[ActionIdentifier, ActionTransitions] = this.synchronized { outgoingActionTransitions }
23+
override def getIncomingActionTransitions: Map[ActionIdentifier, ActionTransitions] = this.synchronized { incomingActionTransitions }
2624

27-
private[api] override def addTransition(a: Action, to: State): Int = {
25+
private[api] override def addTransition(a: ActionIdentifier, to: State): Int = {
2826
val executionCounter = this.synchronized {
2927
outgoingActionTransitions.get(a) match {
3028
case Some(oldTransitions) =>
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package de.retest.guistatemachine.api.impl.serialization
2-
import de.retest.recheck.ui.descriptors.SutState
3-
import de.retest.surili.commons.actions.Action
2+
import de.retest.guistatemachine.api.{ActionIdentifier, SutStateIdentifier}
43

5-
case class GraphActionEdge(from: SutState, to: SutState, action: Action) {
4+
case class GraphActionEdge(from: SutStateIdentifier, to: SutStateIdentifier, action: ActionIdentifier) {
65
override def toString: String = action.toString
76
}

src/main/scala/de/retest/guistatemachine/api/impl/serialization/GraphicsProvider.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ package de.retest.guistatemachine.api.impl.serialization
33
import java.awt.Color
44

55
import com.github.systemdir.gml.model.{EdgeGraphicDefinition, GraphicDefinition, NodeGraphicDefinition, YedGmlGraphicsProvider}
6-
import de.retest.recheck.ui.descriptors.SutState
6+
import de.retest.guistatemachine.api.SutStateIdentifier
77

8-
class GraphicsProvider extends YedGmlGraphicsProvider[SutState, GraphActionEdge, AnyRef] {
9-
override def getVertexGraphics(vertex: SutState): NodeGraphicDefinition =
8+
class GraphicsProvider extends YedGmlGraphicsProvider[SutStateIdentifier, GraphActionEdge, AnyRef] {
9+
override def getVertexGraphics(vertex: SutStateIdentifier): NodeGraphicDefinition =
1010
new NodeGraphicDefinition.Builder().setFill(Color.LIGHT_GRAY).setLineColor(Color.black).setFontStyle(GraphicDefinition.FontStyle.ITALIC).build
11-
override def getEdgeGraphics(edge: GraphActionEdge, edgeSource: SutState, edgeTarget: SutState): EdgeGraphicDefinition =
11+
override def getEdgeGraphics(edge: GraphActionEdge, edgeSource: SutStateIdentifier, edgeTarget: SutStateIdentifier): EdgeGraphicDefinition =
1212
new EdgeGraphicDefinition.Builder()
1313
.setTargetArrow(EdgeGraphicDefinition.ArrowType.SHORT_ARROW)
1414
.setLineType(GraphicDefinition.LineType.DASHED)
1515
.build
16-
override def getGroupGraphics(group: AnyRef, groupElements: java.util.Set[SutState]): NodeGraphicDefinition = null
16+
override def getGroupGraphics(group: AnyRef, groupElements: java.util.Set[SutStateIdentifier]): NodeGraphicDefinition = null
1717
}

src/main/scala/de/retest/guistatemachine/api/impl/serialization/GuiStateMachinGMLSerializer.scala

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ package de.retest.guistatemachine.api.impl.serialization
22
import java.io.{BufferedWriter, File, FileOutputStream, OutputStreamWriter}
33

44
import com.github.systemdir.gml.YedGmlWriter
5-
import de.retest.guistatemachine.api.{GuiStateMachine, GuiStateMachineSerializer}
6-
import de.retest.recheck.ui.descriptors.SutState
5+
import de.retest.guistatemachine.api.{GuiStateMachine, GuiStateMachineSerializer, SutStateIdentifier}
76
import org.jgrapht.graph.DirectedPseudograph
87

98
class GuiStateMachinGMLSerializer(guiStateMachine: GuiStateMachine) extends GuiStateMachineSerializer {
109

11-
type GraphType = DirectedPseudograph[SutState, GraphActionEdge]
10+
type GraphType = DirectedPseudograph[SutStateIdentifier, GraphActionEdge]
1211

1312
/**
1413
* Converts the state machines into GML which can be read by editors like yED.
@@ -25,9 +24,9 @@ class GuiStateMachinGMLSerializer(guiStateMachine: GuiStateMachine) extends GuiS
2524

2625
// get the gml writer
2726
val writer =
28-
new YedGmlWriter.Builder[SutState, GraphActionEdge, AnyRef](graphicsProvider, YedGmlWriter.PRINT_LABELS: _*)
27+
new YedGmlWriter.Builder[SutStateIdentifier, GraphActionEdge, AnyRef](graphicsProvider, YedGmlWriter.PRINT_LABELS: _*)
2928
.setEdgeLabelProvider(_.toString)
30-
.setVertexLabelProvider(sutState => "%s - hash code: %d".format(sutState.toString, sutState.hashCode()))
29+
.setVertexLabelProvider(_.toString)
3130
.build
3231

3332
// write to file
@@ -54,7 +53,7 @@ class GuiStateMachinGMLSerializer(guiStateMachine: GuiStateMachine) extends GuiS
5453
val actionTransitions = transition._2
5554
val action = transition._1
5655
actionTransitions.states.foreach { toState =>
57-
val toVertex = toState.getSutState
56+
val toVertex = toState.getSutStateIdentifier
5857
val edge = GraphActionEdge(fromVertex, toVertex, action)
5958
if (!graph.addEdge(fromVertex, toVertex, edge)) { throw new RuntimeException(s"Failed to add edge $edge") }
6059
}

0 commit comments

Comments
 (0)