aboutsummaryrefslogtreecommitdiff
path: root/examples/shared/src/main/scala/patch.scala
blob: 668a2e2cc7da57a928d57829e0dda3dd89e9aa35 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package magnolia.examples

import scala.language.experimental.macros
import magnolia._

/**
  * Type class for copying an instance of some type `T`,
  * thereby replacing certain fields with other values.
  */
sealed abstract class Patcher[T] {

  /**
    * Returns a copy of `value` whereby all non-null elements of `fieldValues`
    * replace the respective fields of `value`.
    * For all null elements of `fieldValues` the original value of the
    * respective field of `value` is maintained.
    *
    * If the size of `fieldValues` doesn't exactly correspond to the
    * number of fields of `value` an [[IllegalArgumentException]] is thrown.
    */
  def patch(value: T, fieldValues: Seq[Any]): T
}

object Patcher extends LowerPriorityPatcher {

  type Typeclass[T] = Patcher[T]

  def combine[T](ctx: CaseClass[Patcher, T]): Patcher[T] =
    new Patcher[T] {
      def patch(value: T, fieldValues: Seq[Any]): T = {
        if (fieldValues.lengthCompare(ctx.parameters.size) != 0) {
          throw new IllegalArgumentException(
          s"Cannot patch value `$value`, expected ${ctx.parameters.size} fields but got ${fieldValues.size}")
        }
        val effectiveFields = ctx.parameters.zip(fieldValues).map {
          case (param, x) => if (x.asInstanceOf[AnyRef] ne null) x else param dereference value
        }
        ctx.rawConstruct(effectiveFields)
      }
    }

  def dispatch[T](ctx: SealedTrait[Patcher, T]): Patcher[T] =
    new Patcher[T] {
      def patch(value: T, fieldValues: Seq[Any]): T =
        ctx.dispatch(value)(sub  sub.typeclass.patch(sub cast value, fieldValues))
    }

  implicit def gen[T]: Patcher[T] = macro Magnolia.gen[T]
}

sealed abstract class LowerPriorityPatcher {

  private[this] val _forSingleValue =
    new Patcher[Any] {
      def patch(value: Any, fieldValues: Seq[Any]): Any = {
        if (fieldValues.lengthCompare(1) != 0)
          throw new IllegalArgumentException(
            s"Cannot patch single value `$value` with patch sequence of size ${fieldValues.size}")
        val head = fieldValues.head
        if (head.getClass != value.getClass)
          throw new IllegalArgumentException(
            s"Illegal patch value type. Expected `${value.getClass}` but got `${head.getClass}`")
        head
      }
    }

  // once https://github.com/propensive/magnolia/issues/58 is fixed this can be marked `implicit`
  def forSingleValue[T]: Patcher[T] = _forSingleValue.asInstanceOf[Patcher[T]]
}