All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.cloudstate.javasupport.impl.crdt.ORMapImpl.scala Maven / Gradle / Ivy

There is a newer version: 0.6.0
Show newest version
package io.cloudstate.javasupport.impl.crdt

import java.util
import java.util.{function, Map}

import com.google.protobuf.any.{Any => ScalaPbAny}
import io.cloudstate.javasupport.crdt.{Crdt, CrdtFactory, ORMap}
import io.cloudstate.javasupport.impl.AnySupport
import io.cloudstate.protocol.crdt.{CrdtDelta, CrdtState, ORMapDelta, ORMapEntry, ORMapEntryDelta, ORMapState}
import org.slf4j.LoggerFactory

import scala.collection.JavaConverters._

/**
 * A few notes on implementation:
 *
 * - put, and any similar operations (such as Map.Entry.setValue) are not supported, because the only way to create
 *   a CRDT is using a CrdtFactory, and we only make CrdtFactory's available in very specific contexts, such as in the
 *   getOrCreate method. The getOrCreate method is the only way to insert something new into the map.
 * - All mechanisms for removal are supported - eg, calling remove directly, calling remove on any of the derived sets
 *   (entrySet, keySet, values), and calling remove on the entrySet iterator.
 * - ju.AbstractMap is very useful, though bases most of its implementation on entrySet, so we need to take care to
 *   efficiently implement operations that it implements in O(n) time that we can do in O(1) time, such as
 *   get/remove/containsKey.
 */
private[crdt] final class ORMapImpl[K, V <: InternalCrdt](anySupport: AnySupport)
    extends util.AbstractMap[K, V]
    with InternalCrdt
    with ORMap[K, V] {
  override final val name = "ORMap"
  private val value = new util.HashMap[K, V]()
  private val added = new util.HashMap[K, (ScalaPbAny, V)]()
  private val removed = new util.HashSet[ScalaPbAny]()
  private var cleared = false

  override def getOrCreate(key: K, create: function.Function[CrdtFactory, V]): V =
    if (value.containsKey(key)) {
      value.get(key)
    } else {
      val encodedKey = anySupport.encodeScala(key)
      var internalCrdt: InternalCrdt = null
      val crdt = create(new AbstractCrdtFactory {
        override protected def anySupport: AnySupport = ORMapImpl.this.anySupport
        override protected def newCrdt[C <: InternalCrdt](crdt: C): C = {
          if (internalCrdt != null) {
            throw new IllegalStateException(
              "getOrCreate creation callback must only be used to create one CRDT at a time"
            )
          }
          internalCrdt = crdt
          crdt
        }
      })
      if (crdt == null) {
        throw new IllegalArgumentException("getOrCreate creation callback must return a CRDT")
      } else if (crdt != internalCrdt) {
        throw new IllegalArgumentException(
          "CRDT returned by getOrCreate creation callback must have been created by the CrdtFactory passed to it"
        )
      }

      value.put(key, crdt)
      added.put(key, (encodedKey, crdt))
      crdt
    }

  override def containsKey(key: Any): Boolean = value.containsKey(key)

  override def get(key: Any): V = value.get(key)

  override def put(key: K, value: V): V =
    throw new UnsupportedOperationException("Cannot put on an ORMap, use getOrCreate instead.")

  override def remove(key: Any): V = {
    if (value.containsKey(key)) {
      val encodedKey = anySupport.encodeScala(key)
      if (added.containsKey(key)) {
        added.remove(key)
      } else {
        removed.add(encodedKey)
      }
    }
    value.remove(key)
  }

  // Most methods in AbstractMap build on this. Most important thing is to get the mutability aspects right.
  override def entrySet(): util.Set[util.Map.Entry[K, V]] = new EntrySet

  private class EntrySet extends util.AbstractSet[util.Map.Entry[K, V]] {
    override def size(): Int = value.size()
    override def iterator(): util.Iterator[util.Map.Entry[K, V]] = new util.Iterator[util.Map.Entry[K, V]] {
      private val iter = value.entrySet().iterator()
      private var lastNext: util.Map.Entry[K, V] = _
      override def hasNext: Boolean = iter.hasNext
      override def next(): Map.Entry[K, V] = {
        lastNext = iter.next()
        new util.Map.Entry[K, V] {
          private val entry = lastNext
          override def getKey: K = entry.getKey
          override def getValue: V = entry.getValue
          override def setValue(value: V): V = throw new UnsupportedOperationException()
        }
      }
      override def remove(): Unit = {
        if (lastNext != null) {
          val encodedKey = anySupport.encodeScala(lastNext.getKey)
          if (added.containsKey(lastNext.getKey)) {
            added.remove(lastNext.getKey)
          } else {
            removed.add(encodedKey)
          }
        }
        iter.remove()
      }
    }
    override def clear(): Unit = ORMapImpl.this.clear()
  }

  override def size(): Int = value.size()

  override def isEmpty: Boolean = super.isEmpty

  override def clear(): Unit = {
    value.clear()
    cleared = true
    removed.clear()
    added.clear()
  }

  override def hasDelta: Boolean =
    if (cleared || !added.isEmpty || !removed.isEmpty) {
      true
    } else {
      value.values().asScala.exists(_.hasDelta)
    }

  override def delta: Option[CrdtDelta.Delta] =
    if (hasDelta) {
      val updated = (value.asScala -- this.added.keySet().asScala).collect {
        case (key, changed) if changed.hasDelta =>
          ORMapEntryDelta(Some(anySupport.encodeScala(key)), changed.delta.map(CrdtDelta(_)))
      }
      val added = this.added.asScala.values.map {
        case (key, crdt) => ORMapEntry(Some(key), Some(CrdtState(crdt.state)))
      }

      Some(
        CrdtDelta.Delta.Ormap(
          ORMapDelta(
            cleared = cleared,
            removed = removed.asScala.toVector,
            updated = updated.toVector,
            added = added.toVector
          )
        )
      )
    } else None

  override def resetDelta(): Unit = {
    cleared = false
    added.clear()
    removed.clear()
    value.values().asScala.foreach(_.resetDelta())
  }

  override def state: CrdtState.State =
    CrdtState.State.Ormap(
      ORMapState(
        value.asScala.map {
          case (key, crdt) => ORMapEntry(Some(anySupport.encodeScala(key)), Some(CrdtState(crdt.state)))
        }.toVector
      )
    )

  override val applyDelta = {
    case CrdtDelta.Delta.Ormap(ORMapDelta(cleared, removed, updated, added)) =>
      if (cleared) {
        value.clear()
      }
      removed.foreach(key => value.remove(anySupport.decode(key)))
      updated.foreach {
        case ORMapEntryDelta(Some(key), Some(delta)) =>
          val crdt = value.get(anySupport.decode(key))
          if (crdt == null) {
            ORMapImpl.log.warn(s"ORMap entry to update with key $key not found in map")
          } else {
            crdt.applyDelta(delta.delta)
          }
      }
      added.foreach {
        case ORMapEntry(Some(key), Some(state)) =>
          value.put(anySupport.decode(key).asInstanceOf[K],
                    CrdtStateTransformer.create(state, anySupport).asInstanceOf[V])
      }
  }

  override val applyState = {
    case CrdtState.State.Ormap(ORMapState(values)) =>
      value.clear()
      values.foreach {
        case ORMapEntry(Some(key), Some(state)) =>
          value.put(anySupport.decode(key).asInstanceOf[K],
                    CrdtStateTransformer.create(state, anySupport).asInstanceOf[V])
      }
  }

  override def toString = s"ORMap(${value.asScala.map { case (k, v) => s"$k->$v" }.mkString(",")})"
}

private object ORMapImpl {
  private final val log = LoggerFactory.getLogger(classOf[ORMapImpl[_, _]])
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy