From e8f5050911d18593cfb768601bc4f0c9d9b49927 Mon Sep 17 00:00:00 2001 From: Michael Pollmeier Date: Fri, 26 Apr 2024 09:51:10 +0200 Subject: [PATCH] implement graphmlimporter, add tests in domain language (#180) * WIP GraphML reimpl * WIP * implement graphmlimporter, add tests in domain language --- .../scala/flatgraph/DiffGraphApplier.scala | 6 +- core/src/main/scala/flatgraph/Graph.scala | 3 + core/src/main/scala/flatgraph/Schema.scala | 13 +- .../codegen/DomainClassesGenerator.scala | 2 +- .../src/test/resources/graphml-small.xml | 29 ++++ .../src/test/resources/graphson-small.json | 106 ++++++++++++ .../neo4jcsv/invalid_column_content_data.csv | 3 + .../invalid_column_content_header.csv | 1 + .../resources/neo4jcsv/testedges_data.csv | 2 + .../resources/neo4jcsv/testedges_header.csv | 1 + .../resources/neo4jcsv/testnodes_data.csv | 3 + .../resources/neo4jcsv/testnodes_header.csv | 1 + .../unsupported_multiple_labels_data.csv | 1 + .../unsupported_multiple_labels_header.csv | 1 + .../formats/graphml/GraphMLTests.scala | 51 +++--- .../formats/graphml/GraphMLImporter.scala | 140 +++++++++------- .../testdomains/gratefuldead/Accessors.scala | 20 ++- .../testdomains/gratefuldead/BaseTypes.scala | 7 +- .../testdomains/gratefuldead/EdgeTypes.java | 8 + .../testdomains/gratefuldead/EdgeTypes.scala | 4 + .../gratefuldead/GraphSchema.scala | 40 +++-- .../testdomains/gratefuldead/NodeTypes.java | 8 +- .../gratefuldead/PropertyKeys.scala | 4 +- .../gratefuldead/PropertyNames.java | 8 +- .../testdomains/gratefuldead/RootTypes.scala | 10 ++ .../gratefuldead/RootTypesTraversals.scala | 6 + .../testdomains/gratefuldead/Traversals.scala | 158 +++++++++++++++++- .../neighboraccessors/Artist.scala | 54 ++++++ .../gratefuldead/neighboraccessors/Song.scala | 84 +++++++++- .../neighboraccessors/package.scala | 6 + .../gratefuldead/nodes/Artist.scala | 10 +- .../testdomains/gratefuldead/nodes/Song.scala | 36 ++-- .../testdomains/gratefuldead/Schema.scala | 18 +- 33 files changed, 691 insertions(+), 153 deletions(-) create mode 100644 formats-tests/src/test/resources/graphml-small.xml create mode 100644 formats-tests/src/test/resources/graphson-small.json create mode 100644 formats-tests/src/test/resources/neo4jcsv/invalid_column_content_data.csv create mode 100644 formats-tests/src/test/resources/neo4jcsv/invalid_column_content_header.csv create mode 100644 formats-tests/src/test/resources/neo4jcsv/testedges_data.csv create mode 100644 formats-tests/src/test/resources/neo4jcsv/testedges_header.csv create mode 100644 formats-tests/src/test/resources/neo4jcsv/testnodes_data.csv create mode 100644 formats-tests/src/test/resources/neo4jcsv/testnodes_header.csv create mode 100644 formats-tests/src/test/resources/neo4jcsv/unsupported_multiple_labels_data.csv create mode 100644 formats-tests/src/test/resources/neo4jcsv/unsupported_multiple_labels_header.csv create mode 100644 test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/neighboraccessors/Artist.scala diff --git a/core/src/main/scala/flatgraph/DiffGraphApplier.scala b/core/src/main/scala/flatgraph/DiffGraphApplier.scala index a19119fd..ebd47717 100644 --- a/core/src/main/scala/flatgraph/DiffGraphApplier.scala +++ b/core/src/main/scala/flatgraph/DiffGraphApplier.scala @@ -7,9 +7,9 @@ import flatgraph.Edge.Direction.{Incoming, Outgoing} import scala.collection.{Iterator, mutable} object DiffGraphApplier { - def applyDiff(g: Graph, diff: DiffGraphBuilder): Unit = { - if (g.isClosed) throw new GraphClosedException(s"graph cannot be modified any longer since it's closed") - new DiffGraphApplier(g, diff).applyUpdate() + def applyDiff(graph: Graph, diff: DiffGraphBuilder): Unit = { + if (graph.isClosed) throw new GraphClosedException(s"graph cannot be modified any longer since it's closed") + new DiffGraphApplier(graph, diff).applyUpdate() diff.buffer = null } } diff --git a/core/src/main/scala/flatgraph/Graph.scala b/core/src/main/scala/flatgraph/Graph.scala index d499224d..21cd29b3 100644 --- a/core/src/main/scala/flatgraph/Graph.scala +++ b/core/src/main/scala/flatgraph/Graph.scala @@ -97,6 +97,9 @@ class Graph(val schema: Schema, val storagePathMaybe: Option[Path] = None) exten def allEdges: Iterator[Edge] = allNodes.flatMap(Accessors.getEdgesOut) + def edgeCount(): Int = + allEdges.size + /** Lookup nodes with a given label and property value via index. N.b. currently only supported for String properties. Context: * MultiDictIndex requires the key to be a String and this is using reverse indices, i.e. the lookup is from String -> GNode. */ diff --git a/core/src/main/scala/flatgraph/Schema.scala b/core/src/main/scala/flatgraph/Schema.scala index dcfffa98..222dc549 100644 --- a/core/src/main/scala/flatgraph/Schema.scala +++ b/core/src/main/scala/flatgraph/Schema.scala @@ -1,6 +1,7 @@ package flatgraph import flatgraph.Edge.Direction +import flatgraph.Schema.UndefinedKind object DefaultValue object NoProperty @@ -96,7 +97,8 @@ object FormalQtyType { abstract class Schema { def getNumberOfNodeKinds: Int - def nodeKinds: Range = Range(0, getNumberOfNodeKinds) + def nodeKinds: Range = Range(0, getNumberOfNodeKinds) + def nodeLabels: Seq[String] = nodeKinds.map(getNodeLabel) def getNumberOfEdgeKinds: Int def edgeKinds: Range = Range(0, getNumberOfEdgeKinds) @@ -106,6 +108,9 @@ abstract class Schema { def getNodeLabel(nodeKind: Int): String def getNodeKindByLabel(label: String): Int + def getNodeKindByLabelMaybe(label: String): Option[Int] = { + Option(getNodeKindByLabel(label)).filterNot(_ == UndefinedKind) + } // So, the issue here is: We have a couple of pseudo-properties that can only exist at a single node kind // (theoretically same for edges). We want to allow our data-layout to alias these properties. This means that multiple @@ -143,10 +148,10 @@ abstract class Schema { } class FreeSchema( - val nodeLabels: Array[String], - val propertyLabels: Array[String], + nodeLabels: Array[String], + propertyLabels: Array[String], nodePropertyPrototypes: Array[AnyRef], - val edgeLabels: Array[String], + edgeLabels: Array[String], edgePropertyPrototypes: Array[AnyRef], formalQuantities: Array[FormalQtyType.FormalQuantity] = null ) extends Schema { diff --git a/domain-classes-generator/src/main/scala/flatgraph/codegen/DomainClassesGenerator.scala b/domain-classes-generator/src/main/scala/flatgraph/codegen/DomainClassesGenerator.scala index 9612cc13..fc5f16c7 100644 --- a/domain-classes-generator/src/main/scala/flatgraph/codegen/DomainClassesGenerator.scala +++ b/domain-classes-generator/src/main/scala/flatgraph/codegen/DomainClassesGenerator.scala @@ -607,7 +607,7 @@ class DomainClassesGenerator(schema: Schema) { |import flatgraph.FormalQtyType | |object GraphSchema extends flatgraph.Schema { - | val nodeLabels = Array($nodeLabelsSrc) + | private val nodeLabels = IndexedSeq($nodeLabelsSrc) | val nodeKindByLabel = nodeLabels.zipWithIndex.toMap | val edgeLabels = Array(${edgeTypes.map { e => s""""${e.name}"""" }.mkString(", ")}) | val edgeIdByLabel = edgeLabels.zipWithIndex.toMap diff --git a/formats-tests/src/test/resources/graphml-small.xml b/formats-tests/src/test/resources/graphml-small.xml new file mode 100644 index 00000000..6f82c4fd --- /dev/null +++ b/formats-tests/src/test/resources/graphml-small.xml @@ -0,0 +1,29 @@ + + + + + + + + + song + HEY BO DIDDLEY + cover + + + artist + Garcia + + + artist + Bo_Diddley + + + writtenBy + + + sungBy + + + diff --git a/formats-tests/src/test/resources/graphson-small.json b/formats-tests/src/test/resources/graphson-small.json new file mode 100644 index 00000000..387b2df9 --- /dev/null +++ b/formats-tests/src/test/resources/graphson-small.json @@ -0,0 +1,106 @@ +{ + "@type": "tinker:graph", + "@value": { + "edges": [{ + "@type": "g:Edge", + "id": { + "@type": "g:Int64", + "@value": 0 + }, + "inV": { + "@type": "g:Int64", + "@value": 340 + }, + "inVLabel": "artist", + "label": "sungBy", + "outV": { + "@type": "g:Int64", + "@value": 1 + }, + "outVLabel": "song", + "properties": { + + } + }, { + "@type": "g:Edge", + "id": { + "@type": "g:Int64", + "@value": 1 + }, + "inV": { + "@type": "g:Int64", + "@value": 527 + }, + "inVLabel": "artist", + "label": "writtenBy", + "outV": { + "@type": "g:Int64", + "@value": 1 + }, + "outVLabel": "song", + "properties": { + + } + }], + "vertices": [{ + "@type": "g:Vertex", + "id": { + "@type": "g:Int64", + "@value": 1 + }, + "label": "song", + "properties": { + "name": { + "@type": "g:VertexProperty", + "@value": "HEY BO DIDDLEY", + "id": { + "@type": "g:Int64", + "@value": 0 + } + }, + "songType": { + "@type": "g:VertexProperty", + "@value": "cover", + "id": { + "@type": "g:Int64", + "@value": 1 + } + } + } + }, { + "@type": "g:Vertex", + "id": { + "@type": "g:Int64", + "@value": 340 + }, + "label": "artist", + "properties": { + "name": { + "@type": "g:VertexProperty", + "@value": "Garcia", + "id": { + "@type": "g:Int64", + "@value": 2 + } + } + } + }, { + "@type": "g:Vertex", + "id": { + "@type": "g:Int64", + "@value": 527 + }, + "label": "artist", + "properties": { + "name": { + "@type": "g:VertexProperty", + "@value": "Bo_Diddley", + "id": { + "@type": "g:Int64", + "@value": 3 + } + } + } + }] + } +} \ No newline at end of file diff --git a/formats-tests/src/test/resources/neo4jcsv/invalid_column_content_data.csv b/formats-tests/src/test/resources/neo4jcsv/invalid_column_content_data.csv new file mode 100644 index 00000000..3d33c96a --- /dev/null +++ b/formats-tests/src/test/resources/neo4jcsv/invalid_column_content_data.csv @@ -0,0 +1,3 @@ +1,stringProp1,11,testNode,stringListProp1a;stringListProp1b,21;31;41 +2,stringProp2,,testNode,, +3,,NOT_AN_INT,testNode,, diff --git a/formats-tests/src/test/resources/neo4jcsv/invalid_column_content_header.csv b/formats-tests/src/test/resources/neo4jcsv/invalid_column_content_header.csv new file mode 100644 index 00000000..96f0e308 --- /dev/null +++ b/formats-tests/src/test/resources/neo4jcsv/invalid_column_content_header.csv @@ -0,0 +1 @@ +id:ID,StringProperty,IntProperty:int,:LABEL,StringListProperty:string[],IntListProperty:int[] \ No newline at end of file diff --git a/formats-tests/src/test/resources/neo4jcsv/testedges_data.csv b/formats-tests/src/test/resources/neo4jcsv/testedges_data.csv new file mode 100644 index 00000000..e246c6fa --- /dev/null +++ b/formats-tests/src/test/resources/neo4jcsv/testedges_data.csv @@ -0,0 +1,2 @@ +testEdge,1,2,9223372036854775807 +testEdge,2,3, diff --git a/formats-tests/src/test/resources/neo4jcsv/testedges_header.csv b/formats-tests/src/test/resources/neo4jcsv/testedges_header.csv new file mode 100644 index 00000000..5528fbfa --- /dev/null +++ b/formats-tests/src/test/resources/neo4jcsv/testedges_header.csv @@ -0,0 +1 @@ +:TYPE,:START_ID,:END_ID,longProperty:long \ No newline at end of file diff --git a/formats-tests/src/test/resources/neo4jcsv/testnodes_data.csv b/formats-tests/src/test/resources/neo4jcsv/testnodes_data.csv new file mode 100644 index 00000000..40ba5fb1 --- /dev/null +++ b/formats-tests/src/test/resources/neo4jcsv/testnodes_data.csv @@ -0,0 +1,3 @@ +1,stringProp1,11,testNode,stringListProp1a;stringListProp1b,21;31;41 +2,stringProp2,,testNode,, +3,,13,testNode,, diff --git a/formats-tests/src/test/resources/neo4jcsv/testnodes_header.csv b/formats-tests/src/test/resources/neo4jcsv/testnodes_header.csv new file mode 100644 index 00000000..96f0e308 --- /dev/null +++ b/formats-tests/src/test/resources/neo4jcsv/testnodes_header.csv @@ -0,0 +1 @@ +id:ID,StringProperty,IntProperty:int,:LABEL,StringListProperty:string[],IntListProperty:int[] \ No newline at end of file diff --git a/formats-tests/src/test/resources/neo4jcsv/unsupported_multiple_labels_data.csv b/formats-tests/src/test/resources/neo4jcsv/unsupported_multiple_labels_data.csv new file mode 100644 index 00000000..3c3369e0 --- /dev/null +++ b/formats-tests/src/test/resources/neo4jcsv/unsupported_multiple_labels_data.csv @@ -0,0 +1 @@ +1,LABEL1,11,LABEL2 \ No newline at end of file diff --git a/formats-tests/src/test/resources/neo4jcsv/unsupported_multiple_labels_header.csv b/formats-tests/src/test/resources/neo4jcsv/unsupported_multiple_labels_header.csv new file mode 100644 index 00000000..9639be21 --- /dev/null +++ b/formats-tests/src/test/resources/neo4jcsv/unsupported_multiple_labels_header.csv @@ -0,0 +1 @@ +id:ID,:LABEL,IntProperty:int,:LABEL \ No newline at end of file diff --git a/formats-tests/src/test/scala/flatgraph/formats/graphml/GraphMLTests.scala b/formats-tests/src/test/scala/flatgraph/formats/graphml/GraphMLTests.scala index 7b78655e..49fc6680 100644 --- a/formats-tests/src/test/scala/flatgraph/formats/graphml/GraphMLTests.scala +++ b/formats-tests/src/test/scala/flatgraph/formats/graphml/GraphMLTests.scala @@ -1,7 +1,9 @@ package flatgraph.formats.graphml import better.files.File -import org.scalatest.matchers.should.Matchers._ +import flatgraph.testdomains.gratefuldead.GratefulDead +import flatgraph.testdomains.gratefuldead.Language.* +import org.scalatest.matchers.should.Matchers.* import org.scalatest.wordspec.AnyWordSpec import flatgraph.util.DiffTool @@ -12,31 +14,28 @@ import scala.jdk.CollectionConverters.{CollectionHasAsScala, IterableHasAsJava} class GraphMLTests extends AnyWordSpec { "import minified gratefuldead graph" in { - pending -// ??? // TODO -// val graph = GratefulDead.newGraph() -// graph.nodeCount() shouldBe 0 -// -// GraphMLImporter.runImport(graph, Paths.get(getClass.getResource("/graphml-small.xml").toURI)) -// graph.nodeCount() shouldBe 3 -// graph.edgeCount() shouldBe 2 -// -// val node1 = graph.node(1) -// node1.label() shouldBe "song" -// val node340 = node1.out("sungBy").next() -// val node527 = node1.out("writtenBy").next() -// -// node340.label shouldBe "artist" -// node340.property("name") shouldBe "Garcia" -// node340.out().hasNext shouldBe false -// node340.in().hasNext shouldBe true -// -// node527.label shouldBe "artist" -// node527.property("name") shouldBe "Bo_Diddley" -// node527.out().hasNext shouldBe false -// node527.in().hasNext shouldBe true -// -// graph.close() + val gratefulDead = GratefulDead.empty + val graph = gratefulDead.graph + graph.nodeCount() shouldBe 0 + + GraphMLImporter.runImport(graph, Paths.get(getClass.getResource("/graphml-small.xml").toURI)) + graph.nodeCount() shouldBe 3 + graph.edgeCount() shouldBe 2 + + gratefulDead.song.size shouldBe 1 + gratefulDead.song.name.l shouldBe List("HEY BO DIDDLEY") + gratefulDead.artist.size shouldBe 2 + gratefulDead.artist.name.l shouldBe List("Garcia", "Bo_Diddley") + + val Seq(boDiddley, garcia) = gratefulDead.artist.sortBy(_.name).l + val Seq(heyBoDiddley) = gratefulDead.song.l + + heyBoDiddley.sungBy shouldBe garcia + heyBoDiddley.writtenBy shouldBe boDiddley + garcia.sang.l shouldBe List(heyBoDiddley) + boDiddley.wrote.l shouldBe List(heyBoDiddley) + + graph.close() } // "Exporter should export valid xml" when { diff --git a/formats/src/main/scala/flatgraph/formats/graphml/GraphMLImporter.scala b/formats/src/main/scala/flatgraph/formats/graphml/GraphMLImporter.scala index bc1d7fb7..77743160 100644 --- a/formats/src/main/scala/flatgraph/formats/graphml/GraphMLImporter.scala +++ b/formats/src/main/scala/flatgraph/formats/graphml/GraphMLImporter.scala @@ -1,11 +1,14 @@ package flatgraph.formats.graphml -import flatgraph.Graph import flatgraph.formats.Importer +import flatgraph.misc.Conversions.toShortSafely +import flatgraph.* import java.nio.file.Path +import scala.collection.mutable import scala.util.{Failure, Success, Try} -import scala.xml.{NodeSeq, XML} +import scala.xml +import scala.xml.XML /** Imports GraphML into flatgraph * @@ -15,27 +18,69 @@ object GraphMLImporter extends Importer { override def runImport(graph: Graph, inputFiles: Seq[Path]): Unit = { assert(inputFiles.size == 1, s"input must be exactly one file, but got ${inputFiles.size}") - val doc = XML.loadFile(inputFiles.head.toFile) + val doc = XML.loadFile(inputFiles.head.toFile) + val graphXml = doc \ "graph" - val keyEntries = doc \ "key" - val graphXml = doc \ "graph" + val graphmlNodes = graphXml \ "node" + val graphmlNodeIdToGNode = addNodesRaw(graphmlNodes, graph) - { // nodes - val nodePropertyContextById = parsePropertyEntries("node", keyEntries) - for (node <- graphXml \ "node") { - addNode(graph, node, nodePropertyContextById) + val keyEntries = doc \ "key" + val diffGraph = new DiffGraphBuilder(graph.schema) + + // node properties + val nodePropertyContextById = parsePropertyEntries("node", keyEntries) + for { + graphmlNode <- graphmlNodes + nodeId = graphmlNode \@ "id" + entry <- graphmlNode \ "data" + } { + entry \@ "key" match { + case KeyForNodeLabel => // ignore, already extracted in `addNodesRaw` + case key => + val propertyType = nodePropertyContextById + .get(key) + .getOrElse(throw new AssertionError(s"key $key not found in propertyContext...")) + .tpe + val value = entry.text + val convertedValue = convertValue(value, propertyType, context = graphmlNode) + diffGraph.setNodeProperty(graphmlNodeIdToGNode(nodeId), key, value) } } - { // edges - val edgePropertyContextById = parsePropertyEntries("edge", keyEntries) - for (edge <- graphXml \ "edge") { - addEdge(graph, edge, edgePropertyContextById) - } + // edges + val edgePropertyContextById = parsePropertyEntries("edge", keyEntries) + for (edge <- graphXml \ "edge") { + addEdge(diffGraph, graphmlNodeIdToGNode, edge, edgePropertyContextById) + } + + DiffGraphApplier.applyDiff(graph, diffGraph) + } + + private def addNodesRaw(graphmlNodes: Seq[xml.Node], graph: Graph): Map[String, GNode] = { + val diffGraphForRawNodes = new DiffGraphBuilder(graph.schema) + val graphmlNodeIdToGNode = mutable.Map.empty[String, GenericDNode] + graphmlNodes.foreach { graphmlNode => + val id = graphmlNode \@ "id" + val label = (graphmlNode \ "data") + .find(_ \@ "key" == KeyForNodeLabel) + .map(_.text) + .getOrElse(throw new AssertionError(s"node label must be defined, but isn't: $graphmlNode")) + val nodeKind = graph.schema + .getNodeKindByLabelMaybe(label) + .getOrElse( + throw new AssertionError( + s"node label `$label` is not one of the labels defined in the schema, which are: [${graph.schema.nodeLabels.mkString(",")}]" + ) + ) + val newNode = new GenericDNode(graph.schema.getNodeKindByLabel(label).toShortSafely) + diffGraphForRawNodes.addNode(newNode) + graphmlNodeIdToGNode.put(id, newNode) } + DiffGraphApplier.applyDiff(graph, diffGraphForRawNodes) + graphmlNodeIdToGNode.view.mapValues(_.storedRef.get).toMap } - private def parsePropertyEntries(forElementType: String, keyEntries: NodeSeq): Map[String, PropertyContext] = { + private def parsePropertyEntries(forElementType: String, keyEntries: xml.NodeSeq): Map[String, PropertyContext] = { keyEntries .filter(_ \@ "for" == forElementType) .map { node => @@ -47,37 +92,16 @@ object GraphMLImporter extends Importer { .toMap } - private def addNode(graph: Graph, node: scala.xml.Node, propertyContextById: Map[String, PropertyContext]): Unit = { - val id = node \@ "id" - var label: Option[String] = None - val keyValuePairs = Seq.newBuilder[Any] - - for (entry <- node \ "data") { - val value = entry.text - entry \@ "key" match { - case KeyForNodeLabel => label = Option(value) - case key => - val PropertyContext(name, tpe) = propertyContextById - .get(key) - .getOrElse(throw new AssertionError(s"key $key not found in propertyContext...")) - val convertedValue = convertValue(value, tpe, context = node) - keyValuePairs.addAll(Seq(name, convertedValue)) - } - } - - // TODO reimplement -// for { -// id <- id.toLongOption -// label <- label -// } graph.addNode(id, label, keyValuePairs.result: _*) - ??? - } - - private def addEdge(graph: Graph, edge: scala.xml.Node, propertyContextById: Map[String, PropertyContext]): Unit = { - val sourceId = edge \@ "source" - val targetId = edge \@ "target" - var label: Option[String] = None - val keyValuePairs = Seq.newBuilder[Any] + private def addEdge( + diffGraph: DiffGraphBuilder, + graphmlNodeIdToGNode: Map[String, GNode], + edge: xml.Node, + propertyContextById: Map[String, PropertyContext] + ): Unit = { + val sourceId = edge \@ "source" + val targetId = edge \@ "target" + var label: Option[String] = None + var edgePropertyMaybe: Option[(String, Any)] = None for (entry <- edge \ "data") { val value = entry.text @@ -88,22 +112,24 @@ object GraphMLImporter extends Importer { .get(key) .getOrElse(throw new AssertionError(s"key $key not found in propertyContext...")) val convertedValue = convertValue(value, tpe, context = edge) - keyValuePairs.addAll(Seq(name, convertedValue)) + if (edgePropertyMaybe.isDefined) { + logger.warn( + s"flatgraph only supports 0..1 edge properties. This graphml edge has more than one properties though - taking only the first one... graphml node: $edge" + ) + } + edgePropertyMaybe = Some(name -> convertedValue) } } - // TODO reimplement -// for { -// sourceId <- sourceId.toLongOption -// source <- Option(graph.node(sourceId)) -// targetId <- targetId.toLongOption -// target <- Option(graph.node(targetId)) -// label <- label -// } source.addEdge(label, target, keyValuePairs.result: _*) - ??? + for { + source <- graphmlNodeIdToGNode.get(sourceId) + target <- graphmlNodeIdToGNode.get(targetId) + label <- label + edgeProperty = edgePropertyMaybe.map(_._2).getOrElse(flatgraph.DefaultValue) + } diffGraph.addEdge(source, target, label, edgeProperty) } - private def convertValue(stringValue: String, tpe: Type.Value, context: scala.xml.Node): Any = { + private def convertValue(stringValue: String, tpe: Type.Value, context: xml.Node): Any = { tryConvertScalarValue(stringValue, tpe) match { case Success(value) => value case Failure(e) => throw new AssertionError(s"unable to parse `$stringValue` of tpe=$tpe. context: $context", e) diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/Accessors.scala b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/Accessors.scala index 5fb477a9..e1a1ff7b 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/Accessors.scala +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/Accessors.scala @@ -6,31 +6,41 @@ object Lang extends ConcreteStoredConversions object Accessors { /* accessors for concrete stored nodes start */ - final class Access_Property_Name(val node: nodes.StoredNode) extends AnyVal { + final class Access_Property_name(val node: nodes.StoredNode) extends AnyVal { def name: String = flatgraph.Accessors.getNodePropertySingle(node.graph, node.nodeKind, 0, node.seq(), "": String) } + final class Access_Property_songType(val node: nodes.StoredNode) extends AnyVal { + def songtype: Option[String] = + flatgraph.Accessors.getNodePropertyOption[String](node.graph, node.nodeKind, 1, node.seq) + } /* accessors for concrete stored nodes end */ /* accessors for base nodes start */ final class Access_ArtistBase(val node: nodes.ArtistBase) extends AnyVal { def name: String = node match { - case stored: nodes.StoredNode => new Access_Property_Name(stored).name + case stored: nodes.StoredNode => new Access_Property_name(stored).name case newNode: nodes.NewArtist => newNode.name } } final class Access_SongBase(val node: nodes.SongBase) extends AnyVal { def name: String = node match { - case stored: nodes.StoredNode => new Access_Property_Name(stored).name + case stored: nodes.StoredNode => new Access_Property_name(stored).name case newNode: nodes.NewSong => newNode.name } + def songtype: Option[String] = node match { + case stored: nodes.StoredNode => new Access_Property_songType(stored).songtype + case newNode: nodes.NewSong => newNode.songtype + } } /* accessors for base nodes end */ } trait ConcreteStoredConversions extends ConcreteBaseConversions { import Accessors.* - implicit def accessPropertyName(node: nodes.StoredNode & nodes.StaticType[nodes.HasNameEMT]): Access_Property_Name = - new Access_Property_Name(node) + implicit def accessPropertyName(node: nodes.StoredNode & nodes.StaticType[nodes.HasNameEMT]): Access_Property_name = + new Access_Property_name(node) + implicit def accessPropertySongtype(node: nodes.StoredNode & nodes.StaticType[nodes.HasSongtypeEMT]): Access_Property_songType = + new Access_Property_songType(node) } trait ConcreteBaseConversions { diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/BaseTypes.scala b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/BaseTypes.scala index 9682c3a9..9f2f0521 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/BaseTypes.scala +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/BaseTypes.scala @@ -1,6 +1,11 @@ package flatgraph.testdomains.gratefuldead.nodes -/** Node types with this marker trait are guaranteed to have the Name property. EMT stands for: "erased marker trait", it exists only at +/** Node types with this marker trait are guaranteed to have the name property. EMT stands for: "erased marker trait", it exists only at * compile time in order to improve type safety. */ trait HasNameEMT + +/** Node types with this marker trait are guaranteed to have the songType property. EMT stands for: "erased marker trait", it exists only at + * compile time in order to improve type safety. + */ +trait HasSongtypeEMT diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/EdgeTypes.java b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/EdgeTypes.java index c94bdd72..0617ea77 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/EdgeTypes.java +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/EdgeTypes.java @@ -8,8 +8,16 @@ public class EdgeTypes { public static final String followedBy = "followedBy"; + +public static final String sungBy = "sungBy"; + + +public static final String writtenBy = "writtenBy"; + public static Set ALL = new HashSet() {{ add(followedBy); +add(sungBy); +add(writtenBy); }}; } \ No newline at end of file diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/EdgeTypes.scala b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/EdgeTypes.scala index 41884a8d..12d5c11e 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/EdgeTypes.scala +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/EdgeTypes.scala @@ -4,3 +4,7 @@ class Followedby(src_4762: flatgraph.GNode, dst_4762: flatgraph.GNode, subSeq_48 extends flatgraph.Edge(src_4762, dst_4762, 0.toShort, subSeq_4862, property_4862) { def weight: Long = this.property.asInstanceOf[Long] } +class Sungby(src_4762: flatgraph.GNode, dst_4762: flatgraph.GNode, subSeq_4862: Int, property_4862: Any) + extends flatgraph.Edge(src_4762, dst_4762, 1.toShort, subSeq_4862, property_4862) +class Writtenby(src_4762: flatgraph.GNode, dst_4762: flatgraph.GNode, subSeq_4862: Int, property_4862: Any) + extends flatgraph.Edge(src_4762, dst_4762, 2.toShort, subSeq_4862, property_4862) diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/GraphSchema.scala b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/GraphSchema.scala index 5ef63ad8..f008ab52 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/GraphSchema.scala +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/GraphSchema.scala @@ -4,47 +4,53 @@ import flatgraph.testdomains.gratefuldead.edges import flatgraph.FormalQtyType object GraphSchema extends flatgraph.Schema { - val nodeLabels = Array("Artist", "Song") - val nodeKindByLabel = nodeLabels.zipWithIndex.toMap - val edgeLabels = Array("followedBy") - val edgeIdByLabel = edgeLabels.zipWithIndex.toMap + private val nodeLabels = IndexedSeq("artist", "song") + val nodeKindByLabel = nodeLabels.zipWithIndex.toMap + val edgeLabels = Array("followedBy", "sungBy", "writtenBy") + val edgeIdByLabel = edgeLabels.zipWithIndex.toMap val edgePropertyAllocators: Array[Int => Array[?]] = - Array(size => Array.fill(size)(0: Long) /* label = followedBy, id = 0 */ ) + Array(size => Array.fill(size)(0: Long) /* label = followedBy, id = 0 */, size => null, size => null) val nodeFactories: Array[(flatgraph.Graph, Int) => nodes.StoredNode] = Array((g, seq) => new nodes.Artist(g, seq), (g, seq) => new nodes.Song(g, seq)) - val edgeFactories: Array[(flatgraph.GNode, flatgraph.GNode, Int, Any) => flatgraph.Edge] = - Array((s, d, subseq, p) => new edges.Followedby(s, d, subseq, p)) - val nodePropertyAllocators: Array[Int => Array[?]] = Array(size => new Array[String](size)) - val normalNodePropertyNames = Array("Name") - val nodePropertyByLabel = normalNodePropertyNames.zipWithIndex.toMap + val edgeFactories: Array[(flatgraph.GNode, flatgraph.GNode, Int, Any) => flatgraph.Edge] = Array( + (s, d, subseq, p) => new edges.Followedby(s, d, subseq, p), + (s, d, subseq, p) => new edges.Sungby(s, d, subseq, p), + (s, d, subseq, p) => new edges.Writtenby(s, d, subseq, p) + ) + val nodePropertyAllocators: Array[Int => Array[?]] = + Array(size => new Array[String](size), size => new Array[String](size)) + val normalNodePropertyNames = Array("name", "songType") + val nodePropertyByLabel = normalNodePropertyNames.zipWithIndex.toMap val nodePropertyDescriptors: Array[FormalQtyType.FormalQuantity | FormalQtyType.FormalType] = { - val nodePropertyDescriptors = new Array[FormalQtyType.FormalQuantity | FormalQtyType.FormalType](4) - for (idx <- Range(0, 4)) { + val nodePropertyDescriptors = new Array[FormalQtyType.FormalQuantity | FormalQtyType.FormalType](8) + for (idx <- Range(0, 8)) { nodePropertyDescriptors(idx) = if ((idx & 1) == 0) FormalQtyType.NothingType else FormalQtyType.QtyNone } - nodePropertyDescriptors(0) = FormalQtyType.StringType // Artist.Name + nodePropertyDescriptors(0) = FormalQtyType.StringType // artist.name nodePropertyDescriptors(1) = FormalQtyType.QtyOne - nodePropertyDescriptors(2) = FormalQtyType.StringType // Song.Name + nodePropertyDescriptors(2) = FormalQtyType.StringType // song.name nodePropertyDescriptors(3) = FormalQtyType.QtyOne + nodePropertyDescriptors(6) = FormalQtyType.StringType // song.songType + nodePropertyDescriptors(7) = FormalQtyType.QtyOption nodePropertyDescriptors } override def getNumberOfNodeKinds: Int = 2 - override def getNumberOfEdgeKinds: Int = 1 + override def getNumberOfEdgeKinds: Int = 3 override def getNodeLabel(nodeKind: Int): String = nodeLabels(nodeKind) override def getNodeKindByLabel(label: String): Int = nodeKindByLabel.getOrElse(label, flatgraph.Schema.UndefinedKind) override def getEdgeLabel(nodeKind: Int, edgeKind: Int): String = edgeLabels(edgeKind) override def getEdgeKindByLabel(label: String): Int = edgeIdByLabel.getOrElse(label, flatgraph.Schema.UndefinedKind) override def getPropertyLabel(nodeKind: Int, propertyKind: Int): String = { - if (propertyKind < 1) normalNodePropertyNames(propertyKind) + if (propertyKind < 2) normalNodePropertyNames(propertyKind) else null } override def getPropertyKindByName(label: String): Int = nodePropertyByLabel.getOrElse(label, flatgraph.Schema.UndefinedKind) - override def getNumberOfPropertyKinds: Int = 1 + override def getNumberOfPropertyKinds: Int = 2 override def makeNode(graph: flatgraph.Graph, nodeKind: Short, seq: Int): nodes.StoredNode = nodeFactories(nodeKind)(graph, seq) override def makeEdge(src: flatgraph.GNode, dst: flatgraph.GNode, edgeKind: Short, subSeq: Int, property: Any): flatgraph.Edge = diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/NodeTypes.java b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/NodeTypes.java index 675b6ff9..ade294cc 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/NodeTypes.java +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/NodeTypes.java @@ -6,14 +6,14 @@ public class NodeTypes { -public static final String Artist = "Artist"; +public static final String artist = "artist"; -public static final String Song = "Song"; +public static final String song = "song"; public static Set ALL = new HashSet() {{ -add(Artist); -add(Song); +add(artist); +add(song); }}; } \ No newline at end of file diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/PropertyKeys.scala b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/PropertyKeys.scala index 01b6af14..96b31066 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/PropertyKeys.scala +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/PropertyKeys.scala @@ -4,6 +4,8 @@ import flatgraph.PropertyKey object PropertyKeys { - val Name = flatgraph.SinglePropertyKey[String](kind = 0, name = "Name", default = "") + val Name = flatgraph.SinglePropertyKey[String](kind = 0, name = "name", default = "") + + val Songtype = flatgraph.OptionalPropertyKey[String](kind = 1, name = "songType") } diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/PropertyNames.java b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/PropertyNames.java index a5673f4e..adfa6d6e 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/PropertyNames.java +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/PropertyNames.java @@ -6,21 +6,21 @@ public class PropertyNames { -public static final String Name = "Name"; +public static final String name = "name"; public static final String performances = "performances"; -public static final String SongType = "SongType"; +public static final String songType = "songType"; public static final String weight = "weight"; public static Set ALL = new HashSet() {{ -add(Name); +add(name); add(performances); -add(SongType); +add(songType); add(weight); }}; diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/RootTypes.scala b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/RootTypes.scala index 6ed0043d..99224aee 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/RootTypes.scala +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/RootTypes.scala @@ -16,6 +16,16 @@ abstract class StoredNode(graph_4762: flatgraph.Graph, kind_4762: Short, seq_476 final def _followedbyIn: Iterator[StoredNode] = flatgraph.Accessors.getNeighborsIn(this.graph, this.nodeKind, this.seq, 0).asInstanceOf[Iterator[StoredNode]] + final def _sungbyOut: Iterator[StoredNode] = + flatgraph.Accessors.getNeighborsOut(this.graph, this.nodeKind, this.seq, 1).asInstanceOf[Iterator[StoredNode]] + final def _sungbyIn: Iterator[StoredNode] = + flatgraph.Accessors.getNeighborsIn(this.graph, this.nodeKind, this.seq, 1).asInstanceOf[Iterator[StoredNode]] + + final def _writtenbyOut: Iterator[StoredNode] = + flatgraph.Accessors.getNeighborsOut(this.graph, this.nodeKind, this.seq, 2).asInstanceOf[Iterator[StoredNode]] + final def _writtenbyIn: Iterator[StoredNode] = + flatgraph.Accessors.getNeighborsIn(this.graph, this.nodeKind, this.seq, 2).asInstanceOf[Iterator[StoredNode]] + } abstract class NewNode(val nodeKind: Short) extends AbstractNode with flatgraph.DNode { diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/RootTypesTraversals.scala b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/RootTypesTraversals.scala index 2cb8715f..88618693 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/RootTypesTraversals.scala +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/RootTypesTraversals.scala @@ -5,4 +5,10 @@ extension (iterator: Iterator[StoredNode]) { final def _followedbyOut: Iterator[StoredNode] = iterator.flatMap(_._followedbyOut) final def _followedbyIn: Iterator[StoredNode] = iterator.flatMap(_._followedbyIn) + final def _sungbyOut: Iterator[StoredNode] = iterator.flatMap(_._sungbyOut) + final def _sungbyIn: Iterator[StoredNode] = iterator.flatMap(_._sungbyIn) + + final def _writtenbyOut: Iterator[StoredNode] = iterator.flatMap(_._writtenbyOut) + final def _writtenbyIn: Iterator[StoredNode] = iterator.flatMap(_._writtenbyIn) + } diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/Traversals.scala b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/Traversals.scala index 9c20a950..4ebf8621 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/Traversals.scala +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/Traversals.scala @@ -7,7 +7,7 @@ object Accessors { import flatgraph.testdomains.gratefuldead.accessors.Lang.* /* accessors for concrete stored nodes start */ - final class Traversal_Property_Name[NodeType <: nodes.StoredNode & nodes.StaticType[nodes.HasNameEMT]](val traversal: Iterator[NodeType]) + final class Traversal_Property_name[NodeType <: nodes.StoredNode & nodes.StaticType[nodes.HasNameEMT]](val traversal: Iterator[NodeType]) extends AnyVal { /** Traverse to name property */ @@ -69,6 +69,84 @@ object Accessors { traversal.filter { item => matchers.find { _.reset(item.name).matches }.isEmpty } } + } + final class Traversal_Property_songType[NodeType <: nodes.StoredNode & nodes.StaticType[nodes.HasSongtypeEMT]]( + val traversal: Iterator[NodeType] + ) extends AnyVal { + + /** Traverse to songtype property */ + def songtype: Iterator[String] = + traversal.flatMap(_.songtype) + + /** Traverse to nodes where the songtype matches the regular expression `value` + */ + def songtype(pattern: String): Iterator[NodeType] = { + if (!flatgraph.misc.Regex.isRegex(pattern)) { + songtypeExact(pattern) + } else { + val matcher = flatgraph.misc.Regex.multilineMatcher(pattern) + traversal.filter { item => + val tmp = item.songtype; tmp.isDefined && matcher.reset(tmp.get).matches + } + } + } + + /** Traverse to nodes where the songtype matches at least one of the regular expressions in `values` + */ + def songtype(patterns: String*): Iterator[NodeType] = { + val matchers = patterns.map(flatgraph.misc.Regex.multilineMatcher) + traversal.filter { item => + val tmp = item.songtype; tmp.isDefined && matchers.exists { _.reset(tmp.get).matches } + } + } + + /** Traverse to nodes where songtype matches `value` exactly. + */ + def songtypeExact(value: String): Iterator[NodeType] = traversal match { + case init: flatgraph.misc.InitNodeIterator[flatgraph.GNode @unchecked] if init.isVirgin && init.hasNext => + val someNode = init.next + flatgraph.Accessors + .getWithInverseIndex(someNode.graph, someNode.nodeKind, 1, value) + .asInstanceOf[Iterator[NodeType]] + case _ => + traversal.filter { node => + val tmp = node.songtype; tmp.isDefined && tmp.get == value + } + } + + /** Traverse to nodes where songtype matches one of the elements in `values` exactly. + */ + def songtypeExact(values: String*): Iterator[NodeType] = + if (values.length == 1) songtypeExact(values.head) + else { + val valueSet = values.toSet + traversal.filter { item => + val tmp = item.songtype; tmp.isDefined && valueSet.contains(tmp.get) + } + } + + /** Traverse to nodes where songtype does not match the regular expression `value`. + */ + def songtypeNot(pattern: String): Iterator[NodeType] = { + if (!flatgraph.misc.Regex.isRegex(pattern)) { + traversal.filter { node => node.songtype.isEmpty || node.songtype.get != pattern } + } else { + val matcher = flatgraph.misc.Regex.multilineMatcher(pattern) + traversal.filterNot { item => + val tmp = item.songtype; tmp.isDefined && matcher.reset(tmp.get).matches + } + } + } + + /** Traverse to nodes where songtype does not match any of the regular expressions in `values`. + */ + def songtypeNot(patterns: String*): Iterator[NodeType] = { + val matchers = patterns.map(flatgraph.misc.Regex.multilineMatcher) + traversal.filterNot { item => + val tmp = item.songtype; tmp.isDefined && matchers.exists { _.reset(tmp.get).matches } + } + } + } /* accessors for concrete stored nodes end */ @@ -196,6 +274,79 @@ object Accessors { traversal.filter { item => matchers.find { _.reset(item.name).matches }.isEmpty } } + /** Traverse to songtype property */ + def songtype: Iterator[String] = + traversal.flatMap(_.songtype) + + /** Traverse to nodes where the songtype matches the regular expression `value` + */ + def songtype(pattern: String): Iterator[NodeType] = { + if (!flatgraph.misc.Regex.isRegex(pattern)) { + songtypeExact(pattern) + } else { + val matcher = flatgraph.misc.Regex.multilineMatcher(pattern) + traversal.filter { item => + val tmp = item.songtype; tmp.isDefined && matcher.reset(tmp.get).matches + } + } + } + + /** Traverse to nodes where the songtype matches at least one of the regular expressions in `values` + */ + def songtype(patterns: String*): Iterator[NodeType] = { + val matchers = patterns.map(flatgraph.misc.Regex.multilineMatcher) + traversal.filter { item => + val tmp = item.songtype; tmp.isDefined && matchers.exists { _.reset(tmp.get).matches } + } + } + + /** Traverse to nodes where songtype matches `value` exactly. + */ + def songtypeExact(value: String): Iterator[NodeType] = traversal match { + case init: flatgraph.misc.InitNodeIterator[flatgraph.GNode @unchecked] if init.isVirgin && init.hasNext => + val someNode = init.next + flatgraph.Accessors + .getWithInverseIndex(someNode.graph, someNode.nodeKind, 1, value) + .asInstanceOf[Iterator[NodeType]] + case _ => + traversal.filter { node => + val tmp = node.songtype; tmp.isDefined && tmp.get == value + } + } + + /** Traverse to nodes where songtype matches one of the elements in `values` exactly. + */ + def songtypeExact(values: String*): Iterator[NodeType] = + if (values.length == 1) songtypeExact(values.head) + else { + val valueSet = values.toSet + traversal.filter { item => + val tmp = item.songtype; tmp.isDefined && valueSet.contains(tmp.get) + } + } + + /** Traverse to nodes where songtype does not match the regular expression `value`. + */ + def songtypeNot(pattern: String): Iterator[NodeType] = { + if (!flatgraph.misc.Regex.isRegex(pattern)) { + traversal.filter { node => node.songtype.isEmpty || node.songtype.get != pattern } + } else { + val matcher = flatgraph.misc.Regex.multilineMatcher(pattern) + traversal.filterNot { item => + val tmp = item.songtype; tmp.isDefined && matcher.reset(tmp.get).matches + } + } + } + + /** Traverse to nodes where songtype does not match any of the regular expressions in `values`. + */ + def songtypeNot(patterns: String*): Iterator[NodeType] = { + val matchers = patterns.map(flatgraph.misc.Regex.multilineMatcher) + traversal.filterNot { item => + val tmp = item.songtype; tmp.isDefined && matchers.exists { _.reset(tmp.get).matches } + } + } + } /* accessors for base nodes end */ } @@ -203,7 +354,10 @@ trait ConcreteStoredConversions extends ConcreteBaseConversions { import Accessors.* implicit def accessPropertyNameTraversal[NodeType <: nodes.StoredNode & nodes.StaticType[nodes.HasNameEMT]]( traversal: IterableOnce[NodeType] - ): Traversal_Property_Name[NodeType] = new Traversal_Property_Name(traversal.iterator) + ): Traversal_Property_name[NodeType] = new Traversal_Property_name(traversal.iterator) + implicit def accessPropertySongtypeTraversal[NodeType <: nodes.StoredNode & nodes.StaticType[nodes.HasSongtypeEMT]]( + traversal: IterableOnce[NodeType] + ): Traversal_Property_songType[NodeType] = new Traversal_Property_songType(traversal.iterator) } trait ConcreteBaseConversions { diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/neighboraccessors/Artist.scala b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/neighboraccessors/Artist.scala new file mode 100644 index 00000000..9a1f3d00 --- /dev/null +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/neighboraccessors/Artist.scala @@ -0,0 +1,54 @@ +package flatgraph.testdomains.gratefuldead.neighboraccessors + +import flatgraph.testdomains.gratefuldead.nodes +import flatgraph.testdomains.gratefuldead.Language.* + +final class AccessNeighborsForArtist(val node: nodes.Artist) extends AnyVal { + + /** Traverse to song via sungBy IN edge. + */ + @deprecated("please use sang instead") + def songViaSungbyIn: Iterator[nodes.Song] = sang + + /** Traverse to song via sungBy IN edge. + */ + def sang: Iterator[nodes.Song] = sungbyIn.collectAll[nodes.Song] + + /** Traverse to song via writtenBy IN edge. + */ + @deprecated("please use wrote instead") + def songViaWrittenbyIn: Iterator[nodes.Song] = wrote + + /** Traverse to song via writtenBy IN edge. + */ + def wrote: Iterator[nodes.Song] = writtenbyIn.collectAll[nodes.Song] + + def sungbyIn: Iterator[nodes.Song] = node._sungbyIn.cast[nodes.Song] + + def writtenbyIn: Iterator[nodes.Song] = node._writtenbyIn.cast[nodes.Song] +} + +final class AccessNeighborsForArtistTraversal(val traversal: Iterator[nodes.Artist]) extends AnyVal { + + /** Traverse to song via sungBy IN edge. + */ + def sang: Iterator[nodes.Song] = traversal.flatMap(_.sang) + + /** Traverse to song via sungBy IN edge. + */ + @deprecated("please use sang instead") + def songViaSungbyIn: Iterator[nodes.Song] = traversal.flatMap(_.songViaSungbyIn) + + /** Traverse to song via writtenBy IN edge. + */ + def wrote: Iterator[nodes.Song] = traversal.flatMap(_.wrote) + + /** Traverse to song via writtenBy IN edge. + */ + @deprecated("please use wrote instead") + def songViaWrittenbyIn: Iterator[nodes.Song] = traversal.flatMap(_.songViaWrittenbyIn) + + def sungbyIn: Iterator[nodes.Song] = traversal.flatMap(_.sungbyIn) + + def writtenbyIn: Iterator[nodes.Song] = traversal.flatMap(_.writtenbyIn) +} diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/neighboraccessors/Song.scala b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/neighboraccessors/Song.scala index 2b0b17b7..f94a8cb2 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/neighboraccessors/Song.scala +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/neighboraccessors/Song.scala @@ -5,18 +5,59 @@ import flatgraph.testdomains.gratefuldead.Language.* final class AccessNeighborsForSong(val node: nodes.Song) extends AnyVal { - /** Traverse to Song via followedBy IN edge. + /** Traverse to artist via sungBy OUT edge. + */ + @deprecated("please use sungBy instead") + def artistViaSungbyOut: nodes.Artist = sungBy + + /** Traverse to artist via sungBy OUT edge. + */ + def sungBy: nodes.Artist = { + try { sungbyOut.collectAll[nodes.Artist].next() } + catch { + case e: java.util.NoSuchElementException => + throw new flatgraph.SchemaViolationException( + "OUT edge with label sungBy to an adjacent artist is mandatory, but not defined for this song node with seq=" + node.seq, + e + ) + } + } + + /** Traverse to artist via writtenBy OUT edge. + */ + @deprecated("please use writtenBy instead") + def artistViaWrittenbyOut: nodes.Artist = writtenBy + + /** Traverse to artist via writtenBy OUT edge. + */ + def writtenBy: nodes.Artist = { + try { writtenbyOut.collectAll[nodes.Artist].next() } + catch { + case e: java.util.NoSuchElementException => + throw new flatgraph.SchemaViolationException( + "OUT edge with label writtenBy to an adjacent artist is mandatory, but not defined for this song node with seq=" + node.seq, + e + ) + } + } + + /** Traverse to song via followedBy IN edge. */ def songViaFollowedbyIn: Iterator[nodes.Song] = followedbyIn.collectAll[nodes.Song] - /** Traverse to Song via followedBy OUT edge. + /** Traverse to song via followedBy OUT edge. + */ + @deprecated("please use followedBy instead") + def songViaFollowedbyOut: nodes.Song = followedBy + + /** Traverse to song via followedBy OUT edge. */ - def songViaFollowedbyOut: nodes.Song = { + def followedBy: nodes.Song = { try { followedbyOut.collectAll[nodes.Song].next() } catch { case e: java.util.NoSuchElementException => throw new flatgraph.SchemaViolationException( - "OUT edge with label followedBy to an adjacent Song is mandatory, but not defined for this Song node with seq=" + node.seq, + "OUT edge with label followedBy to an adjacent song is mandatory, but not defined for this song node with seq=" + node.seq, e ) } @@ -25,19 +66,50 @@ final class AccessNeighborsForSong(val node: nodes.Song) extends AnyVal { def followedbyIn: Iterator[nodes.Song] = node._followedbyIn.cast[nodes.Song] def followedbyOut: Iterator[nodes.Song] = node._followedbyOut.cast[nodes.Song] + + def sungbyOut: Iterator[nodes.Artist] = node._sungbyOut.cast[nodes.Artist] + + def writtenbyOut: Iterator[nodes.Artist] = node._writtenbyOut.cast[nodes.Artist] } final class AccessNeighborsForSongTraversal(val traversal: Iterator[nodes.Song]) extends AnyVal { - /** Traverse to Song via followedBy IN edge. + /** Traverse to artist via sungBy OUT edge. + */ + def sungBy: Iterator[nodes.Artist] = traversal.map(_.sungBy) + + /** Traverse to artist via sungBy OUT edge. + */ + @deprecated("please use sungBy instead") + def artistViaSungbyOut: Iterator[nodes.Artist] = traversal.map(_.artistViaSungbyOut) + + /** Traverse to artist via writtenBy OUT edge. + */ + def writtenBy: Iterator[nodes.Artist] = traversal.map(_.writtenBy) + + /** Traverse to artist via writtenBy OUT edge. + */ + @deprecated("please use writtenBy instead") + def artistViaWrittenbyOut: Iterator[nodes.Artist] = traversal.map(_.artistViaWrittenbyOut) + + /** Traverse to song via followedBy IN edge. */ def songViaFollowedbyIn: Iterator[nodes.Song] = traversal.flatMap(_.songViaFollowedbyIn) - /** Traverse to Song via followedBy OUT edge. + /** Traverse to song via followedBy OUT edge. */ + def followedBy: Iterator[nodes.Song] = traversal.map(_.followedBy) + + /** Traverse to song via followedBy OUT edge. + */ + @deprecated("please use followedBy instead") def songViaFollowedbyOut: Iterator[nodes.Song] = traversal.map(_.songViaFollowedbyOut) def followedbyIn: Iterator[nodes.Song] = traversal.flatMap(_.followedbyIn) def followedbyOut: Iterator[nodes.Song] = traversal.flatMap(_.followedbyOut) + + def sungbyOut: Iterator[nodes.Artist] = traversal.flatMap(_.sungbyOut) + + def writtenbyOut: Iterator[nodes.Artist] = traversal.flatMap(_.writtenbyOut) } diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/neighboraccessors/package.scala b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/neighboraccessors/package.scala index 4cd3d8c1..b24964e1 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/neighboraccessors/package.scala +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/neighboraccessors/package.scala @@ -7,6 +7,12 @@ package object neighboraccessors { object Lang extends Conversions trait Conversions { + implicit def accessNeighborsForArtist(node: nodes.Artist): AccessNeighborsForArtist = + new AccessNeighborsForArtist(node) + + implicit def accessNeighborsForArtistTraversal(traversal: IterableOnce[nodes.Artist]): AccessNeighborsForArtistTraversal = + new AccessNeighborsForArtistTraversal(traversal.iterator) + implicit def accessNeighborsForSong(node: nodes.Song): AccessNeighborsForSong = new AccessNeighborsForSong(node) diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/nodes/Artist.scala b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/nodes/Artist.scala index 6b1b9729..eb95b56d 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/nodes/Artist.scala +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/nodes/Artist.scala @@ -10,15 +10,15 @@ trait ArtistBase extends AbstractNode with StaticType[ArtistEMT] { override def propertiesMap: java.util.Map[String, Any] = { import flatgraph.testdomains.gratefuldead.accessors.Lang.* val res = new java.util.HashMap[String, Any]() - if (("": String) != this.name) res.put("Name", this.name) + if (("": String) != this.name) res.put("name", this.name) res } } object Artist { - val Label = "Artist" + val Label = "artist" object PropertyNames { - val Name = flatgraph.testdomains.gratefuldead.PropertyNames.Name + val Name = flatgraph.testdomains.gratefuldead.PropertyNames.name } object PropertyDefaults { val Name = "" @@ -51,11 +51,11 @@ class Artist(graph_4762: flatgraph.Graph, seq_4762: Int) object NewArtist { def apply(): NewArtist = new NewArtist private val outNeighbors: Map[String, Set[String]] = Map() - private val inNeighbors: Map[String, Set[String]] = Map() + private val inNeighbors: Map[String, Set[String]] = Map("sungBy" -> Set("song"), "writtenBy" -> Set("song")) } class NewArtist extends NewNode(0.toShort) with ArtistBase { override type StoredNodeType = Artist - override def label: String = "Artist" + override def label: String = "artist" override def isValidOutNeighbor(edgeLabel: String, n: NewNode): Boolean = { NewArtist.outNeighbors.getOrElse(edgeLabel, Set.empty).contains(n.label) diff --git a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/nodes/Song.scala b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/nodes/Song.scala index a0b765ec..b38d1a40 100644 --- a/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/nodes/Song.scala +++ b/test-schemas-domain-classes/src/main/scala/flatgraph/testdomains/gratefuldead/nodes/Song.scala @@ -3,22 +3,24 @@ package flatgraph.testdomains.gratefuldead.nodes import flatgraph.testdomains.gratefuldead.Language.* import scala.collection.immutable.{IndexedSeq, ArraySeq} -trait SongEMT extends AnyRef with HasNameEMT +trait SongEMT extends AnyRef with HasNameEMT with HasSongtypeEMT trait SongBase extends AbstractNode with StaticType[SongEMT] { override def propertiesMap: java.util.Map[String, Any] = { import flatgraph.testdomains.gratefuldead.accessors.Lang.* val res = new java.util.HashMap[String, Any]() - if (("": String) != this.name) res.put("Name", this.name) + if (("": String) != this.name) res.put("name", this.name) + this.songtype.foreach { p => res.put("songType", p) } res } } object Song { - val Label = "Song" + val Label = "song" object PropertyNames { - val Name = flatgraph.testdomains.gratefuldead.PropertyNames.Name + val Name = flatgraph.testdomains.gratefuldead.PropertyNames.name + val Songtype = flatgraph.testdomains.gratefuldead.PropertyNames.songType } object PropertyDefaults { val Name = "" @@ -33,29 +35,32 @@ class Song(graph_4762: flatgraph.Graph, seq_4762: Int) override def productElementName(n: Int): String = n match { case 0 => "name" + case 1 => "songtype" case _ => "" } override def productElement(n: Int): Any = n match { case 0 => this.name + case 1 => this.songtype case _ => null } override def productPrefix = "Song" - override def productArity = 1 + override def productArity = 2 override def canEqual(that: Any): Boolean = that != null && that.isInstanceOf[Song] } object NewSong { - def apply(): NewSong = new NewSong - private val outNeighbors: Map[String, Set[String]] = Map("followedBy" -> Set("Song")) - private val inNeighbors: Map[String, Set[String]] = Map("followedBy" -> Set("Song")) + def apply(): NewSong = new NewSong + private val outNeighbors: Map[String, Set[String]] = + Map("followedBy" -> Set("song"), "sungBy" -> Set("artist"), "writtenBy" -> Set("artist")) + private val inNeighbors: Map[String, Set[String]] = Map("followedBy" -> Set("song")) } class NewSong extends NewNode(1.toShort) with SongBase { override type StoredNodeType = Song - override def label: String = "Song" + override def label: String = "song" override def isValidOutNeighbor(edgeLabel: String, n: NewNode): Boolean = { NewSong.outNeighbors.getOrElse(edgeLabel, Set.empty).contains(n.label) @@ -64,31 +69,38 @@ class NewSong extends NewNode(1.toShort) with SongBase { NewSong.inNeighbors.getOrElse(edgeLabel, Set.empty).contains(n.label) } - var name: String = "": String - def name(value: String): this.type = { this.name = value; this } + var name: String = "": String + var songtype: Option[String] = None + def name(value: String): this.type = { this.name = value; this } + def songtype(value: Option[String]): this.type = { this.songtype = value; this } + def songtype(value: String): this.type = { this.songtype = Option(value); this } override def flattenProperties(interface: flatgraph.BatchedUpdateInterface): Unit = { interface.insertProperty(this, 0, Iterator(this.name)) + if (songtype.nonEmpty) interface.insertProperty(this, 1, this.songtype) } override def copy(): this.type = { val newInstance = new NewSong newInstance.name = this.name + newInstance.songtype = this.songtype newInstance.asInstanceOf[this.type] } override def productElementName(n: Int): String = n match { case 0 => "name" + case 1 => "songtype" case _ => "" } override def productElement(n: Int): Any = n match { case 0 => this.name + case 1 => this.songtype case _ => null } override def productPrefix = "NewSong" - override def productArity = 1 + override def productArity = 2 override def canEqual(that: Any): Boolean = that != null && that.isInstanceOf[NewSong] } diff --git a/test-schemas/src/main/scala/flatgraph/testdomains/gratefuldead/Schema.scala b/test-schemas/src/main/scala/flatgraph/testdomains/gratefuldead/Schema.scala index dbbc5fd3..b454a77c 100644 --- a/test-schemas/src/main/scala/flatgraph/testdomains/gratefuldead/Schema.scala +++ b/test-schemas/src/main/scala/flatgraph/testdomains/gratefuldead/Schema.scala @@ -11,21 +11,29 @@ object Schema { val instance: flatgraph.schema.Schema = { val builder = new SchemaBuilder(domainShortName = "GratefulDead", basePackage = "flatgraph.testdomains.gratefuldead") - val name = builder.addProperty("Name", ValueType.String).mandatory(default = "") - val songType = builder.addProperty("SongType", ValueType.String) + // properties + val name = builder.addProperty("name", ValueType.String).mandatory(default = "") + val songType = builder.addProperty("songType", ValueType.String) val performances = builder.addProperty("performances", ValueType.Int) val weight = builder.addProperty("weight", ValueType.Long).mandatory(default = 0) + // nodes val artist = builder - .addNodeType("Artist") + .addNodeType("artist") .addProperty(name) val song = builder - .addNodeType("Song") + .addNodeType("song") .addProperty(name) + .addProperty(songType) + // edges + val sungBy = builder.addEdgeType("sungBy") + val writtenBy = builder.addEdgeType("writtenBy") val followedBy = builder.addEdgeType("followedBy").addProperty(weight) - song.addOutEdge(followedBy, inNode = song, cardinalityOut = Cardinality.One) + song.addOutEdge(sungBy, inNode = artist, cardinalityOut = Cardinality.One, stepNameOut = "sungBy", stepNameIn = "sang") + song.addOutEdge(writtenBy, inNode = artist, cardinalityOut = Cardinality.One, stepNameOut = "writtenBy", stepNameIn = "wrote") + song.addOutEdge(followedBy, inNode = song, cardinalityOut = Cardinality.One, stepNameOut = "followedBy") builder.build }