+ */
+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 ~ = for (a <- this; b <- next orError) yield new ~(a, b)
+
+ def ~- = for (a <- this; b <- next orError) yield a
+
+ def -~ = 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)
}