#22035 Make it possible to use anything as the key in a map
- All Map types are now generic in their key: ORMap, ORMultiMap, LWWMap, PNCounterMap - test for binary compatibility with previous version for serialization - entries are sorted for deterministic SHA-1 on same value
This commit is contained in:
parent
5c79b81e92
commit
8499ff6faf
28 changed files with 2231 additions and 584 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue