
com.avsystem.commons.serialization.GenCodec.scala Maven / Gradle / Ivy
The newest version!
package com.avsystem.commons
package serialization
import com.avsystem.commons.collection.CollectionAliases._
import com.avsystem.commons.derivation.{AllowImplicitMacro, DeferredInstance}
import com.avsystem.commons.jiop.BasicJavaInterop._
import com.avsystem.commons.jiop.JCanBuildFrom
import com.avsystem.commons.misc.{NOpt, Opt, OptArg, OptRef}
import scala.annotation.implicitNotFound
import scala.collection.generic.CanBuildFrom
import scala.language.higherKinds
import scala.reflect.ClassTag
/**
* Type class for types that can be serialized to [[Output]] (format-agnostic "output stream") and deserialized
* from [[Input]] (format-agnostic "input stream"). `GenCodec` is supposed to capture generic structure of serialized
* objects, without being bound to particular format like JSON. The actual format is determined by implementation
* of [[Input]] and [[Output]].
*
* There are convenient macros for automatic derivation of [[GenCodec]] instances (`materialize` and `materializeRecursively`).
* However, [[GenCodec]] instances still need to be explicitly declared and won't be derived "automagically".
* If you want fully automatic derivation, use [[GenCodec.Auto]].
*/
@implicitNotFound("No GenCodec found for ${T}")
trait GenCodec[T] {
def read(input: Input): T
def write(output: Output, value: T): Unit
}
object GenCodec extends FallbackMapCodecs with TupleGenCodecs {
/**
* Macro that automatically materializes a [[GenCodec]] for some type `T`, which must be one of:
*
* - singleton type, e.g. an `object`
* - case class whose every field type has its own [[GenCodec]]
* - (generalization of case classes) class or trait whose companion object has a pair of case-class-like `apply`
* and `unapply` methods and every parameter type of `apply` method has its own [[GenCodec]]
*
* - sealed hierarchy in which every non-abstract subclass either has its own [[GenCodec]] or it can be
* automatically materialized with the same mechanism
*
* Note that automatic materialization does NOT descend into types that `T` is made of (e.g. types of case class
* fields must have their own codecs independently declared). If you want recursive materialization, use
* `materializeRecursively`.
*/
def materialize[T]: GenCodec[T] = macro macros.serialization.GenCodecMacros.materialize[T]
/**
* Wrapper over `GenCodec` which forces fully automatic derivation.
*
* If you ask for implicit value of type `GenCodec[T]`, the codec must be explicitly declared and imported or
* put into implicit scope (e.g. companion object of `T`), even though it can be automatically implemented
* using `materialize` or `materializeRecursively`.
*
* However, if you ask for implicit value of type `GenCodec.Auto[T]`, the compiler will always fall back to fully
* automatic derivation if it cannot find already declared `GenCodec`. Note that since `GenCodec.Auto` will always
* try to wrap already existing `GenCodec` (if there is one), you should never explicitly declare any instances
* of `GenCodec.Auto`. If you need custom serialization, just write a `GenCodec` and `GenCodec.Auto` will wrap it.
*
* Whether you want to use `GenCodec` or `GenCodec.Auto` depends on your use case. `GenCodec` should be generally
* used when you want the programmer to always explicitly state that some type is serializable. For example, if you
* serialize your objects in order to put them into database, you probably want to use `GenCodec` and not `GenCodec.Auto`,
* because every type that has been written to database is likely to be bound by backwards compatibility constraints and
* cannot be freely refactored. That's why you want to always explicitly make the decision of making a type serializable.
*
*/
case class Auto[T](codec: GenCodec[T]) extends AnyVal
def read[T](input: Input)(implicit codec: GenCodec[T]): T =
codec.read(input)
def write[T](output: Output, value: T)(implicit codec: GenCodec[T]): Unit =
codec.write(output, value)
def autoRead[T](input: Input)(implicit autoCodec: GenCodec.Auto[T]): T =
autoCodec.codec.read(input)
def autoWrite[T](output: Output, value: T)(implicit autoCodec: GenCodec.Auto[T]): Unit =
autoCodec.codec.write(output, value)
def create[T](readFun: Input => T, writeFun: (Output, T) => Any): GenCodec[T] =
new GenCodec[T] {
def write(output: Output, value: T) = writeFun(output, value)
def read(input: Input): T = readFun(input)
}
def createNullSafe[T >: Null](readFun: Input => T, writeFun: (Output, T) => Any): GenCodec[T] =
new NullSafeCodec[T] {
protected def nullable = true
protected def readNonNull(input: Input) = readFun(input)
protected def writeNonNull(output: Output, value: T) = writeFun(output, value)
}
def createList[T >: Null](readFun: ListInput => T, writeFun: (ListOutput, T) => Any) =
new ListCodec[T] {
protected def nullable = true
protected def readList(input: ListInput) = readFun(input)
protected def writeList(output: ListOutput, value: T) = writeFun(output, value)
}
def createObject[T >: Null](readFun: ObjectInput => T, writeFun: (ObjectOutput, T) => Any) =
new ObjectCodec[T] {
protected def nullable = true
protected def readObject(input: ObjectInput) = readFun(input)
protected def writeObject(output: ObjectOutput, value: T) = writeFun(output, value)
}
def fromKeyCodec[T](implicit keyCodec: GenKeyCodec[T]): GenCodec[T] = create(
input => keyCodec.read(input.readString()),
(output, value) => output.writeString(keyCodec.write(value))
)
def forSealedEnum[T]: GenCodec[T] = macro macros.serialization.GenCodecMacros.forSealedEnum[T]
class ReadFailure(msg: String, cause: Throwable) extends RuntimeException(msg, cause) {
def this(msg: String) = this(msg, null)
}
class WriteFailure(msg: String, cause: Throwable) extends RuntimeException(msg, cause) {
def this(msg: String) = this(msg, null)
}
final class Deferred[T] extends DeferredInstance[GenCodec[T]] with GenCodec[T] {
def read(input: Input) = underlying.read(input)
def write(output: Output, value: T) = underlying.write(output, value)
}
trait NullSafeCodec[T] extends GenCodec[T] {
protected def nullable: Boolean
protected def readNonNull(input: Input): T
protected def writeNonNull(output: Output, value: T): Unit
def write(output: Output, value: T): Unit =
if (value == null) output.writeNull() else writeNonNull(output, value)
def read(input: Input): T =
if (nullable && input.inputType == InputType.Null) input.readNull().asInstanceOf[T]
else readNonNull(input)
}
trait ListCodec[T] extends NullSafeCodec[T] {
protected def readList(input: ListInput): T
protected def writeList(output: ListOutput, value: T): Unit
protected def writeNonNull(output: Output, value: T) = {
val lo = output.writeList()
writeList(lo, value)
lo.finish()
}
protected def readNonNull(input: Input) = {
val li = input.readList()
val result = readList(li)
li.skipRemaining()
result
}
}
trait ObjectCodec[T] extends NullSafeCodec[T] {
protected def readObject(input: ObjectInput): T
protected def writeObject(output: ObjectOutput, value: T): Unit
protected def writeNonNull(output: Output, value: T) = {
val oo = output.writeObject()
writeObject(oo, value)
oo.finish()
}
protected def readNonNull(input: Input) = {
val oi = input.readObject()
val result = readObject(oi)
oi.skipRemaining()
result
}
}
trait ErrorReportingCodec[T] extends GenCodec[T] {
protected def typeRepr: String
protected def fieldMissing(field: String) =
throw new ReadFailure(s"Cannot read $typeRepr, field $field is missing in decoded data")
protected def unknownCase(field: String) =
throw new ReadFailure(s"Cannot read $typeRepr, unknown case: $field")
protected def notSingleField(empty: Boolean) =
throw new ReadFailure(s"Cannot read $typeRepr, expected object with exactly one field but got ${if (empty) "empty object" else "more than one"}")
protected def unapplyFailed =
throw new WriteFailure(s"Could not write $typeRepr, unapply failed (returned false or None)")
}
final class SingletonCodec[T <: Singleton](value: => T) extends ObjectCodec[T] {
protected def nullable: Boolean = true
protected def readObject(input: ObjectInput) = value
protected def writeObject(output: ObjectOutput, value: T) = ()
}
class TransformedCodec[A, B](val wrapped: GenCodec[B], onWrite: A => B, onRead: B => A) extends GenCodec[A] {
def read(input: Input) = onRead(wrapped.read(input))
def write(output: Output, value: A) = wrapped.write(output, onWrite(value))
}
def underlyingCodec(codec: GenCodec[_]): GenCodec[_] = codec match {
case tc: TransformedCodec[_, _] => underlyingCodec(tc.wrapped)
case _ => codec
}
implicit val NothingCodec: GenCodec[Nothing] = create[Nothing](_ => sys.error("read Nothing"), (_, _) => sys.error("write Nothing"))
implicit val NullCodec: GenCodec[Null] = create[Null](_.readNull(), (o, _) => o.writeNull())
implicit val UnitCodec: GenCodec[Unit] = create[Unit](_.readUnit(), (o, _) => o.writeUnit())
implicit val VoidCodec: GenCodec[Void] = create[Void](_.readNull(), (o, _) => o.writeNull())
implicit val BooleanCodec: GenCodec[Boolean] = create(_.readBoolean(), _ writeBoolean _)
implicit val CharCodec: GenCodec[Char] = create(_.readChar(), _ writeChar _)
implicit val ByteCodec: GenCodec[Byte] = create(_.readByte(), _ writeByte _)
implicit val ShortCodec: GenCodec[Short] = create(_.readShort(), _ writeShort _)
implicit val IntCodec: GenCodec[Int] = create(_.readInt(), _ writeInt _)
implicit val LongCodec: GenCodec[Long] = create(_.readLong(), _ writeLong _)
implicit val FloatCodec: GenCodec[Float] = create(_.readFloat(), _ writeFloat _)
implicit val DoubleCodec: GenCodec[Double] = create(_.readDouble(), _ writeDouble _)
implicit val JBooleanCodec: GenCodec[JBoolean] = createNullSafe(_.readBoolean(), _ writeBoolean _)
implicit val JCharacterCodec: GenCodec[JCharacter] = createNullSafe(_.readChar(), _ writeChar _)
implicit val JByteCodec: GenCodec[JByte] = createNullSafe(_.readByte(), _ writeByte _)
implicit val JShortCodec: GenCodec[JShort] = createNullSafe(_.readShort(), _ writeShort _)
implicit val JIntegerCodec: GenCodec[JInteger] = createNullSafe(_.readInt(), _ writeInt _)
implicit val JLongCodec: GenCodec[JLong] = createNullSafe(_.readLong(), _ writeLong _)
implicit val JFloatCodec: GenCodec[JFloat] = createNullSafe(_.readFloat(), _ writeFloat _)
implicit val JDoubleCodec: GenCodec[JDouble] = createNullSafe(_.readDouble(), _ writeDouble _)
implicit val JDateCodec: GenCodec[JDate] = createNullSafe(i => new JDate(i.readTimestamp()), (o, d) => o.writeTimestamp(d.getTime))
implicit val StringCodec: GenCodec[String] = createNullSafe(_.readString(), _ writeString _)
implicit val ByteArrayCodec: GenCodec[Array[Byte]] = createNullSafe(_.readBinary(), _ writeBinary _)
private implicit class IteratorOps[A](private val it: Iterator[A]) extends AnyVal {
def writeToList(lo: ListOutput)(implicit writer: GenCodec[A]): Unit =
it.foreach(writer.write(lo.writeElement(), _))
}
protected implicit class TraversableOps[A](private val it: TraversableOnce[A]) extends AnyVal {
def collectWith[C](cbf: CanBuildFrom[Nothing, A, C]): C = {
val b = cbf()
b ++= it
b.result()
}
}
private def readListWithCBF[C[_], T: GenCodec](li: ListInput)(implicit cbf: CanBuildFrom[Nothing, T, C[T]]): C[T] =
li.iterator(read[T]).collectWith(cbf)
implicit def arrayCodec[T: ClassTag : GenCodec]: GenCodec[Array[T]] =
createList[Array[T]](_.iterator(read[T]).toArray[T], (lo, arr) => arr.iterator.writeToList(lo))
// seqCodec, setCodec, jCollectionCodec, mapCodec, jMapCodec, fallbackMapCodec and fallbackJMapCodec
// have these weird return types (e.g. GenCodec[C[T] with BSeq[T]] instead of just GenCodec[C[T]]) because it's a
// workaround for https://groups.google.com/forum/#!topic/scala-user/O_fkaChTtg4
implicit def seqCodec[C[X] >: Null <: BSeq[X], T: GenCodec](
implicit cbf: CanBuildFrom[Nothing, T, C[T]]): GenCodec[C[T] with BSeq[T]] =
createList[C[T] with BSeq[T]](readListWithCBF[C, T], (lo, c) => c.iterator.writeToList(lo))
implicit def setCodec[C[X] >: Null <: BSet[X], T: GenCodec](
implicit cbf: CanBuildFrom[Nothing, T, C[T]]): GenCodec[C[T] with BSet[T]] =
createList[C[T] with BSet[T]](readListWithCBF[C, T], (lo, c) => c.iterator.writeToList(lo))
implicit def jCollectionCodec[C[X] >: Null <: JCollection[X], T: GenCodec](
implicit cbf: JCanBuildFrom[T, C[T]]): GenCodec[C[T] with JCollection[T]] =
createList[C[T] with JCollection[T]](readListWithCBF[C, T], (lo, c) => c.iterator.asScala.writeToList(lo))
implicit def mapCodec[M[X, Y] >: Null <: BMap[X, Y], K: GenKeyCodec, V: GenCodec](
implicit cbf: CanBuildFrom[Nothing, (K, V), M[K, V]]): GenCodec[M[K, V] with BMap[K, V]] =
createObject[M[K, V] with BMap[K, V]](
_.iterator(read[V]).map({ case (k, v) => (GenKeyCodec.read[K](k), v) }).collectWith(cbf),
(oo, value) => value.foreach({ case (k, v) => write[V](oo.writeField(GenKeyCodec.write(k)), v) })
)
implicit def jMapCodec[M[X, Y] >: Null <: JMap[X, Y], K: GenKeyCodec, V: GenCodec](
implicit cbf: JCanBuildFrom[(K, V), M[K, V]]): GenCodec[M[K, V] with JMap[K, V]] =
createObject[M[K, V] with JMap[K, V]](
_.iterator(read[V]).map({ case (k, v) => (GenKeyCodec.read[K](k), v) }).collectWith(cbf),
(oo, value) => value.asScala.foreach({ case (k, v) => write[V](oo.writeField(GenKeyCodec.write(k)), v) })
)
implicit def optionCodec[T: GenCodec]: GenCodec[Option[T]] =
createList[Option[T]](
li => if (li.hasNext) Some(read[T](li.nextElement())) else None,
(lo, opt) => opt.iterator.writeToList(lo)
)
implicit def nOptCodec[T: GenCodec]: GenCodec[NOpt[T]] =
new TransformedCodec[NOpt[T], Option[T]](optionCodec[T], _.toOption, _.toNOpt)
implicit def optCodec[T: GenCodec]: GenCodec[Opt[T]] =
create[Opt[T]](
i => i.inputType match {
case InputType.Null =>
i.readNull()
Opt.Empty
case _ =>
Opt(read[T](i))
},
locally {
case (o, Opt(t)) => write[T](o, t)
case (o, Opt.Empty) => o.writeNull()
}
)
implicit def optArgCodec[T: GenCodec]: GenCodec[OptArg[T]] =
new TransformedCodec[OptArg[T], Opt[T]](optCodec[T], _.toOpt, opt => if (opt.isEmpty) OptArg.Empty else OptArg(opt.get))
implicit def optRefCodec[T >: Null : GenCodec]: GenCodec[OptRef[T]] =
new TransformedCodec[OptRef[T], Opt[T]](optCodec[T], _.toOpt, opt => OptRef(opt.orNull))
implicit def jEnumCodec[E <: Enum[E] : GenKeyCodec]: GenCodec[E] =
fromKeyCodec[E]
// Needed because of SI-9453
implicit val NothingAutoCodec: GenCodec.Auto[Nothing] = GenCodec.Auto[Nothing](NothingCodec)
}
/**
* Contains readers for maps where there is no DBKeyCodec for key type. In such case, we assume reading from a list
* of key-value pairs instead of JSON object.
*/
trait FallbackMapCodecs extends RecursiveAutoCodecs { this: GenCodec.type =>
private def readKVPair[K: GenCodec, V: GenCodec](input: ObjectInput): (K, V) = {
var keyOpt: NOpt[K] = NOpt.empty
var valueOpt: NOpt[V] = NOpt.empty
while (input.hasNext) {
val fi = input.nextField()
fi.fieldName match {
case "k" => keyOpt = NOpt.some(read[K](fi))
case "v" => valueOpt = NOpt.some(read[V](fi))
case _ =>
}
}
val key = keyOpt.getOrElse(throw new ReadFailure("key `k` absent"))
val value = valueOpt.getOrElse(throw new ReadFailure("key `v` absent"))
(key, value)
}
private def writeKVPair[K, V](output: ObjectOutput, key: K, value: V)(implicit keyCodec: GenCodec[K], valueCodec: GenCodec[V]): Unit = {
keyCodec.write(output.writeField("k"), key)
valueCodec.write(output.writeField("v"), value)
output.finish()
}
implicit def fallbackMapCodec[M[X, Y] >: Null <: BMap[X, Y], K: GenCodec, V: GenCodec](
implicit cbf: CanBuildFrom[Nothing, (K, V), M[K, V]]): GenCodec[M[K, V] with BMap[K, V]] =
createList[M[K, V] with BMap[K, V]](
_.iterator(i => readKVPair[K, V](i.readObject())).collectWith(cbf),
(lo, map) => map.iterator.foreach({ case (k, v) => writeKVPair(lo.writeElement().writeObject(), k, v) })
)
implicit def fallbackJMapCodec[M[X, Y] >: Null <: JMap[X, Y], K: GenCodec, V: GenCodec](
implicit cbf: JCanBuildFrom[(K, V), M[K, V]]): GenCodec[M[K, V] with JMap[K, V]] =
createList[M[K, V] with JMap[K, V]](
_.iterator(i => readKVPair[K, V](i.readObject())).collectWith(cbf),
(lo, map) => map.asScala.iterator.foreach({ case (k, v) => writeKVPair(lo.writeElement().writeObject(), k, v) })
)
}
trait RecursiveAutoCodecs { this: GenCodec.type =>
/**
* Like `materialize`, but descends into types that `T` is made of (e.g. case class field types).
*/
def materializeRecursively[T]: GenCodec[T] =
macro macros.serialization.GenCodecMacros.materializeRecursively[T]
/**
* Used internally for materialization of `GenCodec.Auto`. Should not be used directly.
*/
implicit def materializeImplicitly[T](implicit allow: AllowImplicitMacro[GenCodec[T]]): GenCodec[T] =
macro macros.serialization.GenCodecMacros.materializeImplicitly[T]
implicit def materializeAuto[T]: GenCodec.Auto[T] =
macro macros.serialization.GenCodecMacros.materializeAuto[T]
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy