#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
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue