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

org.neo4j.cypher.internal.runtime.ArrayBackedMap.scala Maven / Gradle / Ivy

/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.cypher.internal.runtime

import org.neo4j.memory.HeapEstimator.shallowSizeOfInstance
import org.neo4j.memory.HeapEstimator.shallowSizeOfInstanceWithObjectReferences

import scala.reflect.ClassTag

/**
 * Map implementation optimized for fast writes via putValues.
 *
 * Stores values in an array and has a lookup table from key to array index for doing lookups.
 * @param keyToIndexMap the mapping from keys to array indexes
 */
class ArrayBackedMap[K, V](keyToIndexMap: Map[K, Int], nullValue: V = null.asInstanceOf[V])(implicit
val tag: ClassTag[V]) extends Map[K, V] {
  private var valueArray: Array[V] = _

  /**
   * Writes values by reference straight into the map.
   * When using this make sure the order matches the order specified by keyToIndexMap.
   */
  def putValues(array: Array[V]): Unit = {
    valueArray = array
  }

  /**
   * Creates a copy of the map
   * @return a copy of the map
   */
  def copy: ArrayBackedMap[K, V] = {
    val newArray = new Array[V](valueArray.length)
    System.arraycopy(valueArray, 0, newArray, 0, valueArray.length)
    val newMap = new ArrayBackedMap[K, V](keyToIndexMap, nullValue)
    newMap.putValues(newArray)
    newMap
  }

  override def get(key: K): Option[V] = keyToIndexMap.get(key).map { index =>
    if (valueArray != null && index < valueArray.length)
      valueArray(index)
    else
      nullValue
  }

  override def iterator: Iterator[(K, V)] = new Iterator[(K, V)]() {
    private val inner = keyToIndexMap.iterator

    override def hasNext: Boolean = inner.hasNext

    override def next(): (K, V) = {
      val (key, index) = inner.next()
      if (valueArray != null && index < valueArray.length) (key, valueArray(index))
      else (key, nullValue)
    }
  }

  /**
   * @note This class is not optimized for this operation, so this implementation is not particularly efficient.
   *       (Avoid using it if possible)
   */
  override def updated[B1 >: V](key: K, value: B1): Map[K, B1] = {
    val index = keyToIndexMap.get(key)
    index match {
      // key already existed in map, copy over values and create new map
      case Some(i) =>
        val newArray = new Array[V](valueArray.length)
        System.arraycopy(valueArray, 0, newArray, 0, valueArray.length)
        newArray(i) = value.asInstanceOf[V]
        val newMap: ArrayBackedMap[K, V] = new ArrayBackedMap[K, V](keyToIndexMap, nullValue)
        newMap.putValues(newArray)
        newMap
      // key was not in map, create new map and add new key-value pair at the end of the its valueArray
      case None =>
        val newHeadersMap = keyToIndexMap.updated(key, valueArray.length)
        val newMap = new ArrayBackedMap[K, V](newHeadersMap, nullValue)
        val newArray = new Array[V](valueArray.length + 1)
        System.arraycopy(valueArray, 0, newArray, 0, valueArray.length)
        newArray(valueArray.length) = value.asInstanceOf[V]
        newMap.putValues(newArray)
        newMap
    }
  }

  /**
   * @note This class is not optimized for this operation, so this implementation is not particularly efficient.
   *       (Avoid using it if possible)
   */
  override def removed(key: K): Map[K, V] = {
    val index = keyToIndexMap.get(key)
    index match {
      case Some(indexToRemove) =>
        // Create a new headerToIndex map without the key to be removed
        // Note: We need to update the indexes of the new headers map to match the smaller array
        val newHeadersMap = (keyToIndexMap - key).view.mapValues(i => if (i >= indexToRemove) i - 1 else i).toMap

        // Create a new array by first filtering out the index to be removed
        val newArray = valueArray.indices.filterNot(_ == indexToRemove).map(valueArray).toArray

        val newMap = new ArrayBackedMap[K, V](newHeadersMap, nullValue)
        newMap.putValues(newArray)
        newMap
      case None => this
    }
  }
}

object ArrayBackedMap {

  def apply[K, V](keys: K*)(nullValue: V = null.asInstanceOf[V])(implicit tag: ClassTag[V]): ArrayBackedMap[K, V] =
    new ArrayBackedMap[K, V](keys.zipWithIndex.toMap, nullValue)

  final val SHALLOW_SIZE = shallowSizeOfInstance(classOf[ArrayBackedMap[_, _]]) +
    shallowSizeOfInstanceWithObjectReferences(2) // scala.collection.convert.Wrappers$MapWrapper
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy