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

overflowdb.util.NodesList Maven / Gradle / Ivy

There is a newer version: 1.173
Show newest version
package overflowdb.util;

import gnu.trove.map.TLongIntMap;
import gnu.trove.map.TMap;
import gnu.trove.map.hash.THashMap;
import gnu.trove.map.hash.TLongIntHashMap;
import overflowdb.Node;
import overflowdb.storage.NodesWriter;

import java.util.*;

public class NodesList {
  private Node[] nodes;
  private int size = 0;

  //index into `nodes` array by node id
  private TLongIntMap nodeIndexByNodeId;
  private TMap> nodesByLabel;

  /** list of available slots in `nodes` array. slots become available after nodes have been removed */
  private final BitSet emptySlots;

  private static final int DEFAULT_CAPACITY = 10000;

  public NodesList() {
    this(DEFAULT_CAPACITY);
  }

  public NodesList(int initialCapacity) {
    nodes = new Node[initialCapacity];
    emptySlots = new BitSet(initialCapacity);
    nodeIndexByNodeId = new TLongIntHashMap(initialCapacity);
    nodesByLabel = new THashMap<>(10);
  }

  /** store Node in internal collections */
  public synchronized void add(Node node) {
    verifyUniqueId(node);
    int index = tryClaimEmptySlot();
    if (index == -1) {
      // no empty spot available - append to nodes array instead
      index = size;
      ensureCapacity(size + 1);
    }

    nodes[index] = node;
    nodeIndexByNodeId.put(node.id(), index);
    if(nodesByLabel != null) {
      nodesByLabel(node.label()).add(node);
    }
    size++;
  }

  private void verifyUniqueId(Node node) {
    if (nodeIndexByNodeId.containsKey(node.id())) {
      Node existingNode = nodeById(node.id());
      throw new AssertionError("different Node with same id already exists in this NodesList: " + existingNode);
    }
  }

  /** @return -1 if no available empty slots, otherwise the successfully claimed slot */
  private int tryClaimEmptySlot() {
    final int nextEmptySlot = emptySlots.nextSetBit(0);
    if (nextEmptySlot != -1) {
      emptySlots.clear(nextEmptySlot);
    }
    return nextEmptySlot;
  }

  public boolean contains(long id) {
    return nodeIndexByNodeId.containsKey(id);
  }

  public Node nodeById(long id) {
    if (nodeIndexByNodeId.containsKey(id)) {
      return nodes[nodeIndexByNodeId.get(id)];
    } else {
      return null;
    }
  }

  public synchronized void remove(Node node) {
    int index = nodeIndexByNodeId.remove(node.id());
    nodes[index] = null;
    emptySlots.set(index);

    /** We drop the entire `nodesByLabel` index and will rebuild it on the next index lookup...
      * Context: we don't know the exact position of this node in the `nodesByLabel` index, and
      * searching it is O(n).
      * We considered using a separate TLongIntMap for the Id->Index mapping, but that would
      * consume 12-20b per node */
    this.nodesByLabel = null;

    size--;
    compactMaybe();
  }

  public int size() {
    return size;
  }

  private TMap> getNodesByLabel() {
    TMap> nodesByLabel = this.nodesByLabel;
    if (nodesByLabel != null) {
      return nodesByLabel;
    } else {
      initialiseNodesByLabel();
      return getNodesByLabel();
    }
  }

  private synchronized void initialiseNodesByLabel(){
    if (nodesByLabel == null) {
      TMap> tmp = new THashMap<>();
      for (Node node : nodes) {
        if (node != null) {
          ArrayList nodelist = tmp.get(node.label());
          if (nodelist == null) {
            nodelist = new ArrayList<>();
            tmp.put(node.label(), nodelist);
          }
          nodelist.add(node);
        }
      }
      this.nodesByLabel = tmp;
    }
  }

  public ArrayList nodesByLabel(String label) {
    TMap> nodesByLabel = getNodesByLabel();
    ArrayList nodelist = nodesByLabel.get(label);
    if (nodelist == null){
      nodelist = new ArrayList<>();
      nodesByLabel.put(label, nodelist);
    }
    return nodelist;
  }

  public Set nodeLabels() {
    TMap> nodesByLabel = getNodesByLabel();
    Set ret = new HashSet<>(nodesByLabel.size());
    nodesByLabel.entrySet().forEach(entry -> {
      if (!entry.getValue().isEmpty()) {
        ret.add(entry.getKey());
      }
    });
    return ret;
  }

  public synchronized Iterator iterator() {
    return new NodesIterator(Arrays.copyOf(nodes, nodes.length));
  }

  private void ensureCapacity(int minCapacity) {
    if (nodes.length < minCapacity) grow(minCapacity);
  }

  /** compact if there are many empty slots, and they make up >= 30% of the node array */
  private void compactMaybe() {
    final int emptyCount = emptySlots.cardinality();
    if (emptyCount > 10000 &&
        emptyCount * 100 / nodes.length >= 30) {
      compact();
    }
  }

  /** Trims down internal collections to just about the necessary size, in order to allow the remainder to be
   * garbage collected. Creates a new `nodes` array which contains all existing nodes.  */
  synchronized void compact() {
    final ArrayList newNodes = new ArrayList<>(size);
    Iterator iter = iterator();
    while (iter.hasNext()) {
      newNodes.add(iter.next());
    }
    nodes = newNodes.toArray(new Node[size]);

    //reindex helper collections
    emptySlots.clear();
    nodeIndexByNodeId = new TLongIntHashMap(this.nodes.length);

    int idx = 0;
    while (idx < this.nodes.length) {
      Node node = this.nodes[idx];
      nodeIndexByNodeId.put(node.id(), idx);
      idx++;
    }
  }

  /** The maximum size of array to allocate.
   * Some VMs reserve some header words in an array.
   * Attempts to allocate larger arrays may result in
   * OutOfMemoryError: Requested array size exceeds VM limit
   * @see java.util.ArrayList (copied from there)
   */
  private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

  /** Increases the capacity to ensure that it can hold at least the
   * number of elements specified by the minimum capacity argument.
   * @see java.util.ArrayList (copied from there) */
  private synchronized void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = nodes.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
      newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
      newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    nodes = Arrays.copyOf(nodes, newCapacity);
  }

 /** @see java.util.ArrayList (copied from there) */
  private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
      throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
  }

  /** just for unit test */
  protected int _elementDataSize() {
    return nodes.length;
  }

  /** cardinality of nodes for given label */
  public int cardinality(String label) {
    TMap> nodesByLabel = getNodesByLabel();
    if (nodesByLabel.containsKey(label))
      return nodesByLabel.get(label).size();
    else
      return 0;
  }

  public synchronized void persistAll(NodesWriter nodesWriter) {
    nodesWriter.writeAndClearBatched(Arrays.spliterator(nodes), nodes.length);
  }

  public static class NodesIterator implements Iterator {
    final Node[] nodes;
    int idx = 0;
    Node nextPeeked = null;

    public NodesIterator(Node[] nodes) {
      this.nodes = nodes;
    }

    @Override
    public boolean hasNext() {
      while (nextPeeked == null && idx < nodes.length) {
        nextPeeked = nodes[idx++];
      }
      return nextPeeked != null;
    }

    @Override
    public Node next() {
      if (!hasNext()) throw new NoSuchElementException("next on empty iterator");
      else {
        Node ret = nextPeeked;
        nextPeeked = null;
        return ret;
      }
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy