spotify / ratatool
1
/*
2
 * Copyright 2016 Spotify AB.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing,
11
 * software distributed under the License is distributed on an
12
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13
 * KIND, either express or implied.  See the License for the
14
 * specific language governing permissions and limitations
15
 * under the License.
16
 */
17

18
package com.spotify.ratatool.shapeless
19

20
import com.spotify.ratatool.diffy.{BigDiffy, Delta, Diffy, MultiKey}
21
import com.spotify.ratatool.diffy.BigDiffy.diff
22
import com.spotify.scio.coders.Coder
23
import com.spotify.scio.values.SCollection
24
import shapeless._
25
import shapeless.labelled.FieldType
26

27
import scala.reflect.ClassTag
28

29
@SerialVersionUID(42L)
30
sealed trait MapEncoder[A] extends Serializable {
31
  def toMap(in: A): Map[String, Any]
32
}
33

34
object MapEncoder {
35
  def apply[A](implicit encoder: MapEncoder[A]): MapEncoder[A] =
36
    encoder
37

38 2
  def createEncoder[A](func: A => Map[String, Any]): MapEncoder[A] = new MapEncoder[A] {
39 2
    override def toMap(in: A): Map[String, Any] = func(in)
40
  }
41

42
  implicit def stringEncoder[K <: Symbol](implicit witness: Witness.Aux[K]):
43
  MapEncoder[FieldType[K, String]] = {
44 2
    val name = witness.value.name
45 2
    createEncoder { v =>
46 2
      Map(name -> v.toString)
47
    }
48
  }
49

50
  implicit def intEncoder[K <: Symbol](implicit witness: Witness.Aux[K]):
51
  MapEncoder[FieldType[K, Int]] = {
52 2
    val name = witness.value.name
53 2
    createEncoder { v =>
54 2
      Map(name -> v.self)
55
    }
56
  }
57

58
  implicit def longEncoder[K <: Symbol](implicit witness: Witness.Aux[K]):
59
  MapEncoder[FieldType[K, Long]] = {
60 2
    val name = witness.value.name
61 2
    createEncoder { v =>
62 2
      Map(name -> v.self)
63
    }
64
  }
65

66
  implicit def booleanEncoder[K <: Symbol](implicit witness: Witness.Aux[K]):
67
  MapEncoder[FieldType[K, Boolean]] = {
68 0
    val name = witness.value.name
69 0
    createEncoder { v =>
70 0
      Map(name -> v.self)
71
    }
72
  }
73

74
  implicit def floatEncoder[K <: Symbol](implicit witness: Witness.Aux[K]):
75
  MapEncoder[FieldType[K, Float]] = {
76 0
    val name = witness.value.name
77 0
    createEncoder { v =>
78 0
      Map(name -> v.self)
79
    }
80
  }
81

82
  implicit def doubleEncoder[K <: Symbol](implicit witness: Witness.Aux[K]):
83
  MapEncoder[FieldType[K, Double]] = {
84 2
    val name = witness.value.name
85 2
    createEncoder { v =>
86 2
      Map(name -> v.self)
87
    }
88
  }
89

90
  implicit val hnilEncoder: MapEncoder[HNil] =
91 2
    createEncoder[HNil](n => Map(HNil.toString -> HNil))
92

93
  implicit def seqEncoder[K <: Symbol, V](implicit witness: Witness.Aux[K]):
94
  MapEncoder[FieldType[K, Seq[V]]] = {
95 2
    val name = witness.value.name
96 2
    createEncoder { v =>
97 2
      Map(name -> v.toIndexedSeq)
98
    }
99
  }
100

101
  implicit def listEncoder[K <: Symbol, V](implicit witness: Witness.Aux[K]):
102
  MapEncoder[FieldType[K, List[V]]] = {
103 0
    val name = witness.value.name
104 0
    createEncoder { v =>
105 0
      Map(name -> v.toIndexedSeq)
106
    }
107
  }
108

109
  /*
110
  * null.asInstanceOf[V] derives the default value of primitive types
111
  * null.asInstanceOf[Int] = 0
112
  * null.asInstanceOf[Boolean] = false
113
  * null.asInstanceOf[String] = null
114
  * */
115
  implicit def optionEncoder[K <: Symbol, V](implicit witness: Witness.Aux[K]):
116
  MapEncoder[FieldType[K, Option[V]]] = {
117 0
    val name = witness.value.name
118 0
    createEncoder { v =>
119 0
      Map(name -> v.getOrElse(null.asInstanceOf[V]))
120
    }
121
  }
122

123
  implicit def hlistEncoder0[K <: Symbol, H, T <: HList](
124
                    implicit hEncoder: Lazy[MapEncoder[FieldType[K, H]]],
125
                    tEncoder: Lazy[MapEncoder[T]]): MapEncoder[FieldType[K, H] :: T] = {
126 2
    createEncoder[FieldType[K, H] :: T] { in =>
127 2
      hEncoder.value.toMap(in.head) ++ tEncoder.value.toMap(in.tail)
128
    }
129
  }
130

131
  implicit def hListEncoder1[K <: Symbol, H, T <: HList, R <: HList](
132
                    implicit wit: Witness.Aux[K],
133
                    gen: LabelledGeneric.Aux[H, R],
134
                    encoderH: Lazy[MapEncoder[R]],
135
                    encoderT: Lazy[MapEncoder[T]]): MapEncoder[FieldType[K, H] :: T] =
136 2
    createEncoder(in =>
137 2
      encoderT.value.toMap(in.tail) ++ Map(wit.value.name -> encoderH.value.toMap(gen.to(in.head))))
138

139
  implicit def genericEncoder[A, R](implicit gen: LabelledGeneric.Aux[A, R],
140
                                    enc: MapEncoder[R]): MapEncoder[A] = {
141 2
    createEncoder(a => enc.toMap(gen.to(a)))
142
  }
143
}
144

145
class CaseClassDiffy[T](ignore: Set[String] = Set.empty,
146
                        unordered: Set[String] = Set.empty)(implicit diff: MapEncoder[T])
147
  extends Diffy[T](ignore, unordered) {
148
  import CaseClassDiffy._
149

150
  override def apply(x: T, y: T): Seq[Delta] = {
151 2
    diff(Some(asMap(x)), Some(asMap(y)))
152 2
      .filter(f => !hnilIgnore.exists(f.field.contains(_)))
153
  }
154

155 2
  private def hnilIgnore = ignore ++ Set(HNil.toString)
156

157
  private def diff(left: Any, right: Any, pref: String = ""): Seq[Delta] = {
158
    def diffMap(left: Map[String, Any], right: Map[String, Any], pref: String = pref) = {
159 2
      (left.keySet ++ right.keySet).toSeq
160 2
        .flatMap(k => diff(left.get(k), right.get(k), if (pref.isEmpty) k else s"$pref.$k"))
161
    }
162

163
    (left, right) match {
164 2
      case (Some(l: Map[String, Any]), Some(r: Map[String, Any])) => diffMap(l, r, pref)
165 2
      case (Some(l), Some(r)) => Seq(Delta(pref, Option(l), Option(r), delta(l, r)))
166
    }
167
  }
168
}
169

170
object CaseClassDiffy {
171 2
  private def asMap[T](in : T)(implicit diff: MapEncoder[T]) = diff.toMap(in)
172

173
  /** Diff two SCollection[T] **/
174
  def diffCaseClass[T : ClassTag : MapEncoder : Coder](lhs: SCollection[T],
175
                                               rhs: SCollection[T],
176
                                               keyFn: T => MultiKey,
177
                                               diffy: CaseClassDiffy[T]): BigDiffy[T] =
178 0
    diff(lhs, rhs, diffy, keyFn)
179
}

Read our documentation on viewing source code .

Loading