pekko/akka-remote/src/main/scala/akka/remote/artery/ImmutableLongMap.scala
2017-03-13 17:49:45 +01:00

136 lines
3.8 KiB
Scala

/**
* Copyright (C) 2016-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.remote.artery
import scala.annotation.tailrec
import akka.util.OptionVal
import scala.reflect.ClassTag
import java.util.Arrays
import akka.util.HashCode
/**
* INTERNAL API
*/
private[akka] object ImmutableLongMap {
def empty[A >: Null](implicit t: ClassTag[A]): ImmutableLongMap[A] =
new ImmutableLongMap(Array.emptyLongArray, Array.empty)
private val MaxScanLength = 10
}
/**
* INTERNAL API
* Specialized Map for primitive `Long` keys to avoid allocations (boxing).
* Keys and values are backed by arrays and lookup is performed with binary
* search. It's intended for rather small (<1000) maps.
*/
private[akka] class ImmutableLongMap[A >: Null] private (
private val keys: Array[Long], private val values: Array[A])(implicit t: ClassTag[A]) {
import ImmutableLongMap.MaxScanLength
val size: Int = keys.length
/**
* Worst case `O(log n)`, allocation free.
*/
def get(key: Long): OptionVal[A] = {
val i = Arrays.binarySearch(keys, key)
if (i >= 0) OptionVal(values(i))
else OptionVal.None
}
/**
* Worst case `O(log n)`, allocation free.
*/
def contains(key: Long): Boolean = {
Arrays.binarySearch(keys, key) >= 0
}
/**
* Worst case `O(log n)`, creates new `ImmutableLongMap`
* with copies of the internal arrays for the keys and
* values.
*/
def updated(key: Long, value: A): ImmutableLongMap[A] = {
if (size == 0)
new ImmutableLongMap(Array(key), Array(value))
else {
val i = Arrays.binarySearch(keys, key)
if (i >= 0) {
// existing key, replace value
val newValues = new Array[A](values.length)
System.arraycopy(values, 0, newValues, 0, values.length)
newValues(i) = value
new ImmutableLongMap(keys, newValues)
} else {
// insert the entry at the right position, and keep the arrays sorted
val j = -(i + 1)
val newKeys = new Array[Long](size + 1)
System.arraycopy(keys, 0, newKeys, 0, j)
newKeys(j) = key
System.arraycopy(keys, j, newKeys, j + 1, keys.length - j)
val newValues = new Array[A](size + 1)
System.arraycopy(values, 0, newValues, 0, j)
newValues(j) = value
System.arraycopy(values, j, newValues, j + 1, values.length - j)
new ImmutableLongMap(newKeys, newValues)
}
}
}
def remove(key: Long): ImmutableLongMap[A] = {
val i = Arrays.binarySearch(keys, key)
if (i >= 0) {
if (size == 1)
ImmutableLongMap.empty
else {
val newKeys = new Array[Long](size - 1)
System.arraycopy(keys, 0, newKeys, 0, i)
System.arraycopy(keys, i + 1, newKeys, i, keys.length - i - 1)
val newValues = new Array[A](size - 1)
System.arraycopy(values, 0, newValues, 0, i)
System.arraycopy(values, i + 1, newValues, i, values.length - i - 1)
new ImmutableLongMap(newKeys, newValues)
}
} else
this
}
/**
* All keys
*/
def keysIterator: Iterator[Long] =
keys.iterator
override def toString: String =
keysIterator.map(key s"$key -> ${get(key).get}").mkString("ImmutableLongMap(", ", ", ")")
override def hashCode: Int = {
var result = HashCode.SEED
result = HashCode.hash(result, keys)
result = HashCode.hash(result, values)
result
}
override def equals(obj: Any): Boolean = obj match {
case other: ImmutableLongMap[A]
if (other eq this) true
else if (size != other.size) false
else if (size == 0 && other.size == 0) true
else {
@tailrec def check(i: Int): Boolean = {
if (i == size) true
else if (keys(i) == other.keys(i) && values(i) == other.values(i))
check(i + 1) // recur, next elem
else false
}
check(0)
}
case _ false
}
}