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

rapture.data.data.scala Maven / Gradle / Ivy

/*
  Rapture, version 2.0.0. Copyright 2010-2016 Jon Pretty, Propensive Ltd.

  The primary distribution site is
  
    http://rapture.io/

  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
  compliance with the License. You may obtain a copy of the License at
  
    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software distributed under the License is
  distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and limitations under the License.
*/

package rapture.data

import rapture.core._

import scala.util.Try

import language.dynamics

@implicitNotFound("Cannot find an implicit Formatter for ${AstType} data.")
trait Formatter[-AstType <: DataAst] {
  type Out
  def format(any: Any): Out
}

object DataCompanion { object Empty }

trait DataCompanion[+Type <: DataType[Type, DataAst], -AstType <: DataAst] {

  type ParseMethodConstraint <: MethodConstraint

  def empty(implicit ast: AstType) =
    construct(MutableCell(ast.fromObject(Map())), Vector())

  def construct(any: MutableCell, path: Vector[Either[Int, String]])(implicit ast: AstType): Type

  def parse[Source: StringSerializer](s: Source)(implicit mode: Mode[ParseMethodConstraint],
      parser: Parser[Source, AstType]): mode.Wrap[Type, ParseException] = mode wrap {
    construct(try MutableCell(parser.parse(s).get) catch {
      case e: NoSuchElementException => mode.exception(ParseException(String(s)))
    }, Vector())(parser.ast)
  }

  def apply[T](t: T)(implicit ast: AstType, ser: Serializer[T, Type]): Type =
    construct(MutableCell(ser.serialize(t)), Vector())

  def raw(value: Any)(implicit ast: AstType): Type =
    construct(MutableCell(value), Vector())

  def format[T <: DataType[T, AstType]](data: T)(implicit f: Formatter[_ <: AstType]): f.Out =
    f.format(data.$normalize)

}

case class DynamicApplication[D](path: List[Either[Int, String]], application: ForcedConversion2[D])

case class DynamicPath[D](path: List[Either[Int, String]]) extends Dynamic {
  def selectDynamic(v: String) = DynamicPath[D](Right(v) :: path)
  def applyDynamic(v: String)(i: Int) = DynamicPath[D](Left(i) :: Right(v) :: path)
  def apply(i: Int) = DynamicPath[D](Left(i) :: path)

  def updateDynamic(p: String)(value: ForcedConversion2[D]) =
    DynamicApplication(Right(p) :: path, value)
  
  def update(i: Int, value: ForcedConversion2[D]) =
    DynamicApplication(Left(i) :: path, value)
}

case class MutableCell(var value: Any)

trait DynamicData[+T <: DynamicData[T, AstType], +AstType <: DataAst] extends Dynamic {

  /** Assumes the Json object wraps a `Map`, and extracts the element `key`. */
  def selectDynamic(key: String): T = $deref(Right(key) +: $path)

  def applyDynamic(key: String)(i: Int = 0): T = $deref(Left(i) +: Right(key) +: $path)

  def $deref($path: Vector[Either[Int, String]]): T
  def $path: Vector[Either[Int, String]]

}

object DataType {

  class DataClassOperations[T <: DataType[T, AstType], AstType <: DataAst](dataType: T) {
    def ++[S <: DataType[S, Rep] forSome { type Rep }](b: S): T = {
      val ast = dataType.$ast
      
      def merge(a: Any, b: Any): Any = {
        if(ast.isObject(a) && ast.isObject(b)) {
          ast.fromObject(ast.getKeys(b).foldLeft(ast.getObject(a)) { case (as, k) =>
            as + (k -> {
              if(as contains k) merge(as(k), ast.dereferenceObject(b, k)) else ast.dereferenceObject(b, k)
            })
          })
        } else if(ast.isArray(a) && ast.isArray(b)) ast.fromArray(ast.getArray(a) ++ ast.getArray(b))
        else b
      }

      val left = dataType.$normalize
      val right = if(ast != b.$ast) ast.convert(b.$normalize, b.$ast.asInstanceOf[DataAst]) else b.$normalize
      
      dataType.$wrap(merge(left, right), Vector())
    }

    def copy(pvs: (DynamicPath[T] => DynamicApplication[_ <: DataType[T, _ <: AstType]])*): T = {
      dataType.$wrap(pvs.foldLeft(dataType.$normalize) { case (cur, pv) =>
        
	val dPath = pv(DynamicPath(Nil))
	val ast = dataType.$ast
	
	if(dPath.application.nothing) cur else {
 
	  def nav(path: List[Either[Int, String]], dest: Any, v: Any): Any = path match {
            case Nil =>
	      v
            
	    case Right(next) :: list =>
	      val d = try ast.dereferenceObject(dest, next) catch { case e: Exception => ast.fromObject(Map()) }
	      val src = ast.getObject(if(ast.isObject(dest)) dest else Map())
	      ast.fromObject(src + ((next, nav(list, d, v))))

	    case Left(next) :: list =>
	      val d = try ast.dereferenceArray(dest, next) catch { case e: Exception => ast.fromArray(List()) }
              val src = if(ast.isArray(dest)) ast.getArray(dest) else Nil
              ast.fromArray(src.padTo(next + 1, ast.fromObject(Map())).updated(next, nav(list, d, v)))
	  }

	  nav(dPath.path.reverse, cur, dPath.application.value)
        }
      })
    }
  }
}

trait DataType[+T <: DataType[T, AstType], +AstType <: DataAst] {
  val $root: MutableCell
  implicit def $ast: AstType
  def $path: Vector[Either[Int, String]]
  def $normalize: Any = doNormalize(false)
  def $wrap(any: Any, $path: Vector[Either[Int, String]] = Vector()): T
  def $deref($path: Vector[Either[Int, String]] = Vector()): T
  def $extract($path: Vector[Either[Int, String]]): T

  def \(key: String): T = $deref(Right(key) +: $path)

  def \\(key: String): T = $wrap($ast.fromArray(derefRecursive(key, $normalize)))

  private def derefRecursive(key: String, any: Any): List[Any] =
    if(!$ast.isObject(any)) Nil else $ast.getKeys(any).to[List].flatMap {
      case k if k == key => List($ast.dereferenceObject(any, k))
      case k => derefRecursive(key, $ast.dereferenceObject(any, k))
    }

  protected def doNormalize(orEmpty: Boolean): Any = {
    yCombinator[(Any, Vector[Either[Int, String]]), Any] { fn => _ match {
      case (j, Vector()) => j: Any
      case (j, t :+ e) =>
        fn(({
          if(e.bimap(x => $ast.isArray(j), x => $ast.isObject(j))) {
            try e.bimap($ast.dereferenceArray(j, _), $ast.dereferenceObject(j, _)) catch {
              case TypeMismatchException(exp, fnd) => throw TypeMismatchException(exp, fnd)
              case e: Exception =>
                if(orEmpty) DataCompanion.Empty
                else throw MissingValueException()
            }
          } else throw TypeMismatchException(
            if($ast.isArray(j)) DataTypes.Array else DataTypes.Object,
                e.bimap(l => DataTypes.Array, r => DataTypes.Object)
          )
        }, t))
    } } ($root.value -> $path)
  }

  /** Assumes the Json object is wrapping a `T`, and casts (intelligently) to that type. */
  def as[S](implicit ext: Extractor[S, T], mode: Mode[`Data#as`]):
      mode.Wrap[S, ext.Throws] = ext.extract(this.asInstanceOf[T], $ast, mode)

  def is[S](implicit ext: Extractor[S, T]): Boolean = try {
    ext.extract(this.asInstanceOf[T], $ast, modes.throwExceptions())
    true
  } catch {
    case e: Exception => false
  }

  def apply(i: Int = 0): T = $deref(Left(i) +: $path)

  override def equals(any: Any) = try { any match {
    case any: DataType[_, _] => $normalize == any.$normalize
    case _ => false
  } } catch { case e: Exception => false }

  override def hashCode = $root.value.hashCode ^ 3271912

}

trait MutableDataType[+T <: DataType[T, AstType], AstType <: MutableDataAst]
    extends DataType[T, AstType] {

  def $updateParents(p: Vector[Either[Int, String]], newVal: Any): Unit =
    p match {
      case Vector() =>
        $root.value = newVal
      case Left(idx) +: init =>
        val jb = $deref(init)
        val newJb = $ast.setArrayValue(Try(jb.$normalize).getOrElse($ast.fromArray(Nil)), idx,
            newVal)
        
        if(jb match {
          case jb: AnyRef => newJb match {
            case newJb: AnyRef => jb ne newJb
            case _ => false
          }
          case jb => jb == newJb
        }) $updateParents(init, newJb)
      case Right(key) +: init =>
        val jb = $deref(init)
        val newJb = $ast.setObjectValue(Try(jb.$normalize).getOrElse($ast.fromObject(Map())),
            key, newVal)
        
        if(jb match {
          case jb: AnyRef => newJb match {
            case newJb: AnyRef => jb ne newJb
            case _ => false
          }
          case jb => jb == newJb
        }) $updateParents(init, newJb)
    }

  /** Updates the element `key` of the JSON object with the value `v` */
  def updateDynamic(key: String)(v: ForcedConversion2[T]): Unit =
    if(!v.nothing) $updateParents($path,
        $ast.setObjectValue(Try($normalize).getOrElse($ast.fromObject(Map())), key, v.value))

  /** Updates the `i`th element of the JSON array with the value `v` */
  def update[T2](i: Int, v: T2)(implicit ser: Serializer[T2, T]): Unit =
    $updateParents($path, $ast.setArrayValue(Try($normalize).getOrElse($ast.fromArray(Nil)), i,
        ser.serialize(v)))

  /** Removes the specified key from the JSON object */
  def -=(k: String): Unit = $updateParents($path, $ast.removeObjectValue(doNormalize(true), k))

  /** Adds the specified value to the JSON array */
  def +=[T2](v: T2)(implicit ser: Serializer[T2, T]): Unit = {
    val r = doNormalize(true)
    val insert = if(r == DataCompanion.Empty) $ast.fromArray(Nil) else r
    $updateParents($path, $ast.addArrayValue(insert, ser.serialize(v)))
  }
}

trait `Data#as` extends MethodConstraint
trait `Data#normalize` extends MethodConstraint

object ForcedConversion2 extends ForcedConversion2_1 {
  implicit def forceOptConversion[T, D](opt: Option[T])(implicit ser: Serializer[T, D]) =
    opt.map(t => ForcedConversion2[D](ser.serialize(t), false)) getOrElse
        ForcedConversion2[D](null, true)
}

trait ForcedConversion2_1 {
  implicit def forceConversion[T, D](t: T)(implicit ser: Serializer[T, D]) =
    ForcedConversion2[D](ser.serialize(t), false)
}

case class ForcedConversion2[-D](value: Any, nothing: Boolean)

object ForcedConversion extends ForcedConversion_1 {
  implicit def forceOptConversion[T, D](opt: Option[T])(implicit ser: Serializer[T, D]) =
    opt.map(t => ForcedConversion[D](ser.serialize(t), false)) getOrElse
        ForcedConversion[D](null, true)
}

trait ForcedConversion_1 extends ForcedConversion_2 {
  implicit def forceConversion[T, D](t: T)(implicit ser: Serializer[T, D]) =
    ForcedConversion[D](ser.serialize(t), false)
}

trait ForcedConversion_2 {
  // The name of this method is significant for some additional checking done in the macro `contextMacro`.
  implicit def forceStringConversion[D, T: StringSerializer](value: T)(implicit ser: Serializer[String, D]) =
    ForcedConversion[D](ser.serialize(?[StringSerializer[T]].serialize(value)), false)
}

case class ForcedConversion[-D](value: Any, nothing: Boolean)

case class ParseException(source: String, line: Option[Int] = None, column: Option[Int] = None)
    extends Exception("Failed to parse source")




© 2015 - 2025 Weber Informatics LLC | Privacy Policy