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

flatgraph.Graph.scala Maven / Gradle / Ivy

There is a newer version: 0.0.91
Show newest version
package flatgraph

import flatgraph.Edge.Direction
import flatgraph.GNode.KindAndSeq
import flatgraph.Graph.*
import flatgraph.misc.{InitNodeIterator, InitNodeIteratorArray, InitNodeIteratorArrayFiltered}
import flatgraph.storage.{Deserialization, Serialization}

import java.nio.file.{Files, Path}
import java.util.concurrent.atomic.AtomicReferenceArray
import org.slf4j.LoggerFactory

import java.util

object Graph {
  // Slot size is 3 because we have one pointer to array of quantity array and one pointer to array of
  // neighbors, and one array containing edge properties
  val NeighborsSlotSize  = 3
  val NumberOfDirections = 2
  val PropertySlotSize   = 2

  val logger = LoggerFactory.getLogger(classOf[Graph])

  /** Instantiate a new graph with storage. If the file already exists, this will deserialize the given file into memory. `Graph.close` will
    * serialise graph to that given file (and override whatever was there before), unless you specify `persistOnClose = false`.
    */
  def withStorage(schema: Schema, storagePath: Path, persistOnClose: Boolean = true): Graph = {
    if (Files.exists(storagePath) && Files.size(storagePath) > 0) {
      println(s"initialising from existing storage ($storagePath)")
      Deserialization.readGraph(storagePath, Option(schema), persistOnClose)
    } else {
      val storagePathMaybe =
        if (persistOnClose) Option(storagePath)
        else None
      Graph(schema, storagePathMaybe)
    }
  }
}

class Graph(val schema: Schema, val storagePathMaybe: Option[Path] = None) extends AutoCloseable {
  private val nodeKindCount   = schema.getNumberOfNodeKinds
  private val edgeKindCount   = schema.getNumberOfEdgeKinds
  private val propertiesCount = schema.getNumberOfPropertyKinds
  private var closed          = false

  private[flatgraph] val livingNodeCountByKind: Array[Int] = new Array[Int](nodeKindCount)

  private[flatgraph] val properties     = new Array[AnyRef](nodeKindCount * propertiesCount * PropertySlotSize)
  private[flatgraph] val inverseIndices = new AtomicReferenceArray[Object](nodeKindCount * propertiesCount * PropertySlotSize)
  private[flatgraph] val nodesArray: Array[Array[GNode]] = makeNodesArray()
  private[flatgraph] val neighbors: Array[AnyRef]        = makeNeighbors()

  /** Note: this included `deleted` nodes! You might want to use `livingNodeCountByKind` instead. */
  private[flatgraph] def nodeCountByKind(kind: Int): Int = {
    schema.verifyNodeKindIsValid(kind)
    nodesArray(kind).length
  }

  def _nodes(nodeKind: Int): InitNodeIterator[GNode] = {
    if (nodeKind < 0 || schema.getNumberOfNodeKinds <= nodeKind) InitNodeIteratorArray(Array.empty[GNode])
    else if (nodeCountByKind(nodeKind) == livingNodeCountByKind(nodeKind)) new InitNodeIteratorArray[GNode](nodesArray(nodeKind))
    else new InitNodeIteratorArrayFiltered[GNode](nodesArray(nodeKind))
  }

  def nodes(label: String): InitNodeIterator[GNode] =
    _nodes(schema.getNodeKindByLabel(label))

  /** Lookup nodes for the given labels, or all nodes if no label is given (`.nodes()`) */
  def nodes(labels: String*): Iterator[GNode] =
    if (labels.isEmpty) allNodes
    else labels.iterator.flatMap(nodes)

  /** Lookup node by id - note: this may return null or throw an exception if the referenced node doesn't exist */
  def node(id: Long): GNode =
    node(GNode.extractKind(id), GNode.extractSeq(id))

  /** Lookup node by kind and seq - note: this may return null if the referenced node doesn't exist */
  def node(kind: Int, seq: Int): GNode = {
    if (kind < 0 || kind >= nodesArray.length || seq < 0 || seq >= nodesArray(kind).length) return null
    val node = nodesArray(kind)(seq)
    if (node._isDeleted) null
    else node
  }

  /** Lookup node by kindAndSeq - note: this may return null or throw an exception if the referenced node doesn't exist */
  def node(kindAndSeq: KindAndSeq): GNode =
    node(kindAndSeq.kind, kindAndSeq.seq)

  def allNodes: Iterator[GNode] =
    schema.nodeKinds.iterator.flatMap(_nodes)

  def nodeCount(label: String): Int =
    livingNodeCountByKind(schema.getNodeKindByLabel(label))

  def nodeCount: Int =
    livingNodeCountByKind.sum

  def allEdges: Iterator[Edge] =
    allNodes.flatMap(Accessors.getEdgesOut)

  def edgeCount: Int =
    allEdges.size

  /** Lookup nodes with a given label and property value via index. N.b. currently only supported for String properties. Context:
    * MultiDictIndex requires the key to be a String and this is using reverse indices, i.e. the lookup is from String -> GNode.
    */
  def nodesWithProperty(label: String, propertyName: String, value: String): Iterator[GNode] = {
    val nodeKind     = schema.getNodeKindByLabel(label)
    val propertyKind = schema.getPropertyKindByName(propertyName)
    if (nodeKind == Schema.UndefinedKind || propertyKind == Schema.UndefinedKind)
      Iterator.empty
    else
      Accessors.getWithInverseIndex(this, nodeKind, propertyKind, value)
  }

  /** Lookup nodes with a given property value via index. N.b. currently only supported for String properties. Context: MultiDictIndex
    * requires the key to be a String and this is using reverse indices, i.e. the lookup is from String -> GNode.
    */
  def nodesWithProperty(propertyName: String, value: String): Iterator[GNode] = {
    schema.getPropertyKindByName(propertyName) match {
      case Schema.UndefinedKind =>
        Iterator.empty
      case propertyKind =>
        schema.nodeKinds.iterator.flatMap { nodeKind =>
          Accessors.getWithInverseIndex(this, nodeKind, propertyKind, value)
        }
    }
  }

  private def makeNodesArray(): Array[Array[GNode]] = {
    val nodes = new Array[Array[GNode]](nodeKindCount)
    for (nodeKind <- Range(0, nodes.length))
      nodes(nodeKind) = new Array[GNode](0)
    nodes
  }

  private def makeNeighbors() = {
    val neighbors = new Array[AnyRef](nodeKindCount * edgeKindCount * NeighborsSlotSize * NumberOfDirections)
    for {
      nodeKind  <- schema.nodeKinds
      direction <- Edge.Direction.values
      edgeKind  <- schema.edgeKinds
      pos             = schema.neighborOffsetArrayIndex(nodeKind, direction, edgeKind)
      propertyDefault = schema.allocateEdgeProperty(nodeKind, direction, edgeKind, size = 1)
      value =
        if (propertyDefault == null) null
        else new DefaultValue(propertyDefault(0))
    } neighbors(pos + 2) = value

    neighbors
  }

  def isClosed: Boolean = closed

  override def close(): Unit = {
    this.closed = true

    storagePathMaybe.foreach { storagePath =>
      logger.info(s"closing graph: writing to storage at `$storagePath`")
      Serialization.writeGraph(this, storagePath)
    }
  }

  override def toString(): String =
    s"Graph[$nodeCount nodes]"

  def nodeCountByLabel: Map[String, Int] = {
    schema.nodeKinds
      .map(schema.getNodeLabel)
      .map(label => label -> nodeCount(label))
      .filter { case (label, count) => count > 0 }
      .toMap
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy