All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rojoma.json.v3.zipper.JsonZipper.scala Maven / Gradle / Ivy

The newest version!
package com.rojoma.json.v3
package zipper

import scala.language.implicitConversions
import scala.annotation.tailrec

import ast._
import codec.Path

/** A common parent representing both [[com.rojoma.json.v3.zipper.JsonZipper]]s and the
 * [[com.rojoma.json.v3.zipper.NothingZipper]]s which result from removing items from
 * the tree of [[com.rojoma.json.v3.ast.JValue]]s. */
sealed trait ZipperLike {
  /** Move to the parent object.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the parent object,
   *   or `None` if this is the top-level object. */
  def up: Option[JsonZipper]

  /** Move to the parent object.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the parent object.
   * @throws NoSuchElementException if this is the top-level object. */
  def up_! : JsonZipper

  /** Move to the next element in the parent array.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the next element,
   *   or `None` if there is no next element or if the parent is not a
   *   [[com.rojoma.json.v3.ast.JArray]]. */
  def next: Option[JsonZipper]

  /** Move to the previous element in the parent array.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the previous element,
   *   or `None` if there is no previous element or if the parent is not a
   *   [[com.rojoma.json.v3.ast.JArray]]. */
  def prev: Option[JsonZipper]

  /** Move to the next element in the parent array.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the next element.
   * @throws NoSuchElementException if there is no next element or if the parent is not
   *   a [[com.rojoma.json.v3.ast.JArray]]. */
  def next_! = next.getOrElse(throw new NoSuchElementException("No next sibling"))

  /** Move to the previous element in the parent array.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the previous element.
   * @throws NoSuchElementException if there is no previous element or if the parent is not
   *   a [[com.rojoma.json.v3.ast.JArray]]. */
  def prev_! = prev.getOrElse(throw new NoSuchElementException("No previous sibling"))

  /** Move to a different field in the parent object.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the new field,
   *   or `None` if that field does not exist or the parent is not a
   *   [[com.rojoma.json.v3.ast.JObject]]. */
  def sibling(field: String): Option[JsonZipper]

  /** Move to a different field in the parent object.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the new field.
   * @throws NoSuchElementException if there is no such field or if the parent is not a
   *   [[com.rojoma.json.v3.ast.JObject]]. */
  def sibling_!(field: String) = sibling(field).getOrElse(throw new NoSuchElementException("No sibling " + field))

  /** Replace the current value with an atom.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the same location but
   *   with the current value replaced. */
  def replace(newValue: JAtom): JAtomZipper

  /** Replace the current value with an array.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the same location but
   *   with the current value replaced. */
  def replace(newValue: JArray): JArrayZipper

  /** Replace the current value with an object.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the same location but
   *   with the current value replaced. */
  def replace(newValue: JObject): JObjectZipper

  /** Replace the current value with a new value.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the same location but
   *   with the current value replaced. */
  def replace(newValue: JValue): JsonZipper = newValue match {
    case atom: JAtom => replace(atom)
    case array: JArray => replace(array)
    case obj: JObject => replace(obj)
  }

  private[zipper] def reversePath: List[Path.Entry]
  def path: Path = new Path(reversePath.reverse)
}

/** A zipper referencing the hole left after a `remove` operation. */
sealed trait NothingZipper extends ZipperLike {
  /** Move up the chain of parents to the top of the object.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the top object,
   *   or `None` if the top object was removed. */
  def top : Option[JsonZipper]

  /** Move up the chain of parents to the top of the object.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the top object.
   * @throws NoSuchElementException if the top object was removed. */
  def top_! : JsonZipper
}

/** A zipper that points somewhere in the tree defined by a [[com.rojoma.json.v3.ast.JValue]].
 * It can be used to move around or update the tree in a purely functional manner.
 *
 * @see [[com.rojoma.json.v3.jpath.JPath]] for a higher-level read-only interface to this
 *   functionality. */
sealed trait JsonZipper extends ZipperLike {
  type ValueType <: JValue

  /** The value stored at this location */
  def value: ValueType

  /** Move up the chain of parents to the top of the object.
   *
   * @return A [[com.rojoma.json.v3.zipper.JsonZipper]] pointing at the top object. */
  def top : JsonZipper

  /** Remove the current value from the tree.
   *
   * @return A [[com.rojoma.json.v3.zipper.NothingZipper]] pointing at the hole
   *   left by removing the current value. */
  def remove: NothingZipper

  /** Safe downcast to [[com.rojoma.json.v3.zipper.JAtomZipper]] */
  def asAtom = this.cast[JAtomZipper]
  /** Safe downcast to [[com.rojoma.json.v3.zipper.JArrayZipper]] */
  def asArray = this.cast[JArrayZipper]
  /** Safe downcast to [[com.rojoma.json.v3.zipper.JObjectZipper]] */
  def asObject = this.cast[JObjectZipper]

  def adjust(f: ValueType => JValue): JsonZipper = replace(f(value))
}

/** A [[com.rojoma.json.v3.zipper.JsonZipper]] that points to a [[com.rojoma.json.v3.ast.JAtom]]. */
sealed trait JAtomZipper extends JsonZipper {
  type ValueType = JAtom
}

object JAtomZipper {
  def apply(value: JAtom) = new TopLevelAtomZipper(value)
  def unapply(zipper: JsonZipper) = zipper.cast[JAtomZipper].map(_.value)
}

/** A [[com.rojoma.json.v3.zipper.JsonZipper]] that points to a [[com.rojoma.json.v3.ast.JArray]]. */
sealed trait JArrayZipper extends JsonZipper {
  type ValueType = JArray

  private def subZipper(elem: JValue, idx: Int): JsonZipper = elem match {
    case atom: JAtom => new UnchangedArrayElementAtomZipper(atom, this, idx)
    case array: JArray => new UnchangedArrayElementArrayZipper(array, this, idx)
    case obj: JObject => new UnchangedArrayElementObjectZipper(obj, this, idx)
  }

  def down(idx: Int): Option[JsonZipper] =
    if(idx < 0 || idx >= length) None
    else Some(subZipper(value(idx), idx))

  def down_!(idx: Int): JsonZipper =
    subZipper(value(idx), idx)

  def first = down(0)
  def first_! = down_!(0)
  def last = down(size - 1)
  def last_! = down_!(size - 1)

  def remove(idx: Int) = down(idx) match {
    case Some(child) => child.remove.up_!.asInstanceOf[JArrayZipper]
    case None => None
  }

  def map(f: JValue => JValue): JArrayZipper
  def collect(f: PartialFunction[JValue, JValue]): JArrayZipper

  def set(idx: Int, value: JValue): JArrayZipper = down_!(idx).replace(value).up_!.asInstanceOf[JArrayZipper]
  def :+(value: JValue): JArrayZipper
  def +:(value: JValue): JArrayZipper

  def isEmpty = value.isEmpty
  def nonEmpty = value.nonEmpty

  lazy val size = value.size
  def length = size
}

object JArrayZipper {
  def apply(value: JArray): JArrayZipper = new TopLevelArrayZipper(value)
  def unapply(zipper: JsonZipper) = zipper.cast[JArrayZipper].map(_.value)
}

/** A [[com.rojoma.json.v3.zipper.JsonZipper]] that points to a [[com.rojoma.json.v3.ast.JObject]]. */
sealed trait JObjectZipper extends JsonZipper {
  type ValueType = JObject

  private def subZipper(elem: JValue, field: String): JsonZipper = elem match {
    case atom: JAtom => new UnchangedObjectElementAtomZipper(atom, this, field)
    case array: JArray => new UnchangedObjectElementArrayZipper(array, this, field)
    case obj: JObject => new UnchangedObjectElementObjectZipper(obj, this, field)
  }

  def down(field: String): Option[JsonZipper] = value.get(field) match {
    case Some(elem) => Some(subZipper(elem, field))
    case None => None
  }

  def down_!(field: String): JsonZipper = subZipper(value(field), field)

  def remove(field: String): JObjectZipper =
    down(field) match {
      case Some(child) => child.remove.up_!.asInstanceOf[JObjectZipper]
      case None => this
    }

  def set(field: String, value: JValue): JObjectZipper

  def map(f: ((String, JValue)) => JValue): JObjectZipper

  def contains(f: String) = value.contains(f)
}

object JObjectZipper {
  def apply(value: JObject): JObjectZipper = new TopLevelObjectZipper(value)
  def unapply(zipper: JsonZipper) = zipper.cast[JObjectZipper].map(_.value)
}

object JsonZipper {
  def apply(value: JAtom) = JAtomZipper(value)
  def apply(value: JArray) = JArrayZipper(value)
  def apply(value: JObject) = JObjectZipper(value)
  def apply(value: JValue): JsonZipper = value match {
    case atom: JAtom => apply(atom)
    case array: JArray => apply(array)
    case obj: JObject => apply(obj)
  }

  def unapply(zipper: JsonZipper): Option[JValue] = Some(zipper.value)

  @deprecated(message = "Do not use this function", since = "3.14.1")
  def toCastable[T <: JsonZipper](x: T): com.rojoma.`json-impl`.DownCaster[T] = new com.rojoma.`json-impl`.DownCaster(x)

  implicit def toCorrectCastable[T <: JsonZipper](x: T): com.rojoma.json.v3.`-impl`.GenericDownCaster[T] = new com.rojoma.json.v3.`-impl`.GenericDownCaster(x)
}

// TOP LEVEL

private[zipper] class TopLevelZipperLike { this: ZipperLike =>
  def up = None
  def up_! = throw new NoSuchElementException("Cannot go up from the top level")

  def next = None
  def prev = None

  def sibling(field: String) = None

  def replace(newValue: JAtom) = JAtomZipper(newValue)
  def replace(newValue: JArray) = JArrayZipper(newValue)
  def replace(newValue: JObject) = JObjectZipper(newValue)

  private[zipper] def reversePath = Nil
}

private[zipper] object TopLevelNothingZipper extends TopLevelZipperLike with NothingZipper {
  def top = None
  def top_! = throw new NoSuchElementException("No top-level object")
}

private[zipper] class TopLevelZipper extends TopLevelZipperLike { this: JsonZipper =>
  def remove = TopLevelNothingZipper
  def top : JsonZipper = this
}

private[zipper] class TopLevelAtomZipper(val value: JAtom) extends TopLevelZipper with JAtomZipper
private[zipper] class TopLevelArrayZipper(val value: JArray) extends TopLevelZipper with JArrayZipper {
  def :+(newValue: JValue): JArrayZipper = new TopLevelArrayZipper(JArray(value.toSeq :+ newValue))
  def +:(newValue: JValue): JArrayZipper = new TopLevelArrayZipper(JArray(newValue +: value.toSeq))

  def map(f: JValue => JValue) = new TopLevelArrayZipper(JArray(value.toSeq.map(f)))
  def collect(f: PartialFunction[JValue, JValue]) = new TopLevelArrayZipper(JArray(value.toSeq.collect(f)))
}
private[zipper] class TopLevelObjectZipper(val value: JObject) extends TopLevelZipper with JObjectZipper {
  def set(newField: String, newValue: JValue): JObjectZipper = new TopLevelObjectZipper(JObject(value.fields + (newField -> newValue)))
  def map(f: ((String, JValue)) => JValue) = new TopLevelObjectZipper(JObject(value.fields.map { kv => (kv._1, f(kv)) }))
}

// CODE COMMON TO ALL "ELEMENT" ZIPPER(LIKE)S

private[zipper] abstract class ElementZipperLike[Parent <: JsonZipper](parent : Parent) { this: ZipperLike =>
  def up_! = parent
  def up = Some(up_!)
}

private[zipper] trait ElementZipper[Parent <: JsonZipper] { this: ElementZipperLike[Parent] with JsonZipper =>
  def top = {
    @tailrec
    def loop(last: JsonZipper, next: Option[JsonZipper]): JsonZipper = next match {
      case None => last
      case Some(p) => loop(p, p.up)
    }
    loop(this, up)
  }
}

private[zipper] trait ElementNothingZipper[Parent <: JsonZipper] { this: ElementZipperLike[Parent] with NothingZipper =>
  def top_! : JsonZipper = up_!.top
  def top = Some(top_!)
}

// ARRAY ELEMENT, CHANGED OR NOT

private[zipper] abstract class ArrayElementZipperLike(p: JArrayZipper, val idxInParent: Int) extends ElementZipperLike(p) { this: ZipperLike =>
  def replace(newValue: JAtom) = new ChangedArrayElementAtomZipper(newValue, up_!, idxInParent)
  def replace(newValue: JArray) = new ChangedArrayElementArrayZipper(newValue, up_!, idxInParent)
  def replace(newValue: JObject) = new ChangedArrayElementObjectZipper(newValue, up_!, idxInParent)

  def remove = new ChangedArrayElementNothingZipper(up_!, idxInParent)

  def next : Option[JsonZipper] = up_!.down(idxInParent + 1)
  def prev : Option[JsonZipper] = up_!.down(idxInParent - 1)

  def sibling(field: String) = None

  private[zipper] def reversePath = Path.Index(idxInParent) :: p.reversePath
}

private[zipper] abstract class ArrayElementZipper(p: JArrayZipper, i: Int) extends ArrayElementZipperLike(p, i) with ElementZipper[JArrayZipper] { this: JsonZipper =>
}

private[zipper] trait ArrayElementArrayZipper { this: ArrayElementZipper with JArrayZipper =>
  def :+(newValue: JValue): JArrayZipper = new ChangedArrayElementArrayZipper(JArray(value.toSeq :+ newValue), up_!, idxInParent)
  def +:(newValue: JValue): JArrayZipper = new ChangedArrayElementArrayZipper(JArray(newValue +: value.toSeq), up_!, idxInParent)

  def map(f: JValue => JValue) = new ChangedArrayElementArrayZipper(JArray(value.toSeq.map(f)), up_!, idxInParent)
  def collect(f: PartialFunction[JValue, JValue]) = new ChangedArrayElementArrayZipper(JArray(value.toSeq.collect(f)), up_!, idxInParent)
}

private[zipper] trait ArrayElementObjectZipper { this: ArrayElementZipper with JObjectZipper =>
  def set(newField: String, newValue: JValue): JObjectZipper = new ChangedArrayElementObjectZipper(JObject(value.fields + (newField -> newValue)), up_!, idxInParent)

  def map(f: ((String, JValue)) => JValue) = new ChangedArrayElementObjectZipper(JObject(value.fields.map { kv => (kv._1, f(kv)) }), up_!, idxInParent)
}

// OBJECT ELEMENT, CHANGED OR NOT

private[zipper] abstract class ObjectElementZipperLike(p: JObjectZipper, val fieldInParent: String) extends ElementZipperLike(p) { this: ZipperLike =>
  def replace(newValue: JAtom) = new ChangedObjectElementAtomZipper(newValue, up_!, fieldInParent)
  def replace(newValue: JArray) = new ChangedObjectElementArrayZipper(newValue, up_!, fieldInParent)
  def replace(newValue: JObject) = new ChangedObjectElementObjectZipper(newValue, up_!, fieldInParent)

  def remove = new ChangedObjectElementNothingZipper(up_!, fieldInParent)

  def next = None
  def prev = None

  def sibling(field: String): Option[JsonZipper] = up_!.down(field)

  private[zipper] def reversePath = Path.Field(fieldInParent) :: p.reversePath
}

private[zipper] abstract class ObjectElementZipper(p: JObjectZipper, f: String) extends ObjectElementZipperLike(p, f) with ElementZipper[JObjectZipper] { this: JsonZipper =>
}

private[zipper] trait ObjectElementArrayZipper { this: ObjectElementZipper with JArrayZipper =>
  def :+(newValue: JValue): JArrayZipper = new ChangedObjectElementArrayZipper(JArray(value.toSeq :+ newValue), up_!, fieldInParent)
  def +:(newValue: JValue): JArrayZipper = new ChangedObjectElementArrayZipper(JArray(newValue +: value.toSeq), up_!, fieldInParent)

  def map(f: JValue => JValue) = new ChangedObjectElementArrayZipper(JArray(value.toSeq.map(f)), up_!, fieldInParent)
  def collect(f: PartialFunction[JValue, JValue]) = new ChangedObjectElementArrayZipper(JArray(value.toSeq.collect(f)), up_!, fieldInParent)
}

private[zipper] trait ObjectElementObjectZipper { this: ObjectElementZipper with JObjectZipper =>
  def set(newField: String, newValue: JValue): JObjectZipper = new ChangedObjectElementObjectZipper(JObject(value.fields + (newField -> newValue)), up_!, fieldInParent)

  def map(f: ((String, JValue)) => JValue) = new ChangedObjectElementObjectZipper(JObject(value.fields.map { kv => (kv._1, f(kv)) }), up_!, fieldInParent)
}

// ARRAY ELEMENT BUT UNCHANGED

private[zipper] abstract class UnchangedArrayElementZipper(p: JArrayZipper, i: Int) extends ArrayElementZipper(p, i) { this: JsonZipper =>
}

private[zipper] class UnchangedArrayElementAtomZipper(val value: JAtom, p: JArrayZipper, i: Int) extends UnchangedArrayElementZipper(p, i) with JAtomZipper
private[zipper] class UnchangedArrayElementArrayZipper(val value: JArray, p: JArrayZipper, i: Int) extends UnchangedArrayElementZipper(p, i) with ArrayElementArrayZipper with JArrayZipper
private[zipper] class UnchangedArrayElementObjectZipper(val value: JObject, p: JArrayZipper, i: Int) extends UnchangedArrayElementZipper(p, i) with ArrayElementObjectZipper with JObjectZipper

// OBJECT ELEMENT BUT UNCHANGED

private[zipper] abstract class UnchangedObjectElementZipper(p: JObjectZipper, f: String) extends ObjectElementZipper(p, f) { this: JsonZipper =>
}

private[zipper] class UnchangedObjectElementAtomZipper(val value: JAtom, p: JObjectZipper, f: String) extends UnchangedObjectElementZipper(p, f) with JAtomZipper
private[zipper] class UnchangedObjectElementArrayZipper(val value: JArray, p: JObjectZipper, f: String) extends UnchangedObjectElementZipper(p, f) with ObjectElementArrayZipper with JArrayZipper
private[zipper] class UnchangedObjectElementObjectZipper(val value: JObject, p: JObjectZipper, f: String) extends UnchangedObjectElementZipper(p, f) with ObjectElementObjectZipper with JObjectZipper

// ARRAY ELEMENT AND CHANGED

private[zipper] abstract class ChangedArrayElementZipper(p: JArrayZipper, i: Int) extends ArrayElementZipper(p, i) { this: JsonZipper =>
  override def up_! = super.up_!.replace(JArray(super.up_!.value.toSeq.updated(idxInParent, value)))
}

private[zipper] class ChangedArrayElementAtomZipper(val value: JAtom, p: JArrayZipper, i: Int) extends ChangedArrayElementZipper(p, i) with JAtomZipper
private[zipper] class ChangedArrayElementArrayZipper(val value: JArray, p: JArrayZipper, i: Int) extends ChangedArrayElementZipper(p, i) with ArrayElementArrayZipper with JArrayZipper
private[zipper] class ChangedArrayElementObjectZipper(val value: JObject, p: JArrayZipper, i: Int) extends ChangedArrayElementZipper(p, i) with ArrayElementObjectZipper with JObjectZipper

private[zipper] class ChangedArrayElementNothingZipper(parent: JArrayZipper, i: Int) extends ArrayElementZipperLike(parent, i) with ElementNothingZipper[JArrayZipper] with NothingZipper {
  override def up_! = super.up_!.replace(JArray(super.up_!.value.toSeq.patch(idxInParent, Nil, 1)))

  override def replace(newValue: JAtom) = new ChangedArrayElementAtomZipper(newValue, parent, idxInParent)
  override def replace(newValue: JArray) = new ChangedArrayElementArrayZipper(newValue, parent, idxInParent)
  override def replace(newValue: JObject) = new ChangedArrayElementObjectZipper(newValue, parent, idxInParent)

  override def remove = this

  override def next : Option[JsonZipper] = up_!.down(idxInParent)
}

// OBJECT ELEMENT AND CHANGED

private[zipper] abstract class ChangedObjectElementZipper(p: JObjectZipper, f: String) extends ObjectElementZipper(p, f) with ElementZipper[JObjectZipper] { this: JsonZipper =>
  override def up_! = super.up_!.replace(JObject(super.up_!.value.fields.updated(fieldInParent, value)))
}

private[zipper] class ChangedObjectElementAtomZipper(val value: JAtom, p: JObjectZipper, f: String) extends ChangedObjectElementZipper(p, f) with JAtomZipper
private[zipper] class ChangedObjectElementArrayZipper(val value: JArray, p: JObjectZipper, f: String) extends ChangedObjectElementZipper(p, f) with ObjectElementArrayZipper with JArrayZipper
private[zipper] class ChangedObjectElementObjectZipper(val value: JObject, p: JObjectZipper, f: String) extends ChangedObjectElementZipper(p, f) with ObjectElementObjectZipper with JObjectZipper

private[zipper] class ChangedObjectElementNothingZipper(parent: JObjectZipper, field: String) extends ObjectElementZipperLike(parent, field) with ElementNothingZipper[JObjectZipper] with NothingZipper {
  override def up_! = super.up_!.replace(JObject(super.up_!.value.fields - field))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy