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.115
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 gnu.trove.set.hash.THashSet;
import overflowdb.Node;
import overflowdb.NodeRef;
import overflowdb.OdbNode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;

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.id2(), index);
    nodesByLabel(node.label()).add(node);
    size++;
  }

  private void verifyUniqueId(Node node) {
    if (nodeIndexByNodeId.containsKey(node.id2())) {
      Node existingNode = nodeById(node.id2());
      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 void remove(Node node) {
    int index = nodeIndexByNodeId.remove(node.id2());
    nodes[index] = null;
    emptySlots.set(index);

    NodeRef ref = node instanceof OdbNode
        ? ((OdbNode) node).ref
        : (NodeRef) node;
    nodesByLabel.get(node.label()).remove(ref);

    size--;
    compactMaybe();
  }

  public int size() {
    return size;
  }

  public Set nodesByLabel(String label) {
    if (!nodesByLabel.containsKey(label))
      nodesByLabel.put(label, new THashSet<>(10));

    return nodesByLabel.get(label);
  }

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

  public 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 */
  public 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);
    nodesByLabel = new THashMap<>(10);

    int idx = 0;
    while (idx < this.nodes.length) {
      Node node = this.nodes[idx];
      nodeIndexByNodeId.put(node.id2(), idx);
      nodesByLabel(node.label()).add(node);
      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 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) {
    if (nodesByLabel.containsKey(label))
      return nodesByLabel.get(label).size();
    else
      return 0;
  }

  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