├── project
├── build.properties
└── plugins.sbt
├── .idea
├── .gitignore
└── codeStyleSettings.xml
├── pubring.gpg.enc
├── secring.gpg.enc
├── credentials.sbt.enc
├── .gitignore
├── src
├── test
│ └── scala
│ │ ├── helpers
│ │ ├── ContextMock.scala
│ │ ├── Colors.scala
│ │ ├── CompileSpec.scala
│ │ └── CodeComparisonSpec.scala
│ │ ├── codegeneration
│ │ ├── GraphFactorySpec.scala
│ │ ├── RelationTraitSpec.scala
│ │ ├── PropertiesSpec.scala
│ │ ├── RelationClassSpec.scala
│ │ ├── NodeTraitSpec.scala
│ │ ├── RelationTraitFactorySpec.scala
│ │ ├── HyperRelationClassSpec.scala
│ │ ├── RelationFactorySpec.scala
│ │ ├── SchemaSpec.scala
│ │ ├── NodeClassSpec.scala
│ │ ├── NodeFactorySpec.scala
│ │ ├── HyperRelationFactorySpec.scala
│ │ ├── NodeTraitFactorySpec.scala
│ │ └── GraphClassSpec.scala
│ │ └── errors
│ │ └── ErrorSpec.scala
└── main
│ └── scala
│ ├── Helpers.scala
│ ├── GraphSchema.scala
│ ├── Parameters.scala
│ ├── Patterns.scala
│ └── Generators.scala
├── .travis.yml
├── README.md
└── LICENSE
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.12
2 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !codeStyleSettings.xml
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/pubring.gpg.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renesca/renesca-magic/HEAD/pubring.gpg.enc
--------------------------------------------------------------------------------
/secring.gpg.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renesca/renesca-magic/HEAD/secring.gpg.enc
--------------------------------------------------------------------------------
/credentials.sbt.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renesca/renesca-magic/HEAD/credentials.sbt.enc
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # sbt
2 | target/
3 |
4 | # local stuff
5 | local.*
6 |
7 | # tomcat distriubution folder
8 | .distribution
9 |
10 | /magic
11 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | // code coverage
2 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.0.4")
3 |
4 | addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.0.0")
5 |
6 | // publishing
7 | resolvers += "jgit-repo" at "http://download.eclipse.org/jgit/maven"
8 |
9 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0")
10 |
11 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "0.5.0")
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/test/scala/helpers/ContextMock.scala:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import org.specs2.mock.Mockito
4 | import org.specs2.mutable.Specification
5 |
6 | import scala.reflect.macros.whitebox
7 |
8 | trait ContextMock extends Specification with Mockito {
9 | val contextMock: whitebox.Context = {
10 | import scala.reflect.macros.{Universe => macroUniverse}
11 | import scala.reflect.runtime.{universe => runtimeUniverse}
12 | val context = mock[whitebox.Context].smart
13 | context.universe returns runtimeUniverse.asInstanceOf[macroUniverse]
14 | context
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/GraphFactorySpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 |
6 | class GraphFactorySpec extends CodeComparisonSpec {
7 |
8 | import contextMock.universe._
9 |
10 | "simple Graph" >> {
11 | generatedContainsCode(
12 | q"""
13 | object A {
14 | @Graph trait G
15 | }
16 | """,
17 | q"""
18 | object G {
19 | def empty = new G(raw.Graph.empty);
20 | def remove(items: Item*) = {
21 | val wrapper = empty;
22 | wrapper.remove(((items): _*));
23 | wrapper
24 | }
25 | def apply(items: Item*) = {
26 | val wrapper = empty;
27 | wrapper.add(((items): _*));
28 | wrapper
29 | }
30 | };
31 | """
32 | )
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/test/scala/helpers/Colors.scala:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | trait Colors {
4 | def bold(s: String) = s"\u001b[1m${ s }\u001b[0m"
5 | def red(s: String) = s"\u001b[31m${ s }\u001b[0m"
6 | def green(s: String) = s"\u001b[32m${ s }\u001b[0m"
7 | def brown(s: String) = s"\u001b[33m${ s }\u001b[0m"
8 | def blue(s: String) = s"\u001b[34m${ s }\u001b[0m"
9 | def purple(s: String) = s"\u001b[35m${ s }\u001b[0m"
10 | def cyan(s: String) = s"\u001b[36m${ s }\u001b[0m"
11 |
12 | def highlight(code: String): String = {
13 | val id = "[a-zA-Z_$][a-zA-Z\\d_$]*"
14 | code.
15 | replaceAll(s"(\\s|^)(object|class|case class|trait)(\\s+$id)([^A-Za-z_\\d$$])", "$1" + bold(purple("$2")) + bold(green("$3")) + "$4").
16 | replaceAll(s"(\\s|^)(override def|def|override val|val|override var|var)(\\s+$id)([^A-Za-z_\\d$$])", "$1" + blue("$2") + cyan("$3") + "$4").
17 | replaceAll(s"(\\s|^)(@$id\\s)", "$1" + brown("$2"))
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/scala/Helpers.scala:
--------------------------------------------------------------------------------
1 | package renesca.schema.macros
2 |
3 | import java.io.{File, PrintWriter}
4 |
5 | object Helpers {
6 | def rev(s: String) = "rev_" + s
7 |
8 | def nameToPlural(name: String) = {
9 | val lower = name.take(1).toLowerCase + name.drop(1)
10 | val suffix = if(lower.endsWith("s")) "" else "s"
11 | lower + suffix
12 | }
13 |
14 | def traitFactoryName(name: String) = name + "Factory"
15 | def traitMatchesFactoryName(name: String) = name + "MatchesFactory"
16 | def traitMatchesClassName(name: String) = name + "Matches"
17 | def factoryCreateMethod(name: String) = "create" + name
18 | def factoryMergeMethod(name: String) = "merge" + name
19 | def factoryMatchesMethod(name: String) = "matches" + name
20 | def factoryUniqueMatchesMethod(name: String) = "matchesOn" + name.capitalize
21 | def nameToLabel(name: String) = name.toUpperCase
22 | def hyperStartRelationName(name:String) = s"${ name }Start"
23 | def hyperEndRelationName(name:String) = s"${ name }End"
24 |
25 | def writeFile(filename: String, contents: String) = {
26 | val parent = new File(filename).getParentFile
27 | if(parent != null) parent.mkdirs()
28 |
29 | val out = new PrintWriter(filename)
30 | out.println(contents)
31 | out.close()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: scala
3 | cache:
4 | directories:
5 | - "$HOME/.sbt"
6 | - "$HOME/.ivy2"
7 | scala:
8 | - 2.11.7
9 | jdk:
10 | - oraclejdk7
11 | - oraclejdk8
12 | script:
13 | - sbt ++$TRAVIS_SCALA_VERSION clean coverage test
14 | - sbt ++$TRAVIS_SCALA_VERSION coverageReport
15 | - rm ~/.sbt/boot/scala-*/org.scala-sbt/sbt/*/sbt.components.lock ~/.sbt/boot/sbt.boot.lock
16 | after_success:
17 | - test "${TRAVIS_JDK_VERSION}" = 'oraclejdk7' && sbt ++$TRAVIS_SCALA_VERSION coveralls
18 | # multi-line script?
19 | - test -n "${TRAVIS_TAG}" && test "${TRAVIS_JDK_VERSION}" = 'oraclejdk7' && openssl aes-256-cbc -k "$file_encryption_password" -in credentials.sbt.enc -out local.credentials.sbt -d && openssl aes-256-cbc -k "$file_encryption_password" -in secring.gpg.enc -out local.secring.gpg -d && openssl aes-256-cbc -k "$file_encryption_password" -in pubring.gpg.enc -out local.pubring.gpg -d && sbt ++$TRAVIS_SCALA_VERSION publishSigned sonatypeRelease
20 | env:
21 | global:
22 | - secure: oDpGPZs1hGH2MeOZUdF2UQc91Or72KvbX8ZhkOwVnxgC0dlUc/bWAv5mQfHkTCB9/My6gCwpR/2TTexw60/x/gj2FdT75jXCXEaSMqPL0ZUvLgJ1dZf4cfqG/X+bi+CwgzjDVLsIC9ws8IbZwUxq7M8qn1pbxSxeCiqhSxeAVoE=
23 | - secure: kFXL9YZ4zHjonwjxscQrLfINscmMPjr49lAruQ9s2vhrn2QMrSn2KMUiNWA3Vi1GThTiSJsmq+302JY4DNU++dtTpsmGTNoKExqXgNsgK9XIfOcZGprQCgwZ0RnNn4PKtm7m1QyJEJpHilCaGAZQZ9uZwmHxD/bR8pHagdDmZ10=
24 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/RelationTraitSpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 | class RelationTraitSpec extends CodeComparisonSpec {
6 |
7 | import contextMock.universe._
8 |
9 | "simple trait" >> {
10 | generatedContainsCode(
11 | q"object A {@Relation trait T}",
12 | """trait T[+START <: Node, +END <: Node] extends AbstractRelation[START, END] ;"""
13 | )
14 | }
15 |
16 | "with super trait" >> {
17 | generatedContainsCode(
18 | q"object A { @Relation trait K; @Relation trait T extends K}",
19 | """trait T[+START <: Node, +END <: Node] extends K[START, END] ;"""
20 | )
21 | }
22 |
23 | "with external super trait" >> {
24 | generatedContainsCode(
25 | q"object A { @Relation trait K; @Relation trait T extends K with Immutable}",
26 | """trait T[+START <: Node, +END <: Node] extends K[START, END] with Immutable;"""
27 | )
28 | }
29 |
30 | "with properties" >> {
31 | generatedContainsCode(
32 | q"object A {@Relation trait T {val p:Long}}",
33 | q"""trait T[+START <: Node, +END <: Node] extends AbstractRelation[START, END] {
34 | def p: Long = rawItem.properties("p").asInstanceOf[LongPropertyValue]
35 | }"""
36 | )
37 | }
38 |
39 | "custom code" >> {
40 | generatedContainsCode(
41 | q"object A {@Node trait T {def custom = 5}}",
42 | q"""trait T extends Node { def custom = 5 }"""
43 | )
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/.idea/codeStyleSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/test/scala/helpers/CompileSpec.scala:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | trait CompileSpec extends Colors {
4 |
5 | import scala.tools.cmd.CommandLineParser
6 | import scala.tools.nsc.reporters.StoreReporter
7 | import scala.tools.nsc.{CompilerCommand, Global, Settings}
8 |
9 | object Config {
10 | val paradiseJar = System.getProperty("user.home") + "/.ivy2/cache/org.scalamacros/paradise_2.11.7/jars/paradise_2.11.7-2.1.0.jar"
11 | val classpath = System.getProperty("sbt.paths.tests.classpath")
12 |
13 | val options = s"-Xplugin-require:macroparadise -Xplugin:$paradiseJar -cp $classpath"
14 | val args = CommandLineParser.tokenize(options)
15 | val emptySettings = new Settings(error => sys.error("compilation has failed: " + error))
16 | }
17 |
18 | def compileCode(code: String): Boolean = {
19 | import Config._
20 |
21 | // Step 1: create and initialize the compiler
22 | val reporter = new StoreReporter()
23 | val command = new CompilerCommand(args, emptySettings)
24 | val settings = command.settings
25 | val global = new Global(settings, reporter)
26 | val run = new global.Run
27 | global.phase = run.parserPhase
28 | global.globalPhase = run.parserPhase
29 | import global._
30 |
31 | // Step 2: parse the input code
32 | import scala.compat.Platform.EOL
33 | val unit = newCompilationUnit(code, "")
34 | val tree = newUnitParser(unit).parse()
35 | if(reporter.hasErrors) throw new Exception("parse has failed:" + EOL + (reporter.infos map (_.msg) mkString EOL))
36 |
37 | // Step 3: typecheck the input code
38 | import analyzer._
39 | phase = run.namerPhase
40 | globalPhase = run.namerPhase
41 | val namer = newNamer(rootContext(unit))
42 | namer.enterSym(tree)
43 | phase = run.typerPhase
44 | globalPhase = run.typerPhase
45 | val typer = newTyper(rootContext(unit))
46 | val typedTree = typer.typed(tree)
47 | for(workItem <- unit.toCheck) workItem()
48 | if(reporter.hasErrors) throw new Exception("typecheck has failed:" + EOL + (reporter.infos map (_.msg) mkString EOL) + EOL + highlight(showCode(typedTree)))
49 |
50 | true
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/PropertiesSpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 | class PropertiesSpec extends CodeComparisonSpec {
6 |
7 | import contextMock.universe._
8 |
9 | "immutable property getter" >> {
10 | generatedContainsCode(
11 | q"object A {@Node class N {val p:String}}",
12 | q"""def p: String = rawItem.properties("p").asInstanceOf[StringPropertyValue]""")
13 | }
14 |
15 | "immutable property getter with unique" >> {
16 | generatedContainsCode(
17 | q"object A {@Node class N {@unique val p:String}}",
18 | q"""def p: String = rawItem.properties("p").asInstanceOf[StringPropertyValue]""")
19 | }
20 |
21 | "optional immutable property getter" >> {
22 | generatedContainsCode(
23 | q"object A {@Node class N {val p:Option[String]}}",
24 | q"""def p:Option[String] = rawItem.properties.get("p").asInstanceOf[Option[StringPropertyValue]].map(propertyValueToPrimitive)""")
25 | }
26 |
27 | "mutable property getter and setter" >> {
28 | generatedContainsCode(
29 | q"object A {@Node class N {var p:String}}",
30 | q"""def p: String = rawItem.properties("p").asInstanceOf[StringPropertyValue]""",
31 | q"""def `p_=`(newValue: String): scala.Unit = rawItem.properties.update("p", newValue)"""
32 | )
33 | }
34 |
35 | "mutable property getter and setter with unique" >> {
36 | generatedContainsCode(
37 | q"object A {@Node class N {@unique var p:String}}",
38 | q"""def p: String = rawItem.properties("p").asInstanceOf[StringPropertyValue]""",
39 | q"""def `p_=`(newValue: String): scala.Unit = rawItem.properties.update("p", newValue)"""
40 | )
41 | }
42 |
43 | "optional mutable property getter and setter" >> {
44 | generatedContainsCode(
45 | q"object A {@Node class N {var p:Option[String]}}",
46 | q"""def p:Option[String] = rawItem.properties.get("p").asInstanceOf[Option[StringPropertyValue]].map(propertyValueToPrimitive)""",
47 | q"""def `p_=`(newValue:Option[String]): scala.Unit = { if(newValue.isDefined) rawItem.properties("p") = newValue.get else rawItem.properties -= "p" }"""
48 | )
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/RelationClassSpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 | class RelationClassSpec extends CodeComparisonSpec {
6 |
7 | import contextMock.universe._
8 |
9 | "simple class" >> {
10 | generatedContainsCode(
11 | q"object A {@Node class A; @Node class B; @Relation class R(startNode:A, endNode:B)}",
12 | """case class R(startNode: A, rawItem: raw.Relation, endNode: B) extends Relation[A, B];"""
13 | )
14 | }
15 |
16 | "simple class with property accessors" >> {
17 | generatedContainsCode(
18 | q"object A {@Node class A; @Node class B; @Relation class R(startNode:A, endNode:B) {val p:Long}}",
19 | q"""def p: Long = rawItem.properties("p").asInstanceOf[LongPropertyValue]"""
20 | )
21 | }
22 |
23 | "preserve custom code" >> {
24 | generatedContainsCode(
25 | q"object A {@Node class A; @Node class B; @Relation class R(startNode:A, endNode:B) {def custom = 0}}",
26 | q"""case class R(startNode: A, rawItem: raw.Relation, endNode: B) extends Relation[A, B] { def custom = 0 } ;"""
27 | )
28 | }
29 |
30 | "with super types" >> {
31 | generatedContainsCode(
32 | q"object A {@Node class A; @Node class B; @Relation trait T; @Relation class R(startNode:A, endNode:B) extends T}",
33 | """case class R(startNode: A, rawItem: raw.Relation, endNode: B) extends Relation[A, B] with T[A, B] ;"""
34 | )
35 | }
36 |
37 | "with external super types" >> {
38 | generatedContainsCode(
39 | q"object A {@Node class A; @Node class B; @Relation trait T; @Relation class R(startNode:A, endNode:B) extends T with Immutable}",
40 | """case class R(startNode: A, rawItem: raw.Relation, endNode: B) extends Relation[A, B] with T[A, B] with Immutable;"""
41 | )
42 | }
43 |
44 | "with multiple super types" >> {
45 | generatedContainsCode(
46 | q"object A {@Node class A; @Node class B; @Relation trait T; @Relation trait S; @Relation class R(startNode:A, endNode:B) extends T with S}",
47 | """case class R(startNode: A, rawItem: raw.Relation, endNode: B) extends Relation[A, B] with T[A, B] with S[A, B] ;"""
48 | )
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/scala/GraphSchema.scala:
--------------------------------------------------------------------------------
1 | package renesca.schema.macros
2 |
3 | import scala.annotation.{StaticAnnotation, compileTimeOnly}
4 | import scala.language.experimental.macros
5 | import scala.reflect.macros.whitebox
6 |
7 | class Aborter(context: whitebox.Context) {
8 | def abort(msg: String) {
9 | context.abort(context.universe.NoPosition, msg)
10 | }
11 | }
12 |
13 | class Warner(context: whitebox.Context) {
14 | def warning(msg: String) {
15 | context.warning(context.universe.NoPosition, msg)
16 | }
17 | }
18 |
19 | trait Context {
20 | val context: whitebox.Context
21 | val aborter: Aborter
22 | def abort(msg: String): Nothing = {
23 | aborter.abort(msg)
24 | throw new RuntimeException("This should never happen. It only forces the return of Nothing.")
25 | }
26 | val warner: Warner
27 | def warning(msg: String): Nothing = {
28 | warner.warning(msg)
29 | throw new RuntimeException("This should never happen. It only forces the return of Nothing.")
30 | }
31 | }
32 |
33 | @compileTimeOnly("enable macro paradise to expand macro annotations")
34 | class GraphSchema extends StaticAnnotation {
35 | def macroTransform(annottees: Any*): Any = macro GraphSchemaMacro.graphSchema
36 | }
37 |
38 | object GraphSchemaMacro {
39 | // TODO: why are implicits not working here?
40 | // implicit def treeToString(l: Tree): String = l match { case Literal(Constant(string: String)) => string }
41 | // TODO: validation: nodeTraits(propertyTypes), nodes need to inherit exactly one NodeTrait
42 | // TODO: compile error when nodes inherit not only from nodeTraits
43 |
44 | def graphSchema(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
45 | val env = new Patterns with Generators with Code {
46 | val context: c.type = c
47 | val aborter = new Aborter(c)
48 | val warner = new Warner(c)
49 | }
50 | import env._
51 |
52 | c.Expr[Any](annottees.map(_.tree).toList match {
53 | case SchemaPattern(schemaPattern) :: Nil =>
54 |
55 | val code = schema(Schema(schemaPattern))
56 | Helpers.writeFile(s"magic/${ schemaPattern.name }.generated.scala", c.universe.showCode(code))
57 | code
58 | })
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/test/scala/helpers/CodeComparisonSpec.scala:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import org.specs2.mutable.Specification
4 | import renesca.schema.macros.{Aborter, Code, Generators, Patterns, Warner}
5 |
6 | trait CodeComparisonSpec extends Specification with ContextMock with CompileSpec with Colors {
7 | sequential
8 |
9 | trait ExpectedCode
10 |
11 | case class With(code: String) extends ExpectedCode
12 |
13 | case class Not(code: String) extends ExpectedCode
14 |
15 | val magic = new Patterns with Generators with Code {
16 | val context: contextMock.type = contextMock
17 | val aborter = mock[Aborter].smart
18 | aborter.abort(anyString) answers { msg => val e = new RuntimeException(msg.toString); e.printStackTrace(); throw e }
19 | val warner = mock[Warner].smart
20 | warner.warning(anyString) answers { msg => val e = new RuntimeException(msg.toString); e.printStackTrace(); throw e }
21 | }
22 |
23 | import contextMock.universe._
24 | import magic.{schema, Schema, SchemaPattern}
25 |
26 | implicit def TreeToString(t: Tree): String = showCode(t)
27 | implicit def TreeToWith(t: Tree): With = With(showCode(t))
28 | implicit def StringToWith(code: String): With = With(code)
29 |
30 | private def errorMessage(source: Tree, generated: Tree, snippet: String, shouldContain: Boolean) = {
31 | val failMsg = if(shouldContain) "which doesn't contain:" else "which contains but shouldn't:"
32 |
33 | highlight(comparableWildcards(showCode(source))) +
34 | "\n" + bold(red("--- generates: ---")) + "\n" +
35 | highlight(comparableWildcards(showCode(generated))) +
36 | "\n" + bold(red(s"--- $failMsg ---")) + "\n" +
37 | highlight(comparableWildcards(snippet)) +
38 | "\n" + bold(red("----------")) + "\n"
39 | }
40 |
41 | private val wildcardRegex = "_\\$\\d+".r
42 | private def comparableWildcards(code: String) = wildcardRegex.replaceAllIn(code, "_")
43 | private def withoutSpaces(code: String) = code.replaceAll("\\s", "")
44 | private def comparable(code: String) = withoutSpaces(comparableWildcards(code))
45 | private def containCode(source: Tree, generated: Tree, snippets: ExpectedCode*) = snippets.map {
46 | case With(snippet) =>
47 | comparable(showCode(generated)) must (contain(comparable(snippet))).setMessage(errorMessage(source, generated, snippet, true))
48 | case Not(snippet) =>
49 | comparable(showCode(generated)) must (not(contain(comparable(snippet)).setMessage(errorMessage(source, generated, snippet, false))))
50 | }
51 | def generate(code: Tree) = schema(Schema(SchemaPattern.unapply(code).get))
52 | def annotateCode(code: Tree) = "@renesca.schema.macros.GraphSchema " + showCode(code)
53 | def generatedContainsCode(source: Tree, snippets: ExpectedCode*) = {
54 | (compileCode(annotateCode(source)) mustEqual true) +:
55 | containCode(source, generate(source), snippets: _*)
56 | }
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/NodeTraitSpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 | class NodeTraitSpec extends CodeComparisonSpec {
6 |
7 | import contextMock.universe._
8 |
9 | "simple trait" >> {
10 | generatedContainsCode(
11 | q"object A {@Node trait T}",
12 | """trait T extends Node;"""
13 | )
14 | }
15 |
16 | "with super trait" >> {
17 | generatedContainsCode(
18 | q"object A { @Node trait K; @Node trait T extends K}",
19 | """trait T extends K;"""
20 | )
21 | }
22 |
23 | "with multiple super traits" >> {
24 | generatedContainsCode(
25 | q"object A { @Node trait K;@Node trait L; @Node trait T extends K with L}",
26 | """trait T extends K with L;"""
27 | )
28 | }
29 |
30 | "with external super type" >> {
31 | generatedContainsCode(
32 | q"object A { @Node trait T; @Node trait TE extends T with Immutable}",
33 | q"""trait TE extends T with Immutable;"""
34 | )
35 | }
36 |
37 | "with properties" >> {
38 | generatedContainsCode(
39 | q"object A {@Node trait T {val p:Long}}",
40 | q"""trait T extends Node { def p: Long = rawItem.properties("p").asInstanceOf[LongPropertyValue] }"""
41 | )
42 | }
43 |
44 | "custom code" >> {
45 | generatedContainsCode(
46 | q"object A {@Node trait T {def custom = 5}}",
47 | q"""trait T extends Node { def custom = 5 }"""
48 | )
49 | }
50 |
51 | "accessor interfaces for predecessor and successor traits" >> {
52 | generatedContainsCode(
53 | q"""object A {
54 | @Node trait T
55 | @Node class N extends T
56 | @Node class M extends T
57 | @Relation class R(startNode:T,endNode:T)
58 | }""",
59 | q"""
60 | trait T extends Node {
61 | def rNs: Seq[N];
62 | def rMs: Seq[M];
63 | def rs: Seq[T];
64 | def rev_rNs: Seq[N];
65 | def rev_rMs: Seq[M];
66 | def rev_rs: Seq[T]
67 | };
68 | """
69 | )
70 | }
71 |
72 | "direct neighbour accessor interfaces" >> {
73 | generatedContainsCode(
74 | q"""
75 | object A {
76 | @Node trait T
77 | @Node class N
78 | @Relation class R(startNode:T,endNode:N)
79 | @Relation class S(startNode:N,endNode:T)
80 | }
81 | """,
82 | q"""
83 | trait T extends Node {
84 | def rs: Seq[N];
85 | def rev_s: Seq[N]
86 | };
87 | """
88 | )
89 | }
90 |
91 | "direct neighbour accessor interfaces over hyperrelations" >> {
92 | generatedContainsCode(
93 | q"""object A {
94 | @Node trait T
95 | @Node class N
96 | @HyperRelation class R(startNode:T,endNode:N)
97 | @HyperRelation class S(startNode:N,endNode:T)
98 | }""",
99 | q"""
100 | trait T extends Node {
101 | def rs: Seq[N];
102 | def rev_s: Seq[N]
103 | };
104 | """
105 | )
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/RelationTraitFactorySpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 | class RelationTraitFactorySpec extends CodeComparisonSpec {
6 |
7 | import contextMock.universe._
8 |
9 | "simple relation trait" >> {
10 | generatedContainsCode(
11 | q"object A {@Relation trait T}",
12 | q"""trait TMatchesFactory[START <: Node, +RELATION <: AbstractRelation[START, END], END <: Node]
13 | extends AbstractRelationFactory[START, RELATION, END] {
14 | def matchesT(startNode: START, endNode: END, matches: Set[PropertyKey] = Set.empty): RELATION
15 | }""",
16 | q"""trait TFactory[START <: Node, +RELATION <: AbstractRelation[START, END], END <: Node]
17 | extends AbstractRelationFactory[START, RELATION, END] with TMatchesFactory[START,RELATION,END] {
18 | def createT(startNode: START, endNode: END): RELATION
19 | def mergeT(startNode: START, endNode: END, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): RELATION
20 | }"""
21 | )
22 | }
23 |
24 | "with factory interface" >> {
25 | generatedContainsCode(
26 | q"object A {@Relation trait T {val p:String}}",
27 | q"""trait TMatchesFactory[START <: Node, +RELATION <: AbstractRelation[START, END], END <: Node]
28 | extends AbstractRelationFactory[START, RELATION, END] {
29 | def matchesT(startNode: START, endNode: END, p: Option[String] = None, matches: Set[PropertyKey] = Set.empty): RELATION
30 | }""",
31 | q"""trait TFactory[START <: Node, +RELATION <: AbstractRelation[START, END], END <: Node]
32 | extends AbstractRelationFactory[START, RELATION, END] with TMatchesFactory[START,RELATION,END] {
33 | def createT(startNode: START, endNode: END, p: String): RELATION
34 | def mergeT(startNode: START, endNode: END, p: String, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): RELATION
35 | }"""
36 | )
37 | }
38 |
39 | "without factory interface (only matches)" >> {
40 | generatedContainsCode(
41 | q"object A {@Node class N; @Relation trait T {val p:String}; @Relation class R(startNode: N, endNode: N) extends T { val t: String }}",
42 | q"""trait TMatchesFactory[START <: Node, +RELATION <: AbstractRelation[START, END], END <: Node]
43 | extends AbstractRelationFactory[START, RELATION, END] {
44 | def matchesT(startNode: START, endNode: END, p: Option[String] = None, matches: Set[PropertyKey] = Set.empty): RELATION
45 | }""",
46 | q"""trait TFactory[START <: Node, +RELATION <: AbstractRelation[START, END], END <: Node]
47 | extends AbstractRelationFactory[START, RELATION, END] with TMatchesFactory[START,RELATION,END]"""
48 | )
49 | }
50 |
51 | //TODO: "with own factory" >> { }
52 | "with superType factories" >> {
53 | generatedContainsCode(
54 | q"object A {@Relation trait T; @Relation trait X extends T}",
55 | q"""trait XMatchesFactory[START <: Node, +RELATION <: AbstractRelation[START, END], END <: Node]
56 | extends TMatchesFactory[START, RELATION, END] {
57 | def matchesX(startNode: START, endNode: END, matches: Set[PropertyKey] = Set.empty): RELATION
58 | }""",
59 | q"""trait XFactory[START <: Node, +RELATION <: AbstractRelation[START, END], END <: Node]
60 | extends TFactory[START, RELATION, END] with XMatchesFactory[START,RELATION,END] {
61 | def createX(startNode: START, endNode: END): RELATION
62 | def mergeX(startNode: START, endNode: END, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): RELATION
63 | }"""
64 | )
65 | }
66 |
67 | "with external superType" >> {
68 | generatedContainsCode(
69 | q"object A {@Relation trait T; @Relation trait X extends T with Immutable}",
70 | q"""trait XMatchesFactory[START <: Node, +RELATION <: AbstractRelation[START, END], END <: Node]
71 | extends TMatchesFactory[START, RELATION, END] {
72 | def matchesX(startNode: START, endNode: END, matches: Set[PropertyKey] = Set.empty): RELATION
73 | }""",
74 | q"""trait XFactory[START <: Node, +RELATION <: AbstractRelation[START, END], END <: Node]
75 | extends TFactory[START, RELATION, END] with XMatchesFactory[START,RELATION,END] {
76 | def createX(startNode: START, endNode: END): RELATION
77 | def mergeX(startNode: START, endNode: END, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): RELATION
78 | }"""
79 | )
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/HyperRelationClassSpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 | class HyperRelationClassSpec extends CodeComparisonSpec {
6 |
7 | import contextMock.universe._
8 |
9 | "simple class, helper relations" >> {
10 | generatedContainsCode(
11 | q"object A {@Node class A; @Node class B; @HyperRelation class R(startNode:A, endNode:B)}",
12 | q"""case class R(rawItem: raw.Node) extends HyperRelation[A, RStart, R, REnd, B] {
13 | override val label = raw.Label("R");
14 | override val labels = Set(raw.Label("R"))
15 | }""",
16 | """case class RStart(startNode: A, rawItem: raw.Relation, endNode: R) extends Relation[A, R];""",
17 | """case class REnd(startNode: R, rawItem: raw.Relation, endNode: B) extends Relation[R, B];"""
18 | )
19 | }
20 |
21 | "simple class with property accessors" >> {
22 | generatedContainsCode(
23 | q"object A {@Node class A; @Node class B; @HyperRelation class R(startNode:A, endNode:B) { val p: Long } }",
24 | q"""def p: Long = rawItem.properties("p").asInstanceOf[LongPropertyValue]"""
25 | )
26 | }
27 |
28 | "preserve custom code" >> {
29 | generatedContainsCode(
30 | q"object A {@Node class A; @Node class B; @HyperRelation class R(startNode:A, endNode:B) {def custom = 0}}",
31 | q"""case class R(rawItem: raw.Node) extends HyperRelation[A, RStart, R, REnd, B] {
32 | override val label = raw.Label("R");
33 | override val labels = Set(raw.Label("R"))
34 | def custom = 0
35 | }"""
36 | )
37 | }
38 |
39 | "direct neighbour accessors" >> {
40 | generatedContainsCode(
41 | q"""object A {@Node class A; @Node class B; @Node class M
42 | @HyperRelation class N(startNode:A, endNode:B)
43 | @Relation class R(startNode:N, endNode:M)
44 | @Relation class S(startNode:M, endNode:N)}
45 | """,
46 | q"""case class N(rawItem: raw.Node) extends HyperRelation[A, NStart, N, NEnd, B] {
47 | override val label = raw.Label("N")
48 | override val labels = Set(raw.Label("N"))
49 | def rs: Seq[M] = successorsAs(M, R)
50 | def rev_s: Seq[M] = predecessorsAs(M, S)
51 | };""",
52 | q"""case class M(rawItem: raw.Node) extends Node {
53 | override val label = raw.Label("M")
54 | override val labels = Set(raw.Label("M"))
55 | def s: Seq[N] = successorsAs(N, S)
56 | def rev_rs: Seq[N] = predecessorsAs(N, R)
57 | };"""
58 | )
59 | }
60 |
61 | "direct neighbour accessors over hyperrelations" >> {
62 | generatedContainsCode(
63 | q"""object A {@Node class A; @Node class B; @Node class M
64 | @HyperRelation class N(startNode:A, endNode:B)
65 | @HyperRelation class R(startNode:N,endNode:M)}""",
66 | q"""case class N(rawItem: raw.Node) extends HyperRelation[A, NStart, N, NEnd, B] {
67 | override val label = raw.Label("N")
68 | override val labels = Set(raw.Label("N"))
69 | def rs: Seq[M] = successorsAs(M, R)
70 | };""",
71 | q"""case class M(rawItem: raw.Node) extends Node {
72 | override val label = raw.Label("M")
73 | override val labels = Set(raw.Label("M"))
74 | def rev_rs: Seq[N] = predecessorsAs(N, R)
75 | };"""
76 | )
77 | }
78 |
79 | "with super relation types" >> {
80 | generatedContainsCode(
81 | q"object A {@Node class A; @Node class B; @Relation trait T; @HyperRelation class R(startNode:A, endNode:B) extends T}",
82 | """case class R(rawItem: raw.Node) extends HyperRelation[A, RStart, R, REnd, B] with T[A, B] {
83 | override val label = raw.Label("R");
84 | override val labels = Set(raw.Label("R"))
85 | }"""
86 | )
87 | }
88 |
89 | "with super node types" >> {
90 | generatedContainsCode(
91 | q"object A {@Node class A; @Node class B; @Node trait K; @HyperRelation class R(startNode:A, endNode:B) extends K}",
92 | """case class R(rawItem: raw.Node) extends HyperRelation[A, RStart, R, REnd, B] with K {
93 | override val label = raw.Label("R");
94 | override val labels = Set(raw.Label("R"), raw.Label("K"))
95 | }"""
96 | )
97 | }
98 |
99 | "with super relation and node types" >> {
100 | generatedContainsCode(
101 | q"object A {@Node class A; @Node class B; @Relation trait T; @Node trait K; @HyperRelation class R(startNode:A, endNode:B) extends T with K}",
102 | """case class R(rawItem: raw.Node) extends HyperRelation[A, RStart, R, REnd, B] with T[A, B] with K {
103 | override val label = raw.Label("R");
104 | override val labels = Set(raw.Label("R"), raw.Label("K"))
105 | }"""
106 | )
107 | }
108 |
109 | "with super relation and node types and external trait" >> {
110 | generatedContainsCode(
111 | q"object A {@Node class A; @Node class B; @Relation trait T; @Node trait K; @HyperRelation class R(startNode:A, endNode:B) extends T with K with Immutable}",
112 | """case class R(rawItem: raw.Node) extends HyperRelation[A, RStart, R, REnd, B] with T[A, B] with K with Immutable {
113 | override val label = raw.Label("R");
114 | override val labels = Set(raw.Label("R"), raw.Label("K"))
115 | }"""
116 | )
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/RelationFactorySpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 | class RelationFactorySpec extends CodeComparisonSpec {
6 |
7 | import contextMock.universe._
8 |
9 | "simple relation factory" >> {
10 | generatedContainsCode(
11 | // TODO: fail with compile error when start or endNode does not exist
12 | q"object A {@Relation class R(startNode:A, endNode:B)}",
13 | q"""object R extends RelationFactory[A, R, B] with AbstractRelationFactory[A, R, B] {
14 | val relationType = raw.RelationType("R");
15 | def wrap(relation: raw.Relation) = R(A.wrap(relation.startNode), relation, B.wrap(relation.endNode));
16 | def create(startNode: A, endNode: B): R = {
17 | val wrapped = wrap(raw.Relation.create(startNode.rawItem, relationType, endNode.rawItem));
18 | wrapped
19 | }
20 | def merge(startNode: A, endNode: B, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): R = {
21 | val wrapped = wrap(raw.Relation.merge(startNode.rawItem, relationType, endNode.rawItem, merge = merge, onMatch = onMatch));
22 | wrapped
23 | }
24 | def matches(startNode: A, endNode: B, matches: Set[PropertyKey] = Set.empty): R = {
25 | val wrapped = wrap(raw.Relation.matches(startNode.rawItem, relationType, endNode.rawItem, matches = matches));
26 | wrapped
27 | }
28 | } """
29 | )
30 | }.pendingUntilFixed
31 |
32 | "with super factory" >> {
33 | generatedContainsCode(
34 | q"object A {@Relation trait T; @Node class A; @Node class B;@Relation class R(startNode:A, endNode:B) extends T}",
35 | """object R extends RelationFactory[A, R, B] with TFactory[A, R, B] { """,
36 | q"""def createT(startNode: A, endNode: B): R = this.create(startNode, endNode)"""
37 | )
38 | }
39 |
40 | "with external supertype" >> {
41 | generatedContainsCode(
42 | q"object A {@Relation trait T; @Node class A; @Node class B;@Relation class R(startNode:A, endNode:B) extends T with Immutable}",
43 | """object R extends RelationFactory[A, R, B] with TFactory[A, R, B] { """,
44 | q"""def createT(startNode: A, endNode: B): R = this.create(startNode, endNode)"""
45 | )
46 | }
47 |
48 | "with properties" >> {
49 | generatedContainsCode(
50 | q"object A {@Node class A; @Node class B;@Relation class R(startNode:A, endNode:B) {val p:String; var x:Long}}",
51 | q"""def create(startNode: A, endNode: B, p: String, x: Long): R = {
52 | val wrapped = wrap(raw.Relation.create(startNode.rawItem, relationType, endNode.rawItem));
53 | wrapped.rawItem.properties.update("p", p);
54 | wrapped.rawItem.properties.update("x", x);
55 | wrapped
56 | } """
57 | )
58 | }
59 |
60 | "with properties - parameter order of create" >> {
61 | generatedContainsCode(
62 | q"""object A {@Node class A; @Node class B
63 | @Relation class R(startNode:A, endNode:B) {
64 | var y:Option[Boolean]
65 | val q:Option[Double]
66 | var x:Long
67 | val p:String
68 | }
69 | }""",
70 | q"""def create(startNode: A, endNode: B, p: String, x: Long, q: Option[Double] = None, y: Option[Boolean] = None):R""",
71 | q"""def merge(startNode: A, endNode: B, p: String, x: Long, q: Option[Double] = None, y: Option[Boolean] = None, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty):R""",
72 | q"""def matches(startNode: A, endNode: B, p: Option[String] = None, q: Option[Double] = None, x: Option[Long] = None, y: Option[Boolean] = None, matches: Set[PropertyKey] = Set.empty):R"""
73 | )
74 | }
75 |
76 | "with inherited properties" >> {
77 | generatedContainsCode(
78 | q"object A {@Node class A; @Node class B;@Relation trait T {val p:String; var x:Long}; @Relation class R(startNode:A, endNode:B) extends T}",
79 | q"""def create(startNode: A, endNode: B, p: String, x: Long): R = {
80 | val wrapped = wrap(raw.Relation.create(startNode.rawItem, relationType, endNode.rawItem));
81 | wrapped.rawItem.properties.update("p", p);
82 | wrapped.rawItem.properties.update("x", x);
83 | wrapped
84 | }""",
85 | q""" def createT(startNode: A, endNode: B, p: String, x: Long): R = this.create(startNode, endNode, p, x) """
86 | )
87 | }
88 |
89 | "with inherited properties from two traits" >> {
90 | generatedContainsCode(
91 | q"object A {@Node class A; @Node class B;@Relation trait T {val p:String}; @Relation trait S {var x:Long}; @Relation class R(startNode:A, endNode:B) extends T with S}",
92 | Not( """ def createT(startNode: A, endNode: B, p: String"""),
93 | Not( """ def createS(startNode: A, endNode: B, p: String""")
94 | )
95 | }
96 |
97 | "with indirectly inherited properties" >> {
98 | generatedContainsCode(
99 | q"object A {@Node class A; @Node class B; @Relation trait T {val p:String; var x:Long}; @Relation trait X extends T; @Relation class R(startNode:A, endNode:B) extends X}",
100 | q""" def createT(startNode: A, endNode: B, p: String, x: Long): R = this.create(startNode, endNode, p, x)""",
101 | q""" def createX(startNode: A, endNode: B, p: String, x: Long): R = this.create(startNode, endNode, p, x)"""
102 | )
103 | }
104 |
105 | "with indirectly inherited properties in chain" >> {
106 | generatedContainsCode(
107 | q"object A {@Node class A; @Node class B; @Relation trait T {val p:String;}; @Relation trait X extends T {var x:Long}; @Relation class R(startNode:A, endNode:B) extends X}",
108 | q""" def createX(startNode: A, endNode: B, p: String, x: Long): R = this.create(startNode, endNode, p, x)""",
109 | q""" def matchesT(startNode: A, endNode: B, p: Option[String] = None, matches: Set[PropertyKey] = Set.empty): R = this.matches(startNode, endNode, p, None, matches)""",
110 | Not( """ def createT(startNode: A, endNode: B, p: String""")
111 | )
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/scala/Parameters.scala:
--------------------------------------------------------------------------------
1 | package renesca.schema.macros
2 |
3 | trait Parameters extends Context {
4 |
5 | import Helpers._
6 | import context.universe._
7 |
8 |
9 | case class Parameter(name: Tree, typeName: Tree, optional: Boolean, default: Option[Tree], mutable: Boolean, unique: Boolean) {
10 | def canEqual(other: Any): Boolean = other.isInstanceOf[Parameter]
11 |
12 | override def equals(other: Any): Boolean = other match {
13 | case that: Parameter =>
14 | (that canEqual this) &&
15 | this.name.toString == that.name.toString &&
16 | this.typeName.toString == that.typeName.toString &&
17 | this.optional == that.optional &&
18 | this.default.toString == that.default.toString &&
19 | this.mutable == that.mutable &&
20 | this.unique == that.unique
21 | case _ => false
22 | }
23 |
24 | override def hashCode: Int = List(this.name.toString, this.typeName.toString, this.optional, this.default.toString, this.mutable).hashCode
25 | def toParamCode: Tree = this match {
26 | case Parameter(propertyName, propertyType, _, None, _, _) => q"val ${ TermName(propertyName.toString) }:${ propertyType }"
27 | case Parameter(propertyName, propertyType, _, Some(defaultValue), _, _) => q"val ${ TermName(propertyName.toString) }:${ propertyType } = ${ defaultValue }"
28 | }
29 |
30 | def toAssignmentCode(schemaItem: Tree): Tree = this match {
31 | case Parameter(propertyName, propertyType, false, _, _, _) => q"$schemaItem.properties(${ propertyName.toString }) = $propertyName"
32 | case Parameter(propertyName, propertyType, true, _, _, _) => q"if($propertyName.isDefined) $schemaItem.properties(${ propertyName.toString }) = $propertyName.get"
33 | }
34 | }
35 |
36 | case class ParameterList(parameters: List[Parameter], typeName: String, hasOwnFactory: Option[Boolean]) {
37 |
38 | val (withDefault, nonDefault) = parameters.sortBy(_.name.toString).partition(_.default.isDefined)
39 | val (withDefaultOptional, withDefaultNonOptional) = withDefault.partition(_.optional)
40 | val ordered = nonDefault ::: withDefaultNonOptional ::: withDefaultOptional
41 | def toParamCode: List[Tree] = ordered.map(_.toParamCode)
42 | def toAssignmentCode(schemaItem: Tree): List[Tree] = ordered.map(_.toAssignmentCode(schemaItem))
43 |
44 | def optional = ParameterList(parameters.map {
45 | case Parameter(name, typeName, false, _, mutable, unique) => Parameter(name, tq"Option[$typeName]", true, Some(q"None"), mutable, unique)
46 | case Parameter(name, typeName, true, _, mutable, unique) => Parameter(name, typeName, true, Some(q"None"), mutable, unique)
47 | }, typeName, hasOwnFactory)
48 |
49 | def supplementMissingParametersOf(that: ParameterList): List[Tree] = {
50 | this.ordered.map(p => (p, that.ordered.find(_.name.toString == p.name.toString))).map {
51 | case (_, Some(other)) => other.name
52 | case (mine, None) => mine.default.get // we know that we only handle a default param at this point (put into typesystem?)
53 | }
54 | }
55 |
56 | def toCallerCode = this.ordered.map(_.name)
57 | }
58 |
59 | object ParameterList {
60 | def warnOnUniqueOptional(statement: Tree) = {
61 | warner.warning("@unique constraints cannot be used with optional properties. Annotation has no effect: " + statement.toString)
62 | }
63 |
64 | def warnOnUniqueRelation(representsNode: Boolean, statement: Tree) = {
65 | if(!representsNode)
66 | warner.warning("@unique constraints can only be used in Nodes, NodeTraits and HyperRelations. Annotation has no effect: " + statement.toString)
67 | }
68 |
69 | def create(flatStatements: List[Tree], typeName: String, representsNode: Boolean, hasOwnFactory: Option[Boolean] = Some(true)): ParameterList = ParameterList(flatStatements.collect {
70 | case (q"val $propertyName:Option[$propertyType] = $default") => Parameter(q"$propertyName", tq"Option[$propertyType]", optional = true, default = Some(q"$default"), mutable = false, unique = false)
71 | case (q"var $propertyName:Option[$propertyType] = $default") => Parameter(q"$propertyName", tq"Option[$propertyType]", optional = true, default = Some(q"$default"), mutable = true, unique = false)
72 | case (q"val $propertyName:Option[$propertyType]") => Parameter(q"$propertyName", tq"Option[$propertyType]", optional = true, default = Some(q"None"), mutable = false, unique = false)
73 | case (q"var $propertyName:Option[$propertyType]") => Parameter(q"$propertyName", tq"Option[$propertyType]", optional = true, default = Some(q"None"), mutable = true, unique = false)
74 | case (q"val $propertyName:$propertyType = $default") => Parameter(q"$propertyName", q"$propertyType", optional = false, default = Some(q"$default"), mutable = false, unique = false)
75 | case (q"var $propertyName:$propertyType = $default") => Parameter(q"$propertyName", q"$propertyType", optional = false, default = Some(q"$default"), mutable = true, unique = false)
76 | case (q"val $propertyName:$propertyType") => Parameter(q"$propertyName", q"$propertyType", optional = false, default = None, mutable = false, unique = false)
77 | case (q"var $propertyName:$propertyType") => Parameter(q"$propertyName", q"$propertyType", optional = false, default = None, mutable = true, unique = false)
78 | case s@(q"@unique val $propertyName:Option[$propertyType] = $default") =>
79 | warnOnUniqueOptional(s)
80 | Parameter(q"$propertyName", tq"Option[$propertyType]", optional = true, default = Some(q"$default"), mutable = false, unique = false)
81 | case s@(q"@unique var $propertyName:Option[$propertyType] = $default") =>
82 | warnOnUniqueOptional(s)
83 | Parameter(q"$propertyName", tq"Option[$propertyType]", optional = true, default = Some(q"$default"), mutable = true, unique = false)
84 | case s@(q"@unique val $propertyName:Option[$propertyType]") =>
85 | warnOnUniqueOptional(s)
86 | Parameter(q"$propertyName", tq"Option[$propertyType]", optional = true, default = Some(q"None"), mutable = false, unique = false)
87 | case s@(q"@unique var $propertyName:Option[$propertyType]") =>
88 | warnOnUniqueOptional(s)
89 | Parameter(q"$propertyName", tq"Option[$propertyType]", optional = true, default = Some(q"None"), mutable = true, unique = false)
90 | case s@(q"@unique val $propertyName:$propertyType = $default") =>
91 | warnOnUniqueRelation(representsNode, s)
92 | Parameter(q"$propertyName", q"$propertyType", optional = false, default = Some(q"$default"), mutable = false, unique = representsNode)
93 | case s@(q"@unique var $propertyName:$propertyType = $default") =>
94 | warnOnUniqueRelation(representsNode, s)
95 | Parameter(q"$propertyName", q"$propertyType", optional = false, default = Some(q"$default"), mutable = true, unique = representsNode)
96 | case s@(q"@unique val $propertyName:$propertyType") =>
97 | warnOnUniqueRelation(representsNode, s)
98 | Parameter(q"$propertyName", q"$propertyType", optional = false, default = None, mutable = false, unique = representsNode)
99 | case s@(q"@unique var $propertyName:$propertyType") =>
100 | warnOnUniqueRelation(representsNode, s)
101 | Parameter(q"$propertyName", q"$propertyType", optional = false, default = None, mutable = true, unique = representsNode)
102 | }, typeName, hasOwnFactory)
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/SchemaSpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 | class SchemaSpec extends CodeComparisonSpec {
6 |
7 |
8 | import contextMock.universe._
9 |
10 | "Empty Schema" >> {
11 | generatedContainsCode(
12 | q"object Empty",
13 | q"""
14 | object Empty {
15 | import renesca.{graph => raw}
16 | import renesca.QueryHandler
17 | import renesca.schema._
18 | import renesca.parameter._
19 | import renesca.parameter.implicits._
20 |
21 | val nodeLabelToFactory = Map[Set[raw.Label],NodeFactory[Node]]()
22 |
23 | trait RootNodeTraitFactory[+NODE <: Node] {
24 | def factory(node:raw.Node) = nodeLabelToFactory(node.labels.toSet).asInstanceOf[NodeFactory[NODE]];
25 | def wrap(node: raw.Node) = factory(node).wrap(node)
26 | }
27 |
28 | def setupDbConstraints(queryHandler: QueryHandler) = ()
29 |
30 |
31 | object WholeEmpty {
32 | def empty = new WholeEmpty(raw.Graph.empty);
33 | def remove(items: Item*) = {
34 | val wrapper = empty;
35 | wrapper.remove(((items): _*));
36 | wrapper
37 | }
38 | def apply(items: Item*) = {
39 | val wrapper = empty;
40 | wrapper.add(((items): _*));
41 | wrapper
42 | }
43 | };
44 | case class WholeEmpty(graph: raw.Graph) extends Graph {
45 | def nodes: Seq[Node] = Seq.empty;
46 | def relations: (Seq[_$$3] forSome {
47 | type _$$3 <: (Relation[_$$12, _$$11] forSome {
48 | type _$$12;
49 | type _$$11
50 | })
51 | }) = Seq.empty;
52 | def abstractRelations: (Seq[_$$9] forSome {
53 | type _$$9 <: (AbstractRelation[_$$6, _$$10] forSome {
54 | type _$$6;
55 | type _$$10
56 | })
57 | }) = Seq.empty;
58 | def hyperRelations: (Seq[_$$8] forSome {
59 | type _$$8 <: (HyperRelation[_$$5, _$$4, _$$7, _$$2, _$$1] forSome {
60 | type _$$5;
61 | type _$$4;
62 | type _$$7;
63 | type _$$2;
64 | type _$$1
65 | })
66 | }) = Seq.empty
67 | }
68 | } """)
69 | }
70 |
71 | "Schema with uniqueness constraints" >> {
72 | generatedContainsCode(
73 | q"""object A {
74 | @Node trait S { @unique var w: Long; var noW: Long }
75 | @Node trait T { @unique var x: Long = 1; var noX: Long }
76 | @Node class N extends T { @unique val y: Boolean; val noY: Boolean }
77 | @HyperRelation class R(startNode: T, endNode: N) extends T { @unique val z: String = "a"; val noZ: String }
78 | }
79 | """,
80 | q"""def setupDbConstraints(queryHandler: QueryHandler) = {
81 | queryHandler.query("CREATE CONSTRAINT ON (n:N) ASSERT n.y IS UNIQUE");
82 | queryHandler.query("CREATE CONSTRAINT ON (n:S) ASSERT n.w IS UNIQUE");
83 | queryHandler.query("CREATE CONSTRAINT ON (n:T) ASSERT n.x IS UNIQUE");
84 | queryHandler.query("CREATE CONSTRAINT ON (n:R) ASSERT n.z IS UNIQUE")
85 | }
86 | """)
87 | }
88 |
89 | "custom code" >> {
90 | generatedContainsCode(
91 | q"object A {def custom = 0}",
92 | q"""def custom = 0""")
93 | }
94 |
95 | "RootNodeTraitFactory with one Node" >> {
96 | generatedContainsCode(
97 | q"object A {@Node class A}",
98 | q"""val nodeLabelToFactory = Map[Set[raw.Label], NodeFactory[Node]](scala.Tuple2(A.labels, A));""")
99 | }
100 |
101 | "RootNodeTraitFactory with Nodes, Relation and traits" >> {
102 | generatedContainsCode(
103 | q"""object A {
104 | @Node trait X;
105 | @Relation trait Y;
106 | @Node class A extends X; @Node class B extends X;
107 | @Relation class R(startNode:A, endNode:B) extends Y
108 | }""",
109 | q"""val nodeLabelToFactory = Map[Set[raw.Label], NodeFactory[Node]](scala.Tuple2(X.labels, XMatches), scala.Tuple2(A.labels, A), scala.Tuple2(B.labels, B));""")
110 | }
111 |
112 | "RootNodeTraitFactory with HyperNode" >> {
113 | generatedContainsCode(
114 | q"""object A {
115 | @Node trait X;
116 | @Relation trait Y;
117 | @Node class A extends X; @Node class B extends X;
118 | @HyperRelation class R(startNode:A, endNode:B) extends Y
119 | }""",
120 | q"""val nodeLabelToFactory = Map[Set[raw.Label], NodeFactory[Node]](scala.Tuple2(X.labels, XMatches), scala.Tuple2(A.labels, A), scala.Tuple2(B.labels, B), scala.Tuple2(R.labels, R));""")
121 | }
122 |
123 | "One Graph which covers the whole Schema" >> {
124 | generatedContainsCode(
125 | q"""object A {
126 | @Node trait T
127 | @Relation trait R
128 | @Node class N extends T
129 | @Node class M
130 | @HyperRelation class H(startNode:M, endNode:T) extends T
131 | }""",
132 | q"""
133 | object WholeA {
134 | def empty = new WholeA(raw.Graph.empty);
135 | def remove(items: Item*) = {
136 | val wrapper = empty;
137 | wrapper.remove(((items): _*));
138 | wrapper
139 | }
140 | def apply(items: Item*) = {
141 | val wrapper = empty;
142 | wrapper.add(((items): _*));
143 | wrapper
144 | }
145 | };
146 | """,
147 | q"""
148 | case class WholeA(graph: raw.Graph) extends Graph {
149 | def ns: Seq[N] = nodesAs(N);
150 | def ms: Seq[M] = nodesAs(M);
151 | def hs: Seq[H] = hyperRelationsAs(H);
152 | def ts: Seq[T] = Seq.empty.++(ns).++(hs);
153 | def tRelations: (Seq[_$$1] forSome {
154 | type _$$1 <: Relation[T, T]
155 | }) = Seq.empty;
156 | def tAbstractRelations: (Seq[_$$2] forSome {
157 | type _$$2 <: AbstractRelation[T, T]
158 | }) = Seq.empty;
159 | def tHyperRelations: Seq[(HyperRelation[T, _$$3, _$$9, _$$7, T] forSome {
160 | type _$$3 <: (Relation[T, _$$10] forSome {
161 | type _$$10
162 | });
163 | type _$$9 <: (HyperRelation[T, _$$6, _$$5, _$$8, T] forSome {
164 | type _$$6;
165 | type _$$5;
166 | type _$$8
167 | });
168 | type _$$7 <: (Relation[_$$4, T] forSome {
169 | type _$$4
170 | })
171 | })] = Seq.empty;
172 | def nodes: Seq[Node] = Seq.empty.++(ns).++(ms).++(hs);
173 | def relations: (Seq[_$$13] forSome {
174 | type _$$13 <: (Relation[_$$22, _$$21] forSome {
175 | type _$$22;
176 | type _$$21
177 | })
178 | }) = Seq.empty;
179 | def abstractRelations: (Seq[_$$19] forSome {
180 | type _$$19 <: (AbstractRelation[_$$16, _$$20] forSome {
181 | type _$$16;
182 | type _$$20
183 | })
184 | }) = Seq.empty.++(hs);
185 | def hyperRelations: (Seq[_$$18] forSome {
186 | type _$$18 <: (HyperRelation[_$$15, _$$14, _$$17, _$$12, _$$11] forSome {
187 | type _$$15;
188 | type _$$14;
189 | type _$$17;
190 | type _$$12;
191 | type _$$11
192 | })
193 | }) = Seq.empty.++(hs)
194 | }
195 | """)
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/src/main/scala/Patterns.scala:
--------------------------------------------------------------------------------
1 | package renesca.schema.macros
2 |
3 | import scala.PartialFunction._
4 | import scala.language.experimental.macros
5 |
6 | trait PatternTraits extends Context {
7 |
8 | import context.universe._
9 |
10 | implicit def typeNameToString(tn: TypeName): String = tn.toString
11 | implicit def termNameToString(tn: TermName): String = tn.toString
12 | implicit def treeToString(tn: Tree): String = tn.toString
13 | implicit def treeListToStringList(tnl: List[Tree]): List[String] = tnl.map(_.toString)
14 |
15 | trait NamePattern {
16 | def name: String
17 | }
18 |
19 | trait SuperTypesPattern {
20 | def _superTypes: List[String]
21 | def superTypes = _superTypes diff List("scala.AnyRef")
22 | }
23 |
24 | trait StartEndNodePattern {
25 | def startNode: String
26 | def endNode: String
27 | }
28 |
29 | trait StatementsPattern {
30 | def statements: List[Tree]
31 | }
32 | }
33 |
34 | trait Patterns extends Context with PatternTraits {
35 |
36 | import context.universe._
37 |
38 | case class SchemaPattern(name: String, _superTypes: List[String], statements: List[Tree]) extends NamePattern with SuperTypesPattern
39 |
40 | object SchemaPattern {
41 | def unapply(tree: Tree): Option[SchemaPattern] = condOpt(tree) {
42 | case q""" object $name extends ..$superTypes { ..$statements } """ =>
43 | SchemaPattern(name, superTypes, statements)
44 | }
45 | }
46 |
47 | object GraphPattern {
48 | //TODO: statements
49 | //TODO: extract modifier pattern
50 | def unapply(tree: Tree): Option[GraphPattern] = condOpt(tree) {
51 | case q""" $mods trait $name extends ..$superTypes { Nodes(..$graphNodes) }""" if mods.annotations.collectFirst {
52 | case Apply(Select(New(Ident(TypeName("Graph"))), termNames.CONSTRUCTOR), Nil) => true
53 | case _ => false
54 | }.get => GraphPattern(name, superTypes, graphNodes)
55 |
56 | case q""" $mods trait $name extends ..$superTypes""" if mods.annotations.collectFirst {
57 | case Apply(Select(New(Ident(TypeName("Graph"))), termNames.CONSTRUCTOR), Nil) => true
58 | case _ => false
59 | }.get => GraphPattern(name, superTypes, Nil)
60 |
61 | case q""" $mods class $name extends ..$superTypes { ..$statements }""" if mods.annotations.collectFirst {
62 | case Apply(Select(New(Ident(TypeName("Graph"))), termNames.CONSTRUCTOR), Nil) => true
63 | case _ => false
64 | }.get => abort(s"Graph class `$name` is not allowed. Use a trait instead.")
65 |
66 | case q""" $mods object $name extends ..$superTypes { ..$statements }""" if mods.annotations.collectFirst {
67 | case Apply(Select(New(Ident(TypeName("Graph"))), termNames.CONSTRUCTOR), Nil) => true
68 | case _ => false
69 | }.get => abort(s"Graph object `$name` is not allowed. Use a trait instead.")
70 | }
71 | }
72 |
73 | case class GraphPattern(name: String, _superTypes: List[String], nodes: List[String]) extends NamePattern with SuperTypesPattern
74 |
75 | object NodeTraitPattern {
76 | def unapply(tree: Tree): Option[NodeTraitPattern] = condOpt(tree) {
77 | //http://stackoverflow.com/questions/26305528/scala-annotations-are-not-found
78 | case q""" $mods trait $name extends ..$superTypes { ..$statements } """ if mods.annotations.collectFirst {
79 | case Apply(Select(New(Ident(TypeName("Node"))), termNames.CONSTRUCTOR), Nil) => true
80 | case _ => false
81 | }.get =>
82 | NodeTraitPattern(name, superTypes, statements)
83 | }
84 | }
85 |
86 | case class NodeTraitPattern(name: String, _superTypes: List[String], statements: List[Tree]) extends NamePattern with SuperTypesPattern with StatementsPattern
87 |
88 | object RelationTraitPattern {
89 | def unapply(tree: Tree): Option[RelationTraitPattern] = condOpt(tree) {
90 | case q""" $mods trait $name extends ..$superTypes { ..$statements } """ if mods.annotations.collectFirst {
91 | case Apply(Select(New(Ident(TypeName("Relation"))), termNames.CONSTRUCTOR), Nil) => true
92 | case _ => false
93 | }.get =>
94 | RelationTraitPattern(name, superTypes, statements)
95 | }
96 | }
97 |
98 | case class RelationTraitPattern(name: String, _superTypes: List[String], statements: List[Tree]) extends NamePattern with SuperTypesPattern with StatementsPattern
99 |
100 | object NodePattern {
101 | def unapply(tree: Tree): Option[NodePattern] = condOpt(tree) {
102 | case q"""@Node class $name extends ..${superTypes} { ..$statements }""" =>
103 | NodePattern(name, superTypes, statements)
104 | case q"""@Node object $name extends ..${superTypes} { ..$statements }""" =>
105 | abort(s"Node object `$name` is not allowed. Use a class or trait instead.")
106 | }
107 | }
108 |
109 | case class NodePattern(name: String, _superTypes: List[String], statements: List[Tree]) extends NamePattern with SuperTypesPattern with StatementsPattern
110 |
111 | object RelationPattern {
112 | def unapply(tree: Tree): Option[RelationPattern] = condOpt(tree) {
113 | case q"""@Relation class $name (startNode:$startNode, endNode:$endNode) extends ..$superTypes {..$statements}""" =>
114 | RelationPattern(name, startNode, endNode, superTypes, statements)
115 |
116 | case q"""@Relation class $name extends ..$superTypes {..$statements}""" =>
117 | abort(s"Relation class `$name` needs startNode and endNode.")
118 | case q"""@Relation object $name extends ..$superTypes {..$statements}""" =>
119 | abort(s"Relation object `$name` is not allowed. Use a class or trait instead.")
120 | }
121 | }
122 |
123 | case class RelationPattern(name: String, startNode: String, endNode: String, _superTypes: List[String], statements: List[Tree]) extends NamePattern with StartEndNodePattern with SuperTypesPattern with StatementsPattern
124 |
125 | object HyperRelationPattern {
126 | def unapply(tree: Tree): Option[HyperRelationPattern] = condOpt(tree) {
127 | case q"""@HyperRelation class $name (startNode:$startNode, endNode:$endNode) extends ..$superTypes {..$statements}""" =>
128 | HyperRelationPattern(name, startNode, endNode, superTypes, statements)
129 |
130 | case q"""@HyperRelation class $name extends ..$superTypes {..$statements}""" =>
131 | abort(s"HyperRelation class `$name` needs startNode and endNode.")
132 | case q"""@HyperRelation object $name extends ..$superTypes {..$statements}""" =>
133 | abort(s"HyperRelation object `$name` is not allowed. Use a class instead.")
134 | case q"""$mods trait $name extends ..$superTypes {..$statements}""" if mods.annotations.collectFirst {
135 | case Apply(Select(New(Ident(TypeName("HyperRelation"))), termNames.CONSTRUCTOR), Nil) => true
136 | case _ => false
137 | }.get =>
138 | abort(s"HyperRelation trait `$name` is not allowed. Use a class instead.")
139 | }
140 | }
141 |
142 | case class HyperRelationPattern(name: String, startNode: String, endNode: String, _superTypes: List[String], statements: List[Tree]) extends NamePattern with SuperTypesPattern with StartEndNodePattern with StatementsPattern
143 | }
144 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/NodeClassSpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 | class NodeClassSpec extends CodeComparisonSpec {
6 |
7 | import contextMock.universe._
8 |
9 | "simple class" >> {
10 | generatedContainsCode(
11 | q"object A {@Node class N}",
12 | q"""case class N(rawItem: raw.Node) extends Node {
13 | override val label = raw.Label("N")
14 | override val labels = Set(raw.Label("N"))
15 | }"""
16 | )
17 | }
18 |
19 | "preserve custom code" >> {
20 | generatedContainsCode(
21 | q"object A {@Node class N {def custom = 0}}",
22 | q""" case class N(rawItem: raw.Node) extends Node {
23 | override val label = raw.Label("N")
24 | override val labels = Set(raw.Label("N"))
25 | def custom = 0
26 | } """)
27 | }
28 |
29 | "with super types" >> {
30 | generatedContainsCode(
31 | q"object A {@Node trait T; @Node class N extends T}",
32 | q"""case class N(rawItem: raw.Node) extends T {
33 | override val label = raw.Label("N")
34 | override val labels = Set(raw.Label("N"), raw.Label("T"))
35 | }"""
36 | )
37 | }
38 |
39 | "with multiple super types" >> {
40 | generatedContainsCode(
41 | q"object A {@Node trait T; @Node trait S; @Node class N extends T with S}",
42 | q"""case class N(rawItem: raw.Node) extends T with S {
43 | override val label = raw.Label("N")
44 | override val labels = Set(raw.Label("N"), raw.Label("T"), raw.Label("S"))
45 | }
46 | """
47 | )
48 | }
49 |
50 | //TODO: which other supertype constellations can appear?
51 | "with external super types (no nodeTraits)" >> {
52 | generatedContainsCode(
53 | q"object A { @Node trait T; @Node class N extends T with Immutable}",
54 | q"""case class N(rawItem: raw.Node) extends T with Immutable {
55 | override val label = raw.Label("N")
56 | override val labels = Set(raw.Label("N"), raw.Label("T"))
57 | }
58 | """
59 | )
60 | }
61 |
62 | "direct neighbour accessors" >> {
63 | generatedContainsCode(
64 | q"object A {@Node class N; @Node class M; @Relation class R(startNode:N,endNode:M)}",
65 | q"""case class N(rawItem: raw.Node) extends Node {
66 | override val label = raw.Label("N")
67 | override val labels = Set(raw.Label("N"))
68 | def rs: Seq[M] = successorsAs(M, R)
69 | };""",
70 | q"""case class M(rawItem: raw.Node) extends Node {
71 | override val label = raw.Label("M")
72 | override val labels = Set(raw.Label("M"))
73 | def rev_rs: Seq[N] = predecessorsAs(N, R)
74 | };"""
75 | )
76 | }
77 |
78 | "direct neighbour accessors over hyperrelations" >> {
79 | generatedContainsCode(
80 | q"object A {@Node class N; @Node class M; @HyperRelation class R(startNode:N,endNode:M)}",
81 | q"""case class N(rawItem: raw.Node) extends Node {
82 | override val label = raw.Label("N")
83 | override val labels = Set(raw.Label("N"))
84 | def rs: Seq[M] = successorsAs(M, R)
85 | };""",
86 | q"""case class M(rawItem: raw.Node) extends Node {
87 | override val label = raw.Label("M")
88 | override val labels = Set(raw.Label("M"))
89 | def rev_rs: Seq[N] = predecessorsAs(N, R)
90 | };"""
91 | )
92 | }
93 |
94 | "accessors for successor traits" >> {
95 | generatedContainsCode(
96 | q"""object A {@Node trait T;
97 | @Node class N extends T; @Node class M extends T;
98 | @Node class L;
99 | @Relation class R(startNode:L,endNode:T);
100 | }""",
101 | q"""case class L(rawItem: raw.Node) extends Node {
102 | override val label = raw.Label("L")
103 | override val labels = Set(raw.Label("L"))
104 | def rNs: Seq[N] = successorsAs(N, R)
105 | def rMs: Seq[M] = successorsAs(M, R)
106 | def rs: Seq[T] = Seq.empty.++(rNs).++(rMs)
107 | }"""
108 | )
109 | }
110 |
111 | "accessors for super successor traits" >> {
112 | generatedContainsCode(
113 | q"""object A {@Node trait V; @Node trait T extends V;
114 | @Node class N extends T; @Node class M extends T
115 | @Node class L
116 | @Relation class R(startNode:L,endNode:V)
117 | }""",
118 | q"""case class L(rawItem: raw.Node) extends Node {
119 | override val label = raw.Label("L")
120 | override val labels = Set(raw.Label("L"))
121 | def rNs: Seq[N] = successorsAs(N, R)
122 | def rMs: Seq[M] = successorsAs(M, R)
123 | def rs: Seq[V] = Seq.empty.++(rNs).++(rMs)
124 | };"""
125 | )
126 | }
127 |
128 | "accessors for predecessor traits" >> {
129 | generatedContainsCode(
130 | q"""object A {@Node trait T;
131 | @Node class N extends T; @Node class M extends T;
132 | @Node class L;
133 | @Relation class R(startNode:T,endNode:L);
134 | }""",
135 | q"""case class L(rawItem: raw.Node) extends Node {
136 | override val label = raw.Label("L")
137 | override val labels = Set(raw.Label("L"))
138 | def rev_rNs: Seq[N] = predecessorsAs(N, R);
139 | def rev_rMs: Seq[M] = predecessorsAs(M, R);
140 | def rev_rs: Seq[T] = Seq.empty.++(rev_rNs).++(rev_rMs)
141 | };"""
142 | )
143 | }
144 |
145 | "accessors for super predecessor traits" >> {
146 | generatedContainsCode(
147 | q"""object A {@Node trait V; @Node trait T extends V;
148 | @Node class N extends T; @Node class M extends T;
149 | @Node class L;
150 | @Relation class R(startNode:V,endNode:L);
151 | }""",
152 | q"""case class L(rawItem: raw.Node) extends Node {
153 | override val label = raw.Label("L")
154 | override val labels = Set(raw.Label("L"))
155 | def rev_rNs: Seq[N] = predecessorsAs(N, R);
156 | def rev_rMs: Seq[M] = predecessorsAs(M, R);
157 | def rev_rs: Seq[V] = Seq.empty.++(rev_rNs).++(rev_rMs)
158 | };"""
159 | )
160 | }
161 |
162 | "accessors for predecessor and successor traits" >> {
163 | generatedContainsCode(
164 | q"""object A {@Node trait T;
165 | @Node class N extends T; @Node class M extends T;
166 | @Node class L;
167 | @Relation class R(startNode:T,endNode:T);
168 | }""",
169 | q"""case class N(rawItem: raw.Node) extends T {
170 | override val label = raw.Label("N")
171 | override val labels = Set(raw.Label("N"),raw.Label("T"))
172 | def rNs: Seq[N] = successorsAs(N, R);
173 | def rMs: Seq[M] = successorsAs(M, R);
174 | def rs: Seq[T] = Seq.empty.++(rNs).++(rMs);
175 | def rev_rNs: Seq[N] = predecessorsAs(N, R);
176 | def rev_rMs: Seq[M] = predecessorsAs(M, R);
177 | def rev_rs: Seq[T] = Seq.empty.++(rev_rNs).++(rev_rMs)
178 | };"""
179 | )
180 | }
181 |
182 | "accessors for super predecessor and successor traits" >> {
183 | generatedContainsCode(
184 | q"""object A {@Node trait V; @Node trait T extends V;
185 | @Node class N extends T; @Node class M extends T;
186 | @Node class L;
187 | @Relation class R(startNode:V,endNode:V);
188 | }""",
189 | q"""case class N(rawItem: raw.Node) extends T {
190 | override val label = raw.Label("N")
191 | override val labels = Set(raw.Label("N"), raw.Label("V"), raw.Label("T"))
192 | def rNs: Seq[N] = successorsAs(N, R);
193 | def rMs: Seq[M] = successorsAs(M, R);
194 | def rs: Seq[V] = Seq.empty.++(rNs).++(rMs);
195 | def rev_rNs: Seq[N] = predecessorsAs(N, R);
196 | def rev_rMs: Seq[M] = predecessorsAs(M, R);
197 | def rev_rs: Seq[V] = Seq.empty.++(rev_rNs).++(rev_rMs)
198 | };"""
199 | )
200 | }
201 |
202 | "property accessors" >> {
203 | generatedContainsCode(
204 | q"object A {@Node class N {val p:Long}}",
205 | q"""def p: Long = rawItem.properties("p").asInstanceOf[LongPropertyValue]""")
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/NodeFactorySpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 | class NodeFactorySpec extends CodeComparisonSpec {
6 |
7 | import contextMock.universe._
8 |
9 | "simple node factory" >> {
10 | generatedContainsCode(
11 | q"object A {@Node class N}",
12 | q"""object N extends NodeFactory[N] {
13 | val label = raw.Label("N");
14 | val labels = Set(raw.Label("N"));
15 | def wrap(node: raw.Node) = new N(node);
16 | def matches(matches: Set[PropertyKey] = Set.empty): N = {
17 | val wrapped = wrap(raw.Node.matches(labels, matches = matches));
18 | wrapped
19 | }
20 | def create(): N = {
21 | val wrapped = wrap(raw.Node.create(labels));
22 | wrapped
23 | }
24 | def merge(merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): N = {
25 | val wrapped = wrap(raw.Node.merge(labels, merge = merge, onMatch = onMatch));
26 | wrapped
27 | }
28 | }"""
29 | )
30 | }
31 |
32 | "with super factory" >> {
33 | generatedContainsCode(
34 | q"object A {@Node trait T; @Node class N extends T}",
35 | """object N extends TFactory[N] {""",
36 | q"""val label = raw.Label("N")""",
37 | q"""val labels = Set(raw.Label("N"), raw.Label("T"))""",
38 | q"""def createT(): N = this.create()"""
39 | )
40 | }
41 |
42 | "with super factory with external superType" >> {
43 | generatedContainsCode(
44 | q"object A {@Node trait T; @Node class N extends T with Immutable}",
45 | """object N extends TFactory[N] {""",
46 | q"""val label = raw.Label("N")""",
47 | q"""val labels = Set(raw.Label("N"), raw.Label("T"))""",
48 | q"""def createT(): N = this.create()"""
49 | )
50 | }
51 |
52 | "with multiple super factories" >> {
53 | generatedContainsCode(
54 | q"object A {@Node trait T; @Node trait S; @Node class N extends T with S}",
55 | """object N extends TFactory[N] with SFactory[N] {""",
56 | q"""val label = raw.Label("N")""",
57 | q"""val labels = Set(raw.Label("N"), raw.Label("T"), raw.Label("S"))""",
58 | q"""def createT(): N = this.create()""",
59 | q"""def createS(): N = this.create()"""
60 | )
61 | }
62 |
63 | "with multiple super factories (chain)" >> {
64 | generatedContainsCode(
65 | q"object A {@Node trait T; @Node trait S extends T; @Node class N extends S}",
66 | """object N extends SFactory[N] {""",
67 | q"""val label = raw.Label("N")""",
68 | q"""val labels = Set(raw.Label("N"), raw.Label("T"), raw.Label("S"))""",
69 | q"""def createS(): N = this.create()"""
70 | )
71 | }
72 |
73 | "with properties" >> {
74 | generatedContainsCode(
75 | q"object A {@Node class N {val p:String; var x:Long}}",
76 | q"""def create(p: String, x: Long): N = {
77 | val wrapped = wrap(raw.Node.create(labels));
78 | wrapped.rawItem.properties.update("p", p);
79 | wrapped.rawItem.properties.update("x", x);
80 | wrapped
81 | } """
82 | )
83 | }
84 |
85 | "with properties - parameter order of create" >> {
86 | generatedContainsCode(
87 | q"""object A {
88 | @Node class N {
89 | var y:Option[Boolean]
90 | val q:Option[Double]
91 | var x:Long
92 | val p:String
93 | }
94 | }""",
95 | q"""def create(p: String, x: Long, q: Option[Double] = None, y: Option[Boolean] = None): N""",
96 | q"""def merge(p: String, x: Long, q: Option[Double] = None, y: Option[Boolean] = None, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): N""",
97 | q"""def matches(p: Option[String] = None, q: Option[Double] = None, x: Option[Long] = None, y: Option[Boolean] = None, matches: Set[PropertyKey] = Set.empty): N"""
98 | )
99 | }
100 |
101 | "with inherited properties" >> {
102 | generatedContainsCode(
103 | q"object A {@Node trait T {val p:String; var x:Long}; @Node class N extends T}",
104 | q"""def create(p: String, x: Long): N = {
105 | val wrapped = wrap(raw.Node.create(labels));
106 | wrapped.rawItem.properties.update("p", p);
107 | wrapped.rawItem.properties.update("x", x);
108 | wrapped
109 | }""",
110 | q""" def createT(p: String, x: Long): N = this.create(p, x) """
111 | )
112 | }
113 |
114 | "with inherited properties by two traits" >> {
115 | generatedContainsCode(
116 | q"object A {@Node trait T {val p:String }; @Node trait S {var x:Long}; @Node class N extends T with S}",
117 | q"""def create(p: String, x: Long): N = {
118 | val wrapped = wrap(raw.Node.create(labels));
119 | wrapped.rawItem.properties.update("p", p);
120 | wrapped.rawItem.properties.update("x", x);
121 | wrapped
122 | }"""
123 | )
124 | }
125 |
126 | "with indirectly inherited properties" >> {
127 | generatedContainsCode(
128 | q"object A {@Node trait T {val p:String; var x:Long}; @Node trait X extends T; @Node class N extends X}",
129 | q""" def createX(p: String, x: Long): N = this.create(p, x) """,
130 | q""" def createT(p: String, x: Long): N = this.create(p, x) """
131 | )
132 | }
133 |
134 | "with indirectly inherited properties and default properties" >> {
135 | generatedContainsCode(
136 | q"object A {@Node trait T {val p:String; var x:Long}; @Node trait X extends T { val q: Boolean = true }; @Node class N extends X}",
137 | q""" def createX(p: String, x: Long, q: Boolean = true): N = this.create(p, x, q) """,
138 | q""" def createT(p: String, x: Long): N = this.create(p, x, true) """
139 | )
140 | }
141 |
142 | "with indirectly inherited properties and optional properties" >> {
143 | generatedContainsCode(
144 | q"object A {@Node trait T {val p:String; var x:Long}; @Node trait X extends T { val q: Option[Boolean] }; @Node class N extends X}",
145 | q""" def createX(p: String, x: Long, q: Option[Boolean] = None): N = this.create(p, x, q) """,
146 | q""" def createT(p: String, x: Long): N = this.create(p, x, None) """
147 | )
148 | }
149 |
150 | "with indirectly inherited properties and optional default properties" >> {
151 | generatedContainsCode(
152 | q"object A {@Node trait T {val p:String; var x:Long}; @Node trait X extends T { val q: Option[Boolean] = Some(true) }; @Node class N extends X}",
153 | q""" def createX(p: String, x: Long, q: Option[Boolean] = Some(true)): N = this.create(p, x, q) """,
154 | q""" def createT(p: String, x: Long): N = this.create(p, x, Some(true)) """
155 | )
156 | }
157 |
158 | "with indirectly inherited properties and default properties (var)" >> {
159 | generatedContainsCode(
160 | q"object A {@Node trait T {val p:String; var x:Long}; @Node trait X extends T { var q: Boolean = true }; @Node class N extends X}",
161 | q""" def createX(p: String, x: Long, q: Boolean = true): N = this.create(p, x, q) """,
162 | q""" def createT(p: String, x: Long): N = this.create(p, x, true) """
163 | )
164 | }
165 |
166 | "with indirectly inherited properties and optional properties (var)" >> {
167 | generatedContainsCode(
168 | q"object A {@Node trait T {val p:String; var x:Long}; @Node trait X extends T { var q: Option[Boolean] = Some(true) }; @Node class N extends X}",
169 | q""" def createX(p: String, x: Long, q: Option[Boolean] = Some(true)): N = this.create(p, x, q) """,
170 | q""" def createT(p: String, x: Long): N = this.create(p, x, Some(true)) """
171 | )
172 | }
173 |
174 | "with indirectly inherited properties by two traits" >> {
175 | generatedContainsCode(
176 | q"object A {@Node trait T {val p:String }; @Node trait S {var x:Long}; @Node trait X extends T with S; @Node class N extends X}",
177 | q""" def createX(p: String, x: Long): N = this.create(p, x) """,
178 | Not("def createS("),
179 | Not("def createT(")
180 | )
181 | }
182 |
183 | "with unique matches factory methods in node" >> {
184 | generatedContainsCode(
185 | q"object A {@Node trait T {@unique val p:String}; @Node class N extends T {@unique var q:Boolean }}",
186 | q"""def matchesT(p: Option[String] = None, matches: Set[PropertyKey] = Set.empty): NODE""",
187 | q"""def matchesOnP(p: String): NODE = this.matchesT(p = Some(p), matches = Set("p"))""",
188 | q"""def matches(p: Option[String] = None, q: Option[Boolean] = None, matches: Set[PropertyKey] = Set.empty): N""",
189 | q"""def matchesOnQ(q: Boolean): N = this.matches(q = Some(q), matches = Set("q"))"""
190 | )
191 | }
192 |
193 | "diamond inheritance" >> {
194 | generatedContainsCode(
195 | q"object A {@Node trait T {val p:String }; @Node trait L extends T; @Node trait R extends T; @Node trait X extends L with R; @Node class N extends X}",
196 | q""" def create(p: String): N"""
197 | )
198 | }
199 | // TODO one direct + one indirect
200 | }
201 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/HyperRelationFactorySpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 | class HyperRelationFactorySpec extends CodeComparisonSpec {
6 |
7 | import contextMock.universe._
8 |
9 | "simple hyperrelation factory" >> {
10 | generatedContainsCode(
11 | // TODO: fail with compile error when start or endNode does not exist
12 | q"object A {@Node class A; @Node class B; @HyperRelation class R(startNode:A, endNode:B)}",
13 | q"""object R extends HyperRelationFactory[A, RStart, R, REnd, B] {
14 | override val label = raw.Label("R");
15 | override val labels = Set(raw.Label("R"))
16 | override val startRelationType = raw.RelationType("RSTART");
17 | override val endRelationType = raw.RelationType("REND");
18 | override def wrap(node: raw.Node) = new R(node);
19 | override def wrap(startRelation: raw.Relation, middleNode: raw.Node, endRelation: raw.Relation) = {
20 | val hyperRelation = wrap(middleNode);
21 | hyperRelation._startRelation = RStart(A.wrap(startRelation.startNode), startRelation, hyperRelation);
22 | hyperRelation._endRelation = REnd(hyperRelation, endRelation, B.wrap(endRelation.endNode));
23 | hyperRelation
24 | };
25 | def create(startNode: A, endNode: B): R = {
26 | val middleNode = raw.Node.create(labels);
27 | wrap(raw.Relation.create(startNode.rawItem, startRelationType, middleNode), middleNode, raw.Relation.create(middleNode, endRelationType, endNode.rawItem))
28 | }
29 | def merge(startNode: A, endNode: B, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): R = {
30 | val middleNode = raw.Node.merge(labels, merge = merge, onMatch = onMatch);
31 | wrap(raw.Relation.merge(startNode.rawItem, startRelationType, middleNode), middleNode, raw.Relation.merge(middleNode, endRelationType, endNode.rawItem))
32 | }
33 | def matches(startNode: A, endNode: B, matches: Set[PropertyKey] = Set.empty): R = {
34 | val middleNode = raw.Node.matches(labels, matches = matches);
35 | wrap(raw.Relation.matches(startNode.rawItem, startRelationType, middleNode), middleNode, raw.Relation.matches(middleNode, endRelationType, endNode.rawItem))
36 | }
37 | def matchesNode(matches: Set[PropertyKey] = Set.empty): R = {
38 | val middleNode = raw.Node.matches(labels, matches = matches);
39 | wrap(middleNode)
40 | }
41 | } """,
42 | q"""object RStart extends RelationFactory[A, RStart, R] {
43 | val relationType = raw.RelationType("RSTART");
44 | def wrap(relation: raw.Relation) = RStart(A.wrap(relation.startNode), relation, R.wrap(relation.endNode));
45 | } """,
46 | q"""object REnd extends RelationFactory[R, REnd, B] {
47 | val relationType = raw.RelationType("REND");
48 | def wrap(relation: raw.Relation) = REnd(R.wrap(relation.startNode), relation, B.wrap(relation.endNode));
49 | } """
50 | )
51 | }
52 |
53 | "with node super factory" >> {
54 | generatedContainsCode(
55 | q"object A {@Node class A; @Node class B; @Node trait T; @HyperRelation class R(startNode:A, endNode:B) extends T}",
56 | """object R extends HyperRelationFactory[A, RStart, R, REnd, B] with TFactory[R] {"""
57 | )
58 | }
59 |
60 | "with relation super factory" >> {
61 | generatedContainsCode(
62 | q"object A {@Node class A; @Node class B; @Relation trait T; @HyperRelation class R(startNode:A, endNode:B) extends T}",
63 | """object R extends HyperRelationFactory[A, RStart, R, REnd, B] with TFactory[A, R, B] {""",
64 | q"""def createT(startNode: A, endNode: B): R = this.create(startNode, endNode)""",
65 | q"""def mergeT(startNode: A, endNode: B, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): R = this.merge(startNode, endNode, merge, onMatch)""",
66 | q"""def matchesT(startNode: A, endNode: B, matches: Set[PropertyKey] = Set.empty): R = this.matches(startNode, endNode, matches)"""
67 | )
68 | }
69 |
70 | "with node super factory and external supertype" >> {
71 | generatedContainsCode(
72 | q"object A {@Node class A; @Node class B; @Node trait T; @HyperRelation class R(startNode:A, endNode:B) extends T with Immutable}",
73 | """object R extends HyperRelationFactory[A, RStart, R, REnd, B] with TFactory[R] {"""
74 | )
75 | }
76 |
77 | "with relation super factory and external supertype" >> {
78 | generatedContainsCode(
79 | q"object A {@Node class A; @Node class B; @Relation trait T; @HyperRelation class R(startNode:A, endNode:B) extends T with Immutable}",
80 | """object R extends HyperRelationFactory[A, RStart, R, REnd, B] with TFactory[A, R, B] {""",
81 | q"""def createT(startNode: A, endNode: B): R = this.create(startNode, endNode)""",
82 | q"""def mergeT(startNode: A, endNode: B, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): R = this.merge(startNode, endNode, merge, onMatch)""",
83 | q"""def matchesT(startNode: A, endNode: B, matches: Set[PropertyKey] = Set.empty): R = this.matches(startNode, endNode, matches)"""
84 | )
85 | }
86 |
87 | "with properties" >> {
88 | generatedContainsCode(
89 | q"object A {@Node class A; @Node class B; @HyperRelation class R(startNode:A, endNode:B) {val p:String; var x:Long}}",
90 | q"""def create(startNode: A, endNode: B, p: String, x: Long): R = {
91 | val middleNode = raw.Node.create(labels);
92 | middleNode.properties.update("p", p);
93 | middleNode.properties.update("x", x);
94 | wrap(raw.Relation.create(startNode.rawItem, startRelationType, middleNode), middleNode, raw.Relation.create(middleNode, endRelationType, endNode.rawItem))
95 | }""",
96 | q"""def merge(startNode: A, endNode: B, p: String, x: Long, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): R = {
97 | val middleNode = raw.Node.merge(labels, merge = merge, onMatch = onMatch);
98 | middleNode.properties.update("p", p);
99 | middleNode.properties.update("x", x);
100 | wrap(raw.Relation.merge(startNode.rawItem, startRelationType, middleNode), middleNode, raw.Relation.merge(middleNode, endRelationType, endNode.rawItem))
101 | }""",
102 | q"""def matches(startNode: A, endNode: B, p: Option[String] = None, x: Option[Long] = None, matches: Set[PropertyKey] = Set.empty): R = {
103 | val middleNode = raw.Node.matches(labels, matches = matches);
104 | if (p.isDefined)
105 | middleNode.properties.update("p", p.get)
106 | else
107 | ();
108 | if (x.isDefined)
109 | middleNode.properties.update("x", x.get)
110 | else
111 | ();
112 | wrap(raw.Relation.matches(startNode.rawItem, startRelationType, middleNode), middleNode, raw.Relation.matches(middleNode, endRelationType, endNode.rawItem))
113 | }"""
114 | )
115 | }
116 |
117 | //TODO abort with error when inheriting from myself. Right now this produces a stack overflow error
118 | "with inherited properties" >> {
119 | generatedContainsCode(
120 | q"object A {@Node class A; @Node class B; @Relation trait T {val p:String; var x:Long}; @HyperRelation class R(startNode:A, endNode:B) extends T}",
121 | q"""def create(startNode: A, endNode: B, p: String, x: Long): R = {
122 | val middleNode = raw.Node.create(labels);
123 | middleNode.properties.update("p", p);
124 | middleNode.properties.update("x", x);
125 | wrap(raw.Relation.create(startNode.rawItem, startRelationType, middleNode), middleNode, raw.Relation.create(middleNode, endRelationType, endNode.rawItem))
126 | }""",
127 | q"""def createT(startNode: A, endNode: B, p: String, x: Long): R = this.create(startNode, endNode, p, x)""",
128 | q"""def mergeT(startNode: A, endNode: B, p: String, x: Long, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): R = this.merge(startNode, endNode, p, x, merge, onMatch)""",
129 | q"""def matchesT(startNode: A, endNode: B, p: Option[String] = None, x: Option[Long] = None, matches: Set[PropertyKey] = Set.empty): R = this.matches(startNode, endNode, p, x, matches)"""
130 | )
131 | }
132 |
133 | "with indirectly inherited properties" >> {
134 | generatedContainsCode(
135 | q"object A {@Node class A; @Node class B; @Relation trait T {val p:String; var x:Long}; @Relation trait X extends T; @HyperRelation class R(startNode:A, endNode:B) extends X}",
136 | q"""def create(startNode: A, endNode: B, p: String, x: Long): R = {
137 | val middleNode = raw.Node.create(labels);
138 | middleNode.properties.update("p", p);
139 | middleNode.properties.update("x", x);
140 | wrap(raw.Relation.create(startNode.rawItem, startRelationType, middleNode), middleNode, raw.Relation.create(middleNode, endRelationType, endNode.rawItem))
141 | }""",
142 | q"""def createX(startNode: A, endNode: B, p: String, x: Long): R = this.create(startNode, endNode, p, x)""",
143 | q"""def mergeX(startNode: A, endNode: B, p: String, x: Long, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): R = this.merge(startNode, endNode, p, x, merge, onMatch)""",
144 | q"""def matchesX(startNode: A, endNode: B, p: Option[String] = None, x: Option[Long] = None, matches: Set[PropertyKey] = Set.empty): R = this.matches(startNode, endNode, p, x, matches)"""
145 | )
146 | }
147 |
148 | "with unique matches factory methods in hyperrelation" >> {
149 | generatedContainsCode(
150 | q"object A {@Node class A; @Node class B; @Node trait T {@unique val p:String}; @HyperRelation class R(startNode: T, endNode: T) extends T {@unique var q:Boolean }}",
151 | q"""def matchesT(p: Option[String] = None, matches: Set[PropertyKey] = Set.empty): NODE""",
152 | q"""def matchesOnP(p: String): NODE = this.matchesT(p = Some(p), matches = Set("p"))""",
153 | q"""def matchesNode(p: Option[String] = None, q: Option[Boolean] = None, matches: Set[PropertyKey] = Set.empty): R""",
154 | q"""def matchesOnQ(q: Boolean): R = this.matchesNode(q = Some(q), matches = Set("q"))"""
155 | )
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # renesca-magic
2 | [](https://travis-ci.org/renesca/renesca-magic)
3 | [](https://coveralls.io/r/renesca/renesca-magic?branch=master)
4 |
5 | renesca-magic is an abstraction layer for [renesca](https://github.com/renesca/renesca), which generates typesafe graph database schemas for the Neo4j database using scala macros.
6 |
7 | ## Academic Article
8 | There is also an academic article about renesca and renesca-magic: [An Open-Source Object-Graph-Mapping Framework for Neo4j and Scala: Renesca](https://link.springer.com/chapter/10.1007/978-3-319-45507-5_14). Feel free to use these libraries in any of your projects. If you use them in an academic setting, we appreciate if you cite this article.
9 |
10 |
11 | > Dietze, F., Karoff, J., Calero Valdez, A. , Ziefle, M., Greven, C., & Schroeder, U. (2016, August).
12 | > An Open-Source Object-Graph-Mapping Framework for Neo4j and Scala: Renesca.
13 | > *In International Conference on Availability, Reliability, and Security* (pp. 204-218). Springer International Publishing.
14 |
15 |
16 | ## Feature summary
17 | * Generate boilerplate classes and factories to wrap Nodes, Relations and Graphs
18 | * Generate boilerplate for handling HyperRelations ```(n)-[]->(hyperRelation)-[]->(m)```
19 | * Generate getters, setters and factories for properties (primitives, optional primitives, default values)
20 | * Generate accessors for neighbours on Nodes, over Relations and HyperRelations
21 | * Node and Relation traits with multiple inheritance for labels and properties
22 | * Generate filtered set accessors for Nodes, Relations and traits in a Graph
23 | * Graph can inherit Nodes from multiple other Graphs
24 | * View generated code in ```/magic``` of your sbt project root. (you should add it to your ```.gitignore```)
25 |
26 | ## Installation
27 |
28 | To use renesca-magic in your sbt project, add these dependencies and the marco compiler plugin to your ```build.sbt```:
29 |
30 | ```scala
31 | libraryDependencies += "com.github.renesca" %% "renesca-magic" % "0.3.4-1"
32 |
33 | addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
34 | ```
35 |
36 | ## Feedback
37 | Please don't hesitate to create issues about anything. Ideas, questions, bugs, feature requests, criticism, missing documentation, confusing examples, ... . Are you stuck with renesca or renesca-magic for some time? Is there something in this README that is unclear? Anything else? This means something does not work as intended or the API is not intuitive. Contact us and let's fix this together.
38 |
39 | ## Usage Examples
40 | You can find all of these examples available as sbt project: [renesca/renesca-magic-example](https://github.com/renesca/renesca-magic-example). You can also have a look at the generated code: [renesca-magic-example/magic](https://github.com/renesca/renesca-magic-example/tree/master/magic)
41 |
42 | ### Wrapping low level entities
43 | In [renesca](https://github.com/renesca/renesca), nodes represent low level graph database entities from the property graph model. This is a bit annoying to work with when you have a schema in mind. For example, when we have types of nodes that have a specific label, we always access neighbours with another specific label. The same holds for properties for specific labels.
44 |
45 | We can wrap our low-level nodes and relations in classes that take care of and hide the boilerplate. In the following example we provide some hand-written boilerplate to demonstrate this.
46 |
47 | Here we have two nodes ```Animal``` and ```Food```, connected with the relation ```Eats```. The nodes have getters and setters for their properties and accessors to their neighbours. There are also Factories to wrap low level entities or create new ones.
48 |
49 | ```scala
50 | import renesca.graph._
51 | import renesca.parameter._
52 | import renesca.parameter.implicits._
53 |
54 | case class Animal(node: Node) {
55 | val label = Label("ANIMAL")
56 | def eats: Set[Food] = node.outRelations.
57 | filter(_.relationType == Eats.relationType).map(_.endNode).
58 | filter(_.labels.contains(Food.label)).map(Food.wrap).toSet
59 | def name: String = node.properties("name").asInstanceOf[StringPropertyValue]
60 | }
61 |
62 | object Animal {
63 | val label = Label("ANIMAL")
64 | def wrap(node: Node) = new Animal(node)
65 | def create(name: String): Animal = {
66 | val wrapped = wrap(Node.create(List(label)))
67 | wrapped.node.properties.update("name", name)
68 | wrapped
69 | }
70 | }
71 |
72 | ...
73 | ```
74 | See the full boilerplate example at [renesca-example/.../Schema.scala](https://github.com/renesca/renesca-example/blob/master/src/main/scala/renesca/example/Schema.scala).
75 |
76 | This is a lot of code for a single relation between two nodes. Writing this by hand for a larger schema takes a lot of time and is very error prone. We can use renesca-magic to generate this for us. Simply write:
77 |
78 | ```scala
79 | import renesca.schema.macros
80 |
81 | @macros.GraphSchema
82 | object ExampleSchemaWrapping {
83 | // Nodes get their class name as uppercase label
84 | @Node class Animal { val name: String }
85 | @Node class Food {
86 | val name: String
87 | var amount: Long
88 | }
89 | // Relations get their class name as uppercase relationType
90 | @Relation class Eats(startNode: Animal, endNode: Food)
91 | }
92 |
93 | import ExampleSchemaWrapping._
94 |
95 | val snake = Animal.create("snake")
96 | val cake = Food.create(name = "cake", amount = 1000)
97 | val eats = Eats.create(snake, cake)
98 |
99 | cake.amount -= 100
100 | ```
101 |
102 | Note that ```Food.name``` is a ```val``` and only generates a getter. ```Food.amount``` is a ```var``` and therefore generates a getter and a setter.
103 |
104 | You can have a look at the generated code in the folder ```/magic``` created at the root of your sbt project. Files created in ```/magic``` are not needed for compilation. You can safely delete them and put the folder into your ```.gitignore```.
105 |
106 | ### Wrapping induced subgraphs
107 | Use the ```@Graph``` annotation to wrap a subgraph. This generates filtered Set accessors for each Node and Relation type.
108 |
109 | ```scala
110 | import renesca.schema.macros
111 |
112 | @macros.GraphSchema
113 | object ExampleSchemaSubgraph {
114 | @Node class Animal { val name: String }
115 | @Node class Food {
116 | val name: String
117 | var amount: Long
118 | }
119 | @Relation class Eats(startNode: Animal, endNode: Food)
120 |
121 | // Relations between specified nodes will be induced
122 | @Graph trait Zoo { Nodes(Animal, Food) }
123 | }
124 |
125 | import ExampleSchemaSubgraph._
126 |
127 | val zoo = Zoo(db.queryGraph("MATCH (a:ANIMAL)-[e:EATS]->(f:FOOD) RETURN a,e,f"))
128 | val elefant = Animal.create("elefant")
129 | val pizza = Food.create(name = "pizza", amount = 2)
130 | zoo.add(Eats.create(elefant, pizza))
131 | zoo.animals // Set(elefant)
132 | zoo.relations // Set(elefant eats pizza)
133 | db.persistChanges(zoo)
134 | ```
135 |
136 | ### Traits and relations to traits
137 | ```scala
138 | @macros.GraphSchema
139 | object ExampleSchemaTraits {
140 | // Inheriting Nodes receive their name as additional label
141 | @Node trait Animal { val name: String }
142 |
143 | // Node with labels FISH and ANIMAL
144 | @Node class Fish extends Animal
145 | @Node class Dog extends Animal
146 |
147 | @Relation trait Consumes { val funny:Boolean }
148 |
149 | // Relations can connect Node traits
150 | // instead of defining relations for Fish and Dog explicitly
151 | @Relation class Eats(startNode: Animal, endNode: Animal) extends Consumes
152 | @Relation class Drinks(startNode: Animal, endNode: Animal) extends Consumes
153 |
154 | // Zoo contains all Animals (Animal expands to all subNodes)
155 | @Graph trait Zoo { Nodes(Animal) }
156 | }
157 |
158 | import ExampleSchemaTraits._
159 |
160 | val zoo = Zoo.empty
161 | // merge dog and fish on the name property
162 | // (creates the animal if it does not exist, otherwise the existing animal is matched)
163 | val bello = Dog.merge(name = "bello", merge = Set("name"))
164 | val wanda = Fish.merge(name = "wanda", merge = Set("name"))
165 |
166 | zoo.add(bello)
167 | zoo.add(wanda)
168 | zoo.animals // Set(bello, wanda)
169 |
170 | // We can connect any node extending the trait Animal
171 | zoo.add(Eats.create(bello, wanda, funny = false))
172 | zoo.add(Drinks.create(wanda, bello, funny = true))
173 | ```
174 |
175 | ### Multiple inheritance, multiple Node labels
176 | ```scala
177 | @macros.GraphSchema
178 | object ExampleSchemaMultipleInheritance {
179 | // Assignments are default values for properties
180 | // They can also be arbitrary statements
181 | @Node trait Uuid { val uuid: String = java.util.UUID.randomUUID.toString }
182 | @Node trait Timestamp { val timestamp: Long = System.currentTimeMillis }
183 | @Node trait Taggable
184 |
185 | @Node class Article extends Uuid with Timestamp with Taggable {
186 | val content:String
187 | }
188 | @Node class Tag extends Uuid { val name:String }
189 | @Relation class Categorizes(startNode:Tag, endNode:Taggable)
190 |
191 | @Graph trait Blog {Nodes(Article, Tag)}
192 | }
193 |
194 | import ExampleSchemaMultipleInheritance._
195 |
196 | val initGraph = Blog.empty
197 | initGraph.add(Tag.create(name = "useful"))
198 | initGraph.add(Tag.create(name = "important"))
199 | db.persistChanges(initGraph)
200 |
201 | val blog = Blog.empty
202 |
203 | // match the previously created tags
204 | blog.add(Tag.matches(name = Some("useful"), matches = Set("name")))
205 | blog.add(Tag.matches(name = Some("important"), matches = Set("name")))
206 |
207 | // automatically set uuid and timestamp
208 | val article = Article.create(content = "Some useful and important content")
209 | blog.add(article)
210 |
211 | // set all tags on the article
212 | blog.tags.foreach{ tag =>
213 | blog.add(Categorizes.create(tag, article))
214 | }
215 |
216 | blog.taggables // Set(article)
217 | article.rev_categorizes // blog.tags
218 |
219 | db.persistChanges(blog)
220 | ```
221 |
222 | ### HyperRelations
223 | ```scala
224 | @macros.GraphSchema
225 | object ExampleSchemaHyperRelations {
226 | @Node trait Uuid { val uuid: String = java.util.UUID.randomUUID.toString }
227 | @Node trait Taggable
228 | @Node class Tag extends Uuid { val name:String }
229 | @Node class User extends Uuid { val name:String }
230 | @Node class Article extends Uuid with Taggable { val content:String }
231 |
232 | // A HyperRelation is a node representing a relation:
233 | // (n)-[]->(hyperRelation)-[]->(m)
234 | // It behaves like node and relation at the same time
235 | // and therefore can extend node and relation traits
236 | @HyperRelation class Tags(startNode: Tag, endNode: Taggable) extends Uuid
237 | // Because these are nodes, we can connect a HyperRelation with another Node
238 | @Relation class Supports(startNode: User, endNode: Tags)
239 | }
240 |
241 | import ExampleSchemaHyperRelations._
242 | val user = User.create(name="pumuckl")
243 | val helpful = Tag.create(name="helpful")
244 | val article = Article.create(content="Dog eats Snake")
245 |
246 | val tags = Tags.create(helpful, article) // HyperRelation
247 | val supports = Supports.create(user, tags) // Relation from user to HyperRelation
248 | ```
249 |
250 | ## License
251 | renesca-magic is free software released under the [Apache License, Version 2.0][Apache]
252 |
253 | [Apache]: http://www.apache.org/licenses/LICENSE-2.0
254 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/src/test/scala/errors/ErrorSpec.scala:
--------------------------------------------------------------------------------
1 | package errors
2 |
3 | import helpers.CodeComparisonSpec
4 | import org.mockito.Mockito.reset
5 |
6 | class ErrorSpec extends CodeComparisonSpec {
7 |
8 | import contextMock.universe._
9 |
10 | import scala.util.Try
11 |
12 | def generatedAborts(source: Tree, msg: String) = {
13 | reset(magic.aborter)
14 | Try { generate(source) }
15 | there was one(magic.aborter).abort(msg)
16 | }
17 |
18 | "Invalid inheritance" >> {
19 | "Node" >> {
20 | "class" >> {
21 | "inherits from" >> {
22 | "Node" >> {
23 | "class" >> {
24 | generatedAborts(q"object A {@Node class T; @Node class N extends T}",
25 | "Node class `N` cannot inherit from Node class `T`.")
26 | }
27 | }
28 | "Relation" >> {
29 | "class" >> {
30 | generatedAborts(q"object A {@Relation class T(startNode:A, endNode:B); @Node class N extends T}",
31 | "Node class `N` cannot inherit from Relation class `T`.")
32 | }
33 | "trait" >> {
34 | generatedAborts(q"object A {@Relation trait T; @Node class N extends T}",
35 | "Node class `N` cannot inherit from Relation trait `T`.")
36 | }
37 | }
38 | "Graph trait" >> {
39 | generatedAborts(q"object A {@Graph trait T; @Node class N extends T}",
40 | "Node class `N` cannot inherit from Graph trait `T`.")
41 | }
42 | }
43 |
44 | }
45 | "trait" >> {
46 | "inherits from" >> {
47 | "Node" >> {
48 | "class" >> {
49 | generatedAborts(q"object A {@Node class T; @Node trait N extends T}",
50 | "Node trait `N` cannot inherit from Node class `T`.")
51 | }
52 | }
53 | "Relation" >> {
54 | "class" >> {
55 | generatedAborts(q"object A {@Relation class T(startNode:A, endNode:B); @Node trait N extends T}",
56 | "Node trait `N` cannot inherit from Relation class `T`.")
57 | }
58 | "trait" >> {
59 | generatedAborts(q"object A {@Relation trait T; @Node trait N extends T}",
60 | "Node trait `N` cannot inherit from Relation trait `T`.")
61 | }
62 | }
63 | "Graph trait" >> {
64 | generatedAborts(q"object A {@Graph trait T; @Node trait N extends T}",
65 | "Node trait `N` cannot inherit from Graph trait `T`.")
66 | }
67 | "Itself" >> todo
68 | }
69 | }
70 | }
71 | "Relation" >> {
72 | "class" >> {
73 | "inherits from" >> {
74 | "Node" >> {
75 | "class" >> {
76 | generatedAborts(q"object A {@Node class T; @Relation class R(startNode:A, endNode:B) extends T}",
77 | "Relation class `R` cannot inherit from Node class `T`.")
78 | }
79 | "trait" >> {
80 | generatedAborts(q"object A {@Node trait T; @Relation class R(startNode:A, endNode:B) extends T}",
81 | "Relation class `R` cannot inherit from Node trait `T`.")
82 | }
83 | }
84 | "Relation" >> {
85 | "class" >> {
86 | generatedAborts(q"object A {@Relation class T(startNode:A, endNode:B); @Relation class N(startNode:A, endNode:B) extends T}",
87 | "Relation class `N` cannot inherit from Relation class `T`.")
88 | }
89 | }
90 | "Graph trait" >> {
91 | generatedAborts(q"object A {@Graph trait T; @Relation class N(startNode:A, endNode:B) extends T}",
92 | "Relation class `N` cannot inherit from Graph trait `T`.")
93 | }
94 | }
95 |
96 | }
97 | "trait" >> {
98 | "inherits from" >> {
99 | "Node" >> {
100 | "class" >> {
101 | generatedAborts(q"object A {@Node class T; @Relation trait R extends T}",
102 | "Relation trait `R` cannot inherit from Node class `T`.")
103 | }
104 | "trait" >> {
105 | generatedAborts(q"object A {@Node trait T; @Relation trait R extends T}",
106 | "Relation trait `R` cannot inherit from Node trait `T`.")
107 | }
108 | }
109 | "Relation" >> {
110 | "class" >> {
111 | generatedAborts(q"object A {@Relation class T(startNode:A, endNode:B); @Relation trait R extends T}",
112 | "Relation trait `R` cannot inherit from Relation class `T`.")
113 | }
114 | }
115 | "Graph trait" >> {
116 | generatedAborts(q"object A {@Graph trait T; @Relation trait N extends T}",
117 | "Relation trait `N` cannot inherit from Graph trait `T`.")
118 | }
119 | "Itself" >> todo
120 | }
121 | }
122 | }
123 | "HyperRelation" >> {
124 | "class" >> {
125 | "inherits from" >> {
126 | "Node class" >> {
127 | generatedAborts(q"object A {@Node class T; @HyperRelation class R(startNode:A, endNode:B) extends T}",
128 | "HyperRelation class `R` cannot inherit from Node class `T`.")
129 | }
130 | "Relation class" >> {
131 | generatedAborts(q"object A {@Relation class T(startNode:A, endNode:B); @HyperRelation class N(startNode:A, endNode:B) extends T}",
132 | "HyperRelation class `N` cannot inherit from Relation class `T`.")
133 | }
134 | "Graph trait" >> {
135 | generatedAborts(q"object A {@Graph trait T; @HyperRelation class N(startNode:A, endNode:B) extends T}",
136 | "HyperRelation class `N` cannot inherit from Graph trait `T`.")
137 | }
138 | }
139 |
140 | }
141 | }
142 | "Graph" >> {
143 | "trait" >> {
144 | "inherits from" >> {
145 | "Node" >> {
146 | "class" >> {
147 | generatedAborts(q"object A {@Node class T; @Graph trait N extends T}",
148 | "Graph trait `N` cannot inherit from Node class `T`.")
149 | }
150 | "trait" >> {
151 | generatedAborts(q"object A {@Node trait T; @Graph trait N extends T}",
152 | "Graph trait `N` cannot inherit from Node trait `T`.")
153 | }
154 | }
155 | "Relation" >> {
156 | "class" >> {
157 | generatedAborts(q"object A {@Relation class T(startNode:A, endNode:B); @Graph trait N extends T}",
158 | "Graph trait `N` cannot inherit from Relation class `T`.")
159 | }
160 | "trait" >> {
161 | generatedAborts(q"object A {@Relation trait T; @Graph trait N extends T}",
162 | "Graph trait `N` cannot inherit from Relation trait `T`.")
163 | }
164 | }
165 | }
166 | }
167 | }
168 | }
169 |
170 | "needs startNode and endNode" >> {
171 | "Relation" >> {
172 | generatedAborts(q"object A {@Relation class R}",
173 | "Relation class `R` needs startNode and endNode.")
174 | }
175 | "HyperRelation" >> {
176 | generatedAborts(q"object A {@HyperRelation class R}",
177 | "HyperRelation class `R` needs startNode and endNode.")
178 | }
179 |
180 | "Relation only allows nodes,node traits and hyperRelations" >> {
181 | generatedAborts(q"object A {@Relation class A(startNode:X, endNode:Y); @Relation class R(startNode:A, endNode:B)}",
182 | "Relation class `R` needs startNode `A` to be a Node, Node trait, or HyperRelation. Not a Relation.")
183 | generatedAborts(q"object A {@Relation trait A; @Relation class R(startNode:A, endNode:B)}",
184 | "Relation class `R` needs startNode `A` to be a Node, Node trait, or HyperRelation. Not a Relation trait.")
185 | generatedAborts(q"object A {@Graph trait A; @Relation class R(startNode:A, endNode:B)}",
186 | "Relation class `R` needs startNode `A` to be a Node, Node trait, or HyperRelation. Not a Graph.")
187 | generatedAborts(q"object A {@Relation class B(startNode:X, endNode:Y); @Relation class R(startNode:A, endNode:B)}",
188 | "Relation class `R` needs endNode `B` to be a Node, Node trait, or HyperRelation. Not a Relation.")
189 | generatedAborts(q"object A {@Relation trait B; @Relation class R(startNode:A, endNode:B)}",
190 | "Relation class `R` needs endNode `B` to be a Node, Node trait, or HyperRelation. Not a Relation trait.")
191 | generatedAborts(q"object A {@Graph trait B; @Relation class R(startNode:A, endNode:B)}",
192 | "Relation class `R` needs endNode `B` to be a Node, Node trait, or HyperRelation. Not a Graph.")
193 | }
194 | "HyperRelation only allows nodes,node traits and hyperRelations" >> {
195 | generatedAborts(q"object A {@Relation class A(startNode:X, endNode:Y); @HyperRelation class R(startNode:A, endNode:B)}",
196 | "HyperRelation class `R` needs startNode `A` to be a Node, Node trait, or HyperRelation. Not a Relation.")
197 | generatedAborts(q"object A {@Relation trait A; @HyperRelation class R(startNode:A, endNode:B)}",
198 | "HyperRelation class `R` needs startNode `A` to be a Node, Node trait, or HyperRelation. Not a Relation trait.")
199 | generatedAborts(q"object A {@Graph trait A; @HyperRelation class R(startNode:A, endNode:B)}",
200 | "HyperRelation class `R` needs startNode `A` to be a Node, Node trait, or HyperRelation. Not a Graph.")
201 | generatedAborts(q"object A {@Relation class B(startNode:X, endNode:Y); @HyperRelation class R(startNode:A, endNode:B)}",
202 | "HyperRelation class `R` needs endNode `B` to be a Node, Node trait, or HyperRelation. Not a Relation.")
203 | generatedAborts(q"object A {@Relation trait B; @HyperRelation class R(startNode:A, endNode:B)}",
204 | "HyperRelation class `R` needs endNode `B` to be a Node, Node trait, or HyperRelation. Not a Relation trait.")
205 | generatedAborts(q"object A {@Graph trait B; @HyperRelation class R(startNode:A, endNode:B)}",
206 | "HyperRelation class `R` needs endNode `B` to be a Node, Node trait, or HyperRelation. Not a Graph.")
207 | }
208 |
209 | }
210 |
211 | "not allowed class/object/trait" >> {
212 | "Graph class" >> {
213 | generatedAborts(q"object A {@Graph class G}",
214 | "Graph class `G` is not allowed. Use a trait instead.")
215 | }
216 | "Graph object" >> {
217 | generatedAborts(q"object A {@Graph object G}",
218 | "Graph object `G` is not allowed. Use a trait instead.")
219 | }
220 | "Node object" >> {
221 | generatedAborts(q"object A {@Node object N}",
222 | "Node object `N` is not allowed. Use a class or trait instead.")
223 | }
224 | "Relation object" >> {
225 | generatedAborts(q"object A {@Relation object R}",
226 | "Relation object `R` is not allowed. Use a class or trait instead.")
227 | }
228 | "HyperRelation object" >> {
229 | generatedAborts(q"object A {@HyperRelation object R}",
230 | "HyperRelation object `R` is not allowed. Use a class instead.")
231 | }
232 | "HyperRelation trait" >> {
233 | generatedAborts(q"object A {@HyperRelation trait R}",
234 | "HyperRelation trait `R` is not allowed. Use a class instead.")
235 | }
236 | }
237 |
238 | "Graphs can only be induced on Nodes" >> {
239 | generatedAborts(q"""object A {
240 | @Graph trait M;
241 | @Node class M;
242 | @Node trait N;
243 | @Relation class O(startNode:A, endNode:B);
244 | @Relation trait P;
245 | @HyperRelation class Q(startNode:A, endNode:B);
246 | @Graph trait G{Nodes(M, N, O, NonExistent, P, Q)};
247 | }""",
248 | "Graph `G` cannot contain `O`, `NonExistent`, `P`, `Q`. Only Node classes and traits are allowed.")
249 | }
250 |
251 | //TODO: generics are not allowed
252 | }
253 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/NodeTraitFactorySpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 | class NodeTraitFactorySpec extends CodeComparisonSpec {
6 |
7 | import contextMock.universe._
8 |
9 | "simple factory trait" >> {
10 | generatedContainsCode(
11 | q"object A {@Node trait T}",
12 | q"""trait TMatchesFactory[+NODE <: T] extends NodeFactory[NODE] {
13 | def matchesT(matches: Set[PropertyKey] = Set.empty): NODE
14 | }""",
15 | q"""trait TFactory[+NODE <: T] extends NodeFactory[NODE] with TMatchesFactory[NODE] {
16 | def createT(): NODE
17 | def mergeT(merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): NODE
18 | }"""
19 | )
20 | }
21 |
22 | "with own factory" >> {
23 | generatedContainsCode(
24 | q"object A {@Node trait T; @Node class N extends T}",
25 | q"""object T extends RootNodeTraitFactory[T] with TMatchesFactory[T] {
26 | val label = TMatches.label
27 | val labels = TMatches.labels
28 | def matches(matches: Set[PropertyKey] = Set.empty):TMatches = TMatches.matches(matches)
29 | def matchesT(matches: Set[PropertyKey] = Set.empty):TMatches = this.matches(matches)
30 | }"""
31 | )
32 | }
33 |
34 | "with own factory with superTraits" >> {
35 | generatedContainsCode(
36 | q"object A {@Node trait S; @Node trait T extends S; @Node class N extends T}",
37 | q"""object T extends RootNodeTraitFactory[T] with TMatchesFactory[T] {
38 | val label = TMatches.label
39 | val labels = TMatches.labels
40 | def matches(matches: Set[PropertyKey] = Set.empty):TMatches = TMatches.matches(matches)
41 | def matchesT(matches: Set[PropertyKey] = Set.empty):TMatches = this.matches(matches)
42 | def matchesS(matches: Set[PropertyKey] = Set.empty):TMatches = this.matches(matches)
43 | }"""
44 | )
45 | }
46 |
47 | "with own factory with superTraits and external traits" >> {
48 | generatedContainsCode(
49 | q"object A {@Node trait S; @Node trait T extends S with Immutable;}",
50 | q"""object T extends RootNodeTraitFactory[T] with TMatchesFactory[T] {
51 | val label = TMatches.label
52 | val labels = TMatches.labels
53 | def matches(matches: Set[PropertyKey] = Set.empty):TMatches = TMatches.matches(matches)
54 | def matchesT(matches: Set[PropertyKey] = Set.empty):TMatches = this.matches(matches)
55 | def matchesS(matches: Set[PropertyKey] = Set.empty):TMatches = this.matches(matches)
56 | }"""
57 | )
58 | }
59 |
60 | "with own factory with multiple superTraits" >> {
61 | generatedContainsCode(
62 | q"object A {@Node trait S;@Node trait X; @Node trait T extends S with X; @Node class N extends T}",
63 | q"""object T extends RootNodeTraitFactory[T] with TMatchesFactory[T] {
64 | val label = TMatches.label
65 | val labels = TMatches.labels
66 | def matches(matches: Set[PropertyKey] = Set.empty):TMatches = TMatches.matches(matches)
67 | def matchesT(matches: Set[PropertyKey] = Set.empty):TMatches = this.matches(matches)
68 | def matchesS(matches: Set[PropertyKey] = Set.empty):TMatches = this.matches(matches)
69 | def matchesX(matches: Set[PropertyKey] = Set.empty):TMatches = this.matches(matches)
70 | }"""
71 | )
72 | }
73 |
74 | "with own factory with multiple superTraits (chain)" >> {
75 | generatedContainsCode(
76 | q"object A {@Node trait Y;@Node trait S;@Node trait X extends Y; @Node trait T extends S with X; @Node class N extends T}",
77 | q"""
78 | object S extends RootNodeTraitFactory[S] with SMatchesFactory[S] {
79 | val label = SMatches.label;
80 | val labels = SMatches.labels;
81 | def matches(matches: Set[PropertyKey] = Set.empty):SMatches = SMatches.matches(matches);
82 | def matchesS(matches: Set[PropertyKey] = Set.empty): SMatches = this.matches(matches)
83 | };""",
84 | q"""
85 | object T extends RootNodeTraitFactory[T] with TMatchesFactory[T] {
86 | val label = TMatches.label;
87 | val labels = TMatches.labels;
88 | def matches(matches: Set[PropertyKey] = Set.empty):TMatches = TMatches.matches(matches);
89 | def matchesT(matches: Set[PropertyKey] = Set.empty): TMatches = this.matches(matches);
90 | def matchesY(matches: Set[PropertyKey] = Set.empty): TMatches = this.matches(matches)
91 | def matchesS(matches: Set[PropertyKey] = Set.empty): TMatches = this.matches(matches)
92 | def matchesX(matches: Set[PropertyKey] = Set.empty): TMatches = this.matches(matches)
93 | }""")
94 | }
95 |
96 | "with unique matches factory methods in trait" >> {
97 | generatedContainsCode(
98 | q"object A {@Node trait T {@unique val p:String; @unique var q:Boolean; @unique val r: Long = 1; @unique var s: Boolean = true};}",
99 | q"""trait TMatchesFactory[+NODE <: T] extends NodeFactory[NODE] {
100 | def matchesT(p: Option[String] = None, q: Option[Boolean] = None, r: Option[Long] = None, s: Option[Boolean] = None, matches: Set[PropertyKey] = Set.empty): NODE;
101 | def matchesOnP(p: String): NODE = this.matchesT(p = Some(p), matches = Set("p"));
102 | def matchesOnQ(q: Boolean): NODE = this.matchesT(q = Some(q), matches = Set("q"));
103 | def matchesOnR(r: Long): NODE = this.matchesT(r = Some(r), matches = Set("r"));
104 | def matchesOnS(s: Boolean): NODE = this.matchesT(s = Some(s), matches = Set("s"))
105 | }""",
106 | q"""trait TFactory[+NODE <: T] extends NodeFactory[NODE] with TMatchesFactory[NODE];"""
107 | )
108 | }
109 |
110 | //TODO: no own factory, when there is no node extending the trait
111 | "with factory interface" >> {
112 | generatedContainsCode(
113 | q"object A {@Node trait T {val p:String}}",
114 | q"""
115 | trait TMatchesFactory[+NODE <: T] extends NodeFactory[NODE] {
116 | def matchesT(p: Option[String] = None, matches: Set[PropertyKey] = Set.empty): NODE
117 | }""",
118 | q"""
119 | trait TFactory[+NODE <: T] extends NodeFactory[NODE] with TMatchesFactory[NODE] {
120 | def createT(p: String): NODE
121 | def mergeT(p: String, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): NODE
122 | }"""
123 | )
124 | }
125 |
126 | "without factory interface (only matches)" >> {
127 | generatedContainsCode(
128 | q"object A {@Node trait T {val p:String}; @Node class N extends T { val t: String }}",
129 | q"""
130 | trait TMatchesFactory[+NODE <: T] extends NodeFactory[NODE] {
131 | def matchesT(p: Option[String] = None, matches: Set[PropertyKey] = Set.empty): NODE
132 | }""",
133 | q"""trait TFactory[+NODE <: T] extends NodeFactory[NODE] with TMatchesFactory[NODE];"""
134 | )
135 | }
136 |
137 | "with superType factories" >> {
138 | generatedContainsCode(
139 | q"object A {@Node trait T ; @Node trait X extends T {val p:String} }",
140 | q"""
141 | trait XMatchesFactory[+NODE <: X] extends TMatchesFactory[NODE] {
142 | def matchesX(p: Option[String] = None, matches: Set[PropertyKey] = Set.empty): NODE
143 | }""",
144 | q"""
145 | trait XFactory[+NODE <: X] extends TFactory[NODE] with XMatchesFactory[NODE] {
146 | def createX(p: String): NODE
147 | def mergeX(p: String, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): NODE
148 | }""",
149 | q"""def matchesT(matches: Set[PropertyKey] = Set.empty): NODE;"""
150 | )
151 | }
152 |
153 | "with multiple superType factories (chain)" >> {
154 | generatedContainsCode(
155 | q"object A {@Node trait T; @Node trait S extends T; @Node class N extends S}",
156 | q"""trait SMatchesFactory[+NODE <: S] extends TMatchesFactory[NODE] {
157 | def matchesS(matches: Set[PropertyKey] = Set.empty): NODE
158 | };
159 | """,
160 | q"""trait SFactory[+NODE <: S] extends TFactory[NODE] with SMatchesFactory[NODE] {
161 | def createS(): NODE
162 | def mergeS(merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): NODE
163 | };
164 | """,
165 | q"""def matchesT(matches: Set[PropertyKey] = Set.empty): N = this.matches(matches)""",
166 | q"""def createT(): N = this.create()""",
167 | q"""def mergeT(merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): N = this.merge(merge, onMatch)"""
168 | )
169 | }
170 |
171 | "with superType factories with inherited factory methods" >> {
172 | generatedContainsCode(
173 | q"object A {@Node trait T {val p:String}; @Node trait X extends T; @Node class N extends X }",
174 | q"""trait XMatchesFactory[+NODE <: X] extends TMatchesFactory[NODE] {
175 | def matchesX(p: Option[String] = None, matches: Set[PropertyKey] = Set.empty): NODE
176 | }""",
177 | q"""trait XFactory[+NODE <: X] extends TFactory[NODE] with XMatchesFactory[NODE] {
178 | def createX(p: String): NODE
179 | def mergeX(p: String, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): NODE
180 | }""",
181 | q"""def matchesT(p: Option[String] = None, matches: Set[PropertyKey] = Set.empty): N = this.matches(p, matches)""",
182 | q"""def createT(p: String): N = this.create(p)""",
183 | q"""def mergeT(p: String, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): N = this.merge(p, merge, onMatch)""",
184 | q"""def matchesX(p: Option[String] = None, matches: Set[PropertyKey] = Set.empty): N = this.matches(p, matches)""",
185 | q"""def createX(p: String): N = this.create(p)""",
186 | q"""def mergeX(p: String, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): N = this.merge(p, merge, onMatch)"""
187 | )
188 | }
189 |
190 | "with indirectly inherited properties and no factory trait" >> {
191 | generatedContainsCode(
192 | q"object A {@Node trait T {val p:String; var x:Long}; @Node trait X extends T { val q: Boolean }; @Node class N extends X}",
193 | q"""trait TMatchesFactory[+NODE <: T] extends NodeFactory[NODE] {
194 | def matchesT(p: Option[String] = None, x: Option[Long] = None, matches: Set[PropertyKey] = Set.empty): NODE
195 | }""",
196 | q"""trait XMatchesFactory[+NODE <: X] extends TMatchesFactory[NODE] {
197 | def matchesX(p: Option[String] = None, q: Option[Boolean] = None, x: Option[Long] = None, matches: Set[PropertyKey] = Set.empty): NODE;
198 | }""",
199 | q"""trait TFactory[+NODE <: T] extends NodeFactory[NODE] with TMatchesFactory[NODE];""",
200 | q"""trait XFactory[+NODE <: X] extends TFactory[NODE] with XMatchesFactory[NODE] {
201 | def createX(p: String, q: Boolean, x: Long): NODE;
202 | def mergeX(p: String, q: Boolean, x: Long, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): NODE;
203 | }""",
204 | q"""def matchesT(p: Option[String] = None, x: Option[Long] = None, matches: Set[PropertyKey] = Set.empty): N = this.matches(p, None, x, matches)"""
205 | )
206 | }
207 |
208 | "with indirectly inherited properties and default properties" >> {
209 | generatedContainsCode(
210 | q"object A {@Node trait T {val p:String; var x:Long}; @Node trait X extends T { val q: Boolean = true }; @Node class N extends X}",
211 | q"""trait TMatchesFactory[+NODE <: T] extends NodeFactory[NODE] {
212 | def matchesT(p: Option[String] = None, x: Option[Long] = None, matches: Set[PropertyKey] = Set.empty): NODE
213 | }""",
214 | q"""trait XMatchesFactory[+NODE <: X] extends TMatchesFactory[NODE] {
215 | def matchesX(p: Option[String] = None, q: Option[Boolean] = None, x: Option[Long] = None, matches: Set[PropertyKey] = Set.empty): NODE;
216 | }""",
217 | q"""trait TFactory[+NODE <: T] extends NodeFactory[NODE] with TMatchesFactory[NODE] {
218 | def createT(p: String, x: Long): NODE
219 | def mergeT(p: String, x: Long, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): NODE
220 | }""",
221 | q"""trait XFactory[+NODE <: X] extends TFactory[NODE] with XMatchesFactory[NODE] {
222 | def createX(p: String, x: Long, q: Boolean = true): NODE;
223 | def mergeX(p: String, x: Long, q: Boolean = true, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): NODE;
224 | }"""
225 | )
226 | }
227 |
228 | "with indirectly inherited properties and optional properties" >> {
229 | generatedContainsCode(
230 | q"object A {@Node trait T {val p:String; var x:Long}; @Node trait X extends T { val q: Option[Boolean] }; @Node class N extends X}",
231 | q"""trait TMatchesFactory[+NODE <: T] extends NodeFactory[NODE] {
232 | def matchesT(p: Option[String] = None, x: Option[Long] = None, matches: Set[PropertyKey] = Set.empty): NODE
233 | };
234 | """,
235 | q"""trait XMatchesFactory[+NODE <: X] extends TMatchesFactory[NODE] {
236 | def matchesX(p: Option[String] = None, q: Option[Boolean] = None, x: Option[Long] = None, matches: Set[PropertyKey] = Set.empty): NODE
237 | }""",
238 | q"""trait TFactory[+NODE <: T] extends NodeFactory[NODE] with TMatchesFactory[NODE] {
239 | def createT(p: String, x: Long): NODE
240 | def mergeT(p: String, x: Long, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): NODE
241 | }""",
242 | q"""trait XFactory[+NODE <: X] extends TFactory[NODE] with XMatchesFactory[NODE] {
243 | def createX(p: String, x: Long, q: Option[Boolean] = None): NODE
244 | def mergeX(p: String, x: Long, q: Option[Boolean] = None, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): NODE
245 | }""",
246 | q"""def matchesT(p: Option[String] = None, x: Option[Long] = None, matches: Set[PropertyKey] = Set.empty): N = this.matches(p, None, x, matches)""",
247 | q"""def createT(p: String, x: Long): N = this.create(p, x, None)""",
248 | q"""def mergeT(p: String, x: Long, merge: Set[PropertyKey] = Set.empty, onMatch: Set[PropertyKey] = Set.empty): N = this.merge(p, x, None, merge, onMatch)"""
249 | )
250 | }
251 |
252 | "only matches factory methods if HyperRelation inherits from NodeTrait" >> {
253 | generatedContainsCode(
254 | q"object A {@Node class N; @Node trait T {val p:String}; @HyperRelation class X(startNode: N, endNode: N) extends T}",
255 | q"""trait TMatchesFactory[+NODE <: T] extends NodeFactory[NODE] {
256 | def matchesT(p: Option[String] = None, matches: Set[PropertyKey] = Set.empty): NODE
257 | }""",
258 | q"""trait TFactory[+NODE <: T] extends NodeFactory[NODE] with TMatchesFactory[NODE];"""
259 | )
260 | }
261 |
262 | "only matches factory methods if HyperRelation inherits from NodeTrait with unique property" >> {
263 | generatedContainsCode(
264 | q"object A {@Node class N; @Node trait T {@unique val p:String}; @HyperRelation class X(startNode: N, endNode: N) extends T}",
265 | q"""trait TMatchesFactory[+NODE <: T] extends NodeFactory[NODE] {
266 | def matchesT(p: Option[String] = None, matches: Set[PropertyKey] = Set.empty): NODE
267 | def matchesOnP(p: String): NODE = this.matchesT(p = Some(p), matches = Set("p"))
268 | }""",
269 | q"""trait TFactory[+NODE <: T] extends NodeFactory[NODE] with TMatchesFactory[NODE];"""
270 | )
271 | }
272 |
273 | "generates according matches classes and factories" >> {
274 | generatedContainsCode(
275 | q"object A {@Node trait Y;@Node trait S;@Node trait X extends Y; @Node trait T extends S with X; @Node class N extends T}",
276 | q"""
277 | object YMatches extends YMatchesFactory[YMatches] {
278 | val label = raw.Label("Y");
279 | val labels = Set(raw.Label("Y"));
280 | def wrap(node: raw.Node) = new YMatches(node);
281 | def matches(matches: Set[PropertyKey] = Set.empty): YMatches = {
282 | val wrapped = wrap(raw.Node.matches(labels, matches = matches));
283 | wrapped
284 | };
285 | def matchesY(matches: Set[PropertyKey] = Set.empty): YMatches = this.matches(matches)
286 | };""",
287 | q"""
288 | object SMatches extends SMatchesFactory[SMatches] {
289 | val label = raw.Label("S");
290 | val labels = Set(raw.Label("S"));
291 | def wrap(node: raw.Node) = new SMatches(node);
292 | def matches(matches: Set[PropertyKey] = Set.empty): SMatches = {
293 | val wrapped = wrap(raw.Node.matches(labels, matches = matches));
294 | wrapped
295 | };
296 | def matchesS(matches: Set[PropertyKey] = Set.empty): SMatches = this.matches(matches)
297 | };""",
298 | q"""
299 | object XMatches extends XMatchesFactory[XMatches] {
300 | val label = raw.Label("X");
301 | val labels = Set(raw.Label("X"), raw.Label("Y"));
302 | def wrap(node: raw.Node) = new XMatches(node);
303 | def matches(matches: Set[PropertyKey] = Set.empty): XMatches = {
304 | val wrapped = wrap(raw.Node.matches(labels, matches = matches));
305 | wrapped
306 | };
307 | def matchesY(matches: Set[PropertyKey] = Set.empty): XMatches = this.matches(matches);
308 | def matchesX(matches: Set[PropertyKey] = Set.empty): XMatches = this.matches(matches)
309 | };""",
310 | q"""
311 | object TMatches extends TMatchesFactory[TMatches] {
312 | val label = raw.Label("T");
313 | val labels = Set(raw.Label("T"), raw.Label("Y"), raw.Label("S"), raw.Label("X"));
314 | def wrap(node: raw.Node) = new TMatches(node);
315 | def matches(matches: Set[PropertyKey] = Set.empty): TMatches = {
316 | val wrapped = wrap(raw.Node.matches(labels, matches = matches));
317 | wrapped
318 | };
319 | def matchesY(matches: Set[PropertyKey] = Set.empty): TMatches = this.matches(matches);
320 | def matchesS(matches: Set[PropertyKey] = Set.empty): TMatches = this.matches(matches);
321 | def matchesX(matches: Set[PropertyKey] = Set.empty): TMatches = this.matches(matches);
322 | def matchesT(matches: Set[PropertyKey] = Set.empty): TMatches = this.matches(matches)
323 | };""",
324 | q"""
325 | case class YMatches(rawItem: raw.Node) extends Y {
326 | override val label = raw.Label("Y");
327 | override val labels = Set(raw.Label("Y"))
328 | };""",
329 | q"""
330 | case class SMatches(rawItem: raw.Node) extends S {
331 | override val label = raw.Label("S");
332 | override val labels = Set(raw.Label("S"))
333 | };""",
334 | q"""
335 | case class XMatches(rawItem: raw.Node) extends X {
336 | override val label = raw.Label("X");
337 | override val labels = Set(raw.Label("X"), raw.Label("Y"))
338 | };""",
339 | q"""
340 | case class TMatches(rawItem: raw.Node) extends T {
341 | override val label = raw.Label("T");
342 | override val labels = Set(raw.Label("T"), raw.Label("Y"), raw.Label("S"), raw.Label("X"))
343 | }""")
344 | }
345 | }
346 |
--------------------------------------------------------------------------------
/src/test/scala/codegeneration/GraphClassSpec.scala:
--------------------------------------------------------------------------------
1 | package codegeneration
2 |
3 | import helpers.CodeComparisonSpec
4 |
5 | class GraphClassSpec extends CodeComparisonSpec {
6 |
7 | import contextMock.universe._
8 |
9 | "Empty Graph" >> {
10 | generatedContainsCode(
11 | q"object A {@Graph trait G}",
12 | q"""case class G(graph: raw.Graph) extends Graph {
13 | def nodes: Seq[Node] = Seq.empty;
14 | def relations: (Seq[_$$18] forSome {
15 | type _$$18 <: (Relation[_$$26, _$$24] forSome {
16 | type _$$26;
17 | type _$$24
18 | })
19 | }) = Seq.empty;
20 | def abstractRelations: (Seq[_$$22] forSome {
21 | type _$$22 <: (AbstractRelation[_$$25, _$$23] forSome {
22 | type _$$25;
23 | type _$$23
24 | })
25 | }) = Seq.empty;
26 | def hyperRelations: (Seq[_$$21] forSome {
27 | type _$$21 <: (HyperRelation[_$$20, _$$16, _$$15, _$$19, _$$17] forSome {
28 | type _$$20;
29 | type _$$16;
30 | type _$$15;
31 | type _$$19;
32 | type _$$17
33 | })
34 | }) = Seq.empty
35 | } """
36 | )
37 | }
38 |
39 | "with nodes" >> {
40 | generatedContainsCode(
41 | q"object A {@Graph trait G {Nodes(N,M)}; @Node class N; @Node class M}",
42 | q"""def ns: Seq[N] = nodesAs(N);""",
43 | q"""def ms: Seq[M] = nodesAs(M);""",
44 | """def nodes: Seq[Node] = Seq.empty.++(ns).++(ms);"""
45 | )
46 | }
47 |
48 | "with nodes inserted by traits" >> {
49 | generatedContainsCode(
50 | q"""object A {
51 | @Node class N
52 | @Node trait T
53 | @Node class M extends T
54 | @Node class O extends T
55 | @Graph trait G {Nodes(N,T)}
56 | }""",
57 | q"""def nodes: Seq[Node] = Seq.empty.++(ns).++(ms).++(os)"""
58 | )
59 | }
60 |
61 | "with hyperrelations inserted by traits" >> {
62 | generatedContainsCode(
63 | q"""object A {
64 | @Node class N
65 | @Node class M
66 | @Node trait T
67 | @HyperRelation class H(startNode:N, endNode:M) extends T
68 | @Graph trait G {Nodes(T,N,M)}
69 | }""",
70 | q"""def nodes: Seq[Node] = Seq.empty.++(ns).++(ms).++(hs)""",
71 | q"""def abstractRelations: (Seq[_$$1] forSome {
72 | type _$$2 <: (AbstractRelation[_$$3, _$$4] forSome {
73 | type _$$3;
74 | type _$$4
75 | })
76 | }) = Seq.empty.++(hs)""",
77 | q"""
78 | def hyperRelations: (Seq[_$$21] forSome {
79 | type _$$21 <: (HyperRelation[_$$20, _$$16, _$$15, _$$19, _$$17] forSome {
80 | type _$$20;
81 | type _$$16;
82 | type _$$15;
83 | type _$$19;
84 | type _$$17
85 | })
86 | }) = Seq.empty.++(hs)""",
87 | q"""def ts: Seq[T] = Seq.empty.++(hs)"""
88 | )
89 | }
90 |
91 | "hyperrelations are not inserted only by traits" >> {
92 | generatedContainsCode(
93 | q"""object A {
94 | @Node class N
95 | @Node class M
96 | @Node trait T
97 | @HyperRelation class H(startNode:N, endNode:M) extends T
98 | @Graph trait G {Nodes(T)}
99 | }""",
100 | q"""def nodes: Seq[Node] = Seq.empty""",
101 | q"""def abstractRelations: (Seq[_$$1] forSome {
102 | type _$$2 <: (AbstractRelation[_$$3, _$$4] forSome {
103 | type _$$3;
104 | type _$$4
105 | })
106 | }) = Seq.empty""",
107 | q"""
108 | def hyperRelations: (Seq[_$$21] forSome {
109 | type _$$21 <: (HyperRelation[_$$20, _$$16, _$$15, _$$19, _$$17] forSome {
110 | type _$$20;
111 | type _$$16;
112 | type _$$15;
113 | type _$$19;
114 | type _$$17
115 | })
116 | }) = Seq.empty"""
117 | )
118 | }
119 |
120 | "with relations between trait which only contains one hyperrelation" >> {
121 | generatedContainsCode(
122 | q"""object A {
123 | @Node class N
124 | @Node trait T
125 | @Relation class R(startNode:N, endNode:T)
126 | @HyperRelation class H(startNode:N, endNode: N) extends T
127 | @Graph trait G {Nodes(T, N)}
128 | }""",
129 | q"""def nodes: Seq[Node] = Seq.empty.++(ns).++(hs)""",
130 | q"""
131 | def abstractRelations: (Seq[_$$1] forSome {
132 | type _$$2 <: (AbstractRelation[_$$3, _$$4] forSome {
133 | type _$$3;
134 | type _$$4
135 | })
136 | }) = Seq.empty.++(rs).++(hs);""",
137 | q"""
138 | def relations: (Seq[_$$1] forSome {
139 | type _$$2 <: (Relation[_$$3, _$$4] forSome {
140 | type _$$3;
141 | type _$$4
142 | })
143 | }) = Seq.empty.++(rs);
144 | """,
145 | q"""
146 | def hyperRelations: (Seq[_$$21] forSome {
147 | type _$$21 <: (HyperRelation[_$$20, _$$16, _$$15, _$$19, _$$17] forSome {
148 | type _$$20;
149 | type _$$16;
150 | type _$$15;
151 | type _$$19;
152 | type _$$17
153 | })
154 | }) = Seq.empty.++(hs)"""
155 | )
156 | }
157 |
158 | "exclude relations between trait which only contains one hyperrelation" >> {
159 | // The algorithm selects relations between selected nodes,
160 | // so R is selected, because N and H are selected.
161 | generatedContainsCode(
162 | q"""object A {
163 | @Node class N
164 | @Node trait T
165 | @Relation class R(startNode:N, endNode:T)
166 | @HyperRelation class H(startNode:N, endNode: N) extends T
167 | @Graph trait G {Nodes(N)}
168 | }""",
169 | q"""def nodes: Seq[Node] = Seq.empty.++(ns).++(hs)""",
170 | q"""
171 | def abstractRelations: (Seq[_$$1] forSome {
172 | type _$$2 <: (AbstractRelation[_$$3, _$$4] forSome {
173 | type _$$3;
174 | type _$$4
175 | })
176 | }) = Seq.empty.++(hs);""",
177 | q"""
178 | def relations: (Seq[_$$1] forSome {
179 | type _$$2 <: (Relation[_$$3, _$$4] forSome {
180 | type _$$3;
181 | type _$$4
182 | })
183 | }) = Seq.empty;
184 | """,
185 | q"""
186 | def hyperRelations: (Seq[_$$21] forSome {
187 | type _$$21 <: (HyperRelation[_$$20, _$$16, _$$15, _$$19, _$$17] forSome {
188 | type _$$20;
189 | type _$$16;
190 | type _$$15;
191 | type _$$19;
192 | type _$$17
193 | })
194 | }) = Seq.empty.++(hs)"""
195 | )
196 | }.pendingUntilFixed
197 |
198 | "with relations inserted by inherited traits" >> {
199 | generatedContainsCode(
200 | q"""object A {
201 | @Node trait T
202 | @Node trait S
203 | @Node class N extends T
204 | @Node class M extends S
205 | @Relation class R1(startNode:N, endNode:M)
206 | @Relation class R2(startNode:T, endNode:S)
207 | @Relation class R3(startNode:N, endNode:S)
208 | @Graph trait G {Nodes(T,S)}
209 | }""",
210 | q"""def r1s: Seq[R1] = relationsAs(R1);""",
211 | q"""def r2s: Seq[R2] = relationsAs(R2);""",
212 | q"""def r3s: Seq[R3] = relationsAs(R3);"""
213 | )
214 | }
215 |
216 | "with nodes inserted by inherited traits" >> {
217 | generatedContainsCode(
218 | q"""object A {
219 | @Node trait T
220 | @Node trait S extends T
221 | @Node trait X extends T
222 | @Node class M extends S
223 | @Node class O extends S
224 | @Node class N
225 | @Graph trait G {Nodes(N,S)}
226 | }""",
227 | q"""def ns: Seq[N] = nodesAs(N);""",
228 | q"""def ms: Seq[M] = nodesAs(M);""",
229 | q"""def os: Seq[O] = nodesAs(O);"""
230 | )
231 | }
232 |
233 | "with duplicate nodes" >> {
234 | generatedContainsCode(
235 | q"object A {@Graph trait G {Nodes(N,M,N)}; @Node class N; @Node class M}",
236 | q"""def ns: Seq[N] = nodesAs(N);""",
237 | q"""def ms: Seq[M] = nodesAs(M);""",
238 | """def nodes: Seq[Node] = Seq.empty.++(ns).++(ms);"""
239 | )
240 | }
241 |
242 | "with inherited nodes" >> {
243 | generatedContainsCode(
244 | q"""object A {
245 | @Node class N; @Node class M; @Node class O
246 | @Graph trait T{Nodes(O)}
247 | @Graph trait G extends T {Nodes(N,M)}
248 | }""",
249 | q"""case class T(graph: raw.Graph) extends Graph {
250 | def os: Seq[O] = nodesAs(O);
251 | def nodes: Seq[Node] = Seq.empty.++(os);
252 | def relations: (Seq[_$$18] forSome {
253 | type _$$18 <: (Relation[_$$26, _$$24] forSome {
254 | type _$$26;
255 | type _$$24
256 | })
257 | }) = Seq.empty;
258 | def abstractRelations: (Seq[_$$22] forSome {
259 | type _$$22 <: (AbstractRelation[_$$25, _$$23] forSome {
260 | type _$$25;
261 | type _$$23
262 | })
263 | }) = Seq.empty;
264 | def hyperRelations: (Seq[_$$21] forSome {
265 | type _$$21 <: (HyperRelation[_$$20, _$$16, _$$15, _$$19, _$$17] forSome {
266 | type _$$20;
267 | type _$$16;
268 | type _$$15;
269 | type _$$19;
270 | type _$$17
271 | })
272 | }) = Seq.empty
273 | };""",
274 | q"""case class G(graph: raw.Graph) extends Graph {
275 | def ns: Seq[N] = nodesAs(N);
276 | def ms: Seq[M] = nodesAs(M);
277 | def os: Seq[O] = nodesAs(O);
278 | def nodes: Seq[Node] = Seq.empty.++(ns).++(ms).++(os);
279 | def relations: (Seq[_$$18] forSome {
280 | type _$$18 <: (Relation[_$$26, _$$24] forSome {
281 | type _$$26;
282 | type _$$24
283 | })
284 | }) = Seq.empty;
285 | def abstractRelations: (Seq[_$$22] forSome {
286 | type _$$22 <: (AbstractRelation[_$$25, _$$23] forSome {
287 | type _$$25;
288 | type _$$23
289 | })
290 | }) = Seq.empty;
291 | def hyperRelations: (Seq[_$$21] forSome {
292 | type _$$21 <: (HyperRelation[_$$20, _$$16, _$$15, _$$19, _$$17] forSome {
293 | type _$$20;
294 | type _$$16;
295 | type _$$15;
296 | type _$$19;
297 | type _$$17
298 | })
299 | }) = Seq.empty
300 | }"""
301 | )
302 | }
303 |
304 | "with inherited nodes from two graphs" >> {
305 | generatedContainsCode(
306 | q"""object A {
307 | @Node class N; @Node class M; @Node class O
308 | @Graph trait T{Nodes(O)}
309 | @Graph trait S{Nodes(M)}
310 | @Graph trait G extends T with S {Nodes(N)}
311 | }""",
312 | q"""case class G(graph: raw.Graph) extends Graph {
313 | def ns: Seq[N] = nodesAs(N);
314 | def os: Seq[O] = nodesAs(O);
315 | def ms: Seq[M] = nodesAs(M);
316 | def nodes: Seq[Node] = Seq.empty.++(ns).++(os).++(ms);
317 | def relations: (Seq[_$$18] forSome {
318 | type _$$18 <: (Relation[_$$26, _$$24] forSome {
319 | type _$$26;
320 | type _$$24
321 | })
322 | }) = Seq.empty;
323 | def abstractRelations: (Seq[_$$22] forSome {
324 | type _$$22 <: (AbstractRelation[_$$25, _$$23] forSome {
325 | type _$$25;
326 | type _$$23
327 | })
328 | }) = Seq.empty;
329 | def hyperRelations: (Seq[_$$21] forSome {
330 | type _$$21 <: (HyperRelation[_$$20, _$$16, _$$15, _$$19, _$$17] forSome {
331 | type _$$20;
332 | type _$$16;
333 | type _$$15;
334 | type _$$19;
335 | type _$$17
336 | })
337 | }) = Seq.empty
338 | }"""
339 | )
340 | }
341 |
342 | "with same inherited node from two graphs" >> {
343 | generatedContainsCode(
344 | q"""object A {
345 | @Node class N
346 | @Graph trait T{Nodes(N)}
347 | @Graph trait S{Nodes(N)}
348 | @Graph trait G extends T with S {Nodes()}
349 | }""",
350 | q"""case class G(graph: raw.Graph) extends Graph {
351 | def ns: Seq[N] = nodesAs(N);
352 | def nodes: Seq[Node] = Seq.empty.++(ns);
353 | def relations: (Seq[_$$18] forSome {
354 | type _$$18 <: (Relation[_$$26, _$$24] forSome {
355 | type _$$26;
356 | type _$$24
357 | })
358 | }) = Seq.empty;
359 | def abstractRelations: (Seq[_$$22] forSome {
360 | type _$$22 <: (AbstractRelation[_$$25, _$$23] forSome {
361 | type _$$25;
362 | type _$$23
363 | })
364 | }) = Seq.empty;
365 | def hyperRelations: (Seq[_$$21] forSome {
366 | type _$$21 <: (HyperRelation[_$$20, _$$16, _$$15, _$$19, _$$17] forSome {
367 | type _$$20;
368 | type _$$16;
369 | type _$$15;
370 | type _$$19;
371 | type _$$17
372 | })
373 | }) = Seq.empty
374 | }"""
375 | )
376 | }
377 |
378 | "with same inherited node from diamond inheritance" >> {
379 | generatedContainsCode(
380 | q"""object A {
381 | @Node class N
382 | @Graph trait A{Nodes(N)}
383 | @Graph trait T extends A
384 | @Graph trait S extends A
385 | @Graph trait G extends T with S
386 | }""",
387 | q"""case class G(graph: raw.Graph) extends Graph {
388 | def ns: Seq[N] = nodesAs(N);
389 | def nodes: Seq[Node] = Seq.empty.++(ns);
390 | def relations: (Seq[_$$18] forSome {
391 | type _$$18 <: (Relation[_$$26, _$$24] forSome {
392 | type _$$26;
393 | type _$$24
394 | })
395 | }) = Seq.empty;
396 | def abstractRelations: (Seq[_$$22] forSome {
397 | type _$$22 <: (AbstractRelation[_$$25, _$$23] forSome {
398 | type _$$25;
399 | type _$$23
400 | })
401 | }) = Seq.empty;
402 | def hyperRelations: (Seq[_$$21] forSome {
403 | type _$$21 <: (HyperRelation[_$$20, _$$16, _$$15, _$$19, _$$17] forSome {
404 | type _$$20;
405 | type _$$16;
406 | type _$$15;
407 | type _$$19;
408 | type _$$17
409 | })
410 | }) = Seq.empty
411 | }"""
412 | )
413 | }
414 |
415 | "with relations" >> {
416 | generatedContainsCode(
417 | q"""object A {
418 | @Graph trait G {Nodes(N,M)}
419 | @Node class N
420 | @Node class M
421 | @Relation class R(startNode:N, endNode: M)
422 | @Relation class S(startNode:M, endNode: N)
423 | }""",
424 | q"""def rs: Seq[R] = relationsAs(R);""",
425 | q"""def s: Seq[S] = relationsAs(S);""",
426 | """def relations: (Seq[_] forSome {
427 | type _ <: (Relation[_, _] forSome {
428 | type _;
429 | type _
430 | })
431 | }) = Seq.empty.++(rs).++(s);""",
432 | """def abstractRelations: (Seq[_] forSome {
433 | type _ <: (AbstractRelation[_, _] forSome {
434 | type _;
435 | type _
436 | })
437 | }) = Seq.empty.++(rs).++(s);"""
438 | )
439 | }
440 |
441 | "with relations between chained super trait" >> {
442 | generatedContainsCode(
443 | q"""object A {
444 | @Graph trait G {Nodes(N,M)}
445 | @Node class N
446 | @Node trait S
447 | @Node trait T extends S
448 | @Node class M extends T
449 | @Relation class R(startNode:N, endNode: S)
450 | }""",
451 | q"""def rs: Seq[R] = relationsAs(R);""",
452 | """def relations: (Seq[_] forSome {
453 | type _ <: (Relation[_, _] forSome {
454 | type _;
455 | type _
456 | })
457 | }) = Seq.empty.++(rs);""",
458 | """def abstractRelations: (Seq[_] forSome {
459 | type _ <: (AbstractRelation[_, _] forSome {
460 | type _;
461 | type _
462 | })
463 | }) = Seq.empty.++(rs);"""
464 | )
465 | }
466 |
467 | "with hyperRelations" >> {
468 | generatedContainsCode(
469 | q"""object A {
470 | @Graph trait G {Nodes(N,M)};
471 | @Node class N;
472 | @Node class M;
473 | @HyperRelation class R(startNode:N, endNode: M);
474 | }""",
475 | q"""def rs: Seq[R] = hyperRelationsAs(R);""",
476 | """def hyperRelations: (Seq[_] forSome {
477 | type _ <: (HyperRelation[_, _, _, _, _] forSome {
478 | type _;
479 | type _;
480 | type _;
481 | type _;
482 | type _
483 | })
484 | }) = Seq.empty.++(rs)""",
485 | """def abstractRelations: (Seq[_] forSome {
486 | type _ <: (AbstractRelation[_, _] forSome {
487 | type _;
488 | type _
489 | })
490 | }) = Seq.empty.++(rs); """
491 | )
492 | }
493 |
494 | "with node trait and one node" >> {
495 | generatedContainsCode(
496 | q"object A {@Graph trait G {Nodes(N)}; @Node trait T; @Node class N extends T;}",
497 | """def ts: Seq[T] = Seq.empty.++(ns);""", // tNodes
498 | """def tRelations: (Seq[_] forSome {
499 | type _ <: Relation[T, T]
500 | }) = Seq.empty;""",
501 | """def tAbstractRelations: (Seq[_] forSome {
502 | type _ <: AbstractRelation[T, T]
503 | }) = Seq.empty;""",
504 | """def tHyperRelations: Seq[(HyperRelation[T, _, _, _, T] forSome {
505 | type _ <: (Relation[T, _] forSome {
506 | type _
507 | });
508 | type _ <: (HyperRelation[T, _, _, _, T] forSome {
509 | type _;
510 | type _;
511 | type _
512 | });
513 | type _ <: (Relation[_, T] forSome {
514 | type _
515 | })
516 | })] = Seq.empty;"""
517 | )
518 | }
519 |
520 | "with node trait with relations" >> {
521 | generatedContainsCode(
522 | q"""object A {@Graph trait G {Nodes(N,M)}; @Node trait T;
523 | @Node class N extends T;
524 | @Node class M extends T;
525 | @Relation class R(startNode:N, endNode:M)
526 | }""",
527 | """def tRelations: (Seq[_] forSome {
528 | type _ <: Relation[T, T]
529 | }) = Seq.empty.++(rs);""",
530 | """def tAbstractRelations: (Seq[_] forSome {
531 | type _ <: AbstractRelation[T, T]
532 | }) = Seq.empty.++(rs);"""
533 | )
534 | }
535 |
536 | "list trait relations only if in Graph and trait" >> {
537 | generatedContainsCode(
538 | q"""object A {@Graph trait G {Nodes(M,N,O,P)};
539 | @Node trait T;
540 | @Node trait S;
541 | @Node class M extends T;
542 | @Node class N extends T;
543 | @Node class O extends S;
544 | @Node class P
545 | @Node class Q
546 | @Relation class R(startNode:N, endNode:M)
547 | @Relation class R2(startNode:N, endNode:O)
548 | @Relation class R3(startNode:N, endNode:P)
549 | @Relation class R4(startNode:N, endNode:Q)
550 | }""",
551 | """def tRelations: (Seq[_] forSome {
552 | type _ <: Relation[T, T]
553 | }) = Seq.empty.++(rs);""",
554 | """def tAbstractRelations: (Seq[_] forSome {
555 | type _ <: AbstractRelation[T, T]
556 | }) = Seq.empty.++(rs);"""
557 | )
558 | }
559 |
560 | "list trait hyperrelations only if in Graph and trait" >> {
561 | generatedContainsCode(
562 | q"""object A {@Graph trait G {Nodes(M,N,O,P)};
563 | @Node trait T;
564 | @Node trait S;
565 | @Node class M extends T;
566 | @Node class N extends T;
567 | @Node class O extends S;
568 | @Node class P
569 | @Node class Q
570 | @HyperRelation class R(startNode:N, endNode:M)
571 | @HyperRelation class R2(startNode:N, endNode:O)
572 | @HyperRelation class R3(startNode:N, endNode:P)
573 | @HyperRelation class R4(startNode:N, endNode:Q)
574 | }""",
575 | """def tAbstractRelations: (Seq[_] forSome {
576 | type _ <: AbstractRelation[T, T]
577 | }) = Seq.empty.++(rs);""",
578 | """def tHyperRelations: Seq[(HyperRelation[T, _, _, _, T] forSome {
579 | type _ <: (Relation[T, _] forSome {
580 | type _
581 | });
582 | type _ <: (HyperRelation[T, _, _, _, T] forSome {
583 | type _;
584 | type _;
585 | type _
586 | });
587 | type _ <: (Relation[_, T] forSome {
588 | type _
589 | })
590 | })] = Seq.empty.++(rs);"""
591 | )
592 | }
593 |
594 | "common hyperRelation traits between nodes of trait" >> {
595 | generatedContainsCode(
596 | q"""object A {@Graph trait G {Nodes(M,N,O,P)};
597 | @Node trait T;
598 | @Node class N extends T;
599 | @Node class M extends T;
600 | @Node class O extends T;
601 | @Node class P extends T;
602 | @Node trait X
603 | @HyperRelation class R(startNode:N, endNode:M) extends X
604 | @HyperRelation class R2(startNode:N, endNode:O) extends X
605 | @HyperRelation class R3(startNode:N, endNode:P) extends X
606 | }""",
607 | """def tHyperRelations: Seq[(HyperRelation[T, _, _, _, T] forSome {
608 | type _ <: (Relation[T, _] forSome {
609 | type _
610 | });
611 | type _ <: (HyperRelation[T, _, _, _, T] forSome {
612 | type _;
613 | type _;
614 | type _
615 | }) with X;
616 | type _ <: (Relation[_, T] forSome {
617 | type _
618 | })
619 | }) with X] = Seq.empty.++(rs).++(r2s).++(r3s);"""
620 |
621 | )
622 | }
623 |
624 | "common hyperRelation traits between nodes of trait (multiple inheritance)" >> {
625 | generatedContainsCode(
626 | q"""object A {@Graph trait G {Nodes(M,N,O,P)};
627 | @Node trait T;
628 | @Node class N extends T;
629 | @Node class M extends T;
630 | @Node class O extends T;
631 | @Node class P extends T;
632 | @Node trait X
633 | @Node trait Y
634 | @Node trait Z
635 | @HyperRelation class R(startNode:N, endNode:M) extends X with Y
636 | @HyperRelation class R2(startNode:N, endNode:O) extends X
637 | @HyperRelation class R3(startNode:N, endNode:P) extends Z with X
638 | }""",
639 | """def tHyperRelations: Seq[(HyperRelation[T, _, _, _, T] forSome {
640 | type _ <: (Relation[T, _] forSome {
641 | type _
642 | });
643 | type _ <: (HyperRelation[T, _, _, _, T] forSome {
644 | type _;
645 | type _;
646 | type _
647 | }) with X;
648 | type _ <: (Relation[_, T] forSome {
649 | type _
650 | })
651 | }) with X] = Seq.empty.++(rs).++(r2s).++(r3s);"""
652 | )
653 | }
654 |
655 | "generate node accessors for node traits which are only extended by HyperRelations" >> {
656 | generatedContainsCode(
657 | q"""object A {
658 | @Node trait T;
659 | @Node class N;
660 | @HyperRelation class H(startNode:N, endNode:N) extends T
661 | @Graph trait G {Nodes(T, N)};
662 | }""",
663 | """def hs: Seq[H] = hyperRelationsAs(H)""",
664 | """def ts: Seq[T] = Seq.empty.++(hs)""",
665 | """def ns: Seq[N] = nodesAs(N)"""
666 | )
667 | }
668 | }
669 |
--------------------------------------------------------------------------------
/src/main/scala/Generators.scala:
--------------------------------------------------------------------------------
1 | package renesca.schema.macros
2 |
3 | trait Generators extends Context with Patterns with Parameters {
4 | import Helpers._
5 | import context.universe._
6 |
7 | trait Named {
8 | def pattern: NamePattern
9 | def name = pattern.name
10 |
11 | def name_type = TypeName(name)
12 | def name_term = TermName(name)
13 | def name_label = nameToLabel(name)
14 | def name_plural = nameToPlural(name)
15 | def name_plural_term = TermName(name_plural)
16 | }
17 |
18 | trait SuperTypes {
19 | def pattern: SuperTypesPattern
20 | def externalSuperTypes: List[String]
21 | def externalSuperTypes_type = externalSuperTypes.map(TypeName(_))
22 |
23 | //TODO: should filter external super types here.
24 | //currently the pattern is copied before instantiating this. Because some
25 | //functions in this file rely on pattern.superTypes to be correct.
26 | def superTypes = pattern.superTypes
27 | def superTypes_type = superTypes.map(TypeName(_))
28 | }
29 |
30 |
31 | trait StartEndNode {
32 | def pattern: StartEndNodePattern
33 | def startNode = pattern.startNode
34 | def endNode = pattern.endNode
35 |
36 | def startNode_type = TypeName(startNode)
37 | def startNode_term = TermName(startNode)
38 | def endNode_type = TypeName(endNode)
39 | def endNode_term = TermName(endNode)
40 | }
41 |
42 |
43 | trait StartEndRelation extends StartEndNode with Named {
44 | def pattern: StartEndNodePattern with NamePattern
45 |
46 | def startRelation = hyperStartRelationName(name)
47 | def startRelation_type = TypeName(startRelation)
48 | def startRelation_term = TermName(startRelation)
49 | def startRelation_label = nameToLabel(startRelation)
50 | def endRelation = hyperEndRelationName(name)
51 | def endRelation_type = TypeName(endRelation)
52 | def endRelation_term = TermName(endRelation)
53 | def endRelation_label = nameToLabel(endRelation)
54 | }
55 |
56 | trait HasOwnFactory {
57 | val hasOwnFactory: Option[Boolean]
58 | val parameterList: ParameterList
59 | }
60 |
61 | trait Statements {
62 | def pattern: StatementsPattern
63 | def statements = pattern.statements
64 | }
65 |
66 | trait HasParameterList {
67 | val parameterList: ParameterList
68 | }
69 |
70 | trait HasTraitFactoryParameterList {
71 | def traitFactoryParameterList: List[ParameterList]
72 | }
73 |
74 | object Schema {
75 | def apply(schemaPattern: SchemaPattern): Schema = {
76 | import schemaPattern._
77 | //TODO: dry collect
78 | val nodePatterns: List[NodePattern] = schemaPattern.statements.collect { case NodePattern(node) => node }
79 | val relationPatterns: List[RelationPattern] = schemaPattern.statements.collect { case RelationPattern(relationPattern) => relationPattern }
80 | val hyperRelationPatterns: List[HyperRelationPattern] = schemaPattern.statements.collect { case HyperRelationPattern(hyperRelationPattern) => hyperRelationPattern }
81 | val nodeTraitPatterns: List[NodeTraitPattern] = schemaPattern.statements.collect { case NodeTraitPattern(nodeTraitpattern) => nodeTraitpattern }
82 | val relationTraitPatterns: List[RelationTraitPattern] = schemaPattern.statements.collect { case RelationTraitPattern(nodeTraitpattern) => nodeTraitpattern }
83 | val graphPatterns: List[GraphPattern] = schemaPattern.statements.collect { case GraphPattern(graphPattern) => graphPattern }
84 | val allRelationPatterns = relationPatterns ::: hyperRelationPatterns
85 |
86 | def abortIfInheritsFrom(childItem: String, childType: String, child: NamePattern with SuperTypesPattern, parentItem: String, parentType: String, parents: List[NamePattern]): Unit = {
87 | for(childSuperTypeName <- child.superTypes; parentName <- parents.map(_.name) if childSuperTypeName == parentName)
88 | abort(s"$childItem $childType `${ child.name }` cannot inherit from $parentItem $parentType `$parentName`.")
89 | }
90 |
91 | val nodeTraits = nodeTraitPatterns.map { rawNodeTraitPattern =>
92 | abortIfInheritsFrom("Node", "trait", rawNodeTraitPattern, "Node", "class", nodePatterns)
93 | abortIfInheritsFrom("Node", "trait", rawNodeTraitPattern, "Relation", "class", relationPatterns)
94 | abortIfInheritsFrom("Node", "trait", rawNodeTraitPattern, "Relation", "trait", relationTraitPatterns)
95 | abortIfInheritsFrom("Node", "trait", rawNodeTraitPattern, "Graph", "trait", graphPatterns)
96 |
97 | NodeTrait(rawNodeTraitPattern, nodeTraitPatterns, relationTraitPatterns, nodePatterns, hyperRelationPatterns, relationPatterns, hyperRelationPatterns)
98 | }
99 |
100 | nodeTraits.foreach { nodeTrait =>
101 | nodeTrait.traitFactoryParameterList = findSuperFactoryParameterList(nodeTraitPatterns, nodeTrait.pattern, nodeTraits)
102 | }
103 |
104 | val relationTraits = relationTraitPatterns.map { rawRelationTraitPattern =>
105 | abortIfInheritsFrom("Relation", "trait", rawRelationTraitPattern, "Relation", "class", relationPatterns)
106 | abortIfInheritsFrom("Relation", "trait", rawRelationTraitPattern, "Node", "class", nodePatterns)
107 | abortIfInheritsFrom("Relation", "trait", rawRelationTraitPattern, "Node", "trait", nodeTraitPatterns)
108 | abortIfInheritsFrom("Relation", "trait", rawRelationTraitPattern, "Graph", "trait", graphPatterns)
109 |
110 | val externalSuperTypes = rawRelationTraitPattern.superTypes diff relationTraitPatterns.map(_.name)
111 | val relationTraitPattern = rawRelationTraitPattern.copy(_superTypes = rawRelationTraitPattern.superTypes diff externalSuperTypes)
112 | RelationTrait(relationTraitPattern,
113 | externalSuperTypes = externalSuperTypes,
114 | flatSuperStatements(relationTraitPatterns, relationTraitPattern),
115 | traitCanHaveOwnFactory(allRelationPatterns ::: relationTraitPatterns, rawRelationTraitPattern))
116 | }
117 |
118 | // create special nodepatterns for creating matches class for nodetraits
119 | val (traitImplementationPattern, traitImplementationMap) = {
120 | val (pattern, mapping) = nodeTraits.map(nodeTrait => {
121 | import nodeTrait._
122 | val implName = traitMatchesClassName(name)
123 | (NodePattern(implName, List(name), List.empty), implName -> nodeTrait)
124 | }).unzip
125 |
126 | (pattern, mapping.toMap)
127 | }
128 |
129 | val nodes = (traitImplementationPattern ++ nodePatterns).map { rawNodePattern =>
130 | abortIfInheritsFrom("Node", "class", rawNodePattern, "Node", "class", nodePatterns)
131 | abortIfInheritsFrom("Node", "class", rawNodePattern, "Relation", "class", relationPatterns)
132 | abortIfInheritsFrom("Node", "class", rawNodePattern, "Relation", "trait", relationTraitPatterns)
133 | abortIfInheritsFrom("Node", "class", rawNodePattern, "Graph", "trait", graphPatterns)
134 |
135 | val externalSuperTypes = rawNodePattern.superTypes diff nodeTraitPatterns.map(_.name)
136 | val nodePattern = rawNodePattern.copy(_superTypes = rawNodePattern.superTypes diff externalSuperTypes)
137 | Node(nodePattern,
138 | flatSuperTypesWithSelf = patternToFlatSuperTypesWithSelf(nodeTraitPatterns, nodePattern).map(_.name) intersect (nodePattern.name :: nodeTraits.map(_.name)),
139 | externalSuperTypes = externalSuperTypes,
140 | neighbours = neighbours(nodePattern, allRelationPatterns, nodePatterns, nodeTraitPatterns),
141 | rev_neighbours = rev_neighbours(nodePattern, allRelationPatterns, nodePatterns, nodeTraitPatterns),
142 | outRelationsToTrait = allRelationPatterns.filter { r =>
143 | (patternToFlatSuperTypesWithSelf(nodeTraitPatterns, nodePattern).map(_.name) contains r.startNode) &&
144 | (nodeTraitPatterns.map(_.name) contains r.endNode)
145 | }.map(r => (r.name, r.endNode)),
146 | inRelationsFromTrait = allRelationPatterns.filter { r =>
147 | // endNode is this node or one of its supertypes
148 | // and startNode is a Node Trait
149 | (patternToFlatSuperTypesWithSelf(nodeTraitPatterns, nodePattern).map(_.name) contains r.endNode) &&
150 | (nodeTraitPatterns.map(_.name) contains r.startNode)
151 | }.map(r => (r.name, r.startNode)),
152 | flatStatements = flatSuperStatements(nodeTraitPatterns, nodePattern),
153 | traitFactoryParameterList = findSuperFactoryParameterList(nodeTraitPatterns, nodePattern, nodeTraits),
154 | implementedTrait = traitImplementationMap.get(nodePattern.name))
155 | }
156 |
157 | val wholeGraph = GraphPattern("Whole" + schemaPattern.name, Nil, nodePatterns.map(_.name) ::: nodeTraitPatterns.map(_.name))
158 |
159 | val graphs = (wholeGraph :: graphPatterns).map { graphPattern =>
160 | abortIfInheritsFrom("Graph", "trait", graphPattern, "Node", "class", nodePatterns)
161 | abortIfInheritsFrom("Graph", "trait", graphPattern, "Node", "trait", nodeTraitPatterns)
162 | abortIfInheritsFrom("Graph", "trait", graphPattern, "Relation", "class", relationPatterns)
163 | abortIfInheritsFrom("Graph", "trait", graphPattern, "Relation", "trait", relationTraitPatterns)
164 | // We do not allow HyperRelations to be explicitly added to the Graph.
165 | // They should always be implied by their start/end nodes.
166 | val notAllowed = graphPattern.nodes.distinct diff (nodePatterns ++ nodeTraitPatterns).map(_.name)
167 | if(notAllowed.nonEmpty) abort(s"Graph `${ graphPattern.name }` cannot contain ${ notAllowed.mkString("`", "`, `", "`") }. Only Node classes and traits are allowed.")
168 |
169 | // explicit Nodes: Nodes, NodeTraits but no HyperRelations
170 | val explicitNodes = graphToNodes(graphPatterns, graphPattern).distinct
171 | val traits = explicitNodes intersect nodeTraits.map(_.name)
172 | val expandedTraits = traits.flatMap(t => patternToFlatSubTypesWithoutSelf(nodeTraitPatterns ::: nodePatterns, nameToPattern(nodeTraitPatterns, t))).map(_.name)
173 | val nodes = (explicitNodes ++ expandedTraits).distinct diff nodeTraits.map(_.name)
174 | val connectedHyperRelations = inducedRelations(nodes, nodePatterns ::: hyperRelationPatterns, nodeTraitPatterns, hyperRelationPatterns, hyperRelationPatterns)
175 | val nodesWithHyperRelations = nodes ++ connectedHyperRelations
176 | val graphedNodePatterns = nodesWithHyperRelations.map(nameToPattern(nodePatterns ::: hyperRelationPatterns, _))
177 | val graphedTraits = graphedNodePatterns.flatMap(patternToFlatSuperTypesWithoutSelf(nodeTraitPatterns, _)).distinct
178 |
179 | Graph(graphPattern,
180 | nodes = nodes.map(nameToPattern(nodePatterns, _)).collect { case n: NamePattern => n.name },
181 | nodesWithHyperRelations = nodesWithHyperRelations,
182 | relations = inducedRelations(nodesWithHyperRelations, nodePatterns ::: hyperRelationPatterns, nodeTraitPatterns, hyperRelationPatterns, relationPatterns),
183 | relationsWithHyperRelations = inducedRelations(nodesWithHyperRelations, nodePatterns ::: hyperRelationPatterns, nodeTraitPatterns, hyperRelationPatterns, allRelationPatterns),
184 | hyperRelations = connectedHyperRelations,
185 | nodeTraits = graphedTraits.map(nodeTraitPattern => {
186 | NodeTrait(nodeTraitPattern, nodeTraitPatterns, relationTraitPatterns,
187 | nodesWithHyperRelations.filter((nodePatterns ::: hyperRelationPatterns).map(_.name).toSet).distinct.map(nameToPattern(nodePatterns ::: hyperRelationPatterns, _)), //TODO: use intersect?
188 | connectedHyperRelations.filter(hyperRelationPatterns.map(_.name).toSet).distinct.map(nameToPattern(hyperRelationPatterns, _)),
189 | relationPatterns,
190 | hyperRelationPatterns)
191 | })
192 | )
193 | }
194 |
195 | val hyperRelations = hyperRelationPatterns.map { rawHyperRelationPattern =>
196 | abortIfInheritsFrom("HyperRelation", "class", rawHyperRelationPattern, "Relation", "class", relationPatterns)
197 | abortIfInheritsFrom("HyperRelation", "class", rawHyperRelationPattern, "Node", "class", nodePatterns)
198 | abortIfInheritsFrom("HyperRelation", "class", rawHyperRelationPattern, "Graph", "trait", graphPatterns)
199 | if(graphPatterns.map(_.name) contains rawHyperRelationPattern.startNode) abort(s"HyperRelation class `${ rawHyperRelationPattern.name }` needs startNode `${ rawHyperRelationPattern.startNode }` to be a Node, Node trait, or HyperRelation. Not a Graph.")
200 | if(relationPatterns.map(_.name) contains rawHyperRelationPattern.startNode) abort(s"HyperRelation class `${ rawHyperRelationPattern.name }` needs startNode `${ rawHyperRelationPattern.startNode }` to be a Node, Node trait, or HyperRelation. Not a Relation.")
201 | if(relationTraitPatterns.map(_.name) contains rawHyperRelationPattern.startNode) abort(s"HyperRelation class `${ rawHyperRelationPattern.name }` needs startNode `${ rawHyperRelationPattern.startNode }` to be a Node, Node trait, or HyperRelation. Not a Relation trait.")
202 | if(graphPatterns.map(_.name) contains rawHyperRelationPattern.endNode) abort(s"HyperRelation class `${ rawHyperRelationPattern.name }` needs endNode `${ rawHyperRelationPattern.endNode }` to be a Node, Node trait, or HyperRelation. Not a Graph.")
203 | if(relationPatterns.map(_.name) contains rawHyperRelationPattern.endNode) abort(s"HyperRelation class `${ rawHyperRelationPattern.name }` needs endNode `${ rawHyperRelationPattern.endNode }` to be a Node, Node trait, or HyperRelation. Not a Relation.")
204 | if(relationTraitPatterns.map(_.name) contains rawHyperRelationPattern.endNode) abort(s"HyperRelation class `${ rawHyperRelationPattern.name }` needs endNode `${ rawHyperRelationPattern.endNode }` to be a Node, Node trait, or HyperRelation. Not a Relation trait.")
205 |
206 | val externalSuperTypes = rawHyperRelationPattern.superTypes diff (nodeTraitPatterns.map(_.name) ++ relationTraitPatterns.map(_.name))
207 | val hyperRelationPattern = rawHyperRelationPattern.copy(_superTypes = rawHyperRelationPattern.superTypes diff externalSuperTypes)
208 | HyperRelation(
209 | pattern = hyperRelationPattern,
210 | superNodeTypes = filterSuperTypes(nodeTraitPatterns, hyperRelationPattern),
211 | flatSuperNodeTypesWithSelf = patternToFlatSuperTypesWithSelf(nodeTraitPatterns, hyperRelationPattern).map(_.name) intersect (hyperRelationPattern.name :: nodeTraits.map(_.name)),
212 | externalSuperTypes = externalSuperTypes,
213 | superRelationTypes = filterSuperTypes(relationTraitPatterns, hyperRelationPattern),
214 | neighbours = neighbours(hyperRelationPattern, allRelationPatterns, nodePatterns, nodeTraitPatterns),
215 | rev_neighbours = rev_neighbours(hyperRelationPattern, allRelationPatterns, nodePatterns, nodeTraitPatterns),
216 | outRelationsToTrait = allRelationPatterns.filter { r =>
217 | (patternToFlatSuperTypesWithSelf(nodeTraitPatterns, hyperRelationPattern).map(_.name) contains r.startNode) &&
218 | (nodeTraitPatterns.map(_.name) contains r.endNode)
219 | }.map(r => (r.name, r.endNode)),
220 | inRelationsFromTrait = allRelationPatterns.filter { r =>
221 | // endNode is this node or one of its supertypes
222 | // and startNode is a Node Trait
223 | (patternToFlatSuperTypesWithSelf(nodeTraitPatterns, hyperRelationPattern).map(_.name) contains r.endNode) &&
224 | (nodeTraitPatterns.map(_.name) contains r.startNode)
225 | }.map(r => (r.name, r.startNode)),
226 | flatSuperStatements = flatSuperStatements(nodeTraitPatterns ::: relationTraitPatterns, hyperRelationPattern),
227 | traitFactoryParameterList = findSuperFactoryParameterList(nodeTraitPatterns ::: relationTraitPatterns, hyperRelationPattern, nodeTraits ::: relationTraits))
228 | }
229 |
230 | val relations = relationPatterns.map { rawRelationPattern =>
231 | abortIfInheritsFrom("Relation", "class", rawRelationPattern, "Relation", "class", relationPatterns)
232 | abortIfInheritsFrom("Relation", "class", rawRelationPattern, "Node", "class", nodePatterns)
233 | abortIfInheritsFrom("Relation", "class", rawRelationPattern, "Node", "trait", nodeTraitPatterns)
234 | abortIfInheritsFrom("Relation", "class", rawRelationPattern, "Graph", "trait", graphPatterns)
235 | if(graphPatterns.map(_.name) contains rawRelationPattern.startNode) abort(s"Relation class `${ rawRelationPattern.name }` needs startNode `${ rawRelationPattern.startNode }` to be a Node, Node trait, or HyperRelation. Not a Graph.")
236 | if(relationPatterns.map(_.name) contains rawRelationPattern.startNode) abort(s"Relation class `${ rawRelationPattern.name }` needs startNode `${ rawRelationPattern.startNode }` to be a Node, Node trait, or HyperRelation. Not a Relation.")
237 | if(relationTraitPatterns.map(_.name) contains rawRelationPattern.startNode) abort(s"Relation class `${ rawRelationPattern.name }` needs startNode `${ rawRelationPattern.startNode }` to be a Node, Node trait, or HyperRelation. Not a Relation trait.")
238 | if(graphPatterns.map(_.name) contains rawRelationPattern.endNode) abort(s"Relation class `${ rawRelationPattern.name }` needs endNode `${ rawRelationPattern.endNode }` to be a Node, Node trait, or HyperRelation. Not a Graph.")
239 | if(relationPatterns.map(_.name) contains rawRelationPattern.endNode) abort(s"Relation class `${ rawRelationPattern.name }` needs endNode `${ rawRelationPattern.endNode }` to be a Node, Node trait, or HyperRelation. Not a Relation.")
240 | if(relationTraitPatterns.map(_.name) contains rawRelationPattern.endNode) abort(s"Relation class `${ rawRelationPattern.name }` needs endNode `${ rawRelationPattern.endNode }` to be a Node, Node trait, or HyperRelation. Not a Relation trait.")
241 |
242 | val externalSuperTypes = rawRelationPattern.superTypes diff relationTraitPatterns.map(_.name)
243 | val relationPattern = rawRelationPattern.copy(_superTypes = rawRelationPattern.superTypes diff externalSuperTypes)
244 | Relation(
245 | pattern = relationPattern,
246 | externalSuperTypes = externalSuperTypes,
247 | flatStatements = flatSuperStatements(relationTraitPatterns, relationPattern),
248 | traitFactoryParameterList = findSuperFactoryParameterList(relationTraitPatterns, relationPattern, relationTraits))
249 | }
250 |
251 | Schema(schemaPattern, nodes, relations, hyperRelations, nodeTraits, relationTraits, graphs, statements)
252 | }
253 |
254 | def findSuperFactoryParameterList[P <: NamePattern with SuperTypesPattern, Q <: Named with HasOwnFactory](patterns: List[_ <: P], pattern: P, nameClasses: List[Q]): List[ParameterList] = {
255 | patternToNameClasses(patternToFlatSuperTypesWithoutSelf(patterns, pattern), nameClasses).map(_.parameterList)
256 | }
257 |
258 | def patternToNameClasses[P <: Named with HasOwnFactory](patterns: List[_ <: NamePattern], nameClasses: List[P]): List[P] = nameClasses.filter(nameClass => patterns.map(_.name).contains(nameClass.name))
259 |
260 | def filterSuperTypes(patterns: List[_ <: NamePattern], pattern: SuperTypesPattern): List[String] = {
261 | pattern.superTypes intersect patterns.map(_.name)
262 | }
263 |
264 | def flatSuperStatements[P <: NamePattern with SuperTypesPattern with StatementsPattern](superTypePatterns: List[NamePattern with SuperTypesPattern with StatementsPattern], pattern: P): List[Tree] = {
265 | val superTypes: List[StatementsPattern with NamePattern with SuperTypesPattern] = pattern.superTypes.map(superType => nameToPattern(superTypePatterns, superType))
266 | val flatSuperTypes: List[StatementsPattern] = patternToFlatSuperTypesWithSelf(superTypePatterns, pattern)
267 | flatSuperTypes.flatMap(_.statements)
268 | }
269 |
270 | def nameToPattern[NP <: NamePattern, P <: NP](patterns: List[NP], name: String): NP = {
271 | val found = patterns.find(_.name == name)
272 | if(found.isEmpty)
273 | abort(s"nameToPattern: Cannot find `$name` in `${ patterns.map(_.name).mkString(", ") }`.")
274 | else
275 | found.get
276 | }
277 |
278 | def neighbours(nodePattern: NamePattern with SuperTypesPattern, relations: List[NamePattern with StartEndNodePattern], nodePatterns: List[NamePattern with SuperTypesPattern with StatementsPattern], nodeTraitPatterns: List[NodeTraitPattern]): List[(String, String, String)] = {
279 | val sources = patternToFlatSuperTypesWithSelf(nodeTraitPatterns, nodePattern).map(_.name)
280 | relations.filter(sources contains _.startNode).flatMap { r =>
281 | if(nodeTraitPatterns.map(_.name) contains r.endNode) {
282 | // if r.endNode is a trait
283 | // generate accessors for all childNodes
284 | val childNodes = childNodesOfNodeTrait(nodeTraitPatterns, nodePatterns, nameToPattern(nodeTraitPatterns, r.endNode))
285 | childNodes.map { childNode =>
286 | val accessorName = nameToPlural(r.name + childNode)
287 | (accessorName, r.name, childNode)
288 | }
289 | }
290 | else {
291 | val accessorName = nameToPlural(r.name)
292 | List((accessorName, r.name, r.endNode))
293 | }
294 | }
295 | }
296 |
297 | def rev_neighbours(nodePattern: NamePattern with SuperTypesPattern, relations: List[NamePattern with StartEndNodePattern], nodePatterns: List[NamePattern with SuperTypesPattern with StatementsPattern], nodeTraitPatterns: List[NodeTraitPattern]): List[(String, String, String)] = {
298 | val targets = patternToFlatSuperTypesWithSelf(nodeTraitPatterns, nodePattern).map(_.name)
299 | relations.filter(targets contains _.endNode).flatMap { r =>
300 | if(nodeTraitPatterns.map(_.name) contains r.startNode) {
301 | // if r.startNode is a trait
302 | val childNodes = childNodesOfNodeTrait(nodeTraitPatterns, nodePatterns, nameToPattern(nodeTraitPatterns, r.startNode))
303 | childNodes.map { childNode =>
304 | val accessorName = rev(nameToPlural(r.name + childNode))
305 | (accessorName, r.name, childNode)
306 | }
307 | }
308 | else {
309 | val accessorName = rev(nameToPlural(r.name))
310 | List((accessorName, r.name, r.startNode))
311 | }
312 | }
313 | }
314 |
315 | def isDeepSuperType[P <: NamePattern with SuperTypesPattern](patterns: List[P], subPattern: P, superPattern: P): Boolean = {
316 | subPattern.superTypes match {
317 | case Nil => false
318 | case superTypes => superTypes.exists { name =>
319 | superPattern.name == name || (patterns.exists(_.name == name) && isDeepSuperType(patterns, nameToPattern(patterns, name), superPattern))
320 | }
321 | }
322 | }
323 |
324 | def patternToSuperTypes[P <: NamePattern with SuperTypesPattern](patterns: List[P], pattern: P): List[P] = pattern.superTypes.map(nameToPattern(patterns, _))
325 |
326 | def patternToFlatSuperTypesWithoutSelf[P <: NamePattern with SuperTypesPattern, SUPER <: P](patterns: List[SUPER], pattern: P): List[SUPER] = {
327 | patterns.filter { superPattern => isDeepSuperType(patterns, pattern, superPattern) }
328 | }
329 |
330 | def patternToFlatSuperTypesWithSelf[P <: NamePattern with SuperTypesPattern, SUPER <: P](patterns: List[SUPER], pattern: P): List[P] = {
331 | pattern :: patternToFlatSuperTypesWithoutSelf(patterns, pattern)
332 | }
333 |
334 | def patternToSubTypes[P <: NamePattern with SuperTypesPattern](patterns: List[P], pattern: P): List[P] = patterns.filter(_.superTypes.contains(pattern.name))
335 |
336 | def patternToFlatSubTypesWithoutSelf[P <: NamePattern with SuperTypesPattern, SUB <: P](patterns: List[SUB], pattern: P): List[SUB] = {
337 | patterns.filter { subPattern => isDeepSuperType(patterns, subPattern, pattern) }
338 | }
339 |
340 | def patternToFlatSubTypesWithSelf[P <: NamePattern with SuperTypesPattern, SUB <: P](patterns: List[SUB], pattern: P): List[P] = {
341 | pattern :: patternToFlatSubTypesWithoutSelf(patterns, pattern)
342 | }
343 |
344 | // TODO: check usages if other callers also need intermediate traits
345 | def childNodesOfNodeTrait(nodeTraits: List[NodeTraitPattern], nodePatterns: List[NamePattern with SuperTypesPattern], nodeTrait: NodeTraitPattern): List[String] = {
346 | patternToFlatSubTypesWithSelf(nodeTraits, nodeTrait).flatMap { subTrait =>
347 | nodePatterns.filter(_.superTypes contains subTrait.name)
348 | }.distinct.map(_.name)
349 | }
350 |
351 | def childNodesOfNodeTraitsWithTraits(nodeTraits: List[NodeTraitPattern], nodePatterns: List[NamePattern with SuperTypesPattern], nodeTrait: NodeTraitPattern): List[String] = {
352 | (nodeTrait :: patternToFlatSubTypesWithoutSelf(nodeTraits, nodeTrait)).flatMap { subTrait =>
353 | subTrait :: nodePatterns.filter(_.superTypes contains subTrait.name)
354 | }.distinct.map(_.name)
355 | }
356 |
357 | def nodeNamesToRelations[R <: StartEndNodePattern](nodeNames: List[String], relations: List[R]): List[R] = {
358 | relations.filter(relation => nodeNames.contains(relation.startNode) && nodeNames.contains(relation.endNode))
359 | }
360 |
361 | def nodeTraitToCommonHyperRelationTraits[P <: NamePattern with SuperTypesPattern]
362 | (nodeTraitPatterns: List[NodeTraitPattern], middleNodeTraitPatterns: List[P], nodePatterns: List[NamePattern with SuperTypesPattern],
363 | hyperRelationPatterns: List[HyperRelationPattern], nodeTrait: NodeTraitPattern): List[String] = {
364 | val nodes = childNodesOfNodeTraitsWithTraits(nodeTraitPatterns, nodePatterns ::: hyperRelationPatterns, nodeTrait)
365 | val subHyperRelations = nodeNamesToRelations(nodes, hyperRelationPatterns)
366 | val flatSuperTypes: List[List[String]] = subHyperRelations.map(hyperRelation => patternToFlatSuperTypesWithoutSelf(middleNodeTraitPatterns, hyperRelation).map(_.name))
367 | if(flatSuperTypes.isEmpty) Nil
368 | else if(flatSuperTypes.size == 1) flatSuperTypes.head
369 | else flatSuperTypes.reduce(_ intersect _)
370 | }
371 |
372 | def graphToNodes(graphPatterns: List[GraphPattern], graphPattern: GraphPattern): List[String] = {
373 | // return explicitely named nodes in @Graph (including traits, but no HyperRelations)
374 | patternToFlatSuperTypesWithSelf(graphPatterns, graphPattern).flatMap(_.nodes).distinct
375 | }
376 |
377 | def inducedRelations(nodes: List[String], nodeNamePatterns: List[NamePattern with SuperTypesPattern],
378 | nodeTraitPatterns: List[NodeTraitPattern], hyperRelationPatterns: List[HyperRelationPattern],
379 | relations: List[NamePattern with StartEndNodePattern]): List[String] = {
380 | val traits = nodes.map(nameToPattern(nodeNamePatterns, _)).flatMap(patternToFlatSuperTypesWithSelf(nodeTraitPatterns, _)).distinct.map(_.name)
381 | nodeNamesToRelations(nodes ::: traits ::: hyperRelationPatterns.map(_.name), relations).map(_.name)
382 | }
383 |
384 | def traitCanHaveOwnFactory(hierarchyPatterns: List[NamePattern with SuperTypesPattern with StatementsPattern], currentTrait: NamePattern with SuperTypesPattern): Option[Boolean] = {
385 | val children = patternToFlatSubTypesWithoutSelf(hierarchyPatterns, currentTrait)
386 | val childrenWithFlatParents = children.flatMap(patternToFlatSuperTypesWithSelf(hierarchyPatterns, _)).distinct
387 | val subWithoutSuper = childrenWithFlatParents diff patternToFlatSuperTypesWithSelf(hierarchyPatterns, currentTrait)
388 | // if we currently are at NodeTrait, we need to check whether one of its
389 | // children is a HyperRelation. If that is the case, a factory cannot be
390 | // generated, as the HyperRelation additionally needs Start-/EndNode in
391 | // its create-method.
392 | val isNodeTrait = currentTrait.isInstanceOf[NodeTraitPattern]
393 | val hasHyperRelationChild = children.exists(_.isInstanceOf[HyperRelationPattern])
394 | //TODO: should return something else for matches without start/end
395 | if(isNodeTrait && hasHyperRelationChild)
396 | return None
397 |
398 | //TODO: anything we can do to avoid repeating outselves here all over again?
399 | val statements = subWithoutSuper.flatMap(_.statements)
400 | Some(statements.forall {
401 | case q"val $x:Option[$propertyType]" => true
402 | case q"var $x:Option[$propertyType]" => true
403 | case q"val $x:$propertyType = $y" => true
404 | case q"var $x:$propertyType = $y" => true
405 | case q"val $x:$propertyType" => false
406 | case q"var $x:$propertyType" => false
407 | case q"@unique val $x:Option[$propertyType]" => true
408 | case q"@unique var $x:Option[$propertyType]" => true
409 | case q"@unique val $x:$propertyType = $y" => true
410 | case q"@unique var $x:$propertyType = $y" => true
411 | case q"@unique val $x:$propertyType" => false
412 | case q"@unique var $x:$propertyType" => false
413 | case _ => true // custom statements
414 | })
415 | }
416 | }
417 |
418 | case class Schema(
419 | pattern: SchemaPattern,
420 | nodes: List[Node],
421 | relations: List[Relation],
422 | hyperRelations: List[HyperRelation],
423 | nodeTraits: List[NodeTrait],
424 | relationTraits: List[RelationTrait],
425 | graphs: List[Graph],
426 | statements: List[Tree]
427 | ) extends Named with SuperTypes {
428 | val externalSuperTypes = List.empty
429 | }
430 |
431 | case class Graph(
432 | pattern: GraphPattern,
433 | nodesWithHyperRelations: List[String],
434 | nodes: List[String],
435 | relationsWithHyperRelations: List[String],
436 | relations: List[String],
437 | hyperRelations: List[String],
438 | nodeTraits: List[NodeTrait]
439 | ) extends Named with SuperTypes {
440 | val externalSuperTypes = List.empty
441 | }
442 |
443 |
444 | case class NodeTrait(
445 | pattern: NodeTraitPattern,
446 | flatSuperTypesWithSelf: List[String], // only self and nodeTraits without external traits
447 | externalSuperTypes: List[String],
448 | neighbours: List[(String, String, String)], // accessorName, relation, endNode
449 | rev_neighbours: List[(String, String, String)], // accessorName, relation, startNode
450 | outRelationsToTrait: List[(String, String)],
451 | inRelationsFromTrait: List[(String, String)],
452 | subNodes: List[String],
453 | subRelations: List[String],
454 | subHyperRelations: List[String],
455 | commonHyperRelationNodeTraits: List[String],
456 | commonHyperRelationRelationTraits: List[String],
457 | flatStatements: List[Tree],
458 | hasOwnFactory: Option[Boolean]
459 | ) extends Named with SuperTypes with Statements with HasOwnFactory with HasParameterList with HasTraitFactoryParameterList {
460 |
461 | def commonHyperRelationNodeTraits_type = commonHyperRelationNodeTraits.map(TypeName(_))
462 | def commonHyperRelationRelationTraits_type = commonHyperRelationRelationTraits.map(TypeName(_))
463 |
464 | def neighbours_terms = neighbours.map { case (accessorName, relation, endNode) =>
465 | (TermName(accessorName), TermName(relation), TypeName(endNode), TermName(endNode))
466 | }
467 |
468 | def rev_neighbours_terms = rev_neighbours.map { case (rev_accessorName, relation, startNode) =>
469 | (TermName(rev_accessorName), TermName(relation), TypeName(startNode), TermName(startNode))
470 | }
471 |
472 | val parameterList = ParameterList.create(flatStatements, name, representsNode = true, hasOwnFactory)
473 |
474 | var traitFactoryParameterList: List[ParameterList] = null
475 | }
476 |
477 | object NodeTrait {
478 | def apply(
479 | rawNodeTraitPattern: NodeTraitPattern,
480 | nodeTraitPatterns: List[NodeTraitPattern],
481 | relationTraitPatterns: List[RelationTraitPattern],
482 | selectedNodePatterns: List[NamePattern with SuperTypesPattern with StatementsPattern],
483 | selectedHyperRelationPatterns: List[HyperRelationPattern],
484 | relationPatterns: List[RelationPattern],
485 | hyperRelationPatterns: List[HyperRelationPattern]
486 | ) = {
487 |
488 | import Schema.{childNodesOfNodeTrait, flatSuperStatements, nodeNamesToRelations, nodeTraitToCommonHyperRelationTraits, patternToFlatSuperTypesWithSelf, traitCanHaveOwnFactory}
489 |
490 | val allRelationPatterns = relationPatterns ::: hyperRelationPatterns
491 |
492 | val externalSuperTypes = rawNodeTraitPattern.superTypes diff nodeTraitPatterns.map(_.name)
493 | val nodeTraitPattern = rawNodeTraitPattern.copy(_superTypes = rawNodeTraitPattern.superTypes diff externalSuperTypes)
494 |
495 | val childNodes = childNodesOfNodeTrait(nodeTraitPatterns, selectedNodePatterns ::: selectedHyperRelationPatterns, nodeTraitPattern)
496 | val childTraits = childNodesOfNodeTrait(nodeTraitPatterns, nodeTraitPatterns, nodeTraitPattern)
497 | new NodeTrait(
498 | nodeTraitPattern,
499 | flatSuperTypesWithSelf = patternToFlatSuperTypesWithSelf(nodeTraitPatterns, nodeTraitPattern).map(_.name) intersect nodeTraitPatterns.map(_.name),
500 | externalSuperTypes = externalSuperTypes,
501 |
502 | neighbours = Schema.neighbours(nodeTraitPattern, allRelationPatterns, selectedNodePatterns, nodeTraitPatterns),
503 | rev_neighbours = Schema.rev_neighbours(nodeTraitPattern, allRelationPatterns, selectedNodePatterns, nodeTraitPatterns),
504 | outRelationsToTrait = allRelationPatterns.filter { r =>
505 | (patternToFlatSuperTypesWithSelf(nodeTraitPatterns, nodeTraitPattern).map(_.name) contains r.startNode) &&
506 | (nodeTraitPatterns.map(_.name) contains r.endNode)
507 | }.map(r => (r.name, r.endNode)),
508 | inRelationsFromTrait = allRelationPatterns.filter { r =>
509 | // endNode is this node or one of its supertypes
510 | // and startNode is a Node Trait
511 | (patternToFlatSuperTypesWithSelf(nodeTraitPatterns, nodeTraitPattern).map(_.name) contains r.endNode) &&
512 | (nodeTraitPatterns.map(_.name) contains r.startNode)
513 | }.map(r => (r.name, r.startNode)),
514 |
515 | subNodes = childNodes,
516 | subRelations = nodeNamesToRelations(nodeTraitPattern.name :: childNodes ::: childTraits, hyperRelationPatterns ::: relationPatterns).map(_.name),
517 | subHyperRelations = nodeNamesToRelations(nodeTraitPattern.name :: childNodes ::: childTraits, hyperRelationPatterns).map(_.name),
518 | commonHyperRelationNodeTraits = nodeTraitToCommonHyperRelationTraits(nodeTraitPatterns, nodeTraitPatterns, selectedNodePatterns, hyperRelationPatterns, nodeTraitPattern),
519 | commonHyperRelationRelationTraits = nodeTraitToCommonHyperRelationTraits(nodeTraitPatterns, relationTraitPatterns, selectedNodePatterns, hyperRelationPatterns, nodeTraitPattern),
520 | flatStatements = flatSuperStatements(nodeTraitPatterns, nodeTraitPattern),
521 | hasOwnFactory = traitCanHaveOwnFactory(selectedNodePatterns ::: hyperRelationPatterns ::: relationTraitPatterns ::: nodeTraitPatterns, rawNodeTraitPattern)
522 | )
523 | }
524 | }
525 |
526 | case class RelationTrait(
527 | pattern: RelationTraitPattern,
528 | externalSuperTypes: List[String],
529 | flatStatements: List[Tree],
530 | hasOwnFactory: Option[Boolean]
531 | ) extends Named with SuperTypes with Statements with HasOwnFactory with HasParameterList {
532 |
533 | val parameterList = ParameterList.create(flatStatements, name, representsNode = false, hasOwnFactory)
534 | }
535 |
536 | case class Node(
537 | pattern: NodePattern,
538 | flatSuperTypesWithSelf: List[String], // only self and nodeTraits without external traits
539 | externalSuperTypes: List[String],
540 | neighbours: List[(String, String, String)], // accessorName, relation, endNode
541 | rev_neighbours: List[(String, String, String)], // accessorName, relation, startNode
542 | outRelationsToTrait: List[(String, String)],
543 | inRelationsFromTrait: List[(String, String)],
544 | flatStatements: List[Tree],
545 | traitFactoryParameterList: List[ParameterList],
546 | implementedTrait: Option[NodeTrait]
547 | ) extends Named with SuperTypes with Statements with HasParameterList with HasTraitFactoryParameterList {
548 |
549 | val parameterList = ParameterList.create(flatStatements, name, representsNode = true)
550 |
551 | def isTraitImplementation = implementedTrait.isDefined
552 |
553 | def neighbours_terms = neighbours.map { case (accessorName, relation, endNode) =>
554 | (TermName(accessorName), TermName(relation), TypeName(endNode), TermName(endNode))
555 | }
556 |
557 | def rev_neighbours_terms = rev_neighbours.map { case (rev_accessorName, relation, startNode) =>
558 | (TermName(rev_accessorName), TermName(relation), TypeName(startNode), TermName(startNode))
559 | }
560 | }
561 |
562 | case class Relation(
563 | pattern: RelationPattern,
564 | externalSuperTypes: List[String],
565 | flatStatements: List[Tree], // TODO: rename to flatSuperStatements (same for node etc)
566 | traitFactoryParameterList: List[ParameterList]
567 | ) extends Named with StartEndNode with SuperTypes with Statements with HasParameterList with HasTraitFactoryParameterList {
568 |
569 | val parameterList = ParameterList.create(flatStatements, name, representsNode = false)
570 | }
571 |
572 | case class HyperRelation(
573 | pattern: HyperRelationPattern,
574 | superNodeTypes: List[String],
575 | flatSuperNodeTypesWithSelf: List[String], // only self and nodeTraits without external traits
576 | externalSuperTypes: List[String],
577 | superRelationTypes: List[String],
578 | neighbours: List[(String, String, String)], // accessorName, relation, endNode
579 | rev_neighbours: List[(String, String, String)], // accessorName, relation, startNode
580 | outRelationsToTrait: List[(String, String)],
581 | inRelationsFromTrait: List[(String, String)],
582 | flatSuperStatements: List[Tree],
583 | traitFactoryParameterList: List[ParameterList]
584 | ) extends Named with SuperTypes with StartEndNode with Statements with StartEndRelation with HasParameterList with HasTraitFactoryParameterList {
585 |
586 | val parameterList = ParameterList.create(flatSuperStatements, name, representsNode = true)
587 |
588 | //TODO: share code with Node
589 | def neighbours_terms = neighbours.map { case (accessorName, relation, endNode) =>
590 | (TermName(accessorName), TermName(relation), TypeName(endNode), TermName(endNode))
591 | }
592 |
593 | def rev_neighbours_terms = rev_neighbours.map { case (rev_accessorName, relation, startNode) =>
594 | (TermName(rev_accessorName), TermName(relation), TypeName(startNode), TermName(startNode))
595 | }
596 | }
597 |
598 | }
599 |
--------------------------------------------------------------------------------