├── 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 | 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 | [![Build Status](https://travis-ci.org/renesca/renesca-magic.svg?branch=master)](https://travis-ci.org/renesca/renesca-magic) 3 | [![Coverage Status](https://coveralls.io/repos/renesca/renesca-magic/badge.svg?branch=master)](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 | --------------------------------------------------------------------------------