From ad6216a9bf01cc3df90e5fd3d872cf1c4d5ea5db Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 15 Mar 2011 06:25:50 +0100 Subject: [PATCH 01/82] updated versions --- pom.xml | 326 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 176 insertions(+), 150 deletions(-) diff --git a/pom.xml b/pom.xml index 4b0ae5d..fdc8dd2 100644 --- a/pom.xml +++ b/pom.xml @@ -1,161 +1,187 @@ - - 4.0.0 - org.neo4j - neo4j-scala - bundle - Neo4j Scala - 0.9.9-SNAPSHOT - Scala wrapper for Neo4j Graph Database - http://github.com/jawher/neo4j-scala - 2009 + + 4.0.0 + org.neo4j + neo4j-scala + bundle + Neo4j Scala + 0.9.9-SNAPSHOT + Scala wrapper for Neo4j Graph Database + http://github.com/jawher/neo4j-scala + 2009 - - UTF-8 - 2.7.7 - 1.0 - + + UTF-8 + 2.8.1 + 1.1.1 + 1.1 + - - - jawher - Jawher Moussa - http://twitter.com/jawher - +1 - - - martin - Martin Kleppmann - http://twitter.com/martinkl - - + + + jawher + Jawher Moussa + http://twitter.com/jawher + +1 + + + martin + Martin Kleppmann + http://twitter.com/martinkl + + + FaKod + Christopher Schmidt + +1 + info [at] FaKod.EU + + developer + + + - - - The MIT License - http://www.opensource.org/licenses/mit-license.php - - + + + The MIT License + http://www.opensource.org/licenses/mit-license.php + + - - scm:git:git://github.com/jawher/neo4j-scala.git - scm:git:git://github.com/jawher/neo4j-scala.git - + + scm:git:git://github.com/jawher/neo4j-scala.git + scm:git:git://github.com/jawher/neo4j-scala.git + - - - scala-tools.org - Scala-Tools Maven2 Repository - http://scala-tools.org/repo-releases - - - neo4j-public-repository - http://m2.neo4j.org - - + + + scala-tools.org + Scala-Tools Maven2 Repository + http://scala-tools.org/repo-releases + + + neo4j-public-repository + http://m2.neo4j.org + + - - - scala-tools.org - Scala-Tools Maven2 Repository - http://scala-tools.org/repo-releases - - + + + scala-tools.org + Scala-Tools Maven2 Repository + http://scala-tools.org/repo-releases + + - - - org.scala-lang - scala-library - ${scala.version} - - - junit - junit - 4.7 - test - - - org.scala-tools.testing - specs - 1.6.1 - test - + + + + neo4j-scala.embedded.module + neo4j-scala Embedded Repository + file://${basedir}/maven-repo-neo4j-scala/releases + + + + neo4j-scala.embedded.module + neo4j-scala Embedded Repository + file://${basedir}/maven-repo-neo4j-scala/snapshot + + - - - org.neo4j - neo4j-kernel - ${neo4j.version} - - - org.neo4j - neo4j-shell - ${neo4j.version} - - + + + org.scala-lang + scala-library + ${scala.version} + + + junit + junit + 4.7 + test + + + org.scala-tools.testing + specs_2.8.1 + 1.6.7 + test + - - src/main/scala - src/test/scala - - - org.scala-tools - maven-scala-plugin - 2.12.2 - - - - compile - testCompile - - - - - ${project.build.sourceEncoding} - ${scala.version} - - -target:jvm-1.5 - - - - - org.apache.maven.plugins - maven-eclipse-plugin - - true - - ch.epfl.lamp.sdt.core.scalabuilder - - - ch.epfl.lamp.sdt.core.scalanature - - - org.eclipse.jdt.launching.JRE_CONTAINER - ch.epfl.lamp.sdt.launching.SCALA_CONTAINER - - - - - org.apache.felix - maven-bundle-plugin - true - - - org.neo4j.scala - - - - - - - - - org.scala-tools - maven-scala-plugin - - ${scala.version} - - - - + + + org.neo4j + neo4j-kernel + ${neo4j.version} + + + org.neo4j + neo4j-shell + ${neo4j.shell.version} + + + + + src/main/scala + src/test/scala + + + org.scala-tools + maven-scala-plugin + 2.14 + + + + compile + testCompile + + + + + ${project.build.sourceEncoding} + ${scala.version} + + -target:jvm-1.5 + + + + + org.apache.maven.plugins + maven-eclipse-plugin + + true + + ch.epfl.lamp.sdt.core.scalabuilder + + + ch.epfl.lamp.sdt.core.scalanature + + + org.eclipse.jdt.launching.JRE_CONTAINER + ch.epfl.lamp.sdt.launching.SCALA_CONTAINER + + + + + org.apache.felix + maven-bundle-plugin + true + + + org.neo4j.scala + + + + + + + + + org.scala-tools + maven-scala-plugin + + ${scala.version} + + + + From 58efee118fa76835dd0e63ac829f9b5cfa34cce9 Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 15 Mar 2011 18:11:48 +0100 Subject: [PATCH 02/82] update plugins --- pom.xml | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 99 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index fdc8dd2..855471f 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.neo4j neo4j-scala - bundle + jar Neo4j Scala 0.9.9-SNAPSHOT Scala wrapper for Neo4j Graph Database @@ -13,8 +13,9 @@ UTF-8 2.8.1 - 1.1.1 + 1.3.M03 1.1 + 0.5-SNAPSHOT @@ -113,6 +114,16 @@ neo4j-kernel ${neo4j.version} + + org.neo4j + neo4j-lucene-index + ${neo4j.version} + + org.neo4j neo4j-shell @@ -123,8 +134,88 @@ src/main/scala src/test/scala + + + + org.scala-tools + maven-scala-plugin + 2.14 + + + + scala-compile-first + process-resources + + add-source + compile + + + + scala-test-compile + process-test-resources + + testCompile + + + + + + + maven-compiler-plugin + 2.0.2 + + 1.6 + 1.6 + + + + compile + + compile + + + + + + + maven-surefire-plugin + 2.7.2 + + true + false + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.3.1 + + + false + + + + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + attach-sources + + jar + + + + + + + + + @@ -178,6 +269,7 @@ org.scala-tools maven-scala-plugin + 2.14 ${scala.version} From af30142c2dfcc6dd935249423c74dd47cfc86231 Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 16 Mar 2011 06:21:11 +0100 Subject: [PATCH 03/82] changed shell version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 855471f..46e37ba 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ UTF-8 2.8.1 1.3.M03 - 1.1 + 1.3.M03 0.5-SNAPSHOT From 36cc180e6275c365229106c39b8a07e064c98dd9 Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 16 Mar 2011 06:21:34 +0100 Subject: [PATCH 04/82] refactoring, comments and changed implicits --- .../scala/org/neo4j/scala/Neo4jWrapper.scala | 59 ++++-- .../org/neo4j/scala/Neo4jWrapperTest.scala | 193 ++++++++++-------- 2 files changed, 144 insertions(+), 108 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index fc60ce5..13eab39 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -18,11 +18,11 @@ import org.neo4j.graphdb._ */ trait Neo4jWrapper { - /** + /** * Execute instructions within a Neo4j transaction; rollback if exception is raised and * commit otherwise; and return the return value from the operation. */ - def execInNeo4j[T<:Any](operation: GraphDatabaseService => T)(implicit neo : GraphDatabaseService): T = { + def withTx[T <: Any](neo: GraphDatabaseService)(operation: GraphDatabaseService => T): T = { val tx = synchronized { neo.beginTx } @@ -35,49 +35,72 @@ trait Neo4jWrapper { } } + /** + * + */ + def createNode(implicit neo: GraphDatabaseService):Node = neo.createNode + + /** + * creates incoming and outgoing relationships + */ class NodeRelationshipMethods(node: Node) { def -->(relType: RelationshipType) = new OutgoingRelationshipBuilder(node, relType) - // Create incoming relationship - def <--(relType: RelationshipType) = new IncomingRelationshipBuilder(node, relType) } - // Half-way through building an outgoing relationship - class OutgoingRelationshipBuilder(fromNode: Node, relType: RelationshipType) { + /** + * Half-way through building an outgoing relationship + */ + private[scala] class OutgoingRelationshipBuilder(fromNode: Node, relType: RelationshipType) { + + /** + * + */ def -->(toNode: Node) = { fromNode.createRelationshipTo(toNode, relType) new NodeRelationshipMethods(toNode) } } - // Half-way through building an incoming relationship - class IncomingRelationshipBuilder(toNode: Node, relType: RelationshipType) { + /** + * Half-way through building an incoming relationship + */ + private[scala] class IncomingRelationshipBuilder(toNode: Node, relType: RelationshipType) { def <--(fromNode: Node) = { fromNode.createRelationshipTo(toNode, relType) new NodeRelationshipMethods(fromNode) } } - implicit def node2relationshipBuilder(node: Node) = new NodeRelationshipMethods(node) + /** + * + */ + class RichPropertyContainer(propertyContainer: PropertyContainer) { - implicit def string2RelationshipType(relType: String) = DynamicRelationshipType.withName(relType) + def apply(property: String): Option[Any] = + propertyContainer.hasProperty(property) match { + case true => Some(propertyContainer.getProperty(property)) + case _ => None + } - class RichPropertyContainer(propertyContainer: PropertyContainer) { - def apply(property: String) : Option[Any] = if(propertyContainer.hasProperty(property)) Some(propertyContainer.getProperty(property)) else None - def update(property: String, value: Any) : Unit = propertyContainer.setProperty(property, value) + def update(property: String, value: Any): Unit = propertyContainer.setProperty(property, value) } + implicit def node2relationshipBuilder(node: Node) = new NodeRelationshipMethods(node) + + implicit def string2RelationshipType(relType: String) = DynamicRelationshipType.withName(relType) + implicit def propertyContainer2RichPropertyContainer(propertyContainer: PropertyContainer) = new RichPropertyContainer(propertyContainer) - implicit def fn2StopEvaluator(e : TraversalPosition => Boolean) = + implicit def fn2StopEvaluator(e: TraversalPosition => Boolean) = new StopEvaluator() { - def isStopNode(traversalPosition : TraversalPosition) = e(traversalPosition) + def isStopNode(traversalPosition: TraversalPosition) = e(traversalPosition) } - implicit def fn2ReturnableEvaluator(e : TraversalPosition => Boolean) = - new ReturnableEvaluator () { - def isReturnableNode(traversalPosition : TraversalPosition) = e(traversalPosition) + implicit def fn2ReturnableEvaluator(e: TraversalPosition => Boolean) = + new ReturnableEvaluator() { + def isReturnableNode(traversalPosition: TraversalPosition) = e(traversalPosition) } } diff --git a/src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala b/src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala index 463c8b9..a9c713f 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala +++ b/src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala @@ -12,147 +12,160 @@ class Neo4jWrapperSpecTest extends JUnit4(Neo4jWrapperSpec) object Neo4jWrapperSpec extends Specification with Neo4jWrapper { "NeoWrapper" should { shareVariables() - implicit val neo : GraphDatabaseService = new EmbeddedGraphDatabase("/tmp/temp-neo-test") - + val gds: GraphDatabaseService = new EmbeddedGraphDatabase("/tmp/temp-neo-test") + Runtime.getRuntime.addShutdownHook(new Thread() { - override def run() { - neo.shutdown - } - }) + override def run() { + gds.shutdown + } + }) "create a new relationship in --> relType --> notation" in { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode - val relType = DynamicRelationshipType.withName("foo") - start --> relType --> end - start.getSingleRelationship(relType, Direction.OUTGOING). - getOtherNode(start) must beEqualTo(end) + withTx(gds) { + implicit neo => + val start = createNode + val end = createNode + val relType = DynamicRelationshipType.withName("foo") + start --> relType --> end + start.getSingleRelationship(relType, Direction.OUTGOING). + getOtherNode(start) must beEqualTo(end) } } "create a new relationship in --> \"relName\" --> notation" in { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode - start --> "foo" --> end - start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). - getOtherNode(start) must beEqualTo(end) + withTx(gds) { + implicit neo => + val start = createNode + val end = createNode + start --> "foo" --> end + start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). + getOtherNode(start) must beEqualTo(end) } } "create a new relationship in <-- relType <-- notation" in { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode - val relType = DynamicRelationshipType.withName("foo") - end <-- relType <-- start - start.getSingleRelationship(relType, Direction.OUTGOING). - getOtherNode(start) must beEqualTo(end) + withTx(gds) { + implicit neo => + val start = createNode + val end = createNode + val relType = DynamicRelationshipType.withName("foo") + end <-- relType <-- start + start.getSingleRelationship(relType, Direction.OUTGOING). + getOtherNode(start) must beEqualTo(end) } } "create a new relationship in <-- \"relName\" <-- notation" in { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode - end <-- "foo" <-- start - start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). - getOtherNode(start) must beEqualTo(end) + withTx(gds) { + implicit neo => + val start = createNode + val end = createNode + end <-- "foo" <-- start + start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). + getOtherNode(start) must beEqualTo(end) } } "allow relationships of the same direction to be chained" in { - execInNeo4j { neo => - val start = neo.createNode - val middle = neo.createNode - val end = neo.createNode - start --> "foo" --> middle --> "bar" --> end - start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). - getOtherNode(start) must beEqualTo(middle) - middle.getSingleRelationship(DynamicRelationshipType.withName("bar"), Direction.OUTGOING). - getOtherNode(middle) must beEqualTo(end) + withTx(gds) { + implicit neo => + val start = createNode + val middle = createNode + val end = createNode + start --> "foo" --> middle --> "bar" --> end + start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). + getOtherNode(start) must beEqualTo(middle) + middle.getSingleRelationship(DynamicRelationshipType.withName("bar"), Direction.OUTGOING). + getOtherNode(middle) must beEqualTo(end) } } "allow relationships of different directions to be chained" in { - execInNeo4j { neo => - val left = neo.createNode - val middle = neo.createNode - val right = neo.createNode - left --> "foo" --> middle <-- "bar" <-- right - left.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). - getOtherNode(left) must beEqualTo(middle) - right.getSingleRelationship(DynamicRelationshipType.withName("bar"), Direction.OUTGOING). - getOtherNode(right) must beEqualTo(middle) + withTx(gds) { + implicit neo => + val left = createNode + val middle = createNode + val right = createNode + left --> "foo" --> middle <-- "bar" <-- right + left.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). + getOtherNode(left) must beEqualTo(middle) + right.getSingleRelationship(DynamicRelationshipType.withName("bar"), Direction.OUTGOING). + getOtherNode(right) must beEqualTo(middle) } } "ignore a relationshipBuilder with no end node" in { - execInNeo4j { neo => - val start = neo.createNode - start --> "foo" - start.getRelationships.iterator.hasNext must beEqualTo(false) + withTx(gds) { + implicit neo => + val start = createNode + start --> "foo" + start.getRelationships.iterator.hasNext must beEqualTo(false) } } "read a property in a node in node('property') notation" in { - execInNeo4j { neo => - val start = neo.createNode - start.setProperty("foo", "bar") - start("foo") must beEqualTo(Some("bar")) - start("bar") must beEqualTo(None) + withTx(gds) { + implicit neo => + val start = createNode + start.setProperty("foo", "bar") + start("foo") must beEqualTo(Some("bar")) + start("bar") must beEqualTo(None) } } "create a property in a node in node('property')=value notation" in { - execInNeo4j { neo => - val start = neo.createNode - start("foo") = "bar" - start.getProperty("foo") must beEqualTo("bar") + withTx(gds) { + implicit neo => + val start = createNode + start("foo") = "bar" + start.getProperty("foo") must beEqualTo("bar") } } "read a property in a relationship in rel('property') notation" in { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode - val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) - rel.setProperty("foo", "bar") - rel("foo") must beEqualTo(Some("bar")) - rel("bar") must beEqualTo(None) + withTx(gds) { + implicit neo => + val start = createNode + val end = createNode + val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) + rel.setProperty("foo", "bar") + rel("foo") must beEqualTo(Some("bar")) + rel("bar") must beEqualTo(None) } } "create a property in a relationship in rel('property')=value notation" in { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode - val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) - rel("foo") = "bar" - rel.getProperty("foo") must beEqualTo("bar") + withTx(gds) { + implicit neo => + val start = createNode + val end = createNode + val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) + rel("foo") = "bar" + rel.getProperty("foo") must beEqualTo("bar") } } "allow writing stop evaluators in a functional style" in { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode - val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) - val traverser = start.traverse(Traverser.Order.BREADTH_FIRST, (tp : TraversalPosition) => false, ReturnableEvaluator.ALL_BUT_START_NODE, DynamicRelationshipType.withName("foo"), Direction.OUTGOING) - traverser.iterator.hasNext must beEqualTo(true) - traverser.iterator.next must beEqualTo(end) + withTx(gds) { + implicit neo => + val start = createNode + val end = createNode + val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) + val traverser = start.traverse(Traverser.Order.BREADTH_FIRST, (tp: TraversalPosition) => false, ReturnableEvaluator.ALL_BUT_START_NODE, DynamicRelationshipType.withName("foo"), Direction.OUTGOING) + traverser.iterator.hasNext must beEqualTo(true) + traverser.iterator.next must beEqualTo(end) } } "allow writing returnable evaluators in a functional style" in { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode - val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) - val traverser = start.traverse(Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, (tp : TraversalPosition) => tp.notStartNode(), DynamicRelationshipType.withName("foo"), Direction.OUTGOING) - traverser.iterator.hasNext must beEqualTo(true) - traverser.iterator.next must beEqualTo(end) + withTx(gds) { + implicit neo => + val start = createNode + val end = createNode + val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) + val traverser = start.traverse(Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, (tp: TraversalPosition) => tp.notStartNode(), DynamicRelationshipType.withName("foo"), Direction.OUTGOING) + traverser.iterator.hasNext must beEqualTo(true) + traverser.iterator.next must beEqualTo(end) } } } From 2968b9294d7cbe170cf102cdbc6a97cc3199071c Mon Sep 17 00:00:00 2001 From: FaKod Date: Thu, 17 Mar 2011 16:46:46 +0100 Subject: [PATCH 05/82] 1st step of making it really really complex --- pom.xml | 50 +++++++++++++- .../scala/GraphDatabaseServiceProvider.scala | 51 ++++++++++++++ .../org/neo4j/scala/Neo4jSpatialWrapper.scala | 45 +++++++++++++ .../scala/org/neo4j/scala/Neo4jWrapper.scala | 10 +-- .../org/neo4j/scala/Neo4jSpatialTest.scala | 67 +++++++++++++++++++ .../org/neo4j/scala/Neo4jWrapperTest.scala | 34 +++++----- 6 files changed, 235 insertions(+), 22 deletions(-) create mode 100644 src/main/scala/org/neo4j/scala/GraphDatabaseServiceProvider.scala create mode 100644 src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala create mode 100644 src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala diff --git a/pom.xml b/pom.xml index 46e37ba..c9eae42 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ 1.3.M03 1.3.M03 0.5-SNAPSHOT + 2.7-M3 @@ -64,6 +65,11 @@ neo4j-public-repository http://m2.neo4j.org + + osgeo + Open Source Geospatial Foundation Repository + http://download.osgeo.org/webdav/geotools/ + @@ -119,16 +125,56 @@ neo4j-lucene-index ${neo4j.version} - + org.neo4j neo4j-shell ${neo4j.shell.version} + + + + org.geotools + gt-main + ${geotools.version} + + + + + + + + org.geotools + gt-shapefile + ${geotools.version} + + + + + + org.geotools + gt-render + ${geotools.version} + + + + + + it.geosolutions.imageio-ext + imageio-ext-tiff + + + diff --git a/src/main/scala/org/neo4j/scala/GraphDatabaseServiceProvider.scala b/src/main/scala/org/neo4j/scala/GraphDatabaseServiceProvider.scala new file mode 100644 index 0000000..21586d3 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/GraphDatabaseServiceProvider.scala @@ -0,0 +1,51 @@ +package org.neo4j.scala + +import org.neo4j.kernel.EmbeddedGraphDatabase +import org.neo4j.graphdb.GraphDatabaseService +import org.neo4j.gis.spatial.SpatialDatabaseService + +/** + * + * @author Christopher Schmidt + * Date: 16.03.11 + * Time: 16:27 + */ + +/** + * Interface to access the GraphDatabaseService + */ +trait DatabaseService { + def gds: GraphDatabaseService +} + +/** + * normal DatabaseService store for GraphDatabaseService + */ +case class DatabaseServiceImpl(gds: GraphDatabaseService) extends DatabaseService + +/** + * extended store for combined GraphDatabaseService and SpatialDatabaseService + * used by Neo4jSpatialWrapper + */ +case class CombinedDatabaseService(gds: GraphDatabaseService, sds: SpatialDatabaseService) extends DatabaseService + + +/** + * provides an embedded database service + */ +trait EmbeddedGraphDatabaseServiceProvider { + + def neo4jStoreDir: String + + val ds: DatabaseService = DatabaseServiceImpl(new EmbeddedGraphDatabase(neo4jStoreDir)) + +} + +/** + * provides an spatial database service from a given GraphDatabaseService + */ +trait SpatialDatabaseServiceProvider { + self: EmbeddedGraphDatabaseServiceProvider => + + val sds = new SpatialDatabaseService(ds.gds) +} \ No newline at end of file diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala new file mode 100644 index 0000000..ac88df4 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala @@ -0,0 +1,45 @@ +package org.neo4j.scala + +import org.neo4j.graphdb.GraphDatabaseService +import org.neo4j.gis.spatial.{EditableLayer, Listener, SpatialDatabaseService} + +/** + * + * @author Christopher Schmidt + * Date: 16.03.11 + * Time: 16:18 + */ +trait Neo4jSpatialWrapper extends Neo4jWrapper { + + def ds: DatabaseService + + val sds: SpatialDatabaseService + + /** + * Execute instructions within a Neo4j transaction; rollback if exception is raised and + * commit otherwise; and return the return value from the operation. + */ + def withSpatialTx[T <: Any](operation: CombinedDatabaseService => T): T = { + val tx = synchronized { + ds.gds.beginTx + } + try { + val ret = operation(CombinedDatabaseService(ds.gds, sds)) + tx.success + return ret + } finally { + tx.finish + } + } + + def deleteLayer(name: String, monitor: Listener)(implicit db: CombinedDatabaseService) = + db.sds.deleteLayer(name, monitor) + + def getOrCreateEditableLayer(name: String)(implicit db: CombinedDatabaseService): EditableLayer = + db.sds.getOrCreateEditableLayer(name) + + /** + * methods from Neo4jWrapper usage should still be possible + */ + implicit def databaseServiceToGraphDatabaseService(ds:CombinedDatabaseService):GraphDatabaseService = ds.gds +} \ No newline at end of file diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index 13eab39..4a9e8b1 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -18,16 +18,18 @@ import org.neo4j.graphdb._ */ trait Neo4jWrapper { + def ds: DatabaseService + /** * Execute instructions within a Neo4j transaction; rollback if exception is raised and * commit otherwise; and return the return value from the operation. */ - def withTx[T <: Any](neo: GraphDatabaseService)(operation: GraphDatabaseService => T): T = { + def withTx[T <: Any](operation: DatabaseService => T): T = { val tx = synchronized { - neo.beginTx + ds.gds.beginTx } try { - val ret = operation(neo) + val ret = operation(ds) tx.success return ret } finally { @@ -38,7 +40,7 @@ trait Neo4jWrapper { /** * */ - def createNode(implicit neo: GraphDatabaseService):Node = neo.createNode + def createNode(implicit ds: DatabaseService):Node = ds.gds.createNode /** * creates incoming and outgoing relationships diff --git a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala new file mode 100644 index 0000000..f36a2fd --- /dev/null +++ b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala @@ -0,0 +1,67 @@ +package org.neo4j.scala + +import org.neo4j.kernel.EmbeddedGraphDatabase +import org.specs.runner.JUnit4 +import org.specs.Specification +import com.vividsolutions.jts.geom.{Coordinate, Envelope} +import org.neo4j.gis.spatial.query.{SearchWithin, SearchContain} +import collection.JavaConversions.asScalaBuffer +import collection.mutable.Buffer +import org.neo4j.gis.spatial.{NullListener, SpatialDatabaseRecord, EditableLayer} +import org.neo4j.graphdb.{Direction, DynamicRelationshipType, GraphDatabaseService} + +class Neo4jSpatialSpecTest extends JUnit4(Neo4jSpatialSpec) + +object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with EmbeddedGraphDatabaseServiceProvider with SpatialDatabaseServiceProvider { + + def neo4jStoreDir = "/tmp/temp-neo-spatial-test" + + "NeoWrapper" should { + shareVariables() + + Runtime.getRuntime.addShutdownHook(new Thread() { + override def run() { + ds.gds.shutdown + } + }) + + "Wrapper usage should be possible" in { + + withSpatialTx { + implicit db => + + val start = createNode + val end = createNode + val relType = DynamicRelationshipType.withName("foo") + start --> relType --> end + start.getSingleRelationship(relType, Direction.OUTGOING). + getOtherNode(start) must beEqualTo(end) + } + } + + "blah blah" in { + + withSpatialTx { + implicit db => + + deleteLayer("test", new NullListener) + val layer: EditableLayer = getOrCreateEditableLayer("test") + + val record = layer.add(layer.getGeometryFactory.createPoint(new Coordinate(15.3, 56.2))) + val searchQuery = new SearchContain(layer.getGeometryFactory().toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0))) + layer.getIndex.executeSearch(searchQuery) + + var results: Buffer[SpatialDatabaseRecord] = searchQuery.getResults + + results.size must_== 0 + + val withinQuery = new SearchWithin(layer.getGeometryFactory().toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0))) + layer.getIndex().executeSearch(withinQuery) + results = withinQuery.getResults + + results.size must_== 1 + } + + } + } +} \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala b/src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala index a9c713f..0664dbb 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala +++ b/src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala @@ -9,19 +9,21 @@ import org.neo4j.kernel.EmbeddedGraphDatabase class Neo4jWrapperSpecTest extends JUnit4(Neo4jWrapperSpec) -object Neo4jWrapperSpec extends Specification with Neo4jWrapper { +object Neo4jWrapperSpec extends Specification with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider { + + def neo4jStoreDir = "/tmp/temp-neo-test" + "NeoWrapper" should { shareVariables() - val gds: GraphDatabaseService = new EmbeddedGraphDatabase("/tmp/temp-neo-test") Runtime.getRuntime.addShutdownHook(new Thread() { override def run() { - gds.shutdown + ds.gds.shutdown } }) "create a new relationship in --> relType --> notation" in { - withTx(gds) { + withTx { implicit neo => val start = createNode val end = createNode @@ -33,7 +35,7 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "create a new relationship in --> \"relName\" --> notation" in { - withTx(gds) { + withTx { implicit neo => val start = createNode val end = createNode @@ -44,7 +46,7 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "create a new relationship in <-- relType <-- notation" in { - withTx(gds) { + withTx { implicit neo => val start = createNode val end = createNode @@ -56,7 +58,7 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "create a new relationship in <-- \"relName\" <-- notation" in { - withTx(gds) { + withTx { implicit neo => val start = createNode val end = createNode @@ -67,7 +69,7 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "allow relationships of the same direction to be chained" in { - withTx(gds) { + withTx { implicit neo => val start = createNode val middle = createNode @@ -81,7 +83,7 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "allow relationships of different directions to be chained" in { - withTx(gds) { + withTx { implicit neo => val left = createNode val middle = createNode @@ -95,7 +97,7 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "ignore a relationshipBuilder with no end node" in { - withTx(gds) { + withTx { implicit neo => val start = createNode start --> "foo" @@ -104,7 +106,7 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "read a property in a node in node('property') notation" in { - withTx(gds) { + withTx { implicit neo => val start = createNode start.setProperty("foo", "bar") @@ -114,7 +116,7 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "create a property in a node in node('property')=value notation" in { - withTx(gds) { + withTx { implicit neo => val start = createNode start("foo") = "bar" @@ -123,7 +125,7 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "read a property in a relationship in rel('property') notation" in { - withTx(gds) { + withTx { implicit neo => val start = createNode val end = createNode @@ -135,7 +137,7 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "create a property in a relationship in rel('property')=value notation" in { - withTx(gds) { + withTx { implicit neo => val start = createNode val end = createNode @@ -146,7 +148,7 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "allow writing stop evaluators in a functional style" in { - withTx(gds) { + withTx { implicit neo => val start = createNode val end = createNode @@ -158,7 +160,7 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "allow writing returnable evaluators in a functional style" in { - withTx(gds) { + withTx { implicit neo => val start = createNode val end = createNode From 8dd796387467f0f000e43eca0f925d1fab14d1ca Mon Sep 17 00:00:00 2001 From: FaKod Date: Sun, 20 Mar 2011 11:19:53 +0100 Subject: [PATCH 06/82] added some convenience implicits/objects and tests for them --- .../org/neo4j/scala/Neo4jSpatialWrapper.scala | 77 ++++++++++++++++++- .../org/neo4j/scala/Neo4jSpatialTest.scala | 50 ++++++++++-- 2 files changed, 116 insertions(+), 11 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala index ac88df4..85ec598 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala @@ -1,7 +1,10 @@ package org.neo4j.scala -import org.neo4j.graphdb.GraphDatabaseService -import org.neo4j.gis.spatial.{EditableLayer, Listener, SpatialDatabaseService} +import org.neo4j.gis.spatial._ +import collection.mutable.Buffer +import com.vividsolutions.jts.geom.impl.CoordinateArraySequence +import com.vividsolutions.jts.geom._ +import org.neo4j.graphdb.{Node, GraphDatabaseService} /** * @@ -32,6 +35,18 @@ trait Neo4jSpatialWrapper extends Neo4jWrapper { } } + /** + * retrieves the layer object and executes operation + */ + def withLayer[T <: Any](getLayer: => EditableLayer)(operation: EditableLayer => T): T = { + val layer = getLayer + operation(layer) + } + + /** + * DatabaseService Wrapper + */ + def deleteLayer(name: String, monitor: Listener)(implicit db: CombinedDatabaseService) = db.sds.deleteLayer(name, monitor) @@ -41,5 +56,61 @@ trait Neo4jSpatialWrapper extends Neo4jWrapper { /** * methods from Neo4jWrapper usage should still be possible */ - implicit def databaseServiceToGraphDatabaseService(ds:CombinedDatabaseService):GraphDatabaseService = ds.gds + implicit def databaseServiceToGraphDatabaseService(ds: CombinedDatabaseService): GraphDatabaseService = ds.gds + + /** + * Layer Wrapper + */ + + implicit def tupleToCoordinate(t: (Double, Double)): Coordinate = Coord(t._1, t._2) + + def getGeometryFactory(implicit layer: EditableLayer) = layer.getGeometryFactory + + def add(implicit layer: EditableLayer) = new AddGeometry(layer) + + class AddGeometry(layer: EditableLayer) { + val gf = layer.getGeometryFactory + + def newPoint(coordinate: Coordinate): SpatialDatabaseRecord = layer.add(gf.createPoint(coordinate)) + + def newPolygon(shell: LinearRing, holes: Array[LinearRing] = null) = layer.add(gf.createPolygon(shell, holes)) + } + + /** + * Database Record convenience defs + */ + + // converts SpatialDatabaseRecord to Node + implicit def spatialDatabaseRecordToNode(sdr: SpatialDatabaseRecord): Node = sdr.getGeomNode + + // delegation to Neo4jWrapper + implicit def node2relationshipBuilder(sdr: SpatialDatabaseRecord) = new NodeRelationshipMethods(sdr.getGeomNode) +} + +/** + * convenience object of handling Coordinates + */ +object Coord { + def apply(x: Double, y: Double) = new Coordinate(x, y) +} + +/** + * convenience object for CoordinateArraySequence + */ +object CoordArraySequence { + def apply(b: Buffer[(Double, Double)]) = { + val a = for (t <- b; c = Coord(t._1, t._2)) yield c + new CoordinateArraySequence(a.toArray) + } +} + +/** + * convenience object for LinearRing + */ +object LinRing { + def apply(cs: CoordinateSequence)(implicit layer: EditableLayer) = + new LinearRing(cs, layer.getGeometryFactory) + + def apply(b: Buffer[(Double, Double)])(implicit layer: EditableLayer) = + new LinearRing(CoordArraySequence(b), layer.getGeometryFactory) } \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala index f36a2fd..6f46eec 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala +++ b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala @@ -1,14 +1,16 @@ package org.neo4j.scala -import org.neo4j.kernel.EmbeddedGraphDatabase import org.specs.runner.JUnit4 import org.specs.Specification -import com.vividsolutions.jts.geom.{Coordinate, Envelope} import org.neo4j.gis.spatial.query.{SearchWithin, SearchContain} import collection.JavaConversions.asScalaBuffer import collection.mutable.Buffer -import org.neo4j.gis.spatial.{NullListener, SpatialDatabaseRecord, EditableLayer} -import org.neo4j.graphdb.{Direction, DynamicRelationshipType, GraphDatabaseService} +import org.neo4j.gis.spatial.{NullListener, SpatialDatabaseRecord} +import java.util.ArrayList +import com.vividsolutions.jts.geom.{LinearRing, Coordinate, Envelope} +import com.vividsolutions.jts.geom.impl.CoordinateArraySequence +import collection.JavaConversions._ +import org.neo4j.graphdb.{Node, RelationshipType, Direction, DynamicRelationshipType} class Neo4jSpatialSpecTest extends JUnit4(Neo4jSpatialSpec) @@ -39,15 +41,47 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe } } - "blah blah" in { + "interim test all" in { withSpatialTx { implicit db => + val cities = createNode + deleteLayer("test", new NullListener) - val layer: EditableLayer = getOrCreateEditableLayer("test") + val layer = getOrCreateEditableLayer("test") + val gf = layer.getGeometryFactory + + + + withLayer(getOrCreateEditableLayer("test")) { + implicit layer => + + // adding Point + val munich = add newPoint ((15.3, 56.2)) + munich.setProperty("City", "Munich") + cities --> "isCity" --> munich + + // adding new Polygon + val buf = Buffer((15.3, 56.2), (15.4, 56.3), (15.5, 56.4), (15.6, 56.5), (15.3, 56.2)) + val bayern = add newPolygon (LinRing(buf)) + + bayern.setProperty("FederalState", "Bayern") + munich --> "CapitalCityOf" --> bayern + + val relation = munich.getSingleRelationship("CapitalCityOf", Direction.OUTGOING) + + relation.getStartNode must beEqual(spatialDatabaseRecordToNode(munich)) + relation.getEndNode must beEqual(spatialDatabaseRecordToNode(bayern)) + + for (r <- bayern.getRelationships) + println(r.getType) + + for (r <- munich.getRelationships) + println(r.getType) + } - val record = layer.add(layer.getGeometryFactory.createPoint(new Coordinate(15.3, 56.2))) + // search val searchQuery = new SearchContain(layer.getGeometryFactory().toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0))) layer.getIndex.executeSearch(searchQuery) @@ -59,7 +93,7 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe layer.getIndex().executeSearch(withinQuery) results = withinQuery.getResults - results.size must_== 1 + results.size must_== 2 } } From 09c24589e13cf1b88bce0c6a8465ed31391951b0 Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 21 Mar 2011 11:28:19 +0100 Subject: [PATCH 07/82] added spatial query convenience methods --- .../org/neo4j/scala/Neo4jSpatialWrapper.scala | 8 +++++ .../org/neo4j/scala/Neo4jSpatialTest.scala | 33 +++++++------------ 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala index 85ec598..b961aef 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala @@ -66,6 +66,10 @@ trait Neo4jSpatialWrapper extends Neo4jWrapper { def getGeometryFactory(implicit layer: EditableLayer) = layer.getGeometryFactory + def toGeometry(envelope: Envelope)(implicit layer: EditableLayer): Geometry = getGeometryFactory.toGeometry(envelope) + + def executeSearch(search: Search)(implicit layer: EditableLayer): Unit = layer.getIndex.executeSearch(search) + def add(implicit layer: EditableLayer) = new AddGeometry(layer) class AddGeometry(layer: EditableLayer) { @@ -85,6 +89,10 @@ trait Neo4jSpatialWrapper extends Neo4jWrapper { // delegation to Neo4jWrapper implicit def node2relationshipBuilder(sdr: SpatialDatabaseRecord) = new NodeRelationshipMethods(sdr.getGeomNode) + + implicit def nodeToSpatialDatabaseRecord(node: Node)(implicit layer: Layer): SpatialDatabaseRecord = + new SpatialDatabaseRecord(layer, node) + } /** diff --git a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala index 6f46eec..2fe09ac 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala +++ b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala @@ -63,37 +63,26 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe cities --> "isCity" --> munich // adding new Polygon - val buf = Buffer((15.3, 56.2), (15.4, 56.3), (15.5, 56.4), (15.6, 56.5), (15.3, 56.2)) - val bayern = add newPolygon (LinRing(buf)) + val bayernBuffer = Buffer[(Double,Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) + val bayern = add newPolygon (LinRing(bayernBuffer)) bayern.setProperty("FederalState", "Bayern") munich --> "CapitalCityOf" --> bayern - val relation = munich.getSingleRelationship("CapitalCityOf", Direction.OUTGOING) + val searchBayern = new SearchWithin(bayern.getGeometry) + executeSearch(searchBayern) + for(r <- searchBayern.getResults) + r.getProperty("City") must beEqual("Munich") - relation.getStartNode must beEqual(spatialDatabaseRecordToNode(munich)) - relation.getEndNode must beEqual(spatialDatabaseRecordToNode(bayern)) + // search + val withinQuery = new SearchWithin(toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0))) + executeSearch(withinQuery) + val results = withinQuery.getResults - for (r <- bayern.getRelationships) - println(r.getType) - - for (r <- munich.getRelationships) - println(r.getType) + results.size must_== 2 } - // search - val searchQuery = new SearchContain(layer.getGeometryFactory().toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0))) - layer.getIndex.executeSearch(searchQuery) - - var results: Buffer[SpatialDatabaseRecord] = searchQuery.getResults - - results.size must_== 0 - - val withinQuery = new SearchWithin(layer.getGeometryFactory().toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0))) - layer.getIndex().executeSearch(withinQuery) - results = withinQuery.getResults - results.size must_== 2 } } From b1d6505016bab5f7e0d240fee15985df56096f24 Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 21 Mar 2011 18:19:37 +0100 Subject: [PATCH 08/82] added searchWithin convenience methods --- .../org/neo4j/scala/Neo4jSpatialWrapper.scala | 16 +++++++++++- .../org/neo4j/scala/Neo4jSpatialTest.scala | 25 ++++++++++--------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala index b961aef..02e8c24 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala @@ -5,6 +5,7 @@ import collection.mutable.Buffer import com.vividsolutions.jts.geom.impl.CoordinateArraySequence import com.vividsolutions.jts.geom._ import org.neo4j.graphdb.{Node, GraphDatabaseService} +import query.SearchWithin /** * @@ -68,7 +69,7 @@ trait Neo4jSpatialWrapper extends Neo4jWrapper { def toGeometry(envelope: Envelope)(implicit layer: EditableLayer): Geometry = getGeometryFactory.toGeometry(envelope) - def executeSearch(search: Search)(implicit layer: EditableLayer): Unit = layer.getIndex.executeSearch(search) + //def executeSearch(search: Search)(implicit layer: EditableLayer): Unit = layer.getIndex.executeSearch(search) def add(implicit layer: EditableLayer) = new AddGeometry(layer) @@ -93,6 +94,19 @@ trait Neo4jSpatialWrapper extends Neo4jWrapper { implicit def nodeToSpatialDatabaseRecord(node: Node)(implicit layer: Layer): SpatialDatabaseRecord = new SpatialDatabaseRecord(layer, node) + /** + * Search convenience defs + */ + + def withSearchWithin[T <: Any](geometry: Geometry)(operation: (SearchWithin) => T): T = { + val search = new SearchWithin(geometry) + operation(search) + } + + def executeSearch(implicit search:SearchWithin, layer: EditableLayer) = layer.getIndex.executeSearch(search) + + def getResults(implicit search:SearchWithin) = search.getResults + } /** diff --git a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala index 2fe09ac..a5ab845 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala +++ b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala @@ -63,23 +63,24 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe cities --> "isCity" --> munich // adding new Polygon - val bayernBuffer = Buffer[(Double,Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) + val bayernBuffer = Buffer[(Double, Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) val bayern = add newPolygon (LinRing(bayernBuffer)) bayern.setProperty("FederalState", "Bayern") munich --> "CapitalCityOf" --> bayern - val searchBayern = new SearchWithin(bayern.getGeometry) - executeSearch(searchBayern) - for(r <- searchBayern.getResults) - r.getProperty("City") must beEqual("Munich") - - // search - val withinQuery = new SearchWithin(toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0))) - executeSearch(withinQuery) - val results = withinQuery.getResults - - results.size must_== 2 + withSearchWithin(bayern.getGeometry) { + implicit s => + executeSearch + for (r <- getResults) + r.getProperty("City") must beEqual("Munich") + } + + withSearchWithin(toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0))) { + implicit s => + executeSearch + getResults.size must_== 2 + } } From 0911694a7336b9f6c780e21ff5254a9e7c847eef Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 22 Mar 2011 16:40:44 +0100 Subject: [PATCH 09/82] using node pattern (http://wiki.neo4j.org/content/Design_Guide) --- .../org/neo4j/scala/Neo4jSpatialWrapper.scala | 12 ++- .../scala/org/neo4j/scala/TestNodes.scala | 75 +++++++++++++++++++ .../org/neo4j/scala/Neo4jSpatialTest.scala | 54 ++++++++++--- 3 files changed, 128 insertions(+), 13 deletions(-) create mode 100644 src/main/scala/org/neo4j/scala/TestNodes.scala diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala index 02e8c24..a89a417 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala @@ -103,9 +103,17 @@ trait Neo4jSpatialWrapper extends Neo4jWrapper { operation(search) } - def executeSearch(implicit search:SearchWithin, layer: EditableLayer) = layer.getIndex.executeSearch(search) + def executeSearch(implicit search: SearchWithin, layer: EditableLayer) = layer.getIndex.executeSearch(search) - def getResults(implicit search:SearchWithin) = search.getResults + def getResults(implicit search: SearchWithin) = search.getResults + + /** + * node convenience defs + */ + + implicit def IsSpatialDatabaseRecordToNode(r: IsSpatialDatabaseRecord): Node = r.node.getGeomNode + + implicit def record2relationshipBuilder(record: IsSpatialDatabaseRecord) = new NodeRelationshipMethods(record.node) } diff --git a/src/main/scala/org/neo4j/scala/TestNodes.scala b/src/main/scala/org/neo4j/scala/TestNodes.scala new file mode 100644 index 0000000..2c61b62 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/TestNodes.scala @@ -0,0 +1,75 @@ +package org.neo4j.scala + +import org.neo4j.gis.spatial.{EditableLayer, SpatialDatabaseRecord} +import Types._ +import annotation.implicitNotFound +import collection.mutable.Buffer + +/** + * + * @author Christopher Schmidt + * Date: 22.03.11 + * Time: 06:00 + */ + +object Types { + type PointLocation = (Double, Double) + type PolylineLocation = Buffer[(Double, Double)] +} + +trait IsSpatialDatabaseRecord { + + val node: SpatialDatabaseRecord +} + +/** + * + */ +object City { + val KEY_CITY_NAME = "cityName" + + def apply(node: SpatialDatabaseRecord) = new City(node) + + @implicitNotFound("implicit instance of EditableLayer not in scope") + def apply(point: PointLocation)(implicit layer: EditableLayer) = { + val record = layer.add(layer.getGeometryFactory.createPoint(Coord(point._1, point._2))) + new City(record) + } +} + +/** + * + */ + +import City._ + +class City(val node: SpatialDatabaseRecord) extends IsSpatialDatabaseRecord { + + def name = node.getProperty(KEY_CITY_NAME) + + def name_=(n: String) { + node.setProperty(KEY_CITY_NAME, n) + } +} + +object FedaralState { + val KEY_FEDSTATE_NAME = "federalState" + + def apply(node: SpatialDatabaseRecord) = new FedaralState(node) + + @implicitNotFound("implicit instance of EditableLayer not in scope") + def apply(shell: PolylineLocation)(implicit layer: EditableLayer) = { + val record = layer.add(layer.getGeometryFactory.createPolygon(LinRing(shell), null)) + new FedaralState(record) + } +} + +import FedaralState._ + +class FedaralState(val node: SpatialDatabaseRecord) extends IsSpatialDatabaseRecord { + def name = node.getProperty(KEY_FEDSTATE_NAME) + + def name_=(n: String) { + node.setProperty(KEY_FEDSTATE_NAME, n) + } +} \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala index a5ab845..66f3627 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala +++ b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala @@ -18,7 +18,7 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe def neo4jStoreDir = "/tmp/temp-neo-spatial-test" - "NeoWrapper" should { + "NeoSpatialWrapper" should { shareVariables() Runtime.getRuntime.addShutdownHook(new Thread() { @@ -27,7 +27,7 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe } }) - "Wrapper usage should be possible" in { + "allow usage of Neo4jWrapper" in { withSpatialTx { implicit db => @@ -41,18 +41,16 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe } } - "interim test all" in { + "simplify layer, node and search usage" in { withSpatialTx { implicit db => - val cities = createNode - - deleteLayer("test", new NullListener) - val layer = getOrCreateEditableLayer("test") - val gf = layer.getGeometryFactory - + // remove existing layer + deleteLayer("test", new NullListener) + val cities = createNode + val federalStates = createNode withLayer(getOrCreateEditableLayer("test")) { implicit layer => @@ -65,8 +63,9 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe // adding new Polygon val bayernBuffer = Buffer[(Double, Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) val bayern = add newPolygon (LinRing(bayernBuffer)) - bayern.setProperty("FederalState", "Bayern") + federalStates --> "isFederalState" --> bayern + munich --> "CapitalCityOf" --> bayern withSearchWithin(bayern.getGeometry) { @@ -82,10 +81,43 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe getResults.size must_== 2 } } + } + } + } + "simplify Node wrapping" in { - } + withSpatialTx { + implicit db => + // remove existing layer + deleteLayer("test", new NullListener) + + val cities = createNode + val federalStates = createNode + + withLayer(getOrCreateEditableLayer("test")) { + implicit layer => + + // adding Point + val munich = City((15.3, 56.2)) + munich.name = "Munich" + cities --> "isCity" --> munich + + val bayernBuffer = Buffer[(Double, Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) + val bayern = FedaralState(bayernBuffer) + bayern.name = "Bayern" + + federalStates --> "isFederalState" --> bayern + munich --> "CapitalCityOf" --> bayern + + withSearchWithin(toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0))) { + implicit s => + executeSearch + getResults.size must_== 2 + } + + } } } } \ No newline at end of file From 591c6a613ed6493c23bad5473de1b1d766374e28 Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 23 Mar 2011 06:39:49 +0100 Subject: [PATCH 10/82] added factory --- .../org/neo4j/scala/Neo4jSpatialWrapper.scala | 7 ++ .../scala/org/neo4j/scala/TestNodes.scala | 75 ----------------- .../org/neo4j/scala/Neo4jSpatialTest.scala | 6 +- .../scala/org/neo4j/scala/TestNodes.scala | 84 +++++++++++++++++++ 4 files changed, 95 insertions(+), 77 deletions(-) delete mode 100644 src/main/scala/org/neo4j/scala/TestNodes.scala create mode 100644 src/test/scala/org/neo4j/scala/TestNodes.scala diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala index a89a417..6ababb0 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala @@ -13,6 +13,13 @@ import query.SearchWithin * Date: 16.03.11 * Time: 16:18 */ + + +trait IsSpatialDatabaseRecord { + val node: SpatialDatabaseRecord +} + + trait Neo4jSpatialWrapper extends Neo4jWrapper { def ds: DatabaseService diff --git a/src/main/scala/org/neo4j/scala/TestNodes.scala b/src/main/scala/org/neo4j/scala/TestNodes.scala deleted file mode 100644 index 2c61b62..0000000 --- a/src/main/scala/org/neo4j/scala/TestNodes.scala +++ /dev/null @@ -1,75 +0,0 @@ -package org.neo4j.scala - -import org.neo4j.gis.spatial.{EditableLayer, SpatialDatabaseRecord} -import Types._ -import annotation.implicitNotFound -import collection.mutable.Buffer - -/** - * - * @author Christopher Schmidt - * Date: 22.03.11 - * Time: 06:00 - */ - -object Types { - type PointLocation = (Double, Double) - type PolylineLocation = Buffer[(Double, Double)] -} - -trait IsSpatialDatabaseRecord { - - val node: SpatialDatabaseRecord -} - -/** - * - */ -object City { - val KEY_CITY_NAME = "cityName" - - def apply(node: SpatialDatabaseRecord) = new City(node) - - @implicitNotFound("implicit instance of EditableLayer not in scope") - def apply(point: PointLocation)(implicit layer: EditableLayer) = { - val record = layer.add(layer.getGeometryFactory.createPoint(Coord(point._1, point._2))) - new City(record) - } -} - -/** - * - */ - -import City._ - -class City(val node: SpatialDatabaseRecord) extends IsSpatialDatabaseRecord { - - def name = node.getProperty(KEY_CITY_NAME) - - def name_=(n: String) { - node.setProperty(KEY_CITY_NAME, n) - } -} - -object FedaralState { - val KEY_FEDSTATE_NAME = "federalState" - - def apply(node: SpatialDatabaseRecord) = new FedaralState(node) - - @implicitNotFound("implicit instance of EditableLayer not in scope") - def apply(shell: PolylineLocation)(implicit layer: EditableLayer) = { - val record = layer.add(layer.getGeometryFactory.createPolygon(LinRing(shell), null)) - new FedaralState(record) - } -} - -import FedaralState._ - -class FedaralState(val node: SpatialDatabaseRecord) extends IsSpatialDatabaseRecord { - def name = node.getProperty(KEY_FEDSTATE_NAME) - - def name_=(n: String) { - node.setProperty(KEY_FEDSTATE_NAME, n) - } -} \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala index 66f3627..a8af418 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala +++ b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala @@ -99,13 +99,15 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe withLayer(getOrCreateEditableLayer("test")) { implicit layer => + import NewSpatialNode._ + // adding Point - val munich = City((15.3, 56.2)) + val munich = NewSpatialNode[City]((15.3, 56.2)) munich.name = "Munich" cities --> "isCity" --> munich val bayernBuffer = Buffer[(Double, Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) - val bayern = FedaralState(bayernBuffer) + val bayern = NewSpatialNode[FedaralState](bayernBuffer) bayern.name = "Bayern" federalStates --> "isFederalState" --> bayern diff --git a/src/test/scala/org/neo4j/scala/TestNodes.scala b/src/test/scala/org/neo4j/scala/TestNodes.scala new file mode 100644 index 0000000..06af9d5 --- /dev/null +++ b/src/test/scala/org/neo4j/scala/TestNodes.scala @@ -0,0 +1,84 @@ +package org.neo4j.scala + +import org.neo4j.gis.spatial.{EditableLayer, SpatialDatabaseRecord} +import Types._ +import annotation.implicitNotFound +import collection.mutable.Buffer + +/** + * Examples following this Design Guide: http://wiki.neo4j.org/content/Design_Guide + * + * @author Christopher Schmidt + * Date: 22.03.11 + * Time: 06:00 + */ + +/** + * defines some shorter types for this examples + */ +object Types { + type PointLocation = (Double, Double) + type PolylineLocation = Buffer[(Double, Double)] +} + +/** + * example implementation for a City Node + */ +class City(val node: SpatialDatabaseRecord) extends IsSpatialDatabaseRecord { + object City { + val KEY_CITY_NAME = "cityName" + } + def name = node.getProperty(City.KEY_CITY_NAME) + def name_=(n: String) { + node.setProperty(City.KEY_CITY_NAME, n) + } +} + +/** + * example implementation for a polyline node (a federal state) + */ +class FedaralState(val node: SpatialDatabaseRecord) extends IsSpatialDatabaseRecord { + object FedaralState { + val KEY_FEDSTATE_NAME = "federalState" + } + def name = node.getProperty(FedaralState.KEY_FEDSTATE_NAME) + def name_=(n: String) { + node.setProperty(FedaralState.KEY_FEDSTATE_NAME, n) + } +} + +/** + * factory object + * creates new SpatialDatabaseRecords resp. Nodes via reflection + */ +object NewSpatialNode { + + /** + * uses a given node to create a instance of IsSpatialDatabaseRecord + */ + def apply[T: ClassManifest](node: SpatialDatabaseRecord): T = { + val m = implicitly[ClassManifest[T]] + val ctor = m.erasure.getConstructor(classOf[SpatialDatabaseRecord]) + ctor.newInstance(node).asInstanceOf[T] + } + + /** + * creates a new SpatialDatabaseRecord from a given Geometry + * and calls the contructor + */ + @implicitNotFound("implicit instance of EditableLayer not in scope") + def apply[T](shell: PolylineLocation)(implicit layer: EditableLayer, m: ClassManifest[T]): T = { + val record = layer.add(layer.getGeometryFactory.createPolygon(LinRing(shell), null)) + apply[T](record) + } + + /** + * creates a new SpatialDatabaseRecord from a given Geometry + * and calls the contructor + */ + @implicitNotFound("implicit instance of EditableLayer not in scope") + def apply[T](point: PointLocation)(implicit layer: EditableLayer, m: ClassManifest[T]): T = { + val record = layer.add(layer.getGeometryFactory.createPoint(Coord(point._1, point._2))) + apply[T](record) + } +} \ No newline at end of file From 94e87345a364c1a8404bcd36d01119e6c3d5fe16 Mon Sep 17 00:00:00 2001 From: FaKod Date: Fri, 25 Mar 2011 06:46:56 +0100 Subject: [PATCH 11/82] some refactorings (not finished) --- .../scala/org/neo4j/scala/Neo4jSpatialWrapper.scala | 12 ++++++++++-- src/main/scala/org/neo4j/scala/Neo4jWrapper.scala | 2 +- .../scala/org/neo4j/scala/Neo4jSpatialTest.scala | 8 +++----- src/test/scala/org/neo4j/scala/TestNodes.scala | 11 ++++++++++- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala index 6ababb0..440b36c 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala @@ -6,6 +6,7 @@ import com.vividsolutions.jts.geom.impl.CoordinateArraySequence import com.vividsolutions.jts.geom._ import org.neo4j.graphdb.{Node, GraphDatabaseService} import query.SearchWithin +import collection.JavaConversions._ /** * @@ -22,9 +23,9 @@ trait IsSpatialDatabaseRecord { trait Neo4jSpatialWrapper extends Neo4jWrapper { - def ds: DatabaseService + implicit val ds: DatabaseService - val sds: SpatialDatabaseService + implicit val sds: SpatialDatabaseService /** * Execute instructions within a Neo4j transaction; rollback if exception is raised and @@ -110,6 +111,13 @@ trait Neo4jSpatialWrapper extends Neo4jWrapper { operation(search) } + def searchWithin(geometry: Geometry)(implicit layer: EditableLayer) = { + val search = new SearchWithin(geometry) + layer.getIndex.executeSearch(search) + val result:Buffer[SpatialDatabaseRecord] = search.getResults + result + } + def executeSearch(implicit search: SearchWithin, layer: EditableLayer) = layer.getIndex.executeSearch(search) def getResults(implicit search: SearchWithin) = search.getResults diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index 4a9e8b1..81c9619 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -40,7 +40,7 @@ trait Neo4jWrapper { /** * */ - def createNode(implicit ds: DatabaseService):Node = ds.gds.createNode + def createNode(implicit ds: DatabaseService): Node = ds.gds.createNode /** * creates incoming and outgoing relationships diff --git a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala index a8af418..9ddff0b 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala +++ b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala @@ -113,12 +113,10 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe federalStates --> "isFederalState" --> bayern munich --> "CapitalCityOf" --> bayern - withSearchWithin(toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0))) { - implicit s => - executeSearch - getResults.size must_== 2 - } + var result = for( r <- searchWithin(toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0)))) yield r + result.size must_== 2 + bayern.getCapitalCity.name must beEqual (munich.name) } } } diff --git a/src/test/scala/org/neo4j/scala/TestNodes.scala b/src/test/scala/org/neo4j/scala/TestNodes.scala index 06af9d5..16d1063 100644 --- a/src/test/scala/org/neo4j/scala/TestNodes.scala +++ b/src/test/scala/org/neo4j/scala/TestNodes.scala @@ -1,9 +1,10 @@ package org.neo4j.scala -import org.neo4j.gis.spatial.{EditableLayer, SpatialDatabaseRecord} import Types._ import annotation.implicitNotFound import collection.mutable.Buffer +import org.neo4j.graphdb.{DynamicRelationshipType, Direction} +import org.neo4j.gis.spatial.{SpatialDatabaseService, EditableLayer, SpatialDatabaseRecord} /** * Examples following this Design Guide: http://wiki.neo4j.org/content/Design_Guide @@ -45,6 +46,14 @@ class FedaralState(val node: SpatialDatabaseRecord) extends IsSpatialDatabaseRec def name_=(n: String) { node.setProperty(FedaralState.KEY_FEDSTATE_NAME, n) } + + def getCapitalCity(implicit layer:EditableLayer) = { + val n = node.getGeomNode + val o = n.getSingleRelationship( + DynamicRelationshipType.withName("CapitalCityOf"), Direction.INCOMING + ).getOtherNode(node.getGeomNode) + new City(new SpatialDatabaseRecord(layer, o)) + } } /** From 53fb31ba1b549610faf001e6107502e8a0b616a6 Mon Sep 17 00:00:00 2001 From: FaKod Date: Fri, 25 Mar 2011 16:44:48 +0100 Subject: [PATCH 12/82] 1st refactored version --- .../org/neo4j/scala/Neo4jSpatialWrapper.scala | 95 ++++++++++--------- .../scala/org/neo4j/scala/Neo4jWrapper.scala | 81 ++++++++-------- .../scala/org/neo4j/scala/TestNodes.scala | 12 +-- 3 files changed, 97 insertions(+), 91 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala index 440b36c..bbb6f32 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala @@ -21,7 +21,7 @@ trait IsSpatialDatabaseRecord { } -trait Neo4jSpatialWrapper extends Neo4jWrapper { +trait Neo4jSpatialWrapper extends Neo4jWrapper with Neo4jSpatialWrapperImplicits { implicit val ds: DatabaseService @@ -51,43 +51,40 @@ trait Neo4jSpatialWrapper extends Neo4jWrapper { val layer = getLayer operation(layer) } +} - /** - * DatabaseService Wrapper - */ - - def deleteLayer(name: String, monitor: Listener)(implicit db: CombinedDatabaseService) = - db.sds.deleteLayer(name, monitor) - - def getOrCreateEditableLayer(name: String)(implicit db: CombinedDatabaseService): EditableLayer = - db.sds.getOrCreateEditableLayer(name) - - /** - * methods from Neo4jWrapper usage should still be possible - */ - implicit def databaseServiceToGraphDatabaseService(ds: CombinedDatabaseService): GraphDatabaseService = ds.gds +/** + * + */ +trait Neo4jSpatialWrapperImplicits { /** - * Layer Wrapper + * Search convenience defs */ - implicit def tupleToCoordinate(t: (Double, Double)): Coordinate = Coord(t._1, t._2) - - def getGeometryFactory(implicit layer: EditableLayer) = layer.getGeometryFactory + def withSearchWithin[T <: Any](geometry: Geometry)(operation: (SearchWithin) => T): T = { + val search = new SearchWithin(geometry) + operation(search) + } - def toGeometry(envelope: Envelope)(implicit layer: EditableLayer): Geometry = getGeometryFactory.toGeometry(envelope) + def searchWithin(geometry: Geometry)(implicit layer: EditableLayer) = { + val search = new SearchWithin(geometry) + layer.getIndex.executeSearch(search) + val result: Buffer[SpatialDatabaseRecord] = search.getResults + result + } - //def executeSearch(search: Search)(implicit layer: EditableLayer): Unit = layer.getIndex.executeSearch(search) + def executeSearch(implicit search: SearchWithin, layer: EditableLayer) = layer.getIndex.executeSearch(search) - def add(implicit layer: EditableLayer) = new AddGeometry(layer) + def getResults(implicit search: SearchWithin) = search.getResults - class AddGeometry(layer: EditableLayer) { - val gf = layer.getGeometryFactory + /** + * node convenience defs + */ - def newPoint(coordinate: Coordinate): SpatialDatabaseRecord = layer.add(gf.createPoint(coordinate)) + implicit def IsSpatialDatabaseRecordToNode(r: IsSpatialDatabaseRecord): Node = r.node.getGeomNode - def newPolygon(shell: LinearRing, holes: Array[LinearRing] = null) = layer.add(gf.createPolygon(shell, holes)) - } + implicit def record2relationshipBuilder(record: IsSpatialDatabaseRecord) = new NodeRelationshipMethods(record.node) /** * Database Record convenience defs @@ -103,35 +100,45 @@ trait Neo4jSpatialWrapper extends Neo4jWrapper { new SpatialDatabaseRecord(layer, node) /** - * Search convenience defs + * DatabaseService Wrapper */ - def withSearchWithin[T <: Any](geometry: Geometry)(operation: (SearchWithin) => T): T = { - val search = new SearchWithin(geometry) - operation(search) - } - - def searchWithin(geometry: Geometry)(implicit layer: EditableLayer) = { - val search = new SearchWithin(geometry) - layer.getIndex.executeSearch(search) - val result:Buffer[SpatialDatabaseRecord] = search.getResults - result - } + def deleteLayer(name: String, monitor: Listener)(implicit db: CombinedDatabaseService) = + db.sds.deleteLayer(name, monitor) - def executeSearch(implicit search: SearchWithin, layer: EditableLayer) = layer.getIndex.executeSearch(search) + def getOrCreateEditableLayer(name: String)(implicit db: CombinedDatabaseService): EditableLayer = + db.sds.getOrCreateEditableLayer(name) - def getResults(implicit search: SearchWithin) = search.getResults + /** + * methods from Neo4jWrapper usage should still be possible + */ + implicit def databaseServiceToGraphDatabaseService(ds: CombinedDatabaseService): GraphDatabaseService = ds.gds /** - * node convenience defs + * Layer Wrapper */ - implicit def IsSpatialDatabaseRecordToNode(r: IsSpatialDatabaseRecord): Node = r.node.getGeomNode + implicit def tupleToCoordinate(t: (Double, Double)): Coordinate = Coord(t._1, t._2) - implicit def record2relationshipBuilder(record: IsSpatialDatabaseRecord) = new NodeRelationshipMethods(record.node) + def getGeometryFactory(implicit layer: EditableLayer) = layer.getGeometryFactory + + def toGeometry(envelope: Envelope)(implicit layer: EditableLayer): Geometry = getGeometryFactory.toGeometry(envelope) + + //def executeSearch(search: Search)(implicit layer: EditableLayer): Unit = layer.getIndex.executeSearch(search) + + def add(implicit layer: EditableLayer) = new AddGeometry(layer) } +private[scala] class AddGeometry(layer: EditableLayer) { + val gf = layer.getGeometryFactory + + def newPoint(coordinate: Coordinate): SpatialDatabaseRecord = layer.add(gf.createPoint(coordinate)) + + def newPolygon(shell: LinearRing, holes: Array[LinearRing] = null) = layer.add(gf.createPolygon(shell, holes)) +} + + /** * convenience object of handling Coordinates */ diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index 81c9619..c192b1a 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -16,7 +16,7 @@ import org.neo4j.graphdb._ * * Feel free to use this example to tell all your friends how awesome scala is :) */ -trait Neo4jWrapper { +trait Neo4jWrapper extends Neo4jWrapperImplicits { def ds: DatabaseService @@ -41,54 +41,52 @@ trait Neo4jWrapper { * */ def createNode(implicit ds: DatabaseService): Node = ds.gds.createNode +} - /** - * creates incoming and outgoing relationships - */ - class NodeRelationshipMethods(node: Node) { - - def -->(relType: RelationshipType) = new OutgoingRelationshipBuilder(node, relType) +/** + * creates incoming and outgoing relationships + */ +private[scala] class NodeRelationshipMethods(node: Node) { + def -->(relType: RelationshipType) = new OutgoingRelationshipBuilder(node, relType) + def <--(relType: RelationshipType) = new IncomingRelationshipBuilder(node, relType) +} - def <--(relType: RelationshipType) = new IncomingRelationshipBuilder(node, relType) +/** + * Half-way through building an outgoing relationship + */ +private[scala] class OutgoingRelationshipBuilder(fromNode: Node, relType: RelationshipType) { + def -->(toNode: Node) = { + fromNode.createRelationshipTo(toNode, relType) + new NodeRelationshipMethods(toNode) } +} - /** - * Half-way through building an outgoing relationship - */ - private[scala] class OutgoingRelationshipBuilder(fromNode: Node, relType: RelationshipType) { - - /** - * - */ - def -->(toNode: Node) = { - fromNode.createRelationshipTo(toNode, relType) - new NodeRelationshipMethods(toNode) - } +/** + * Half-way through building an incoming relationship + */ +private[scala] class IncomingRelationshipBuilder(toNode: Node, relType: RelationshipType) { + def <--(fromNode: Node) = { + fromNode.createRelationshipTo(toNode, relType) + new NodeRelationshipMethods(fromNode) } +} - /** - * Half-way through building an incoming relationship - */ - private[scala] class IncomingRelationshipBuilder(toNode: Node, relType: RelationshipType) { - def <--(fromNode: Node) = { - fromNode.createRelationshipTo(toNode, relType) - new NodeRelationshipMethods(fromNode) +/** + * convenience for handling properties + */ +private[scala] class RichPropertyContainer(propertyContainer: PropertyContainer) { + def apply(property: String): Option[Any] = + propertyContainer.hasProperty(property) match { + case true => Some(propertyContainer.getProperty(property)) + case _ => None } - } - - /** - * - */ - class RichPropertyContainer(propertyContainer: PropertyContainer) { - - def apply(property: String): Option[Any] = - propertyContainer.hasProperty(property) match { - case true => Some(propertyContainer.getProperty(property)) - case _ => None - } + def update(property: String, value: Any): Unit = propertyContainer.setProperty(property, value) +} - def update(property: String, value: Any): Unit = propertyContainer.setProperty(property, value) - } +/** + * trait for implicits + */ +trait Neo4jWrapperImplicits { implicit def node2relationshipBuilder(node: Node) = new NodeRelationshipMethods(node) @@ -105,4 +103,5 @@ trait Neo4jWrapper { new ReturnableEvaluator() { def isReturnableNode(traversalPosition: TraversalPosition) = e(traversalPosition) } + } diff --git a/src/test/scala/org/neo4j/scala/TestNodes.scala b/src/test/scala/org/neo4j/scala/TestNodes.scala index 16d1063..3b05076 100644 --- a/src/test/scala/org/neo4j/scala/TestNodes.scala +++ b/src/test/scala/org/neo4j/scala/TestNodes.scala @@ -22,10 +22,12 @@ object Types { type PolylineLocation = Buffer[(Double, Double)] } +trait MyAllInOneTrait extends IsSpatialDatabaseRecord with Neo4jSpatialWrapperImplicits with Neo4jWrapperImplicits + /** * example implementation for a City Node */ -class City(val node: SpatialDatabaseRecord) extends IsSpatialDatabaseRecord { +class City(val node: SpatialDatabaseRecord) extends MyAllInOneTrait { object City { val KEY_CITY_NAME = "cityName" } @@ -38,7 +40,8 @@ class City(val node: SpatialDatabaseRecord) extends IsSpatialDatabaseRecord { /** * example implementation for a polyline node (a federal state) */ -class FedaralState(val node: SpatialDatabaseRecord) extends IsSpatialDatabaseRecord { +class FedaralState(val node: SpatialDatabaseRecord) extends MyAllInOneTrait { + object FedaralState { val KEY_FEDSTATE_NAME = "federalState" } @@ -48,10 +51,7 @@ class FedaralState(val node: SpatialDatabaseRecord) extends IsSpatialDatabaseRec } def getCapitalCity(implicit layer:EditableLayer) = { - val n = node.getGeomNode - val o = n.getSingleRelationship( - DynamicRelationshipType.withName("CapitalCityOf"), Direction.INCOMING - ).getOtherNode(node.getGeomNode) + val o = node.getSingleRelationship("CapitalCityOf", Direction.INCOMING).getOtherNode(node) new City(new SpatialDatabaseRecord(layer, o)) } } From 358aa1f5e4e8a3fc44d104d96f2164b9d15c91a1 Mon Sep 17 00:00:00 2001 From: FaKod Date: Fri, 25 Mar 2011 20:31:18 +0100 Subject: [PATCH 13/82] some refactorings --- .../org/neo4j/scala/Neo4jSpatialTest.scala | 41 ++-------- .../scala/SpatialTestsUsingTestNodes.scala | 74 +++++++++++++++++++ .../scala/org/neo4j/scala/TestNodes.scala | 22 ++++-- 3 files changed, 94 insertions(+), 43 deletions(-) create mode 100644 src/test/scala/org/neo4j/scala/SpatialTestsUsingTestNodes.scala diff --git a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala index 9ddff0b..06d1593 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala +++ b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala @@ -47,7 +47,12 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe implicit db => // remove existing layer + try { deleteLayer("test", new NullListener) + } + catch { + case _ => + } val cities = createNode val federalStates = createNode @@ -84,40 +89,4 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe } } } - - "simplify Node wrapping" in { - - withSpatialTx { - implicit db => - - // remove existing layer - deleteLayer("test", new NullListener) - - val cities = createNode - val federalStates = createNode - - withLayer(getOrCreateEditableLayer("test")) { - implicit layer => - - import NewSpatialNode._ - - // adding Point - val munich = NewSpatialNode[City]((15.3, 56.2)) - munich.name = "Munich" - cities --> "isCity" --> munich - - val bayernBuffer = Buffer[(Double, Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) - val bayern = NewSpatialNode[FedaralState](bayernBuffer) - bayern.name = "Bayern" - - federalStates --> "isFederalState" --> bayern - munich --> "CapitalCityOf" --> bayern - - var result = for( r <- searchWithin(toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0)))) yield r - result.size must_== 2 - - bayern.getCapitalCity.name must beEqual (munich.name) - } - } - } } \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/SpatialTestsUsingTestNodes.scala b/src/test/scala/org/neo4j/scala/SpatialTestsUsingTestNodes.scala new file mode 100644 index 0000000..c86f01b --- /dev/null +++ b/src/test/scala/org/neo4j/scala/SpatialTestsUsingTestNodes.scala @@ -0,0 +1,74 @@ +package org.neo4j.scala + +import org.specs.runner.JUnit4 +import org.specs.Specification +import org.neo4j.gis.spatial.NullListener +import collection.mutable.Buffer +import com.vividsolutions.jts.geom.Envelope + +class SpatialTestsUsingTestNodesTest extends JUnit4(SpatialTestsUsingTestNodes) + +object SpatialTestsUsingTestNodes extends Specification with Neo4jSpatialWrapper with EmbeddedGraphDatabaseServiceProvider with SpatialDatabaseServiceProvider { + + def neo4jStoreDir = "/tmp/temp-neo-spatial-test2" + + "NeoSpatialWrapper" should { + shareVariables() + + Runtime.getRuntime.addShutdownHook(new Thread() { + override def run() { + ds.gds.shutdown + } + }) + + "allow usage of common pattern like those in TestNodes.scala" in { + + withSpatialTx { + implicit db => + + // remove existing layer + try { + deleteLayer("test", new NullListener) + } + catch { + case _ => + } + + val cities = createNode + val federalStates = createNode + + withLayer(getOrCreateEditableLayer("test")) { + implicit layer => + + /** + * create Munich and "attach" it to the cities node + */ + val munich = NewSpatialNode[City]((15.3, 56.2)) + munich.name = "Munich" + cities --> "isCity" --> munich + + /** + * create a polygon called Bayern, "attach" it to the federal state node and + * "attach" the capital city Munich + */ + val bayernBuffer = Buffer[(Double, Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) + val bayern = NewSpatialNode[FedaralState](bayernBuffer) + bayern.name = "Bayern" + federalStates --> "isFederalState" --> bayern + munich --> "CapitalCityOf" --> bayern + + /** + * search all geometries inside an Envelope + */ + var result = for (r <- searchWithin(toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0)))) yield r + result.size must_== 2 + + /** + * retrieve the capital city of Bayern + */ + bayern.getCapitalCity.name must beEqual(munich.name) + } + } + } + } +} \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/TestNodes.scala b/src/test/scala/org/neo4j/scala/TestNodes.scala index 3b05076..3ac0cd0 100644 --- a/src/test/scala/org/neo4j/scala/TestNodes.scala +++ b/src/test/scala/org/neo4j/scala/TestNodes.scala @@ -3,12 +3,12 @@ package org.neo4j.scala import Types._ import annotation.implicitNotFound import collection.mutable.Buffer -import org.neo4j.graphdb.{DynamicRelationshipType, Direction} -import org.neo4j.gis.spatial.{SpatialDatabaseService, EditableLayer, SpatialDatabaseRecord} +import org.neo4j.graphdb.{Direction} +import org.neo4j.gis.spatial.{EditableLayer, SpatialDatabaseRecord} /** * Examples following this Design Guide: http://wiki.neo4j.org/content/Design_Guide - * + * * @author Christopher Schmidt * Date: 22.03.11 * Time: 06:00 @@ -22,16 +22,22 @@ object Types { type PolylineLocation = Buffer[(Double, Double)] } +/** + * convenience trait + */ trait MyAllInOneTrait extends IsSpatialDatabaseRecord with Neo4jSpatialWrapperImplicits with Neo4jWrapperImplicits /** * example implementation for a City Node */ class City(val node: SpatialDatabaseRecord) extends MyAllInOneTrait { + object City { val KEY_CITY_NAME = "cityName" } + def name = node.getProperty(City.KEY_CITY_NAME) + def name_=(n: String) { node.setProperty(City.KEY_CITY_NAME, n) } @@ -45,12 +51,14 @@ class FedaralState(val node: SpatialDatabaseRecord) extends MyAllInOneTrait { object FedaralState { val KEY_FEDSTATE_NAME = "federalState" } + def name = node.getProperty(FedaralState.KEY_FEDSTATE_NAME) + def name_=(n: String) { node.setProperty(FedaralState.KEY_FEDSTATE_NAME, n) } - def getCapitalCity(implicit layer:EditableLayer) = { + def getCapitalCity(implicit layer: EditableLayer) = { val o = node.getSingleRelationship("CapitalCityOf", Direction.INCOMING).getOtherNode(node) new City(new SpatialDatabaseRecord(layer, o)) } @@ -60,7 +68,7 @@ class FedaralState(val node: SpatialDatabaseRecord) extends MyAllInOneTrait { * factory object * creates new SpatialDatabaseRecords resp. Nodes via reflection */ -object NewSpatialNode { +object NewSpatialNode extends Neo4jSpatialWrapperImplicits with Neo4jWrapperImplicits { /** * uses a given node to create a instance of IsSpatialDatabaseRecord @@ -77,7 +85,7 @@ object NewSpatialNode { */ @implicitNotFound("implicit instance of EditableLayer not in scope") def apply[T](shell: PolylineLocation)(implicit layer: EditableLayer, m: ClassManifest[T]): T = { - val record = layer.add(layer.getGeometryFactory.createPolygon(LinRing(shell), null)) + val record = add newPolygon LinRing(shell) apply[T](record) } @@ -87,7 +95,7 @@ object NewSpatialNode { */ @implicitNotFound("implicit instance of EditableLayer not in scope") def apply[T](point: PointLocation)(implicit layer: EditableLayer, m: ClassManifest[T]): T = { - val record = layer.add(layer.getGeometryFactory.createPoint(Coord(point._1, point._2))) + val record = add newPoint Coord(point._1, point._2) apply[T](record) } } \ No newline at end of file From 753c6249b063d31dbc7778cb9e74dfaf7913ebae Mon Sep 17 00:00:00 2001 From: FaKod Date: Fri, 25 Mar 2011 20:40:57 +0100 Subject: [PATCH 14/82] added some text --- README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/README.md b/README.md index ffd7b9d..1427087 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,41 @@ +Neo4j Spatial Scala wrapper library +======================= + +I tried to add some wrapper stuff for the Neo4j Spatial implementation. +So you are able to create a city Munich as follows: + + val munich = add newPoint ((15.3, 56.2)) + munich.setProperty("City", "Munich") + +and attach it to a federal state like Bavaria: + + val bayernBuffer = Buffer[(Double, Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) + val bayern = add newPolygon (LinRing(bayernBuffer)) + bayern.setProperty("FederalState", "Bayern") + federalStates --> "isFederalState" --> bayern + +Aditionally I added some examples like those shown in the [Neo4j Design Guide](http://wiki.neo4j.org/content/Design_Guide): + + class FedaralState(val node: SpatialDatabaseRecord) extends MyAllInOneTrait { + + object FedaralState { + val KEY_FEDSTATE_NAME = "federalState" + } + + def name = node.getProperty(FedaralState.KEY_FEDSTATE_NAME) + + def name_=(n: String) { + node.setProperty(FedaralState.KEY_FEDSTATE_NAME, n) + } + + def getCapitalCity(implicit layer: EditableLayer) = { + val o = node.getSingleRelationship("CapitalCityOf", Direction.INCOMING).getOtherNode(node) + new City(new SpatialDatabaseRecord(layer, o)) + } + } + +Lookes rather nice IMHO, but is still very incomplete... + Neo4j Scala wrapper library ======================= From baa937603970bd957ebd7de91bf5d65003ff6029 Mon Sep 17 00:00:00 2001 From: FaKod Date: Fri, 25 Mar 2011 20:43:45 +0100 Subject: [PATCH 15/82] typos --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1427087..4986a22 100644 --- a/README.md +++ b/README.md @@ -7,16 +7,17 @@ So you are able to create a city Munich as follows: val munich = add newPoint ((15.3, 56.2)) munich.setProperty("City", "Munich") -and attach it to a federal state like Bavaria: +and attached it to a federal state like Bavaria: val bayernBuffer = Buffer[(Double, Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) val bayern = add newPolygon (LinRing(bayernBuffer)) bayern.setProperty("FederalState", "Bayern") federalStates --> "isFederalState" --> bayern -Aditionally I added some examples like those shown in the [Neo4j Design Guide](http://wiki.neo4j.org/content/Design_Guide): +Additionally I added some examples like those pattern shown in the [Neo4j Design Guide](http://wiki.neo4j.org/content/Design_Guide): - class FedaralState(val node: SpatialDatabaseRecord) extends MyAllInOneTrait { + . . . + class FedaralState(val node: SpatialDatabaseRecord) extends . . . { object FedaralState { val KEY_FEDSTATE_NAME = "federalState" @@ -33,6 +34,7 @@ Aditionally I added some examples like those shown in the [Neo4j Design Guide](h new City(new SpatialDatabaseRecord(layer, o)) } } + . . . Lookes rather nice IMHO, but is still very incomplete... From edcad7638bb532ffd093afd9ecd226d452f03f54 Mon Sep 17 00:00:00 2001 From: FaKod Date: Fri, 25 Mar 2011 20:56:04 +0100 Subject: [PATCH 16/82] added example to README --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 4986a22..800d8ac 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,25 @@ Additionally I added some examples like those pattern shown in the [Neo4j Design } . . . +that finaly result in code as follows: + + /** + * create Munich and "attach" it to the cities node + */ + val munich = NewSpatialNode[City]((15.3, 56.2)) + munich.name = "Munich" + cities --> "isCity" --> munich + + /** + * create a polygon called Bayern, "attach" it to the federal state node and + * "attach" the capital city Munich + */ + val bayernBuffer = Buffer[(Double, Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) + val bayern = NewSpatialNode[FedaralState](bayernBuffer) + bayern.name = "Bayern" + federalStates --> "isFederalState" --> bayern + munich --> "CapitalCityOf" --> bayern + Lookes rather nice IMHO, but is still very incomplete... Neo4j Scala wrapper library From b983c941724677cc616294f3d2893985ba921753 Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 28 Mar 2011 20:52:23 +0200 Subject: [PATCH 17/82] new search convenience method --- .../scala/org/neo4j/scala/Neo4jSpatialWrapper.scala | 12 ++++++++++-- .../org/neo4j/scala/SpatialTestsUsingTestNodes.scala | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala index bbb6f32..8d30087 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala @@ -67,8 +67,16 @@ trait Neo4jSpatialWrapperImplicits { operation(search) } - def searchWithin(geometry: Geometry)(implicit layer: EditableLayer) = { - val search = new SearchWithin(geometry) +// def searchWithin(geometry: Geometry)(implicit layer: EditableLayer) = { +// val search = new SearchWithin(geometry) +// layer.getIndex.executeSearch(search) +// val result: Buffer[SpatialDatabaseRecord] = search.getResults +// result +// } + + def search[T <: AbstractSearch](geometry: Geometry)(implicit layer: EditableLayer, m: ClassManifest[T]) = { + val ctor = m.erasure.getConstructor(classOf[Geometry]) + val search = ctor.newInstance(geometry).asInstanceOf[T] layer.getIndex.executeSearch(search) val result: Buffer[SpatialDatabaseRecord] = search.getResults result diff --git a/src/test/scala/org/neo4j/scala/SpatialTestsUsingTestNodes.scala b/src/test/scala/org/neo4j/scala/SpatialTestsUsingTestNodes.scala index c86f01b..d91a6b1 100644 --- a/src/test/scala/org/neo4j/scala/SpatialTestsUsingTestNodes.scala +++ b/src/test/scala/org/neo4j/scala/SpatialTestsUsingTestNodes.scala @@ -5,6 +5,7 @@ import org.specs.Specification import org.neo4j.gis.spatial.NullListener import collection.mutable.Buffer import com.vividsolutions.jts.geom.Envelope +import org.neo4j.gis.spatial.query.SearchWithin class SpatialTestsUsingTestNodesTest extends JUnit4(SpatialTestsUsingTestNodes) @@ -60,7 +61,7 @@ object SpatialTestsUsingTestNodes extends Specification with Neo4jSpatialWrapper /** * search all geometries inside an Envelope */ - var result = for (r <- searchWithin(toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0)))) yield r + var result = for (r <- search[SearchWithin](toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0)))) yield r result.size must_== 2 /** From 94651cb9dd9a0f64b49759dff665cf4aee18588d Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 29 Mar 2011 06:47:04 +0200 Subject: [PATCH 18/82] added SearchWithDistance --- .../org/neo4j/scala/Neo4jSpatialWrapper.scala | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala index 8d30087..97860b1 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala @@ -5,8 +5,8 @@ import collection.mutable.Buffer import com.vividsolutions.jts.geom.impl.CoordinateArraySequence import com.vividsolutions.jts.geom._ import org.neo4j.graphdb.{Node, GraphDatabaseService} -import query.SearchWithin import collection.JavaConversions._ +import query.{SearchWithinDistance, SearchWithin} /** * @@ -67,13 +67,16 @@ trait Neo4jSpatialWrapperImplicits { operation(search) } -// def searchWithin(geometry: Geometry)(implicit layer: EditableLayer) = { -// val search = new SearchWithin(geometry) -// layer.getIndex.executeSearch(search) -// val result: Buffer[SpatialDatabaseRecord] = search.getResults -// result -// } + def searchWithinDistance(point: Point, distance:Double)(implicit layer: EditableLayer) = { + val search = new SearchWithinDistance(point, distance) + layer.getIndex.executeSearch(search) + val result: Buffer[SpatialDatabaseRecord] = search.getResults + result + } + /** + * handles most of the searches with one Geometry parameter + */ def search[T <: AbstractSearch](geometry: Geometry)(implicit layer: EditableLayer, m: ClassManifest[T]) = { val ctor = m.erasure.getConstructor(classOf[Geometry]) val search = ctor.newInstance(geometry).asInstanceOf[T] From d073ea9f60e26a76603175006842d811295ee78e Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 11 Apr 2011 20:36:21 +0200 Subject: [PATCH 19/82] implicit for getting IndexManager --- src/main/scala/org/neo4j/scala/Neo4jWrapper.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index c192b1a..80b85f9 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -1,6 +1,7 @@ package org.neo4j.scala import org.neo4j.graphdb._ +import index.RelationshipIndex /** * Extend your class with this trait to get really neat new notation for creating @@ -104,4 +105,9 @@ trait Neo4jWrapperImplicits { def isReturnableNode(traversalPosition: TraversalPosition) = e(traversalPosition) } + /** + * Stuff for Indexes + */ + + implicit def indexManager(implicit ds: DatabaseService) = ds.gds.index } From 9be4cb9ba63341cdc5cd0765f8b6438081643c03 Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 11 Apr 2011 20:36:39 +0200 Subject: [PATCH 20/82] index creation convenience trait --- .../scala/org/neo4j/scala/IndexProvider.scala | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/main/scala/org/neo4j/scala/IndexProvider.scala diff --git a/src/main/scala/org/neo4j/scala/IndexProvider.scala b/src/main/scala/org/neo4j/scala/IndexProvider.scala new file mode 100644 index 0000000..74628f2 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/IndexProvider.scala @@ -0,0 +1,83 @@ +package org.neo4j.scala + +import org.neo4j.graphdb.Node +import org.neo4j.graphdb.index.{Index, RelationshipIndex} +import scala.collection.mutable.{Map => mutableMap} +import collection.JavaConversions._ + +/** + * Provides Index acces as trait if needed + * + * @author Christopher Schmidt + * Date: 11.04.11 + * Time: 20:27 + */ + +trait IndexProvider { + + /** + * type convenience definition + */ + type IndexCustomConfig = Option[Map[String, String]] + + /** + * required DatabaseService provided by XXXServiceProvider + */ + val ds: DatabaseService + + /** + * has to be overwritten to define Node Index and configuration + */ + def NodeIndexConfig: List[(String, IndexCustomConfig)] = Nil + + /** + * has to be overwritten to define Relation Index and configuration + */ + def RelationIndexConfig: List[(String, IndexCustomConfig)] = Nil + + /** + * private cache + */ + private val nodeIndexStore = mutableMap[String, Index[Node]]() + + /** + * private cache + */ + private val relationIndexStore = mutableMap[String, RelationshipIndex]() + + /** + * constructor creates indexes + */ + for (forNode <- NodeIndexConfig) { + nodeIndexStore += forNode._1 -> + (forNode._2 match { + case Some(config) => getIndexManager.forNodes(forNode._1, config) + case _ => getIndexManager.forNodes(forNode._1) + }) + } + + for (forRelation <- RelationIndexConfig) { + relationIndexStore += forRelation._1 -> + (forRelation._2 match { + case Some(config) => getIndexManager.forRelationships(forRelation._1, config) + case _ => getIndexManager.forRelationships(forRelation._1) + }) + } + + /** + * returns the index manager + * @return IndexManager the index manager + */ + def getIndexManager = ds.gds.index + + /** + * @return Option[Index[Node]] the created index if available + */ + def getNodeIndex(name: String) = nodeIndexStore.get(name) + + /** + * @return Option[RelationshipIndex] the created index if available + */ + def getRelationIndex(name: String) = relationIndexStore.get(name) + +} \ No newline at end of file From b153b6a1a7d36a922cf6cb8fe973c4f86448f731 Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 11 Apr 2011 20:38:31 +0200 Subject: [PATCH 21/82] renamed --- .../scala/{IndexProvider.scala => Neo4jIndexProvider.scala} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/scala/org/neo4j/scala/{IndexProvider.scala => Neo4jIndexProvider.scala} (98%) diff --git a/src/main/scala/org/neo4j/scala/IndexProvider.scala b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala similarity index 98% rename from src/main/scala/org/neo4j/scala/IndexProvider.scala rename to src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala index 74628f2..8b3ab31 100644 --- a/src/main/scala/org/neo4j/scala/IndexProvider.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala @@ -13,7 +13,7 @@ import collection.JavaConversions._ * Time: 20:27 */ -trait IndexProvider { +trait Neo4jIndexProvider { /** * type convenience definition From bb39385031c1dd20c9cb41c22d26625bb04f3585 Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 12 Apr 2011 06:39:28 +0200 Subject: [PATCH 22/82] added Neo4j Index Provider Trait test --- .../scala/org/neo4j/scala/IndexTest.scala | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/test/scala/org/neo4j/scala/IndexTest.scala diff --git a/src/test/scala/org/neo4j/scala/IndexTest.scala b/src/test/scala/org/neo4j/scala/IndexTest.scala new file mode 100644 index 0000000..36cf529 --- /dev/null +++ b/src/test/scala/org/neo4j/scala/IndexTest.scala @@ -0,0 +1,52 @@ +package org.neo4j.scala + +import org.specs.Specification +import org.specs.runner.JUnit4 +import collection.JavaConversions._ + +/** + * + * @author Christopher Schmidt + * Date: 12.04.11 + * Time: 06:15 + */ + +class IndexTest extends JUnit4(IndexTestSpec) + +object IndexTestSpec extends Specification with Neo4jSpatialWrapper with EmbeddedGraphDatabaseServiceProvider +with SpatialDatabaseServiceProvider with Neo4jIndexProvider { + + def neo4jStoreDir = "/tmp/temp-neo-index-test" + + override def NodeIndexConfig = ("MyTestIndex", Option(Map("provider" -> "lucene", "type" -> "fulltext"))) :: Nil + + + "Neo4jIndexProvider" should { + shareVariables() + + Runtime.getRuntime.addShutdownHook(new Thread() { + override def run() { + ds.gds.shutdown + } + }) + + "use the fulltext search index" in { + + val nodeIndex = getNodeIndex("MyTestIndex").get + + withSpatialTx { + implicit db => + + val theMatrix = createNode + val theMatrixReloaded = createNode + theMatrixReloaded.setProperty("name", "theMatrixReloaded") + + nodeIndex.add(theMatrix, "title", "The Matrix") + nodeIndex.add(theMatrixReloaded, "title", "The Matrix Reloaded") + // search in the fulltext index + val found = nodeIndex.query("title", "reloAdEd") + found.size must beGreaterThanOrEqualTo(1) + } + } + } +} \ No newline at end of file From 8f9872ae693a3f9144b9b4a3acf4ae1252ace358 Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 12 Apr 2011 16:09:05 +0200 Subject: [PATCH 23/82] conversion to ease the use of optional configuration --- src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala | 5 +++++ src/test/scala/org/neo4j/scala/IndexTest.scala | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala index 8b3ab31..d87bf1f 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala @@ -80,4 +80,9 @@ trait Neo4jIndexProvider { */ def getRelationIndex(name: String) = relationIndexStore.get(name) + /** + * conversion to ease the use of optional configuration + */ + implicit def mapToOptionMap(t:(String, Map[String, String])) = (t._1, Option(t._2)) + } \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/IndexTest.scala b/src/test/scala/org/neo4j/scala/IndexTest.scala index 36cf529..561423d 100644 --- a/src/test/scala/org/neo4j/scala/IndexTest.scala +++ b/src/test/scala/org/neo4j/scala/IndexTest.scala @@ -18,7 +18,7 @@ with SpatialDatabaseServiceProvider with Neo4jIndexProvider { def neo4jStoreDir = "/tmp/temp-neo-index-test" - override def NodeIndexConfig = ("MyTestIndex", Option(Map("provider" -> "lucene", "type" -> "fulltext"))) :: Nil + override def NodeIndexConfig = ("MyTestIndex", Map("provider" -> "lucene", "type" -> "fulltext")) :: Nil "Neo4jIndexProvider" should { From 992f2ff349af87c77d4a9399c2de4de5785b4c18 Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 12 Apr 2011 16:32:00 +0200 Subject: [PATCH 24/82] add index trait example to README --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 800d8ac..5f3288e 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,32 @@ that finaly result in code as follows: Lookes rather nice IMHO, but is still very incomplete... +Index Access +============ + +The access of the Neo4j Lucene Index will be handled by the trait Neo4jIndexProvider. +It can be used like this example to configure and use a index for full text search: + + object XXX extends . . . with Neo4jIndexProvider { + + override def NodeIndexConfig = ("MyTestIndex", Map("provider" -> "lucene", "type" -> "fulltext")) :: Nil + . . . + withSpatialTx { + implicit db => + + val theMatrix = createNode + val theMatrixReloaded = createNode + theMatrixReloaded.setProperty("name", "theMatrixReloaded") + + nodeIndex.add(theMatrix, "title", "The Matrix") + nodeIndex.add(theMatrixReloaded, "title", "The Matrix Reloaded") + // search in the fulltext index + val found = nodeIndex.query("title", "reloAdEd") + ... + } + . . . + } + Neo4j Scala wrapper library ======================= From d640ddea887bbf0e604725a841b17fe94b5ba8e3 Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 13 Apr 2011 06:31:55 +0200 Subject: [PATCH 25/82] forgot the node index --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5f3288e..51ca261 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,8 @@ It can be used like this example to configure and use a index for full text sear override def NodeIndexConfig = ("MyTestIndex", Map("provider" -> "lucene", "type" -> "fulltext")) :: Nil . . . + val nodeIndex = getNodeIndex("MyTestIndex").get + withSpatialTx { implicit db => From de3e4244ada52d23d2ec08705250faa96b549016 Mon Sep 17 00:00:00 2001 From: FaKod Date: Thu, 14 Apr 2011 06:39:03 +0200 Subject: [PATCH 26/82] Refactoring (should be easier to understand now) added some comments --- README.md | 2 + .../org/neo4j/scala/DatabaseService.scala | 25 ++++ ...er.scala => DatabaseServiceProvider.scala} | 28 +--- .../org/neo4j/scala/Neo4jIndexProvider.scala | 2 +- .../org/neo4j/scala/Neo4jSpatialWrapper.scala | 140 +----------------- .../neo4j/scala/Neo4jSpatialWrapperUtil.scala | 109 ++++++++++++++ .../scala/org/neo4j/scala/Neo4jWrapper.scala | 35 +---- .../org/neo4j/scala/Neo4jWrapperUtil.scala | 34 +++++ .../scala/org/neo4j/scala/SpatialUtil.scala | 52 +++++++ .../scala/org/neo4j/scala/TestNodes.scala | 4 +- 10 files changed, 232 insertions(+), 199 deletions(-) create mode 100644 src/main/scala/org/neo4j/scala/DatabaseService.scala rename src/main/scala/org/neo4j/scala/{GraphDatabaseServiceProvider.scala => DatabaseServiceProvider.scala} (50%) create mode 100644 src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala create mode 100644 src/main/scala/org/neo4j/scala/Neo4jWrapperUtil.scala create mode 100644 src/main/scala/org/neo4j/scala/SpatialUtil.scala diff --git a/README.md b/README.md index 51ca261..d03c88a 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ It can be used like this example to configure and use a index for full text sear } . . . } + +The rest if this README is the original one. Neo4j Scala wrapper library ======================= diff --git a/src/main/scala/org/neo4j/scala/DatabaseService.scala b/src/main/scala/org/neo4j/scala/DatabaseService.scala new file mode 100644 index 0000000..a881a04 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/DatabaseService.scala @@ -0,0 +1,25 @@ +package org.neo4j.scala + +import org.neo4j.graphdb.GraphDatabaseService +import org.neo4j.gis.spatial.SpatialDatabaseService + +/** + * Interface for GraphDatabaseService + * + * @author Christopher Schmidt + */ +trait DatabaseService { + def gds: GraphDatabaseService +} + +/** + * standard DatabaseService store for GraphDatabaseService + */ +case class DatabaseServiceImpl(gds: GraphDatabaseService) extends DatabaseService + +/** + * extended store for combined GraphDatabaseService and SpatialDatabaseService + * used by Neo4jSpatialWrapper + */ +case class CombinedDatabaseService(gds: GraphDatabaseService, sds: SpatialDatabaseService) extends DatabaseService + diff --git a/src/main/scala/org/neo4j/scala/GraphDatabaseServiceProvider.scala b/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala similarity index 50% rename from src/main/scala/org/neo4j/scala/GraphDatabaseServiceProvider.scala rename to src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala index 21586d3..2ff2a5b 100644 --- a/src/main/scala/org/neo4j/scala/GraphDatabaseServiceProvider.scala +++ b/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala @@ -1,44 +1,18 @@ package org.neo4j.scala import org.neo4j.kernel.EmbeddedGraphDatabase -import org.neo4j.graphdb.GraphDatabaseService import org.neo4j.gis.spatial.SpatialDatabaseService /** + * provides an embedded database service * * @author Christopher Schmidt - * Date: 16.03.11 - * Time: 16:27 - */ - -/** - * Interface to access the GraphDatabaseService - */ -trait DatabaseService { - def gds: GraphDatabaseService -} - -/** - * normal DatabaseService store for GraphDatabaseService - */ -case class DatabaseServiceImpl(gds: GraphDatabaseService) extends DatabaseService - -/** - * extended store for combined GraphDatabaseService and SpatialDatabaseService - * used by Neo4jSpatialWrapper - */ -case class CombinedDatabaseService(gds: GraphDatabaseService, sds: SpatialDatabaseService) extends DatabaseService - - -/** - * provides an embedded database service */ trait EmbeddedGraphDatabaseServiceProvider { def neo4jStoreDir: String val ds: DatabaseService = DatabaseServiceImpl(new EmbeddedGraphDatabase(neo4jStoreDir)) - } /** diff --git a/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala index d87bf1f..8b16f4a 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala @@ -6,7 +6,7 @@ import scala.collection.mutable.{Map => mutableMap} import collection.JavaConversions._ /** - * Provides Index acces as trait if needed + * Provides Index access as trait * * @author Christopher Schmidt * Date: 11.04.11 diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala index 97860b1..de1ccae 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala @@ -1,27 +1,15 @@ package org.neo4j.scala -import org.neo4j.gis.spatial._ -import collection.mutable.Buffer -import com.vividsolutions.jts.geom.impl.CoordinateArraySequence -import com.vividsolutions.jts.geom._ -import org.neo4j.graphdb.{Node, GraphDatabaseService} -import collection.JavaConversions._ -import query.{SearchWithinDistance, SearchWithin} +import org.neo4j.gis.spatial.{EditableLayer, SpatialDatabaseService} /** + * Basic Neo4j Spatial Wrapper trait * * @author Christopher Schmidt * Date: 16.03.11 * Time: 16:18 */ - - -trait IsSpatialDatabaseRecord { - val node: SpatialDatabaseRecord -} - - -trait Neo4jSpatialWrapper extends Neo4jWrapper with Neo4jSpatialWrapperImplicits { +trait Neo4jSpatialWrapper extends Neo4jWrapper with Neo4jSpatialWrapperUtil { implicit val ds: DatabaseService @@ -53,127 +41,5 @@ trait Neo4jSpatialWrapper extends Neo4jWrapper with Neo4jSpatialWrapperImplicits } } -/** - * - */ -trait Neo4jSpatialWrapperImplicits { - - /** - * Search convenience defs - */ - - def withSearchWithin[T <: Any](geometry: Geometry)(operation: (SearchWithin) => T): T = { - val search = new SearchWithin(geometry) - operation(search) - } - - def searchWithinDistance(point: Point, distance:Double)(implicit layer: EditableLayer) = { - val search = new SearchWithinDistance(point, distance) - layer.getIndex.executeSearch(search) - val result: Buffer[SpatialDatabaseRecord] = search.getResults - result - } - - /** - * handles most of the searches with one Geometry parameter - */ - def search[T <: AbstractSearch](geometry: Geometry)(implicit layer: EditableLayer, m: ClassManifest[T]) = { - val ctor = m.erasure.getConstructor(classOf[Geometry]) - val search = ctor.newInstance(geometry).asInstanceOf[T] - layer.getIndex.executeSearch(search) - val result: Buffer[SpatialDatabaseRecord] = search.getResults - result - } - - def executeSearch(implicit search: SearchWithin, layer: EditableLayer) = layer.getIndex.executeSearch(search) - - def getResults(implicit search: SearchWithin) = search.getResults - - /** - * node convenience defs - */ - - implicit def IsSpatialDatabaseRecordToNode(r: IsSpatialDatabaseRecord): Node = r.node.getGeomNode - - implicit def record2relationshipBuilder(record: IsSpatialDatabaseRecord) = new NodeRelationshipMethods(record.node) - - /** - * Database Record convenience defs - */ - - // converts SpatialDatabaseRecord to Node - implicit def spatialDatabaseRecordToNode(sdr: SpatialDatabaseRecord): Node = sdr.getGeomNode - - // delegation to Neo4jWrapper - implicit def node2relationshipBuilder(sdr: SpatialDatabaseRecord) = new NodeRelationshipMethods(sdr.getGeomNode) - - implicit def nodeToSpatialDatabaseRecord(node: Node)(implicit layer: Layer): SpatialDatabaseRecord = - new SpatialDatabaseRecord(layer, node) - - /** - * DatabaseService Wrapper - */ - - def deleteLayer(name: String, monitor: Listener)(implicit db: CombinedDatabaseService) = - db.sds.deleteLayer(name, monitor) - - def getOrCreateEditableLayer(name: String)(implicit db: CombinedDatabaseService): EditableLayer = - db.sds.getOrCreateEditableLayer(name) - - /** - * methods from Neo4jWrapper usage should still be possible - */ - implicit def databaseServiceToGraphDatabaseService(ds: CombinedDatabaseService): GraphDatabaseService = ds.gds - - /** - * Layer Wrapper - */ - - implicit def tupleToCoordinate(t: (Double, Double)): Coordinate = Coord(t._1, t._2) - - def getGeometryFactory(implicit layer: EditableLayer) = layer.getGeometryFactory - - def toGeometry(envelope: Envelope)(implicit layer: EditableLayer): Geometry = getGeometryFactory.toGeometry(envelope) - - //def executeSearch(search: Search)(implicit layer: EditableLayer): Unit = layer.getIndex.executeSearch(search) - - def add(implicit layer: EditableLayer) = new AddGeometry(layer) - -} - -private[scala] class AddGeometry(layer: EditableLayer) { - val gf = layer.getGeometryFactory - - def newPoint(coordinate: Coordinate): SpatialDatabaseRecord = layer.add(gf.createPoint(coordinate)) - - def newPolygon(shell: LinearRing, holes: Array[LinearRing] = null) = layer.add(gf.createPolygon(shell, holes)) -} - - -/** - * convenience object of handling Coordinates - */ -object Coord { - def apply(x: Double, y: Double) = new Coordinate(x, y) -} - -/** - * convenience object for CoordinateArraySequence - */ -object CoordArraySequence { - def apply(b: Buffer[(Double, Double)]) = { - val a = for (t <- b; c = Coord(t._1, t._2)) yield c - new CoordinateArraySequence(a.toArray) - } -} -/** - * convenience object for LinearRing - */ -object LinRing { - def apply(cs: CoordinateSequence)(implicit layer: EditableLayer) = - new LinearRing(cs, layer.getGeometryFactory) - def apply(b: Buffer[(Double, Double)])(implicit layer: EditableLayer) = - new LinearRing(CoordArraySequence(b), layer.getGeometryFactory) -} \ No newline at end of file diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala new file mode 100644 index 0000000..ce2814d --- /dev/null +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala @@ -0,0 +1,109 @@ +package org.neo4j.scala + + +import org.neo4j.gis.spatial._ +import collection.mutable.Buffer +import com.vividsolutions.jts.geom._ +import org.neo4j.graphdb.{Node, GraphDatabaseService} +import collection.JavaConversions._ +import query.{SearchWithinDistance, SearchWithin} + +/** + * Util and implicits Trait for spatial stuff + * extended by spatial wrapper + * + * @author Christopher Schmidt + * Date: 14.04.11 + * Time: 06:15 + */ +trait Neo4jSpatialWrapperUtil { + + /** + * Search convenience defs + */ + + def withSearchWithin[T <: Any](geometry: Geometry)(operation: (SearchWithin) => T): T = { + val search = new SearchWithin(geometry) + operation(search) + } + + def searchWithinDistance(point: Point, distance:Double)(implicit layer: EditableLayer) = { + val search = new SearchWithinDistance(point, distance) + layer.getIndex.executeSearch(search) + val result: Buffer[SpatialDatabaseRecord] = search.getResults + result + } + + /** + * handles most of the searches with one Geometry parameter + */ + def search[T <: AbstractSearch](geometry: Geometry)(implicit layer: EditableLayer, m: ClassManifest[T]) = { + val ctor = m.erasure.getConstructor(classOf[Geometry]) + val search = ctor.newInstance(geometry).asInstanceOf[T] + layer.getIndex.executeSearch(search) + val result: Buffer[SpatialDatabaseRecord] = search.getResults + result + } + + def executeSearch(implicit search: SearchWithin, layer: EditableLayer) = layer.getIndex.executeSearch(search) + + def getResults(implicit search: SearchWithin) = search.getResults + + /** + * node convenience defs + */ + + implicit def IsSpatialDatabaseRecordToNode(r: IsSpatialDatabaseRecord): Node = r.node.getGeomNode + + implicit def record2relationshipBuilder(record: IsSpatialDatabaseRecord) = new NodeRelationshipMethods(record.node) + + /** + * Database Record convenience defs + */ + + // converts SpatialDatabaseRecord to Node + implicit def spatialDatabaseRecordToNode(sdr: SpatialDatabaseRecord): Node = sdr.getGeomNode + + // delegation to Neo4jWrapper + implicit def node2relationshipBuilder(sdr: SpatialDatabaseRecord) = new NodeRelationshipMethods(sdr.getGeomNode) + + implicit def nodeToSpatialDatabaseRecord(node: Node)(implicit layer: Layer): SpatialDatabaseRecord = + new SpatialDatabaseRecord(layer, node) + + /** + * DatabaseService Wrapper + */ + + def deleteLayer(name: String, monitor: Listener)(implicit db: CombinedDatabaseService) = + db.sds.deleteLayer(name, monitor) + + def getOrCreateEditableLayer(name: String)(implicit db: CombinedDatabaseService): EditableLayer = + db.sds.getOrCreateEditableLayer(name) + + /** + * methods from Neo4jWrapper usage should still be possible + */ + implicit def databaseServiceToGraphDatabaseService(ds: CombinedDatabaseService): GraphDatabaseService = ds.gds + + /** + * Layer Wrapper + */ + + implicit def tupleToCoordinate(t: (Double, Double)): Coordinate = Coord(t._1, t._2) + + def getGeometryFactory(implicit layer: EditableLayer) = layer.getGeometryFactory + + def toGeometry(envelope: Envelope)(implicit layer: EditableLayer): Geometry = getGeometryFactory.toGeometry(envelope) + + //def executeSearch(search: Search)(implicit layer: EditableLayer): Unit = layer.getIndex.executeSearch(search) + + def add(implicit layer: EditableLayer) = new AddGeometry(layer) + +} + +/** + * container trait to hold an instance of SpatialDatabaseRecord + */ +trait IsSpatialDatabaseRecord { + val node: SpatialDatabaseRecord +} \ No newline at end of file diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index 80b85f9..e7790f8 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -1,7 +1,6 @@ package org.neo4j.scala -import org.neo4j.graphdb._ -import index.RelationshipIndex +import org.neo4j.graphdb.{PropertyContainer, RelationshipType, Node} /** * Extend your class with this trait to get really neat new notation for creating @@ -17,7 +16,7 @@ import index.RelationshipIndex * * Feel free to use this example to tell all your friends how awesome scala is :) */ -trait Neo4jWrapper extends Neo4jWrapperImplicits { +trait Neo4jWrapper extends Neo4jWrapperUtil { def ds: DatabaseService @@ -82,32 +81,4 @@ private[scala] class RichPropertyContainer(propertyContainer: PropertyContainer) case _ => None } def update(property: String, value: Any): Unit = propertyContainer.setProperty(property, value) -} - -/** - * trait for implicits - */ -trait Neo4jWrapperImplicits { - - implicit def node2relationshipBuilder(node: Node) = new NodeRelationshipMethods(node) - - implicit def string2RelationshipType(relType: String) = DynamicRelationshipType.withName(relType) - - implicit def propertyContainer2RichPropertyContainer(propertyContainer: PropertyContainer) = new RichPropertyContainer(propertyContainer) - - implicit def fn2StopEvaluator(e: TraversalPosition => Boolean) = - new StopEvaluator() { - def isStopNode(traversalPosition: TraversalPosition) = e(traversalPosition) - } - - implicit def fn2ReturnableEvaluator(e: TraversalPosition => Boolean) = - new ReturnableEvaluator() { - def isReturnableNode(traversalPosition: TraversalPosition) = e(traversalPosition) - } - - /** - * Stuff for Indexes - */ - - implicit def indexManager(implicit ds: DatabaseService) = ds.gds.index -} +} \ No newline at end of file diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapperUtil.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapperUtil.scala new file mode 100644 index 0000000..8861774 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapperUtil.scala @@ -0,0 +1,34 @@ +package org.neo4j.scala + +import org.neo4j.graphdb._ + +/** + * trait for implicits + * used by Neo4j wrapper + * + * @author Christopher Schmidt + */ +trait Neo4jWrapperUtil { + + implicit def node2relationshipBuilder(node: Node) = new NodeRelationshipMethods(node) + + implicit def string2RelationshipType(relType: String) = DynamicRelationshipType.withName(relType) + + implicit def propertyContainer2RichPropertyContainer(propertyContainer: PropertyContainer) = new RichPropertyContainer(propertyContainer) + + implicit def fn2StopEvaluator(e: TraversalPosition => Boolean) = + new StopEvaluator() { + def isStopNode(traversalPosition: TraversalPosition) = e(traversalPosition) + } + + implicit def fn2ReturnableEvaluator(e: TraversalPosition => Boolean) = + new ReturnableEvaluator() { + def isReturnableNode(traversalPosition: TraversalPosition) = e(traversalPosition) + } + + /** + * Stuff for Indexes + */ + + implicit def indexManager(implicit ds: DatabaseService) = ds.gds.index +} diff --git a/src/main/scala/org/neo4j/scala/SpatialUtil.scala b/src/main/scala/org/neo4j/scala/SpatialUtil.scala new file mode 100644 index 0000000..9aaeaf4 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/SpatialUtil.scala @@ -0,0 +1,52 @@ +package org.neo4j.scala + +import org.neo4j.gis.spatial._ +import collection.mutable.Buffer +import com.vividsolutions.jts.geom.impl.CoordinateArraySequence +import com.vividsolutions.jts.geom._ +import collection.JavaConversions._ + +/** + * Really simple and very incomplete list of spatial utility classes + * + * @author Christopher Schmidt + * Date: 14.04.11 + * Time: 06:17 + */ + +private[scala] class AddGeometry(layer: EditableLayer) { + val gf = layer.getGeometryFactory + + def newPoint(coordinate: Coordinate): SpatialDatabaseRecord = layer.add(gf.createPoint(coordinate)) + + def newPolygon(shell: LinearRing, holes: Array[LinearRing] = null) = layer.add(gf.createPolygon(shell, holes)) +} + + +/** + * convenience object of handling Coordinates + */ +object Coord { + def apply(x: Double, y: Double) = new Coordinate(x, y) +} + +/** + * convenience object for CoordinateArraySequence + */ +object CoordArraySequence { + def apply(b: Buffer[(Double, Double)]) = { + val a = for (t <- b; c = Coord(t._1, t._2)) yield c + new CoordinateArraySequence(a.toArray) + } +} + +/** + * convenience object for LinearRing + */ +object LinRing { + def apply(cs: CoordinateSequence)(implicit layer: EditableLayer) = + new LinearRing(cs, layer.getGeometryFactory) + + def apply(b: Buffer[(Double, Double)])(implicit layer: EditableLayer) = + new LinearRing(CoordArraySequence(b), layer.getGeometryFactory) +} \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/TestNodes.scala b/src/test/scala/org/neo4j/scala/TestNodes.scala index 3ac0cd0..a25a30d 100644 --- a/src/test/scala/org/neo4j/scala/TestNodes.scala +++ b/src/test/scala/org/neo4j/scala/TestNodes.scala @@ -25,7 +25,7 @@ object Types { /** * convenience trait */ -trait MyAllInOneTrait extends IsSpatialDatabaseRecord with Neo4jSpatialWrapperImplicits with Neo4jWrapperImplicits +trait MyAllInOneTrait extends IsSpatialDatabaseRecord with Neo4jSpatialWrapperUtil with Neo4jWrapperUtil /** * example implementation for a City Node @@ -68,7 +68,7 @@ class FedaralState(val node: SpatialDatabaseRecord) extends MyAllInOneTrait { * factory object * creates new SpatialDatabaseRecords resp. Nodes via reflection */ -object NewSpatialNode extends Neo4jSpatialWrapperImplicits with Neo4jWrapperImplicits { +object NewSpatialNode extends Neo4jSpatialWrapperUtil with Neo4jWrapperUtil { /** * uses a given node to create a instance of IsSpatialDatabaseRecord From 9245cb88285aa77837f9daae7899a2a1201de6ac Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 20 Jul 2011 14:44:20 +0200 Subject: [PATCH 27/82] refactoring --- src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala | 1 + src/main/scala/org/neo4j/scala/{ => util}/SpatialUtil.scala | 2 +- src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala | 1 + src/test/scala/org/neo4j/scala/TestNodes.scala | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) rename src/main/scala/org/neo4j/scala/{ => util}/SpatialUtil.scala (97%) diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala index ce2814d..ad1d998 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala @@ -7,6 +7,7 @@ import com.vividsolutions.jts.geom._ import org.neo4j.graphdb.{Node, GraphDatabaseService} import collection.JavaConversions._ import query.{SearchWithinDistance, SearchWithin} +import util.{AddGeometry, Coord} /** * Util and implicits Trait for spatial stuff diff --git a/src/main/scala/org/neo4j/scala/SpatialUtil.scala b/src/main/scala/org/neo4j/scala/util/SpatialUtil.scala similarity index 97% rename from src/main/scala/org/neo4j/scala/SpatialUtil.scala rename to src/main/scala/org/neo4j/scala/util/SpatialUtil.scala index 9aaeaf4..2a8fc63 100644 --- a/src/main/scala/org/neo4j/scala/SpatialUtil.scala +++ b/src/main/scala/org/neo4j/scala/util/SpatialUtil.scala @@ -1,4 +1,4 @@ -package org.neo4j.scala +package org.neo4j.scala.util import org.neo4j.gis.spatial._ import collection.mutable.Buffer diff --git a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala index 06d1593..91e532b 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala +++ b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala @@ -11,6 +11,7 @@ import com.vividsolutions.jts.geom.{LinearRing, Coordinate, Envelope} import com.vividsolutions.jts.geom.impl.CoordinateArraySequence import collection.JavaConversions._ import org.neo4j.graphdb.{Node, RelationshipType, Direction, DynamicRelationshipType} +import util.LinRing class Neo4jSpatialSpecTest extends JUnit4(Neo4jSpatialSpec) diff --git a/src/test/scala/org/neo4j/scala/TestNodes.scala b/src/test/scala/org/neo4j/scala/TestNodes.scala index a25a30d..1858351 100644 --- a/src/test/scala/org/neo4j/scala/TestNodes.scala +++ b/src/test/scala/org/neo4j/scala/TestNodes.scala @@ -5,6 +5,7 @@ import annotation.implicitNotFound import collection.mutable.Buffer import org.neo4j.graphdb.{Direction} import org.neo4j.gis.spatial.{EditableLayer, SpatialDatabaseRecord} +import util.{Coord, LinRing} /** * Examples following this Design Guide: http://wiki.neo4j.org/content/Design_Guide From ce2fdc12de87bd1912d56517d30f123b8b48f381 Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 20 Jul 2011 14:44:43 +0200 Subject: [PATCH 28/82] changed to Scala 2.9.0-1 --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c9eae42..0b52bad 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,7 @@ 4.0.0 + org.neo4j neo4j-scala jar @@ -12,7 +13,7 @@ UTF-8 - 2.8.1 + 2.9.0-1 1.3.M03 1.3.M03 0.5-SNAPSHOT From 8952fa3d81dd0f49c88ca51cf99b9e89315265b9 Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 20 Jul 2011 14:53:28 +0200 Subject: [PATCH 29/82] added layer convenience methods --- .../scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala index ad1d998..1e0b0af 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala @@ -81,6 +81,14 @@ trait Neo4jSpatialWrapperUtil { def getOrCreateEditableLayer(name: String)(implicit db: CombinedDatabaseService): EditableLayer = db.sds.getOrCreateEditableLayer(name) + def createLayer(name: String)(implicit db: CombinedDatabaseService): EditableLayer = + db.sds.createLayer(name, classOf[WKBGeometryEncoder], classOf[EditableLayerImpl]).asInstanceOf[EditableLayer] + + def getLayerNames(implicit db: CombinedDatabaseService) = db.sds.getLayerNames + + def getLayer(name: String)(implicit db: CombinedDatabaseService): Layer = + db.sds.getLayer(name) + /** * methods from Neo4jWrapper usage should still be possible */ From 7a645e369f732cfcd6f94e9a456dba09d808fb44 Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 20 Jul 2011 14:57:57 +0200 Subject: [PATCH 30/82] renamed --- src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala index 1e0b0af..abbb2a9 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala @@ -81,7 +81,7 @@ trait Neo4jSpatialWrapperUtil { def getOrCreateEditableLayer(name: String)(implicit db: CombinedDatabaseService): EditableLayer = db.sds.getOrCreateEditableLayer(name) - def createLayer(name: String)(implicit db: CombinedDatabaseService): EditableLayer = + def createEditableLayer(name: String)(implicit db: CombinedDatabaseService): EditableLayer = db.sds.createLayer(name, classOf[WKBGeometryEncoder], classOf[EditableLayerImpl]).asInstanceOf[EditableLayer] def getLayerNames(implicit db: CombinedDatabaseService) = db.sds.getLayerNames From dccf3942b94688583d3891cce4c392b5dea95d57 Mon Sep 17 00:00:00 2001 From: FaKod Date: Fri, 22 Jul 2011 09:30:24 +0200 Subject: [PATCH 31/82] serialize and deserialize case classes --- .../scala/org/neo4j/scala/util/Poso.scala | 180 +++++++++ .../scala/util/scalax/rules/Memoisable.scala | 55 +++ .../scala/util/scalax/rules/Result.scala | 77 ++++ .../neo4j/scala/util/scalax/rules/Rule.scala | 180 +++++++++ .../neo4j/scala/util/scalax/rules/Rules.scala | 149 +++++++ .../scala/util/scalax/rules/SeqRule.scala | 96 +++++ .../rules/scalasig/ClassFileParser.scala | 302 ++++++++++++++ .../util/scalax/rules/scalasig/Flags.scala | 104 +++++ .../util/scalax/rules/scalasig/ScalaSig.scala | 375 ++++++++++++++++++ .../util/scalax/rules/scalasig/Symbol.scala | 93 +++++ .../util/scalax/rules/scalasig/Type.scala | 49 +++ .../scala/org/neo4j/scala/SigParserTest.scala | 45 +++ 12 files changed, 1705 insertions(+) create mode 100644 src/main/scala/org/neo4j/scala/util/Poso.scala create mode 100644 src/main/scala/org/neo4j/scala/util/scalax/rules/Memoisable.scala create mode 100644 src/main/scala/org/neo4j/scala/util/scalax/rules/Result.scala create mode 100644 src/main/scala/org/neo4j/scala/util/scalax/rules/Rule.scala create mode 100644 src/main/scala/org/neo4j/scala/util/scalax/rules/Rules.scala create mode 100644 src/main/scala/org/neo4j/scala/util/scalax/rules/SeqRule.scala create mode 100644 src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/ClassFileParser.scala create mode 100644 src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/Flags.scala create mode 100644 src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/ScalaSig.scala create mode 100644 src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/Symbol.scala create mode 100644 src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/Type.scala create mode 100644 src/test/scala/org/neo4j/scala/SigParserTest.scala diff --git a/src/main/scala/org/neo4j/scala/util/Poso.scala b/src/main/scala/org/neo4j/scala/util/Poso.scala new file mode 100644 index 0000000..072fc9d --- /dev/null +++ b/src/main/scala/org/neo4j/scala/util/Poso.scala @@ -0,0 +1,180 @@ +package org.neo4j.scala.util + +import org.neo4j.scala.so.neo +import scalax.rules.scalasig._ +import collection.mutable.ArrayBuffer + +/** + * helper class to store Class object + */ +case class JavaType(c: Class[_]) + +/** + * Case Class deserializing object + */ +object CaseClassDeserializer { + + /** + * convenience method using class manifest + * use it like val test = deserialize[Test](myMap) + */ + def deserialize[T: ClassManifest](m: Map[String, AnyRef]): T = { + val cm = implicitly[ClassManifest[T]] + deserialize(m, JavaType(cm.erasure)).asInstanceOf[T] + } + + /**Creates a case class instance from parameter map + * + * @param m Map[String, AnyRef] map of parameter name an parameter type + * @param javaTypeTarget JavaType case class class to create + */ + def deserialize(m: Map[String, AnyRef], javaTypeTarget: JavaType) = { + require(javaTypeTarget.c.getConstructors.length == 1, "Case classes must only have one constructor.") + val constructor = javaTypeTarget.c.getConstructors.head + val params = CaseClassSigParser.parse(javaTypeTarget.c) + + val values = new ArrayBuffer[AnyRef] + for ((paramName, paramType) <- params) { + val field = m.getOrElse(paramName, throw new RuntimeException("Field: " + paramName + " of type: " + paramType.c + " not found")) + + /** + * if the value is directly assignable: use it + * otherwise try to create an instnace using der String Constructor + */ + if (field.getClass.isAssignableFrom(paramType.c)) + values += field + else { + val paramCtor = paramType.c.getConstructor(classOf[String]) + val value = paramCtor.newInstance(field).asInstanceOf[AnyRef] + values += value + } + } + constructor.newInstance(values.toArray: _*).asInstanceOf[AnyRef] + } + + /** + * creates a map from case class parameter + * @param o AnyRef case class instance + */ + def serialize(o: AnyRef): Map[String, AnyRef] = { + val methods = o.getClass.getDeclaredMethods + .filter { + _.getParameterTypes.isEmpty + } + .map { + m => m.getName -> m + }.toMap + val params = CaseClassSigParser.parse(o.getClass) + val l = for ((paramName, _) <- params; value = methods.get(paramName).get.invoke(o)) yield (paramName, value) + l.toMap + } +} + +class MissingPickledSig(clazz: Class[_]) extends Error("Failed to parse pickled Scala signature from: %s".format(clazz)) + +class MissingExpectedType(clazz: Class[_]) extends Error( + "Parsed pickled Scala signature, but no expected type found: %s" + .format(clazz) +) + +object CaseClassSigParser { + val SCALA_SIG = "ScalaSig" + val SCALA_SIG_ANNOTATION = "Lscala/reflect/ScalaSignature;" + val BYTES_VALUE = "bytes" + + protected def parseScalaSig[A](clazz: Class[A]): Option[ScalaSig] = { + val firstPass = ScalaSigParser.parse(clazz) + firstPass match { + case Some(x) => { + Some(x) + } + case None if clazz.getName.endsWith("$") => { + val clayy = Class.forName(clazz.getName.replaceFirst("\\$$", "")) + val secondPass = ScalaSigParser.parse(clayy) + secondPass + } + case x => x + } + } + + protected def findSym[A](clazz: Class[A]) = { + val pss = parseScalaSig(clazz) + pss match { + case Some(x) => { + val topLevelClasses = x.topLevelClasses + topLevelClasses.headOption match { + case Some(tlc) => { + tlc + } + case None => { + val topLevelObjects = x.topLevelObjects + topLevelObjects.headOption match { + case Some(tlo) => { + tlo + } + case _ => throw new MissingExpectedType(clazz) + } + } + } + } + case None => throw new MissingPickledSig(clazz) + } + } + + def parse[A](clazz: Class[A]) = { + findSym(clazz).children + .filter(c => c.isCaseAccessor && !c.isPrivate) + .map(_.asInstanceOf[MethodSymbol]) + .zipWithIndex + .flatMap { + case (ms, idx) => { + ms.infoType match { + case NullaryMethodType(t: TypeRefType) => Some(ms.name -> typeRef2JavaType(t)) + case _ => None + } + } + } + } + + protected def typeRef2JavaType(ref: TypeRefType): JavaType = { + try { + JavaType(loadClass(ref.symbol.path)) + } catch { + case e: Throwable => { + e.printStackTrace() + null + } + } + } + + protected def loadClass(path: String) = path match { + case "scala.Predef.Map" => classOf[Map[_, _]] + case "scala.Predef.Set" => classOf[Set[_]] + case "scala.Predef.String" => classOf[String] + case "scala.package.List" => classOf[List[_]] + case "scala.package.Seq" => classOf[Seq[_]] + case "scala.package.Sequence" => classOf[Seq[_]] + case "scala.package.Collection" => classOf[Seq[_]] + case "scala.package.IndexedSeq" => classOf[IndexedSeq[_]] + case "scala.package.RandomAccessSeq" => classOf[IndexedSeq[_]] + case "scala.package.Iterable" => classOf[Iterable[_]] + case "scala.package.Iterator" => classOf[Iterator[_]] + case "scala.package.Vector" => classOf[Vector[_]] + case "scala.package.BigDecimal" => classOf[BigDecimal] + case "scala.package.BigInt" => classOf[BigInt] + case "scala.package.Integer" => classOf[java.lang.Integer] + case "scala.package.Character" => classOf[java.lang.Character] + case "scala.Long" => classOf[java.lang.Long] + case "scala.Int" => classOf[java.lang.Integer] + case "scala.Boolean" => classOf[java.lang.Boolean] + case "scala.Short" => classOf[java.lang.Short] + case "scala.Byte" => classOf[java.lang.Byte] + case "scala.Float" => classOf[java.lang.Float] + case "scala.Double" => classOf[java.lang.Double] + case "scala.Char" => classOf[java.lang.Character] + case "scala.Any" => classOf[Any] + case "scala.AnyRef" => classOf[AnyRef] + case name => Class.forName(name) + } +} + diff --git a/src/main/scala/org/neo4j/scala/util/scalax/rules/Memoisable.scala b/src/main/scala/org/neo4j/scala/util/scalax/rules/Memoisable.scala new file mode 100644 index 0000000..26c2d1e --- /dev/null +++ b/src/main/scala/org/neo4j/scala/util/scalax/rules/Memoisable.scala @@ -0,0 +1,55 @@ +package org.neo4j.scala.util +package scalax +package rules + +import scala.collection.mutable.HashMap + +trait MemoisableRules extends Rules { + def memo[In <: Memoisable, Out, A, X](key: AnyRef) + (toRule: => In => Result[Out, A, X]) = { + lazy val rule = toRule + from[In] {in => in.memo(key, rule(in))} + } + + override def ruleWithName[In, Out, A, X](name: String, + f: In => rules.Result[Out, A, X]) = super.ruleWithName( + name, (in: In) => + in match { + case s: Memoisable => s.memo(name, f(in)) + case _ => f(in) + } + ) +} + +trait Memoisable { + def memo[A](key: AnyRef, a: => A): A +} + + +object DefaultMemoisable { + var debug = false +} + +trait DefaultMemoisable extends Memoisable { + protected val map = new HashMap[AnyRef, Any] + + def memo[A](key: AnyRef, a: => A) = { + map.getOrElseUpdate(key, compute(key, a)).asInstanceOf[A] + } + + protected def compute[A](key: AnyRef, a: => A): Any = a match { + case success: Success[_, _] => onSuccess(key, success); success + case other => + if (DefaultMemoisable.debug) println(key + " -> " + other) + other + } + + protected def onSuccess[S, T](key: AnyRef, result: Success[S, T]) { + val Success(out, t) = result + if (DefaultMemoisable.debug) println(key + " -> " + t + " (" + out + ")") + } +} + + + + diff --git a/src/main/scala/org/neo4j/scala/util/scalax/rules/Result.scala b/src/main/scala/org/neo4j/scala/util/scalax/rules/Result.scala new file mode 100644 index 0000000..89eab78 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/util/scalax/rules/Result.scala @@ -0,0 +1,77 @@ +package org.neo4j.scala.util +package scalax +package rules + +/**Represents the combined value of two rules applied in sequence. + * + * @see the Scala parser combinator + */ +case class ~[+A, +B](_1: A, _2: B) { + override def toString = "(" + _1 + " ~ " + _2 + ")" +} + + +sealed abstract class Result[+Out, +A, +X] { + def out: Out + + def value: A + + def error: X + + implicit def toOption: Option[A] + + def map[B](f: A => B): Result[Out, B, X] + + def mapOut[Out2](f: Out => Out2): Result[Out2, A, X] + + def map[Out2, B](f: (Out, A) => (Out2, B)): Result[Out2, B, X] + + def flatMap[Out2, B](f: (Out, A) => Result[Out2, B, Nothing]): Result[Out2, B, X] + + def orElse[Out2 >: Out, B >: A](other: => Result[Out2, B, Nothing]): Result[Out2, B, X] +} + +case class Success[+Out, +A](out: Out, + value: A) extends Result[Out, A, Nothing] { + def error = throw new ScalaSigParserError("No error") + + def toOption = Some(value) + + def map[B](f: A => B): Result[Out, B, Nothing] = Success(out, f(value)) + + def mapOut[Out2](f: Out => Out2): Result[Out2, A, Nothing] = Success(f(out), value) + + def map[Out2, B](f: (Out, A) => (Out2, B)): Success[Out2, B] = f(out, value) match {case (out2, b) => Success(out2, b)} + + def flatMap[Out2, B](f: (Out, A) => Result[Out2, B, Nothing]): Result[Out2, B, Nothing] = f(out, value) + + def orElse[Out2 >: Out, B >: A](other: => Result[Out2, B, Nothing]): Result[Out2, B, Nothing] = this +} + +sealed abstract class NoSuccess[+X] extends Result[Nothing, Nothing, X] { + def out = throw new ScalaSigParserError("No output") + + def value = throw new ScalaSigParserError("No value") + + def toOption = None + + def map[B](f: Nothing => B) = this + + def mapOut[Out2](f: Nothing => Out2) = this + + def map[Out2, B](f: (Nothing, Nothing) => (Out2, B)) = this + + def flatMap[Out2, B](f: (Nothing, Nothing) => Result[Out2, B, Nothing]) = this + + def orElse[Out2, B](other: => Result[Out2, B, Nothing]) = other +} + +case object Failure extends NoSuccess[Nothing] { + def error = throw new ScalaSigParserError("No error") +} + +case class ScalaSigParserError(msg: String) extends RuntimeException(msg) + +case class Error[+X](error: X) extends NoSuccess[X] { +} + diff --git a/src/main/scala/org/neo4j/scala/util/scalax/rules/Rule.scala b/src/main/scala/org/neo4j/scala/util/scalax/rules/Rule.scala new file mode 100644 index 0000000..a890f74 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/util/scalax/rules/Rule.scala @@ -0,0 +1,180 @@ +package org.neo4j.scala.util +package scalax +package rules + +/**A Rule is a function from some input to a Result. The result may be: + *
    + *
  • Success, with a value of some type and an output that may serve as the input to subsequent rules.
  • + *
  • Failure. A failure may result in some alternative rule being applied.
  • + *
  • Error. No further rules should be attempted.
  • + *
+ * + * @author Andrew Foggin + * + * Inspired by the Scala parser combinator. + */ +trait Rule[-In, +Out, +A, +X] extends (In => Result[Out, A, X]) { + val factory: Rules + + import factory._ + + def as(name: String) = ruleWithName(name, this) + + def flatMap[Out2, B, X2 >: X](fa2ruleb: A => Out => Result[Out2, B, X2]) = mapResult { + case Success(out, a) => fa2ruleb(a)(out) + case Failure => Failure + case err@Error(_) => err + } + + def map[B](fa2b: A => B) = flatMap {a => out => Success(out, fa2b(a))} + + def filter(f: A => Boolean) = flatMap {a => + out => if (f(a)) Success(out, a) else Failure + } + + def mapResult[Out2, B, Y](f: Result[Out, A, X] => Result[Out2, B, Y]) = rule { + in: In => f(apply(in)) + } + + def orElse[In2 <: In, Out2 >: Out, A2 >: A, X2 >: X](other: => Rule[In2, Out2, A2, X2]): Rule[In2, Out2, A2, X2] = new Choice[In2, Out2, A2, X2] { + val factory = Rule.this.factory + lazy val choices = Rule.this :: other :: Nil + } + + def orError[In2 <: In] = this orElse (error[In2]) + + def |[In2 <: In, Out2 >: Out, A2 >: A, X2 >: X](other: => Rule[In2, Out2, A2, X2]) = orElse(other) + + def ^^[B](fa2b: A => B) = map(fa2b) + + def ^^?[B](pf: PartialFunction[A, B]) = filter(pf.isDefinedAt(_)) ^^ pf + + def ??(pf: PartialFunction[A, Any]) = filter(pf.isDefinedAt(_)) + + def -^[B](b: B) = map {any => b} + + /**Maps an Error */ + def !^[Y](fx2y: X => Y) = mapResult { + case s@Success(_, _) => s + case Failure => Failure + case Error(x) => Error(fx2y(x)) + } + + def >>[Out2, B, X2 >: X](fa2ruleb: A => Out => Result[Out2, B, X2]) = flatMap(fa2ruleb) + + def >->[Out2, B, X2 >: X](fa2resultb: A => Result[Out2, B, X2]) = flatMap { + a => any => fa2resultb(a) + } + + def >>?[Out2, B, X2 >: X](pf: PartialFunction[A, Rule[Out, Out2, B, X2]]) = filter(pf isDefinedAt _) flatMap pf + + def >>&[B, X2 >: X](fa2ruleb: A => Out => Result[Any, B, X2]) = flatMap {a => + out => fa2ruleb(a)(out) mapOut {any => out} + } + + def ~[Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next) yield new ~(a, b) + + def ~-[Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next) yield a + + def -~[Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next) yield b + + def ~++[Out2, B >: A, X2 >: X](next: => Rule[Out, Out2, Seq[B], X2]) = for (a <- this; b <- next) yield a :: b.toList + + /**Apply the result of this rule to the function returned by the next rule */ + def ~>[Out2, B, X2 >: X](next: => Rule[Out, Out2, A => B, X2]) = for (a <- this; fa2b <- next) yield fa2b(a) + + /**Apply the result of this rule to the function returned by the previous rule */ + def <~:[InPrev, B, X2 >: X](prev: => Rule[InPrev, In, A => B, X2]) = for (fa2b <- prev; a <- this) yield fa2b(a) + + def ~![Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next orError) yield new ~(a, b) + + def ~-![Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next orError) yield a + + def -~![Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next orError) yield b + + def -[In2 <: In](exclude: => Rule[In2, Any, Any, Any]) = !exclude -~ this + + /**^~^(f) is equivalent to ^^ { case b1 ~ b2 => f(b1, b2) } + */ + def ^~^[B1, B2, B >: A <% B1 ~ B2, C](f: (B1, B2) => C) = map {a => + (a: B1 ~ B2) match {case b1 ~ b2 => f(b1, b2)} + } + + /**^~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 => f(b1, b2, b3) } + */ + def ^~~^[B1, B2, B3, B >: A <% B1 ~ B2 ~ B3, C](f: (B1, B2, B3) => C) = map { + a => + (a: B1 ~ B2 ~ B3) match {case b1 ~ b2 ~ b3 => f(b1, b2, b3)} + } + + /**^~~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 ~ b4 => f(b1, b2, b3, b4) } + */ + def ^~~~^[B1, B2, B3, B4, B >: A <% B1 ~ B2 ~ B3 ~ B4, C](f: (B1, B2, B3, B4) => C) = map { + a => + (a: B1 ~ B2 ~ B3 ~ B4) match {case b1 ~ b2 ~ b3 ~ b4 => f(b1, b2, b3, b4)} + } + + /**^~~~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 ~ b4 ~ b5 => f(b1, b2, b3, b4, b5) } + */ + def ^~~~~^[B1, B2, B3, B4, B5, B >: A <% B1 ~ B2 ~ B3 ~ B4 ~ B5, C](f: (B1, B2, B3, B4, B5) => C) = map { + a => + (a: B1 ~ B2 ~ B3 ~ B4 ~ B5) match {case b1 ~ b2 ~ b3 ~ b4 ~ b5 => f(b1, b2, b3, b4, b5)} + } + + /**^~~~~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 ~ b4 ~ b5 ~ b6 => f(b1, b2, b3, b4, b5, b6) } + */ + def ^~~~~~^[B1, B2, B3, B4, B5, B6, B >: A <% B1 ~ B2 ~ B3 ~ B4 ~ B5 ~ B6, C](f: (B1, B2, B3, B4, B5, B6) => C) = map { + a => + (a: B1 ~ B2 ~ B3 ~ B4 ~ B5 ~ B6) match {case b1 ~ b2 ~ b3 ~ b4 ~ b5 ~ b6 => f(b1, b2, b3, b4, b5, b6)} + } + + /**^~~~~~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 ~ b4 ~ b5 ~ b6 => f(b1, b2, b3, b4, b5, b6) } + */ + def ^~~~~~~^[B1, B2, B3, B4, B5, B6, B7, B >: A <% B1 ~ B2 ~ B3 ~ B4 ~ B5 ~ B6 ~ B7, C](f: (B1, B2, B3, B4, B5, B6, B7) => C) = map { + a => + (a: B1 ~ B2 ~ B3 ~ B4 ~ B5 ~ B6 ~ B7) match {case b1 ~ b2 ~ b3 ~ b4 ~ b5 ~ b6 ~ b7 => f(b1, b2, b3, b4, b5, b6, b7)} + } + + /**>~>(f) is equivalent to >> { case b1 ~ b2 => f(b1, b2) } + */ + def >~>[Out2, B1, B2, B >: A <% B1 ~ B2, C, X2 >: X](f: (B1, B2) => Out => Result[Out2, C, X2]) = flatMap { + a => + (a: B1 ~ B2) match {case b1 ~ b2 => f(b1, b2)} + } + + /**^-^(f) is equivalent to ^^ { b2 => b1 => f(b1, b2) } + */ + def ^-^[B1, B2 >: A, C](f: (B1, B2) => C) = map {b2: B2 => + b1: B1 => f(b1, b2) + } + + /**^~>~^(f) is equivalent to ^^ { case b2 ~ b3 => b1 => f(b1, b2, b3) } + */ + def ^~>~^[B1, B2, B3, B >: A <% B2 ~ B3, C](f: (B1, B2, B3) => C) = map {a => + (a: B2 ~ B3) match {case b2 ~ b3 => b1: B1 => f(b1, b2, b3)} + } +} + + +trait Choice[-In, +Out, +A, +X] extends Rule[In, Out, A, X] { + def choices: List[Rule[In, Out, A, X]] + + def apply(in: In) = { + def oneOf(list: List[Rule[In, Out, A, X]]): Result[Out, A, X] = list match { + case Nil => Failure + case first :: rest => first(in) match { + case Failure => oneOf(rest) + case result => result + } + } + oneOf(choices) + } + + override def orElse[In2 <: In, Out2 >: Out, A2 >: A, X2 >: X](other: => Rule[In2, Out2, A2, X2]): Rule[In2, Out2, A2, X2] = new Choice[In2, Out2, A2, X2] { + val factory = Choice.this.factory + lazy val choices = Choice.this.choices ::: other :: Nil + } +} + + + diff --git a/src/main/scala/org/neo4j/scala/util/scalax/rules/Rules.scala b/src/main/scala/org/neo4j/scala/util/scalax/rules/Rules.scala new file mode 100644 index 0000000..e40848b --- /dev/null +++ b/src/main/scala/org/neo4j/scala/util/scalax/rules/Rules.scala @@ -0,0 +1,149 @@ +package org.neo4j.scala.util +package scalax +package rules + +trait Name { + def name: String + + override def toString = name +} + +/**A factory for rules. + * + * @author Andrew Foggin + * + * Inspired by the Scala parser combinator. + */ +trait Rules { + implicit def rule[In, Out, A, X](f: In => Result[Out, A, X]): Rule[In, Out, A, X] = new DefaultRule(f) + + implicit def inRule[In, Out, A, X](rule: Rule[In, Out, A, X]): InRule[In, Out, A, X] = new InRule(rule) + + implicit def seqRule[In, A, X](rule: Rule[In, In, A, X]): SeqRule[In, A, X] = new SeqRule(rule) + + def from[In] = new { + def apply[Out, A, X](f: In => Result[Out, A, X]) = rule(f) + } + + def state[s] = new StateRules { + type S = s + val factory = Rules.this + } + + def success[Out, A](out: Out, a: A) = rule {in: Any => Success(out, a)} + + def failure = rule {in: Any => Failure} + + def error[In] = rule {in: In => Error(in)} + + def error[X](err: X) = rule {in: Any => Error(err)} + + def oneOf[In, Out, A, X](rules: Rule[In, Out, A, X]*): Rule[In, Out, A, X] = new Choice[In, Out, A, X] { + val factory = Rules.this + val choices = rules.toList + } + + def ruleWithName[In, Out, A, X](_name: String, + f: In => Result[Out, A, X]): Rule[In, Out, A, X] with Name = + new DefaultRule(f) with Name { + val name = _name + } + + class DefaultRule[In, Out, A, X](f: In => Result[Out, A, X]) extends Rule[In, Out, A, X] { + val factory = Rules.this + + def apply(in: In) = f(in) + } + + /**Converts a rule into a function that throws an Exception on failure. */ + def expect[In, Out, A, Any](rule: Rule[In, Out, A, Any]): In => A = (in) => + rule(in) match { + case Success(_, a) => a + case Failure => throw new ScalaSigParserError("Unexpected failure") + case Error(x) => throw new ScalaSigParserError("Unexpected error: " + x) + } +} + +/**A factory for rules that apply to a particular context. + * + * @requires S the context to which rules apply. + * + * @author Andrew Foggin + * + * Inspired by the Scala parser combinator. + */ +trait StateRules { + type S + type Rule[+A, +X] = rules.Rule[S, S, A, X] + + val factory: Rules + + import factory._ + + def apply[A, X](f: S => Result[S, A, X]) = rule(f) + + def unit[A](a: => A) = apply {s => Success(s, a)} + + def read[A](f: S => A) = apply {s => Success(s, f(s))} + + def get = apply {s => Success(s, s)} + + def set(s: => S) = apply {oldS => Success(s, oldS)} + + def update(f: S => S) = apply {s => Success(s, f(s))} + + def nil = unit(Nil) + + def none = unit(None) + + /**Create a rule that identities if f(in) is true. */ + def cond(f: S => Boolean) = get filter f + + /**Create a rule that succeeds if all of the given rules succeed. + @param rules the rules to apply in sequence. + */ + def allOf[A, X](rules: Seq[Rule[A, X]]) = { + def rep(in: S, rules: List[Rule[A, X]], + results: List[A]): Result[S, List[A], X] = { + rules match { + case Nil => Success(in, results.reverse) + case rule :: tl => rule(in) match { + case Failure => Failure + case Error(x) => Error(x) + case Success(out, v) => rep(out, tl, v :: results) + } + } + } + in: S => rep(in, rules.toList, Nil) + } + + + /**Create a rule that succeeds with a list of all the provided rules that succeed. + @param rules the rules to apply in sequence. + */ + def anyOf[A, X](rules: Seq[Rule[A, X]]) = allOf(rules.map(_ ?)) ^^ { + opts => opts.flatMap(x => x) + } + + /**Repeatedly apply a rule from initial value until finished condition is met. */ + def repeatUntil[T, X](rule: Rule[T => T, X])(finished: T => Boolean) + (initial: T) = apply { + // more compact using HoF but written this way so it's tail-recursive + def rep(in: S, t: T): Result[S, T, X] = { + if (finished(t)) Success(in, t) + else rule(in) match { + case Success(out, f) => rep(out, f(t)) + case Failure => Failure + case Error(x) => Error(x) + } + } + in => rep(in, initial) + } + + +} + +trait RulesWithState extends Rules with StateRules { + val factory = this +} + diff --git a/src/main/scala/org/neo4j/scala/util/scalax/rules/SeqRule.scala b/src/main/scala/org/neo4j/scala/util/scalax/rules/SeqRule.scala new file mode 100644 index 0000000..0c9af01 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/util/scalax/rules/SeqRule.scala @@ -0,0 +1,96 @@ +package org.neo4j.scala.util +package scalax +package rules + +/** + * A workaround for the difficulties of dealing with + * a contravariant 'In' parameter type... + */ +class InRule[In, +Out, +A, +X](rule: Rule[In, Out, A, X]) { + + def mapRule[Out2, B, Y](f: Result[Out, A, X] => In => Result[Out2, B, Y]): Rule[In, Out2, B, Y] = rule.factory.rule { + in: In => f(rule(in))(in) + } + + /**Creates a rule that succeeds only if the original rule would fail on the given context. */ + def unary_! : Rule[In, In, Unit, Nothing] = mapRule { + case Success(_, _) => in: In => Failure + case _ => in: In => Success(in, ()) + } + + /**Creates a rule that succeeds if the original rule succeeds, but returns the original input. */ + def & : Rule[In, In, A, X] = mapRule { + case Success(_, a) => in: In => Success(in, a) + case Failure => in: In => Failure + case Error(x) => in: In => Error(x) + } +} + +class SeqRule[S, +A, +X](rule: Rule[S, S, A, X]) { + + import rule.factory._ + + def ? = rule mapRule { + case Success(out, a) => in: S => Success(out, Some(a)) + case Failure => in: S => Success(in, None) + case Error(x) => in: S => Error(x) + } + + /**Creates a rule that always succeeds with a Boolean value. + * Value is 'true' if this rule succeeds, 'false' otherwise */ + def -? = ? map {_ isDefined} + + def * = from[S] { + // tail-recursive function with reverse list accumulator + def rep(in: S, acc: List[A]): Result[S, List[A], X] = rule(in) match { + case Success(out, a) => rep(out, a :: acc) + case Failure => Success(in, acc.reverse) + case err: Error[_] => err + } + in => rep(in, Nil) + } + + def + = rule ~++ * + + def ~>?[B >: A, X2 >: X](f: => Rule[S, S, B => B, X2]) = for (a <- rule; fs <- f ?) yield fs.foldLeft[B](a) {(b, + f) => f(b) + } + + def ~>*[B >: A, X2 >: X](f: => Rule[S, S, B => B, X2]) = for (a <- rule; fs <- f *) yield fs.foldLeft[B](a) {(b, + f) => f(b) + } + + def ~*~[B >: A, X2 >: X](join: => Rule[S, S, (B, B) => B, X2]) = { + this ~>* (for (f <- join; a <- rule) yield f(_: B, a)) + } + + /**Repeats this rule one or more times with a separator (which is discarded) */ + def +/[X2 >: X](sep: => Rule[S, S, Any, X2]) = rule ~++ (sep -~ rule *) + + /**Repeats this rule zero or more times with a separator (which is discarded) */ + def */[X2 >: X](sep: => Rule[S, S, Any, X2]) = +/(sep) | state[S].nil + + def *~-[Out, X2 >: X](end: => Rule[S, Out, Any, X2]) = (rule - end *) ~- end + + def +~-[Out, X2 >: X](end: => Rule[S, Out, Any, X2]) = (rule - end +) ~- end + + /**Repeats this rule num times */ + def times(num: Int): Rule[S, S, Seq[A], X] = from[S] { + val result = new collection.mutable.ArraySeq[A](num) + // more compact using HoF but written this way so it's tail-recursive + def rep(i: Int, in: S): Result[S, Seq[A], X] = { + if (i == num) Success(in, result) + else rule(in) match { + case Success(out, a) => { + result(i) = a + rep(i + 1, out) + } + case Failure => Failure + case err: Error[_] => err + } + } + in => rep(0, in) + } +} + + diff --git a/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/ClassFileParser.scala b/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/ClassFileParser.scala new file mode 100644 index 0000000..890e3df --- /dev/null +++ b/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/ClassFileParser.scala @@ -0,0 +1,302 @@ +package org.neo4j.scala.util +package scalax +package rules +package scalasig + + +import java.io.IOException + +import scala._ +import scala.Predef._ + +object ByteCode { + def apply(bytes: Array[Byte]) = new ByteCode(bytes, 0, bytes.length) + + def forClass(clazz: Class[_]) = { + val name = clazz.getName + val subPath = name.substring(name.lastIndexOf('.') + 1) + ".class" + val in = clazz.getResourceAsStream(subPath) + + try { + var rest = in.available() + val bytes = new Array[Byte](rest) + while (rest > 0) { + val res = in.read(bytes, bytes.length - rest, rest) + if (res == -1) throw new IOException("read error") + rest -= res + } + ByteCode(bytes) + + } finally { + in.close() + } + } +} + +/**Represents a chunk of raw bytecode. Used as input for the parsers + */ +class ByteCode(val bytes: Array[Byte], val pos: Int, val length: Int) { + + assert(pos >= 0 && length >= 0 && pos + length <= bytes.length) + + def nextByte = if (length == 0) Failure else Success(drop(1), bytes(pos)) + + def next(n: Int) = if (length >= n) Success(drop(n), take(n)) else Failure + + def take(n: Int) = new ByteCode(bytes, pos, n) + + def drop(n: Int) = new ByteCode(bytes, pos + n, length - n) + + def fold[X](x: X)(f: (X, Byte) => X): X = { + var result = x + var i = pos + while (i < pos + length) { + result = f(result, bytes(i)) + i += 1 + } + result + } + + override def toString = length + " bytes" + + def toInt = fold(0) {(x, b) => (x << 8) + (b & 0xFF)} + + def toLong = fold(0L) {(x, b) => (x << 8) + (b & 0xFF)} + + private val utf8: Array[Byte] => Array[Char] = { + // scala 2.8.1 + try { + val m1 = io.Codec.getClass.getDeclaredMethod("toUTF8", classOf[Array[Byte]]); + {f => m1.invoke(io.Codec, f).asInstanceOf[Array[Char]]} + } catch { + case e: NoSuchMethodException => { + // scala 2.9.0.RC1 + val m2 = io.Codec.getClass.getDeclaredMethod("fromUTF8", classOf[Array[Byte]]); + {f => m2.invoke(io.Codec, f).asInstanceOf[Array[Char]]} + } + } + } + + /** + * Transforms array subsequence of the current buffer into the UTF8 String and + * stores and array of bytes for the decompiler + */ + def fromUTF8StringAndBytes: StringBytesPair = { + val chunk: Array[Byte] = bytes drop pos take length + StringBytesPair(utf8(chunk).mkString, chunk) + } + + def byte(i: Int) = bytes(pos) & 0xFF +} + +/** + * The wrapper for decode UTF-8 string + */ +case class StringBytesPair(string: String, bytes: Array[Byte]) + +/**Provides rules for parsing byte-code. + */ +trait ByteCodeReader extends RulesWithState { + type S = ByteCode + type Parser[A] = Rule[A, String] + + val byte = apply(_ nextByte) + + val u1 = byte ^^ (_ & 0xFF) + val u2 = bytes(2) ^^ (_ toInt) + val u4 = bytes(4) ^^ (_ toInt) + // should map to Long?? + + def bytes(n: Int) = apply(_ next n) +} + +object ClassFileParser extends ByteCodeReader { + def parse(byteCode: ByteCode) = expect(classFile)(byteCode) + + def parseAnnotations(byteCode: ByteCode) = expect(annotations)(byteCode) + + val magicNumber = (u4 filter (_ == 0xCAFEBABE)) | error("Not a valid class file") + val version = u2 ~ u2 ^^ {case minor ~ major => (major, minor)} + val constantPool = (u2 ^^ ConstantPool) >> repeatUntil(constantPoolEntry)(_ isFull) + + // NOTE currently most constants just evaluate to a string description + // TODO evaluate to useful values + val utf8String = (u2 >> bytes) ^^ add1 {raw => + pool => raw.fromUTF8StringAndBytes + } + val intConstant = u4 ^^ add1 {x => pool => x} + val floatConstant = bytes(4) ^^ add1 {raw => pool => "Float: TODO"} + val longConstant = bytes(8) ^^ add2 {raw => pool => raw.toLong} + val doubleConstant = bytes(8) ^^ add2 {raw => pool => "Double: TODO"} + val classRef = u2 ^^ add1 {x => pool => "Class: " + pool(x)} + val stringRef = u2 ^^ add1 {x => pool => "String: " + pool(x)} + val fieldRef = memberRef("Field") + val methodRef = memberRef("Method") + val interfaceMethodRef = memberRef("InterfaceMethod") + val nameAndType = u2 ~ u2 ^^ add1 { + case name ~ descriptor => + pool => "NameAndType: " + pool(name) + ", " + pool(descriptor) + } + + val constantPoolEntry = u1 >> { + case 1 => utf8String + case 3 => intConstant + case 4 => floatConstant + case 5 => longConstant + case 6 => doubleConstant + case 7 => classRef + case 8 => stringRef + case 9 => fieldRef + case 10 => methodRef + case 11 => interfaceMethodRef + case 12 => nameAndType + } + + val interfaces = u2 >> u2.times + + // bytes are parametrizes by the length, declared in u4 section + val attribute = u2 ~ (u4 >> bytes) ^~^ Attribute + // parse attributes u2 times + val attributes = u2 >> attribute.times + + // parse runtime-visible annotations + abstract class ElementValue + + case class AnnotationElement(elementNameIndex: Int, + elementValue: ElementValue) + + case class ConstValueIndex(index: Int) extends ElementValue + + case class EnumConstValue(typeNameIndex: Int, + constNameIndex: Int) extends ElementValue + + case class ClassInfoIndex(index: Int) extends ElementValue + + case class Annotation(typeIndex: Int, + elementValuePairs: Seq[AnnotationElement]) extends ElementValue + + case class ArrayValue(values: Seq[ElementValue]) extends ElementValue + + def element_value: Parser[ElementValue] = u1 >> { + case 'B' | 'C' | 'D' | 'F' | 'I' | 'J' | 'S' | 'Z' | 's' => u2 ^^ ConstValueIndex + case 'e' => u2 ~ u2 ^~^ EnumConstValue + case 'c' => u2 ^^ ClassInfoIndex + case '@' => annotation //nested annotation + case '[' => u2 >> element_value.times ^^ ArrayValue + } + + val element_value_pair = u2 ~ element_value ^~^ AnnotationElement + val annotation: Parser[Annotation] = u2 ~ (u2 >> element_value_pair.times) ^~^ Annotation + val annotations = u2 >> annotation.times + + val field = u2 ~ u2 ~ u2 ~ attributes ^~~~^ Field + val fields = u2 >> field.times + + val method = u2 ~ u2 ~ u2 ~ attributes ^~~~^ Method + val methods = u2 >> method.times + + val header = magicNumber -~ u2 ~ u2 ~ constantPool ~ u2 ~ u2 ~ u2 ~ interfaces ^~~~~~~^ ClassFileHeader + val classFile = header ~ fields ~ methods ~ attributes ~- !u1 ^~~~^ ClassFile + + // TODO create a useful object, not just a string + def memberRef(description: String) = u2 ~ u2 ^^ add1 { + case classRef ~ nameAndTypeRef => + pool => description + ": " + pool(classRef) + ", " + pool(nameAndTypeRef) + } + + def add1[T](f: T => ConstantPool => Any)(raw: T) + (pool: ConstantPool) = pool add f(raw) + + def add2[T](f: T => ConstantPool => Any)(raw: T) + (pool: ConstantPool) = pool add f(raw) add {pool => ""} +} + +case class ClassFile( + header: ClassFileHeader, + fields: Seq[Field], + methods: Seq[Method], + attributes: Seq[Attribute]) { + + def majorVersion = header.major + + def minorVersion = header.minor + + def className = constant(header.classIndex) + + def superClass = constant(header.superClassIndex) + + def interfaces = header.interfaces.map(constant) + + def constant(index: Int) = header.constants(index) match { + case StringBytesPair(str, _) => str + case z => z + } + + def constantWrapped(index: Int) = header.constants(index) + + def attribute(name: String) = attributes.find { + attrib => constant(attrib.nameIndex) == name + } + + val RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations" + + def annotations = (attributes.find( + attr => constant(attr.nameIndex) == RUNTIME_VISIBLE_ANNOTATIONS + ) + .map( + attr => ClassFileParser.parseAnnotations(attr.byteCode) + )) + + def annotation(name: String) = annotations.flatMap( + seq => seq.find( + annot => constant(annot.typeIndex) == name + ) + ) +} + +case class Attribute(nameIndex: Int, byteCode: ByteCode) + +case class Field(flags: Int, nameIndex: Int, descriptorIndex: Int, + attributes: Seq[Attribute]) + +case class Method(flags: Int, nameIndex: Int, descriptorIndex: Int, + attributes: Seq[Attribute]) + +case class ClassFileHeader( + minor: Int, + major: Int, + constants: ConstantPool, + flags: Int, + classIndex: Int, + superClassIndex: Int, + interfaces: Seq[Int]) { + + def constant(index: Int) = constants(index) +} + +case class ConstantPool(len: Int) { + val size = len - 1 + + private val buffer = new scala.collection.mutable.ArrayBuffer[ConstantPool => Any] + private val values = Array.fill[Option[Any]](size)(None) + + def isFull = buffer.length >= size + + def apply(index: Int) = { + // Note constant pool indices are 1-based + val i = index - 1 + values(i) getOrElse { + val value = buffer(i)(this) + buffer(i) = null + values(i) = Some(value) + value + } + } + + def add(f: ConstantPool => Any) = { + buffer += f + this + } +} + + diff --git a/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/Flags.scala b/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/Flags.scala new file mode 100644 index 0000000..9589ee8 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/Flags.scala @@ -0,0 +1,104 @@ +package org.neo4j.scala.util.scalax.rules.scalasig + +trait Flags { + def hasFlag(flag: Long): Boolean + + def isImplicit = hasFlag(0x00000001) + + def isFinal = hasFlag(0x00000002) + + def isPrivate = hasFlag(0x00000004) + + def isProtected = hasFlag(0x00000008) + + def isSealed = hasFlag(0x00000010) + + def isOverride = hasFlag(0x00000020) + + def isCase = hasFlag(0x00000040) + + def isAbstract = hasFlag(0x00000080) + + def isDeferred = hasFlag(0x00000100) + + def isMethod = hasFlag(0x00000200) + + def isModule = hasFlag(0x00000400) + + def isInterface = hasFlag(0x00000800) + + def isMutable = hasFlag(0x00001000) + + def isParam = hasFlag(0x00002000) + + def isPackage = hasFlag(0x00004000) + + def isDeprecated = hasFlag(0x00008000) + + def isCovariant = hasFlag(0x00010000) + + def isCaptured = hasFlag(0x00010000) + + def isByNameParam = hasFlag(0x00010000) + + def isContravariant = hasFlag(0x00020000) + + def isLabel = hasFlag(0x00020000) + + // method symbol is a label. Set by TailCall + def isInConstructor = hasFlag(0x00020000) + + // class symbol is defined in this/superclass constructor + + def isAbstractOverride = hasFlag(0x00040000) + + def isLocal = hasFlag(0x00080000) + + def isJava = hasFlag(0x00100000) + + def isSynthetic = hasFlag(0x00200000) + + def isStable = hasFlag(0x00400000) + + def isStatic = hasFlag(0x00800000) + + def isCaseAccessor = hasFlag(0x01000000) + + def isTrait = hasFlag(0x02000000) + + def isBridge = hasFlag(0x04000000) + + def isAccessor = hasFlag(0x08000000) + + def isSuperAccessor = hasFlag(0x10000000) + + def isParamAccessor = hasFlag(0x20000000) + + def isModuleVar = hasFlag(0x40000000) + + // for variables: is the variable caching a module value + def isMonomorphic = hasFlag(0x40000000) + + // for type symbols: does not have type parameters + def isLazy = hasFlag(0x80000000L) + + // symbol is a lazy val. can't have MUTABLE unless transformed by typer + + def isError = hasFlag(0x100000000L) + + def isOverloaded = hasFlag(0x200000000L) + + def isLifted = hasFlag(0x400000000L) + + def isMixedIn = hasFlag(0x800000000L) + + def isExistential = hasFlag(0x800000000L) + + def isExpandedName = hasFlag(0x1000000000L) + + def isImplementationClass = hasFlag(0x2000000000L) + + def isPreSuper = hasFlag(0x2000000000L) + +} + diff --git a/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/ScalaSig.scala b/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/ScalaSig.scala new file mode 100644 index 0000000..ed1f0b9 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/ScalaSig.scala @@ -0,0 +1,375 @@ +package org.neo4j.scala.util +package scalax +package rules +package scalasig + +import ClassFileParser.{ConstValueIndex, Annotation} +import scala.reflect.generic.ByteCodecs + +object ScalaSigParser { + + import CaseClassSigParser.{SCALA_SIG, SCALA_SIG_ANNOTATION, BYTES_VALUE} + + def scalaSigFromAnnotation(classFile: ClassFile): Option[ScalaSig] = { + import classFile._ + + classFile.annotation(SCALA_SIG_ANNOTATION) map { + case Annotation(_, elements) => + val bytesElem = elements.find( + elem => constant(elem.elementNameIndex) == BYTES_VALUE + ).get + val bytes = ((bytesElem.elementValue match {case ConstValueIndex(index) => constantWrapped(index)}) + .asInstanceOf[StringBytesPair].bytes) + val length = ByteCodecs.decode(bytes) + + ScalaSigAttributeParsers.parse(ByteCode(bytes.take(length))) + } + } + + def scalaSigFromAttribute(classFile: ClassFile): Option[ScalaSig] = + classFile.attribute(SCALA_SIG).map(_.byteCode).map(ScalaSigAttributeParsers.parse) + + def parse(classFile: ClassFile): Option[ScalaSig] = { + val scalaSig = scalaSigFromAttribute(classFile) + + scalaSig match { + // No entries in ScalaSig attribute implies that the signature is stored in the annotation + case Some(ScalaSig(_, _, entries)) if entries.length == 0 => + scalaSigFromAnnotation(classFile) + case x => x + } + } + + def parse(clazz: Class[_]): Option[ScalaSig] = { + val byteCode = ByteCode.forClass(clazz) + val classFile = ClassFileParser.parse(byteCode) + + parse(classFile) + } +} + +object ScalaSigAttributeParsers extends ByteCodeReader { + def parse(byteCode: ByteCode) = expect(scalaSig)(byteCode) + + val nat = apply { + def natN(in: ByteCode, + x: Int): Result[ByteCode, Int, Nothing] = in.nextByte match { + case Success(out, b) => { + val y = (x << 7) + (b & 0x7f) + if ((b & 0x80) == 0) Success(out, y) else natN(out, y) + } + case _ => Failure + } + in => natN(in, 0) + } + + val rawBytes = nat >> bytes + val entry = nat ~ rawBytes + val symtab = nat >> entry.times + val scalaSig = nat ~ nat ~ symtab ^~~^ ScalaSig + + val utf8 = read(x => x.fromUTF8StringAndBytes.string) + val longValue = read(_ toLong) +} + +case class ScalaSig(majorVersion: Int, minorVersion: Int, + table: Seq[Int ~ ByteCode]) extends DefaultMemoisable { + + case class Entry(index: Int, entryType: Int, + byteCode: ByteCode) extends DefaultMemoisable { + def scalaSig = ScalaSig.this + + def setByteCode(byteCode: ByteCode) = Entry(index, entryType, byteCode) + } + + def hasEntry(index: Int) = table isDefinedAt index + + def getEntry(index: Int) = { + val entryType ~ byteCode = table(index) + Entry(index, entryType, byteCode) + } + + def parseEntry(index: Int) = applyRule(ScalaSigParsers.parseEntry(ScalaSigEntryParsers.entry)(index)) + + implicit def applyRule[A](parser: ScalaSigParsers.Parser[A]) = ScalaSigParsers.expect(parser)(this) + + override def toString = "ScalaSig version " + majorVersion + "." + minorVersion + { + for (i <- 0 until table.size) yield i + ":\t" + parseEntry(i) // + "\n\t" + getEntry(i) + }.mkString("\n", "\n", "") + + lazy val symbols: Seq[Symbol] = ScalaSigParsers.symbols + + lazy val topLevelClasses: List[ClassSymbol] = ScalaSigParsers.topLevelClasses + lazy val topLevelObjects: List[ObjectSymbol] = ScalaSigParsers.topLevelObjects +} + +object ScalaSigParsers extends RulesWithState with MemoisableRules { + type S = ScalaSig + type Parser[A] = Rule[A, String] + + val symTab = read(_.table) + val size = symTab ^^ (_.size) + + def entry(index: Int) = memo(("entry", index)) { + cond(_ hasEntry index) -~ read(_ getEntry index) >-> { + entry => Success(entry, entry.entryType) + } + } + + def parseEntry[A](parser: ScalaSigEntryParsers.EntryParser[A]) + (index: Int): Parser[A] = + entry(index) -~ parser >> {a => entry => Success(entry.scalaSig, a)} + + def allEntries[A](f: ScalaSigEntryParsers.EntryParser[A]) = size >> { + n => anyOf((0 until n) map parseEntry(f)) + } + + lazy val entries = allEntries(ScalaSigEntryParsers.entry) as "entries" + lazy val symbols = allEntries(ScalaSigEntryParsers.symbol) as "symbols" + lazy val methods = allEntries(ScalaSigEntryParsers.methodSymbol) as "methods" + lazy val attributes = allEntries(ScalaSigEntryParsers.attributeInfo) as "attributes" + + lazy val topLevelClasses = allEntries(ScalaSigEntryParsers.topLevelClass) + lazy val topLevelObjects = allEntries(ScalaSigEntryParsers.topLevelObject) +} + +object ScalaSigEntryParsers extends RulesWithState with MemoisableRules { + + import ScalaSigAttributeParsers.{nat, utf8, longValue} + + type S = ScalaSig#Entry + type EntryParser[A] = Rule[A, String] + + implicit def byteCodeEntryParser[A](rule: ScalaSigAttributeParsers.Parser[A]): EntryParser[A] = apply { + entry => + rule(entry.byteCode) mapOut (entry setByteCode _) + } + + def toEntry[A](index: Int) = apply { + sigEntry => ScalaSigParsers.entry(index)(sigEntry.scalaSig) + } + + def parseEntry[A](parser: EntryParser[A]) + (index: Int) = (toEntry(index) -~ parser) + + implicit def entryType(code: Int) = key filter (_ == code) + + val index = read(_.index) + val key = read(_.entryType) + + lazy val entry: EntryParser[Any] = symbol | typeEntry | literal | name | attributeInfo | annotInfo | children | get + + val ref = byteCodeEntryParser(nat) + + val termName = 1 -~ utf8 + val typeName = 2 -~ utf8 + + val name = termName | typeName as "name" + + def refTo[A](rule: EntryParser[A]): EntryParser[A] = ref >>& parseEntry(rule) + + lazy val nameRef = refTo(name) + lazy val symbolRef = refTo(symbol) + lazy val typeRef = refTo(typeEntry) + lazy val constantRef = refTo(literal) + + val symbolInfo = nameRef ~ symbolRef ~ nat ~ (symbolRef ?) ~ ref ~ get ^~~~~~^ SymbolInfo + + def symHeader(key: Int) = (key -~ none | (key + 64) -~ nat) + + def symbolEntry(key: Int) = symHeader(key) -~ symbolInfo + + /*************************************************** + * Symbol table attribute format: + * Symtab = nentries_Nat {Entry} + * Entry = 1 TERMNAME len_Nat NameInfo + * | 2 TYPENAME len_Nat NameInfo + * | 3 NONEsym len_Nat + * | 4 TYPEsym len_Nat SymbolInfo + * | 5 ALIASsym len_Nat SymbolInfo + * | 6 CLASSsym len_Nat SymbolInfo [thistype_Ref] + * | 7 MODULEsym len_Nat SymbolInfo + * | 8 VALsym len_Nat [defaultGetter_Ref /* no longer needed*/] SymbolInfo [alias_Ref] + * | 9 EXTref len_Nat name_Ref [owner_Ref] + * | 10 EXTMODCLASSref len_Nat name_Ref [owner_Ref] + * | 11 NOtpe len_Nat + * | 12 NOPREFIXtpe len_Nat + * | 13 THIStpe len_Nat sym_Ref + * | 14 SINGLEtpe len_Nat type_Ref sym_Ref + * | 15 CONSTANTtpe len_Nat constant_Ref + * | 16 TYPEREFtpe len_Nat type_Ref sym_Ref {targ_Ref} + * | 17 TYPEBOUNDStpe len_Nat tpe_Ref tpe_Ref + * | 18 REFINEDtpe len_Nat classsym_Ref {tpe_Ref} + * | 19 CLASSINFOtpe len_Nat classsym_Ref {tpe_Ref} + * | 20 METHODtpe len_Nat tpe_Ref {sym_Ref} + * | 21 POLYTtpe len_Nat tpe_Ref {sym_Ref} + * | 22 IMPLICITMETHODtpe len_Nat tpe_Ref {sym_Ref} /* no longer needed */ + * | 52 SUPERtpe len_Nat tpe_Ref tpe_Ref + * | 24 LITERALunit len_Nat + * | 25 LITERALboolean len_Nat value_Long + * | 26 LITERALbyte len_Nat value_Long + * | 27 LITERALshort len_Nat value_Long + * | 28 LITERALchar len_Nat value_Long + * | 29 LITERALint len_Nat value_Long + * | 30 LITERALlong len_Nat value_Long + * | 31 LITERALfloat len_Nat value_Long + * | 32 LITERALdouble len_Nat value_Long + * | 33 LITERALstring len_Nat name_Ref + * | 34 LITERALnull len_Nat + * | 35 LITERALclass len_Nat tpe_Ref + * | 36 LITERALenum len_Nat sym_Ref + * | 40 SYMANNOT len_Nat sym_Ref AnnotInfoBody + * | 41 CHILDREN len_Nat sym_Ref {sym_Ref} + * | 42 ANNOTATEDtpe len_Nat [sym_Ref /* no longer needed */] tpe_Ref {annotinfo_Ref} + * | 43 ANNOTINFO len_Nat AnnotInfoBody + * | 44 ANNOTARGARRAY len_Nat {constAnnotArg_Ref} + * | 47 DEBRUIJNINDEXtpe len_Nat level_Nat index_Nat + * | 48 EXISTENTIALtpe len_Nat type_Ref {symbol_Ref} + */ + val noSymbol = 3 -^ NoSymbol + val typeSymbol = symbolEntry(4) ^^ TypeSymbol as "typeSymbol" + val aliasSymbol = symbolEntry(5) ^^ AliasSymbol as "alias" + val classSymbol = symbolEntry(6) ~ (ref ?) ^~^ ClassSymbol as "class" + val objectSymbol = symbolEntry(7) ^^ ObjectSymbol as "object" + val methodSymbol = symHeader(8) -~ /*(ref?) -~*/ symbolInfo ~ (ref ?) ^~^ MethodSymbol as "method" + val extRef = 9 -~ nameRef ~ (symbolRef ?) ~ get ^~~^ ExternalSymbol as "extRef" + val extModClassRef = 10 -~ nameRef ~ (symbolRef ?) ~ get ^~~^ ExternalSymbol as "extModClassRef" + + lazy val symbol: EntryParser[Symbol] = oneOf( + noSymbol, + typeSymbol, + aliasSymbol, + classSymbol, + objectSymbol, + methodSymbol, + extRef, + extModClassRef + ) as "symbol" + + val classSymRef = refTo(classSymbol) + val attribTreeRef = ref + val typeLevel = nat + val typeIndex = nat + + lazy val typeEntry: EntryParser[Type] = oneOf( + 11 -^ NoType, + 12 -^ NoPrefixType, + 13 -~ symbolRef ^^ ThisType, + 14 -~ typeRef ~ symbolRef ^~^ SingleType, + 15 -~ constantRef ^^ ConstantType, + 16 -~ typeRef ~ symbolRef ~ (typeRef *) ^~~^ TypeRefType, + 17 -~ typeRef ~ typeRef ^~^ TypeBoundsType, + 18 -~ classSymRef ~ (typeRef *) ^~^ RefinedType, + 19 -~ symbolRef ~ (typeRef *) ^~^ ClassInfoType, + 20 -~ typeRef ~ (symbolRef *) ^~^ MethodType, + 21 -~ typeRef ~ (refTo(typeSymbol) +) ^~^ PolyType, // TODO: make future safe for past by doing the same transformation as in the full unpickler in case we're reading pre-2.9 classfiles + 21 -~ typeRef ^^ NullaryMethodType, + 22 -~ typeRef ~ (symbolRef *) ^~^ ImplicitMethodType, + 42 -~ typeRef ~ (attribTreeRef *) ^~^ AnnotatedType, + 51 -~ typeRef ~ symbolRef ~ (attribTreeRef *) ^~~^ AnnotatedWithSelfType, + 47 -~ typeLevel ~ typeIndex ^~^ DeBruijnIndexType, + 48 -~ typeRef ~ (symbolRef *) ^~^ ExistentialType + ) as "type" + + lazy val literal = oneOf( + 24 -^ (), + 25 -~ longValue ^^ (_ != 0L), + 26 -~ longValue ^^ (_.toByte), + 27 -~ longValue ^^ (_.toShort), + 28 -~ longValue ^^ (_.toChar), + 29 -~ longValue ^^ (_.toInt), + 30 -~ longValue ^^ (_.toLong), + 31 -~ longValue ^^ (l => java.lang.Float.intBitsToFloat(l.toInt)), + 32 -~ longValue ^^ (java.lang.Double.longBitsToDouble), + 33 -~ nameRef, + 34 -^ null, + 35 -~ typeRef + ) + + lazy val attributeInfo = 40 -~ symbolRef ~ typeRef ~ (constantRef ?) ~ (nameRef ~ constantRef *) ^~~~^ AttributeInfo + // sym_Ref info_Ref {constant_Ref} {nameRef constantRef} + lazy val children = 41 -~ (nat *) ^^ Children + //sym_Ref {sym_Ref} + lazy val annotInfo = 43 -~ (nat *) ^^ AnnotInfo + // attarg_Ref {constant_Ref attarg_Ref} + + lazy val topLevelClass = classSymbol filter isTopLevelClass + lazy val topLevelObject = objectSymbol filter isTopLevel + + def isTopLevel(symbol: Symbol) = symbol.parent match { + case Some(ext: ExternalSymbol) => true + case _ => false + } + + def isTopLevelClass(symbol: Symbol) = !symbol.isModule && isTopLevel(symbol) +} + +case class AttributeInfo(symbol: Symbol, typeRef: Type, value: Option[Any], + values: Seq[String ~ Any]) + +// sym_Ref info_Ref {constant_Ref} {nameRef constantRef} +case class Children(symbolRefs: Seq[Int]) + +//sym_Ref {sym_Ref} + +case class AnnotInfo(refs: Seq[Int]) + +// attarg_Ref {constant_Ref attarg_Ref} + +/*************************************************** + * | 49 TREE len_Nat 1 EMPTYtree + * | 49 TREE len_Nat 2 PACKAGEtree type_Ref sym_Ref mods_Ref name_Ref {tree_Ref} + * | 49 TREE len_Nat 3 CLASStree type_Ref sym_Ref mods_Ref name_Ref tree_Ref {tree_Ref} + * | 49 TREE len_Nat 4 MODULEtree type_Ref sym_Ref mods_Ref name_Ref tree_Ref + * | 49 TREE len_Nat 5 VALDEFtree type_Ref sym_Ref mods_Ref name_Ref tree_Ref tree_Ref + * | 49 TREE len_Nat 6 DEFDEFtree type_Ref sym_Ref mods_Ref name_Ref numtparams_Nat {tree_Ref} numparamss_Nat {numparams_Nat {tree_Ref}} tree_Ref tree_Ref + * | 49 TREE len_Nat 7 TYPEDEFtree type_Ref sym_Ref mods_Ref name_Ref tree_Ref {tree_Ref} + * | 49 TREE len_Nat 8 LABELtree type_Ref sym_Ref tree_Ref {tree_Ref} + * | 49 TREE len_Nat 9 IMPORTtree type_Ref sym_Ref tree_Ref {name_Ref name_Ref} + * | 49 TREE len_Nat 11 DOCDEFtree type_Ref sym_Ref string_Ref tree_Ref + * | 49 TREE len_Nat 12 TEMPLATEtree type_Ref sym_Ref numparents_Nat {tree_Ref} tree_Ref {tree_Ref} + * | 49 TREE len_Nat 13 BLOCKtree type_Ref tree_Ref {tree_Ref} + * | 49 TREE len_Nat 14 CASEtree type_Ref tree_Ref tree_Ref tree_Ref + * | 49 TREE len_Nat 15 SEQUENCEtree type_Ref {tree_Ref} + * | 49 TREE len_Nat 16 ALTERNATIVEtree type_Ref {tree_Ref} + * | 49 TREE len_Nat 17 STARtree type_Ref {tree_Ref} + * | 49 TREE len_Nat 18 BINDtree type_Ref sym_Ref name_Ref tree_Ref + * | 49 TREE len_Nat 19 UNAPPLYtree type_Ref tree_Ref {tree_Ref} + * | 49 TREE len_Nat 20 ARRAYVALUEtree type_Ref tree_Ref {tree_Ref} + * | 49 TREE len_Nat 21 FUNCTIONtree type_Ref sym_Ref tree_Ref {tree_Ref} + * | 49 TREE len_Nat 22 ASSIGNtree type_Ref tree_Ref tree_Ref + * | 49 TREE len_Nat 23 IFtree type_Ref tree_Ref tree_Ref tree_Ref + * | 49 TREE len_Nat 24 MATCHtree type_Ref tree_Ref {tree_Ref} + * | 49 TREE len_Nat 25 RETURNtree type_Ref sym_Ref tree_Ref + * | 49 TREE len_Nat 26 TREtree type_Ref tree_Ref tree_Ref {tree_Ref} + * | 49 TREE len_Nat 27 THROWtree type_Ref tree_Ref + * | 49 TREE len_Nat 28 NEWtree type_Ref tree_Ref + * | 49 TREE len_Nat 29 TYPEDtree type_Ref tree_Ref tree_Ref + * | 49 TREE len_Nat 30 TYPEAPPLYtree type_Ref tree_Ref {tree_Ref} + * | 49 TREE len_Nat 31 APPLYtree type_Ref tree_Ref {tree_Ref} + * | 49 TREE len_Nat 32 APPLYDYNAMICtree type_Ref sym_Ref tree_Ref {tree_Ref} + * | 49 TREE len_Nat 33 SUPERtree type_Ref sym_Ref tree_Ref name_Ref + * | 49 TREE len_Nat 34 THIStree type_Ref sym_Ref name_Ref + * | 49 TREE len_Nat 35 SELECTtree type_Ref sym_Ref tree_Ref name_Ref + * | 49 TREE len_Nat 36 IDENTtree type_Ref sym_Ref name_Ref + * | 49 TREE len_Nat 37 LITERALtree type_Ref constant_Ref + * | 49 TREE len_Nat 38 TYPEtree type_Ref + * | 49 TREE len_Nat 39 ANNOTATEDtree type_Ref tree_Ref tree_Ref + * | 49 TREE len_Nat 40 SINGLETONTYPEtree type_Ref tree_Ref + * | 49 TREE len_Nat 41 SELECTFROMTYPEtree type_Ref tree_Ref name_Ref + * | 49 TREE len_Nat 42 COMPOUNDTYPEtree type_Ref tree_Ref + * | 49 TREE len_Nat 43 APPLIEDTYPEtree type_Ref tree_Ref {tree_Ref} + * | 49 TREE len_Nat 44 TYPEBOUNDStree type_Ref tree_Ref tree_Ref + * | 49 TREE len_Nat 45 EXISTENTIALTYPEtree type_Ref tree_Ref {tree_Ref} + * | 50 MODIFIERS len_Nat flags_Long privateWithin_Ref + * SymbolInfo = name_Ref owner_Ref flags_LongNat [privateWithin_Ref] info_Ref + * NameInfo = + * NumInfo = + * Ref = Nat + * AnnotInfoBody = info_Ref {annotArg_Ref} {name_Ref constAnnotArg_Ref} + * AnnotArg = Tree | Constant + * ConstAnnotArg = Constant | AnnotInfo | AnnotArgArray + * + * len is remaining length after `len'. + */ + diff --git a/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/Symbol.scala b/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/Symbol.scala new file mode 100644 index 0000000..39aab50 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/Symbol.scala @@ -0,0 +1,93 @@ +package org.neo4j.scala.util +package scalax +package rules +package scalasig + +import ScalaSigEntryParsers._ + +trait Symbol extends Flags { + def name: String + + def parent: Option[Symbol] + + def children: Seq[Symbol] + + def path: String = parent.map(_.path + ".").getOrElse("") + name +} + +case object NoSymbol extends Symbol { + def name = "" + + def parent = None + + def hasFlag(flag: Long) = false + + def children = Nil +} + +abstract class ScalaSigSymbol extends Symbol { + def applyRule[A](rule: EntryParser[A]): A = expect(rule)(entry) + + def applyScalaSigRule[A](rule: ScalaSigParsers.Parser[A]) = ScalaSigParsers.expect(rule)(entry.scalaSig) + + def entry: ScalaSig#Entry + + def index = entry.index + + lazy val children: Seq[Symbol] = applyScalaSigRule(ScalaSigParsers.symbols) filter (_.parent == Some(this)) + lazy val attributes: Seq[AttributeInfo] = applyScalaSigRule(ScalaSigParsers.attributes) filter (_.symbol == this) +} + +case class ExternalSymbol(name: String, parent: Option[Symbol], + entry: ScalaSig#Entry) extends ScalaSigSymbol { + override def toString = path + + def hasFlag(flag: Long) = false +} + +case class SymbolInfo(name: String, owner: Symbol, flags: Int, + privateWithin: Option[AnyRef], info: Int, + entry: ScalaSig#Entry) { + def symbolString(any: AnyRef) = any match { + case sym: SymbolInfoSymbol => sym.index.toString + case other => other.toString + } + + override def toString = name + ", owner=" + symbolString(owner) + ", flags=" + flags.toHexString + ", info=" + info + (privateWithin match { + case Some(any) => ", privateWithin=" + symbolString(any) + case None => " " + }) +} + +abstract class SymbolInfoSymbol extends ScalaSigSymbol { + def symbolInfo: SymbolInfo + + def entry = symbolInfo.entry + + def name = symbolInfo.name + + def parent = Some(symbolInfo.owner) + + def hasFlag(flag: Long) = (symbolInfo.flags & flag) != 0L + + lazy val infoType = applyRule(parseEntry(typeEntry)(symbolInfo.info)) +} + +case class TypeSymbol(symbolInfo: SymbolInfo) extends SymbolInfoSymbol { + override def path = name +} + +case class AliasSymbol(symbolInfo: SymbolInfo) extends SymbolInfoSymbol { + override def path = name +} + +case class ClassSymbol(symbolInfo: SymbolInfo, + thisTypeRef: Option[Int]) extends SymbolInfoSymbol { + lazy val selfType = thisTypeRef.map {(x: Int) => applyRule(parseEntry(typeEntry)(x))} +} + +case class ObjectSymbol(symbolInfo: SymbolInfo) extends SymbolInfoSymbol + +case class MethodSymbol(symbolInfo: SymbolInfo, + aliasRef: Option[Int]) extends SymbolInfoSymbol + diff --git a/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/Type.scala b/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/Type.scala new file mode 100644 index 0000000..a81ad86 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/util/scalax/rules/scalasig/Type.scala @@ -0,0 +1,49 @@ +package org.neo4j.scala.util +package scalax +package rules +package scalasig + +abstract class Type + +case object NoType extends Type + +case object NoPrefixType extends Type + +case class ThisType(symbol: Symbol) extends Type + +case class SingleType(typeRef: Type, symbol: Symbol) extends Type + +case class ConstantType(constant: Any) extends Type + +case class TypeRefType(prefix: Type, symbol: Symbol, + typeArgs: Seq[Type]) extends Type + +case class TypeBoundsType(lower: Type, upper: Type) extends Type + +case class RefinedType(classSym: Symbol, typeRefs: List[Type]) extends Type + +case class ClassInfoType(symbol: Symbol, typeRefs: Seq[Type]) extends Type + +case class ClassInfoTypeWithCons(symbol: Symbol, typeRefs: Seq[Type], + cons: String) extends Type + +case class MethodType(resultType: Type, paramSymbols: Seq[Symbol]) extends Type + +case class NullaryMethodType(resultType: Type) extends Type + +case class PolyType(typeRef: Type, symbols: Seq[TypeSymbol]) extends Type + +case class PolyTypeWithCons(typeRef: Type, symbols: Seq[TypeSymbol], + cons: String) extends Type + +case class ImplicitMethodType(resultType: Type, + paramSymbols: Seq[Symbol]) extends Type + +case class AnnotatedType(typeRef: Type, attribTreeRefs: List[Int]) extends Type + +case class AnnotatedWithSelfType(typeRef: Type, symbol: Symbol, + attribTreeRefs: List[Int]) extends Type + +case class DeBruijnIndexType(typeLevel: Int, typeIndex: Int) extends Type + +case class ExistentialType(typeRef: Type, symbols: Seq[Symbol]) extends Type diff --git a/src/test/scala/org/neo4j/scala/SigParserTest.scala b/src/test/scala/org/neo4j/scala/SigParserTest.scala new file mode 100644 index 0000000..935425a --- /dev/null +++ b/src/test/scala/org/neo4j/scala/SigParserTest.scala @@ -0,0 +1,45 @@ +package org.neo4j.scala + +import org.specs.runner.JUnit4 +import org.specs.Specification +import util.{CaseClassDeserializer, JavaType} +import CaseClassDeserializer._ + +/** + * + * @author Christopher Schmidt + * Date: 20.07.11 + * Time: 06:29 + */ + +case class Test(s: String, i: Int, ji: java.lang.Integer, d: Double, l: Long, b: Boolean) + + +class DeSerializingTest extends JUnit4(DeSerializingSpec) + +object DeSerializingSpec extends Specification { + + "DeSerializing" should { + + "able to create an instance from map" in { + val m = Map[String, AnyRef]("s" -> "sowas", "i" -> "1", "ji" -> "2", "d" -> (3.3).asInstanceOf[AnyRef], "l" -> "10", "b" -> "true") + var r = deserialize[Test](m) + + r.s must endWith("sowas") + r.i must_== (1) + r.ji must_== (2) + r.d must_== (3.3) + r.l must_== (10) + r.b must_== (true) + } + + "able to create a map from an instance" in { + var o = Test("sowas", 1, 2, 3.3, 10, true) + var resMap = serialize(o) + + resMap.size must_== 6 + resMap.get("d").get mustEqual (3.3) + resMap.get("b").get mustEqual (true) + } + } +} \ No newline at end of file From 0f777babec9ddfe051a023db9deb9dd9a3a77609 Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 25 Jul 2011 12:49:12 +0200 Subject: [PATCH 32/82] added Node Case Class serialization and test (1st version) --- pom.xml | 2 +- .../scala/org/neo4j/scala/Neo4jWrapper.scala | 31 ++++++++++++++++ ...rserTest.scala => DeSerializingTest.scala} | 35 +++++++++++++++++-- 3 files changed, 65 insertions(+), 3 deletions(-) rename src/test/scala/org/neo4j/scala/{SigParserTest.scala => DeSerializingTest.scala} (57%) diff --git a/pom.xml b/pom.xml index 0b52bad..264fd85 100644 --- a/pom.xml +++ b/pom.xml @@ -229,7 +229,7 @@ maven-surefire-plugin 2.7.2 - true + false false diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index e7790f8..d6e4e4b 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -1,6 +1,9 @@ package org.neo4j.scala import org.neo4j.graphdb.{PropertyContainer, RelationshipType, Node} +import util.CaseClassDeserializer +import CaseClassDeserializer._ +import collection.JavaConversions._ /** * Extend your class with this trait to get really neat new notation for creating @@ -20,6 +23,8 @@ trait Neo4jWrapper extends Neo4jWrapperUtil { def ds: DatabaseService + val ClassPropertyName = "__CLASS__" + /** * Execute instructions within a Neo4j transaction; rollback if exception is raised and * commit otherwise; and return the return value from the operation. @@ -41,6 +46,30 @@ trait Neo4jWrapper extends Neo4jWrapperUtil { * */ def createNode(implicit ds: DatabaseService): Node = ds.gds.createNode + + /** + * serializes a given case class into a Node instance + */ + def serializeCaseClass[T <: Product](cc: T, node: Node): Unit = { + serialize(cc).foreach { + case (name, value) => node.setProperty(name, value) + } + node.setProperty(ClassPropertyName, cc.getClass.toString) + } + + /** + * deserializes a given case class type from a given Node instance + */ + def deSerializeCaseClass[T <: Product](node: Node)(implicit m: ClassManifest[T]): T = { + val cpn = node.getProperty(ClassPropertyName).asInstanceOf[String] + val kv = for (k <- node.getPropertyKeys; v = node.getProperty(k)) yield (k -> v) + val o = deserialize[T](kv.toMap)(m) + if(cpn != null) { + if(!cpn.equalsIgnoreCase(o.getClass.toString)) + throw new IllegalArgumentException("given Case Class does not fir to stored properties") + } + o + } } /** @@ -48,6 +77,7 @@ trait Neo4jWrapper extends Neo4jWrapperUtil { */ private[scala] class NodeRelationshipMethods(node: Node) { def -->(relType: RelationshipType) = new OutgoingRelationshipBuilder(node, relType) + def <--(relType: RelationshipType) = new IncomingRelationshipBuilder(node, relType) } @@ -80,5 +110,6 @@ private[scala] class RichPropertyContainer(propertyContainer: PropertyContainer) case true => Some(propertyContainer.getProperty(property)) case _ => None } + def update(property: String, value: Any): Unit = propertyContainer.setProperty(property, value) } \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/SigParserTest.scala b/src/test/scala/org/neo4j/scala/DeSerializingTest.scala similarity index 57% rename from src/test/scala/org/neo4j/scala/SigParserTest.scala rename to src/test/scala/org/neo4j/scala/DeSerializingTest.scala index 935425a..9f97037 100644 --- a/src/test/scala/org/neo4j/scala/SigParserTest.scala +++ b/src/test/scala/org/neo4j/scala/DeSerializingTest.scala @@ -2,8 +2,9 @@ package org.neo4j.scala import org.specs.runner.JUnit4 import org.specs.Specification -import util.{CaseClassDeserializer, JavaType} +import util.{CaseClassDeserializer} import CaseClassDeserializer._ +import org.neo4j.graphdb.Node /** * @@ -17,6 +18,8 @@ case class Test(s: String, i: Int, ji: java.lang.Integer, d: Double, l: Long, b: class DeSerializingTest extends JUnit4(DeSerializingSpec) +class DeSerializing2Test extends JUnit4(DeSerializingSpec2) + object DeSerializingSpec extends Specification { "DeSerializing" should { @@ -37,9 +40,37 @@ object DeSerializingSpec extends Specification { var o = Test("sowas", 1, 2, 3.3, 10, true) var resMap = serialize(o) - resMap.size must_== 6 + resMap.size must_== 6 resMap.get("d").get mustEqual (3.3) resMap.get("b").get mustEqual (true) } } +} + +object DeSerializingSpec2 extends Specification with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider { + + def neo4jStoreDir = "/tmp/temp-neo-test2" + + "Node" should { + shareVariables() + + Runtime.getRuntime.addShutdownHook(new Thread() { + override def run() { + ds.gds.shutdown + } + }) + + "be serializable" in { + var o = Test("sowas", 1, 2, 3.3, 10, true) + var node: Node = null + withTx { + implicit neo => + node = createNode + serializeCaseClass(o, node) + } + + var oo = deSerializeCaseClass[Test](node) + oo must beEqual(o) + } + } } \ No newline at end of file From f93d403aa7c7b87d5e2c139433843611761b91e6 Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 25 Jul 2011 13:07:14 +0200 Subject: [PATCH 33/82] removed unused import --- src/main/scala/org/neo4j/scala/util/Poso.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/org/neo4j/scala/util/Poso.scala b/src/main/scala/org/neo4j/scala/util/Poso.scala index 072fc9d..2cca830 100644 --- a/src/main/scala/org/neo4j/scala/util/Poso.scala +++ b/src/main/scala/org/neo4j/scala/util/Poso.scala @@ -1,6 +1,5 @@ package org.neo4j.scala.util -import org.neo4j.scala.so.neo import scalax.rules.scalasig._ import collection.mutable.ArrayBuffer From 72d1dcd092215384f3f186c21d2445d114fa132a Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 25 Jul 2011 16:21:55 +0200 Subject: [PATCH 34/82] added access to relations with "<" operator --- src/main/scala/org/neo4j/scala/Neo4jWrapper.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index d6e4e4b..4393b8d 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -1,9 +1,9 @@ package org.neo4j.scala -import org.neo4j.graphdb.{PropertyContainer, RelationshipType, Node} import util.CaseClassDeserializer import CaseClassDeserializer._ import collection.JavaConversions._ +import org.neo4j.graphdb.{Relationship, PropertyContainer, RelationshipType, Node} /** * Extend your class with this trait to get really neat new notation for creating @@ -75,10 +75,10 @@ trait Neo4jWrapper extends Neo4jWrapperUtil { /** * creates incoming and outgoing relationships */ -private[scala] class NodeRelationshipMethods(node: Node) { +private[scala] class NodeRelationshipMethods(node: Node, rel:Relationship = null) { def -->(relType: RelationshipType) = new OutgoingRelationshipBuilder(node, relType) - def <--(relType: RelationshipType) = new IncomingRelationshipBuilder(node, relType) + def < = rel } /** @@ -86,8 +86,8 @@ private[scala] class NodeRelationshipMethods(node: Node) { */ private[scala] class OutgoingRelationshipBuilder(fromNode: Node, relType: RelationshipType) { def -->(toNode: Node) = { - fromNode.createRelationshipTo(toNode, relType) - new NodeRelationshipMethods(toNode) + val rel = fromNode.createRelationshipTo(toNode, relType) + new NodeRelationshipMethods(toNode, rel) } } @@ -96,8 +96,8 @@ private[scala] class OutgoingRelationshipBuilder(fromNode: Node, relType: Relati */ private[scala] class IncomingRelationshipBuilder(toNode: Node, relType: RelationshipType) { def <--(fromNode: Node) = { - fromNode.createRelationshipTo(toNode, relType) - new NodeRelationshipMethods(fromNode) + val rel = fromNode.createRelationshipTo(toNode, relType) + new NodeRelationshipMethods(fromNode, rel) } } From 29fa6734aff77754a496f0bceba8d4a0df9726e5 Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 25 Jul 2011 16:41:15 +0200 Subject: [PATCH 35/82] added convenience method for node creation and serialization --- .../scala/org/neo4j/scala/Neo4jWrapper.scala | 18 +++++++++++++----- .../org/neo4j/scala/DeSerializingTest.scala | 3 +-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index 4393b8d..d69e378 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -43,18 +43,24 @@ trait Neo4jWrapper extends Neo4jWrapperUtil { } /** - * + * creates a new Node from Database service */ def createNode(implicit ds: DatabaseService): Node = ds.gds.createNode + /** + * convenience method to create and serialize + */ + def createNode[T <: Product](cc: T)(implicit ds: DatabaseService): Node = serializeCaseClass(cc, createNode) + /** * serializes a given case class into a Node instance */ - def serializeCaseClass[T <: Product](cc: T, node: Node): Unit = { + def serializeCaseClass[T <: Product](cc: T, node: Node): Node = { serialize(cc).foreach { case (name, value) => node.setProperty(name, value) } node.setProperty(ClassPropertyName, cc.getClass.toString) + node } /** @@ -64,8 +70,8 @@ trait Neo4jWrapper extends Neo4jWrapperUtil { val cpn = node.getProperty(ClassPropertyName).asInstanceOf[String] val kv = for (k <- node.getPropertyKeys; v = node.getProperty(k)) yield (k -> v) val o = deserialize[T](kv.toMap)(m) - if(cpn != null) { - if(!cpn.equalsIgnoreCase(o.getClass.toString)) + if (cpn != null) { + if (!cpn.equalsIgnoreCase(o.getClass.toString)) throw new IllegalArgumentException("given Case Class does not fir to stored properties") } o @@ -75,9 +81,11 @@ trait Neo4jWrapper extends Neo4jWrapperUtil { /** * creates incoming and outgoing relationships */ -private[scala] class NodeRelationshipMethods(node: Node, rel:Relationship = null) { +private[scala] class NodeRelationshipMethods(node: Node, rel: Relationship = null) { def -->(relType: RelationshipType) = new OutgoingRelationshipBuilder(node, relType) + def <--(relType: RelationshipType) = new IncomingRelationshipBuilder(node, relType) + def < = rel } diff --git a/src/test/scala/org/neo4j/scala/DeSerializingTest.scala b/src/test/scala/org/neo4j/scala/DeSerializingTest.scala index 9f97037..4cd11b3 100644 --- a/src/test/scala/org/neo4j/scala/DeSerializingTest.scala +++ b/src/test/scala/org/neo4j/scala/DeSerializingTest.scala @@ -65,8 +65,7 @@ object DeSerializingSpec2 extends Specification with Neo4jWrapper with EmbeddedG var node: Node = null withTx { implicit neo => - node = createNode - serializeCaseClass(o, node) + node = createNode(o) } var oo = deSerializeCaseClass[Test](node) From 5042267a839d3d1c9f205bdb613415bf25a7224f Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 25 Jul 2011 20:52:22 +0200 Subject: [PATCH 36/82] simpler names --- src/main/scala/org/neo4j/scala/Neo4jWrapper.scala | 13 ++++++------- .../scala/org/neo4j/scala/DeSerializingTest.scala | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index d69e378..0c21ff2 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -1,7 +1,6 @@ package org.neo4j.scala import util.CaseClassDeserializer -import CaseClassDeserializer._ import collection.JavaConversions._ import org.neo4j.graphdb.{Relationship, PropertyContainer, RelationshipType, Node} @@ -50,13 +49,13 @@ trait Neo4jWrapper extends Neo4jWrapperUtil { /** * convenience method to create and serialize */ - def createNode[T <: Product](cc: T)(implicit ds: DatabaseService): Node = serializeCaseClass(cc, createNode) + def createNode[T <: Product](cc: T)(implicit ds: DatabaseService): Node = serialize(cc, createNode) /** * serializes a given case class into a Node instance */ - def serializeCaseClass[T <: Product](cc: T, node: Node): Node = { - serialize(cc).foreach { + def serialize[T <: Product](cc: T, node: Node): Node = { + CaseClassDeserializer.serialize(cc).foreach { case (name, value) => node.setProperty(name, value) } node.setProperty(ClassPropertyName, cc.getClass.toString) @@ -66,13 +65,13 @@ trait Neo4jWrapper extends Neo4jWrapperUtil { /** * deserializes a given case class type from a given Node instance */ - def deSerializeCaseClass[T <: Product](node: Node)(implicit m: ClassManifest[T]): T = { + def deSerialize[T <: Product](node: Node)(implicit m: ClassManifest[T]): T = { val cpn = node.getProperty(ClassPropertyName).asInstanceOf[String] val kv = for (k <- node.getPropertyKeys; v = node.getProperty(k)) yield (k -> v) - val o = deserialize[T](kv.toMap)(m) + val o = CaseClassDeserializer.deserialize[T](kv.toMap)(m) if (cpn != null) { if (!cpn.equalsIgnoreCase(o.getClass.toString)) - throw new IllegalArgumentException("given Case Class does not fir to stored properties") + throw new IllegalArgumentException("given Case Class does not fit to serialized properties") } o } diff --git a/src/test/scala/org/neo4j/scala/DeSerializingTest.scala b/src/test/scala/org/neo4j/scala/DeSerializingTest.scala index 4cd11b3..afcde2a 100644 --- a/src/test/scala/org/neo4j/scala/DeSerializingTest.scala +++ b/src/test/scala/org/neo4j/scala/DeSerializingTest.scala @@ -68,7 +68,7 @@ object DeSerializingSpec2 extends Specification with Neo4jWrapper with EmbeddedG node = createNode(o) } - var oo = deSerializeCaseClass[Test](node) + var oo = deSerialize[Test](node) oo must beEqual(o) } } From 635d4470f6e5b405c4c28377c84313dfc15d4973 Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 26 Jul 2011 06:36:19 +0200 Subject: [PATCH 37/82] added some geometry creation convenience methods --- .../org/neo4j/scala/util/SpatialUtil.scala | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/main/scala/org/neo4j/scala/util/SpatialUtil.scala b/src/main/scala/org/neo4j/scala/util/SpatialUtil.scala index 2a8fc63..d78b52f 100644 --- a/src/main/scala/org/neo4j/scala/util/SpatialUtil.scala +++ b/src/main/scala/org/neo4j/scala/util/SpatialUtil.scala @@ -17,9 +17,30 @@ import collection.JavaConversions._ private[scala] class AddGeometry(layer: EditableLayer) { val gf = layer.getGeometryFactory + /** + * Points + */ def newPoint(coordinate: Coordinate): SpatialDatabaseRecord = layer.add(gf.createPoint(coordinate)) + def newMultiPoint(points: Array[Point]) = layer.add(gf.createMultiPoint(points)) + + def newMultiPoint(coordinates: CoordinateArraySequence) = layer.add(gf.createMultiPoint(coordinates)) + + /** + * Polygon + */ def newPolygon(shell: LinearRing, holes: Array[LinearRing] = null) = layer.add(gf.createPolygon(shell, holes)) + + def newMultiPolygon(polygons: Array[Polygon]) = layer.add(gf.createMultiPolygon(polygons)) + + /** + * Line String + */ + def newLineString(coordinates: Array[Coordinate]) = layer.add(gf.createLineString(coordinates)) + + def newLineString(coordinates: CoordinateArraySequence) = layer.add(gf.createLineString(coordinates)) + + def newMultiLineString(lineStrings: Array[LineString]) = layer.add(gf.createMultiLineString(lineStrings)) } @@ -49,4 +70,15 @@ object LinRing { def apply(b: Buffer[(Double, Double)])(implicit layer: EditableLayer) = new LinearRing(CoordArraySequence(b), layer.getGeometryFactory) +} + +/** + * convenience object for LineString + */ +object lineString { + def apply(cs: CoordinateSequence)(implicit layer: EditableLayer) = + new LineString(cs, layer.getGeometryFactory) + + def apply(b: Buffer[(Double, Double)])(implicit layer: EditableLayer) = + new LineString(CoordArraySequence(b), layer.getGeometryFactory) } \ No newline at end of file From 0dc7849c3c1000a715de7f8aba75badfa3d58575 Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 26 Jul 2011 06:38:45 +0200 Subject: [PATCH 38/82] removed import --- src/main/scala/org/neo4j/scala/util/SpatialUtil.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/org/neo4j/scala/util/SpatialUtil.scala b/src/main/scala/org/neo4j/scala/util/SpatialUtil.scala index d78b52f..ee82214 100644 --- a/src/main/scala/org/neo4j/scala/util/SpatialUtil.scala +++ b/src/main/scala/org/neo4j/scala/util/SpatialUtil.scala @@ -4,7 +4,6 @@ import org.neo4j.gis.spatial._ import collection.mutable.Buffer import com.vividsolutions.jts.geom.impl.CoordinateArraySequence import com.vividsolutions.jts.geom._ -import collection.JavaConversions._ /** * Really simple and very incomplete list of spatial utility classes From 38eeb43c3ac0511f671059afb2f88544e7656f41 Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 27 Jul 2011 06:38:29 +0200 Subject: [PATCH 39/82] cleaned up some searches --- .../neo4j/scala/Neo4jSpatialWrapperUtil.scala | 28 +++++++++++-------- .../org/neo4j/scala/Neo4jSpatialTest.scala | 4 +-- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala index abbb2a9..f8b4839 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala @@ -23,20 +23,20 @@ trait Neo4jSpatialWrapperUtil { * Search convenience defs */ - def withSearchWithin[T <: Any](geometry: Geometry)(operation: (SearchWithin) => T): T = { - val search = new SearchWithin(geometry) + def withSearchWithinDistance[T](point: Point, distance: Double)(operation: (SearchWithinDistance) => T): T = { + val search = new SearchWithinDistance(point, distance) operation(search) } - def searchWithinDistance(point: Point, distance:Double)(implicit layer: EditableLayer) = { - val search = new SearchWithinDistance(point, distance) - layer.getIndex.executeSearch(search) - val result: Buffer[SpatialDatabaseRecord] = search.getResults - result - } + def searchWithinDistance(point: Point, distance: Double)(implicit layer: EditableLayer) = { + val search = new SearchWithinDistance(point, distance) + layer.getIndex.executeSearch(search) + val result: Buffer[SpatialDatabaseRecord] = search.getResults + result + } /** - * handles most of the searches with one Geometry parameter + * handles the searches with one Geometry parameter */ def search[T <: AbstractSearch](geometry: Geometry)(implicit layer: EditableLayer, m: ClassManifest[T]) = { val ctor = m.erasure.getConstructor(classOf[Geometry]) @@ -46,9 +46,15 @@ trait Neo4jSpatialWrapperUtil { result } - def executeSearch(implicit search: SearchWithin, layer: EditableLayer) = layer.getIndex.executeSearch(search) + def withSearch[T <: AbstractSearch](geometry: Geometry)(operation: (AbstractSearch) => Unit)(implicit m: ClassManifest[T]): Unit = { + val ctor = m.erasure.getConstructor(classOf[Geometry]) + val search = ctor.newInstance(geometry).asInstanceOf[T] + operation(search) + } + + def executeSearch(implicit search: Search, layer: EditableLayer) = layer.getIndex.executeSearch(search) - def getResults(implicit search: SearchWithin) = search.getResults + def getResults(implicit search: Search) = search.getResults /** * node convenience defs diff --git a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala index 91e532b..1ee8984 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala +++ b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala @@ -74,14 +74,14 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe munich --> "CapitalCityOf" --> bayern - withSearchWithin(bayern.getGeometry) { + withSearch[SearchWithin](bayern.getGeometry) { implicit s => executeSearch for (r <- getResults) r.getProperty("City") must beEqual("Munich") } - withSearchWithin(toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0))) { + withSearch[SearchWithin](toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0))) { implicit s => executeSearch getResults.size must_== 2 From 14cbd75599120f5c05430fda72ed637edd6bf3a0 Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 27 Jul 2011 06:43:34 +0200 Subject: [PATCH 40/82] reformat --- .../scala/org/neo4j/scala/Neo4jSpatialTest.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala index 1ee8984..b24289e 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala +++ b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala @@ -48,12 +48,12 @@ object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with Embe implicit db => // remove existing layer - try { - deleteLayer("test", new NullListener) - } - catch { - case _ => - } + try { + deleteLayer("test", new NullListener) + } + catch { + case _ => + } val cities = createNode val federalStates = createNode From 2154b629388446f34f82e8790f0f123c617093fb Mon Sep 17 00:00:00 2001 From: FaKod Date: Fri, 26 Aug 2011 21:22:49 +0200 Subject: [PATCH 41/82] =?UTF-8?q?nodeIndex=20+=3D=20(=E2=80=A6)=20adding?= =?UTF-8?q?=20to=20index=20convenience=20implicit=20conversion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/neo4j/scala/Neo4jIndexProvider.scala | 16 ++++++++++++++-- src/test/scala/org/neo4j/scala/IndexTest.scala | 5 +++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala index 8b16f4a..3c7f0d4 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala @@ -1,9 +1,9 @@ package org.neo4j.scala -import org.neo4j.graphdb.Node import org.neo4j.graphdb.index.{Index, RelationshipIndex} import scala.collection.mutable.{Map => mutableMap} import collection.JavaConversions._ +import org.neo4j.graphdb.{PropertyContainer, Node} /** * Provides Index access as trait @@ -83,6 +83,18 @@ trait Neo4jIndexProvider { /** * conversion to ease the use of optional configuration */ - implicit def mapToOptionMap(t:(String, Map[String, String])) = (t._1, Option(t._2)) + implicit def mapToOptionMap(t: (String, Map[String, String])) = (t._1, Option(t._2)) + + /** + * wrapper class for subsequent implicit conversion + */ + class IndexWrapper[T <: PropertyContainer](i: Index[T]) { + def +=(t: T, k: String, v: AnyRef) = i.add(t, k, v) + } + + /** + * more convenient index adding + */ + implicit def indexToRichIndex[T <: PropertyContainer](i: Index[T]) = new IndexWrapper[T](i) } \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/IndexTest.scala b/src/test/scala/org/neo4j/scala/IndexTest.scala index 561423d..15fd957 100644 --- a/src/test/scala/org/neo4j/scala/IndexTest.scala +++ b/src/test/scala/org/neo4j/scala/IndexTest.scala @@ -41,8 +41,9 @@ with SpatialDatabaseServiceProvider with Neo4jIndexProvider { val theMatrixReloaded = createNode theMatrixReloaded.setProperty("name", "theMatrixReloaded") - nodeIndex.add(theMatrix, "title", "The Matrix") - nodeIndex.add(theMatrixReloaded, "title", "The Matrix Reloaded") + nodeIndex += (theMatrix, "title", "The Matrix") + nodeIndex += (theMatrixReloaded, "title", "The Matrix Reloaded") + // search in the fulltext index val found = nodeIndex.query("title", "reloAdEd") found.size must beGreaterThanOrEqualTo(1) From 226676c28a5d589873e313db11f7033b807cb495 Mon Sep 17 00:00:00 2001 From: holdensmagicalunicorn Date: Sun, 28 Aug 2011 15:18:15 -0700 Subject: [PATCH 42/82] Spelling correction in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d03c88a..249c1f5 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ The Neo4j Scala wrapper library allows you the [Neo4j open source graph database domain-specific simplified language. It is written in Scala and is intended to be used in other Scala projects. -This wrapper is mostly based on the work done by [Martin Kleppmann](http://twitter.com/martinkl) in his [Scala implementation of RESTful JSON HTTP resources on top of the Neo4j graph database and Jersey](http://github.com/ept/neo4j-resources) project. I thought it'd be usefull to extract the Neo4j DSL into a seperate project, and Marting agreed to this. +This wrapper is mostly based on the work done by [Martin Kleppmann](http://twitter.com/martinkl) in his [Scala implementation of RESTful JSON HTTP resources on top of the Neo4j graph database and Jersey](http://github.com/ept/neo4j-resources) project. I thought it'd be useful to extract the Neo4j DSL into a separate project, and Marting agreed to this. Building From 07e54c97a5f66571e3ad8d31fee9e0f7e28b25fb Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 29 Aug 2011 06:27:34 +0200 Subject: [PATCH 43/82] added remove from index convenience --- src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala index 3c7f0d4..2403bf4 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala @@ -90,6 +90,9 @@ trait Neo4jIndexProvider { */ class IndexWrapper[T <: PropertyContainer](i: Index[T]) { def +=(t: T, k: String, v: AnyRef) = i.add(t, k, v) + def -=(t: T, k: String, v: AnyRef) = i.remove(t, k, v) + def -=(t: T, k: String) = i.remove(t, k) + def -=(t: T) = i.remove(t) } /** From b1647bb77cc2bb35140a8b125173c4c5d4d02ba3 Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 30 Aug 2011 21:06:14 +0200 Subject: [PATCH 44/82] changed class property from toString to getName --- src/main/scala/org/neo4j/scala/Neo4jWrapper.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index 0c21ff2..6d22d18 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -58,7 +58,7 @@ trait Neo4jWrapper extends Neo4jWrapperUtil { CaseClassDeserializer.serialize(cc).foreach { case (name, value) => node.setProperty(name, value) } - node.setProperty(ClassPropertyName, cc.getClass.toString) + node.setProperty(ClassPropertyName, cc.getClass.getName) node } @@ -70,7 +70,7 @@ trait Neo4jWrapper extends Neo4jWrapperUtil { val kv = for (k <- node.getPropertyKeys; v = node.getProperty(k)) yield (k -> v) val o = CaseClassDeserializer.deserialize[T](kv.toMap)(m) if (cpn != null) { - if (!cpn.equalsIgnoreCase(o.getClass.toString)) + if (!cpn.equalsIgnoreCase(o.getClass.getName)) throw new IllegalArgumentException("given Case Class does not fit to serialized properties") } o From 348c30acc857a4529cf538db0d85fb108a3aaac1 Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 14 Sep 2011 06:33:45 +0200 Subject: [PATCH 45/82] removed spatial wrapper stuff and - switched to Neo4j 1.4.1, Scala 2.9.1 - some refactoring - some more comments - cleaned up pom.xml --- pom.xml | 191 +++--------------- .../org/neo4j/scala/DatabaseService.scala | 12 +- .../neo4j/scala/DatabaseServiceProvider.scala | 31 +-- .../org/neo4j/scala/Neo4jIndexProvider.scala | 5 +- .../org/neo4j/scala/Neo4jSpatialWrapper.scala | 45 ----- .../neo4j/scala/Neo4jSpatialWrapperUtil.scala | 124 ------------ .../scala/org/neo4j/scala/Neo4jWrapper.scala | 54 +++-- ...Util.scala => Neo4jWrapperImplicits.scala} | 19 +- .../org/neo4j/scala/util/SpatialUtil.scala | 83 -------- .../org/neo4j/scala/Neo4jSpatialTest.scala | 93 --------- .../scala/SpatialTestsUsingTestNodes.scala | 75 ------- .../scala/org/neo4j/scala/TestNodes.scala | 102 ---------- .../{ => unittest}/DeSerializingTest.scala | 27 +-- .../scala/{ => unittest}/IndexTest.scala | 18 +- .../{ => unittest}/Neo4jWrapperTest.scala | 16 +- 15 files changed, 123 insertions(+), 772 deletions(-) delete mode 100644 src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala delete mode 100644 src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala rename src/main/scala/org/neo4j/scala/{Neo4jWrapperUtil.scala => Neo4jWrapperImplicits.scala} (70%) delete mode 100644 src/main/scala/org/neo4j/scala/util/SpatialUtil.scala delete mode 100644 src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala delete mode 100644 src/test/scala/org/neo4j/scala/SpatialTestsUsingTestNodes.scala delete mode 100644 src/test/scala/org/neo4j/scala/TestNodes.scala rename src/test/scala/org/neo4j/scala/{ => unittest}/DeSerializingTest.scala (70%) rename src/test/scala/org/neo4j/scala/{ => unittest}/IndexTest.scala (69%) rename src/test/scala/org/neo4j/scala/{ => unittest}/Neo4jWrapperTest.scala (94%) diff --git a/pom.xml b/pom.xml index 264fd85..164c3ef 100644 --- a/pom.xml +++ b/pom.xml @@ -1,37 +1,24 @@ 4.0.0 - + org.neo4j neo4j-scala jar Neo4j Scala - 0.9.9-SNAPSHOT + 0.1.0-SNAPSHOT Scala wrapper for Neo4j Graph Database - http://github.com/jawher/neo4j-scala - 2009 + http://github.com/fakod/neo4j-scala + 2011 UTF-8 - 2.9.0-1 - 1.3.M03 - 1.3.M03 - 0.5-SNAPSHOT - 2.7-M3 + 2.9.1 + 1.4.1 + 1.4.1 - - jawher - Jawher Moussa - http://twitter.com/jawher - +1 - - - martin - Martin Kleppmann - http://twitter.com/martinkl - FaKod Christopher Schmidt @@ -43,19 +30,6 @@ - - - The MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - scm:git:git://github.com/jawher/neo4j-scala.git - scm:git:git://github.com/jawher/neo4j-scala.git - - - scala-tools.org @@ -66,36 +40,8 @@ neo4j-public-repository http://m2.neo4j.org - - osgeo - Open Source Geospatial Foundation Repository - http://download.osgeo.org/webdav/geotools/ - - - - scala-tools.org - Scala-Tools Maven2 Repository - http://scala-tools.org/repo-releases - - - - - - - neo4j-scala.embedded.module - neo4j-scala Embedded Repository - file://${basedir}/maven-repo-neo4j-scala/releases - - - - neo4j-scala.embedded.module - neo4j-scala Embedded Repository - file://${basedir}/maven-repo-neo4j-scala/snapshot - - - org.scala-lang @@ -108,10 +54,18 @@ 4.7 test + + + org.specs2 + specs2_2.9.1 + 1.6.1 + test + + - org.scala-tools.testing - specs_2.8.1 - 1.6.7 + org.specs2 + specs2-scalaz-core_2.9.1 + 6.0.1 test @@ -126,68 +80,19 @@ neo4j-lucene-index ${neo4j.version} - - org.neo4j - neo4j-spatial - ${neo4j.spatial.version} - org.neo4j neo4j-shell ${neo4j.shell.version} - - - - org.geotools - gt-main - ${geotools.version} - - - - - - - - org.geotools - gt-shapefile - ${geotools.version} - - - - - - org.geotools - gt-render - ${geotools.version} - - - - - - it.geosolutions.imageio-ext - imageio-ext-tiff - - - - src/main/scala - src/test/scala - - org.scala-tools maven-scala-plugin - 2.14 + 2.15.2 @@ -226,15 +131,16 @@ + org.apache.maven.plugins maven-surefire-plugin - 2.7.2 + 2.9 - false - false + true - + **/unittest/*.java + org.apache.maven.plugins @@ -260,55 +166,6 @@ - - - - - diff --git a/src/main/scala/org/neo4j/scala/DatabaseService.scala b/src/main/scala/org/neo4j/scala/DatabaseService.scala index a881a04..18a1446 100644 --- a/src/main/scala/org/neo4j/scala/DatabaseService.scala +++ b/src/main/scala/org/neo4j/scala/DatabaseService.scala @@ -1,25 +1,19 @@ package org.neo4j.scala import org.neo4j.graphdb.GraphDatabaseService -import org.neo4j.gis.spatial.SpatialDatabaseService /** * Interface for GraphDatabaseService - * * @author Christopher Schmidt + * */ trait DatabaseService { def gds: GraphDatabaseService } /** - * standard DatabaseService store for GraphDatabaseService + * standard DatabaseService implementation + * for GraphDatabaseService */ case class DatabaseServiceImpl(gds: GraphDatabaseService) extends DatabaseService -/** - * extended store for combined GraphDatabaseService and SpatialDatabaseService - * used by Neo4jSpatialWrapper - */ -case class CombinedDatabaseService(gds: GraphDatabaseService, sds: SpatialDatabaseService) extends DatabaseService - diff --git a/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala b/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala index 2ff2a5b..0dc49bc 100644 --- a/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala +++ b/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala @@ -1,25 +1,30 @@ package org.neo4j.scala import org.neo4j.kernel.EmbeddedGraphDatabase -import org.neo4j.gis.spatial.SpatialDatabaseService /** - * provides an embedded database service - * - * @author Christopher Schmidt + * Interface for a GraphDatabaseServiceProvider + * must be implemented by and Graph Database Service Provider */ -trait EmbeddedGraphDatabaseServiceProvider { +trait GraphDatabaseServiceProvider { + val ds: DatabaseService +} + + +/** + * provides a specific Database Service + * in this case an embedded database service + */ +trait EmbeddedGraphDatabaseServiceProvider extends GraphDatabaseServiceProvider { + /** + * directory where to store the data files + */ def neo4jStoreDir: String + /** + * using an instance of an embedded graph database + */ val ds: DatabaseService = DatabaseServiceImpl(new EmbeddedGraphDatabase(neo4jStoreDir)) } -/** - * provides an spatial database service from a given GraphDatabaseService - */ -trait SpatialDatabaseServiceProvider { - self: EmbeddedGraphDatabaseServiceProvider => - - val sds = new SpatialDatabaseService(ds.gds) -} \ No newline at end of file diff --git a/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala index 2403bf4..c54218d 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala @@ -6,11 +6,8 @@ import collection.JavaConversions._ import org.neo4j.graphdb.{PropertyContainer, Node} /** - * Provides Index access as trait - * + * Provides Index access trait * @author Christopher Schmidt - * Date: 11.04.11 - * Time: 20:27 */ trait Neo4jIndexProvider { diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala deleted file mode 100644 index de1ccae..0000000 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapper.scala +++ /dev/null @@ -1,45 +0,0 @@ -package org.neo4j.scala - -import org.neo4j.gis.spatial.{EditableLayer, SpatialDatabaseService} - -/** - * Basic Neo4j Spatial Wrapper trait - * - * @author Christopher Schmidt - * Date: 16.03.11 - * Time: 16:18 - */ -trait Neo4jSpatialWrapper extends Neo4jWrapper with Neo4jSpatialWrapperUtil { - - implicit val ds: DatabaseService - - implicit val sds: SpatialDatabaseService - - /** - * Execute instructions within a Neo4j transaction; rollback if exception is raised and - * commit otherwise; and return the return value from the operation. - */ - def withSpatialTx[T <: Any](operation: CombinedDatabaseService => T): T = { - val tx = synchronized { - ds.gds.beginTx - } - try { - val ret = operation(CombinedDatabaseService(ds.gds, sds)) - tx.success - return ret - } finally { - tx.finish - } - } - - /** - * retrieves the layer object and executes operation - */ - def withLayer[T <: Any](getLayer: => EditableLayer)(operation: EditableLayer => T): T = { - val layer = getLayer - operation(layer) - } -} - - - diff --git a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala b/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala deleted file mode 100644 index f8b4839..0000000 --- a/src/main/scala/org/neo4j/scala/Neo4jSpatialWrapperUtil.scala +++ /dev/null @@ -1,124 +0,0 @@ -package org.neo4j.scala - - -import org.neo4j.gis.spatial._ -import collection.mutable.Buffer -import com.vividsolutions.jts.geom._ -import org.neo4j.graphdb.{Node, GraphDatabaseService} -import collection.JavaConversions._ -import query.{SearchWithinDistance, SearchWithin} -import util.{AddGeometry, Coord} - -/** - * Util and implicits Trait for spatial stuff - * extended by spatial wrapper - * - * @author Christopher Schmidt - * Date: 14.04.11 - * Time: 06:15 - */ -trait Neo4jSpatialWrapperUtil { - - /** - * Search convenience defs - */ - - def withSearchWithinDistance[T](point: Point, distance: Double)(operation: (SearchWithinDistance) => T): T = { - val search = new SearchWithinDistance(point, distance) - operation(search) - } - - def searchWithinDistance(point: Point, distance: Double)(implicit layer: EditableLayer) = { - val search = new SearchWithinDistance(point, distance) - layer.getIndex.executeSearch(search) - val result: Buffer[SpatialDatabaseRecord] = search.getResults - result - } - - /** - * handles the searches with one Geometry parameter - */ - def search[T <: AbstractSearch](geometry: Geometry)(implicit layer: EditableLayer, m: ClassManifest[T]) = { - val ctor = m.erasure.getConstructor(classOf[Geometry]) - val search = ctor.newInstance(geometry).asInstanceOf[T] - layer.getIndex.executeSearch(search) - val result: Buffer[SpatialDatabaseRecord] = search.getResults - result - } - - def withSearch[T <: AbstractSearch](geometry: Geometry)(operation: (AbstractSearch) => Unit)(implicit m: ClassManifest[T]): Unit = { - val ctor = m.erasure.getConstructor(classOf[Geometry]) - val search = ctor.newInstance(geometry).asInstanceOf[T] - operation(search) - } - - def executeSearch(implicit search: Search, layer: EditableLayer) = layer.getIndex.executeSearch(search) - - def getResults(implicit search: Search) = search.getResults - - /** - * node convenience defs - */ - - implicit def IsSpatialDatabaseRecordToNode(r: IsSpatialDatabaseRecord): Node = r.node.getGeomNode - - implicit def record2relationshipBuilder(record: IsSpatialDatabaseRecord) = new NodeRelationshipMethods(record.node) - - /** - * Database Record convenience defs - */ - - // converts SpatialDatabaseRecord to Node - implicit def spatialDatabaseRecordToNode(sdr: SpatialDatabaseRecord): Node = sdr.getGeomNode - - // delegation to Neo4jWrapper - implicit def node2relationshipBuilder(sdr: SpatialDatabaseRecord) = new NodeRelationshipMethods(sdr.getGeomNode) - - implicit def nodeToSpatialDatabaseRecord(node: Node)(implicit layer: Layer): SpatialDatabaseRecord = - new SpatialDatabaseRecord(layer, node) - - /** - * DatabaseService Wrapper - */ - - def deleteLayer(name: String, monitor: Listener)(implicit db: CombinedDatabaseService) = - db.sds.deleteLayer(name, monitor) - - def getOrCreateEditableLayer(name: String)(implicit db: CombinedDatabaseService): EditableLayer = - db.sds.getOrCreateEditableLayer(name) - - def createEditableLayer(name: String)(implicit db: CombinedDatabaseService): EditableLayer = - db.sds.createLayer(name, classOf[WKBGeometryEncoder], classOf[EditableLayerImpl]).asInstanceOf[EditableLayer] - - def getLayerNames(implicit db: CombinedDatabaseService) = db.sds.getLayerNames - - def getLayer(name: String)(implicit db: CombinedDatabaseService): Layer = - db.sds.getLayer(name) - - /** - * methods from Neo4jWrapper usage should still be possible - */ - implicit def databaseServiceToGraphDatabaseService(ds: CombinedDatabaseService): GraphDatabaseService = ds.gds - - /** - * Layer Wrapper - */ - - implicit def tupleToCoordinate(t: (Double, Double)): Coordinate = Coord(t._1, t._2) - - def getGeometryFactory(implicit layer: EditableLayer) = layer.getGeometryFactory - - def toGeometry(envelope: Envelope)(implicit layer: EditableLayer): Geometry = getGeometryFactory.toGeometry(envelope) - - //def executeSearch(search: Search)(implicit layer: EditableLayer): Unit = layer.getIndex.executeSearch(search) - - def add(implicit layer: EditableLayer) = new AddGeometry(layer) - -} - -/** - * container trait to hold an instance of SpatialDatabaseRecord - */ -trait IsSpatialDatabaseRecord { - val node: SpatialDatabaseRecord -} \ No newline at end of file diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index 6d22d18..218a299 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -3,6 +3,7 @@ package org.neo4j.scala import util.CaseClassDeserializer import collection.JavaConversions._ import org.neo4j.graphdb.{Relationship, PropertyContainer, RelationshipType, Node} +import CaseClassDeserializer._ /** * Extend your class with this trait to get really neat new notation for creating @@ -15,13 +16,14 @@ import org.neo4j.graphdb.{Relationship, PropertyContainer, RelationshipType, Nod * * can be replaced with a beautiful Scala one-liner: *
start --> "KNOWS" --> intermediary --> "KNOWS" --> end
- * - * Feel free to use this example to tell all your friends how awesome scala is :) */ -trait Neo4jWrapper extends Neo4jWrapperUtil { - - def ds: DatabaseService +trait Neo4jWrapper extends GraphDatabaseServiceProvider with Neo4jWrapperImplicits { + /** + * this name will be used to store the class name of + * the serialized case class that will be verified + * in deserialization + */ val ClassPropertyName = "__CLASS__" /** @@ -47,7 +49,7 @@ trait Neo4jWrapper extends Neo4jWrapperUtil { def createNode(implicit ds: DatabaseService): Node = ds.gds.createNode /** - * convenience method to create and serialize + * convenience method to create and serialize a case class */ def createNode[T <: Product](cc: T)(implicit ds: DatabaseService): Node = serialize(cc, createNode) @@ -58,22 +60,26 @@ trait Neo4jWrapper extends Neo4jWrapperUtil { CaseClassDeserializer.serialize(cc).foreach { case (name, value) => node.setProperty(name, value) } - node.setProperty(ClassPropertyName, cc.getClass.getName) + node(ClassPropertyName) = cc.getClass.getName node } /** * deserializes a given case class type from a given Node instance + * throws a IllegalArgumentException if a Nodes properties + * do not fit to the case class properties */ def deSerialize[T <: Product](node: Node)(implicit m: ClassManifest[T]): T = { - val cpn = node.getProperty(ClassPropertyName).asInstanceOf[String] - val kv = for (k <- node.getPropertyKeys; v = node.getProperty(k)) yield (k -> v) - val o = CaseClassDeserializer.deserialize[T](kv.toMap)(m) - if (cpn != null) { - if (!cpn.equalsIgnoreCase(o.getClass.getName)) - throw new IllegalArgumentException("given Case Class does not fit to serialized properties") + node[String](ClassPropertyName) match { + case Some(cpn) => + val kv = for (k <- node.getPropertyKeys; v = node.getProperty(k)) yield (k -> v) + val o = deserialize[T](kv.toMap)(m) + if (!cpn.equalsIgnoreCase(o.getClass.getName)) + throw new IllegalArgumentException("given Case Class does not fit to serialized properties") + o + case _ => + throw new IllegalArgumentException("this is not a Node with a serialized case class") } - o } } @@ -85,6 +91,10 @@ private[scala] class NodeRelationshipMethods(node: Node, rel: Relationship = nul def <--(relType: RelationshipType) = new IncomingRelationshipBuilder(node, relType) + /** + * use this to get the created relationship object + *
start --> "KNOWS" --> end <;
+ */ def < = rel } @@ -112,11 +122,21 @@ private[scala] class IncomingRelationshipBuilder(toNode: Node, relType: Relation * convenience for handling properties */ private[scala] class RichPropertyContainer(propertyContainer: PropertyContainer) { - def apply(property: String): Option[Any] = + + /** + * type of properties is normally Object + * use type identifier T to cast it + */ + def apply[T](property: String): Option[T] = propertyContainer.hasProperty(property) match { - case true => Some(propertyContainer.getProperty(property)) + case true => Some(propertyContainer.getProperty(property).asInstanceOf[T]) case _ => None } - def update(property: String, value: Any): Unit = propertyContainer.setProperty(property, value) + /** + * updates the property + * node("property") = value + */ + def update(property: String, value: Any): Unit = + propertyContainer.setProperty(property, value) } \ No newline at end of file diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapperUtil.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala similarity index 70% rename from src/main/scala/org/neo4j/scala/Neo4jWrapperUtil.scala rename to src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala index 8861774..4c3ea17 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapperUtil.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala @@ -8,19 +8,35 @@ import org.neo4j.graphdb._ * * @author Christopher Schmidt */ -trait Neo4jWrapperUtil { +trait Neo4jWrapperImplicits { + self: Neo4jWrapper => + /** + * converts to a relationship builder to use --> <-- methods + */ implicit def node2relationshipBuilder(node: Node) = new NodeRelationshipMethods(node) + /** + * converts a String to a relationship type + */ implicit def string2RelationshipType(relType: String) = DynamicRelationshipType.withName(relType) + /** + * cpnversion to use property set and get convenience + */ implicit def propertyContainer2RichPropertyContainer(propertyContainer: PropertyContainer) = new RichPropertyContainer(propertyContainer) + /** + * creates a functional correct StopEvaluator instance + */ implicit def fn2StopEvaluator(e: TraversalPosition => Boolean) = new StopEvaluator() { def isStopNode(traversalPosition: TraversalPosition) = e(traversalPosition) } + /** + * creates a functional correct ReturnableEvaluator instance + */ implicit def fn2ReturnableEvaluator(e: TraversalPosition => Boolean) = new ReturnableEvaluator() { def isReturnableNode(traversalPosition: TraversalPosition) = e(traversalPosition) @@ -29,6 +45,5 @@ trait Neo4jWrapperUtil { /** * Stuff for Indexes */ - implicit def indexManager(implicit ds: DatabaseService) = ds.gds.index } diff --git a/src/main/scala/org/neo4j/scala/util/SpatialUtil.scala b/src/main/scala/org/neo4j/scala/util/SpatialUtil.scala deleted file mode 100644 index ee82214..0000000 --- a/src/main/scala/org/neo4j/scala/util/SpatialUtil.scala +++ /dev/null @@ -1,83 +0,0 @@ -package org.neo4j.scala.util - -import org.neo4j.gis.spatial._ -import collection.mutable.Buffer -import com.vividsolutions.jts.geom.impl.CoordinateArraySequence -import com.vividsolutions.jts.geom._ - -/** - * Really simple and very incomplete list of spatial utility classes - * - * @author Christopher Schmidt - * Date: 14.04.11 - * Time: 06:17 - */ - -private[scala] class AddGeometry(layer: EditableLayer) { - val gf = layer.getGeometryFactory - - /** - * Points - */ - def newPoint(coordinate: Coordinate): SpatialDatabaseRecord = layer.add(gf.createPoint(coordinate)) - - def newMultiPoint(points: Array[Point]) = layer.add(gf.createMultiPoint(points)) - - def newMultiPoint(coordinates: CoordinateArraySequence) = layer.add(gf.createMultiPoint(coordinates)) - - /** - * Polygon - */ - def newPolygon(shell: LinearRing, holes: Array[LinearRing] = null) = layer.add(gf.createPolygon(shell, holes)) - - def newMultiPolygon(polygons: Array[Polygon]) = layer.add(gf.createMultiPolygon(polygons)) - - /** - * Line String - */ - def newLineString(coordinates: Array[Coordinate]) = layer.add(gf.createLineString(coordinates)) - - def newLineString(coordinates: CoordinateArraySequence) = layer.add(gf.createLineString(coordinates)) - - def newMultiLineString(lineStrings: Array[LineString]) = layer.add(gf.createMultiLineString(lineStrings)) -} - - -/** - * convenience object of handling Coordinates - */ -object Coord { - def apply(x: Double, y: Double) = new Coordinate(x, y) -} - -/** - * convenience object for CoordinateArraySequence - */ -object CoordArraySequence { - def apply(b: Buffer[(Double, Double)]) = { - val a = for (t <- b; c = Coord(t._1, t._2)) yield c - new CoordinateArraySequence(a.toArray) - } -} - -/** - * convenience object for LinearRing - */ -object LinRing { - def apply(cs: CoordinateSequence)(implicit layer: EditableLayer) = - new LinearRing(cs, layer.getGeometryFactory) - - def apply(b: Buffer[(Double, Double)])(implicit layer: EditableLayer) = - new LinearRing(CoordArraySequence(b), layer.getGeometryFactory) -} - -/** - * convenience object for LineString - */ -object lineString { - def apply(cs: CoordinateSequence)(implicit layer: EditableLayer) = - new LineString(cs, layer.getGeometryFactory) - - def apply(b: Buffer[(Double, Double)])(implicit layer: EditableLayer) = - new LineString(CoordArraySequence(b), layer.getGeometryFactory) -} \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala b/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala deleted file mode 100644 index b24289e..0000000 --- a/src/test/scala/org/neo4j/scala/Neo4jSpatialTest.scala +++ /dev/null @@ -1,93 +0,0 @@ -package org.neo4j.scala - -import org.specs.runner.JUnit4 -import org.specs.Specification -import org.neo4j.gis.spatial.query.{SearchWithin, SearchContain} -import collection.JavaConversions.asScalaBuffer -import collection.mutable.Buffer -import org.neo4j.gis.spatial.{NullListener, SpatialDatabaseRecord} -import java.util.ArrayList -import com.vividsolutions.jts.geom.{LinearRing, Coordinate, Envelope} -import com.vividsolutions.jts.geom.impl.CoordinateArraySequence -import collection.JavaConversions._ -import org.neo4j.graphdb.{Node, RelationshipType, Direction, DynamicRelationshipType} -import util.LinRing - -class Neo4jSpatialSpecTest extends JUnit4(Neo4jSpatialSpec) - -object Neo4jSpatialSpec extends Specification with Neo4jSpatialWrapper with EmbeddedGraphDatabaseServiceProvider with SpatialDatabaseServiceProvider { - - def neo4jStoreDir = "/tmp/temp-neo-spatial-test" - - "NeoSpatialWrapper" should { - shareVariables() - - Runtime.getRuntime.addShutdownHook(new Thread() { - override def run() { - ds.gds.shutdown - } - }) - - "allow usage of Neo4jWrapper" in { - - withSpatialTx { - implicit db => - - val start = createNode - val end = createNode - val relType = DynamicRelationshipType.withName("foo") - start --> relType --> end - start.getSingleRelationship(relType, Direction.OUTGOING). - getOtherNode(start) must beEqualTo(end) - } - } - - "simplify layer, node and search usage" in { - - withSpatialTx { - implicit db => - - // remove existing layer - try { - deleteLayer("test", new NullListener) - } - catch { - case _ => - } - - val cities = createNode - val federalStates = createNode - - withLayer(getOrCreateEditableLayer("test")) { - implicit layer => - - // adding Point - val munich = add newPoint ((15.3, 56.2)) - munich.setProperty("City", "Munich") - cities --> "isCity" --> munich - - // adding new Polygon - val bayernBuffer = Buffer[(Double, Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) - val bayern = add newPolygon (LinRing(bayernBuffer)) - bayern.setProperty("FederalState", "Bayern") - federalStates --> "isFederalState" --> bayern - - munich --> "CapitalCityOf" --> bayern - - withSearch[SearchWithin](bayern.getGeometry) { - implicit s => - executeSearch - for (r <- getResults) - r.getProperty("City") must beEqual("Munich") - } - - withSearch[SearchWithin](toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0))) { - implicit s => - executeSearch - getResults.size must_== 2 - } - } - } - } - } -} \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/SpatialTestsUsingTestNodes.scala b/src/test/scala/org/neo4j/scala/SpatialTestsUsingTestNodes.scala deleted file mode 100644 index d91a6b1..0000000 --- a/src/test/scala/org/neo4j/scala/SpatialTestsUsingTestNodes.scala +++ /dev/null @@ -1,75 +0,0 @@ -package org.neo4j.scala - -import org.specs.runner.JUnit4 -import org.specs.Specification -import org.neo4j.gis.spatial.NullListener -import collection.mutable.Buffer -import com.vividsolutions.jts.geom.Envelope -import org.neo4j.gis.spatial.query.SearchWithin - -class SpatialTestsUsingTestNodesTest extends JUnit4(SpatialTestsUsingTestNodes) - -object SpatialTestsUsingTestNodes extends Specification with Neo4jSpatialWrapper with EmbeddedGraphDatabaseServiceProvider with SpatialDatabaseServiceProvider { - - def neo4jStoreDir = "/tmp/temp-neo-spatial-test2" - - "NeoSpatialWrapper" should { - shareVariables() - - Runtime.getRuntime.addShutdownHook(new Thread() { - override def run() { - ds.gds.shutdown - } - }) - - "allow usage of common pattern like those in TestNodes.scala" in { - - withSpatialTx { - implicit db => - - // remove existing layer - try { - deleteLayer("test", new NullListener) - } - catch { - case _ => - } - - val cities = createNode - val federalStates = createNode - - withLayer(getOrCreateEditableLayer("test")) { - implicit layer => - - /** - * create Munich and "attach" it to the cities node - */ - val munich = NewSpatialNode[City]((15.3, 56.2)) - munich.name = "Munich" - cities --> "isCity" --> munich - - /** - * create a polygon called Bayern, "attach" it to the federal state node and - * "attach" the capital city Munich - */ - val bayernBuffer = Buffer[(Double, Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) - val bayern = NewSpatialNode[FedaralState](bayernBuffer) - bayern.name = "Bayern" - federalStates --> "isFederalState" --> bayern - munich --> "CapitalCityOf" --> bayern - - /** - * search all geometries inside an Envelope - */ - var result = for (r <- search[SearchWithin](toGeometry(new Envelope(15.0, 16.0, 56.0, 57.0)))) yield r - result.size must_== 2 - - /** - * retrieve the capital city of Bayern - */ - bayern.getCapitalCity.name must beEqual(munich.name) - } - } - } - } -} \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/TestNodes.scala b/src/test/scala/org/neo4j/scala/TestNodes.scala deleted file mode 100644 index 1858351..0000000 --- a/src/test/scala/org/neo4j/scala/TestNodes.scala +++ /dev/null @@ -1,102 +0,0 @@ -package org.neo4j.scala - -import Types._ -import annotation.implicitNotFound -import collection.mutable.Buffer -import org.neo4j.graphdb.{Direction} -import org.neo4j.gis.spatial.{EditableLayer, SpatialDatabaseRecord} -import util.{Coord, LinRing} - -/** - * Examples following this Design Guide: http://wiki.neo4j.org/content/Design_Guide - * - * @author Christopher Schmidt - * Date: 22.03.11 - * Time: 06:00 - */ - -/** - * defines some shorter types for this examples - */ -object Types { - type PointLocation = (Double, Double) - type PolylineLocation = Buffer[(Double, Double)] -} - -/** - * convenience trait - */ -trait MyAllInOneTrait extends IsSpatialDatabaseRecord with Neo4jSpatialWrapperUtil with Neo4jWrapperUtil - -/** - * example implementation for a City Node - */ -class City(val node: SpatialDatabaseRecord) extends MyAllInOneTrait { - - object City { - val KEY_CITY_NAME = "cityName" - } - - def name = node.getProperty(City.KEY_CITY_NAME) - - def name_=(n: String) { - node.setProperty(City.KEY_CITY_NAME, n) - } -} - -/** - * example implementation for a polyline node (a federal state) - */ -class FedaralState(val node: SpatialDatabaseRecord) extends MyAllInOneTrait { - - object FedaralState { - val KEY_FEDSTATE_NAME = "federalState" - } - - def name = node.getProperty(FedaralState.KEY_FEDSTATE_NAME) - - def name_=(n: String) { - node.setProperty(FedaralState.KEY_FEDSTATE_NAME, n) - } - - def getCapitalCity(implicit layer: EditableLayer) = { - val o = node.getSingleRelationship("CapitalCityOf", Direction.INCOMING).getOtherNode(node) - new City(new SpatialDatabaseRecord(layer, o)) - } -} - -/** - * factory object - * creates new SpatialDatabaseRecords resp. Nodes via reflection - */ -object NewSpatialNode extends Neo4jSpatialWrapperUtil with Neo4jWrapperUtil { - - /** - * uses a given node to create a instance of IsSpatialDatabaseRecord - */ - def apply[T: ClassManifest](node: SpatialDatabaseRecord): T = { - val m = implicitly[ClassManifest[T]] - val ctor = m.erasure.getConstructor(classOf[SpatialDatabaseRecord]) - ctor.newInstance(node).asInstanceOf[T] - } - - /** - * creates a new SpatialDatabaseRecord from a given Geometry - * and calls the contructor - */ - @implicitNotFound("implicit instance of EditableLayer not in scope") - def apply[T](shell: PolylineLocation)(implicit layer: EditableLayer, m: ClassManifest[T]): T = { - val record = add newPolygon LinRing(shell) - apply[T](record) - } - - /** - * creates a new SpatialDatabaseRecord from a given Geometry - * and calls the contructor - */ - @implicitNotFound("implicit instance of EditableLayer not in scope") - def apply[T](point: PointLocation)(implicit layer: EditableLayer, m: ClassManifest[T]): T = { - val record = add newPoint Coord(point._1, point._2) - apply[T](record) - } -} \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/DeSerializingTest.scala b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala similarity index 70% rename from src/test/scala/org/neo4j/scala/DeSerializingTest.scala rename to src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala index afcde2a..d55ca2b 100644 --- a/src/test/scala/org/neo4j/scala/DeSerializingTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala @@ -1,28 +1,22 @@ -package org.neo4j.scala +package org.neo4j.scala.unittest -import org.specs.runner.JUnit4 -import org.specs.Specification -import util.{CaseClassDeserializer} -import CaseClassDeserializer._ import org.neo4j.graphdb.Node +import org.specs2.mutable.SpecificationWithJUnit +import org.neo4j.scala.{EmbeddedGraphDatabaseServiceProvider, Neo4jWrapper} +import org.neo4j.scala.util.CaseClassDeserializer /** + * Test spec to check deserialization and serialization of case classes * * @author Christopher Schmidt - * Date: 20.07.11 - * Time: 06:29 */ case class Test(s: String, i: Int, ji: java.lang.Integer, d: Double, l: Long, b: Boolean) +import CaseClassDeserializer._ +class DeSerializingWithoutNeo4jSpec extends SpecificationWithJUnit { -class DeSerializingTest extends JUnit4(DeSerializingSpec) - -class DeSerializing2Test extends JUnit4(DeSerializingSpec2) - -object DeSerializingSpec extends Specification { - - "DeSerializing" should { + "De- and Serializing" should { "able to create an instance from map" in { val m = Map[String, AnyRef]("s" -> "sowas", "i" -> "1", "ji" -> "2", "d" -> (3.3).asInstanceOf[AnyRef], "l" -> "10", "b" -> "true") @@ -47,12 +41,11 @@ object DeSerializingSpec extends Specification { } } -object DeSerializingSpec2 extends Specification with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider { +class DeSerializingSpec extends SpecificationWithJUnit with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider { def neo4jStoreDir = "/tmp/temp-neo-test2" "Node" should { - shareVariables() Runtime.getRuntime.addShutdownHook(new Thread() { override def run() { @@ -69,7 +62,7 @@ object DeSerializingSpec2 extends Specification with Neo4jWrapper with EmbeddedG } var oo = deSerialize[Test](node) - oo must beEqual(o) + oo must beEqualTo(o) } } } \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/IndexTest.scala b/src/test/scala/org/neo4j/scala/unittest/IndexTest.scala similarity index 69% rename from src/test/scala/org/neo4j/scala/IndexTest.scala rename to src/test/scala/org/neo4j/scala/unittest/IndexTest.scala index 15fd957..5abc11d 100644 --- a/src/test/scala/org/neo4j/scala/IndexTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/IndexTest.scala @@ -1,20 +1,15 @@ -package org.neo4j.scala +package org.neo4j.scala.unittest -import org.specs.Specification -import org.specs.runner.JUnit4 -import collection.JavaConversions._ +import org.specs2.mutable.SpecificationWithJUnit +import org.neo4j.scala.{Neo4jIndexProvider, EmbeddedGraphDatabaseServiceProvider, Neo4jWrapper} /** + * Test spec to check usage of index convenience methods * * @author Christopher Schmidt - * Date: 12.04.11 - * Time: 06:15 */ -class IndexTest extends JUnit4(IndexTestSpec) - -object IndexTestSpec extends Specification with Neo4jSpatialWrapper with EmbeddedGraphDatabaseServiceProvider -with SpatialDatabaseServiceProvider with Neo4jIndexProvider { +class IndexTestSpec extends SpecificationWithJUnit with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider with Neo4jIndexProvider { def neo4jStoreDir = "/tmp/temp-neo-index-test" @@ -22,7 +17,6 @@ with SpatialDatabaseServiceProvider with Neo4jIndexProvider { "Neo4jIndexProvider" should { - shareVariables() Runtime.getRuntime.addShutdownHook(new Thread() { override def run() { @@ -34,7 +28,7 @@ with SpatialDatabaseServiceProvider with Neo4jIndexProvider { val nodeIndex = getNodeIndex("MyTestIndex").get - withSpatialTx { + withTx { implicit db => val theMatrix = createNode diff --git a/src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala b/src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala similarity index 94% rename from src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala rename to src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala index 0664dbb..11ea1aa 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala @@ -1,20 +1,18 @@ -package org.neo4j.scala - -import org.specs._ -import org.specs.runner._ +package org.neo4j.scala.unittest import org.neo4j.graphdb._ -import org.neo4j.kernel.EmbeddedGraphDatabase - +import org.specs2.mutable.SpecificationWithJUnit +import org.neo4j.scala.{EmbeddedGraphDatabaseServiceProvider, Neo4jWrapper} -class Neo4jWrapperSpecTest extends JUnit4(Neo4jWrapperSpec) +/** + * Test spec to check relationship builder and evaluators + */ -object Neo4jWrapperSpec extends Specification with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider { +class Neo4jWrapperSpec extends SpecificationWithJUnit with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider { def neo4jStoreDir = "/tmp/temp-neo-test" "NeoWrapper" should { - shareVariables() Runtime.getRuntime.addShutdownHook(new Thread() { override def run() { From 449b9931ab7f45e495331107419958c88ff8f9cb Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 14 Sep 2011 06:42:17 +0200 Subject: [PATCH 46/82] removed spatial stuff from read me --- README.md | 58 ------------------------------------------------------- 1 file changed, 58 deletions(-) diff --git a/README.md b/README.md index 249c1f5..e47f997 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,3 @@ -Neo4j Spatial Scala wrapper library -======================= - -I tried to add some wrapper stuff for the Neo4j Spatial implementation. -So you are able to create a city Munich as follows: - - val munich = add newPoint ((15.3, 56.2)) - munich.setProperty("City", "Munich") - -and attached it to a federal state like Bavaria: - - val bayernBuffer = Buffer[(Double, Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) - val bayern = add newPolygon (LinRing(bayernBuffer)) - bayern.setProperty("FederalState", "Bayern") - federalStates --> "isFederalState" --> bayern - -Additionally I added some examples like those pattern shown in the [Neo4j Design Guide](http://wiki.neo4j.org/content/Design_Guide): - - . . . - class FedaralState(val node: SpatialDatabaseRecord) extends . . . { - - object FedaralState { - val KEY_FEDSTATE_NAME = "federalState" - } - - def name = node.getProperty(FedaralState.KEY_FEDSTATE_NAME) - - def name_=(n: String) { - node.setProperty(FedaralState.KEY_FEDSTATE_NAME, n) - } - - def getCapitalCity(implicit layer: EditableLayer) = { - val o = node.getSingleRelationship("CapitalCityOf", Direction.INCOMING).getOtherNode(node) - new City(new SpatialDatabaseRecord(layer, o)) - } - } - . . . - -that finaly result in code as follows: - - /** - * create Munich and "attach" it to the cities node - */ - val munich = NewSpatialNode[City]((15.3, 56.2)) - munich.name = "Munich" - cities --> "isCity" --> munich - - /** - * create a polygon called Bayern, "attach" it to the federal state node and - * "attach" the capital city Munich - */ - val bayernBuffer = Buffer[(Double, Double)]((15, 56), (16, 56), (15, 57), (16, 57), (15, 56)) - val bayern = NewSpatialNode[FedaralState](bayernBuffer) - bayern.name = "Bayern" - federalStates --> "isFederalState" --> bayern - munich --> "CapitalCityOf" --> bayern - -Lookes rather nice IMHO, but is still very incomplete... Index Access ============ From 569633fcf437340c95a6266f659c7800888dcec3 Mon Sep 17 00:00:00 2001 From: FaKod Date: Thu, 15 Sep 2011 06:33:09 +0200 Subject: [PATCH 47/82] removed self reference to Neo4jWrapper --- src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala index 4c3ea17..3d44cef 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala @@ -9,7 +9,6 @@ import org.neo4j.graphdb._ * @author Christopher Schmidt */ trait Neo4jWrapperImplicits { - self: Neo4jWrapper => /** * converts to a relationship builder to use --> <-- methods @@ -22,7 +21,7 @@ trait Neo4jWrapperImplicits { implicit def string2RelationshipType(relType: String) = DynamicRelationshipType.withName(relType) /** - * cpnversion to use property set and get convenience + * conversion to use property set and get convenience */ implicit def propertyContainer2RichPropertyContainer(propertyContainer: PropertyContainer) = new RichPropertyContainer(propertyContainer) From 69c844933eb3249cc7eb2a3a9aa405a9507cb760 Mon Sep 17 00:00:00 2001 From: FaKod Date: Thu, 15 Sep 2011 06:36:09 +0200 Subject: [PATCH 48/82] old licence --- LICENSE | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 16c769e..0000000 --- a/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ - The MIT License - -Copyright (c) 2009, Jawher Moussa and Martin Kleppmann - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - From 0e102a74146ae83c04b566b27a7c4b02238af934 Mon Sep 17 00:00:00 2001 From: FaKod Date: Thu, 15 Sep 2011 06:37:17 +0200 Subject: [PATCH 49/82] added IDEA files --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 6646f3e..d706915 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ generated target .manager -*~ - +.idea +*.i?? +*~ \ No newline at end of file From 47376f130585c6facd891afabb38a6c7123f419f Mon Sep 17 00:00:00 2001 From: FaKod Date: Fri, 16 Sep 2011 06:46:20 +0200 Subject: [PATCH 50/82] created Neo4jWrapper Object make DeSerialization "static" --- .../scala/org/neo4j/scala/Neo4jWrapper.scala | 20 +++++++++++-------- .../scala/unittest/DeSerializingTest.scala | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index 218a299..c22dd2b 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -19,13 +19,6 @@ import CaseClassDeserializer._ */ trait Neo4jWrapper extends GraphDatabaseServiceProvider with Neo4jWrapperImplicits { - /** - * this name will be used to store the class name of - * the serialized case class that will be verified - * in deserialization - */ - val ClassPropertyName = "__CLASS__" - /** * Execute instructions within a Neo4j transaction; rollback if exception is raised and * commit otherwise; and return the return value from the operation. @@ -51,8 +44,19 @@ trait Neo4jWrapper extends GraphDatabaseServiceProvider with Neo4jWrapperImplici /** * convenience method to create and serialize a case class */ - def createNode[T <: Product](cc: T)(implicit ds: DatabaseService): Node = serialize(cc, createNode) + def createNode[T <: Product](cc: T)(implicit ds: DatabaseService): Node = Neo4jWrapper.serialize(cc, createNode) +} +/** + * Neo4jWrapper Object + */ +object Neo4jWrapper extends Neo4jWrapperImplicits { + /** + * this name will be used to store the class name of + * the serialized case class that will be verified + * in deserialization + */ + val ClassPropertyName = "__CLASS__" /** * serializes a given case class into a Node instance */ diff --git a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala index d55ca2b..26fcb44 100644 --- a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala @@ -61,7 +61,7 @@ class DeSerializingSpec extends SpecificationWithJUnit with Neo4jWrapper with Em node = createNode(o) } - var oo = deSerialize[Test](node) + var oo = Neo4jWrapper.deSerialize[Test](node) oo must beEqualTo(o) } } From 9a4a6f0e7e0439b6325d9a5d93a32fc0234f7451 Mon Sep 17 00:00:00 2001 From: FaKod Date: Sun, 18 Sep 2011 16:07:06 +0200 Subject: [PATCH 51/82] updated documentation --- README.md | 138 +++++++++++++++++++++++++++++------------------------- 1 file changed, 75 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index e47f997..0c6ea3c 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,3 @@ - -Index Access -============ - -The access of the Neo4j Lucene Index will be handled by the trait Neo4jIndexProvider. -It can be used like this example to configure and use a index for full text search: - - object XXX extends . . . with Neo4jIndexProvider { - - override def NodeIndexConfig = ("MyTestIndex", Map("provider" -> "lucene", "type" -> "fulltext")) :: Nil - . . . - val nodeIndex = getNodeIndex("MyTestIndex").get - - withSpatialTx { - implicit db => - - val theMatrix = createNode - val theMatrixReloaded = createNode - theMatrixReloaded.setProperty("name", "theMatrixReloaded") - - nodeIndex.add(theMatrix, "title", "The Matrix") - nodeIndex.add(theMatrixReloaded, "title", "The Matrix Reloaded") - // search in the fulltext index - val found = nodeIndex.query("title", "reloAdEd") - ... - } - . . . - } - -The rest if this README is the original one. - Neo4j Scala wrapper library ======================= @@ -42,65 +11,108 @@ This wrapper is mostly based on the work done by [Martin Kleppmann](http://twitt Building -------- -You need a Java 5 (or newer) environment and Maven 2.0.9 (or newer) installed: + $ git clone git://github.com/fakod/neo4j-scala.git + $ cd neo4j-scala + $ mvn clean install - $ mvn --version - Apache Maven 3.0-alpha-5 (r883378; 2009-11-23 16:53:41+0100) - Java version: 1.6.0_15 - Java home: /usr/lib/jvm/java-6-sun-1.6.0.15/jre - Default locale: en_US, platform encoding: UTF-8 - OS name: "linux" version: "2.6.31-12-generic" arch: "i386" Family: "unix" +Troubleshooting +--------------- -You should now be able to do a full build of `neo4j-resources`: +Please consider using [Github issues tracker](http://github.com/fakod/neo4j-scala/issues) to submit bug reports or feature requests. - $ git clone git://github.com/jawher/neo4j-scala.git - $ cd neo4j-scala - $ mvn clean install -To use this library in your projects, add the following to the `dependencies` section of your -`pom.xml`: +Using this library +================== + +Graph Database Service Provider +------------------------------ +Neo4j Scala Wrapper needs a Graph Database Service Provider, it has to implement GraphDatabaseServiceProvider trait. +One possibility is to use the EmbeddedGraphDatabaseServiceProvider for embedded Neo4j instances where you simply have to +define a neo4jStoreDir. +A class using the wrapper is f.e.: + + class MyNeo4jClass extends SomethingClass with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider { + def neo4jStoreDir = "/tmp/temp-neo-test" + . . . + } - - org.neo4j - neo4j-scala - 0.9.9-SNAPSHOT - +Transaction Wrapping +-------------------- +Transactions are wrapped by withTx. After leaving the "scope" success is called or rollback if exception is raised: -If you don't use Maven, take `target/neo4j-scala-0.9.9-SNAPSHOT.jar` and all of its dependencies, and add them to your classpath. + withTx { + implicit neo => + val start = createNode + val end = createNode + start --> "foo" --> end + } +Using the Lucene Index +--------------------- +Neo4j provides indexes for nodes and relationships. The indexes can be configured by mixing in the Neo4jIndexProvider trait. +See [Indexing](http://docs.neo4j.org/chunked/stable/indexing.html) -Troubleshooting ---------------- + class MyNeo4jClass extends . . . with Neo4jIndexProvider { + // configuration for the index being created. + override def NodeIndexConfig = ("MyTest1stIndex", Map("provider" -> "lucene", "type" -> "fulltext")) :: + ("MyTest2ndIndex", Map("provider" -> "lucene", "type" -> "fulltext")) :: Nil + } -Please consider using [Github issues tracker](http://github.com/jawher/neo4j-scala/issues) to submit bug reports or feature requests. +Use one of the configured indexes with + val nodeIndex = getNodeIndex("MyTest1stIndex").get -Using this library ------------------- +Add and remove entires by: + + nodeIndex += (Node_A, "title", "The Matrix") + nodeIndex -= (Node_A) + +Relations +--------- Using this wrapper, this is how creating two relationships can look in Scala: start --> "KNOWS" --> intermediary --> "KNOWS" --> end + left --> "foo" --> middle <-- "bar" <-- right + +Returning the Relation Object: + + val relation = start --> "KNOWS" --> end <; + +Properties +---------- And this is how getting and setting properties on a node or relationship looks like : + // setting the property foo start("foo") = "bar" start("foo") match { case Some(x) => println(x) - case None => println("aww") + case None => println("aww") } +Using Case Classes +------------------ +Neo4j provides storing keys (String) and values (Object) into Nodes. To store Case Classes the property names of the case class are used as keys and the values are stored Strings as well. Working types are limited. + + case class Test(s: String, i: Int, ji: java.lang.Integer, d: Double, l: Long, b: Boolean) + withTx { + implicit neo => + // create Node with Case Class Test + val node1 = createNode(Test("Something", 1, 2, 3.3, 10, true)) + // "recreate" Case Class Test from Node + val node2 = Neo4jWrapper.deSerialize[Test](node) + } + +Traversing +---------- + Besides, the neo4j scala binding makes it possible to write stop and returnable evaluators in a functional style : //StopEvaluator.END_OF_GRAPH, written in a Scala idiomatic way : - start.traverse(Traverser.Order.BREADTH_FIRST, (tp : TraversalPosition) => false, ReturnableEvaluator.ALL_BUT_START_NODE, DynamicRelationshipType.withName("foo"), Direction.OUTGOING) + start.traverse(Traverser.Order.BREADTH_FIRST, (tp : TraversalPosition) => false, ReturnableEvaluator.ALL_BUT_START_NODE, "foo", Direction.OUTGOING) //ReturnableEvaluator.ALL_BUT_START_NODE, written in a Scala idiomatic way : - start.traverse(Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, (tp : TraversalPosition) => tp.notStartNode(), DynamicRelationshipType.withName("foo"), Direction.OUTGOING) - - -License -------- + start.traverse(Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, (tp : TraversalPosition) => tp.notStartNode(), "foo", Direction.OUTGOING) -See `LICENSE` for details. From 9f311effe992055b50fd699b2040dc2acb71f0c2 Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 19 Sep 2011 06:05:28 +0200 Subject: [PATCH 52/82] some typos --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0c6ea3c..c5017fe 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ The Neo4j Scala wrapper library allows you the [Neo4j open source graph database domain-specific simplified language. It is written in Scala and is intended to be used in other Scala projects. -This wrapper is mostly based on the work done by [Martin Kleppmann](http://twitter.com/martinkl) in his [Scala implementation of RESTful JSON HTTP resources on top of the Neo4j graph database and Jersey](http://github.com/ept/neo4j-resources) project. I thought it'd be useful to extract the Neo4j DSL into a separate project, and Marting agreed to this. +This wrapper is mostly based on the work done by [Martin Kleppmann](http://twitter.com/martinkl) in his [Scala implementation of RESTful JSON HTTP resources on top of the Neo4j graph database and Jersey](http://github.com/ept/neo4j-resources) project. Building @@ -27,8 +27,7 @@ Using this library Graph Database Service Provider ------------------------------ Neo4j Scala Wrapper needs a Graph Database Service Provider, it has to implement GraphDatabaseServiceProvider trait. -One possibility is to use the EmbeddedGraphDatabaseServiceProvider for embedded Neo4j instances where you simply have to -define a neo4jStoreDir. +One possibility is to use the EmbeddedGraphDatabaseServiceProvider for embedded Neo4j instances where you simply have to define a Neo4j storage directory. A class using the wrapper is f.e.: class MyNeo4jClass extends SomethingClass with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider { @@ -38,7 +37,7 @@ A class using the wrapper is f.e.: Transaction Wrapping -------------------- -Transactions are wrapped by withTx. After leaving the "scope" success is called or rollback if exception is raised: +Transactions are wrapped by withTx. After leaving the "scope" success is called (or rollback if an exception is raised): withTx { implicit neo => @@ -47,10 +46,9 @@ Transactions are wrapped by withTx. After leaving the "scope" success is called start --> "foo" --> end } -Using the Lucene Index +Using an Index --------------------- -Neo4j provides indexes for nodes and relationships. The indexes can be configured by mixing in the Neo4jIndexProvider trait. -See [Indexing](http://docs.neo4j.org/chunked/stable/indexing.html) +Neo4j provides indexes for nodes and relationships. The indexes can be configured by mixing in the Neo4jIndexProvider trait. See [Indexing](http://docs.neo4j.org/chunked/stable/indexing.html) class MyNeo4jClass extends . . . with Neo4jIndexProvider { // configuration for the index being created. @@ -62,7 +60,7 @@ Use one of the configured indexes with val nodeIndex = getNodeIndex("MyTest1stIndex").get -Add and remove entires by: +Add and remove entries by: nodeIndex += (Node_A, "title", "The Matrix") nodeIndex -= (Node_A) @@ -93,9 +91,10 @@ And this is how getting and setting properties on a node or relationship looks l Using Case Classes ------------------ -Neo4j provides storing keys (String) and values (Object) into Nodes. To store Case Classes the property names of the case class are used as keys and the values are stored Strings as well. Working types are limited. +Neo4j provides storing keys (String) and values (Object) into Nodes. To store Case Classes the property names of the case class are used as keys and the values are stored Strings as well. Working types are limited to basic types like String, integer etc. case class Test(s: String, i: Int, ji: java.lang.Integer, d: Double, l: Long, b: Boolean) + . . . withTx { implicit neo => // create Node with Case Class Test From f5b5da608e20b264ceeff62beb4503a924871c4b Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 19 Sep 2011 20:25:16 +0200 Subject: [PATCH 53/82] added some more Case Class convenience --- README.md | 13 ++++++++- .../scala/org/neo4j/scala/Neo4jWrapper.scala | 29 +++++++++++++------ .../neo4j/scala/Neo4jWrapperImplicits.scala | 7 +++++ .../scala/unittest/DeSerializingTest.scala | 11 +++++-- 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c5017fe..2b5ff32 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ to be used in other Scala projects. This wrapper is mostly based on the work done by [Martin Kleppmann](http://twitter.com/martinkl) in his [Scala implementation of RESTful JSON HTTP resources on top of the Neo4j graph database and Jersey](http://github.com/ept/neo4j-resources) project. +You may find [Neo4j-Spatial-Scala](http://github.com/FaKod/neo4j-spatial-scala) interesting as well. + + Building -------- @@ -84,7 +87,8 @@ And this is how getting and setting properties on a node or relationship looks l // setting the property foo start("foo") = "bar" - start("foo") match { + // cast Object to String and match . . . + start[String]("foo") match { case Some(x) => println(x) case None => println("aww") } @@ -99,8 +103,15 @@ Neo4j provides storing keys (String) and values (Object) into Nodes. To store Ca implicit neo => // create Node with Case Class Test val node1 = createNode(Test("Something", 1, 2, 3.3, 10, true)) + // "recreate" Case Class Test from Node val node2 = Neo4jWrapper.deSerialize[Test](node) + + // or using Option[T] (returning Some[T] if possible) + val nodeOption: Option[Test] = node.toCC[Test] + + // yield all Nodes that are of Case Class Test + val tests = for(n <- getTraverser; t <- n.toCC[Test]) yield t } Traversing diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index c22dd2b..b95385b 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -57,6 +57,7 @@ object Neo4jWrapper extends Neo4jWrapperImplicits { * in deserialization */ val ClassPropertyName = "__CLASS__" + /** * serializes a given case class into a Node instance */ @@ -68,21 +69,31 @@ object Neo4jWrapper extends Neo4jWrapperImplicits { node } + /** + * conditional case class deserialization + * Some(T) if possible + * None if not + */ + def toCC[T <: Product](node: Node)(implicit m: ClassManifest[T]): Option[T] = { + node[String](ClassPropertyName) match { + case Some(cpn) if (cpn.equals(m.erasure.getName)) => + val kv = for (k <- node.getPropertyKeys; v = node.getProperty(k)) yield (k -> v) + val o = deserialize[T](kv.toMap) + Some(o) + case _ => + None + } + } + /** * deserializes a given case class type from a given Node instance * throws a IllegalArgumentException if a Nodes properties * do not fit to the case class properties */ def deSerialize[T <: Product](node: Node)(implicit m: ClassManifest[T]): T = { - node[String](ClassPropertyName) match { - case Some(cpn) => - val kv = for (k <- node.getPropertyKeys; v = node.getProperty(k)) yield (k -> v) - val o = deserialize[T](kv.toMap)(m) - if (!cpn.equalsIgnoreCase(o.getClass.getName)) - throw new IllegalArgumentException("given Case Class does not fit to serialized properties") - o - case _ => - throw new IllegalArgumentException("this is not a Node with a serialized case class") + toCC[T](node) match { + case Some(t) => t + case _ => throw new IllegalArgumentException("given Case Class: " + m.erasure.getName + " does not fit to serialized properties") } } } diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala index 3d44cef..2205e4c 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala @@ -45,4 +45,11 @@ trait Neo4jWrapperImplicits { * Stuff for Indexes */ implicit def indexManager(implicit ds: DatabaseService) = ds.gds.index + + /** + * for serialization + */ + implicit def nodeToCaseClass(node: Node) = new { + def toCC[T <: Product](implicit m: ClassManifest[T]): Option[T] = Neo4jWrapper.toCC[T](node) + } } diff --git a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala index 26fcb44..9a22faa 100644 --- a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala @@ -14,6 +14,7 @@ import org.neo4j.scala.util.CaseClassDeserializer case class Test(s: String, i: Int, ji: java.lang.Integer, d: Double, l: Long, b: Boolean) import CaseClassDeserializer._ + class DeSerializingWithoutNeo4jSpec extends SpecificationWithJUnit { "De- and Serializing" should { @@ -61,8 +62,14 @@ class DeSerializingSpec extends SpecificationWithJUnit with Neo4jWrapper with Em node = createNode(o) } - var oo = Neo4jWrapper.deSerialize[Test](node) - oo must beEqualTo(o) + var oo1 = Neo4jWrapper.deSerialize[Test](node) + oo1 must beEqualTo(o) + + var oo2 = node.toCC[Test].get + oo2 must beEqualTo(o) + + var oo3 = node.toCC[Test] + oo3 must beEqualTo(Option(o)) } } } \ No newline at end of file From 9a663a6f0b37aed8b645a8f3697ef869c5c6dbe8 Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 20 Sep 2011 06:06:26 +0200 Subject: [PATCH 54/82] fixed a typo in git repo path --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2b5ff32..d2c89bf 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ You may find [Neo4j-Spatial-Scala](http://github.com/FaKod/neo4j-spatial-scala) Building -------- - $ git clone git://github.com/fakod/neo4j-scala.git + $ git clone git://github.com/FaKod/neo4j-scala.git $ cd neo4j-scala $ mvn clean install @@ -110,7 +110,7 @@ Neo4j provides storing keys (String) and values (Object) into Nodes. To store Ca // or using Option[T] (returning Some[T] if possible) val nodeOption: Option[Test] = node.toCC[Test] - // yield all Nodes that are of Case Class Test + // yield all Nodes that are of type Case Class Test val tests = for(n <- getTraverser; t <- n.toCC[Test]) yield t } From bfb1b10e16cdd9fbd99469b1cc319fcb684a02b4 Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 21 Sep 2011 06:41:33 +0200 Subject: [PATCH 55/82] more tests --- .../scala/unittest/DeSerializingTest.scala | 51 +++++++++++++------ .../org/neo4j/scala/unittest/IndexTest.scala | 25 +++++++-- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala index 9a22faa..d8743c8 100644 --- a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala @@ -1,6 +1,5 @@ package org.neo4j.scala.unittest -import org.neo4j.graphdb.Node import org.specs2.mutable.SpecificationWithJUnit import org.neo4j.scala.{EmbeddedGraphDatabaseServiceProvider, Neo4jWrapper} import org.neo4j.scala.util.CaseClassDeserializer @@ -13,6 +12,10 @@ import org.neo4j.scala.util.CaseClassDeserializer case class Test(s: String, i: Int, ji: java.lang.Integer, d: Double, l: Long, b: Boolean) +case class Test2(jl: java.lang.Long, jd: java.lang.Double, jb: java.lang.Boolean) + +case class NotTest(s: String, i: Int, ji: java.lang.Integer, d: Double, l: Long, b: Boolean) + import CaseClassDeserializer._ class DeSerializingWithoutNeo4jSpec extends SpecificationWithJUnit { @@ -21,7 +24,7 @@ class DeSerializingWithoutNeo4jSpec extends SpecificationWithJUnit { "able to create an instance from map" in { val m = Map[String, AnyRef]("s" -> "sowas", "i" -> "1", "ji" -> "2", "d" -> (3.3).asInstanceOf[AnyRef], "l" -> "10", "b" -> "true") - var r = deserialize[Test](m) + val r = deserialize[Test](m) r.s must endWith("sowas") r.i must_== (1) @@ -32,8 +35,8 @@ class DeSerializingWithoutNeo4jSpec extends SpecificationWithJUnit { } "able to create a map from an instance" in { - var o = Test("sowas", 1, 2, 3.3, 10, true) - var resMap = serialize(o) + val o = Test("sowas", 1, 2, 3.3, 10, true) + val resMap = serialize(o) resMap.size must_== 6 resMap.get("d").get mustEqual (3.3) @@ -54,22 +57,40 @@ class DeSerializingSpec extends SpecificationWithJUnit with Neo4jWrapper with Em } }) - "be serializable" in { - var o = Test("sowas", 1, 2, 3.3, 10, true) - var node: Node = null - withTx { - implicit neo => - node = createNode(o) + "be serializable with Test" in { + val o = Test("sowas", 1, 2, 3.3, 10, true) + val node = withTx { + createNode(o)(_) } - var oo1 = Neo4jWrapper.deSerialize[Test](node) + val oo1 = Neo4jWrapper.deSerialize[Test](node) oo1 must beEqualTo(o) - var oo2 = node.toCC[Test].get - oo2 must beEqualTo(o) + val oo2 = node.toCC[Test] + oo2 must beEqualTo(Option(o)) + + val oo3 = node.toCC[NotTest] + oo3 must beEqualTo(None) + + Neo4jWrapper.deSerialize[NotTest](node) must throwA[IllegalArgumentException] + } + + "be serializable with Test2" in { + val o = Test2(1, 3.3, true) + val node = withTx { + createNode(o)(_) + } + + val oo1 = Neo4jWrapper.deSerialize[Test2](node) + oo1 must beEqualTo(o) + + val oo2 = node.toCC[Test2] + oo2 must beEqualTo(Option(o)) + + val oo3 = node.toCC[NotTest] + oo3 must beEqualTo(None) - var oo3 = node.toCC[Test] - oo3 must beEqualTo(Option(o)) + Neo4jWrapper.deSerialize[NotTest](node) must throwA[IllegalArgumentException] } } } \ No newline at end of file diff --git a/src/test/scala/org/neo4j/scala/unittest/IndexTest.scala b/src/test/scala/org/neo4j/scala/unittest/IndexTest.scala index 5abc11d..451469f 100644 --- a/src/test/scala/org/neo4j/scala/unittest/IndexTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/IndexTest.scala @@ -2,6 +2,7 @@ package org.neo4j.scala.unittest import org.specs2.mutable.SpecificationWithJUnit import org.neo4j.scala.{Neo4jIndexProvider, EmbeddedGraphDatabaseServiceProvider, Neo4jWrapper} +import collection.JavaConversions._ /** * Test spec to check usage of index convenience methods @@ -35,13 +36,31 @@ class IndexTestSpec extends SpecificationWithJUnit with Neo4jWrapper with Embedd val theMatrixReloaded = createNode theMatrixReloaded.setProperty("name", "theMatrixReloaded") - nodeIndex += (theMatrix, "title", "The Matrix") - nodeIndex += (theMatrixReloaded, "title", "The Matrix Reloaded") - + nodeIndex +=(theMatrix, "title", "The Matrix") + nodeIndex +=(theMatrixReloaded, "title", "The Matrix Reloaded") + // search in the fulltext index val found = nodeIndex.query("title", "reloAdEd") found.size must beGreaterThanOrEqualTo(1) } } + + "remove items from index" in { + + val nodeIndex = getNodeIndex("MyTestIndex").get + + withTx { + implicit db => + + val found = nodeIndex.query("title", "reloAdEd") + val size = found.size + for (f <- found.iterator) + nodeIndex -= f + + // search in the fulltext index + val found2 = nodeIndex.query("title", "reloAdEd") + found2.size must beLessThanOrEqualTo(size) + } + } } } \ No newline at end of file From 99a4c107921dfa9c02f31fe9fe4539161bb2b99b Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 27 Sep 2011 20:15:54 +0200 Subject: [PATCH 56/82] using Neo4j 1.5-SNAPSHOT --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 164c3ef..21fb2d8 100644 --- a/pom.xml +++ b/pom.xml @@ -14,8 +14,8 @@ UTF-8 2.9.1 - 1.4.1 - 1.4.1 + 1.5-SNAPSHOT + 1.5-SNAPSHOT From 043e397a984b17c24bd675a2fd84cd1a9154ef9d Mon Sep 17 00:00:00 2001 From: FaKod Date: Thu, 6 Oct 2011 17:44:26 +0200 Subject: [PATCH 57/82] case class marshaling for Relations --- .../scala/org/neo4j/scala/Neo4jWrapper.scala | 24 +++++++++++-------- .../neo4j/scala/Neo4jWrapperImplicits.scala | 4 ++-- .../scala/unittest/DeSerializingTest.scala | 15 ++++++++++++ 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index b95385b..0dfeba9 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -44,7 +44,8 @@ trait Neo4jWrapper extends GraphDatabaseServiceProvider with Neo4jWrapperImplici /** * convenience method to create and serialize a case class */ - def createNode[T <: Product](cc: T)(implicit ds: DatabaseService): Node = Neo4jWrapper.serialize(cc, createNode) + def createNode[T <: Product](cc: T)(implicit ds: DatabaseService): Node = + Neo4jWrapper.serialize(cc, createNode).asInstanceOf[Node] } /** @@ -61,12 +62,12 @@ object Neo4jWrapper extends Neo4jWrapperImplicits { /** * serializes a given case class into a Node instance */ - def serialize[T <: Product](cc: T, node: Node): Node = { + def serialize(cc: Product, pc: PropertyContainer): PropertyContainer = { CaseClassDeserializer.serialize(cc).foreach { - case (name, value) => node.setProperty(name, value) + case (name, value) => pc.setProperty(name, value) } - node(ClassPropertyName) = cc.getClass.getName - node + pc(ClassPropertyName) = cc.getClass.getName + pc } /** @@ -74,10 +75,10 @@ object Neo4jWrapper extends Neo4jWrapperImplicits { * Some(T) if possible * None if not */ - def toCC[T <: Product](node: Node)(implicit m: ClassManifest[T]): Option[T] = { - node[String](ClassPropertyName) match { + def toCC[T <: Product](pc: PropertyContainer)(implicit m: ClassManifest[T]): Option[T] = { + pc[String](ClassPropertyName) match { case Some(cpn) if (cpn.equals(m.erasure.getName)) => - val kv = for (k <- node.getPropertyKeys; v = node.getProperty(k)) yield (k -> v) + val kv = for (k <- pc.getPropertyKeys; v = pc.getProperty(k)) yield (k -> v) val o = deserialize[T](kv.toMap) Some(o) case _ => @@ -90,8 +91,8 @@ object Neo4jWrapper extends Neo4jWrapperImplicits { * throws a IllegalArgumentException if a Nodes properties * do not fit to the case class properties */ - def deSerialize[T <: Product](node: Node)(implicit m: ClassManifest[T]): T = { - toCC[T](node) match { + def deSerialize[T <: Product](pc: PropertyContainer)(implicit m: ClassManifest[T]): T = { + toCC[T](pc) match { case Some(t) => t case _ => throw new IllegalArgumentException("given Case Class: " + m.erasure.getName + " does not fit to serialized properties") } @@ -111,6 +112,9 @@ private[scala] class NodeRelationshipMethods(node: Node, rel: Relationship = nul *
start --> "KNOWS" --> end <;
*/ def < = rel + + def <(cc: Product) = + Neo4jWrapper.serialize(cc, rel).asInstanceOf[Relationship] } /** diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala index 2205e4c..8761e6d 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala @@ -49,7 +49,7 @@ trait Neo4jWrapperImplicits { /** * for serialization */ - implicit def nodeToCaseClass(node: Node) = new { - def toCC[T <: Product](implicit m: ClassManifest[T]): Option[T] = Neo4jWrapper.toCC[T](node) + implicit def nodeToCaseClass(pc: PropertyContainer) = new { + def toCC[T <: Product](implicit m: ClassManifest[T]): Option[T] = Neo4jWrapper.toCC[T](pc) } } diff --git a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala index d8743c8..f098993 100644 --- a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala @@ -3,6 +3,7 @@ package org.neo4j.scala.unittest import org.specs2.mutable.SpecificationWithJUnit import org.neo4j.scala.{EmbeddedGraphDatabaseServiceProvider, Neo4jWrapper} import org.neo4j.scala.util.CaseClassDeserializer +import org.neo4j.graphdb.{Direction, DynamicRelationshipType} /** * Test spec to check deserialization and serialization of case classes @@ -92,5 +93,19 @@ class DeSerializingSpec extends SpecificationWithJUnit with Neo4jWrapper with Em Neo4jWrapper.deSerialize[NotTest](node) must throwA[IllegalArgumentException] } + + "be possible with relations" in { + val o = Test2(1, 3.3, true) + withTx { + implicit neo => + val start = createNode + val end = createNode + end <-- "foo" <-- start <(o) + + val rel = start.getSingleRelationship("foo", Direction.OUTGOING) + val oo = rel.toCC[Test2] + oo must beEqualTo(Some(o)) + } + } } } \ No newline at end of file From 8779c1c22097c1c1512f04dd034cf1ed85d440ba Mon Sep 17 00:00:00 2001 From: FaKod Date: Thu, 6 Oct 2011 20:30:43 +0200 Subject: [PATCH 58/82] convenience in using case classes with Relations --- README.md | 7 +++++-- .../scala/org/neo4j/scala/Neo4jWrapper.scala | 16 +++++++++------- .../neo4j/scala/unittest/DeSerializingTest.scala | 2 +- .../neo4j/scala/unittest/Neo4jWrapperTest.scala | 14 ++++++++------ 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d2c89bf..6bca17d 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Using this wrapper, this is how creating two relationships can look in Scala: Returning the Relation Object: - val relation = start --> "KNOWS" --> end <; + val relation = start --> "KNOWS" --> end < Properties ---------- @@ -101,7 +101,7 @@ Neo4j provides storing keys (String) and values (Object) into Nodes. To store Ca . . . withTx { implicit neo => - // create Node with Case Class Test + // create new Node with Case Class Test val node1 = createNode(Test("Something", 1, 2, 3.3, 10, true)) // "recreate" Case Class Test from Node @@ -112,6 +112,9 @@ Neo4j provides storing keys (String) and values (Object) into Nodes. To store Ca // yield all Nodes that are of type Case Class Test val tests = for(n <- getTraverser; t <- n.toCC[Test]) yield t + + // create new relation with Case Class Test + node1 --> "foo" --> node2 < Test("other", 0, 1, 1.3, 1, false) } Traversing diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index 0dfeba9..d60d117 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -45,7 +45,7 @@ trait Neo4jWrapper extends GraphDatabaseServiceProvider with Neo4jWrapperImplici * convenience method to create and serialize a case class */ def createNode[T <: Product](cc: T)(implicit ds: DatabaseService): Node = - Neo4jWrapper.serialize(cc, createNode).asInstanceOf[Node] + Neo4jWrapper.serialize(cc, createNode) } /** @@ -62,12 +62,12 @@ object Neo4jWrapper extends Neo4jWrapperImplicits { /** * serializes a given case class into a Node instance */ - def serialize(cc: Product, pc: PropertyContainer): PropertyContainer = { + def serialize[T <: PropertyContainer](cc: Product, pc: PropertyContainer): T = { CaseClassDeserializer.serialize(cc).foreach { case (name, value) => pc.setProperty(name, value) } pc(ClassPropertyName) = cc.getClass.getName - pc + pc.asInstanceOf[T] } /** @@ -109,12 +109,14 @@ private[scala] class NodeRelationshipMethods(node: Node, rel: Relationship = nul /** * use this to get the created relationship object - *
start --> "KNOWS" --> end <;
+ *
start --> "KNOWS" --> end <()
*/ - def < = rel + def <() = rel - def <(cc: Product) = - Neo4jWrapper.serialize(cc, rel).asInstanceOf[Relationship] + /** + *
start --> "KNOWS" --> end <(MyCaseClass(...))
+ */ + def <(cc: Product):Relationship = Neo4jWrapper.serialize(cc, rel) } /** diff --git a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala index f098993..3beaa27 100644 --- a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala @@ -100,7 +100,7 @@ class DeSerializingSpec extends SpecificationWithJUnit with Neo4jWrapper with Em implicit neo => val start = createNode val end = createNode - end <-- "foo" <-- start <(o) + end <-- "foo" <-- start < o val rel = start.getSingleRelationship("foo", Direction.OUTGOING) val oo = rel.toCC[Test2] diff --git a/src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala b/src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala index 11ea1aa..91e7dde 100644 --- a/src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala @@ -26,9 +26,10 @@ class Neo4jWrapperSpec extends SpecificationWithJUnit with Neo4jWrapper with Emb val start = createNode val end = createNode val relType = DynamicRelationshipType.withName("foo") - start --> relType --> end - start.getSingleRelationship(relType, Direction.OUTGOING). - getOtherNode(start) must beEqualTo(end) + val rel1 = start --> relType --> end < + val rel2 = start.getSingleRelationship(relType, Direction.OUTGOING) + rel2.getOtherNode(start) must beEqualTo(end) + rel1 must beEqualTo(rel2) } } @@ -49,9 +50,10 @@ class Neo4jWrapperSpec extends SpecificationWithJUnit with Neo4jWrapper with Emb val start = createNode val end = createNode val relType = DynamicRelationshipType.withName("foo") - end <-- relType <-- start - start.getSingleRelationship(relType, Direction.OUTGOING). - getOtherNode(start) must beEqualTo(end) + val rel1 = end <-- relType <-- start < + val rel2 = start.getSingleRelationship(relType, Direction.OUTGOING) + rel2.getOtherNode(start) must beEqualTo(end) + rel1 must beEqualTo(rel2) } } From 87675f427524f7e2b2207d22909ad9b686ba2f7c Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 12 Oct 2011 12:47:23 +0200 Subject: [PATCH 59/82] changed all T <: Product to AnyRef --- .../scala/org/neo4j/scala/Neo4jWrapper.scala | 26 ++++++++++++++----- .../neo4j/scala/Neo4jWrapperImplicits.scala | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index d60d117..9cbf1c6 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -44,7 +44,7 @@ trait Neo4jWrapper extends GraphDatabaseServiceProvider with Neo4jWrapperImplici /** * convenience method to create and serialize a case class */ - def createNode[T <: Product](cc: T)(implicit ds: DatabaseService): Node = + def createNode(cc: AnyRef)(implicit ds: DatabaseService): Node = Neo4jWrapper.serialize(cc, createNode) } @@ -62,7 +62,7 @@ object Neo4jWrapper extends Neo4jWrapperImplicits { /** * serializes a given case class into a Node instance */ - def serialize[T <: PropertyContainer](cc: Product, pc: PropertyContainer): T = { + def serialize[T <: PropertyContainer](cc: AnyRef, pc: PropertyContainer): T = { CaseClassDeserializer.serialize(cc).foreach { case (name, value) => pc.setProperty(name, value) } @@ -75,7 +75,20 @@ object Neo4jWrapper extends Neo4jWrapperImplicits { * Some(T) if possible * None if not */ - def toCC[T <: Product](pc: PropertyContainer)(implicit m: ClassManifest[T]): Option[T] = { + def toCC[T: Manifest](pc: PropertyContainer): Option[T] = + if (toCCPossible(pc)) { + val kv = for (k <- pc.getPropertyKeys; v = pc.getProperty(k)) yield (k -> v) + val o = deserialize[T](kv.toMap) + Some(o) + } + else + None + + /** + * only checks if this property container has been serialized + * with T + */ + def toCCPossible[T: Manifest](pc: PropertyContainer): Boolean = pc[String](ClassPropertyName) match { case Some(cpn) if (cpn.equals(m.erasure.getName)) => val kv = for (k <- pc.getPropertyKeys; v = pc.getProperty(k)) yield (k -> v) @@ -91,10 +104,11 @@ object Neo4jWrapper extends Neo4jWrapperImplicits { * throws a IllegalArgumentException if a Nodes properties * do not fit to the case class properties */ - def deSerialize[T <: Product](pc: PropertyContainer)(implicit m: ClassManifest[T]): T = { + def deSerialize[T: Manifest](pc: PropertyContainer): T = { toCC[T](pc) match { case Some(t) => t - case _ => throw new IllegalArgumentException("given Case Class: " + m.erasure.getName + " does not fit to serialized properties") + case _ => throw new IllegalArgumentException("given Case Class: " + + manifest[T].erasure.getName + " does not fit to serialized properties") } } } @@ -116,7 +130,7 @@ private[scala] class NodeRelationshipMethods(node: Node, rel: Relationship = nul /** *
start --> "KNOWS" --> end <(MyCaseClass(...))
*/ - def <(cc: Product):Relationship = Neo4jWrapper.serialize(cc, rel) + def <(cc: AnyRef): Relationship = Neo4jWrapper.serialize(cc, rel) } /** diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala index 8761e6d..60554b9 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala @@ -50,6 +50,6 @@ trait Neo4jWrapperImplicits { * for serialization */ implicit def nodeToCaseClass(pc: PropertyContainer) = new { - def toCC[T <: Product](implicit m: ClassManifest[T]): Option[T] = Neo4jWrapper.toCC[T](pc) + def toCC[T: Manifest]: Option[T] = Neo4jWrapper.toCC[T](pc) } } From aa218a4a0100df7e3195f3e5a3014ec46b23b4c4 Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 12 Oct 2011 12:48:50 +0200 Subject: [PATCH 60/82] added method toCCPossible to allow "instance of" checks --- src/main/scala/org/neo4j/scala/Neo4jWrapper.scala | 9 +++------ .../scala/org/neo4j/scala/Neo4jWrapperImplicits.scala | 2 ++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index 9cbf1c6..0d628b7 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -90,14 +90,11 @@ object Neo4jWrapper extends Neo4jWrapperImplicits { */ def toCCPossible[T: Manifest](pc: PropertyContainer): Boolean = pc[String](ClassPropertyName) match { - case Some(cpn) if (cpn.equals(m.erasure.getName)) => - val kv = for (k <- pc.getPropertyKeys; v = pc.getProperty(k)) yield (k -> v) - val o = deserialize[T](kv.toMap) - Some(o) + case Some(cpn) if (cpn.equals(manifest[T].erasure.getName)) => + true case _ => - None + false } - } /** * deserializes a given case class type from a given Node instance diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala index 60554b9..a67c86a 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala @@ -51,5 +51,7 @@ trait Neo4jWrapperImplicits { */ implicit def nodeToCaseClass(pc: PropertyContainer) = new { def toCC[T: Manifest]: Option[T] = Neo4jWrapper.toCC[T](pc) + + def toCCPossible[T: Manifest]: Boolean = Neo4jWrapper.toCCPossible[T](pc) } } From aa1906ef5506616160a1daca0f4e93ad1a160fe5 Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 12 Oct 2011 12:49:43 +0200 Subject: [PATCH 61/82] added some missing GraphDatabaseService methods --- .../scala/org/neo4j/scala/Neo4jWrapper.scala | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index 0d628b7..4ae21b9 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -2,8 +2,9 @@ package org.neo4j.scala import util.CaseClassDeserializer import collection.JavaConversions._ -import org.neo4j.graphdb.{Relationship, PropertyContainer, RelationshipType, Node} import CaseClassDeserializer._ +import org.neo4j.graphdb._ +import index.IndexManager /** * Extend your class with this trait to get really neat new notation for creating @@ -46,6 +47,71 @@ trait Neo4jWrapper extends GraphDatabaseServiceProvider with Neo4jWrapperImplici */ def createNode(cc: AnyRef)(implicit ds: DatabaseService): Node = Neo4jWrapper.serialize(cc, createNode) + + /** + * Looks up a node by id. + * + * @param id the id of the node + * @return the node with id id if found + * @throws NotFoundException if not found + */ + def getNodeById(id: Long)(implicit ds: DatabaseService): Node = + ds.gds.getNodeById(id) + + /** + * Looks up a relationship by id. + * + * @param id the id of the relationship + * @return the relationship with id id if found + * @throws NotFoundException if not found + */ + def getRelationshipById(id: Long)(implicit ds: DatabaseService): Relationship = + ds.gds.getRelationshipById(id) + + /** + * Returns the reference node, which is a "starting point" in the node + * space. Usually, a client attaches relationships to this node that leads + * into various parts of the node space. For more information about common + * node space organizational patterns, see the design guide at wiki.neo4j.org/content/Design_Guide. + * + * @return the reference node + * @throws NotFoundException if unable to get the reference node + */ + def getReferenceNode(implicit ds: DatabaseService): Node = + ds.gds.getReferenceNode + + /** + * Returns all nodes in the node space. + * + * @return all nodes in the node space + */ + def getAllNodes(implicit ds: DatabaseService): Iterable[Node] = + ds.gds.getAllNodes + + /** + * Returns all relationship types currently in the underlying store. + * Relationship types are added to the underlying store the first time they + * are used in a successfully commited {@link Node#createRelationshipTo + * node.createRelationshipTo(...)}. Note that this method is guaranteed to + * return all known relationship types, but it does not guarantee that it + * won't return more than that (e.g. it can return "historic" + * relationship types that no longer have any relationships in the node + * space). + * + * @return all relationship types in the underlying store + */ + def getRelationshipTypes(implicit ds: DatabaseService): Iterable[RelationshipType] = + ds.gds.getRelationshipTypes + + /** + * Shuts down Neo4j. After this method has been invoked, it's invalid to + * invoke any methods in the Neo4j API and all references to this instance + * of GraphDatabaseService should be discarded. + */ + def shutdown(implicit ds: DatabaseService): Unit = + ds.gds.shutdown } /** From 411f78a05392bebddaa6fa18e59e50831d11294c Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 12 Oct 2011 12:50:56 +0200 Subject: [PATCH 62/82] significant (un-)marshaling performance improvements (caching of Scala signature) --- .../scala/org/neo4j/scala/util/Poso.scala | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/util/Poso.scala b/src/main/scala/org/neo4j/scala/util/Poso.scala index 2cca830..0342faf 100644 --- a/src/main/scala/org/neo4j/scala/util/Poso.scala +++ b/src/main/scala/org/neo4j/scala/util/Poso.scala @@ -1,7 +1,7 @@ package org.neo4j.scala.util import scalax.rules.scalasig._ -import collection.mutable.ArrayBuffer +import collection.mutable.{SynchronizedMap, ArrayBuffer, HashMap} /** * helper class to store Class object @@ -13,14 +13,24 @@ case class JavaType(c: Class[_]) */ object CaseClassDeserializer { + /** + * Method Map cache for method serialize + */ + private val methodCache = new HashMap[AnyRef, Map[String, java.lang.reflect.Method]]() + with SynchronizedMap[AnyRef, Map[String, java.lang.reflect.Method]] + + /** + * signature parser cache + */ + private val sigParserCache = new HashMap[Class[_], Seq[(String, JavaType)]]() + with SynchronizedMap[Class[_], Seq[(String, JavaType)]] + /** * convenience method using class manifest * use it like val test = deserialize[Test](myMap) */ - def deserialize[T: ClassManifest](m: Map[String, AnyRef]): T = { - val cm = implicitly[ClassManifest[T]] - deserialize(m, JavaType(cm.erasure)).asInstanceOf[T] - } + def deserialize[T: Manifest](m: Map[String, AnyRef]): T = + deserialize(m, JavaType(manifest[T].erasure)).asInstanceOf[T] /**Creates a case class instance from parameter map * @@ -30,7 +40,7 @@ object CaseClassDeserializer { def deserialize(m: Map[String, AnyRef], javaTypeTarget: JavaType) = { require(javaTypeTarget.c.getConstructors.length == 1, "Case classes must only have one constructor.") val constructor = javaTypeTarget.c.getConstructors.head - val params = CaseClassSigParser.parse(javaTypeTarget.c) + val params = sigParserCache.getOrElseUpdate(javaTypeTarget.c, CaseClassSigParser.parse(javaTypeTarget.c)) val values = new ArrayBuffer[AnyRef] for ((paramName, paramType) <- params) { @@ -38,7 +48,7 @@ object CaseClassDeserializer { /** * if the value is directly assignable: use it - * otherwise try to create an instnace using der String Constructor + * otherwise try to create an instance using der String Constructor */ if (field.getClass.isAssignableFrom(paramType.c)) values += field @@ -56,14 +66,15 @@ object CaseClassDeserializer { * @param o AnyRef case class instance */ def serialize(o: AnyRef): Map[String, AnyRef] = { - val methods = o.getClass.getDeclaredMethods - .filter { - _.getParameterTypes.isEmpty - } - .map { - m => m.getName -> m - }.toMap - val params = CaseClassSigParser.parse(o.getClass) + val methods = methodCache.getOrElseUpdate(o, + o.getClass.getDeclaredMethods + .filter { + _.getParameterTypes.isEmpty + } + .map { + m => m.getName -> m + }.toMap) + val params = sigParserCache.getOrElseUpdate(o.getClass, CaseClassSigParser.parse(o.getClass)) val l = for ((paramName, _) <- params; value = methods.get(paramName).get.invoke(o)) yield (paramName, value) l.toMap } From 86006a09b9ffd20c298eec200dc4d510e0ffd573 Mon Sep 17 00:00:00 2001 From: FaKod Date: Sun, 16 Oct 2011 20:06:07 +0200 Subject: [PATCH 63/82] added singleton Db Service Provider --- .../neo4j/scala/DatabaseServiceProvider.scala | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala b/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala index 0dc49bc..e855c77 100644 --- a/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala +++ b/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala @@ -28,3 +28,23 @@ trait EmbeddedGraphDatabaseServiceProvider extends GraphDatabaseServiceProvider val ds: DatabaseService = DatabaseServiceImpl(new EmbeddedGraphDatabase(neo4jStoreDir)) } +/** + * provides a specific Database Service + * in this case an singleton embedded database service + */ +trait SingletonEmbeddedGraphDatabaseServiceProvider extends GraphDatabaseServiceProvider { + + object Provider { + val ds: DatabaseService = DatabaseServiceImpl(new EmbeddedGraphDatabase(neo4jStoreDir)) + } + + /** + * directory where to store the data files + */ + def neo4jStoreDir: String + + /** + * using an instance of an embedded graph database + */ + val ds: DatabaseService = Provider.ds +} \ No newline at end of file From 5875fa8a6d983f5b556549ee6defe5abc8b884a2 Mon Sep 17 00:00:00 2001 From: FaKod Date: Sun, 16 Oct 2011 20:08:44 +0200 Subject: [PATCH 64/82] added Scala Lib Shutdown Hook --- .../scala/unittest/DeSerializingTest.scala | 9 +- .../org/neo4j/scala/unittest/IndexTest.scala | 9 +- .../scala/unittest/Neo4jWrapperTest.scala | 155 +++++++++--------- 3 files changed, 85 insertions(+), 88 deletions(-) diff --git a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala index 3beaa27..c2771ba 100644 --- a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala @@ -4,6 +4,7 @@ import org.specs2.mutable.SpecificationWithJUnit import org.neo4j.scala.{EmbeddedGraphDatabaseServiceProvider, Neo4jWrapper} import org.neo4j.scala.util.CaseClassDeserializer import org.neo4j.graphdb.{Direction, DynamicRelationshipType} +import sys.ShutdownHookThread /** * Test spec to check deserialization and serialization of case classes @@ -52,11 +53,9 @@ class DeSerializingSpec extends SpecificationWithJUnit with Neo4jWrapper with Em "Node" should { - Runtime.getRuntime.addShutdownHook(new Thread() { - override def run() { - ds.gds.shutdown - } - }) + ShutdownHookThread { + shutdown(ds) + } "be serializable with Test" in { val o = Test("sowas", 1, 2, 3.3, 10, true) diff --git a/src/test/scala/org/neo4j/scala/unittest/IndexTest.scala b/src/test/scala/org/neo4j/scala/unittest/IndexTest.scala index 451469f..976d3c4 100644 --- a/src/test/scala/org/neo4j/scala/unittest/IndexTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/IndexTest.scala @@ -3,6 +3,7 @@ package org.neo4j.scala.unittest import org.specs2.mutable.SpecificationWithJUnit import org.neo4j.scala.{Neo4jIndexProvider, EmbeddedGraphDatabaseServiceProvider, Neo4jWrapper} import collection.JavaConversions._ +import sys.ShutdownHookThread /** * Test spec to check usage of index convenience methods @@ -19,11 +20,9 @@ class IndexTestSpec extends SpecificationWithJUnit with Neo4jWrapper with Embedd "Neo4jIndexProvider" should { - Runtime.getRuntime.addShutdownHook(new Thread() { - override def run() { - ds.gds.shutdown - } - }) + ShutdownHookThread { + shutdown(ds) + } "use the fulltext search index" in { diff --git a/src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala b/src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala index 91e7dde..22ea6e4 100644 --- a/src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala @@ -3,6 +3,7 @@ package org.neo4j.scala.unittest import org.neo4j.graphdb._ import org.specs2.mutable.SpecificationWithJUnit import org.neo4j.scala.{EmbeddedGraphDatabaseServiceProvider, Neo4jWrapper} +import sys.ShutdownHookThread /** * Test spec to check relationship builder and evaluators @@ -14,160 +15,158 @@ class Neo4jWrapperSpec extends SpecificationWithJUnit with Neo4jWrapper with Emb "NeoWrapper" should { - Runtime.getRuntime.addShutdownHook(new Thread() { - override def run() { - ds.gds.shutdown - } - }) + ShutdownHookThread { + shutdown(ds) + } "create a new relationship in --> relType --> notation" in { withTx { implicit neo => - val start = createNode - val end = createNode - val relType = DynamicRelationshipType.withName("foo") - val rel1 = start --> relType --> end < - val rel2 = start.getSingleRelationship(relType, Direction.OUTGOING) - rel2.getOtherNode(start) must beEqualTo(end) - rel1 must beEqualTo(rel2) + val start = createNode + val end = createNode + val relType = DynamicRelationshipType.withName("foo") + val rel1 = start --> relType --> end < + val rel2 = start.getSingleRelationship(relType, Direction.OUTGOING) + rel2.getOtherNode(start) must beEqualTo(end) + rel1 must beEqualTo(rel2) } } "create a new relationship in --> \"relName\" --> notation" in { withTx { implicit neo => - val start = createNode - val end = createNode - start --> "foo" --> end - start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). - getOtherNode(start) must beEqualTo(end) + val start = createNode + val end = createNode + start --> "foo" --> end + start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). + getOtherNode(start) must beEqualTo(end) } } "create a new relationship in <-- relType <-- notation" in { withTx { implicit neo => - val start = createNode - val end = createNode - val relType = DynamicRelationshipType.withName("foo") - val rel1 = end <-- relType <-- start < - val rel2 = start.getSingleRelationship(relType, Direction.OUTGOING) - rel2.getOtherNode(start) must beEqualTo(end) - rel1 must beEqualTo(rel2) + val start = createNode + val end = createNode + val relType = DynamicRelationshipType.withName("foo") + val rel1 = end <-- relType <-- start < + val rel2 = start.getSingleRelationship(relType, Direction.OUTGOING) + rel2.getOtherNode(start) must beEqualTo(end) + rel1 must beEqualTo(rel2) } } "create a new relationship in <-- \"relName\" <-- notation" in { withTx { implicit neo => - val start = createNode - val end = createNode - end <-- "foo" <-- start - start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). - getOtherNode(start) must beEqualTo(end) + val start = createNode + val end = createNode + end <-- "foo" <-- start + start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). + getOtherNode(start) must beEqualTo(end) } } "allow relationships of the same direction to be chained" in { withTx { implicit neo => - val start = createNode - val middle = createNode - val end = createNode - start --> "foo" --> middle --> "bar" --> end - start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). - getOtherNode(start) must beEqualTo(middle) - middle.getSingleRelationship(DynamicRelationshipType.withName("bar"), Direction.OUTGOING). - getOtherNode(middle) must beEqualTo(end) + val start = createNode + val middle = createNode + val end = createNode + start --> "foo" --> middle --> "bar" --> end + start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). + getOtherNode(start) must beEqualTo(middle) + middle.getSingleRelationship(DynamicRelationshipType.withName("bar"), Direction.OUTGOING). + getOtherNode(middle) must beEqualTo(end) } } "allow relationships of different directions to be chained" in { withTx { implicit neo => - val left = createNode - val middle = createNode - val right = createNode - left --> "foo" --> middle <-- "bar" <-- right - left.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). - getOtherNode(left) must beEqualTo(middle) - right.getSingleRelationship(DynamicRelationshipType.withName("bar"), Direction.OUTGOING). - getOtherNode(right) must beEqualTo(middle) + val left = createNode + val middle = createNode + val right = createNode + left --> "foo" --> middle <-- "bar" <-- right + left.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). + getOtherNode(left) must beEqualTo(middle) + right.getSingleRelationship(DynamicRelationshipType.withName("bar"), Direction.OUTGOING). + getOtherNode(right) must beEqualTo(middle) } } "ignore a relationshipBuilder with no end node" in { withTx { implicit neo => - val start = createNode - start --> "foo" - start.getRelationships.iterator.hasNext must beEqualTo(false) + val start = createNode + start --> "foo" + start.getRelationships.iterator.hasNext must beEqualTo(false) } } "read a property in a node in node('property') notation" in { withTx { implicit neo => - val start = createNode - start.setProperty("foo", "bar") - start("foo") must beEqualTo(Some("bar")) - start("bar") must beEqualTo(None) + val start = createNode + start.setProperty("foo", "bar") + start("foo") must beEqualTo(Some("bar")) + start("bar") must beEqualTo(None) } } "create a property in a node in node('property')=value notation" in { withTx { implicit neo => - val start = createNode - start("foo") = "bar" - start.getProperty("foo") must beEqualTo("bar") + val start = createNode + start("foo") = "bar" + start.getProperty("foo") must beEqualTo("bar") } } "read a property in a relationship in rel('property') notation" in { withTx { implicit neo => - val start = createNode - val end = createNode - val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) - rel.setProperty("foo", "bar") - rel("foo") must beEqualTo(Some("bar")) - rel("bar") must beEqualTo(None) + val start = createNode + val end = createNode + val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) + rel.setProperty("foo", "bar") + rel("foo") must beEqualTo(Some("bar")) + rel("bar") must beEqualTo(None) } } "create a property in a relationship in rel('property')=value notation" in { withTx { implicit neo => - val start = createNode - val end = createNode - val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) - rel("foo") = "bar" - rel.getProperty("foo") must beEqualTo("bar") + val start = createNode + val end = createNode + val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) + rel("foo") = "bar" + rel.getProperty("foo") must beEqualTo("bar") } } "allow writing stop evaluators in a functional style" in { withTx { implicit neo => - val start = createNode - val end = createNode - val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) - val traverser = start.traverse(Traverser.Order.BREADTH_FIRST, (tp: TraversalPosition) => false, ReturnableEvaluator.ALL_BUT_START_NODE, DynamicRelationshipType.withName("foo"), Direction.OUTGOING) - traverser.iterator.hasNext must beEqualTo(true) - traverser.iterator.next must beEqualTo(end) + val start = createNode + val end = createNode + val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) + val traverser = start.traverse(Traverser.Order.BREADTH_FIRST, (tp: TraversalPosition) => false, ReturnableEvaluator.ALL_BUT_START_NODE, DynamicRelationshipType.withName("foo"), Direction.OUTGOING) + traverser.iterator.hasNext must beEqualTo(true) + traverser.iterator.next must beEqualTo(end) } } "allow writing returnable evaluators in a functional style" in { withTx { implicit neo => - val start = createNode - val end = createNode - val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) - val traverser = start.traverse(Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, (tp: TraversalPosition) => tp.notStartNode(), DynamicRelationshipType.withName("foo"), Direction.OUTGOING) - traverser.iterator.hasNext must beEqualTo(true) - traverser.iterator.next must beEqualTo(end) + val start = createNode + val end = createNode + val rel = start.createRelationshipTo(end, DynamicRelationshipType.withName("foo")) + val traverser = start.traverse(Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, (tp: TraversalPosition) => tp.notStartNode(), DynamicRelationshipType.withName("foo"), Direction.OUTGOING) + traverser.iterator.hasNext must beEqualTo(true) + traverser.iterator.next must beEqualTo(end) } } } From 8587bcf576972ad84bea255130d79345a04a485b Mon Sep 17 00:00:00 2001 From: FaKod Date: Sun, 16 Oct 2011 22:00:15 +0200 Subject: [PATCH 65/82] removed index config from trait ctor --- .../org/neo4j/scala/Neo4jIndexProvider.scala | 60 ++++++++++++------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala index c54218d..47fe156 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala @@ -35,31 +35,48 @@ trait Neo4jIndexProvider { /** * private cache */ - private val nodeIndexStore = mutableMap[String, Index[Node]]() + private var nodeIndexStore: mutableMap[String, Index[Node]] = null /** * private cache */ - private val relationIndexStore = mutableMap[String, RelationshipIndex]() + private var relationIndexStore: mutableMap[String, RelationshipIndex] = null /** - * constructor creates indexes + * lazy initializes Indexes for Nodes */ - for (forNode <- NodeIndexConfig) { - nodeIndexStore += forNode._1 -> - (forNode._2 match { - case Some(config) => getIndexManager.forNodes(forNode._1, config) - case _ => getIndexManager.forNodes(forNode._1) - }) - } + private def getNodeIndexStore = + nodeIndexStore match { + case null => + nodeIndexStore = mutableMap[String, Index[Node]]() + for (forNode <- NodeIndexConfig) { + nodeIndexStore += forNode._1 -> + (forNode._2 match { + case Some(config) => getIndexManager.forNodes(forNode._1, config) + case _ => getIndexManager.forNodes(forNode._1) + }) + } + nodeIndexStore + case x => x + } - for (forRelation <- RelationIndexConfig) { - relationIndexStore += forRelation._1 -> - (forRelation._2 match { - case Some(config) => getIndexManager.forRelationships(forRelation._1, config) - case _ => getIndexManager.forRelationships(forRelation._1) - }) - } + /** + * lazy initializes Indexes for Relations + */ + private def getRelationIndexStore = + relationIndexStore match { + case null => + relationIndexStore = mutableMap[String, RelationshipIndex]() + for (forRelation <- RelationIndexConfig) { + relationIndexStore += forRelation._1 -> + (forRelation._2 match { + case Some(config) => getIndexManager.forRelationships(forRelation._1, config) + case _ => getIndexManager.forRelationships(forRelation._1) + }) + } + relationIndexStore + case x => x + } /** * returns the index manager @@ -70,12 +87,12 @@ trait Neo4jIndexProvider { /** * @return Option[Index[Node]] the created index if available */ - def getNodeIndex(name: String) = nodeIndexStore.get(name) + def getNodeIndex(name: String) = getNodeIndexStore.get(name) /** * @return Option[RelationshipIndex] the created index if available */ - def getRelationIndex(name: String) = relationIndexStore.get(name) + def getRelationIndex(name: String) = getRelationIndexStore.get(name) /** * conversion to ease the use of optional configuration @@ -85,10 +102,13 @@ trait Neo4jIndexProvider { /** * wrapper class for subsequent implicit conversion */ - class IndexWrapper[T <: PropertyContainer](i: Index[T]) { + class IndexWrapper[T <: PropertyContainer](i: Index[T]) { def +=(t: T, k: String, v: AnyRef) = i.add(t, k, v) + def -=(t: T, k: String, v: AnyRef) = i.remove(t, k, v) + def -=(t: T, k: String) = i.remove(t, k) + def -=(t: T) = i.remove(t) } From 1514b4128b328581eb189208a1b953cf716886de Mon Sep 17 00:00:00 2001 From: FaKod Date: Wed, 19 Oct 2011 21:34:56 +0200 Subject: [PATCH 66/82] initial commit to support transparent batch processing --- .../neo4j/scala/DatabaseServiceProvider.scala | 32 ++ .../neo4j/scala/Neo4jBatchIndexProvider.scala | 316 ++++++++++++++++++ 2 files changed, 348 insertions(+) create mode 100644 src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala diff --git a/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala b/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala index e855c77..735ab3f 100644 --- a/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala +++ b/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala @@ -1,6 +1,7 @@ package org.neo4j.scala import org.neo4j.kernel.EmbeddedGraphDatabase +import org.neo4j.kernel.impl.batchinsert.{BatchInserter, BatchInserterImpl} /** * Interface for a GraphDatabaseServiceProvider @@ -43,6 +44,37 @@ trait SingletonEmbeddedGraphDatabaseServiceProvider extends GraphDatabaseService */ def neo4jStoreDir: String + /** + * using an instance of an embedded graph database + */ + val ds: DatabaseService = Provider.ds +} + +/** + * provides a specific GraphDatabaseServiceProvider for + * Batch processing + */ +trait BatchGraphDatabaseServiceProvider extends GraphDatabaseServiceProvider { + + /** + * singleton provider + */ + object Provider { + val inserter: BatchInserter = new BatchInserterImpl(neo4jStoreDir) + + val ds: DatabaseService = DatabaseServiceImpl(inserter.getGraphDbService) + } + + /** + * instance of BatchInserter + */ + def batchInserter = Provider.inserter + + /** + * directory where to store the data files + */ + def neo4jStoreDir: String + /** * using an instance of an embedded graph database */ diff --git a/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala b/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala new file mode 100644 index 0000000..21826c3 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala @@ -0,0 +1,316 @@ +package org.neo4j.scala + +import org.neo4j.kernel.impl.batchinsert.BatchInserter +import java.util.{Map => juMap} +import org.neo4j.graphdb.index._ +import org.neo4j.graphdb.Traverser.Order +import org.neo4j.graphdb._ +import org.neo4j.index.impl.lucene.{AbstractIndexHits, LuceneBatchInserterIndexProvider} +import collection.JavaConversions._ +import sun.reflect.generics.reflectiveObjects.NotImplementedException + +/** + * provides Index access trait + * class must mixin a trait that provides an instance of class BatchInserter + * i.g. BatchGraphDatabaseServiceProvider + */ +trait Neo4jBatchIndexProvider extends Neo4jIndexProvider { + + /** + * instance of BatchInserter + */ + def batchInserter: BatchInserter + + /** + * store for IndexManager + */ + override def getIndexManager: IndexManager = new BatchIndexManager(batchInserter) +} + + +/** + * delegated methods of IndexManager to BatchInserter + */ +class BatchIndexManager(bi: BatchInserter) extends IndexManager { + + /** + * instance of LuceneBatchInserterIndexProvider + */ + private val batchInserterIndexProvider: BatchInserterIndexProvider = new LuceneBatchInserterIndexProvider(bi) + + + def forNodes(indexName: String, customConfiguration: juMap[String, String]) = + new BatchIndex(batchInserterIndexProvider.nodeIndex(indexName, customConfiguration)) + + def forRelationships(indexName: String, customConfiguration: juMap[String, String]) = + new BatchRelationshipIndex(batchInserterIndexProvider.relationshipIndex(indexName, customConfiguration)) + + /** + * Shuts down this index provider and ensures that all indexes are fully + * written to disk. + */ + def shutdown = batchInserterIndexProvider.shutdown + + def existsForNodes(indexName: String) = throw new NotImplementedException + + def forNodes(indexName: String) = throw new NotImplementedException + + def nodeIndexNames() = throw new NotImplementedException + + def existsForRelationships(indexName: String) = throw new NotImplementedException + + def forRelationships(indexName: String) = throw new NotImplementedException + + def relationshipIndexNames() = throw new NotImplementedException + + def getConfiguration(index: Index[_ <: PropertyContainer]) = throw new NotImplementedException + + def setConfiguration(index: Index[_ <: PropertyContainer], key: String, value: String) = throw new NotImplementedException + + def removeConfiguration(index: Index[_ <: PropertyContainer], key: String) = throw new NotImplementedException + + def getNodeAutoIndexer = throw new NotImplementedException + + def getRelationshipAutoIndexer = throw new NotImplementedException +} + + +/** + * delegates Index[Node] methods to BatchInserterIndex methods + */ +class BatchIndex(bii: BatchInserterIndex) extends Index[Node] { + + /** + * implicitly converts IndexHits[Long] to IndexHits[BatchNode] + */ + private implicit def toNodeIndexHits(hits: IndexHits[java.lang.Long]): IndexHits[Node] = { + val listOfNodes = for (l <- hits.iterator) yield new BatchNode(l) + new ConstantScoreIterator[Node](listOfNodes) + } + + def updateOrAdd(entityId: Long, properties: Map[String, AnyRef]) = bii.updateOrAdd(entityId, properties) + + def flush = bii.flush + + def setCacheCapacity(key: String, size: Int) = bii.setCacheCapacity(key, size) + + def add(node: Node, key: String, value: AnyRef) = bii.add(node.getId, Map(key -> value)) + + def get(key: String, value: AnyRef) = bii.get(key, value) + + def query(key: String, queryOrQueryObject: AnyRef) = bii.query(key, queryOrQueryObject) + + def query(queryOrQueryObject: AnyRef) = bii.query(queryOrQueryObject) + + def isWriteable = false + + def remove(entity: Node, key: String, value: AnyRef) { + throw new NotImplementedException + } + + def remove(entity: Node, key: String) { + throw new NotImplementedException + } + + def remove(entity: Node) { + throw new NotImplementedException + } + + def delete() { + throw new NotImplementedException + } + + def getName = throw new NotImplementedException + + def getEntityType = throw new NotImplementedException +} + +/** + * delegates RelationshipIndex methods to BatchInserterIndex methods + */ +class BatchRelationshipIndex(bii: BatchInserterIndex) extends RelationshipIndex { + + /** + * implicitly converts IndexHits[Long] to IndexHits[BatchRelationship] + */ + private implicit def toRelationshipIndexHits(hits: IndexHits[java.lang.Long]): IndexHits[Relationship] = { + val listOfNodes = for (l <- hits.iterator) yield new BatchRelationship(l) + new ConstantScoreIterator[Relationship](listOfNodes) + } + + def updateOrAdd(entityId: Long, properties: Map[String, AnyRef]) = bii.updateOrAdd(entityId, properties) + + def flush = bii.flush + + def setCacheCapacity(key: String, size: Int) = bii.setCacheCapacity(key, size) + + def add(entity: Relationship, key: String, value: AnyRef) = bii.add(entity.getId, Map(key -> value)) + + def get(key: String, value: AnyRef) = bii.get(key, value) + + def query(key: String, queryOrQueryObject: AnyRef) = bii.query(key, queryOrQueryObject) + + def query(queryOrQueryObject: AnyRef) = bii.query(queryOrQueryObject) + + def isWriteable = false + + def remove(entity: Relationship, key: String, value: AnyRef) { + throw new NotImplementedException + } + + def remove(entity: Relationship, key: String) { + throw new NotImplementedException + } + + def remove(entity: Relationship) { + throw new NotImplementedException + } + + def delete() { + throw new NotImplementedException + } + + def getName = throw new NotImplementedException + + def getEntityType = throw new NotImplementedException + + def get(key: String, valueOrNull: AnyRef, startNodeOrNull: Node, endNodeOrNull: Node) = throw new NotImplementedException + + def query(key: String, queryOrQueryObjectOrNull: AnyRef, startNodeOrNull: Node, endNodeOrNull: Node) = throw new NotImplementedException + + def query(queryOrQueryObjectOrNull: AnyRef, startNodeOrNull: Node, endNodeOrNull: Node) = throw new NotImplementedException +} + +/** + * this class exists because this class: + * see private static class org.neo4j.kernel.impl.batchinsert.NodeBatchImpl implements Node + * is private. This is just a id container + */ +class BatchNode(id: Long) extends Node { + + /** + * id + */ + def getId = id + + def delete() { + throw new NotImplementedException + } + + def getRelationships = throw new NotImplementedException + + def hasRelationship = throw new NotImplementedException + + def getRelationships(types: RelationshipType*) = throw new NotImplementedException + + def getRelationships(direction: Direction, types: RelationshipType*) = throw new NotImplementedException + + def hasRelationship(types: RelationshipType*) = throw new NotImplementedException + + def hasRelationship(direction: Direction, types: RelationshipType*) = throw new NotImplementedException + + def getRelationships(dir: Direction) = throw new NotImplementedException + + def hasRelationship(dir: Direction) = throw new NotImplementedException + + def getRelationships(`type` : RelationshipType, dir: Direction) = throw new NotImplementedException + + def hasRelationship(`type` : RelationshipType, dir: Direction) = throw new NotImplementedException + + def getSingleRelationship(`type` : RelationshipType, dir: Direction) = throw new NotImplementedException + + def createRelationshipTo(otherNode: Node, `type` : RelationshipType) = throw new NotImplementedException + + def traverse(traversalOrder: Order, stopEvaluator: StopEvaluator, returnableEvaluator: ReturnableEvaluator, relationshipType: RelationshipType, direction: Direction) = throw new NotImplementedException + + def traverse(traversalOrder: Order, stopEvaluator: StopEvaluator, returnableEvaluator: ReturnableEvaluator, firstRelationshipType: RelationshipType, firstDirection: Direction, secondRelationshipType: RelationshipType, secondDirection: Direction) = throw new NotImplementedException + + def traverse(traversalOrder: Order, stopEvaluator: StopEvaluator, returnableEvaluator: ReturnableEvaluator, relationshipTypesAndDirections: Object*) = throw new NotImplementedException + + def getGraphDatabase = throw new NotImplementedException + + def hasProperty(key: String) = throw new NotImplementedException + + def getProperty(key: String) = throw new NotImplementedException + + def getProperty(key: String, defaultValue: AnyRef) = throw new NotImplementedException + + def setProperty(key: String, value: AnyRef) { + throw new NotImplementedException + } + + def removeProperty(key: String) = throw new NotImplementedException + + def getPropertyKeys = throw new NotImplementedException + + def getPropertyValues = throw new NotImplementedException +} + +/** + * this class exists because this class: + * private static class org.neo4j.kernel.impl.batchinsert.RelationshipBatchImpl implements Relationship + * is private. This is just a id container + */ +class BatchRelationship(id: Long) extends Relationship { + + /** + * id + */ + def getId = id + + def delete() { + throw new NotImplementedException + } + + def getStartNode = throw new NotImplementedException + + def getEndNode = throw new NotImplementedException + + def getOtherNode(node: Node) = throw new NotImplementedException + + def getNodes = throw new NotImplementedException + + def getType = throw new NotImplementedException + + def isType(`type` : RelationshipType) = throw new NotImplementedException + + def getGraphDatabase = throw new NotImplementedException + + def hasProperty(key: String) = throw new NotImplementedException + + def getProperty(key: String) = throw new NotImplementedException + + def getProperty(key: String, defaultValue: AnyRef) = throw new NotImplementedException + + def setProperty(key: String, value: AnyRef) { + throw new NotImplementedException + } + + def removeProperty(key: String) = throw new NotImplementedException + + def getPropertyKeys = throw new NotImplementedException + + def getPropertyValues = throw new NotImplementedException +} + +/** + * replica of the original ConstantScoreIterator which has package visibility + * class org.neo4j.index.impl.lucene.ConstantScoreIterator extends AbstractIndexHits + */ +class ConstantScoreIterator[T](i: Iterator[T], score: Float = Float.NaN) extends AbstractIndexHits[T] { + + private final val items = i //.iterator + private final val _size: Int = items.size + + def currentScore: Float = { + return score + } + + def size: Int = { + return _size + } + + protected def fetchNextOrNull: T = { + return if (items.hasNext) items.next else null.asInstanceOf[T] + } +} \ No newline at end of file From d7d21a8573ac73bfddf0ff90d5370cae14e51c71 Mon Sep 17 00:00:00 2001 From: FaKod Date: Thu, 20 Oct 2011 21:12:45 +0200 Subject: [PATCH 67/82] fixed some bugs --- .../neo4j/scala/Neo4jBatchIndexProvider.scala | 162 ++++-------------- 1 file changed, 31 insertions(+), 131 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala b/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala index 21826c3..0248f38 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala @@ -25,6 +25,16 @@ trait Neo4jBatchIndexProvider extends Neo4jIndexProvider { * store for IndexManager */ override def getIndexManager: IndexManager = new BatchIndexManager(batchInserter) + + /** + * converts implicitly to the underlying batch instance + */ + implicit def nodeIndexToBatchIndex(ni: Index[Node]) = ni.asInstanceOf[BatchIndex] + + /** + * converts implicitly to the underlying batch instance + */ + implicit def relationIndexToBatchRelationshipIndex(ri: RelationshipIndex) = ri.asInstanceOf[BatchRelationshipIndex] } @@ -40,10 +50,10 @@ class BatchIndexManager(bi: BatchInserter) extends IndexManager { def forNodes(indexName: String, customConfiguration: juMap[String, String]) = - new BatchIndex(batchInserterIndexProvider.nodeIndex(indexName, customConfiguration)) + new BatchIndex(batchInserterIndexProvider.nodeIndex(indexName, customConfiguration), bi) def forRelationships(indexName: String, customConfiguration: juMap[String, String]) = - new BatchRelationshipIndex(batchInserterIndexProvider.relationshipIndex(indexName, customConfiguration)) + new BatchRelationshipIndex(batchInserterIndexProvider.relationshipIndex(indexName, customConfiguration), bi) /** * Shuts down this index provider and ensures that all indexes are fully @@ -78,14 +88,16 @@ class BatchIndexManager(bi: BatchInserter) extends IndexManager { /** * delegates Index[Node] methods to BatchInserterIndex methods */ -class BatchIndex(bii: BatchInserterIndex) extends Index[Node] { +class BatchIndex(bii: BatchInserterIndex, bi: BatchInserter) extends Index[Node] { + + private val gds = bi.getGraphDbService /** * implicitly converts IndexHits[Long] to IndexHits[BatchNode] */ private implicit def toNodeIndexHits(hits: IndexHits[java.lang.Long]): IndexHits[Node] = { - val listOfNodes = for (l <- hits.iterator) yield new BatchNode(l) - new ConstantScoreIterator[Node](listOfNodes) + val listOfNodes = for (l <- hits.iterator) yield gds.getNodeById(l) + new ConstantScoreIterator[Node](listOfNodes.toList) } def updateOrAdd(entityId: Long, properties: Map[String, AnyRef]) = bii.updateOrAdd(entityId, properties) @@ -128,14 +140,16 @@ class BatchIndex(bii: BatchInserterIndex) extends Index[Node] { /** * delegates RelationshipIndex methods to BatchInserterIndex methods */ -class BatchRelationshipIndex(bii: BatchInserterIndex) extends RelationshipIndex { +class BatchRelationshipIndex(bii: BatchInserterIndex, bi: BatchInserter) extends RelationshipIndex { + + private val gds = bi.getGraphDbService /** * implicitly converts IndexHits[Long] to IndexHits[BatchRelationship] */ private implicit def toRelationshipIndexHits(hits: IndexHits[java.lang.Long]): IndexHits[Relationship] = { - val listOfNodes = for (l <- hits.iterator) yield new BatchRelationship(l) - new ConstantScoreIterator[Relationship](listOfNodes) + val listOfNodes = for (l <- hits.iterator) yield gds.getRelationshipById(l) + new ConstantScoreIterator[Relationship](listOfNodes.toList) } def updateOrAdd(entityId: Long, properties: Map[String, AnyRef]) = bii.updateOrAdd(entityId, properties) @@ -181,136 +195,22 @@ class BatchRelationshipIndex(bii: BatchInserterIndex) extends RelationshipIndex def query(queryOrQueryObjectOrNull: AnyRef, startNodeOrNull: Node, endNodeOrNull: Node) = throw new NotImplementedException } -/** - * this class exists because this class: - * see private static class org.neo4j.kernel.impl.batchinsert.NodeBatchImpl implements Node - * is private. This is just a id container - */ -class BatchNode(id: Long) extends Node { - - /** - * id - */ - def getId = id - - def delete() { - throw new NotImplementedException - } - - def getRelationships = throw new NotImplementedException - - def hasRelationship = throw new NotImplementedException - - def getRelationships(types: RelationshipType*) = throw new NotImplementedException - - def getRelationships(direction: Direction, types: RelationshipType*) = throw new NotImplementedException - - def hasRelationship(types: RelationshipType*) = throw new NotImplementedException - - def hasRelationship(direction: Direction, types: RelationshipType*) = throw new NotImplementedException - - def getRelationships(dir: Direction) = throw new NotImplementedException - - def hasRelationship(dir: Direction) = throw new NotImplementedException - - def getRelationships(`type` : RelationshipType, dir: Direction) = throw new NotImplementedException - - def hasRelationship(`type` : RelationshipType, dir: Direction) = throw new NotImplementedException - - def getSingleRelationship(`type` : RelationshipType, dir: Direction) = throw new NotImplementedException - - def createRelationshipTo(otherNode: Node, `type` : RelationshipType) = throw new NotImplementedException - - def traverse(traversalOrder: Order, stopEvaluator: StopEvaluator, returnableEvaluator: ReturnableEvaluator, relationshipType: RelationshipType, direction: Direction) = throw new NotImplementedException - - def traverse(traversalOrder: Order, stopEvaluator: StopEvaluator, returnableEvaluator: ReturnableEvaluator, firstRelationshipType: RelationshipType, firstDirection: Direction, secondRelationshipType: RelationshipType, secondDirection: Direction) = throw new NotImplementedException - - def traverse(traversalOrder: Order, stopEvaluator: StopEvaluator, returnableEvaluator: ReturnableEvaluator, relationshipTypesAndDirections: Object*) = throw new NotImplementedException - - def getGraphDatabase = throw new NotImplementedException - - def hasProperty(key: String) = throw new NotImplementedException - - def getProperty(key: String) = throw new NotImplementedException - - def getProperty(key: String, defaultValue: AnyRef) = throw new NotImplementedException - - def setProperty(key: String, value: AnyRef) { - throw new NotImplementedException - } - - def removeProperty(key: String) = throw new NotImplementedException - - def getPropertyKeys = throw new NotImplementedException - - def getPropertyValues = throw new NotImplementedException -} - -/** - * this class exists because this class: - * private static class org.neo4j.kernel.impl.batchinsert.RelationshipBatchImpl implements Relationship - * is private. This is just a id container - */ -class BatchRelationship(id: Long) extends Relationship { - - /** - * id - */ - def getId = id - - def delete() { - throw new NotImplementedException - } - - def getStartNode = throw new NotImplementedException - - def getEndNode = throw new NotImplementedException - - def getOtherNode(node: Node) = throw new NotImplementedException - - def getNodes = throw new NotImplementedException - - def getType = throw new NotImplementedException - - def isType(`type` : RelationshipType) = throw new NotImplementedException - - def getGraphDatabase = throw new NotImplementedException - - def hasProperty(key: String) = throw new NotImplementedException - - def getProperty(key: String) = throw new NotImplementedException - - def getProperty(key: String, defaultValue: AnyRef) = throw new NotImplementedException - - def setProperty(key: String, value: AnyRef) { - throw new NotImplementedException - } - - def removeProperty(key: String) = throw new NotImplementedException - - def getPropertyKeys = throw new NotImplementedException - - def getPropertyValues = throw new NotImplementedException -} - /** * replica of the original ConstantScoreIterator which has package visibility * class org.neo4j.index.impl.lucene.ConstantScoreIterator extends AbstractIndexHits */ -class ConstantScoreIterator[T](i: Iterator[T], score: Float = Float.NaN) extends AbstractIndexHits[T] { +class ConstantScoreIterator[T](items: List[T], score: Float = Float.NaN) extends AbstractIndexHits[T] { - private final val items = i //.iterator private final val _size: Int = items.size + private final val iter = items.iterator - def currentScore: Float = { - return score - } + def currentScore: Float = score - def size: Int = { - return _size - } + def size: Int = _size - protected def fetchNextOrNull: T = { - return if (items.hasNext) items.next else null.asInstanceOf[T] - } + protected def fetchNextOrNull: T = + if (iter.hasNext) + iter.next + else + null.asInstanceOf[T] } \ No newline at end of file From 6fed45b27134a31166a42fc952658dd5129d2f6a Mon Sep 17 00:00:00 2001 From: FaKod Date: Thu, 20 Oct 2011 21:22:16 +0200 Subject: [PATCH 68/82] fixed dramatic memory leak --- src/main/scala/org/neo4j/scala/util/Poso.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/util/Poso.scala b/src/main/scala/org/neo4j/scala/util/Poso.scala index 0342faf..544c0c3 100644 --- a/src/main/scala/org/neo4j/scala/util/Poso.scala +++ b/src/main/scala/org/neo4j/scala/util/Poso.scala @@ -16,8 +16,8 @@ object CaseClassDeserializer { /** * Method Map cache for method serialize */ - private val methodCache = new HashMap[AnyRef, Map[String, java.lang.reflect.Method]]() - with SynchronizedMap[AnyRef, Map[String, java.lang.reflect.Method]] + private val methodCache = new HashMap[Class[_], Map[String, java.lang.reflect.Method]]() + with SynchronizedMap[Class[_], Map[String, java.lang.reflect.Method]] /** * signature parser cache @@ -66,7 +66,7 @@ object CaseClassDeserializer { * @param o AnyRef case class instance */ def serialize(o: AnyRef): Map[String, AnyRef] = { - val methods = methodCache.getOrElseUpdate(o, + val methods = methodCache.getOrElseUpdate(o.getClass, o.getClass.getDeclaredMethods .filter { _.getParameterTypes.isEmpty From 21b3b35b1a341645f427a9100047f80c129584ca Mon Sep 17 00:00:00 2001 From: FaKod Date: Thu, 20 Oct 2011 21:56:27 +0200 Subject: [PATCH 69/82] added index shutdown --- .../org/neo4j/scala/Neo4jBatchIndexProvider.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala b/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala index 0248f38..c589b5e 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala @@ -21,10 +21,18 @@ trait Neo4jBatchIndexProvider extends Neo4jIndexProvider { */ def batchInserter: BatchInserter + + private val batchIndexManager = new BatchIndexManager(batchInserter) + + /** + * delegates to shutdown method + */ + def shutdownIndex = batchIndexManager.shutdown + /** * store for IndexManager */ - override def getIndexManager: IndexManager = new BatchIndexManager(batchInserter) + override def getIndexManager: IndexManager = batchIndexManager /** * converts implicitly to the underlying batch instance From d22925e759a4eea466ebfe5284674d282c377f0a Mon Sep 17 00:00:00 2001 From: FaKod Date: Fri, 21 Oct 2011 06:40:14 +0200 Subject: [PATCH 70/82] unused import --- src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala b/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala index c589b5e..e41308b 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala @@ -3,7 +3,6 @@ package org.neo4j.scala import org.neo4j.kernel.impl.batchinsert.BatchInserter import java.util.{Map => juMap} import org.neo4j.graphdb.index._ -import org.neo4j.graphdb.Traverser.Order import org.neo4j.graphdb._ import org.neo4j.index.impl.lucene.{AbstractIndexHits, LuceneBatchInserterIndexProvider} import collection.JavaConversions._ From 679771443d0db5e589ca5236f9649276a67b2047 Mon Sep 17 00:00:00 2001 From: FaKod Date: Fri, 21 Oct 2011 06:40:30 +0200 Subject: [PATCH 71/82] added Batch processing --- README.md | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6bca17d..59277ac 100644 --- a/README.md +++ b/README.md @@ -123,9 +123,45 @@ Traversing Besides, the neo4j scala binding makes it possible to write stop and returnable evaluators in a functional style : //StopEvaluator.END_OF_GRAPH, written in a Scala idiomatic way : - start.traverse(Traverser.Order.BREADTH_FIRST, (tp : TraversalPosition) => false, ReturnableEvaluator.ALL_BUT_START_NODE, "foo", Direction.OUTGOING) + start.traverse(Traverser.Order.BREADTH_FIRST, (tp : TraversalPosition) => false, + ReturnableEvaluator.ALL_BUT_START_NODE, "foo", Direction.OUTGOING) //ReturnableEvaluator.ALL_BUT_START_NODE, written in a Scala idiomatic way : - start.traverse(Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, (tp : TraversalPosition) => tp.notStartNode(), "foo", Direction.OUTGOING) + start.traverse(Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, (tp : TraversalPosition) => tp.notStartNode(), + "foo", Direction.OUTGOING) +Batch Processing +----------------- +Neo4j has a batch insertion mode intended for initial imports, which must run in a single thread and bypasses transactions and other checks in favor of performance. See [Batch insertion](http://docs.neo4j.org/chunked/milestone/indexing-batchinsert.html). +The Java interfaces are slightly different. I wrote some wrapper classes to support nearly transparent usage of batch node and batch relation insertion. Means same code for batch insertion and for normal non batch mode. Instead of using + + class Builder extends Neo4jWrapper with SingletonEmbeddedGraphDatabaseServiceProvider with Neo4jIndexProvider {...} + +simply exchange the provider traits with + + class Builder extends Neo4jWrapper with Neo4jBatchIndexProvider with BatchGraphDatabaseServiceProvider {...} + +getting the indexes is still the same code + + val nodeIndex = getNodeIndex("NodeIndex").get + val relationIndex = getRelationIndex("RelationIndex").get + +setting cache size: + + nodeIndex.setCacheCapacity("NodeIndex", 1000000) + relationIndex.setCacheCapacity("RelationIndex", 1000000) + +Nevertheless, indexes are not available till flushing. To flush call: + + nodeIndex.flush + relationIndex.flush + +After insertion, the batch index manager and batch insertion manager have to be shut down + + class Builder extends Neo4jWrapper . . .{ + . . . + shutdownIndex + shutdown(ds) + . . . + } \ No newline at end of file From 2bf9f7af441c87dadfb0b315de74742487c20c69 Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 25 Oct 2011 16:39:50 +0200 Subject: [PATCH 72/82] fix singleton Providers --- .../neo4j/scala/DatabaseServiceProvider.scala | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala b/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala index 735ab3f..e39b6ea 100644 --- a/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala +++ b/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala @@ -29,16 +29,26 @@ trait EmbeddedGraphDatabaseServiceProvider extends GraphDatabaseServiceProvider val ds: DatabaseService = DatabaseServiceImpl(new EmbeddedGraphDatabase(neo4jStoreDir)) } +/** + * singleton EmbeddedGraphDatabase + */ +private[scala] object SingeltonProvider { + private var ds: Option[DatabaseService] = None + + def apply(neo4jStoreDir: String) = ds match { + case Some(x) => x + case None => + ds = Some(DatabaseServiceImpl(new EmbeddedGraphDatabase(neo4jStoreDir))) + ds.get + } +} + /** * provides a specific Database Service * in this case an singleton embedded database service */ trait SingletonEmbeddedGraphDatabaseServiceProvider extends GraphDatabaseServiceProvider { - object Provider { - val ds: DatabaseService = DatabaseServiceImpl(new EmbeddedGraphDatabase(neo4jStoreDir)) - } - /** * directory where to store the data files */ @@ -47,7 +57,23 @@ trait SingletonEmbeddedGraphDatabaseServiceProvider extends GraphDatabaseService /** * using an instance of an embedded graph database */ - val ds: DatabaseService = Provider.ds + val ds: DatabaseService = SingeltonProvider(neo4jStoreDir) +} + +/** + * singleton provider + */ +private[scala] object SingeltonBatchProvider { + private var inserter: Option[BatchInserter] = None + + def apply(neo4jStoreDir: String) = inserter match { + case Some(x) => x + case None => + inserter = Some(new BatchInserterImpl(neo4jStoreDir)) + inserter.get + } + + lazy val ds: DatabaseService = DatabaseServiceImpl(inserter.get.getGraphDbService) } /** @@ -56,19 +82,11 @@ trait SingletonEmbeddedGraphDatabaseServiceProvider extends GraphDatabaseService */ trait BatchGraphDatabaseServiceProvider extends GraphDatabaseServiceProvider { - /** - * singleton provider - */ - object Provider { - val inserter: BatchInserter = new BatchInserterImpl(neo4jStoreDir) - - val ds: DatabaseService = DatabaseServiceImpl(inserter.getGraphDbService) - } /** * instance of BatchInserter */ - def batchInserter = Provider.inserter + def batchInserter = SingeltonBatchProvider(neo4jStoreDir) /** * directory where to store the data files @@ -78,5 +96,5 @@ trait BatchGraphDatabaseServiceProvider extends GraphDatabaseServiceProvider { /** * using an instance of an embedded graph database */ - val ds: DatabaseService = Provider.ds + val ds: DatabaseService = SingeltonBatchProvider.ds } \ No newline at end of file From f6399a21b0dee4271c59aabfb70bfce2f35565ad Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 25 Oct 2011 16:45:07 +0200 Subject: [PATCH 73/82] added Github as Maven Repo (temporarily) --- pom.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pom.xml b/pom.xml index 21fb2d8..4cf5e8c 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,18 @@
+ + + repo + file:///${basedir}/../fakod-mvn-repo/releases + + + snapshot-repo + file:///${basedir}/../fakod-mvn-repo/snapshots + + + + scala-tools.org @@ -40,6 +52,11 @@ neo4j-public-repository http://m2.neo4j.org + + fakod-snapshots + https://raw.github.com/FaKod/fakod-mvn-repo/master/snapshots + + From 0762acfa928f712d5fce5ab760ffd8ead95f51a4 Mon Sep 17 00:00:00 2001 From: FaKod Date: Fri, 28 Oct 2011 17:05:05 +0200 Subject: [PATCH 74/82] added maven repo info --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 59277ac..ab28b2d 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,23 @@ Building $ cd neo4j-scala $ mvn clean install +Or try to maven fetch it with a Github Maven Repo: + + + + fakod-snapshots + https://raw.github.com/FaKod/fakod-mvn-repo/master/snapshots + + + + + + org.neo4j + neo4j-scala + 0.1.0-SNAPSHOT + + + Troubleshooting --------------- From 12692dc5eb7da2a7c6964054eacdc5bb33aa0197 Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 1 Nov 2011 07:16:30 +0100 Subject: [PATCH 75/82] allow null Case Class properties (-> no Neo4j property is set in that case) --- .../scala/org/neo4j/scala/Neo4jWrapper.scala | 2 ++ .../scala/org/neo4j/scala/util/Poso.scala | 26 ++++++++++--------- .../scala/unittest/DeSerializingTest.scala | 14 +++++----- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index 4ae21b9..6866265 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -127,9 +127,11 @@ object Neo4jWrapper extends Neo4jWrapperImplicits { /** * serializes a given case class into a Node instance + * for null values not property will be set */ def serialize[T <: PropertyContainer](cc: AnyRef, pc: PropertyContainer): T = { CaseClassDeserializer.serialize(cc).foreach { + case (name, null) => case (name, value) => pc.setProperty(name, value) } pc(ClassPropertyName) = cc.getClass.getName diff --git a/src/main/scala/org/neo4j/scala/util/Poso.scala b/src/main/scala/org/neo4j/scala/util/Poso.scala index 544c0c3..35e527f 100644 --- a/src/main/scala/org/neo4j/scala/util/Poso.scala +++ b/src/main/scala/org/neo4j/scala/util/Poso.scala @@ -44,18 +44,20 @@ object CaseClassDeserializer { val values = new ArrayBuffer[AnyRef] for ((paramName, paramType) <- params) { - val field = m.getOrElse(paramName, throw new RuntimeException("Field: " + paramName + " of type: " + paramType.c + " not found")) - - /** - * if the value is directly assignable: use it - * otherwise try to create an instance using der String Constructor - */ - if (field.getClass.isAssignableFrom(paramType.c)) - values += field - else { - val paramCtor = paramType.c.getConstructor(classOf[String]) - val value = paramCtor.newInstance(field).asInstanceOf[AnyRef] - values += value + val field = m.getOrElse(paramName, null) + + field match { + // use null if the property does not exist + case null => + values += null + // if the value is directly assignable: use it + case x: AnyRef if (x.getClass.isAssignableFrom(paramType.c)) => + values += x + // otherwise try to create an instance using der String Constructor + case x: AnyRef => + val paramCtor = paramType.c.getConstructor(classOf[String]) + val value = paramCtor.newInstance(x).asInstanceOf[AnyRef] + values += value } } constructor.newInstance(values.toArray: _*).asInstanceOf[AnyRef] diff --git a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala index c2771ba..64b2ba4 100644 --- a/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala @@ -14,7 +14,7 @@ import sys.ShutdownHookThread case class Test(s: String, i: Int, ji: java.lang.Integer, d: Double, l: Long, b: Boolean) -case class Test2(jl: java.lang.Long, jd: java.lang.Double, jb: java.lang.Boolean) +case class Test2(jl: java.lang.Long, jd: java.lang.Double, jb: java.lang.Boolean, nullString: String = null) case class NotTest(s: String, i: Int, ji: java.lang.Integer, d: Double, l: Long, b: Boolean) @@ -97,13 +97,13 @@ class DeSerializingSpec extends SpecificationWithJUnit with Neo4jWrapper with Em val o = Test2(1, 3.3, true) withTx { implicit neo => - val start = createNode - val end = createNode - end <-- "foo" <-- start < o + val start = createNode + val end = createNode + end <-- "foo" <-- start < o - val rel = start.getSingleRelationship("foo", Direction.OUTGOING) - val oo = rel.toCC[Test2] - oo must beEqualTo(Some(o)) + val rel = start.getSingleRelationship("foo", Direction.OUTGOING) + val oo = rel.toCC[Test2] + oo must beEqualTo(Some(o)) } } } From d6fea4234d60c264d80889a2a1d4ba7ae1542a6a Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 1 Nov 2011 20:27:22 +0100 Subject: [PATCH 76/82] added The Matrix Example --- examples/eu/fakod/examples/TheMatrix.scala | 89 ++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 examples/eu/fakod/examples/TheMatrix.scala diff --git a/examples/eu/fakod/examples/TheMatrix.scala b/examples/eu/fakod/examples/TheMatrix.scala new file mode 100644 index 0000000..a45899e --- /dev/null +++ b/examples/eu/fakod/examples/TheMatrix.scala @@ -0,0 +1,89 @@ +package eu.fakod.examples + +import org.neo4j.scala.{EmbeddedGraphDatabaseServiceProvider, Neo4jWrapper} +import sys.ShutdownHookThread +import org.neo4j.graphdb.Traverser.Order +import collection.JavaConversions._ +import org.neo4j.graphdb._ + +/** + * The Matrix Example + * http://wiki.neo4j.org/content/The_Matrix + */ +case class Matrix(name: String, profession: String) + +object TheMatrix extends App with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider { + + ShutdownHookThread { + shutdown(ds) + } + + def neo4jStoreDir = "/tmp/temp-neo-TheMatrix" + + final val nodes = Map("Neo" -> "Hacker", + "Morpheus" -> "Hacker", + "Trinity" -> "Hacker", + "Cypher" -> "Hacker", + "Agent Smith" -> "Program", + "The Architect" -> "Whatever") + + withTx { + implicit neo => + val nodeMap = for ((name, prof) <- nodes) yield (name, createNode(Matrix(name, prof))) + + getReferenceNode --> "ROOT" --> nodeMap("Neo") + + nodeMap("Neo") --> "KNOWS" --> nodeMap("Trinity") + nodeMap("Neo") --> "KNOWS" --> nodeMap("Morpheus") --> "KNOWS" --> nodeMap("Trinity") + nodeMap("Morpheus") --> "KNOWS" --> nodeMap("Cypher") --> "KNOWS" --> nodeMap("Agent Smith") + nodeMap("Agent Smith") --> "CODED_BY" --> nodeMap("The Architect") + + /** + * Find the friends + */ + + println("\n***** Find the friends") + + nodeMap("Neo").traverse(Order.BREADTH_FIRST, + StopEvaluator.END_OF_GRAPH, + ReturnableEvaluator.ALL_BUT_START_NODE, + DynamicRelationshipType.withName("KNOWS"), + Direction.OUTGOING).foreach { + n => + n.toCC[Matrix] match { + case None => println("not a Matrix Case Class") + case Some(Matrix(name, prof)) => println("Name: " + name + " Profession: " + prof) + } + } + + /** + * Find the hackers + */ + + println("\n***** Find the hackers") + + def isReturnableNode(currentPosition: TraversalPosition) = + currentPosition.lastRelationshipTraversed match { + case null => false + case rel => rel.isType(DynamicRelationshipType.withName("CODED_BY")) + } + + val traverser = nodeMap("Neo").traverse(Order.BREADTH_FIRST, + StopEvaluator.END_OF_GRAPH, + isReturnableNode _, + DynamicRelationshipType.withName("CODED_BY"), + Direction.OUTGOING, + DynamicRelationshipType.withName("KNOWS"), + Direction.OUTGOING) + + traverser.foreach { + n => + n.toCC[Matrix] match { + case None => println("not a Matrix Case Class") + case Some(Matrix(name, prof)) => + println("At depth " +traverser.currentPosition.depth + " Name: " + name + " Profession: " + prof) + } + } + } + +} From 5dad0f797c2dd929efb6fc0e9532efe7bc5ee804 Mon Sep 17 00:00:00 2001 From: FaKod Date: Fri, 11 Nov 2011 23:05:35 +0100 Subject: [PATCH 77/82] emulating the normal index add semantic with batch inserter --- .../neo4j/scala/Neo4jBatchIndexProvider.scala | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala b/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala index e41308b..b57bd1c 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala @@ -7,6 +7,7 @@ import org.neo4j.graphdb._ import org.neo4j.index.impl.lucene.{AbstractIndexHits, LuceneBatchInserterIndexProvider} import collection.JavaConversions._ import sun.reflect.generics.reflectiveObjects.NotImplementedException +import collection.mutable.{SynchronizedMap, ConcurrentMap, HashMap} /** * provides Index access trait @@ -91,11 +92,23 @@ class BatchIndexManager(bi: BatchInserter) extends IndexManager { def getRelationshipAutoIndexer = throw new NotImplementedException } +private[scala] trait IndexCacheHelper { + + private val cache = new HashMap[Long, HashMap[String, AnyRef]] with SynchronizedMap[Long, HashMap[String, AnyRef]] + + /** + * caches multible values + */ + protected def addToCache(id: Long, key: String, value: AnyRef) = + cache.getOrElseUpdate(id, HashMap[String, AnyRef]()) += ((key, value)) + + protected def cacheClear = cache.clear +} /** * delegates Index[Node] methods to BatchInserterIndex methods */ -class BatchIndex(bii: BatchInserterIndex, bi: BatchInserter) extends Index[Node] { +class BatchIndex(bii: BatchInserterIndex, bi: BatchInserter) extends Index[Node] with IndexCacheHelper { private val gds = bi.getGraphDbService @@ -109,11 +122,19 @@ class BatchIndex(bii: BatchInserterIndex, bi: BatchInserter) extends Index[Node] def updateOrAdd(entityId: Long, properties: Map[String, AnyRef]) = bii.updateOrAdd(entityId, properties) - def flush = bii.flush + def flush = { + cacheClear + bii.flush + } def setCacheCapacity(key: String, size: Int) = bii.setCacheCapacity(key, size) - def add(node: Node, key: String, value: AnyRef) = bii.add(node.getId, Map(key -> value)) + /** + * uses the implementation that removes existing documents + * and replaces them with the cached ones + */ + def add(node: Node, key: String, value: AnyRef) = + bii.updateOrAdd(node.getId, addToCache(node.getId, key, value)) def get(key: String, value: AnyRef) = bii.get(key, value) @@ -147,7 +168,7 @@ class BatchIndex(bii: BatchInserterIndex, bi: BatchInserter) extends Index[Node] /** * delegates RelationshipIndex methods to BatchInserterIndex methods */ -class BatchRelationshipIndex(bii: BatchInserterIndex, bi: BatchInserter) extends RelationshipIndex { +class BatchRelationshipIndex(bii: BatchInserterIndex, bi: BatchInserter) extends RelationshipIndex with IndexCacheHelper { private val gds = bi.getGraphDbService @@ -161,11 +182,15 @@ class BatchRelationshipIndex(bii: BatchInserterIndex, bi: BatchInserter) extends def updateOrAdd(entityId: Long, properties: Map[String, AnyRef]) = bii.updateOrAdd(entityId, properties) - def flush = bii.flush + def flush = { + cacheClear + bii.flush + } def setCacheCapacity(key: String, size: Int) = bii.setCacheCapacity(key, size) - def add(entity: Relationship, key: String, value: AnyRef) = bii.add(entity.getId, Map(key -> value)) + def add(entity: Relationship, key: String, value: AnyRef) = + bii.updateOrAdd(entity.getId, addToCache(entity.getId, key, value)) def get(key: String, value: AnyRef) = bii.get(key, value) From ab2c131353338e11ddaa52d3dbecda97442fe64a Mon Sep 17 00:00:00 2001 From: Christopher Schmidt Date: Sun, 13 Nov 2011 20:18:11 +0100 Subject: [PATCH 78/82] Added Matrix Example GIST --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ab28b2d..168172d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ to be used in other Scala projects. This wrapper is mostly based on the work done by [Martin Kleppmann](http://twitter.com/martinkl) in his [Scala implementation of RESTful JSON HTTP resources on top of the Neo4j graph database and Jersey](http://github.com/ept/neo4j-resources) project. + +See this [GIST](https://gist.github.com/1331556) for a usual Neo4j Matrix Example + You may find [Neo4j-Spatial-Scala](http://github.com/FaKod/neo4j-spatial-scala) interesting as well. From db113a038939b4530fce900faf441fb92f1f644a Mon Sep 17 00:00:00 2001 From: FaKod Date: Mon, 14 Nov 2011 07:03:39 +0100 Subject: [PATCH 79/82] working against 1.5 now --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4cf5e8c..edc9639 100644 --- a/pom.xml +++ b/pom.xml @@ -14,8 +14,8 @@ UTF-8 2.9.1 - 1.5-SNAPSHOT - 1.5-SNAPSHOT + 1.5 + 1.5 From 443d9a1bde7282ef0a8e552c669b66396a0d4daa Mon Sep 17 00:00:00 2001 From: FaKod Date: Tue, 15 Nov 2011 07:03:57 +0100 Subject: [PATCH 80/82] released 0.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index edc9639..c9c39b6 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ neo4j-scala jar Neo4j Scala - 0.1.0-SNAPSHOT + 0.1.0 Scala wrapper for Neo4j Graph Database http://github.com/fakod/neo4j-scala 2011 From cd3b739620126b5714cf6bf94402990e25a5ea0c Mon Sep 17 00:00:00 2001 From: Christopher Schmidt Date: Wed, 16 Nov 2011 06:15:55 +0100 Subject: [PATCH 81/82] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 168172d..ded7c34 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Or try to maven fetch it with a Github Maven Repo: org.neo4j neo4j-scala - 0.1.0-SNAPSHOT + 0.1.0 From 57f4b32b93205034e4fd74cce6e5cc94b83cb945 Mon Sep 17 00:00:00 2001 From: Christopher Schmidt Date: Wed, 16 Nov 2011 06:18:56 +0100 Subject: [PATCH 82/82] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ded7c34..add4b1f 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ Or try to maven fetch it with a Github Maven Repo: - fakod-snapshots - https://raw.github.com/FaKod/fakod-mvn-repo/master/snapshots + fakod-releases + https://raw.github.com/FaKod/fakod-mvn-repo/master/releases