Skip to content

Commit 006e83e

Browse files
committed
Full support for automatic trait familly mapping
1 parent 23b9b17 commit 006e83e

File tree

8 files changed

+130
-62
lines changed

8 files changed

+130
-62
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.nullvector
2+
3+
import reactivemongo.api.bson.{BSON, BSONDocument}
4+
5+
import scala.reflect.ClassTag
6+
7+
class EventAdapterMapping[E](val manifest: String, fTags: Any => Set[String])
8+
(implicit mapping: BSONDocumentMapping[E], ev: ClassTag[E]
9+
) extends EventAdapter[E] {
10+
11+
def this(manifest: String)
12+
(implicit mapping: BSONDocumentMapping[E], ev: ClassTag[E]
13+
) = this(manifest, _ => Set.empty)
14+
15+
def this(manifest: String, tags: Set[String])
16+
(implicit mapping: BSONDocumentMapping[E], ev: ClassTag[E]
17+
) = this(manifest, _ => tags)
18+
19+
override def tags(payload: Any): Set[String] = fTags(payload)
20+
21+
override def payloadToBson(payload: E): BSONDocument = BSON.writeDocument(payload).get
22+
23+
override def bsonToPayload(doc: BSONDocument): E = BSON.readDocument[E](doc).get
24+
}

core/src/main/scala/org/nullvector/package.scala api/src/main/scala/org/nullvector/package.scala

+2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package org
22

3+
import reactivemongo.api.bson.BSONDocumentHandler
34
import reactivemongo.api.commands.{MultiBulkWriteResult, WriteResult}
45

56
import scala.concurrent.{ExecutionContext, Future}
67
import scala.util.{Failure, Success, Try}
78

89
package object nullvector {
10+
type BSONDocumentMapping[T] = BSONDocumentHandler[T]
911

1012
implicit def futureWriteResult2Try(futureResult: Future[WriteResult])(implicit ec: ExecutionContext): Future[Try[Unit]] = {
1113
futureResult.map(result =>

build.sbt

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ lazy val rxmongoVersion = "0.20.3"
77
lazy val commonSettings = Seq(
88
name := "akka-reactivemongo-plugin",
99
organization := "null-vector",
10-
version := "1.3.8",
10+
version := "1.3.9",
1111
scalaVersion := scala213,
1212
crossScalaVersions := supportedScalaVersions,
1313
scalacOptions := Seq(
@@ -51,7 +51,7 @@ lazy val commonSettings = Seq(
5151

5252
lazy val core = (project in file("core"))
5353
.dependsOn(
54-
macros % "compile-internal, test-internal",
54+
macros,
5555
api)
5656
.settings(
5757
commonSettings,

core/src/test/scala/org/nullvector/EventAdapterFactorySpec.scala

+17-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package org.nullvector
22

33
import akka.actor.ActorSystem
44
import org.nullvector.domin._
5+
import org.nullvector.domin.planets.{Jupiter, Mars, PlanetDistanceBetweenEarth, SolarPlanet}
56
import org.scalatest.Matchers._
67
import org.scalatest._
78
import reactivemongo.api.bson.MacroConfiguration.Aux
8-
import reactivemongo.api.bson.{BSON, BSONDocument, BSONDocumentHandler, BSONDocumentReader, BSONDocumentWriter, BSONReader, BSONString, BSONValue, BSONWriter, FieldNaming, MacroConfiguration, MacroOptions, Macros, TypeNaming}
9+
import reactivemongo.api.bson.{BSONDocument, BSONDocumentReader, BSONReader, BSONString, BSONValue, BSONWriter, MacroConfiguration, MacroOptions, Macros, TypeNaming}
910

1011
import scala.util.{Success, Try}
1112

@@ -113,6 +114,21 @@ class EventAdapterFactorySpec extends FlatSpec {
113114
eventAdapter.bsonToPayload(document) should be (Jupiter)
114115
}
115116

117+
it should "create EventAdapter by hand" in {
118+
val jupiter: SolarPlanet = Jupiter
119+
120+
implicit val conf: Aux[MacroOptions] = MacroConfiguration(discriminator = "_type", typeNaming = TypeNaming.SimpleName)
121+
122+
implicit val a: BSONDocumentMapping[SolarPlanet] = EventAdapterFactory.mappingOf[SolarPlanet]
123+
124+
val eventAdapter = new EventAdapterMapping[SolarPlanet]("planet")
125+
126+
val document = eventAdapter.payloadToBson(jupiter)
127+
128+
document.getAsOpt[String]("_type").get should be ("Jupiter")
129+
eventAdapter.bsonToPayload(document) should be (Jupiter)
130+
}
131+
116132
}
117133

118134

core/src/test/scala/org/nullvector/domin/package.scala

-20
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,4 @@ package object domin {
3838

3939
case object Sunday extends Day
4040

41-
case class PlanetDistanceBetweenEarth(and: SolarPlanet, kilometers: Double)
42-
43-
sealed trait SolarPlanet
44-
45-
case object Mercury extends SolarPlanet
46-
47-
case object Venus extends SolarPlanet
48-
49-
case object Earth extends SolarPlanet
50-
51-
case object Mars extends SolarPlanet
52-
53-
case object Jupiter extends SolarPlanet
54-
55-
case object Saturn extends SolarPlanet
56-
57-
case object Uranus extends SolarPlanet
58-
59-
case object Neptune extends SolarPlanet
60-
6141
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.nullvector.domin
2+
3+
package object planets {
4+
5+
case class PlanetDistanceBetweenEarth(and: SolarPlanet, kilometers: Double)
6+
7+
sealed trait SolarPlanet
8+
9+
case object Mercury extends SolarPlanet
10+
11+
case object Venus extends SolarPlanet
12+
13+
case object Earth extends SolarPlanet
14+
15+
case object Mars extends SolarPlanet
16+
17+
case object Jupiter extends SolarPlanet
18+
19+
case object Saturn extends SolarPlanet
20+
21+
case object Uranus extends SolarPlanet
22+
23+
case object Neptune extends SolarPlanet
24+
25+
}

macros/src/main/scala/org/nullvector/EventAdapterFactory.scala

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ object EventAdapterFactory {
44

55
def adapt[E](withManifest: String): EventAdapter[E] = macro EventAdapterMacroFactory.adapt[E]
66

7+
def mappingOf[T]: BSONDocumentMapping[T] = macro EventAdapterMacroFactory.mappingOf[T]
8+
79
def adapt[E](withManifest: String, tags: Any => Set[String]): EventAdapter[E] = macro EventAdapterMacroFactory.adaptWithPayload2Tags[E]
810

911
def adapt[E](withManifest: String, tags: Set[String]): EventAdapter[E] = macro EventAdapterMacroFactory.adaptWithTags[E]

macros/src/main/scala/org/nullvector/EventAdapterMacroFactory.scala

+58-39
Original file line numberDiff line numberDiff line change
@@ -6,77 +6,96 @@ import scala.reflect.macros.blackbox
66

77
private object EventAdapterMacroFactory {
88

9-
private val supportedClassTypes = List(
10-
"scala.Option",
11-
"scala.collection.immutable.List",
12-
"scala.collection.immutable.Seq",
13-
"scala.collection.Seq",
14-
"scala.collection.immutable.Set",
15-
"scala.collection.immutable.Map",
16-
)
9+
def mappingOf[E](context: blackbox.Context)(implicit eventTypeTag: context.WeakTypeTag[E]): context.Expr[BSONDocumentMapping[E]] = {
10+
import context.universe._
11+
val (imports, implicits) = implicitMappingsFor(context)(eventTypeTag.tpe, noImplicitForMainType = true)
12+
val code =
13+
q"""
14+
import reactivemongo.api.bson._
15+
import org.nullvector._
16+
..$imports
17+
..$implicits
18+
"""
19+
//println(code)
20+
context.Expr[BSONDocumentMapping[E]](code)
21+
}
1722

1823
def adaptWithTags[E](context: blackbox.Context)(withManifest: context.Expr[String], tags: context.Expr[Set[String]])
1924
(implicit eventTypeTag: context.WeakTypeTag[E]): context.Expr[EventAdapter[E]] = {
2025
import context.universe._
21-
buildAdapterExpression(context)(withManifest, q"override def tags(payload: Any) = $tags")
26+
buildAdapterExpression(context)(withManifest, q"new EventAdapterMapping($withManifest, $tags)")
2227
}
2328

2429
def adaptWithPayload2Tags[E](context: blackbox.Context)(withManifest: context.Expr[String], tags: context.Expr[Any => Set[String]])
2530
(implicit eventTypeTag: context.WeakTypeTag[E]): context.Expr[EventAdapter[E]] = {
2631
import context.universe._
27-
buildAdapterExpression(context)(withManifest, q"override def tags(payload: Any) = $tags(payload)")
32+
buildAdapterExpression(context)(withManifest, q"new EventAdapterMapping($withManifest, $tags)")
2833
}
2934

3035
def adapt[E](context: blackbox.Context)(withManifest: context.Expr[String])
3136
(implicit eventTypeTag: context.WeakTypeTag[E]): context.Expr[EventAdapter[E]] = {
32-
buildAdapterExpression(context)(withManifest, context.universe.EmptyTree)
37+
import context.universe._
38+
buildAdapterExpression(context)(withManifest, q"new EventAdapterMapping($withManifest)")
3339
}
3440

3541
private def buildAdapterExpression[E](context: blackbox.Context)
3642
(withManifest: context.Expr[String],
37-
tags: context.universe.Tree
43+
createEventAdapter: context.universe.Tree
3844
)
3945
(implicit eventTypeTag: context.WeakTypeTag[E]): context.Expr[EventAdapter[E]] = {
4046

4147
import context.universe._
4248
val eventType = eventTypeTag.tpe
43-
val eventAdapterTypeName = TypeName(eventType.toString + "EventAdapter")
44-
val handlers: Seq[context.Tree] = implicitMappingsFor(context)(eventType)
49+
val (imports, handlers) = implicitMappingsFor(context)(eventType, noImplicitForMainType = false)
4550
val code =
4651
q"""
47-
import reactivemongo.api.bson._
48-
class $eventAdapterTypeName extends org.nullvector.EventAdapter[$eventType]{
49-
val manifest: String = $withManifest
50-
..$handlers
51-
override def payloadToBson(payload: $eventType): BSONDocument = BSON.writeDocument(payload).get
52-
override def bsonToPayload(doc: BSONDocument): $eventType = BSON.readDocument[$eventType](doc).get
53-
$tags
54-
}
55-
new $eventAdapterTypeName
56-
"""
57-
//println(code)
52+
import reactivemongo.api.bson._
53+
import org.nullvector._
54+
..$imports
55+
..$handlers
56+
$createEventAdapter
57+
"""
58+
//println(code)hhh
5859
context.Expr[EventAdapter[E]](code)
5960
}
6061

6162
private def implicitMappingsFor(context: blackbox.Context)
62-
(eventType: context.universe.Type,
63-
): List[context.universe.Tree] = {
63+
(eventType: context.universe.Type, noImplicitForMainType: Boolean = false
64+
): (Set[context.Tree], List[context.Tree]) = {
6465
import context.universe._
6566

6667
val bsonWrtterType = context.typeOf[BSONWriter[_]]
6768
val bsonReaderType = context.typeOf[BSONReader[_]]
6869

6970
val caseClassTypes = extractCaseTypes(context)(eventType).toList.reverse.distinct
7071

71-
caseClassTypes.collect {
72-
case caseType if context.inferImplicitValue(appliedType(bsonWrtterType, caseType)).isEmpty &&
73-
context.inferImplicitValue(appliedType(bsonReaderType, caseType)).isEmpty =>
74-
q" private implicit val ${TermName(context.freshName())}: BSONDocumentHandler[$caseType] = Macros.handler[$caseType]"
72+
@scala.annotation.tailrec
73+
def findPackage(symbol: Symbol): Option[String] = {
74+
symbol match {
75+
case NoSymbol => None
76+
case _ if !symbol.isPackage => findPackage(symbol.owner)
77+
case _ if symbol.isPackage => Some(symbol.fullName)
78+
case _ => None
79+
}
80+
}
81+
82+
val (mappedTypes, mappingCode) = caseClassTypes.collect {
83+
case caseType if noImplicitForMainType & caseType =:= eventType =>
84+
caseType -> q"Macros.handler[$caseType]"
85+
case caseType if context.inferImplicitValue(appliedType(bsonWrtterType, caseType)).isEmpty && context.inferImplicitValue(appliedType(bsonReaderType, caseType)).isEmpty =>
86+
caseType -> q"private implicit val ${TermName(context.freshName())}: BSONDocumentHandler[${caseType}] = Macros.handler[${caseType}]"
7587
case caseType if !context.inferImplicitValue(appliedType(bsonReaderType, caseType)).isEmpty =>
76-
q" private implicit val ${TermName(context.freshName())}: BSONWriter[$caseType] = Macros.handler[$caseType]"
88+
caseType -> q"private implicit val ${TermName(context.freshName())}: BSONWriter[${caseType}] = Macros.handler[${caseType}]"
7789
case caseType if !context.inferImplicitValue(appliedType(bsonWrtterType, caseType)).isEmpty =>
78-
q" private implicit val ${TermName(context.freshName())}: BSONReader[$caseType] = Macros.handler[$caseType]"
79-
}
90+
caseType -> q"private implicit val ${TermName(context.freshName())}: BSONReader[${caseType}] = Macros.handler[${caseType}]"
91+
}.unzip
92+
(
93+
mappedTypes
94+
.flatMap(tpe => findPackage(tpe.typeSymbol))
95+
.toSet
96+
.map((packageName: String) => context.parse(s"import $packageName._")),
97+
mappingCode
98+
)
8099
}
81100

82101
private def extractCaseTypes(context: blackbox.Context)
@@ -85,11 +104,11 @@ private object EventAdapterMacroFactory {
85104

86105
def isSupprtedTrait(aTypeClass: ClassSymbol) = aTypeClass.isTrait && aTypeClass.isSealed && !aTypeClass.fullName.startsWith("scala")
87106

88-
def extaracCaseClassesFromSupportedTypeClasses(classType: Type): List[Type] = {
89-
if (supportedClassTypes.contains(classType.typeSymbol.fullName)) classType.typeArgs.collect {
107+
def extaracCaseClassesFromTypeArgs(classType: Type): List[Type] = {
108+
classType.typeArgs.collect {
90109
case argType if argType.typeSymbol.asClass.isCaseClass => List(classType, argType)
91-
case t => extaracCaseClassesFromSupportedTypeClasses(t)
92-
}.flatten else Nil
110+
case t => extaracCaseClassesFromTypeArgs(t)
111+
}.flatten
93112
}
94113

95114
if (caseType.typeSymbol.asClass.isCaseClass) {
@@ -98,7 +117,7 @@ private object EventAdapterMacroFactory {
98117
.collect { case method: MethodSymbol if method.isCaseAccessor => method.returnType }
99118
.collect {
100119
case aType if aType.typeSymbol.asClass.isCaseClass || isSupprtedTrait(aType.typeSymbol.asClass) => List(extractCaseTypes(context)(aType))
101-
case aType => extaracCaseClassesFromSupportedTypeClasses(aType).map(arg => extractCaseTypes(context)(arg))
120+
case aType => extaracCaseClassesFromTypeArgs(aType).map(arg => extractCaseTypes(context)(arg))
102121
}.flatten
103122
)
104123
}

0 commit comments

Comments
 (0)