This repository has been archived by the owner on Mar 12, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use an embedded database and the Java API.
- Loading branch information
Showing
9 changed files
with
308 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
src/main/scala/de/retest/guistatemachine/api/neo4j/Example.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package de.retest.guistatemachine.api.neo4j | ||
import java.util.Arrays | ||
|
||
import de.retest.guistatemachine.api.GuiStateMachineApi | ||
import de.retest.recheck.ui.descriptors._ | ||
import de.retest.recheck.ui.image.Screenshot | ||
import de.retest.surili.commons.actions.NavigateToAction | ||
|
||
// TODO #19 Replace this example with unit tests when everything works. | ||
object Example extends App { | ||
private val rootElementA = getRootElement("a", 0) | ||
private val rootElementB = getRootElement("b", 0) | ||
private val rootElementC = getRootElement("c", 0) | ||
private val action0 = new NavigateToAction("http://google.com") | ||
private val action1 = new NavigateToAction("http://wikipedia.org") | ||
|
||
val stateMachine = GuiStateMachineApi.neo4j.createStateMachine("tmp") | ||
val startState = new SutState(Arrays.asList(rootElementA, rootElementB, rootElementC)) | ||
val endState = new SutState(Arrays.asList(rootElementA)) | ||
stateMachine.executeAction(startState, action0, endState) | ||
|
||
/** | ||
* Creates a new identifying attributes collection which should only match other identifying attributes with the same ID. | ||
* | ||
* @param id The ID is used as value for different attributes. | ||
* @return The identifying attributes. | ||
*/ | ||
def getIdentifyingAttributes(id: String): IdentifyingAttributes = | ||
new IdentifyingAttributes(Arrays.asList(new StringAttribute("a", id), new StringAttribute("b", id), new StringAttribute("c", id))) | ||
|
||
/** | ||
* The identifying attributes and the contained components specify the equality. | ||
* | ||
* @param id This value is a criteria for equality of the returned element. | ||
* @param numberOfContainedComponents This value is a criteria for equality of the returned element. | ||
* @return A new root element which is equal to itself but not to any other root element. | ||
*/ | ||
def getRootElement(id: String, numberOfContainedComponents: Int): RootElement = { | ||
val r = new RootElement( | ||
"retestId", | ||
getIdentifyingAttributes(id), | ||
new Attributes(), | ||
new Screenshot("prefix", Array(1, 2, 3), Screenshot.ImageType.PNG), | ||
"screen0", | ||
0, | ||
"My Window" | ||
) | ||
if (numberOfContainedComponents > 0) { | ||
r.addChildren(scala.collection.JavaConverters.seqAsJavaList[Element](0 to numberOfContainedComponents map { _ => | ||
getRootElement("x", 0) | ||
})) | ||
} | ||
r | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
src/main/scala/de/retest/guistatemachine/api/neo4j/GuiStateMachineApiNeo4J.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package de.retest.guistatemachine.api.neo4j | ||
|
||
import java.io.File | ||
|
||
import com.typesafe.scalalogging.Logger | ||
import de.retest.guistatemachine.api.impl.GuiStateMachineImpl | ||
import de.retest.guistatemachine.api.{GuiStateMachine, GuiStateMachineApi} | ||
import org.neo4j.graphdb.GraphDatabaseService | ||
import org.neo4j.graphdb.factory.GraphDatabaseFactory | ||
|
||
import scala.collection.concurrent.TrieMap | ||
|
||
class GuiStateMachineApiNeo4J extends GuiStateMachineApi { | ||
private val logger = Logger[GuiStateMachineImpl] | ||
private val stateMachines = TrieMap[String, GuiStateMachine]() | ||
// TODO #19 Load existing state machines from the disk. | ||
|
||
override def createStateMachine(name: String): GuiStateMachine = { | ||
var dir = new File(name) | ||
var graphDb = new GraphDatabaseFactory().newEmbeddedDatabase(dir) | ||
registerShutdownHook(graphDb) | ||
|
||
logger.info("Created new graph DB in {}.", dir.getAbsolutePath) | ||
|
||
val guiStateMachine = new GuiStateMachineNeo4J(graphDb) | ||
stateMachines += (name -> guiStateMachine) | ||
guiStateMachine | ||
} | ||
|
||
override def removeStateMachine(name: String): Boolean = stateMachines.remove(name).isDefined // TODO #19 Remove from disk? | ||
|
||
override def getStateMachine(name: String): Option[GuiStateMachine] = stateMachines.get(name) | ||
|
||
override def clear(): Unit = stateMachines.clear() | ||
|
||
private def registerShutdownHook(graphDb: GraphDatabaseService): Unit = { // Registers a shutdown hook for the Neo4j instance so that it | ||
// shuts down nicely when the VM exits (even if you "Ctrl-C" the | ||
// running application). | ||
Runtime.getRuntime.addShutdownHook(new Thread() { override def run(): Unit = { graphDb.shutdown() } }) | ||
} | ||
} |
91 changes: 91 additions & 0 deletions
91
src/main/scala/de/retest/guistatemachine/api/neo4j/GuiStateMachineNeo4J.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package de.retest.guistatemachine.api.neo4j | ||
|
||
import de.retest.guistatemachine.api.{GuiStateMachine, State} | ||
import de.retest.recheck.ui.descriptors.SutState | ||
import de.retest.surili.commons.actions.Action | ||
import org.neo4j.graphdb.{GraphDatabaseService, Node, Transaction} | ||
|
||
import scala.collection.immutable.HashMap | ||
|
||
class GuiStateMachineNeo4J(var graphDb: GraphDatabaseService) extends GuiStateMachine { | ||
|
||
override def getState(sutState: SutState): State = { | ||
var tx: Option[Transaction] = None | ||
try { | ||
tx = Some(graphDb.beginTx) | ||
getNodeBySutState(sutState) match { | ||
case None => { | ||
val node = graphDb.createNode | ||
// TODO #19 SutState is not a supported property value! | ||
node.setProperty("sutState", sutState) | ||
} | ||
case _ => | ||
} | ||
tx.get.success | ||
} finally { | ||
if (tx.isDefined) { tx.get.close() } | ||
} | ||
|
||
new StateNeo4J(sutState, this) | ||
} | ||
|
||
override def executeAction(from: State, a: Action, to: State): State = { | ||
from.addTransition(a, to) | ||
to | ||
} | ||
|
||
override def getAllStates: Map[SutState, State] = { | ||
var tx: Option[Transaction] = None | ||
val allNodes = try { | ||
tx = Some(graphDb.beginTx) | ||
val allNodes = graphDb.getAllNodes() | ||
tx.get.success() | ||
allNodes | ||
} finally { | ||
if (tx.isDefined) { tx.get.close() } | ||
} | ||
|
||
var result = HashMap[SutState, State]() | ||
val iterator = allNodes.iterator() | ||
|
||
while (iterator.hasNext) { | ||
val node = iterator.next() | ||
val sutState = node.getProperty("sutState").asInstanceOf[SutState] | ||
result = result + (sutState -> new StateNeo4J(sutState, this)) | ||
} | ||
result | ||
} | ||
|
||
override def getAllExploredActions: Set[Action] = Set() // TODO #19 get all relationships in a transaction | ||
|
||
override def getActionExecutionTimes: Map[Action, Int] = Map() // TODO #19 get all execution time properties "counter" from all actions | ||
|
||
override def clear(): Unit = { | ||
var tx: Transaction = null | ||
try { | ||
tx = graphDb.beginTx | ||
// Deletes all nodes and relationships. | ||
graphDb.execute("MATCH (n)\nDETACH DELETE n") | ||
tx.success | ||
} finally { | ||
if (tx != null) { tx.close() } | ||
} | ||
} | ||
|
||
override def assignFrom(other: GuiStateMachine): Unit = { | ||
clear() | ||
val otherStateMachine = other.asInstanceOf[GuiStateMachineNeo4J] | ||
graphDb = otherStateMachine.graphDb | ||
} | ||
|
||
// TODO #19 Create an index on the property "sutState": https://neo4j.com/docs/cypher-manual/current/schema/index/#schema-index-create-a-single-property-index | ||
private[neo4j] def getNodeBySutState(sutState: SutState): Option[Node] = { | ||
val nodes = graphDb.findNodes(SutStateLabel, "sutState", sutState) | ||
val first = nodes.stream().findFirst() | ||
if (first.isPresent) { | ||
Some(first.get()) | ||
} else { | ||
None | ||
} | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
src/main/scala/de/retest/guistatemachine/api/neo4j/RelationshipTypeAction.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package de.retest.guistatemachine.api.neo4j | ||
|
||
import de.retest.surili.commons.actions.Action | ||
import org.neo4j.graphdb.RelationshipType | ||
|
||
case class RelationshipTypeAction(action: Action) extends RelationshipType { | ||
override def name() = action.toString | ||
} |
79 changes: 79 additions & 0 deletions
79
src/main/scala/de/retest/guistatemachine/api/neo4j/StateNeo4J.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package de.retest.guistatemachine.api.neo4j | ||
import de.retest.guistatemachine.api.{ActionTransitions, State} | ||
import de.retest.recheck.ui.descriptors.SutState | ||
import de.retest.surili.commons.actions.Action | ||
import org.neo4j.graphdb.{Direction, Node, Relationship, Transaction} | ||
|
||
import scala.collection.immutable.HashMap | ||
|
||
case class StateNeo4J(sutState: SutState, guiStateMachine: GuiStateMachineNeo4J) extends State { | ||
|
||
override def getSutState: SutState = sutState | ||
override def getTransitions: Map[Action, ActionTransitions] = { | ||
val node = getNode() | ||
val outgoingRelationships = node.getRelationships(Direction.OUTGOING) | ||
var result = HashMap[Action, ActionTransitions]() | ||
val iterator = outgoingRelationships.iterator() | ||
while (iterator.hasNext()) { | ||
val relationship = iterator.next() | ||
val relationshipTypeAction = relationship.getType.asInstanceOf[RelationshipTypeAction] | ||
val action = relationshipTypeAction.action | ||
val sutState = relationship.getEndNode.getProperty("sutState").asInstanceOf[SutState] | ||
val actionTransitions = if (result.contains(action)) { | ||
val existing = result.get(action).get | ||
ActionTransitions(existing.to ++ Set(new StateNeo4J(sutState, guiStateMachine)), existing.executionCounter + 1) | ||
} else { | ||
ActionTransitions(Set(new StateNeo4J(sutState, guiStateMachine)), 1) | ||
} | ||
result = result + (action -> actionTransitions) | ||
} | ||
result | ||
} | ||
|
||
private[api] override def addTransition(a: Action, to: State): Int = { | ||
var tx: Option[Transaction] = None | ||
try { | ||
tx = Some(guiStateMachine.graphDb.beginTx) | ||
val node = guiStateMachine.getNodeBySutState(sutState).get // TODO #19 What happens if the node is not found? | ||
val relationshipTypeAction = RelationshipTypeAction(a) | ||
val existingRelationships = node.getRelationships(relationshipTypeAction, Direction.OUTGOING) | ||
var existingRelationship: Option[Relationship] = None | ||
val iterator = existingRelationships.iterator() | ||
while (iterator.hasNext && existingRelationship.isEmpty) { | ||
val relationship = iterator.next() | ||
val sutState = relationship.getEndNode().getProperty("sutState").asInstanceOf[SutState] | ||
if (to.getSutState == sutState) { | ||
existingRelationship = Some(relationship) | ||
} | ||
} | ||
|
||
val counter = if (existingRelationship.isEmpty) { | ||
val other = guiStateMachine.getNodeBySutState(to.getSutState).get // TODO #19 What happens if the node is not found? | ||
node.createRelationshipTo(other, relationshipTypeAction) | ||
1 | ||
} else { | ||
val r = existingRelationship.get | ||
val counter = r.getProperty("counter").asInstanceOf[Int] + 1 | ||
existingRelationship.get.setProperty("counter", counter) | ||
counter | ||
} | ||
tx.get.success() | ||
counter | ||
} finally { | ||
if (tx.isDefined) { tx.get.close() } | ||
} | ||
} | ||
|
||
private def getNode(): Node = { | ||
var tx: Option[Transaction] = None | ||
|
||
try { | ||
tx = Some(guiStateMachine.graphDb.beginTx) | ||
val node = guiStateMachine.getNodeBySutState(sutState).get // TODO #19 What happens if the node is not found? | ||
tx.get.success() | ||
node | ||
} finally { | ||
if (tx.isDefined) { tx.get.close() } | ||
} | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
src/main/scala/de/retest/guistatemachine/api/neo4j/SutStateLabel.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package de.retest.guistatemachine.api.neo4j | ||
|
||
import org.neo4j.graphdb.Label | ||
|
||
object SutStateLabel extends Label { | ||
override def name() = "SutState" | ||
} |