@@ -8,6 +8,80 @@ case class TypeName(asString: String) extends AnyVal
88sealed trait Projection {
99 def andThen (accessor : Accessor , typeName : TypeName ): Projection =
1010 Property (this , accessor, typeName)
11+
12+ def rootProjection : TypeReference = {
13+ @ tailrec def loop (p : Projection ): TypeReference =
14+ p match {
15+ case p @ TypeReference (_) => p
16+ case Property (p, _, _) => loop(p)
17+ }
18+ loop(this )
19+ }
20+
21+ /**
22+ * Given a base projection, returns the projection based on it if applicable.
23+ *
24+ * For instance, given a quoted function
25+ * `val contact = Quoted.function { (c: Contact) => c.contact }`
26+ * and a call
27+ * `(p: Person) => contact(p.name)`
28+ * produces the projection
29+ * `Person.name.contact`
30+ */
31+ def basedOn (base : Projection ): Option [Projection ] =
32+ this match {
33+ case TypeReference (tpe) =>
34+ base match {
35+ case TypeReference (`tpe`) => Some (base)
36+ case Property (_, _, `tpe`) => Some (base)
37+ case other => None
38+ }
39+ case Property (path, name, tpe) =>
40+ path.basedOn(base).map(Property (_, name, tpe))
41+ }
42+
43+ /**
44+ * Limits projections to only values of `superClass`. Example:
45+ *
46+ * case class Person(name: String, contact: Contact) extends ThriftObject
47+ * case class Contact(phone: Phone) extends ThriftObject
48+ * case class Phone(number: String)
49+ *
50+ * For the super class `ThriftObject`, it produces the transformations:
51+ *
52+ * Person.contact.phone => Some(Person.contact.phone)
53+ * Person.contact.phone.number => Some(Person.contact.phone)
54+ * Person.name.isEmpty => Some(Person.name)
55+ * Phone.number => None
56+ */
57+ def bySuperClass (superClass : Class [_]): Option [Projection ] = {
58+
59+ def isSubclass (c : TypeName ) =
60+ try
61+ superClass.isAssignableFrom(Class .forName(c.asString))
62+ catch {
63+ case _ : ClassNotFoundException =>
64+ false
65+ }
66+
67+ def loop (p : Projection ): Either [Projection , Option [Projection ]] =
68+ p match {
69+ case TypeReference (typeName) =>
70+ Either .cond(! isSubclass(typeName), None , p)
71+ case p @ Property (path, name, typeName) =>
72+ loop(path) match {
73+ case Left (_) =>
74+ Either .cond(! isSubclass(typeName), Some (p), p)
75+ case Right (path) =>
76+ Right (path)
77+ }
78+ }
79+
80+ loop(this ) match {
81+ case Left (path) => Some (path)
82+ case Right (opt) => opt
83+ }
84+ }
1185}
1286
1387/**
@@ -30,81 +104,21 @@ final case class Property(path: Projection, accessor: Accessor, typeName: TypeNa
30104final class Projections private (val set : Set [Projection ]) extends Serializable {
31105
32106 /**
33- * Returns the projections that are based on `tpe ` and limits projections
107+ * Returns the projections that are based on `typeName ` and limits projections
34108 * to only properties that extend from `superClass`.
35109 */
36- def of (typeName : TypeName , superClass : Class [_]): Projections = {
37-
38- def byType (p : Projection ) = {
39- @ tailrec def loop (p : Projection ): Boolean =
40- p match {
41- case TypeReference (`typeName`) => true
42- case TypeReference (_) => false
43- case Property (p, _, _) => loop(p)
44- }
45- loop(p)
46- }
47-
48- def bySuperClass (p : Projection ): Option [Projection ] = {
49-
50- def isSubclass (c : TypeName ) =
51- try
52- superClass.isAssignableFrom(Class .forName(c.asString))
53- catch {
54- case _ : ClassNotFoundException =>
55- false
56- }
57-
58- def loop (p : Projection ): Either [Projection , Option [Projection ]] =
59- p match {
60- case TypeReference (tpe) =>
61- Either .cond(! isSubclass(tpe), None , p)
62- case p @ Property (path, name, tpe) =>
63- loop(path) match {
64- case Left (_) =>
65- Either .cond(! isSubclass(tpe), Some (p), p)
66- case Right (path) =>
67- Right (path)
68- }
69- }
70-
71- loop(p) match {
72- case Left (path) => Some (path)
73- case Right (opt) => opt
74- }
110+ def of (typeName : TypeName , superClass : Class [_]): Projections =
111+ Projections {
112+ set.filter(_.rootProjection.typeName == typeName)
113+ .flatMap(_.bySuperClass(superClass))
75114 }
76115
77- Projections (set.filter(byType).flatMap(bySuperClass))
78- }
79-
80- /**
81- * Given a set of base projections, returns the projections based on them.
82- *
83- * For instance, given a quoted function
84- * `val contact = Quoted.function { (c: Contact) => c.contact }`
85- * and a call
86- * `(p: Person) => contact(p.name)`
87- * returns the projection
88- * `Person.name.contact`
89- */
90- def basedOn (base : Set [Projection ]): Projections = {
91- def loop (base : Projection , p : Projection ): Option [Projection ] =
92- p match {
93- case TypeReference (tpe) =>
94- base match {
95- case TypeReference (`tpe`) => Some (base)
96- case Property (_, _, `tpe`) => Some (base)
97- case other => None
98- }
99- case Property (path, name, tpe) =>
100- loop(base, path).map(Property (_, name, tpe))
101- }
116+ def basedOn (base : Set [Projection ]): Projections =
102117 Projections {
103118 set.flatMap { p =>
104- base.flatMap(loop(_, p) )
119+ base.flatMap(p.basedOn )
105120 }
106121 }
107- }
108122
109123 def ++ (p : Projections ) =
110124 Projections (set ++ p.set)
@@ -115,7 +129,7 @@ final class Projections private (val set: Set[Projection]) extends Serializable
115129 override def equals (other : Any ) =
116130 other match {
117131 case other : Projections => set == other.set
118- case other => false
132+ case other => false
119133 }
120134
121135 override def hashCode =
@@ -135,7 +149,7 @@ object Projections {
135149 p match {
136150 case Property (path, acessor, property) =>
137151 set.contains(path) || isNested(path)
138- case _ =>
152+ case _ =>
139153 false
140154 }
141155 new Projections (set.filter(! isNested(_)))
0 commit comments