
com.avsystem.commons.serialization.InputOutput.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-core_2.12 Show documentation
Show all versions of commons-core_2.12 Show documentation
AVSystem commons library for Scala
The newest version!
package com.avsystem.commons
package serialization
import com.avsystem.commons.misc.{AbstractValueEnum, AbstractValueEnumCompanion, EnumCtx}
import com.avsystem.commons.serialization.GenCodec.ReadFailure
/**
* Base trait for type markers identifying custom native types that particular `Input` and `Output`
* implementations might want to support. Custom type markers are passed to
* `Output.writeCustom` and `Input.readCustom`.
*/
trait TypeMarker[T]
/**
* Base trait for metadata markers identifying custom native metadata information that particular `Input` and
* `Output` implementations might want to support.
* Input metadata markers are passed into `Output.keepsMetadata` and `Input.readMetadata`.
*
* Example: [[com.avsystem.commons.serialization.json.JsonType JsonType]]
*/
trait InputMetadata[T]
/**
* Base trait for event markers identifying custom events that may be emitted into
* `Input`s and `Output`s. See [[AcceptsCustomEvents.customEvent]] for more details.
*/
trait CustomEventMarker[T]
trait AcceptsCustomEvents extends Any {
/**
* Emits a custom "event" into an `Input` or `Output`. This event may be literally
* anything - its meaning depends purely on particular event and is usually bound to
* some particular `Input`/`Output` implementation. In other words, this is a hacky
* solution that allows codecs to do custom logic tailored for some particular
* `Input`/`Output` implementations.
*
* @return `true` when an event was understood and processed, `false` if it was
* not understood and ignored
*/
def customEvent[T](marker: CustomEventMarker[T], event: T): Boolean = false
}
/**
* Represents an abstract sink to which a value may be serialized (written).
* An `Output` instance should be assumed to be stateful. After calling any of the `write` methods, it MUST NOT be
* reused. This means that `Output` instance can be used only to write a single value. However, if the value
* to write is complex, one can use `writeList`/`writeSet` or `writeObject`/`writeMap`.
*/
trait Output extends Any with AcceptsCustomEvents {
def writeNull(): Unit
def writeSimple(): SimpleOutput
def writeList(): ListOutput
def writeObject(): ObjectOutput
/**
* Attempts to write some arbitrary custom "native" value that this output may or may not support.
* The custom type is identified by an instance of `TypeMarker` which is usually an object (e.g. companion
* object of the custom `T` type itself). This way `Input` and `Output` implementations may support other
* native types than the ones supported by default by `Input` and `Output` interfaces.
*
* Codecs may use this method to optimize encoded format in case it it possible with particular `Output`
* implementation. `GenCodec` may generally assume that if this method returns `true` then corresponding
* `Input` will return a non-empty `Opt` from `readCustom` method.
*
* `false` returned by this method indicates that this output does not support this particular type.
* In such situation the codec must fall back to some other strategy. If the native type is supported but there was
* some error writing it then a `WriteFailure` should be thrown instead of returning `false`.
*/
def writeCustom[T](typeMarker: TypeMarker[T], value: T): Boolean = false
/**
* Determines whether serialization format implemented by this `Output` preserves particular arbitrary
* "metadata" which is identified by [[com.avsystem.commons.serialization.InputMetadata InputMetadata]]
* which is usually an object (e.g. companion object of metadata value type `T`).
*
* An example of [[com.avsystem.commons.serialization.InputMetadata InputMetadata]] is
* [[com.avsystem.commons.serialization.json.JsonType JsonType]] supported by
* [[com.avsystem.commons.serialization.json.JsonStringOutput JsonStringOutput]].
*
* If this method returns `true` then codec may optimize its encoded format and assume that a corresponding
* `Input` implementation will return a non-empty `Opt` from its `readMetadata` implementation when passed the
* same [[com.avsystem.commons.serialization.InputMetadata InputMetadata]] identifier.
* If this method returns `false` then this `Output` does not support this
* medatata type and codec should fall back to some other serialization strategy.
*/
def keepsMetadata(metadata: InputMetadata[_]): Boolean = false
/**
* This ugly workaround has been introduced when standard `Option` encoding changed from zero-or-one element list
* encoding to unwrapped-or-null encoding which effectively disallowed serializing `null` and `Some(null)`.
* If some `Output` implementation still wants to use the list encoding, it may do it by overriding this method
* and returning `true`.
*/
def legacyOptionEncoding: Boolean = false
}
/**
* Represent an abstract sink for "primitive" values, i.e. ones that can be written as a whole with a simple
* method call (as opposed to lists and objects). Simple values must NEVER be `null`. `Output.writeNull` must
* be used instead to handle `null` values.
*/
trait SimpleOutput extends Any with AcceptsCustomEvents {
/** Value written MUST NOT be `null` */
def writeString(str: String): Unit
def writeChar(char: Char): Unit = writeString(char.toString)
def writeBoolean(boolean: Boolean): Unit
def writeByte(byte: Byte): Unit = writeShort(byte)
def writeShort(short: Short): Unit = writeInt(short)
def writeInt(int: Int): Unit
def writeLong(long: Long): Unit
def writeTimestamp(millis: Long): Unit = writeLong(millis)
def writeFloat(float: Float): Unit = writeDouble(float)
def writeDouble(double: Double): Unit
/** Value written MUST NOT be `null` */
def writeBigInt(bigInt: BigInt): Unit
/** Value written MUST NOT be `null` */
def writeBigDecimal(bigDecimal: BigDecimal): Unit
/** Value written MUST NOT be `null` */
def writeBinary(binary: Array[Byte]): Unit
}
trait OutputAndSimpleOutput extends Any with Output with SimpleOutput {
override final def writeSimple(): SimpleOutput = this
}
/**
* Using `SizePolicy`, a [[SequentialOutput]] ([[ListOutput]] or [[ObjectOutput]]) may hint the codec whether it
* makes use or requires explicit list or object size to be declared with [[SequentialOutput.declareSize]].
*/
final class SizePolicy(implicit enumCtx: EnumCtx) extends AbstractValueEnum
object SizePolicy extends AbstractValueEnumCompanion[SizePolicy] {
/**
* Indicates that the [[SequentialOutput]] implementation does not utilize explicitly declared size in any way.
* This means that the codec may always omit the `declareSize` invocation.
*/
final val Ignored: Value = new SizePolicy
/**
* Indicates that the [[SequentialOutput]] implementation is able to take advantage of explicitly declared size
* (e.g. in order to preallocate some buffers with accurate size or use more compact representation) but it is still
* able to work without size known upfront. With this policy, the codec may decide on its own whether it's worth
* computing the size upfront. Typically it will do it only when that computation is cheap, e.g. for a Scala `Vector`
* but omit it when it could degrade performance, e.g. for a Scala `List` which requires entire list traversal to
* compute its size.
*/
final val Optional: Value = new SizePolicy
/**
* Indicates that the [[SequentialOutput]] implementation always requires the codec to declare list or object size
* explicitly. The codec is then obliged to call [[SequentialOutput.declareSize]] before writing any elements or
* fields, regardless of the cost of computing that size.
*/
final val Required: Value = new SizePolicy
}
/**
* Base trait for outputs which allow writing of multiple values in sequence, i.e. [[ListOutput]] and [[ObjectOutput]].
*/
trait SequentialOutput extends Any with AcceptsCustomEvents {
/**
* Gives the output explicit information about the number of elements or fields that will be written to this
* output by the codec. This method must be called at most once, before any elements or fields have been written.
* The codec is then required to write exactly the declared number of elements or fields.
* Whether the codec should or must call this method depends on [[sizePolicy]] and the cost of computing the size.
*/
def declareSize(size: Int): Unit = ()
/**
* Provides information about whether this output makes use of list or object size explicitly declared with
* [[declareSize]]. See [[SizePolicy]] for more details.
*/
def sizePolicy: SizePolicy = SizePolicy.Optional
/**
* Indicates that all elements or fields in this [[com.avsystem.commons.serialization.SequentialOutput SequentialOutput]]
* have been written. This method MUST always be called after list/object writing has been finished.
*/
def finish(): Unit
/**
* Based on given collection's `knownSize` and [[sizePolicy]], declares the size
* of this output as size of this collection if it is either cheap or required to do so.
*/
final def declareSizeOf(coll: BIterable[_]): Unit = sizePolicy match {
case SizePolicy.Ignored =>
case SizePolicy.Optional =>
coll.knownSize match {
case -1 =>
case size => declareSize(size)
}
case SizePolicy.Required =>
declareSize(coll.size)
}
}
/**
* Represents an abstract sink for serialization of sequences of values. Any [[ListOutput]] instance
* must be assumed to be stateful and used in strictly sequential manner. After all elements have been written,
* `finish()` must be called to explicitly mark that the list is complete.
*/
trait ListOutput extends Any with SequentialOutput {
/**
* Returns an [[com.avsystem.commons.serialization.Output Output]] representing next element in this list.
* This [[com.avsystem.commons.serialization.Output Output]] instance MUST be fully used
* before calling [[writeElement]] next time. That means, one can NOT simultaneously use multiple instances of
* [[com.avsystem.commons.serialization.Output Output]] returned by subsequent calls to this method.
*/
def writeElement(): Output
}
/**
* Represents an abstract sink for serialization of string-to-value mappings. Any [[ObjectOutput]] instance
* must be assumed to be stateful and used in strictly sequential manner. After all key-value pairs have been
* written, `finish()` must be called to explicitly mark that the object is complete.
*
* [[ObjectOutput]] MUST preserve information about the order in which fields are written.
* [[ObjectInput]] is required to read fields in exactly the same order as [[ObjectOutput]] writes them.
*/
trait ObjectOutput extends Any with SequentialOutput {
/**
* Returns an [[com.avsystem.commons.serialization.Output Output]] representing value mapped to given string key.
* This [[com.avsystem.commons.serialization.Output Output]] instance must be fully
* used before calling [[writeField]] next time. That means, one can NOT simultaneously use multiple instances
* of [[com.avsystem.commons.serialization.Output Output]] returned by subsequent calls to this method.
*/
def writeField(key: String): Output
}
/**
* Represents an abstract source from which a value may be deserialized (read).
* Each of the `read` methods tries to read a value of specified type and may throw an exception
* (usually [[com.avsystem.commons.serialization.GenCodec.ReadFailure ReadFailure]]) when reading is not successful.
*
* An `Input` value should be assumed to be stateful. If any of the `readX` methods have already been called,
* the `Input` instance can no longer be used and MUST be discarded.
*
* In order to ignore the value kept in this `Input`, `skip()` MUST be called.
*
* In summary: every `Input` MUST be fully exhausted by either calling one of the `read` methods which returns
* successful value or by calling `skip()`. Also, [[ListInput]] and [[ObjectInput]] instances returned from this
* `Input` must also be fully exhausted on their own.
*/
trait Input extends Any with AcceptsCustomEvents {
/**
* Attempts to read `null` value from an `Input`. Returning `true` means that input instance contained a
* `null` value. Its state should then be changed so that input can be considered "consumed"
* (no other reads are possible on this instance). Returning `false` means that the input contains something else
* than a `null` value. Its state must not change in this situation and it must be possible to call some other
* read method on it.
*/
def readNull(): Boolean
def readSimple(): SimpleInput
def readList(): ListInput
def readObject(): ObjectInput
/**
* Attempts to read some arbitrary "metadata" about this input instance. Metadata is identified by
* [[com.avsystem.commons.serialization.InputMetadata InputMetadata]] which is usually an object
* (e.g. companion object of metadata value type `T`).
* An example of [[com.avsystem.commons.serialization.InputMetadata InputMetadata]] is
* [[com.avsystem.commons.serialization.json.JsonType JsonType]] supported by
* [[com.avsystem.commons.serialization.json.JsonStringInput JsonStringInput]].
*
* Codecs may use this method to optimize encoded format in case it it possible with particular `Input`
* implementation. `GenCodec` may generally assume that if the data was written by a corresponding `Output`
* that preserves particular metadata type (which may be determined by `Output.keepsMetadata()`) then
* `readMetadata` will return a non-empty value.
*
* `Opt.Empty` may be returned form this method ONLY if this `Input` implementation does not support
* this metadata type AT ALL. Any errors should be signaled by throwing `ReadFailure`.
*/
def readMetadata[T](metadata: InputMetadata[T]): Opt[T] = Opt.Empty
/**
* Attempts to read some arbitrary custom "native" value that this input may or may not support.
* The custom type is identified by an instance of `TypeMarker` which is usually an object (e.g. companion
* object of the custom `T` type itself). This way `Input` and `Output` implementations may support other
* native types than the ones supported by default by `Input` and `Output` interfaces.
*
* Codecs may use this method to optimize encoded format in case it it possible with particular `Input`
* implementation. `GenCodec` may generally assume that if the data was written by a corresponding `Output`
* which also support this custom native type then `readCustom` should return non-empty value.
*
* `Opt.Empty` returned by this method indicates that this input does not support this particular type.
* If it supports it but there was some error reading it then a `ReadFailure` should be thrown instead of
* returning `Opt.Empty`.
*/
def readCustom[T](typeMarker: TypeMarker[T]): Opt[T] = Opt.Empty
/** Ignores this input and skips its contents internally, if necessary */
def skip(): Unit
/**
* This ugly workaround has been introduced when standard `Option` encoding changed from zero-or-one element list
* encoding to unwrapped-or-null encoding which effectively disallowed serializing `null` and `Some(null)`.
* If some `Input` implementation still wants to use the list encoding, it may do it by overriding this method
* and returning `true`.
*/
def legacyOptionEncoding: Boolean = false
}
/**
* Represents an abstract source of primitive (or "simple") values. May be obtained from `Input`.
*/
trait SimpleInput extends Any {
def readString(): String
def readChar(): Char = readString().charAt(0)
def readBoolean(): Boolean
def readByte(): Byte = readShort().toByte
def readShort(): Short = readInt().toShort
def readInt(): Int
def readLong(): Long
def readTimestamp(): Long = readLong()
def readFloat(): Float = readDouble().toFloat
def readDouble(): Double
def readBigInt(): BigInt
def readBigDecimal(): BigDecimal
def readBinary(): Array[Byte]
}
trait InputAndSimpleInput extends Any with Input with SimpleInput {
override final def readSimple(): SimpleInput = this
}
trait SequentialInput extends Any with AcceptsCustomEvents {
/**
* Returns total number of elements or fields in this input or -1 if it is unknown.
* This method can be used by codecs in order to optimize decoding of collections.
*/
def knownSize: Int = -1
def hasNext: Boolean
def skipRemaining(): Unit
}
/**
* Represents an abstract source of sequence of values that can be deserialized.
* [[ListInput]] instance is stateful and MUST be read strictly sequentially. This means, you MUST fully exhaust
* an `Input` instance returned by `nextElement()` before calling `nextElement()` again. For this reason,
* [[ListInput]] is not an `Iterator` despite having similar interface
* (`Iterator` would easily allow e.g. conversion to `List[Input]` which would be illegal).
*
* [[ListInput]] MUST always be fully exhausted. In order to ignore any remaining elements, skipRemaining() may be
* used.
*/
trait ListInput extends Any with SequentialInput { self =>
/**
* Returns an [[com.avsystem.commons.serialization.Input Input]] representing next element in a sequence of values
* represented by this [[com.avsystem.commons.serialization.ListInput ListInput]].
* Returned [[com.avsystem.commons.serialization.Input Input]] instance must be fully exhausted before calling
* `nextElement()` next time.
*/
def nextElement(): Input
def skipRemaining(): Unit = while (hasNext) nextElement().skip()
def iterator[A](readFun: Input => A): Iterator[A] =
new Iterator[A] {
def hasNext: Boolean = self.hasNext
def next(): A = readFun(nextElement())
}
}
/**
* Represents an abstract source of key-value mappings that can be deserialized.
* [[com.avsystem.commons.serialization.ObjectInput ObjectInput]] instance is stateful and MUST be read strictly
* sequentially. This means, you MUST fully exhaust any [[com.avsystem.commons.serialization.Input Input]] instance
* returned by `nextField()` before calling `nextField()` again. For this reason,
* [[com.avsystem.commons.serialization.ObjectInput ObjectInput]] is not an `Iterator` despite having similar interface
* (`Iterator` would easily allow e.g. conversion to `List[(String, Input)]` which would be illegal).
*
* [[com.avsystem.commons.serialization.ObjectInput ObjectInput]] MUST always be fully exhausted.
* In order to ignore any remaining key-value mappings, `skipRemaining()` may be used.
*
*/
trait ObjectInput extends Any with SequentialInput { self =>
/**
* Returns [[com.avsystem.commons.serialization.FieldInput FieldInput]] that represents next field of this object.
* You MUST NOT call `nextField()` again until this [[com.avsystem.commons.serialization.FieldInput FieldInput]]
* is fully read or skipped.
*
* Serialization format implemented by this `ObjectInput` must either preserve order of fields (as they are
* written by corresponding `ObjectOutput`) OR it must provide random field access capability.
*
* - If the serialization format is able to preserve object field order then [[nextField]] must return
* object fields in exactly the same order as they were written by `ObjectOutput.writeField`. This is
* natural for most serialization formats backed by strings, raw character or byte sequences, e.g.
* JSON implemented by [[com.avsystem.commons.serialization.json.JsonStringOutput JsonStringOutput]]/
* [[com.avsystem.commons.serialization.json.JsonStringInput JsonStringInput]].
* - If the serialization format is unable to preserve object field order (e.g. because it uses hash maps to
* represent objects) then it must instead support random, by-name field access by overriding [[peekField]].
*
*
*/
def nextField(): FieldInput
/**
* If serialization format implemented by `ObjectInput` does NOT preserve field order, then this method MUST
* be overridden to support random field access. It should return non-empty [[Opt]] containing input for every field
* present in the object, regardless of field order assumed by [[nextField]].
* `Opt.Empty` is returned when field is absent or always when this `ObjectInput` does not support random field
* access (in which case it must preserve field order instead).
* NOTE: calling [[peekField]] and using [[com.avsystem.commons.serialization.FieldInput FieldInput]] returned by
* it MUST NOT change state of this `ObjectInput`.
* Therefore, it cannot in any way influence results returned by [[nextField]] and [[hasNext]].
* For example, if a [[com.avsystem.commons.serialization.FieldInput FieldInput]] for particular field has already been
* accessed using [[peekField]] but has not yet been returned by [[nextField]] then it MUST be returned at some
* point in the future by [[nextField]].
*/
def peekField(name: String): Opt[FieldInput] = Opt.Empty
/**
* Tries to obtain [[com.avsystem.commons.serialization.FieldInput FieldInput]] for field with specified name,
* either by using [[peekField]] (assuming format with random field access) or [[nextField]] (assuming format that
* preserves field order). A codec that uses this method must ensure that it reads fields in the same order as they
* were written using `writeField` on [[ObjectOutput]].
*/
def getNextNamedField(name: String): FieldInput =
peekField(name).getOrElse {
val fi = nextField()
if (fi.fieldName != name) {
throw new ReadFailure(s"Expected field $name, got ${fi.fieldName}")
}
fi
}
def skipRemaining(): Unit = while (hasNext) nextField().skip()
def iterator[A](readFun: Input => A): Iterator[(String, A)] =
new Iterator[(String, A)] {
def hasNext: Boolean = self.hasNext
def next(): (String, A) = {
val fi = nextField()
(fi.fieldName, readFun(fi))
}
}
}
/**
* An `Input` representing an object field. The same as `Input` but also provides field name.
*/
trait FieldInput extends Input {
def fieldName: String
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy