Merge branch 'master' into 21648-Prefer_reachable_nodes_in_consistency-jgordijn

This commit is contained in:
Patrik Nordwall 2017-01-13 10:21:09 +01:00 committed by GitHub
commit a8f9ad4775
37 changed files with 2442 additions and 625 deletions

View file

@ -58,8 +58,11 @@ message VersionVector {
message ORMap {
message Entry {
required string key = 1;
optional string stringKey = 1;
required OtherMessage value = 2;
optional sint32 intKey = 3;
optional sint64 longKey = 4;
optional OtherMessage otherKey = 5;
}
required ORSet keys = 1;
@ -68,8 +71,11 @@ message ORMap {
message LWWMap {
message Entry {
required string key = 1;
optional string stringKey = 1;
required LWWRegister value = 2;
optional sint32 intKey = 3;
optional sint64 longKey = 4;
optional OtherMessage otherKey = 5;
}
required ORSet keys = 1;
@ -78,8 +84,11 @@ message LWWMap {
message PNCounterMap {
message Entry {
required string key = 1;
optional string stringKey = 1;
required PNCounter value = 2;
optional sint32 intKey = 3;
optional sint64 longKey = 4;
optional OtherMessage otherKey = 5;
}
required ORSet keys = 1;
@ -88,8 +97,11 @@ message PNCounterMap {
message ORMultiMap {
message Entry {
required string key = 1;
optional string stringKey = 1;
required ORSet value = 2;
optional sint32 intKey = 3;
optional sint64 longKey = 4;
optional OtherMessage otherKey = 5;
}
required ORSet keys = 1;

View file

@ -7,18 +7,18 @@ import akka.cluster.Cluster
import akka.cluster.UniqueAddress
object LWWMap {
private val _empty: LWWMap[Any] = new LWWMap(ORMap.empty)
def empty[A]: LWWMap[A] = _empty.asInstanceOf[LWWMap[A]]
def apply(): LWWMap[Any] = _empty
private val _empty: LWWMap[Any, Any] = new LWWMap(ORMap.empty)
def empty[A, B]: LWWMap[A, B] = _empty.asInstanceOf[LWWMap[A, B]]
def apply(): LWWMap[Any, Any] = _empty
/**
* Java API
*/
def create[A](): LWWMap[A] = empty
def create[A, B](): LWWMap[A, B] = empty
/**
* Extract the [[LWWMap#entries]].
*/
def unapply[A](m: LWWMap[A]): Option[Map[String, A]] = Some(m.entries)
def unapply[A, B](m: LWWMap[A, B]): Option[Map[A, B]] = Some(m.entries)
}
/**
@ -44,29 +44,29 @@ object LWWMap {
* This class is immutable, i.e. "modifying" methods return a new instance.
*/
@SerialVersionUID(1L)
final class LWWMap[A] private[akka] (
private[akka] val underlying: ORMap[LWWRegister[A]])
final class LWWMap[A, B] private[akka] (
private[akka] val underlying: ORMap[A, LWWRegister[B]])
extends ReplicatedData with ReplicatedDataSerialization with RemovedNodePruning {
import LWWRegister.{ Clock, defaultClock }
type T = LWWMap[A]
type T = LWWMap[A, B]
/**
* Scala API: All entries of the map.
*/
def entries: Map[String, A] = underlying.entries.map { case (k, r) k r.value }
def entries: Map[A, B] = underlying.entries.map { case (k, r) k r.value }
/**
* Java API: All entries of the map.
*/
def getEntries(): java.util.Map[String, A] = {
def getEntries(): java.util.Map[A, B] = {
import scala.collection.JavaConverters._
entries.asJava
}
def get(key: String): Option[A] = underlying.get(key).map(_.value)
def get(key: A): Option[B] = underlying.get(key).map(_.value)
def contains(key: String): Boolean = underlying.contains(key)
def contains(key: A): Boolean = underlying.contains(key)
def isEmpty: Boolean = underlying.isEmpty
@ -75,7 +75,7 @@ final class LWWMap[A] private[akka] (
/**
* Adds an entry to the map
*/
def +(entry: (String, A))(implicit node: Cluster): LWWMap[A] = {
def +(entry: (A, B))(implicit node: Cluster): LWWMap[A, B] = {
val (key, value) = entry
put(node, key, value)
}
@ -83,8 +83,8 @@ final class LWWMap[A] private[akka] (
/**
* Adds an entry to the map
*/
def put(node: Cluster, key: String, value: A): LWWMap[A] =
put(node, key, value, defaultClock[A])
def put(node: Cluster, key: A, value: B): LWWMap[A, B] =
put(node, key, value, defaultClock[B])
/**
* Adds an entry to the map.
@ -94,7 +94,7 @@ final class LWWMap[A] private[akka] (
* increasing version number from a database record that is used for optimistic
* concurrency control.
*/
def put(node: Cluster, key: String, value: A, clock: Clock[A]): LWWMap[A] =
def put(node: Cluster, key: A, value: B, clock: Clock[B]): LWWMap[A, B] =
put(node.selfUniqueAddress, key, value, clock)
/**
@ -105,13 +105,13 @@ final class LWWMap[A] private[akka] (
* increasing version number from a database record that is used for optimistic
* concurrency control.
*/
def put(key: String, value: A)(implicit node: Cluster, clock: Clock[A] = defaultClock[A]): LWWMap[A] =
def put(key: A, value: B)(implicit node: Cluster, clock: Clock[B] = defaultClock[B]): LWWMap[A, B] =
put(node, key, value, clock)
/**
* INTERNAL API
*/
private[akka] def put(node: UniqueAddress, key: String, value: A, clock: Clock[A]): LWWMap[A] = {
private[akka] def put(node: UniqueAddress, key: A, value: B, clock: Clock[B]): LWWMap[A, B] = {
val newRegister = underlying.get(key) match {
case Some(r) r.withValue(node, value, clock)
case None LWWRegister(node, value, clock)
@ -124,32 +124,32 @@ final class LWWMap[A] private[akka] (
* Note that if there is a conflicting update on another node the entry will
* not be removed after merge.
*/
def -(key: String)(implicit node: Cluster): LWWMap[A] = remove(node, key)
def -(key: A)(implicit node: Cluster): LWWMap[A, B] = remove(node, key)
/**
* Removes an entry from the map.
* Note that if there is a conflicting update on another node the entry will
* not be removed after merge.
*/
def remove(node: Cluster, key: String): LWWMap[A] =
def remove(node: Cluster, key: A): LWWMap[A, B] =
remove(node.selfUniqueAddress, key)
/**
* INTERNAL API
*/
private[akka] def remove(node: UniqueAddress, key: String): LWWMap[A] =
private[akka] def remove(node: UniqueAddress, key: A): LWWMap[A, B] =
new LWWMap(underlying.remove(node, key))
override def merge(that: LWWMap[A]): LWWMap[A] =
override def merge(that: LWWMap[A, B]): LWWMap[A, B] =
new LWWMap(underlying.merge(that.underlying))
override def needPruningFrom(removedNode: UniqueAddress): Boolean =
underlying.needPruningFrom(removedNode)
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): LWWMap[A] =
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): LWWMap[A, B] =
new LWWMap(underlying.prune(removedNode, collapseInto))
override def pruningCleanup(removedNode: UniqueAddress): LWWMap[A] =
override def pruningCleanup(removedNode: UniqueAddress): LWWMap[A, B] =
new LWWMap(underlying.pruningCleanup(removedNode))
// this class cannot be a `case class` because we need different `unapply`
@ -157,16 +157,16 @@ final class LWWMap[A] private[akka] (
override def toString: String = s"LWW$entries" //e.g. LWWMap(a -> 1, b -> 2)
override def equals(o: Any): Boolean = o match {
case other: LWWMap[_] underlying == other.underlying
case _ false
case other: LWWMap[_, _] underlying == other.underlying
case _ false
}
override def hashCode: Int = underlying.hashCode
}
object LWWMapKey {
def create[A](id: String): Key[LWWMap[A]] = LWWMapKey(id)
def create[A, B](id: String): Key[LWWMap[A, B]] = LWWMapKey(id)
}
@SerialVersionUID(1L)
final case class LWWMapKey[A](_id: String) extends Key[LWWMap[A]](_id) with ReplicatedDataSerialization
final case class LWWMapKey[A, B](_id: String) extends Key[LWWMap[A, B]](_id) with ReplicatedDataSerialization

View file

@ -8,18 +8,18 @@ import akka.cluster.UniqueAddress
import akka.util.HashCode
object ORMap {
private val _empty: ORMap[ReplicatedData] = new ORMap(ORSet.empty, Map.empty)
def empty[A <: ReplicatedData]: ORMap[A] = _empty.asInstanceOf[ORMap[A]]
def apply(): ORMap[ReplicatedData] = _empty
private val _empty: ORMap[Any, ReplicatedData] = new ORMap(ORSet.empty, Map.empty)
def empty[A, B <: ReplicatedData]: ORMap[A, B] = _empty.asInstanceOf[ORMap[A, B]]
def apply(): ORMap[Any, ReplicatedData] = _empty
/**
* Java API
*/
def create[A <: ReplicatedData](): ORMap[A] = empty[A]
def create[A, B <: ReplicatedData](): ORMap[A, B] = empty[A, B]
/**
* Extract the [[ORMap#entries]].
*/
def unapply[A <: ReplicatedData](m: ORMap[A]): Option[Map[String, A]] = Some(m.entries)
def unapply[A, B <: ReplicatedData](m: ORMap[A, B]): Option[Map[A, B]] = Some(m.entries)
}
@ -32,35 +32,35 @@ object ORMap {
* This class is immutable, i.e. "modifying" methods return a new instance.
*/
@SerialVersionUID(1L)
final class ORMap[A <: ReplicatedData] private[akka] (
private[akka] val keys: ORSet[String],
private[akka] val values: Map[String, A])
final class ORMap[A, B <: ReplicatedData] private[akka] (
private[akka] val keys: ORSet[A],
private[akka] val values: Map[A, B])
extends ReplicatedData with ReplicatedDataSerialization with RemovedNodePruning {
type T = ORMap[A]
type T = ORMap[A, B]
/**
* Scala API: All entries of the map.
*/
def entries: Map[String, A] = values
def entries: Map[A, B] = values
/**
* Java API: All entries of the map.
*/
def getEntries(): java.util.Map[String, A] = {
def getEntries(): java.util.Map[A, B] = {
import scala.collection.JavaConverters._
entries.asJava
}
def get(key: String): Option[A] = values.get(key)
def get(key: A): Option[B] = values.get(key)
/**
* Scala API: Get the value associated with the key if there is one,
* else return the given default.
*/
def getOrElse(key: String, default: A): A = values.getOrElse(key, default)
def getOrElse(key: A, default: B): B = values.getOrElse(key, default)
def contains(key: String): Boolean = values.contains(key)
def contains(key: A): Boolean = values.contains(key)
def isEmpty: Boolean = values.isEmpty
@ -70,7 +70,7 @@ final class ORMap[A <: ReplicatedData] private[akka] (
* Adds an entry to the map
* @see [[#put]]
*/
def +(entry: (String, A))(implicit node: Cluster): ORMap[A] = {
def +(entry: (A, B))(implicit node: Cluster): ORMap[A, B] = {
val (key, value) = entry
put(node, key, value)
}
@ -88,12 +88,12 @@ final class ORMap[A <: ReplicatedData] private[akka] (
* value, because important history can be lost when replacing the `ORSet` and
* undesired effects of merging will occur. Use [[ORMultiMap]] or [[#updated]] instead.
*/
def put(node: Cluster, key: String, value: A): ORMap[A] = put(node.selfUniqueAddress, key, value)
def put(node: Cluster, key: A, value: B): ORMap[A, B] = put(node.selfUniqueAddress, key, value)
/**
* INTERNAL API
*/
private[akka] def put(node: UniqueAddress, key: String, value: A): ORMap[A] =
private[akka] def put(node: UniqueAddress, key: A, value: B): ORMap[A, B] =
if (value.isInstanceOf[ORSet[_]] && values.contains(key))
throw new IllegalArgumentException(
"`ORMap.put` must not be used to replace an existing `ORSet` " +
@ -108,7 +108,7 @@ final class ORMap[A <: ReplicatedData] private[akka] (
* If there is no current value for the `key` the `initial` value will be
* passed to the `modify` function.
*/
def updated(node: Cluster, key: String, initial: A)(modify: A A): ORMap[A] =
def updated(node: Cluster, key: A, initial: B)(modify: B B): ORMap[A, B] =
updated(node.selfUniqueAddress, key, initial)(modify)
/**
@ -117,13 +117,13 @@ final class ORMap[A <: ReplicatedData] private[akka] (
* If there is no current value for the `key` the `initial` value will be
* passed to the `modify` function.
*/
def updated(node: Cluster, key: String, initial: A, modify: java.util.function.Function[A, A]): ORMap[A] =
def updated(node: Cluster, key: A, initial: B, modify: java.util.function.Function[B, B]): ORMap[A, B] =
updated(node, key, initial)(value modify.apply(value))
/**
* INTERNAL API
*/
private[akka] def updated(node: UniqueAddress, key: String, initial: A)(modify: A A): ORMap[A] = {
private[akka] def updated(node: UniqueAddress, key: A, initial: B)(modify: B B): ORMap[A, B] = {
val newValue = values.get(key) match {
case Some(old) modify(old)
case _ modify(initial)
@ -136,25 +136,25 @@ final class ORMap[A <: ReplicatedData] private[akka] (
* Note that if there is a conflicting update on another node the entry will
* not be removed after merge.
*/
def -(key: String)(implicit node: Cluster): ORMap[A] = remove(node, key)
def -(key: A)(implicit node: Cluster): ORMap[A, B] = remove(node, key)
/**
* Removes an entry from the map.
* Note that if there is a conflicting update on another node the entry will
* not be removed after merge.
*/
def remove(node: Cluster, key: String): ORMap[A] = remove(node.selfUniqueAddress, key)
def remove(node: Cluster, key: A): ORMap[A, B] = remove(node.selfUniqueAddress, key)
/**
* INTERNAL API
*/
private[akka] def remove(node: UniqueAddress, key: String): ORMap[A] = {
private[akka] def remove(node: UniqueAddress, key: A): ORMap[A, B] = {
new ORMap(keys.remove(node, key), values - key)
}
override def merge(that: ORMap[A]): ORMap[A] = {
override def merge(that: ORMap[A, B]): ORMap[A, B] = {
val mergedKeys = keys.merge(that.keys)
var mergedValues = Map.empty[String, A]
var mergedValues = Map.empty[A, B]
mergedKeys.elementsMap.keysIterator.foreach { key
(this.values.get(key), that.values.get(key)) match {
case (Some(thisValue), Some(thatValue))
@ -164,7 +164,7 @@ final class ORMap[A <: ReplicatedData] private[akka] (
throw new IllegalArgumentException(errMsg)
}
// TODO can we get rid of these (safe) casts?
val mergedValue = thisValue.merge(thatValue.asInstanceOf[thisValue.T]).asInstanceOf[A]
val mergedValue = thisValue.merge(thatValue.asInstanceOf[thisValue.T]).asInstanceOf[B]
mergedValues = mergedValues.updated(key, mergedValue)
case (Some(thisValue), None)
mergedValues = mergedValues.updated(key, thisValue)
@ -184,21 +184,21 @@ final class ORMap[A <: ReplicatedData] private[akka] (
}
}
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): ORMap[A] = {
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): ORMap[A, B] = {
val prunedKeys = keys.prune(removedNode, collapseInto)
val prunedValues = values.foldLeft(values) {
case (acc, (key, data: RemovedNodePruning)) if data.needPruningFrom(removedNode)
acc.updated(key, data.prune(removedNode, collapseInto).asInstanceOf[A])
acc.updated(key, data.prune(removedNode, collapseInto).asInstanceOf[B])
case (acc, _) acc
}
new ORMap(prunedKeys, prunedValues)
}
override def pruningCleanup(removedNode: UniqueAddress): ORMap[A] = {
override def pruningCleanup(removedNode: UniqueAddress): ORMap[A, B] = {
val pruningCleanupedKeys = keys.pruningCleanup(removedNode)
val pruningCleanupedValues = values.foldLeft(values) {
case (acc, (key, data: RemovedNodePruning)) if data.needPruningFrom(removedNode)
acc.updated(key, data.pruningCleanup(removedNode).asInstanceOf[A])
acc.updated(key, data.pruningCleanup(removedNode).asInstanceOf[B])
case (acc, _) acc
}
new ORMap(pruningCleanupedKeys, pruningCleanupedValues)
@ -209,8 +209,8 @@ final class ORMap[A <: ReplicatedData] private[akka] (
override def toString: String = s"OR$entries"
override def equals(o: Any): Boolean = o match {
case other: ORMap[_] keys == other.keys && values == other.values
case _ false
case other: ORMap[_, _] keys == other.keys && values == other.values
case _ false
}
override def hashCode: Int = {
@ -223,8 +223,8 @@ final class ORMap[A <: ReplicatedData] private[akka] (
}
object ORMapKey {
def create[A <: ReplicatedData](id: String): Key[ORMap[A]] = ORMapKey(id)
def create[A, B <: ReplicatedData](id: String): Key[ORMap[A, B]] = ORMapKey(id)
}
@SerialVersionUID(1L)
final case class ORMapKey[A <: ReplicatedData](_id: String) extends Key[ORMap[A]](_id) with ReplicatedDataSerialization
final case class ORMapKey[A, B <: ReplicatedData](_id: String) extends Key[ORMap[A, B]](_id) with ReplicatedDataSerialization

View file

@ -7,29 +7,29 @@ import akka.cluster.{ UniqueAddress, Cluster }
object ORMultiMap {
val _empty: ORMultiMap[Any] = new ORMultiMap(ORMap.empty)
val _empty: ORMultiMap[Any, Any] = new ORMultiMap(ORMap.empty)
/**
* Provides an empty multimap.
*/
def empty[A]: ORMultiMap[A] = _empty.asInstanceOf[ORMultiMap[A]]
def apply(): ORMultiMap[Any] = _empty
def empty[A, B]: ORMultiMap[A, B] = _empty.asInstanceOf[ORMultiMap[A, B]]
def apply(): ORMultiMap[Any, Any] = _empty
/**
* Java API
*/
def create[A](): ORMultiMap[A] = empty[A]
def create[A, B](): ORMultiMap[A, B] = empty[A, B]
/**
* Extract the [[ORMultiMap#entries]].
*/
def unapply[A](m: ORMultiMap[A]): Option[Map[String, Set[A]]] = Some(m.entries)
def unapply[A, B](m: ORMultiMap[A, B]): Option[Map[A, Set[B]]] = Some(m.entries)
/**
* Extract the [[ORMultiMap#entries]] of an `ORMultiMap`.
*/
def unapply(value: Any): Option[Map[String, Set[Any]]] = value match {
case m: ORMultiMap[Any] @unchecked Some(m.entries)
case _ None
def unapply[A, B <: ReplicatedData](value: Any): Option[Map[A, Set[B]]] = value match {
case m: ORMultiMap[A, B] @unchecked Some(m.entries)
case _ None
}
}
@ -40,10 +40,10 @@ object ORMultiMap {
* This class is immutable, i.e. "modifying" methods return a new instance.
*/
@SerialVersionUID(1L)
final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORSet[A]])
final class ORMultiMap[A, B] private[akka] (private[akka] val underlying: ORMap[A, ORSet[B]])
extends ReplicatedData with ReplicatedDataSerialization with RemovedNodePruning {
override type T = ORMultiMap[A]
override type T = ORMultiMap[A, B]
override def merge(that: T): T =
new ORMultiMap(underlying.merge(that.underlying))
@ -51,15 +51,15 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
/**
* Scala API: All entries of a multimap where keys are strings and values are sets.
*/
def entries: Map[String, Set[A]] =
def entries: Map[A, Set[B]] =
underlying.entries.map { case (k, v) k v.elements }
/**
* Java API: All entries of a multimap where keys are strings and values are sets.
*/
def getEntries(): java.util.Map[String, java.util.Set[A]] = {
def getEntries(): java.util.Map[A, java.util.Set[B]] = {
import scala.collection.JavaConverters._
val result = new java.util.HashMap[String, java.util.Set[A]]
val result = new java.util.HashMap[A, java.util.Set[B]]
underlying.entries.foreach {
case (k, v) result.put(k, v.elements.asJava)
}
@ -69,17 +69,17 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
/**
* Get the set associated with the key if there is one.
*/
def get(key: String): Option[Set[A]] =
def get(key: A): Option[Set[B]] =
underlying.get(key).map(_.elements)
/**
* Scala API: Get the set associated with the key if there is one,
* else return the given default.
*/
def getOrElse(key: String, default: Set[A]): Set[A] =
def getOrElse(key: A, default: Set[B]): Set[B] =
get(key).getOrElse(default)
def contains(key: String): Boolean = underlying.contains(key)
def contains(key: A): Boolean = underlying.contains(key)
def isEmpty: Boolean = underlying.isEmpty
@ -89,7 +89,7 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
* Convenience for put. Requires an implicit Cluster.
* @see [[#put]]
*/
def +(entry: (String, Set[A]))(implicit node: Cluster): ORMultiMap[A] = {
def +(entry: (A, Set[B]))(implicit node: Cluster): ORMultiMap[A, B] = {
val (key, value) = entry
put(node, key, value)
}
@ -98,14 +98,14 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
* Scala API: Associate an entire set with the key while retaining the history of the previous
* replicated data set.
*/
def put(node: Cluster, key: String, value: Set[A]): ORMultiMap[A] =
def put(node: Cluster, key: A, value: Set[B]): ORMultiMap[A, B] =
put(node.selfUniqueAddress, key, value)
/**
* Java API: Associate an entire set with the key while retaining the history of the previous
* replicated data set.
*/
def put(node: Cluster, key: String, value: java.util.Set[A]): ORMultiMap[A] = {
def put(node: Cluster, key: A, value: java.util.Set[B]): ORMultiMap[A, B] = {
import scala.collection.JavaConverters._
put(node, key, value.asScala.toSet)
}
@ -113,8 +113,8 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
/**
* INTERNAL API
*/
private[akka] def put(node: UniqueAddress, key: String, value: Set[A]): ORMultiMap[A] = {
val newUnderlying = underlying.updated(node, key, ORSet.empty[A]) { existing
private[akka] def put(node: UniqueAddress, key: A, value: Set[B]): ORMultiMap[A, B] = {
val newUnderlying = underlying.updated(node, key, ORSet.empty[B]) { existing
value.foldLeft(existing.clear(node)) { (s, element) s.add(node, element) }
}
new ORMultiMap(newUnderlying)
@ -124,38 +124,38 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
* Convenience for remove. Requires an implicit Cluster.
* @see [[#remove]]
*/
def -(key: String)(implicit node: Cluster): ORMultiMap[A] =
def -(key: A)(implicit node: Cluster): ORMultiMap[A, B] =
remove(node, key)
/**
* Remove an entire set associated with the key.
*/
def remove(node: Cluster, key: String): ORMultiMap[A] =
def remove(node: Cluster, key: A): ORMultiMap[A, B] =
remove(node.selfUniqueAddress, key)
/**
* INTERNAL API
*/
private[akka] def remove(node: UniqueAddress, key: String): ORMultiMap[A] =
private[akka] def remove(node: UniqueAddress, key: A): ORMultiMap[A, B] =
new ORMultiMap(underlying.remove(node, key))
/**
* Scala API: Add an element to a set associated with a key. If there is no existing set then one will be initialised.
*/
def addBinding(key: String, element: A)(implicit node: Cluster): ORMultiMap[A] =
def addBinding(key: A, element: B)(implicit node: Cluster): ORMultiMap[A, B] =
addBinding(node.selfUniqueAddress, key, element)
/**
* Java API: Add an element to a set associated with a key. If there is no existing set then one will be initialised.
*/
def addBinding(node: Cluster, key: String, element: A): ORMultiMap[A] =
def addBinding(node: Cluster, key: A, element: B): ORMultiMap[A, B] =
addBinding(key, element)(node)
/**
* INTERNAL API
*/
private[akka] def addBinding(node: UniqueAddress, key: String, element: A): ORMultiMap[A] = {
val newUnderlying = underlying.updated(node, key, ORSet.empty[A])(_.add(node, element))
private[akka] def addBinding(node: UniqueAddress, key: A, element: B): ORMultiMap[A, B] = {
val newUnderlying = underlying.updated(node, key, ORSet.empty[B])(_.add(node, element))
new ORMultiMap(newUnderlying)
}
@ -163,22 +163,22 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
* Scala API: Remove an element of a set associated with a key. If there are no more elements in the set then the
* entire set will be removed.
*/
def removeBinding(key: String, element: A)(implicit node: Cluster): ORMultiMap[A] =
def removeBinding(key: A, element: B)(implicit node: Cluster): ORMultiMap[A, B] =
removeBinding(node.selfUniqueAddress, key, element)
/**
* Java API: Remove an element of a set associated with a key. If there are no more elements in the set then the
* entire set will be removed.
*/
def removeBinding(node: Cluster, key: String, element: A): ORMultiMap[A] =
def removeBinding(node: Cluster, key: A, element: B): ORMultiMap[A, B] =
removeBinding(key, element)(node)
/**
* INTERNAL API
*/
private[akka] def removeBinding(node: UniqueAddress, key: String, element: A): ORMultiMap[A] = {
private[akka] def removeBinding(node: UniqueAddress, key: A, element: B): ORMultiMap[A, B] = {
val newUnderlying = {
val u = underlying.updated(node, key, ORSet.empty[A])(_.remove(node, element))
val u = underlying.updated(node, key, ORSet.empty[B])(_.remove(node, element))
u.get(key) match {
case Some(s) if s.isEmpty u.remove(node, key)
case _ u
@ -192,13 +192,13 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
* and another one is added within the same Update. The order of addition and removal is important in order
* to retain history for replicated data.
*/
def replaceBinding(key: String, oldElement: A, newElement: A)(implicit node: Cluster): ORMultiMap[A] =
def replaceBinding(key: A, oldElement: B, newElement: B)(implicit node: Cluster): ORMultiMap[A, B] =
replaceBinding(node.selfUniqueAddress, key, oldElement, newElement)
/**
* INTERNAL API
*/
private[akka] def replaceBinding(node: UniqueAddress, key: String, oldElement: A, newElement: A): ORMultiMap[A] =
private[akka] def replaceBinding(node: UniqueAddress, key: A, oldElement: B, newElement: B): ORMultiMap[A, B] =
if (newElement != oldElement)
addBinding(node, key, newElement).removeBinding(node, key, oldElement)
else
@ -218,16 +218,16 @@ final class ORMultiMap[A] private[akka] (private[akka] val underlying: ORMap[ORS
override def toString: String = s"ORMulti$entries"
override def equals(o: Any): Boolean = o match {
case other: ORMultiMap[_] underlying == other.underlying
case _ false
case other: ORMultiMap[_, _] underlying == other.underlying
case _ false
}
override def hashCode: Int = underlying.hashCode
}
object ORMultiMapKey {
def create[A](id: String): Key[ORMultiMap[A]] = ORMultiMapKey(id)
def create[A, B](id: String): Key[ORMultiMap[A, B]] = ORMultiMapKey(id)
}
@SerialVersionUID(1L)
final case class ORMultiMapKey[A](_id: String) extends Key[ORMultiMap[A]](_id) with ReplicatedDataSerialization
final case class ORMultiMapKey[A, B](_id: String) extends Key[ORMultiMap[A, B]](_id) with ReplicatedDataSerialization

View file

@ -8,17 +8,17 @@ import akka.cluster.UniqueAddress
import java.math.BigInteger
object PNCounterMap {
val empty: PNCounterMap = new PNCounterMap(ORMap.empty)
def apply(): PNCounterMap = empty
def empty[A]: PNCounterMap[A] = new PNCounterMap(ORMap.empty)
def apply[A](): PNCounterMap[A] = empty
/**
* Java API
*/
def create(): PNCounterMap = empty
def create[A](): PNCounterMap[A] = empty
/**
* Extract the [[PNCounterMap#entries]].
*/
def unapply(m: PNCounterMap): Option[Map[String, BigInt]] = Some(m.entries)
def unapply[A](m: PNCounterMap[A]): Option[Map[A, BigInt]] = Some(m.entries)
}
/**
@ -27,17 +27,17 @@ object PNCounterMap {
* This class is immutable, i.e. "modifying" methods return a new instance.
*/
@SerialVersionUID(1L)
final class PNCounterMap private[akka] (
private[akka] val underlying: ORMap[PNCounter])
final class PNCounterMap[A] private[akka] (
private[akka] val underlying: ORMap[A, PNCounter])
extends ReplicatedData with ReplicatedDataSerialization with RemovedNodePruning {
type T = PNCounterMap
type T = PNCounterMap[A]
/** Scala API */
def entries: Map[String, BigInt] = underlying.entries.map { case (k, c) k c.value }
def entries: Map[A, BigInt] = underlying.entries.map { case (k, c) k c.value }
/** Java API */
def getEntries: java.util.Map[String, BigInteger] = {
def getEntries: java.util.Map[A, BigInteger] = {
import scala.collection.JavaConverters._
underlying.entries.map { case (k, c) k c.value.bigInteger }.asJava
}
@ -45,14 +45,14 @@ final class PNCounterMap private[akka] (
/**
* Scala API: The count for a key
*/
def get(key: String): Option[BigInt] = underlying.get(key).map(_.value)
def get(key: A): Option[BigInt] = underlying.get(key).map(_.value)
/**
* Java API: The count for a key, or `null` if it doesn't exist
*/
def getValue(key: String): BigInteger = underlying.get(key).map(_.value.bigInteger).orNull
def getValue(key: A): BigInteger = underlying.get(key).map(_.value.bigInteger).orNull
def contains(key: String): Boolean = underlying.contains(key)
def contains(key: A): Boolean = underlying.contains(key)
def isEmpty: Boolean = underlying.isEmpty
@ -62,40 +62,40 @@ final class PNCounterMap private[akka] (
* Increment the counter with the delta specified.
* If the delta is negative then it will decrement instead of increment.
*/
def increment(key: String, delta: Long = 1)(implicit node: Cluster): PNCounterMap =
def increment(key: A, delta: Long = 1)(implicit node: Cluster): PNCounterMap[A] =
increment(node, key, delta)
/**
* Increment the counter with the delta specified.
* If the delta is negative then it will decrement instead of increment.
*/
def increment(node: Cluster, key: String, delta: Long): PNCounterMap =
def increment(node: Cluster, key: A, delta: Long): PNCounterMap[A] =
increment(node.selfUniqueAddress, key, delta)
/**
* INTERNAL API
*/
private[akka] def increment(node: UniqueAddress, key: String, delta: Long): PNCounterMap =
private[akka] def increment(node: UniqueAddress, key: A, delta: Long): PNCounterMap[A] =
new PNCounterMap(underlying.updated(node, key, PNCounter())(_.increment(node, delta)))
/**
* Decrement the counter with the delta specified.
* If the delta is negative then it will increment instead of decrement.
*/
def decrement(key: String, delta: Long = 1)(implicit node: Cluster): PNCounterMap =
def decrement(key: A, delta: Long = 1)(implicit node: Cluster): PNCounterMap[A] =
decrement(node, key, delta)
/**
* Decrement the counter with the delta specified.
* If the delta is negative then it will increment instead of decrement.
*/
def decrement(node: Cluster, key: String, delta: Long): PNCounterMap =
def decrement(node: Cluster, key: A, delta: Long): PNCounterMap[A] =
decrement(node.selfUniqueAddress, key, delta)
/**
* INTERNAL API
*/
private[akka] def decrement(node: UniqueAddress, key: String, delta: Long): PNCounterMap = {
private[akka] def decrement(node: UniqueAddress, key: A, delta: Long): PNCounterMap[A] = {
new PNCounterMap(underlying.updated(node, key, PNCounter())(_.decrement(node, delta)))
}
@ -104,32 +104,32 @@ final class PNCounterMap private[akka] (
* Note that if there is a conflicting update on another node the entry will
* not be removed after merge.
*/
def -(key: String)(implicit node: Cluster): PNCounterMap = remove(node, key)
def -(key: A)(implicit node: Cluster): PNCounterMap[A] = remove(node, key)
/**
* Removes an entry from the map.
* Note that if there is a conflicting update on another node the entry will
* not be removed after merge.
*/
def remove(node: Cluster, key: String): PNCounterMap =
def remove(node: Cluster, key: A): PNCounterMap[A] =
remove(node.selfUniqueAddress, key)
/**
* INTERNAL API
*/
private[akka] def remove(node: UniqueAddress, key: String): PNCounterMap =
private[akka] def remove(node: UniqueAddress, key: A): PNCounterMap[A] =
new PNCounterMap(underlying.remove(node, key))
override def merge(that: PNCounterMap): PNCounterMap =
override def merge(that: PNCounterMap[A]): PNCounterMap[A] =
new PNCounterMap(underlying.merge(that.underlying))
override def needPruningFrom(removedNode: UniqueAddress): Boolean =
underlying.needPruningFrom(removedNode)
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): PNCounterMap =
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): PNCounterMap[A] =
new PNCounterMap(underlying.prune(removedNode, collapseInto))
override def pruningCleanup(removedNode: UniqueAddress): PNCounterMap =
override def pruningCleanup(removedNode: UniqueAddress): PNCounterMap[A] =
new PNCounterMap(underlying.pruningCleanup(removedNode))
// this class cannot be a `case class` because we need different `unapply`
@ -137,16 +137,16 @@ final class PNCounterMap private[akka] (
override def toString: String = s"PNCounter$entries"
override def equals(o: Any): Boolean = o match {
case other: PNCounterMap underlying == other.underlying
case _ false
case other: PNCounterMap[A] underlying == other.underlying
case _ false
}
override def hashCode: Int = underlying.hashCode
}
object PNCounterMapKey {
def create[A](id: String): Key[PNCounterMap] = PNCounterMapKey(id)
def create[A](id: String): Key[PNCounterMap[A]] = PNCounterMapKey[A](id)
}
@SerialVersionUID(1L)
final case class PNCounterMapKey(_id: String) extends Key[PNCounterMap](_id) with ReplicatedDataSerialization
final case class PNCounterMapKey[A](_id: String) extends Key[PNCounterMap[A]](_id) with ReplicatedDataSerialization

View file

@ -3,10 +3,11 @@
*/
package akka.cluster.ddata.protobuf
import java.{ lang jl }
import java.{ util, lang jl }
import java.util.ArrayList
import java.util.Collections
import java.util.Comparator
import java.util.TreeSet
import scala.annotation.tailrec
import scala.collection.JavaConverters._
import scala.collection.breakOut
@ -17,11 +18,137 @@ import akka.cluster.ddata.protobuf.msg.{ ReplicatedDataMessages ⇒ rd }
import akka.cluster.ddata.protobuf.msg.{ ReplicatorMessages dm }
import akka.serialization.SerializerWithStringManifest
import akka.serialization.BaseSerializer
import akka.protobuf.ByteString
import akka.protobuf.{ ByteString, GeneratedMessage }
import akka.util.ByteString.UTF_8
import scala.collection.immutable.TreeMap
import akka.cluster.UniqueAddress
import java.io.NotSerializableException
import akka.cluster.ddata.protobuf.msg.ReplicatorMessages.OtherMessage
private object ReplicatedDataSerializer {
/*
* Generic superclass to allow to compare Entry types used in protobuf.
*/
abstract class KeyComparator[A <: GeneratedMessage] extends Comparator[A] {
/**
* Get the key from the entry. The key may be a String, Integer, Long, or Any
* @param entry The protobuf entry used with Map types
* @return The Key
*/
def getKey(entry: A): Any
final def compare(x: A, y: A): Int = compareKeys(getKey(x), getKey(y))
private final def compareKeys(t1: Any, t2: Any): Int = (t1, t2) match {
case (k1: String, k2: String) k1.compareTo(k2)
case (k1: String, k2) -1
case (k1, k2: String) 1
case (k1: Int, k2: Int) k1.compareTo(k2)
case (k1: Int, k2) -1
case (k1, k2: Int) 1
case (k1: Long, k2: Long) k1.compareTo(k2)
case (k1: Long, k2) -1
case (k1, k2: Long) 1
case (k1: OtherMessage, k2: OtherMessage) OtherMessageComparator.compare(k1, k2)
}
}
implicit object ORMapEntryComparator extends KeyComparator[rd.ORMap.Entry] {
override def getKey(e: rd.ORMap.Entry): Any = if (e.hasStringKey) e.getStringKey else if (e.hasIntKey) e.getIntKey else if (e.hasLongKey) e.getLongKey else e.getOtherKey
}
implicit object LWWMapEntryComparator extends KeyComparator[rd.LWWMap.Entry] {
override def getKey(e: rd.LWWMap.Entry): Any = if (e.hasStringKey) e.getStringKey else if (e.hasIntKey) e.getIntKey else if (e.hasLongKey) e.getLongKey else e.getOtherKey
}
implicit object PNCounterMapEntryComparator extends KeyComparator[rd.PNCounterMap.Entry] {
override def getKey(e: rd.PNCounterMap.Entry): Any = if (e.hasStringKey) e.getStringKey else if (e.hasIntKey) e.getIntKey else if (e.hasLongKey) e.getLongKey else e.getOtherKey
}
implicit object ORMultiMapEntryComparator extends KeyComparator[rd.ORMultiMap.Entry] {
override def getKey(e: rd.ORMultiMap.Entry): Any = if (e.hasStringKey) e.getStringKey else if (e.hasIntKey) e.getIntKey else if (e.hasLongKey) e.getLongKey else e.getOtherKey
}
sealed trait ProtoMapEntryWriter[Entry <: GeneratedMessage, EntryBuilder <: GeneratedMessage.Builder[EntryBuilder], Value <: GeneratedMessage] {
def setStringKey(builder: EntryBuilder, key: String, value: Value): Entry
def setLongKey(builder: EntryBuilder, key: Long, value: Value): Entry
def setIntKey(builder: EntryBuilder, key: Int, value: Value): Entry
def setOtherKey(builder: EntryBuilder, key: dm.OtherMessage, value: Value): Entry
}
sealed trait ProtoMapEntryReader[Entry <: GeneratedMessage, A <: GeneratedMessage] {
def hasStringKey(entry: Entry): Boolean
def getStringKey(entry: Entry): String
def hasIntKey(entry: Entry): Boolean
def getIntKey(entry: Entry): Int
def hasLongKey(entry: Entry): Boolean
def getLongKey(entry: Entry): Long
def hasOtherKey(entry: Entry): Boolean
def getOtherKey(entry: Entry): dm.OtherMessage
def getValue(entry: Entry): A
}
implicit object ORMapEntry extends ProtoMapEntryWriter[rd.ORMap.Entry, rd.ORMap.Entry.Builder, dm.OtherMessage] with ProtoMapEntryReader[rd.ORMap.Entry, dm.OtherMessage] {
override def setStringKey(builder: rd.ORMap.Entry.Builder, key: String, value: dm.OtherMessage): rd.ORMap.Entry = builder.setStringKey(key).setValue(value).build()
override def setLongKey(builder: rd.ORMap.Entry.Builder, key: Long, value: dm.OtherMessage): rd.ORMap.Entry = builder.setLongKey(key).setValue(value).build()
override def setIntKey(builder: rd.ORMap.Entry.Builder, key: Int, value: dm.OtherMessage): rd.ORMap.Entry = builder.setIntKey(key).setValue(value).build()
override def setOtherKey(builder: rd.ORMap.Entry.Builder, key: dm.OtherMessage, value: dm.OtherMessage): rd.ORMap.Entry = builder.setOtherKey(key).setValue(value).build()
override def hasStringKey(entry: rd.ORMap.Entry): Boolean = entry.hasStringKey
override def getStringKey(entry: rd.ORMap.Entry): String = entry.getStringKey
override def hasIntKey(entry: rd.ORMap.Entry): Boolean = entry.hasIntKey
override def getIntKey(entry: rd.ORMap.Entry): Int = entry.getIntKey
override def hasLongKey(entry: rd.ORMap.Entry): Boolean = entry.hasLongKey
override def getLongKey(entry: rd.ORMap.Entry): Long = entry.getLongKey
override def hasOtherKey(entry: rd.ORMap.Entry): Boolean = entry.hasOtherKey
override def getOtherKey(entry: rd.ORMap.Entry): OtherMessage = entry.getOtherKey
override def getValue(entry: rd.ORMap.Entry): dm.OtherMessage = entry.getValue
}
implicit object LWWMapEntry extends ProtoMapEntryWriter[rd.LWWMap.Entry, rd.LWWMap.Entry.Builder, rd.LWWRegister] with ProtoMapEntryReader[rd.LWWMap.Entry, rd.LWWRegister] {
override def setStringKey(builder: rd.LWWMap.Entry.Builder, key: String, value: rd.LWWRegister): rd.LWWMap.Entry = builder.setStringKey(key).setValue(value).build()
override def setLongKey(builder: rd.LWWMap.Entry.Builder, key: Long, value: rd.LWWRegister): rd.LWWMap.Entry = builder.setLongKey(key).setValue(value).build()
override def setIntKey(builder: rd.LWWMap.Entry.Builder, key: Int, value: rd.LWWRegister): rd.LWWMap.Entry = builder.setIntKey(key).setValue(value).build()
override def setOtherKey(builder: rd.LWWMap.Entry.Builder, key: OtherMessage, value: rd.LWWRegister): rd.LWWMap.Entry = builder.setOtherKey(key).setValue(value).build()
override def hasStringKey(entry: rd.LWWMap.Entry): Boolean = entry.hasStringKey
override def getStringKey(entry: rd.LWWMap.Entry): String = entry.getStringKey
override def hasIntKey(entry: rd.LWWMap.Entry): Boolean = entry.hasIntKey
override def getIntKey(entry: rd.LWWMap.Entry): Int = entry.getIntKey
override def hasLongKey(entry: rd.LWWMap.Entry): Boolean = entry.hasLongKey
override def getLongKey(entry: rd.LWWMap.Entry): Long = entry.getLongKey
override def hasOtherKey(entry: rd.LWWMap.Entry): Boolean = entry.hasOtherKey
override def getOtherKey(entry: rd.LWWMap.Entry): OtherMessage = entry.getOtherKey
override def getValue(entry: rd.LWWMap.Entry): rd.LWWRegister = entry.getValue
}
implicit object PNCounterMapEntry extends ProtoMapEntryWriter[rd.PNCounterMap.Entry, rd.PNCounterMap.Entry.Builder, rd.PNCounter] with ProtoMapEntryReader[rd.PNCounterMap.Entry, rd.PNCounter] {
override def setStringKey(builder: rd.PNCounterMap.Entry.Builder, key: String, value: rd.PNCounter): rd.PNCounterMap.Entry = builder.setStringKey(key).setValue(value).build()
override def setLongKey(builder: rd.PNCounterMap.Entry.Builder, key: Long, value: rd.PNCounter): rd.PNCounterMap.Entry = builder.setLongKey(key).setValue(value).build()
override def setIntKey(builder: rd.PNCounterMap.Entry.Builder, key: Int, value: rd.PNCounter): rd.PNCounterMap.Entry = builder.setIntKey(key).setValue(value).build()
override def setOtherKey(builder: rd.PNCounterMap.Entry.Builder, key: OtherMessage, value: rd.PNCounter): rd.PNCounterMap.Entry = builder.setOtherKey(key).setValue(value).build()
override def hasStringKey(entry: rd.PNCounterMap.Entry): Boolean = entry.hasStringKey
override def getStringKey(entry: rd.PNCounterMap.Entry): String = entry.getStringKey
override def hasIntKey(entry: rd.PNCounterMap.Entry): Boolean = entry.hasIntKey
override def getIntKey(entry: rd.PNCounterMap.Entry): Int = entry.getIntKey
override def hasLongKey(entry: rd.PNCounterMap.Entry): Boolean = entry.hasLongKey
override def getLongKey(entry: rd.PNCounterMap.Entry): Long = entry.getLongKey
override def hasOtherKey(entry: rd.PNCounterMap.Entry): Boolean = entry.hasOtherKey
override def getOtherKey(entry: rd.PNCounterMap.Entry): OtherMessage = entry.getOtherKey
override def getValue(entry: rd.PNCounterMap.Entry): rd.PNCounter = entry.getValue
}
implicit object ORMultiMapEntry extends ProtoMapEntryWriter[rd.ORMultiMap.Entry, rd.ORMultiMap.Entry.Builder, rd.ORSet] with ProtoMapEntryReader[rd.ORMultiMap.Entry, rd.ORSet] {
override def setStringKey(builder: rd.ORMultiMap.Entry.Builder, key: String, value: rd.ORSet): rd.ORMultiMap.Entry = builder.setStringKey(key).setValue(value).build()
override def setLongKey(builder: rd.ORMultiMap.Entry.Builder, key: Long, value: rd.ORSet): rd.ORMultiMap.Entry = builder.setLongKey(key).setValue(value).build()
override def setIntKey(builder: rd.ORMultiMap.Entry.Builder, key: Int, value: rd.ORSet): rd.ORMultiMap.Entry = builder.setIntKey(key).setValue(value).build()
override def setOtherKey(builder: rd.ORMultiMap.Entry.Builder, key: dm.OtherMessage, value: rd.ORSet): rd.ORMultiMap.Entry = builder.setOtherKey(key).setValue(value).build()
override def hasStringKey(entry: rd.ORMultiMap.Entry): Boolean = entry.hasStringKey
override def getStringKey(entry: rd.ORMultiMap.Entry): String = entry.getStringKey
override def hasIntKey(entry: rd.ORMultiMap.Entry): Boolean = entry.hasIntKey
override def getIntKey(entry: rd.ORMultiMap.Entry): Int = entry.getIntKey
override def hasLongKey(entry: rd.ORMultiMap.Entry): Boolean = entry.hasLongKey
override def getLongKey(entry: rd.ORMultiMap.Entry): Long = entry.getLongKey
override def hasOtherKey(entry: rd.ORMultiMap.Entry): Boolean = entry.hasOtherKey
override def getOtherKey(entry: rd.ORMultiMap.Entry): OtherMessage = entry.getOtherKey
override def getValue(entry: rd.ORMultiMap.Entry): rd.ORSet = entry.getValue
}
}
/**
* Protobuf serializer of ReplicatedData.
@ -29,6 +156,8 @@ import java.io.NotSerializableException
class ReplicatedDataSerializer(val system: ExtendedActorSystem)
extends SerializerWithStringManifest with SerializationSupport with BaseSerializer {
import ReplicatedDataSerializer._
private val DeletedDataManifest = "A"
private val GSetManifest = "B"
private val GSetKeyManifest = "b"
@ -78,48 +207,48 @@ class ReplicatedDataSerializer(val system: ExtendedActorSystem)
ORMultiMapKeyManifest (bytes ORMultiMapKey(keyIdFromBinary(bytes))))
override def manifest(obj: AnyRef): String = obj match {
case _: ORSet[_] ORSetManifest
case _: GSet[_] GSetManifest
case _: GCounter GCounterManifest
case _: PNCounter PNCounterManifest
case _: Flag FlagManifest
case _: LWWRegister[_] LWWRegisterManifest
case _: ORMap[_] ORMapManifest
case _: LWWMap[_] LWWMapManifest
case _: PNCounterMap PNCounterMapManifest
case _: ORMultiMap[_] ORMultiMapManifest
case DeletedData DeletedDataManifest
case _: VersionVector VersionVectorManifest
case _: ORSet[_] ORSetManifest
case _: GSet[_] GSetManifest
case _: GCounter GCounterManifest
case _: PNCounter PNCounterManifest
case _: Flag FlagManifest
case _: LWWRegister[_] LWWRegisterManifest
case _: ORMap[_, _] ORMapManifest
case _: LWWMap[_, _] LWWMapManifest
case _: PNCounterMap[_] PNCounterMapManifest
case _: ORMultiMap[_, _] ORMultiMapManifest
case DeletedData DeletedDataManifest
case _: VersionVector VersionVectorManifest
case _: ORSetKey[_] ORSetKeyManifest
case _: GSetKey[_] GSetKeyManifest
case _: GCounterKey GCounterKeyManifest
case _: PNCounterKey PNCounterKeyManifest
case _: FlagKey FlagKeyManifest
case _: LWWRegisterKey[_] LWWRegisterKeyManifest
case _: ORMapKey[_] ORMapKeyManifest
case _: LWWMapKey[_] LWWMapKeyManifest
case _: PNCounterMapKey PNCounterMapKeyManifest
case _: ORMultiMapKey[_] ORMultiMapKeyManifest
case _: ORSetKey[_] ORSetKeyManifest
case _: GSetKey[_] GSetKeyManifest
case _: GCounterKey GCounterKeyManifest
case _: PNCounterKey PNCounterKeyManifest
case _: FlagKey FlagKeyManifest
case _: LWWRegisterKey[_] LWWRegisterKeyManifest
case _: ORMapKey[_, _] ORMapKeyManifest
case _: LWWMapKey[_, _] LWWMapKeyManifest
case _: PNCounterMapKey[_] PNCounterMapKeyManifest
case _: ORMultiMapKey[_, _] ORMultiMapKeyManifest
case _
throw new IllegalArgumentException(s"Can't serialize object of type ${obj.getClass} in [${getClass.getName}]")
}
def toBinary(obj: AnyRef): Array[Byte] = obj match {
case m: ORSet[_] compress(orsetToProto(m))
case m: GSet[_] gsetToProto(m).toByteArray
case m: GCounter gcounterToProto(m).toByteArray
case m: PNCounter pncounterToProto(m).toByteArray
case m: Flag flagToProto(m).toByteArray
case m: LWWRegister[_] lwwRegisterToProto(m).toByteArray
case m: ORMap[_] compress(ormapToProto(m))
case m: LWWMap[_] compress(lwwmapToProto(m))
case m: PNCounterMap compress(pncountermapToProto(m))
case m: ORMultiMap[_] compress(multimapToProto(m))
case DeletedData dm.Empty.getDefaultInstance.toByteArray
case m: VersionVector versionVectorToProto(m).toByteArray
case Key(id) keyIdToBinary(id)
case m: ORSet[_] compress(orsetToProto(m))
case m: GSet[_] gsetToProto(m).toByteArray
case m: GCounter gcounterToProto(m).toByteArray
case m: PNCounter pncounterToProto(m).toByteArray
case m: Flag flagToProto(m).toByteArray
case m: LWWRegister[_] lwwRegisterToProto(m).toByteArray
case m: ORMap[_, _] compress(ormapToProto(m))
case m: LWWMap[_, _] compress(lwwmapToProto(m))
case m: PNCounterMap[_] compress(pncountermapToProto(m))
case m: ORMultiMap[_, _] compress(multimapToProto(m))
case DeletedData dm.Empty.getDefaultInstance.toByteArray
case m: VersionVector versionVectorToProto(m).toByteArray
case Key(id) keyIdToBinary(id)
case _
throw new IllegalArgumentException(s"Can't serialize object of type ${obj.getClass} in [${getClass.getName}]")
}
@ -328,83 +457,88 @@ class ReplicatedDataSerializer(val system: ExtendedActorSystem)
}
}
def ormapToProto(ormap: ORMap[_]): rd.ORMap = {
val b = rd.ORMap.newBuilder().setKeys(orsetToProto(ormap.keys))
ormap.entries.toVector.sortBy { case (key, _) key }.foreach {
case (key, value) b.addEntries(rd.ORMap.Entry.newBuilder().
setKey(key).setValue(otherMessageToProto(value)))
/*
* Convert a Map[A, B] to an Iterable[Entry] where Entry is the protobuf map entry.
*/
private def getEntries[IKey, IValue, EntryBuilder <: GeneratedMessage.Builder[EntryBuilder], PEntry <: GeneratedMessage, PValue <: GeneratedMessage](input: Map[IKey, IValue], createBuilder: () EntryBuilder, valueConverter: IValue PValue)(implicit comparator: Comparator[PEntry], eh: ProtoMapEntryWriter[PEntry, EntryBuilder, PValue]): java.lang.Iterable[PEntry] = {
// The resulting Iterable needs to be ordered deterministically in order to create same signature upon serializing same data
val protoEntries = new TreeSet[PEntry](comparator)
input.foreach {
case (key: String, value) protoEntries.add(eh.setStringKey(createBuilder(), key, valueConverter(value)))
case (key: Int, value) protoEntries.add(eh.setIntKey(createBuilder(), key, valueConverter(value)))
case (key: Long, value) protoEntries.add(eh.setLongKey(createBuilder(), key, valueConverter(value)))
case (key, value) protoEntries.add(eh.setOtherKey(createBuilder(), otherMessageToProto(key), valueConverter(value)))
}
b.build()
protoEntries
}
def ormapFromBinary(bytes: Array[Byte]): ORMap[ReplicatedData] =
def ormapToProto(ormap: ORMap[_, _]): rd.ORMap = {
val entries: jl.Iterable[rd.ORMap.Entry] = getEntries(ormap.values, rd.ORMap.Entry.newBuilder, otherMessageToProto)
rd.ORMap.newBuilder().setKeys(orsetToProto(ormap.keys)).addAllEntries(entries).build()
}
def ormapFromBinary(bytes: Array[Byte]): ORMap[Any, ReplicatedData] =
ormapFromProto(rd.ORMap.parseFrom(decompress(bytes)))
def ormapFromProto(ormap: rd.ORMap): ORMap[ReplicatedData] = {
val entries = ormap.getEntriesList.asScala.map(entry
entry.getKey otherMessageFromProto(entry.getValue).asInstanceOf[ReplicatedData]).toMap
def mapTypeFromProto[PEntry <: GeneratedMessage, A <: GeneratedMessage, B <: ReplicatedData](input: util.List[PEntry], valueCreator: A B)(implicit eh: ProtoMapEntryReader[PEntry, A]): Map[Any, B] = {
input.asScala.map { entry
if (eh.hasStringKey(entry)) eh.getStringKey(entry) valueCreator(eh.getValue(entry))
else if (eh.hasIntKey(entry)) eh.getIntKey(entry) valueCreator(eh.getValue(entry))
else if (eh.hasLongKey(entry)) eh.getLongKey(entry) valueCreator(eh.getValue(entry))
else if (eh.hasOtherKey(entry)) otherMessageFromProto(eh.getOtherKey(entry)) valueCreator(eh.getValue(entry))
else throw new IllegalArgumentException(s"Can't deserialize ${entry.getClass} because it does not have any key in the serialized message.")
}.toMap
}
def ormapFromProto(ormap: rd.ORMap): ORMap[Any, ReplicatedData] = {
val entries = mapTypeFromProto(ormap.getEntriesList, (v: dm.OtherMessage) otherMessageFromProto(v).asInstanceOf[ReplicatedData])
new ORMap(
keys = orsetFromProto(ormap.getKeys).asInstanceOf[ORSet[String]],
keys = orsetFromProto(ormap.getKeys),
entries)
}
def lwwmapToProto(lwwmap: LWWMap[_]): rd.LWWMap = {
val b = rd.LWWMap.newBuilder().setKeys(orsetToProto(lwwmap.underlying.keys))
lwwmap.underlying.entries.toVector.sortBy { case (key, _) key }.foreach {
case (key, value) b.addEntries(rd.LWWMap.Entry.newBuilder().
setKey(key).setValue(lwwRegisterToProto(value)))
}
b.build()
def lwwmapToProto(lwwmap: LWWMap[_, _]): rd.LWWMap = {
val entries: jl.Iterable[rd.LWWMap.Entry] = getEntries(lwwmap.underlying.entries, rd.LWWMap.Entry.newBuilder, lwwRegisterToProto)
rd.LWWMap.newBuilder().setKeys(orsetToProto(lwwmap.underlying.keys)).addAllEntries(entries).build()
}
def lwwmapFromBinary(bytes: Array[Byte]): LWWMap[Any] =
def lwwmapFromBinary(bytes: Array[Byte]): LWWMap[Any, Any] =
lwwmapFromProto(rd.LWWMap.parseFrom(decompress(bytes)))
def lwwmapFromProto(lwwmap: rd.LWWMap): LWWMap[Any] = {
val entries = lwwmap.getEntriesList.asScala.map(entry
entry.getKey lwwRegisterFromProto(entry.getValue)).toMap
def lwwmapFromProto(lwwmap: rd.LWWMap): LWWMap[Any, Any] = {
val entries = mapTypeFromProto(lwwmap.getEntriesList, lwwRegisterFromProto)
new LWWMap(new ORMap(
keys = orsetFromProto(lwwmap.getKeys).asInstanceOf[ORSet[String]],
keys = orsetFromProto(lwwmap.getKeys),
entries))
}
def pncountermapToProto(pncountermap: PNCounterMap): rd.PNCounterMap = {
val b = rd.PNCounterMap.newBuilder().setKeys(orsetToProto(pncountermap.underlying.keys))
pncountermap.underlying.entries.toVector.sortBy { case (key, _) key }.foreach {
case (key, value: PNCounter) b.addEntries(rd.PNCounterMap.Entry.newBuilder().
setKey(key).setValue(pncounterToProto(value)))
}
b.build()
def pncountermapToProto(pncountermap: PNCounterMap[_]): rd.PNCounterMap = {
val entries: jl.Iterable[rd.PNCounterMap.Entry] = getEntries(pncountermap.underlying.entries, rd.PNCounterMap.Entry.newBuilder, pncounterToProto)
rd.PNCounterMap.newBuilder().setKeys(orsetToProto(pncountermap.underlying.keys)).addAllEntries(entries).build()
}
def pncountermapFromBinary(bytes: Array[Byte]): PNCounterMap =
def pncountermapFromBinary(bytes: Array[Byte]): PNCounterMap[_] =
pncountermapFromProto(rd.PNCounterMap.parseFrom(decompress(bytes)))
def pncountermapFromProto(pncountermap: rd.PNCounterMap): PNCounterMap = {
val entries = pncountermap.getEntriesList.asScala.map(entry
entry.getKey pncounterFromProto(entry.getValue)).toMap
def pncountermapFromProto(pncountermap: rd.PNCounterMap): PNCounterMap[_] = {
val entries = mapTypeFromProto(pncountermap.getEntriesList, pncounterFromProto)
new PNCounterMap(new ORMap(
keys = orsetFromProto(pncountermap.getKeys).asInstanceOf[ORSet[String]],
keys = orsetFromProto(pncountermap.getKeys),
entries))
}
def multimapToProto(multimap: ORMultiMap[_]): rd.ORMultiMap = {
val b = rd.ORMultiMap.newBuilder().setKeys(orsetToProto(multimap.underlying.keys))
multimap.underlying.entries.toVector.sortBy { case (key, _) key }.foreach {
case (key, value) b.addEntries(rd.ORMultiMap.Entry.newBuilder().
setKey(key).setValue(orsetToProto(value)))
}
b.build()
def multimapToProto(multimap: ORMultiMap[_, _]): rd.ORMultiMap = {
val entries: jl.Iterable[rd.ORMultiMap.Entry] = getEntries(multimap.underlying.entries, rd.ORMultiMap.Entry.newBuilder, orsetToProto)
rd.ORMultiMap.newBuilder().setKeys(orsetToProto(multimap.underlying.keys)).addAllEntries(entries).build()
}
def multimapFromBinary(bytes: Array[Byte]): ORMultiMap[Any] =
def multimapFromBinary(bytes: Array[Byte]): ORMultiMap[Any, Any] =
multimapFromProto(rd.ORMultiMap.parseFrom(decompress(bytes)))
def multimapFromProto(multimap: rd.ORMultiMap): ORMultiMap[Any] = {
val entries = multimap.getEntriesList.asScala.map(entry
entry.getKey orsetFromProto(entry.getValue)).toMap
def multimapFromProto(multimap: rd.ORMultiMap): ORMultiMap[Any, Any] = {
val entries = mapTypeFromProto(multimap.getEntriesList, orsetFromProto)
new ORMultiMap(new ORMap(
keys = orsetFromProto(multimap.getKeys).asInstanceOf[ORSet[String]],
keys = orsetFromProto(multimap.getKeys),
entries))
}

View file

@ -46,7 +46,7 @@ class ReplicatorPruningSpec extends MultiNodeSpec(ReplicatorPruningSpec) with ST
val KeyA = GCounterKey("A")
val KeyB = ORSetKey[String]("B")
val KeyC = PNCounterMapKey("C")
val KeyC = PNCounterMapKey[String]("C")
def join(from: RoleName, to: RoleName): Unit = {
runOn(from) {
@ -86,7 +86,7 @@ class ReplicatorPruningSpec extends MultiNodeSpec(ReplicatorPruningSpec) with ST
replicator ! Update(KeyB, ORSet(), WriteAll(timeout))(_ + "a" + "b" + "c")
expectMsg(UpdateSuccess(KeyB, None))
replicator ! Update(KeyC, PNCounterMap(), WriteAll(timeout))(_ increment "x" increment "y")
replicator ! Update(KeyC, PNCounterMap.empty[String], WriteAll(timeout)) { _ increment "x" increment "y" }
expectMsg(UpdateSuccess(KeyC, None))
enterBarrier("updates-done")
@ -100,7 +100,7 @@ class ReplicatorPruningSpec extends MultiNodeSpec(ReplicatorPruningSpec) with ST
oldSet.elements should be(Set("a", "b", "c"))
replicator ! Get(KeyC, ReadLocal)
val oldMap = expectMsgType[GetSuccess[PNCounterMap]].dataValue
val oldMap = expectMsgType[GetSuccess[PNCounterMap[String]]].dataValue
oldMap.get("x") should be(Some(3))
oldMap.get("y") should be(Some(3))

View file

@ -57,7 +57,7 @@ class ReplicatorSpec extends MultiNodeSpec(ReplicatorSpec) with STMultiNodeSpec
val KeyE2 = GCounterKey("E2")
val KeyF = GCounterKey("F")
val KeyG = ORSetKey[String]("G")
val KeyH = ORMapKey[Flag]("H")
val KeyH = ORMapKey[String, Flag]("H")
val KeyI = GSetKey[String]("I")
val KeyJ = GSetKey[String]("J")
val KeyX = GCounterKey("X")
@ -526,20 +526,20 @@ class ReplicatorSpec extends MultiNodeSpec(ReplicatorSpec) with STMultiNodeSpec
runOn(second) {
replicator ! Subscribe(KeyH, changedProbe.ref)
replicator ! Update(KeyH, ORMap.empty[Flag], writeTwo)(_ + ("a" Flag(enabled = false)))
replicator ! Update(KeyH, ORMap.empty[String, Flag], writeTwo)(_ + ("a" Flag(enabled = false)))
changedProbe.expectMsgPF() { case c @ Changed(KeyH) c.get(KeyH).entries } should be(Map("a" Flag(enabled = false)))
}
enterBarrier("update-h1")
runOn(first) {
replicator ! Update(KeyH, ORMap.empty[Flag], writeTwo)(_ + ("a" Flag(enabled = true)))
replicator ! Update(KeyH, ORMap.empty[String, Flag], writeTwo)(_ + ("a" Flag(enabled = true)))
}
runOn(second) {
changedProbe.expectMsgPF() { case c @ Changed(KeyH) c.get(KeyH).entries } should be(Map("a" Flag(enabled = true)))
replicator ! Update(KeyH, ORMap.empty[Flag], writeTwo)(_ + ("b" Flag(enabled = true)))
replicator ! Update(KeyH, ORMap.empty[String, Flag], writeTwo)(_ + ("b" Flag(enabled = true)))
changedProbe.expectMsgPF() { case c @ Changed(KeyH) c.get(KeyH).entries } should be(
Map("a" Flag(enabled = true), "b" Flag(enabled = true)))
}

View file

@ -19,7 +19,7 @@ class LWWMapSpec extends WordSpec with Matchers {
"A LWWMap" must {
"be able to set entries" in {
val m = LWWMap.empty[Int].put(node1, "a", 1, defaultClock[Int]).put(node2, "b", 2, defaultClock[Int])
val m = LWWMap.empty[String, Int].put(node1, "a", 1, defaultClock[Int]).put(node2, "b", 2, defaultClock[Int])
m.entries should be(Map("a" 1, "b" 2))
}
@ -51,7 +51,7 @@ class LWWMapSpec extends WordSpec with Matchers {
val m1 = LWWMap.empty.put(node1, "a", 1L, defaultClock[Long])
val LWWMap(entries1) = m1
val entries2: Map[String, Long] = entries1
Changed(LWWMapKey[Long]("key"))(m1) match {
Changed(LWWMapKey[String, Long]("key"))(m1) match {
case c @ Changed(LWWMapKey("key"))
val LWWMap(entries3) = c.dataValue
val entries4: Map[String, Long] = entries3

View file

@ -119,11 +119,11 @@ class ORMapSpec extends WordSpec with Matchers {
}
"be able to update entry" in {
val m1 = ORMap.empty[ORSet[String]].put(node1, "a", ORSet.empty.add(node1, "A"))
val m1 = ORMap.empty[String, ORSet[String]].put(node1, "a", ORSet.empty.add(node1, "A"))
.put(node1, "b", ORSet.empty.add(node1, "B01").add(node1, "B02").add(node1, "B03"))
val m2 = ORMap.empty[ORSet[String]].put(node2, "c", ORSet.empty.add(node2, "C"))
val m2 = ORMap.empty[String, ORSet[String]].put(node2, "c", ORSet.empty.add(node2, "C"))
val merged1: ORMap[ORSet[String]] = m1 merge m2
val merged1: ORMap[String, ORSet[String]] = m1 merge m2
val m3 = merged1.updated(node1, "b", ORSet.empty[String])(_.clear(node1).add(node1, "B2"))
@ -140,11 +140,11 @@ class ORMapSpec extends WordSpec with Matchers {
}
"be able to update ORSet entry with remove+put" in {
val m1 = ORMap.empty[ORSet[String]].put(node1, "a", ORSet.empty.add(node1, "A01"))
val m1 = ORMap.empty[String, ORSet[String]].put(node1, "a", ORSet.empty.add(node1, "A01"))
.updated(node1, "a", ORSet.empty[String])(_.add(node1, "A02"))
.updated(node1, "a", ORSet.empty[String])(_.add(node1, "A03"))
.put(node1, "b", ORSet.empty.add(node1, "B01").add(node1, "B02").add(node1, "B03"))
val m2 = ORMap.empty[ORSet[String]].put(node2, "c", ORSet.empty.add(node2, "C"))
val m2 = ORMap.empty[String, ORSet[String]].put(node2, "c", ORSet.empty.add(node2, "C"))
val merged1 = m1 merge m2
@ -190,10 +190,10 @@ class ORMapSpec extends WordSpec with Matchers {
"have unapply extractor" in {
val m1 = ORMap.empty.put(node1, "a", Flag(true)).put(node2, "b", Flag(false))
val m2: ORMap[Flag] = m1
val m2: ORMap[String, Flag] = m1
val ORMap(entries1) = m1
val entries2: Map[String, Flag] = entries1
Changed(ORMapKey[Flag]("key"))(m1) match {
Changed(ORMapKey[String, Flag]("key"))(m1) match {
case c @ Changed(ORMapKey("key"))
val ORMap(entries3) = c.dataValue
val entries4: Map[String, ReplicatedData] = entries3

View file

@ -109,10 +109,10 @@ class ORMultiMapSpec extends WordSpec with Matchers {
"have unapply extractor" in {
val m1 = ORMultiMap.empty.put(node1, "a", Set(1L, 2L)).put(node2, "b", Set(3L))
val m2: ORMultiMap[Long] = m1
val m2: ORMultiMap[String, Long] = m1
val ORMultiMap(entries1) = m1
val entries2: Map[String, Set[Long]] = entries1
Changed(ORMultiMapKey[Long]("key"))(m1) match {
Changed(ORMultiMapKey[String, Long]("key"))(m1) match {
case c @ Changed(ORMultiMapKey("key"))
val ORMultiMap(entries3) = c.dataValue
val entries4: Map[String, Set[Long]] = entries3

View file

@ -50,7 +50,7 @@ class PNCounterMapSpec extends WordSpec with Matchers {
val m1 = PNCounterMap.empty.increment(node1, "a", 1).increment(node2, "b", 2)
val PNCounterMap(entries1) = m1
val entries2: Map[String, BigInt] = entries1
Changed(PNCounterMapKey("key"))(m1) match {
Changed(PNCounterMapKey[String]("key"))(m1) match {
case c @ Changed(PNCounterMapKey("key"))
val PNCounterMap(entries3) = c.dataValue
val entries4: Map[String, BigInt] = entries3

View file

@ -3,24 +3,16 @@
*/
package akka.cluster.ddata.protobuf
import java.util.Base64
import org.scalatest.BeforeAndAfterAll
import org.scalatest.Matchers
import org.scalatest.WordSpecLike
import akka.actor.ActorSystem
import akka.actor.Address
import akka.actor.ExtendedActorSystem
import akka.cluster.ddata.Flag
import akka.cluster.ddata.GCounter
import akka.cluster.ddata.GSet
import akka.cluster.ddata.LWWMap
import akka.cluster.ddata.LWWRegister
import akka.cluster.ddata.ORMap
import akka.cluster.ddata.ORMultiMap
import akka.cluster.ddata.ORSet
import akka.cluster.ddata.PNCounter
import akka.cluster.ddata.PNCounterMap
import akka.cluster.ddata._
import akka.cluster.ddata.Replicator.Internal._
import akka.cluster.ddata.VersionVector
import akka.testkit.TestKit
import akka.cluster.UniqueAddress
import akka.remote.RARP
@ -46,6 +38,17 @@ class ReplicatedDataSerializerSpec extends TestKit(ActorSystem(
shutdown()
}
/**
* Given a blob created with the previous serializer (with only string keys for maps). If we deserialize it and then
* serialize it again and arive at the same BLOB we can assume that we are compatible in both directions.
*/
def checkCompatibility(oldBlobAsBase64: String, obj: AnyRef): Unit = {
val oldBlob = Base64.getDecoder.decode(oldBlobAsBase64)
val deserialized = serializer.fromBinary(oldBlob, serializer.manifest(obj))
val newBlob = serializer.toBinary(deserialized)
newBlob should equal(oldBlob)
}
def checkSerialization(obj: AnyRef): Unit = {
val blob = serializer.toBinary(obj)
val ref = serializer.fromBinary(blob, serializer.manifest(obj))
@ -140,38 +143,73 @@ class ReplicatedDataSerializerSpec extends TestKit(ActorSystem(
}
"serialize ORMap" in {
checkSerialization(ORMap())
checkSerialization(ORMap().put(address1, "a", GSet() + "A"))
checkSerialization(ORMap().put(address1, "a", GSet() + "A").put(address2, "b", GSet() + "B"))
checkSerialization(ORMap().put(address1, 1, GSet() + "A"))
checkSerialization(ORMap().put(address1, 1L, GSet() + "A"))
// use Flag for this test as object key because it is serializable
checkSerialization(ORMap().put(address1, Flag(), GSet() + "A"))
}
"be compatible with old ORMap serialization" in {
// Below blob was created with previous version of the serializer
val oldBlobAsBase64 = "H4sIAAAAAAAAAOOax8jlyaXMJc8lzMWXX5KRWqSXkV9copdflC7wXEWUiYGBQRaIGQQkuJS45LiEuHiL83NTUdQwwtWIC6kQpUqVKAulGBOlGJOE+LkYE4W4uJi5GB0FuJUYnUACSRABJ7AAAOLO3C3DAAAA"
checkCompatibility(oldBlobAsBase64, ORMap())
}
"serialize LWWMap" in {
checkSerialization(LWWMap())
checkSerialization(LWWMap().put(address1, "a", "value1", LWWRegister.defaultClock[Any]))
checkSerialization(LWWMap().put(address1, 1, "value1", LWWRegister.defaultClock[Any]))
checkSerialization(LWWMap().put(address1, 1L, "value1", LWWRegister.defaultClock[Any]))
checkSerialization(LWWMap().put(address1, Flag(), "value1", LWWRegister.defaultClock[Any]))
checkSerialization(LWWMap().put(address1, "a", "value1", LWWRegister.defaultClock[Any])
.put(address2, "b", 17, LWWRegister.defaultClock[Any]))
}
"be compatible with old LWWMap serialization" in {
// Below blob was created with previous version of the serializer
val oldBlobAsBase64 = "H4sIAAAAAAAAAOPy51LhUuKS4xLi4i3Oz03Vy8gvLtHLL0oXeK4iysjAwCALxAwC0kJEqZJiTBSy5wISVhwzrl2fuyRMiIAWKUEu3jVvGVhLGNjKEnNKUw0FGAG1K/3VkgAAAA=="
checkCompatibility(oldBlobAsBase64, LWWMap())
}
"serialize PNCounterMap" in {
checkSerialization(PNCounterMap())
checkSerialization(PNCounterMap().increment(address1, "a", 3))
checkSerialization(PNCounterMap().increment(address1, 1, 3))
checkSerialization(PNCounterMap().increment(address1, 1L, 3))
checkSerialization(PNCounterMap().increment(address1, Flag(), 3))
checkSerialization(PNCounterMap().increment(address1, "a", 3).decrement(address2, "a", 2).
increment(address2, "b", 5))
}
"be compatible with old PNCounterMap serialization" in {
// Below blob was created with previous version of the serializer
val oldBlobAsBase64 = "H4sIAAAAAAAAAOPy51LhUuKS4xLi4i3Oz03Vy8gvLtHLL0oXeK4iysjAwCALxAwC8kJEqZJiTBTS4wISmlyqXMqE1AsxMgsxAADYQs/9gQAAAA=="
checkCompatibility(oldBlobAsBase64, PNCounterMap())
}
"serialize ORMultiMap" in {
checkSerialization(ORMultiMap())
checkSerialization(ORMultiMap().addBinding(address1, "a", "A"))
checkSerialization(ORMultiMap.empty[String]
checkSerialization(ORMultiMap().addBinding(address1, 1, "A"))
checkSerialization(ORMultiMap().addBinding(address1, 1L, "A"))
checkSerialization(ORMultiMap().addBinding(address1, Flag(), "A"))
checkSerialization(ORMultiMap.empty[String, String]
.addBinding(address1, "a", "A1")
.put(address2, "b", Set("B1", "B2", "B3"))
.addBinding(address2, "a", "A2"))
val m1 = ORMultiMap.empty[String].addBinding(address1, "a", "A1").addBinding(address2, "a", "A2")
val m2 = ORMultiMap.empty[String].put(address2, "b", Set("B1", "B2", "B3"))
val m1 = ORMultiMap.empty[String, String].addBinding(address1, "a", "A1").addBinding(address2, "a", "A2")
val m2 = ORMultiMap.empty[String, String].put(address2, "b", Set("B1", "B2", "B3"))
checkSameContent(m1.merge(m2), m2.merge(m1))
}
"be compatible with old ORMultiMap serialization" in {
// Below blob was created with previous version of the serializer
val oldBlobAsBase64 = "H4sIAAAAAAAAAOPy51LhUuKS4xLi4i3Oz03Vy8gvLtHLL0oXeK4iysjAwCALxAwCakJEqZJiTBQK4QISxJmqSpSpqlKMjgDlsHjDpwAAAA=="
checkCompatibility(oldBlobAsBase64, ORMultiMap())
}
"serialize DeletedData" in {
checkSerialization(DeletedData)
}