pekko/akka-distributed-data/src/main/scala/akka/cluster/ddata/ORMap.scala

576 lines
24 KiB
Scala
Raw Normal View History

/*
* Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.cluster.ddata
import akka.cluster.Cluster
import akka.cluster.UniqueAddress
import akka.util.HashCode
import akka.annotation.InternalApi
import akka.cluster.ddata.ORMap.ZeroTag
2017-02-23 01:20:33 +01:00
import scala.collection.immutable
object ORMap {
private val _empty: ORMap[Any, ReplicatedData] = new ORMap(ORSet.empty, Map.empty, VanillaORMapTag)
def empty[A, B <: ReplicatedData]: ORMap[A, B] = _empty.asInstanceOf[ORMap[A, B]]
def apply(): ORMap[Any, ReplicatedData] = _empty
2019-03-11 10:38:24 +01:00
/**
* Java API
*/
def create[A, B <: ReplicatedData](): ORMap[A, B] = empty[A, B]
/**
* Extract the [[ORMap#entries]].
*/
def unapply[A, B <: ReplicatedData](m: ORMap[A, B]): Option[Map[A, B]] = Some(m.entries)
2017-03-20 10:42:38 +01:00
sealed trait DeltaOp extends ReplicatedDelta with RequiresCausalDeliveryOfDeltas with ReplicatedDataSerialization {
2017-02-23 01:20:33 +01:00
type T = DeltaOp
override def zero: DeltaReplicatedData
}
/**
* INTERNAL API
* Tags for ORMap.DeltaOp's, so that when the Replicator needs to re-create full value from delta,
* the right map type will be used
*/
@InternalApi private[akka] trait ZeroTag {
def zero: DeltaReplicatedData
def value: Int
}
/**
* INTERNAL API
*/
@InternalApi private[akka] case object VanillaORMapTag extends ZeroTag {
override def zero: DeltaReplicatedData = ORMap.empty
override final val value: Int = 0
}
/**
* INTERNAL API
*/
2019-03-11 10:38:24 +01:00
@InternalApi private[akka] sealed abstract class AtomicDeltaOp[A, B <: ReplicatedData]
extends DeltaOp
with ReplicatedDeltaSize {
2017-02-23 01:20:33 +01:00
def underlying: ORSet.DeltaOp
def zeroTag: ZeroTag
override def zero: DeltaReplicatedData = zeroTag.zero
override def merge(that: DeltaOp): DeltaOp = that match {
case other: AtomicDeltaOp[A, B] => DeltaGroup(Vector(this, other))
case DeltaGroup(ops) => DeltaGroup(this +: ops)
2017-02-23 01:20:33 +01:00
}
override def deltaSize: Int = 1
2017-02-23 01:20:33 +01:00
}
// PutDeltaOp contains ORSet delta and full value
/** INTERNAL API */
2019-03-11 10:38:24 +01:00
@InternalApi private[akka] final case class PutDeltaOp[A, B <: ReplicatedData](underlying: ORSet.DeltaOp,
value: (A, B),
zeroTag: ZeroTag)
extends AtomicDeltaOp[A, B] {
override def merge(that: DeltaOp): DeltaOp = that match {
case put: PutDeltaOp[A, B] if this.value._1 == put.value._1 =>
new PutDeltaOp[A, B](this.underlying.merge(put.underlying), put.value, zeroTag)
case update: UpdateDeltaOp[A, B] if update.values.size == 1 && update.values.contains(this.value._1) =>
val (key, elem1) = this.value
val newValue = elem1 match {
case e1: DeltaReplicatedData =>
val e2 = update.values.head._2.asInstanceOf[e1.D]
(key, e1.mergeDelta(e2).asInstanceOf[B])
case _ =>
val elem2 = update.values.head._2.asInstanceOf[elem1.T]
(key, elem1.merge(elem2).asInstanceOf[B])
}
new PutDeltaOp[A, B](this.underlying.merge(update.underlying), newValue, zeroTag)
case other: AtomicDeltaOp[A, B] => DeltaGroup(Vector(this, other))
case DeltaGroup(ops) => DeltaGroup(this +: ops)
}
2017-02-23 01:20:33 +01:00
}
// UpdateDeltaOp contains ORSet delta and either delta of value (in case where underlying type supports deltas) or full value
/** INTERNAL API */
2019-03-11 10:38:24 +01:00
@InternalApi private[akka] final case class UpdateDeltaOp[A, B <: ReplicatedData](underlying: ORSet.DeltaOp,
values: Map[A, B],
zeroTag: ZeroTag)
extends AtomicDeltaOp[A, B] {
override def merge(that: DeltaOp): DeltaOp = that match {
case update: UpdateDeltaOp[A, B] =>
2019-03-11 10:38:24 +01:00
new UpdateDeltaOp[A, B](this.underlying.merge(update.underlying), update.values.foldLeft(this.values) {
(map, pair) =>
val (key, value) = pair
if (this.values.contains(key)) {
val elem1 = this.values(key)
val elem2 = value.asInstanceOf[elem1.T]
map + (key -> elem1.merge(elem2).asInstanceOf[B])
} else map + pair
}, zeroTag)
case put: PutDeltaOp[A, B] if this.values.size == 1 && this.values.contains(put.value._1) =>
new PutDeltaOp[A, B](this.underlying.merge(put.underlying), put.value, zeroTag)
case other: AtomicDeltaOp[A, B] => DeltaGroup(Vector(this, other))
case DeltaGroup(ops) => DeltaGroup(this +: ops)
}
2017-02-23 01:20:33 +01:00
}
// RemoveDeltaOp does not contain any value at all - the propagated 'value' map would be empty
2017-02-23 01:20:33 +01:00
/** INTERNAL API */
2019-03-11 10:38:24 +01:00
@InternalApi private[akka] final case class RemoveDeltaOp[A, B <: ReplicatedData](underlying: ORSet.DeltaOp,
zeroTag: ZeroTag)
extends AtomicDeltaOp[A, B]
2017-02-23 01:20:33 +01:00
// RemoveKeyDeltaOp contains a single value - to provide the recipient with the removed key for value map
/** INTERNAL API */
2019-03-11 10:38:24 +01:00
@InternalApi private[akka] final case class RemoveKeyDeltaOp[A, B <: ReplicatedData](underlying: ORSet.DeltaOp,
removedKey: A,
zeroTag: ZeroTag)
extends AtomicDeltaOp[A, B]
2017-02-23 01:20:33 +01:00
// DeltaGroup is effectively a causally ordered list of individual deltas
/** INTERNAL API */
@InternalApi private[akka] final case class DeltaGroup[A, B <: ReplicatedData](ops: immutable.IndexedSeq[DeltaOp])
2019-03-11 10:38:24 +01:00
extends DeltaOp
with ReplicatedDeltaSize {
2017-02-23 01:20:33 +01:00
override def merge(that: DeltaOp): DeltaOp = that match {
case that: AtomicDeltaOp[A, B] =>
ops.last match {
case thisPut: PutDeltaOp[A, B] =>
val merged = thisPut.merge(that)
merged match {
case op: AtomicDeltaOp[A, B] => DeltaGroup(ops.dropRight(1) :+ op)
case DeltaGroup(thatOps) => DeltaGroup(ops.dropRight(1) ++ thatOps)
}
case thisUpdate: UpdateDeltaOp[A, B] =>
val merged = thisUpdate.merge(that)
merged match {
case op: AtomicDeltaOp[A, B] => DeltaGroup(ops.dropRight(1) :+ op)
case DeltaGroup(thatOps) => DeltaGroup(ops.dropRight(1) ++ thatOps)
}
case _ => DeltaGroup(ops :+ that)
}
case DeltaGroup(thatOps) => DeltaGroup(ops ++ thatOps)
2017-02-23 01:20:33 +01:00
}
2019-03-11 10:38:24 +01:00
override def zero: DeltaReplicatedData =
ops.headOption.fold(ORMap.empty[A, B].asInstanceOf[DeltaReplicatedData])(_.zero)
override def deltaSize: Int = ops.size
2017-02-23 01:20:33 +01:00
}
}
/**
* Implements a 'Observed Remove Map' CRDT, also called a 'OR-Map'.
*
* It has similar semantics as an [[ORSet]], but in case of concurrent updates
* the values are merged, and must therefore be [[ReplicatedData]] types themselves.
*
* This class is immutable, i.e. "modifying" methods return a new instance.
*/
@SerialVersionUID(1L)
2019-03-11 10:38:24 +01:00
final class ORMap[A, B <: ReplicatedData] private[akka] (private[akka] val keys: ORSet[A],
private[akka] val values: Map[A, B],
private[akka] val zeroTag: ZeroTag,
override val delta: Option[ORMap.DeltaOp] = None)
extends DeltaReplicatedData
with ReplicatedDataSerialization
with RemovedNodePruning {
2017-02-23 01:20:33 +01:00
2019-03-11 10:38:24 +01:00
import ORMap.{ PutDeltaOp, RemoveDeltaOp, RemoveKeyDeltaOp, UpdateDeltaOp }
type T = ORMap[A, B]
2017-02-23 01:20:33 +01:00
type D = ORMap.DeltaOp
/**
* Scala API: All entries of the map.
*/
def entries: Map[A, B] = values
/**
* Java API: All entries of the map.
*/
def getEntries(): java.util.Map[A, B] = {
import scala.collection.JavaConverters._
entries.asJava
}
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: A, default: => B): B = values.getOrElse(key, default)
def contains(key: A): Boolean = values.contains(key)
def isEmpty: Boolean = values.isEmpty
def size: Int = values.size
/**
* Adds an entry to the map
* @see [[#put]]
*/
def :+(entry: (A, B))(implicit node: SelfUniqueAddress): ORMap[A, B] = {
val (key, value) = entry
put(node.uniqueAddress, key, value)
}
@deprecated("Use `:+` that takes a `SelfUniqueAddress` parameter instead.", since = "2.5.20")
def +(entry: (A, B))(implicit node: Cluster): ORMap[A, B] = {
val (key, value) = entry
put(node.selfUniqueAddress, key, value)
}
/**
* Adds an entry to the map.
* Note that the new `value` will be merged with existing values
* on other nodes and the outcome depends on what `ReplicatedData`
* type that is used.
*
* Consider using [[#updated]] instead of `put` if you want modify
* existing entry.
*
* `IllegalArgumentException` is thrown if you try to replace an existing `ORSet`
* value, because important history can be lost when replacing the `ORSet` and
2015-06-18 16:17:53 +02:00
* undesired effects of merging will occur. Use [[ORMultiMap]] or [[#updated]] instead.
*/
def put(node: SelfUniqueAddress, key: A, value: B): ORMap[A, B] = put(node.uniqueAddress, key, value)
@deprecated("Use `put` that takes a `SelfUniqueAddress` parameter instead.", since = "2.5.20")
def put(node: Cluster, key: A, value: B): ORMap[A, B] = put(node.selfUniqueAddress, key, value)
/**
* INTERNAL API
*/
@InternalApi 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` " +
2019-03-11 10:38:24 +01:00
"value, because important history can be lost when replacing the `ORSet` and " +
"undesired effects of merging will occur. Use `ORMultiMap` or `ORMap.updated` instead.")
2017-02-23 01:20:33 +01:00
else {
val newKeys = keys.resetDelta.add(node, key)
val putDeltaOp = PutDeltaOp(newKeys.delta.get, key -> value, zeroTag)
// put forcibly damages history, so we consciously propagate full value that will overwrite previous value
new ORMap(newKeys, values.updated(key, value), zeroTag, Some(newDelta(putDeltaOp)))
2017-02-23 01:20:33 +01:00
}
/**
* Scala API: Replace a value by applying the `modify` function on the existing value.
*
* If there is no current value for the `key` the `initial` value will be
* passed to the `modify` function.
*/
def updated(node: SelfUniqueAddress, key: A, initial: B)(modify: B => B): ORMap[A, B] =
updated(node.uniqueAddress, key, initial)(modify)
@deprecated("Use `updated` that takes a `SelfUniqueAddress` parameter instead.", since = "2.5.20")
def updated(node: Cluster, key: A, initial: B)(modify: B => B): ORMap[A, B] =
updated(node.selfUniqueAddress, key, initial)(modify)
/**
* Java API: Replace a value by applying the `modify` function on the existing value.
*
* If there is no current value for the `key` the `initial` value will be
* passed to the `modify` function.
*/
@Deprecated
@deprecated("use update for the Java API as updated is ambiguous with the Scala API", "2.5.20")
def updated(node: Cluster, key: A, initial: B, modify: java.util.function.Function[B, B]): ORMap[A, B] =
updated(node.selfUniqueAddress, key, initial)(value => modify.apply(value))
/**
* Java API: Replace a value by applying the `modify` function on the existing value.
*
* If there is no current value for the `key` the `initial` value will be
* passed to the `modify` function.
*/
def update(node: SelfUniqueAddress, key: A, initial: B, modify: java.util.function.Function[B, B]): ORMap[A, B] =
updated(node.uniqueAddress, key, initial)(value => modify.apply(value))
@Deprecated
@deprecated("Use `update` that takes a `SelfUniqueAddress` parameter instead.", since = "2.5.20")
def update(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
*/
2019-03-11 10:38:24 +01:00
@InternalApi private[akka] def updated(node: UniqueAddress, key: A, initial: B, valueDeltas: Boolean = false)(
modify: B => B): ORMap[A, B] = {
2017-02-23 01:20:33 +01:00
val (oldValue, hasOldValue) = values.get(key) match {
case Some(old) => (old, true)
case _ => (initial, false)
2017-02-23 01:20:33 +01:00
}
// Optimization: for some types - like GSet, GCounter, PNCounter and ORSet - that are delta based
// we can emit (and later merge) their deltas instead of full updates.
// However to avoid necessity of tombstones, the derived map type needs to support this
// with clearing the value (e.g. removing all elements if value is a set)
// before removing the key - like e.g. ORMultiMap.emptyWithValueDeltas does
val newKeys = keys.resetDelta.add(node, key)
2017-02-23 01:20:33 +01:00
oldValue match {
case _: DeltaReplicatedData if valueDeltas =>
2017-02-23 01:20:33 +01:00
val newValue = modify(oldValue.asInstanceOf[DeltaReplicatedData].resetDelta.asInstanceOf[B])
val newValueDelta = newValue.asInstanceOf[DeltaReplicatedData].delta
val deltaOp = newValueDelta match {
case Some(d) if hasOldValue => UpdateDeltaOp(newKeys.delta.get, Map(key -> d), zeroTag)
case _ => PutDeltaOp(newKeys.delta.get, key -> newValue, zeroTag)
2017-02-23 01:20:33 +01:00
}
new ORMap(newKeys, values.updated(key, newValue), zeroTag, Some(newDelta(deltaOp)))
case _ =>
2017-02-23 01:20:33 +01:00
val newValue = modify(oldValue)
val deltaOp = PutDeltaOp(newKeys.delta.get, key -> newValue, zeroTag)
new ORMap(newKeys, values.updated(key, newValue), zeroTag, Some(newDelta(deltaOp)))
}
}
/**
* Scala API
* 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(key: A)(implicit node: SelfUniqueAddress): ORMap[A, B] = remove(node.uniqueAddress, key)
/**
* Java API
* 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: SelfUniqueAddress, key: A): ORMap[A, B] = remove(node.uniqueAddress, key)
@deprecated("Use `remove` that takes a `SelfUniqueAddress` parameter instead.", since = "2.5.20")
def -(key: A)(implicit node: Cluster): ORMap[A, B] = remove(node.selfUniqueAddress, key)
@deprecated("Use `remove` that takes a `SelfUniqueAddress` parameter instead.", since = "2.5.20")
def remove(node: Cluster, key: A): ORMap[A, B] = remove(node.selfUniqueAddress, key)
/**
* INTERNAL API
*/
@InternalApi private[akka] def remove(node: UniqueAddress, key: A): ORMap[A, B] = {
2017-02-23 01:20:33 +01:00
// for removals the delta values map emitted will be empty
val newKeys = keys.resetDelta.remove(node, key)
val removeDeltaOp = RemoveDeltaOp(newKeys.delta.get, zeroTag)
new ORMap(newKeys, values - key, zeroTag, Some(newDelta(removeDeltaOp)))
}
2017-02-23 01:20:33 +01:00
/**
* INTERNAL API
* This function is only to be used by derived maps that avoid remove anomalies
* by keeping the vvector (in form of key -> value pair) for deleted keys
*/
@InternalApi private[akka] def removeKey(node: UniqueAddress, key: A): ORMap[A, B] = {
val newKeys = keys.resetDelta.remove(node, key)
val removeKeyDeltaOp = RemoveKeyDeltaOp(newKeys.delta.get, key, zeroTag)
new ORMap(newKeys, values, zeroTag, Some(newDelta(removeKeyDeltaOp)))
2017-02-23 01:20:33 +01:00
}
private def dryMerge(that: ORMap[A, B], mergedKeys: ORSet[A], valueKeysIterator: Iterator[A]): ORMap[A, B] = {
var mergedValues = Map.empty[A, B]
valueKeysIterator.foreach { key =>
(this.values.get(key), that.values.get(key)) match {
case (Some(thisValue), Some(thatValue)) =>
if (thisValue.getClass != thatValue.getClass) {
val errMsg = s"Wrong type for merging [$key] in [${getClass.getName}], existing type " +
s"[${thisValue.getClass.getName}], got [${thatValue.getClass.getName}]"
throw new IllegalArgumentException(errMsg)
}
// TODO can we get rid of these (safe) casts?
val mergedValue = thisValue.merge(thatValue.asInstanceOf[thisValue.T]).asInstanceOf[B]
mergedValues = mergedValues.updated(key, mergedValue)
case (Some(thisValue), None) =>
if (mergedKeys.contains(key))
mergedValues = mergedValues.updated(key, thisValue)
// else thisValue is a tombstone, but we don't want to carry it forward, as the other side does not have the element at all
case (None, Some(thatValue)) =>
if (mergedKeys.contains(key))
mergedValues = mergedValues.updated(key, thatValue)
// else thatValue is a tombstone, but we don't want to carry it forward, as the other side does not have the element at all
case (None, None) => throw new IllegalStateException(s"missing value for $key")
}
}
2017-02-23 01:20:33 +01:00
new ORMap(mergedKeys, mergedValues, zeroTag = zeroTag)
}
override def merge(that: ORMap[A, B]): ORMap[A, B] = {
val mergedKeys = keys.merge(that.keys)
dryMerge(that, mergedKeys, mergedKeys.elementsMap.keysIterator)
}
/**
* INTERNAL API
* This function is only to be used by derived maps that avoid remove anomalies
* by keeping the vvector (in form of key -> value pair) for deleted keys
*/
@InternalApi private[akka] def mergeRetainingDeletedValues(that: ORMap[A, B]): ORMap[A, B] = {
val mergedKeys = keys.merge(that.keys)
dryMerge(that, mergedKeys, (this.values.keySet ++ that.values.keySet).iterator)
}
override def resetDelta: ORMap[A, B] =
if (delta.isEmpty) this
else new ORMap[A, B](keys.resetDelta, values, zeroTag = zeroTag)
2017-02-23 01:20:33 +01:00
private def dryMergeDelta(thatDelta: ORMap.DeltaOp, withValueDeltas: Boolean = false): ORMap[A, B] = {
def mergeValue(lvalue: ReplicatedData, rvalue: ReplicatedData): B =
(lvalue, rvalue) match {
case (v: DeltaReplicatedData, delta: ReplicatedDelta) =>
v.mergeDelta(delta.asInstanceOf[v.D]).asInstanceOf[B]
case _ =>
lvalue.merge(rvalue.asInstanceOf[lvalue.T]).asInstanceOf[B]
2017-02-23 01:20:33 +01:00
}
var mergedKeys: ORSet[A] = this.keys
2019-03-11 10:38:24 +01:00
var (mergedValues, tombstonedVals): (Map[A, B], Map[A, B]) = this.values.partition {
case (k, _) => this.keys.contains(k)
}
2017-02-23 01:20:33 +01:00
val processDelta: PartialFunction[ORMap.DeltaOp, Unit] = {
case putOp: PutDeltaOp[A, B] =>
val keyDelta = putOp.underlying
mergedKeys = mergedKeys.mergeDelta(keyDelta)
mergedValues = mergedValues + putOp.value // put is destructive and propagates only full values of B!
case removeOp: RemoveDeltaOp[A, B] =>
val removedKey = removeOp.underlying match {
// if op is RemoveDeltaOp then it must have exactly one element in the elements
case op: ORSet.RemoveDeltaOp[_] => op.underlying.elements.head.asInstanceOf[A]
case _ => throw new IllegalArgumentException("ORMap.RemoveDeltaOp must contain ORSet.RemoveDeltaOp inside")
}
mergedValues = mergedValues - removedKey
mergedKeys = mergedKeys.mergeDelta(removeOp.underlying)
// please note that if RemoveDeltaOp is not preceded by update clearing the value
// anomalies may result
case removeKeyOp: RemoveKeyDeltaOp[A, B] =>
// removeKeyOp tombstones values for later use
if (mergedValues.contains(removeKeyOp.removedKey)) {
tombstonedVals = tombstonedVals + (removeKeyOp.removedKey -> mergedValues(removeKeyOp.removedKey))
}
mergedValues = mergedValues - removeKeyOp.removedKey
mergedKeys = mergedKeys.mergeDelta(removeKeyOp.underlying)
case updateOp: UpdateDeltaOp[A, _] =>
mergedKeys = mergedKeys.mergeDelta(updateOp.underlying)
updateOp.values.foreach {
case (key, value) =>
if (mergedKeys.contains(key)) {
if (mergedValues.contains(key)) {
mergedValues = mergedValues + (key -> mergeValue(mergedValues(key), value))
} else if (tombstonedVals.contains(key)) {
mergedValues = mergedValues + (key -> mergeValue(tombstonedVals(key), value))
} else {
value match {
case _: ReplicatedDelta =>
mergedValues = mergedValues + (key -> mergeValue(value.asInstanceOf[ReplicatedDelta].zero, value))
case _ =>
mergedValues = mergedValues + (key -> value.asInstanceOf[B])
}
}
}
}
2017-02-23 01:20:33 +01:00
}
val processNestedDelta: PartialFunction[ORMap.DeltaOp, Unit] = {
case ORMap.DeltaGroup(ops) =>
2017-02-23 01:20:33 +01:00
ops.foreach {
processDelta.orElse {
case ORMap.DeltaGroup(args) =>
2017-02-23 01:20:33 +01:00
throw new IllegalStateException("Cannot nest DeltaGroups")
}
}
}
2019-03-11 10:38:24 +01:00
processDelta.orElse(processNestedDelta)(thatDelta)
2017-02-23 01:20:33 +01:00
if (withValueDeltas)
new ORMap[A, B](mergedKeys, tombstonedVals ++ mergedValues, zeroTag = zeroTag)
else
new ORMap[A, B](mergedKeys, mergedValues, zeroTag = zeroTag)
}
2017-02-23 01:20:33 +01:00
override def mergeDelta(thatDelta: ORMap.DeltaOp): ORMap[A, B] = {
val thisWithDeltas = dryMergeDelta(thatDelta)
this.merge(thisWithDeltas)
}
2017-02-23 01:20:33 +01:00
/**
* INTERNAL API
* This function is only to be used by derived maps that avoid remove anomalies
* by keeping the vvector (in form of key -> value pair) for deleted keys
*/
@InternalApi private[akka] def mergeDeltaRetainingDeletedValues(thatDelta: ORMap.DeltaOp): ORMap[A, B] = {
val thisWithDeltas = dryMergeDelta(thatDelta, true)
this.mergeRetainingDeletedValues(thisWithDeltas)
2017-02-23 01:20:33 +01:00
}
2017-02-23 01:20:33 +01:00
private def newDelta(deltaOp: ORMap.DeltaOp) = delta match {
case Some(d) =>
2017-02-23 01:20:33 +01:00
d.merge(deltaOp)
case None =>
2017-02-23 01:20:33 +01:00
deltaOp
}
override def modifiedByNodes: Set[UniqueAddress] = {
2019-03-11 10:38:24 +01:00
keys.modifiedByNodes.union(values.foldLeft(Set.empty[UniqueAddress]) {
case (acc, (_, data: RemovedNodePruning)) => acc.union(data.modifiedByNodes)
case (acc, _) => acc
2019-03-11 10:38:24 +01:00
})
}
override def needPruningFrom(removedNode: UniqueAddress): Boolean = {
keys.needPruningFrom(removedNode) || values.exists {
case (_, data: RemovedNodePruning) => data.needPruningFrom(removedNode)
case _ => false
}
}
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[B])
case (acc, _) => acc
}
2017-02-23 01:20:33 +01:00
new ORMap(prunedKeys, prunedValues, zeroTag = zeroTag)
}
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[B])
case (acc, _) => acc
}
2017-02-23 01:20:33 +01:00
new ORMap(pruningCleanupedKeys, pruningCleanupedValues, zeroTag = zeroTag)
}
// this class cannot be a `case class` because we need different `unapply`
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
}
override def hashCode: Int = {
var result = HashCode.SEED
result = HashCode.hash(result, keys)
result = HashCode.hash(result, values)
result
}
}
object ORMapKey {
def create[A, B <: ReplicatedData](id: String): Key[ORMap[A, B]] = ORMapKey(id)
}
@SerialVersionUID(1L)
2019-03-11 10:38:24 +01:00
final case class ORMapKey[A, B <: ReplicatedData](_id: String)
extends Key[ORMap[A, B]](_id)
with ReplicatedDataSerialization