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 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. - diff --git a/README.md b/README.md index ffd7b9d..add4b1f 100644 --- a/README.md +++ b/README.md @@ -5,71 +5,183 @@ 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. -Building --------- -You need a Java 5 (or newer) environment and Maven 2.0.9 (or newer) installed: +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. - $ 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" -You should now be able to do a full build of `neo4j-resources`: +Building +-------- - $ git clone git://github.com/jawher/neo4j-scala.git + $ git clone git://github.com/FaKod/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`: +Or try to maven fetch it with a Github Maven Repo: - - org.neo4j - neo4j-scala - 0.9.9-SNAPSHOT - - -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. + + + fakod-releases + https://raw.github.com/FaKod/fakod-mvn-repo/master/releases + + + + + org.neo4j + neo4j-scala + 0.1.0 + + Troubleshooting --------------- -Please consider using [Github issues tracker](http://github.com/jawher/neo4j-scala/issues) to submit bug reports or feature requests. +Please consider using [Github issues tracker](http://github.com/fakod/neo4j-scala/issues) to submit bug reports or feature requests. 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 Neo4j storage directory. +A class using the wrapper is f.e.: + + class MyNeo4jClass extends SomethingClass with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider { + def neo4jStoreDir = "/tmp/temp-neo-test" + . . . + } + +Transaction Wrapping +-------------------- +Transactions are wrapped by withTx. After leaving the "scope" success is called (or rollback if an exception is raised): + + withTx { + implicit neo => + val start = createNode + val end = createNode + start --> "foo" --> end + } + +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) + + 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 + } + +Use one of the configured indexes with + + val nodeIndex = getNodeIndex("MyTest1stIndex").get + +Add and remove entries 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 { + // cast Object to String and match . . . + start[String]("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 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 new 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 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 +---------- + 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) + 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: -License -------- + nodeIndex.flush + relationIndex.flush -See `LICENSE` for details. +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 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) + } + } + } + +} diff --git a/pom.xml b/pom.xml index 4b0ae5d..c9c39b6 100644 --- a/pom.xml +++ b/pom.xml @@ -1,161 +1,200 @@ - - 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 - - - - - jawher - Jawher Moussa - http://twitter.com/jawher - +1 - - - martin - Martin Kleppmann - http://twitter.com/martinkl - - - - - - 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 - 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 - - - - - - org.scala-lang - scala-library - ${scala.version} - - - junit - junit - 4.7 - test - - - org.scala-tools.testing - specs - 1.6.1 - test - - - - - org.neo4j - neo4j-kernel - ${neo4j.version} - - - org.neo4j - neo4j-shell - ${neo4j.version} - - - - - 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} - - - - + + 4.0.0 + + org.neo4j + neo4j-scala + jar + Neo4j Scala + 0.1.0 + Scala wrapper for Neo4j Graph Database + http://github.com/fakod/neo4j-scala + 2011 + + + UTF-8 + 2.9.1 + 1.5 + 1.5 + + + + + FaKod + Christopher Schmidt + +1 + info [at] FaKod.EU + + developer + + + + + + + repo + file:///${basedir}/../fakod-mvn-repo/releases + + + snapshot-repo + file:///${basedir}/../fakod-mvn-repo/snapshots + + + + + + + scala-tools.org + Scala-Tools Maven2 Repository + http://scala-tools.org/repo-releases + + + neo4j-public-repository + http://m2.neo4j.org + + + fakod-snapshots + https://raw.github.com/FaKod/fakod-mvn-repo/master/snapshots + + + + + + + org.scala-lang + scala-library + ${scala.version} + + + junit + junit + 4.7 + test + + + + org.specs2 + specs2_2.9.1 + 1.6.1 + test + + + + org.specs2 + specs2-scalaz-core_2.9.1 + 6.0.1 + test + + + + + org.neo4j + neo4j-kernel + ${neo4j.version} + + + org.neo4j + neo4j-lucene-index + ${neo4j.version} + + + org.neo4j + neo4j-shell + ${neo4j.shell.version} + + + + + + + org.scala-tools + maven-scala-plugin + 2.15.2 + + + + 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 + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.9 + + true + + **/unittest/*.java + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.3.1 + + + false + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + attach-sources + + jar + + + + + + + + + + org.scala-tools + maven-scala-plugin + 2.14 + + ${scala.version} + + + + 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..18a1446 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/DatabaseService.scala @@ -0,0 +1,19 @@ +package org.neo4j.scala + +import org.neo4j.graphdb.GraphDatabaseService + +/** + * Interface for GraphDatabaseService + * @author Christopher Schmidt + * + */ +trait DatabaseService { + def gds: GraphDatabaseService +} + +/** + * standard DatabaseService implementation + * for GraphDatabaseService + */ +case class DatabaseServiceImpl(gds: GraphDatabaseService) extends DatabaseService + diff --git a/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala b/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala new file mode 100644 index 0000000..e39b6ea --- /dev/null +++ b/src/main/scala/org/neo4j/scala/DatabaseServiceProvider.scala @@ -0,0 +1,100 @@ +package org.neo4j.scala + +import org.neo4j.kernel.EmbeddedGraphDatabase +import org.neo4j.kernel.impl.batchinsert.{BatchInserter, BatchInserterImpl} + +/** + * Interface for a GraphDatabaseServiceProvider + * must be implemented by and Graph Database Service Provider + */ +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)) +} + +/** + * 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 { + + /** + * directory where to store the data files + */ + def neo4jStoreDir: String + + /** + * using an instance of an embedded graph database + */ + 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) +} + +/** + * provides a specific GraphDatabaseServiceProvider for + * Batch processing + */ +trait BatchGraphDatabaseServiceProvider extends GraphDatabaseServiceProvider { + + + /** + * instance of BatchInserter + */ + def batchInserter = SingeltonBatchProvider(neo4jStoreDir) + + /** + * directory where to store the data files + */ + def neo4jStoreDir: String + + /** + * using an instance of an embedded graph database + */ + val ds: DatabaseService = SingeltonBatchProvider.ds +} \ No newline at end of file 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..b57bd1c --- /dev/null +++ b/src/main/scala/org/neo4j/scala/Neo4jBatchIndexProvider.scala @@ -0,0 +1,248 @@ +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._ +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 + * 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 + + + private val batchIndexManager = new BatchIndexManager(batchInserter) + + /** + * delegates to shutdown method + */ + def shutdownIndex = batchIndexManager.shutdown + + /** + * store for IndexManager + */ + override def getIndexManager: IndexManager = batchIndexManager + + /** + * 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] +} + + +/** + * 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), bi) + + def forRelationships(indexName: String, customConfiguration: juMap[String, String]) = + new BatchRelationshipIndex(batchInserterIndexProvider.relationshipIndex(indexName, customConfiguration), bi) + + /** + * 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 +} + +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] with IndexCacheHelper { + + 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 gds.getNodeById(l) + new ConstantScoreIterator[Node](listOfNodes.toList) + } + + def updateOrAdd(entityId: Long, properties: Map[String, AnyRef]) = bii.updateOrAdd(entityId, properties) + + def flush = { + cacheClear + bii.flush + } + + def setCacheCapacity(key: String, size: Int) = bii.setCacheCapacity(key, size) + + /** + * 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) + + 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, bi: BatchInserter) extends RelationshipIndex with IndexCacheHelper { + + 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 gds.getRelationshipById(l) + new ConstantScoreIterator[Relationship](listOfNodes.toList) + } + + def updateOrAdd(entityId: Long, properties: Map[String, AnyRef]) = bii.updateOrAdd(entityId, properties) + + def flush = { + cacheClear + bii.flush + } + + def setCacheCapacity(key: String, size: Int) = bii.setCacheCapacity(key, size) + + 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) + + 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 +} + +/** + * replica of the original ConstantScoreIterator which has package visibility + * class org.neo4j.index.impl.lucene.ConstantScoreIterator extends AbstractIndexHits + */ +class ConstantScoreIterator[T](items: List[T], score: Float = Float.NaN) extends AbstractIndexHits[T] { + + private final val _size: Int = items.size + private final val iter = items.iterator + + def currentScore: Float = score + + def size: Int = _size + + protected def fetchNextOrNull: T = + if (iter.hasNext) + iter.next + else + null.asInstanceOf[T] +} \ 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 new file mode 100644 index 0000000..47fe156 --- /dev/null +++ b/src/main/scala/org/neo4j/scala/Neo4jIndexProvider.scala @@ -0,0 +1,120 @@ +package org.neo4j.scala + +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 trait + * @author Christopher Schmidt + */ + +trait Neo4jIndexProvider { + + /** + * 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 var nodeIndexStore: mutableMap[String, Index[Node]] = null + + /** + * private cache + */ + private var relationIndexStore: mutableMap[String, RelationshipIndex] = null + + /** + * lazy initializes Indexes for Nodes + */ + 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 + } + + /** + * 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 + * @return IndexManager the index manager + */ + def getIndexManager = ds.gds.index + + /** + * @return Option[Index[Node]] the created index if available + */ + def getNodeIndex(name: String) = getNodeIndexStore.get(name) + + /** + * @return Option[RelationshipIndex] the created index if available + */ + def getRelationIndex(name: String) = getRelationIndexStore.get(name) + + /** + * conversion to ease the use of optional configuration + */ + 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) + + 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) + } + + /** + * 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/main/scala/org/neo4j/scala/Neo4jWrapper.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala index fc60ce5..6866265 100644 --- a/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapper.scala @@ -1,6 +1,10 @@ package org.neo4j.scala +import util.CaseClassDeserializer +import collection.JavaConversions._ +import CaseClassDeserializer._ import org.neo4j.graphdb._ +import index.IndexManager /** * Extend your class with this trait to get really neat new notation for creating @@ -13,21 +17,19 @@ import org.neo4j.graphdb._ * * 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 { +trait Neo4jWrapper extends GraphDatabaseServiceProvider with Neo4jWrapperImplicits { - /** + /** * 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](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 { @@ -35,49 +37,206 @@ trait Neo4jWrapper { } } - class NodeRelationshipMethods(node: Node) { + /** + * creates a new Node from Database service + */ + def createNode(implicit ds: DatabaseService): Node = ds.gds.createNode - def -->(relType: RelationshipType) = new OutgoingRelationshipBuilder(node, relType) + /** + * convenience method to create and serialize a case class + */ + def createNode(cc: AnyRef)(implicit ds: DatabaseService): Node = + Neo4jWrapper.serialize(cc, createNode) - // Create incoming relationship + /** + * 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) - def <--(relType: RelationshipType) = new IncomingRelationshipBuilder(node, relType) - } + /** + * 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) - // Half-way through building an outgoing relationship - class OutgoingRelationshipBuilder(fromNode: Node, relType: RelationshipType) { - def -->(toNode: Node) = { - fromNode.createRelationshipTo(toNode, relType) - new NodeRelationshipMethods(toNode) + /** + * 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 +} + +/** + * 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 + * 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 + pc.asInstanceOf[T] } - // Half-way through building an incoming relationship - class IncomingRelationshipBuilder(toNode: Node, relType: RelationshipType) { - def <--(fromNode: Node) = { - fromNode.createRelationshipTo(toNode, relType) - new NodeRelationshipMethods(fromNode) + /** + * conditional case class deserialization + * Some(T) if possible + * None if not + */ + 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(manifest[T].erasure.getName)) => + true + case _ => + false + } + + /** + * 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: Manifest](pc: PropertyContainer): T = { + toCC[T](pc) match { + case Some(t) => t + case _ => throw new IllegalArgumentException("given Case Class: " + + manifest[T].erasure.getName + " does not fit to serialized properties") } } +} + +/** + * creates incoming and outgoing relationships + */ +private[scala] class NodeRelationshipMethods(node: Node, rel: Relationship = null) { + def -->(relType: RelationshipType) = new OutgoingRelationshipBuilder(node, relType) - implicit def node2relationshipBuilder(node: Node) = new NodeRelationshipMethods(node) + def <--(relType: RelationshipType) = new IncomingRelationshipBuilder(node, relType) - implicit def string2RelationshipType(relType: String) = DynamicRelationshipType.withName(relType) + /** + * use this to get the created relationship object + *
start --> "KNOWS" --> end <()
+ */ + def <() = rel + + /** + *
start --> "KNOWS" --> end <(MyCaseClass(...))
+ */ + def <(cc: AnyRef): Relationship = Neo4jWrapper.serialize(cc, rel) +} + +/** + * Half-way through building an outgoing relationship + */ +private[scala] class OutgoingRelationshipBuilder(fromNode: Node, relType: RelationshipType) { + def -->(toNode: Node) = { + val rel = fromNode.createRelationshipTo(toNode, relType) + new NodeRelationshipMethods(toNode, rel) + } +} - 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) +/** + * Half-way through building an incoming relationship + */ +private[scala] class IncomingRelationshipBuilder(toNode: Node, relType: RelationshipType) { + def <--(fromNode: Node) = { + val rel = fromNode.createRelationshipTo(toNode, relType) + new NodeRelationshipMethods(fromNode, rel) } +} - implicit def propertyContainer2RichPropertyContainer(propertyContainer: PropertyContainer) = new RichPropertyContainer(propertyContainer) +/** + * convenience for handling properties + */ +private[scala] class RichPropertyContainer(propertyContainer: PropertyContainer) { - implicit def fn2StopEvaluator(e : TraversalPosition => Boolean) = - new StopEvaluator() { - def isStopNode(traversalPosition : TraversalPosition) = e(traversalPosition) + /** + * 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).asInstanceOf[T]) + case _ => None } - implicit def fn2ReturnableEvaluator(e : TraversalPosition => Boolean) = - new ReturnableEvaluator () { - def isReturnableNode(traversalPosition : TraversalPosition) = e(traversalPosition) - } -} + /** + * 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/Neo4jWrapperImplicits.scala b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala new file mode 100644 index 0000000..a67c86a --- /dev/null +++ b/src/main/scala/org/neo4j/scala/Neo4jWrapperImplicits.scala @@ -0,0 +1,57 @@ +package org.neo4j.scala + +import org.neo4j.graphdb._ + +/** + * trait for implicits + * used by Neo4j wrapper + * + * @author Christopher Schmidt + */ +trait Neo4jWrapperImplicits { + + /** + * 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) + + /** + * conversion 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) + } + + /** + * Stuff for Indexes + */ + implicit def indexManager(implicit ds: DatabaseService) = ds.gds.index + + /** + * for serialization + */ + 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) + } +} 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..35e527f --- /dev/null +++ b/src/main/scala/org/neo4j/scala/util/Poso.scala @@ -0,0 +1,192 @@ +package org.neo4j.scala.util + +import scalax.rules.scalasig._ +import collection.mutable.{SynchronizedMap, ArrayBuffer, HashMap} + +/** + * helper class to store Class object + */ +case class JavaType(c: Class[_]) + +/** + * Case Class deserializing object + */ +object CaseClassDeserializer { + + /** + * Method Map cache for method serialize + */ + private val methodCache = new HashMap[Class[_], Map[String, java.lang.reflect.Method]]() + with SynchronizedMap[Class[_], 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: Manifest](m: Map[String, AnyRef]): T = + deserialize(m, JavaType(manifest[T].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 = sigParserCache.getOrElseUpdate(javaTypeTarget.c, CaseClassSigParser.parse(javaTypeTarget.c)) + + val values = new ArrayBuffer[AnyRef] + for ((paramName, paramType) <- params) { + 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] + } + + /** + * creates a map from case class parameter + * @param o AnyRef case class instance + */ + def serialize(o: AnyRef): Map[String, AnyRef] = { + val methods = methodCache.getOrElseUpdate(o.getClass, + 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 + } +} + +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/unittest/DeSerializingTest.scala b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala new file mode 100644 index 0000000..64b2ba4 --- /dev/null +++ b/src/test/scala/org/neo4j/scala/unittest/DeSerializingTest.scala @@ -0,0 +1,110 @@ +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} +import sys.ShutdownHookThread + +/** + * Test spec to check deserialization and serialization of case classes + * + * @author Christopher Schmidt + */ + +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, nullString: String = null) + +case class NotTest(s: String, i: Int, ji: java.lang.Integer, d: Double, l: Long, b: Boolean) + +import CaseClassDeserializer._ + +class DeSerializingWithoutNeo4jSpec extends SpecificationWithJUnit { + + "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") + val 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 { + 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) + resMap.get("b").get mustEqual (true) + } + } +} + +class DeSerializingSpec extends SpecificationWithJUnit with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider { + + def neo4jStoreDir = "/tmp/temp-neo-test2" + + "Node" should { + + ShutdownHookThread { + shutdown(ds) + } + + "be serializable with Test" in { + val o = Test("sowas", 1, 2, 3.3, 10, true) + val node = withTx { + createNode(o)(_) + } + + val oo1 = Neo4jWrapper.deSerialize[Test](node) + oo1 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) + + 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 diff --git a/src/test/scala/org/neo4j/scala/unittest/IndexTest.scala b/src/test/scala/org/neo4j/scala/unittest/IndexTest.scala new file mode 100644 index 0000000..976d3c4 --- /dev/null +++ b/src/test/scala/org/neo4j/scala/unittest/IndexTest.scala @@ -0,0 +1,65 @@ +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 + * + * @author Christopher Schmidt + */ + +class IndexTestSpec extends SpecificationWithJUnit with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider with Neo4jIndexProvider { + + def neo4jStoreDir = "/tmp/temp-neo-index-test" + + override def NodeIndexConfig = ("MyTestIndex", Map("provider" -> "lucene", "type" -> "fulltext")) :: Nil + + + "Neo4jIndexProvider" should { + + ShutdownHookThread { + shutdown(ds) + } + + "use the fulltext search index" in { + + val nodeIndex = getNodeIndex("MyTestIndex").get + + withTx { + implicit db => + + val theMatrix = createNode + val theMatrixReloaded = createNode + theMatrixReloaded.setProperty("name", "theMatrixReloaded") + + 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 diff --git a/src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala b/src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala similarity index 59% rename from src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala rename to src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala index 463c8b9..22ea6e4 100644 --- a/src/test/scala/org/neo4j/scala/Neo4jWrapperTest.scala +++ b/src/test/scala/org/neo4j/scala/unittest/Neo4jWrapperTest.scala @@ -1,40 +1,42 @@ -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} +import sys.ShutdownHookThread + +/** + * Test spec to check relationship builder and evaluators + */ +class Neo4jWrapperSpec extends SpecificationWithJUnit with Neo4jWrapper with EmbeddedGraphDatabaseServiceProvider { -class Neo4jWrapperSpecTest extends JUnit4(Neo4jWrapperSpec) + def neo4jStoreDir = "/tmp/temp-neo-test" -object Neo4jWrapperSpec extends Specification with Neo4jWrapper { "NeoWrapper" should { - shareVariables() - implicit val neo : GraphDatabaseService = new EmbeddedGraphDatabase("/tmp/temp-neo-test") - - Runtime.getRuntime.addShutdownHook(new Thread() { - override def run() { - neo.shutdown - } - }) + + ShutdownHookThread { + shutdown(ds) + } "create a new relationship in --> relType --> notation" in { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode + withTx { + 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) + 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 { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode + withTx { + implicit neo => + val start = createNode + val end = createNode start --> "foo" --> end start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). getOtherNode(start) must beEqualTo(end) @@ -42,20 +44,23 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "create a new relationship in <-- relType <-- notation" in { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode + withTx { + 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) + 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 { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode + withTx { + implicit neo => + val start = createNode + val end = createNode end <-- "foo" <-- start start.getSingleRelationship(DynamicRelationshipType.withName("foo"), Direction.OUTGOING). getOtherNode(start) must beEqualTo(end) @@ -63,10 +68,11 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "allow relationships of the same direction to be chained" in { - execInNeo4j { neo => - val start = neo.createNode - val middle = neo.createNode - val end = neo.createNode + 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) @@ -76,10 +82,11 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "allow relationships of different directions to be chained" in { - execInNeo4j { neo => - val left = neo.createNode - val middle = neo.createNode - val right = neo.createNode + 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) @@ -89,16 +96,18 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "ignore a relationshipBuilder with no end node" in { - execInNeo4j { neo => - val start = neo.createNode + withTx { + 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 + withTx { + implicit neo => + val start = createNode start.setProperty("foo", "bar") start("foo") must beEqualTo(Some("bar")) start("bar") must beEqualTo(None) @@ -106,17 +115,19 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "create a property in a node in node('property')=value notation" in { - execInNeo4j { neo => - val start = neo.createNode + withTx { + 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 + 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")) @@ -125,9 +136,10 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "create a property in a relationship in rel('property')=value notation" in { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode + 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") @@ -135,22 +147,24 @@ object Neo4jWrapperSpec extends Specification with Neo4jWrapper { } "allow writing stop evaluators in a functional style" in { - execInNeo4j { neo => - val start = neo.createNode - val end = neo.createNode + 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) + 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 + 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) + 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) }