io.shiftleft.overflowdb.NodeLayoutInformation Maven / Gradle / Ivy
package io.shiftleft.overflowdb;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Contains all static node-specific information. This could in theory be part of OverflowDbNode, but to save memory
* we need to minimize the amount of fields per node instance. While there may be millions of node instances, there's
* only one NodeLayoutInformation instance per node type, which holds this static information.
*
* Please make sure to instantiate only one instance per node type to not waste memory.
*/
public class NodeLayoutInformation {
private final Set propertyKeys;
private final String[] allowedOutEdgeLabels;
private final String[] allowedInEdgeLabels;
/* position for given OUT edge label in edgeOffsets */
private final Map outEdgeToOffsetPosition;
/* position for given IN edge label in edgeOffsets */
private final Map inEdgeToOffsetPosition;
/* possible edge property keys, grouped by edge label.
* n.b. property keys are of type `HashSet` (rather than just `Set`) to ensure `.size` has constant time */
private final Map> edgePropertyKeysByLabel;
/* position in stride (entry within `adjacentVerticesWithProperties`) for a given edge label and edge property key
* 1-based, because index `0` is the adjacent node ref */
private final Map edgeLabelAndKeyToStrideIndex;
public NodeLayoutInformation(Set propertyKeys,
List outEdgeLayouts,
List inEdgeLayouts) {
this.propertyKeys = propertyKeys;
Set allEdgeLayouts = new HashSet<>();
allEdgeLayouts.addAll(outEdgeLayouts);
allEdgeLayouts.addAll(inEdgeLayouts);
edgePropertyKeysByLabel = createEdgePropertyKeysByLabel(allEdgeLayouts);
edgeLabelAndKeyToStrideIndex = createEdgeLabelAndKeyToStrideIndex(allEdgeLayouts);
/* create unique offsets for each edge type and direction
* sort them by edge label to ensure we get the same offsets between restarts
* n.b. this doesn't support schema changes */
int offsetPosition = 0;
outEdgeToOffsetPosition = new HashMap<>(outEdgeLayouts.size());
for (EdgeLayoutInformation edgeLayout : sortByLabel(outEdgeLayouts)) {
outEdgeToOffsetPosition.put(edgeLayout.label, offsetPosition++);
}
inEdgeToOffsetPosition = new HashMap<>(inEdgeLayouts.size());
for (EdgeLayoutInformation edgeLayout : sortByLabel(inEdgeLayouts)) {
inEdgeToOffsetPosition.put(edgeLayout.label, offsetPosition++);
}
/* only so we don't need to calculate it every time */
allowedOutEdgeLabels = outEdgeToOffsetPosition.keySet().toArray(new String[0]);
allowedInEdgeLabels = inEdgeToOffsetPosition.keySet().toArray(new String[0]);
}
private Map> createEdgePropertyKeysByLabel(Set allEdgeLayouts) {
Map> edgePropertyKeysByLabel = new HashMap<>(allEdgeLayouts.size());
for (EdgeLayoutInformation edgeLayout : allEdgeLayouts) {
edgePropertyKeysByLabel.put(edgeLayout.label, new HashSet<>(edgeLayout.propertyKeys));
}
return edgePropertyKeysByLabel;
}
private Map createEdgeLabelAndKeyToStrideIndex(Set allEdgeLayouts) {
Map edgeLabelAndKeyToStrideIndex = new HashMap<>(allEdgeLayouts.size());
for (EdgeLayoutInformation edgeLayout : allEdgeLayouts) {
/* 1-based, because index `0` is the adjacent node ref */
int strideIndex = 1;
/* sort property keys to ensure we get the same offsets between restarts
* n.b. this doesn't support schema changes */
for (String propertyKey : sorted(edgeLayout.propertyKeys)) {
edgeLabelAndKeyToStrideIndex.put(new LabelAndKey(edgeLayout.label, propertyKey), strideIndex++);
}
}
return edgeLabelAndKeyToStrideIndex;
}
private Iterable sorted(Set propertyKeys) {
SortedSet sortedSet = new TreeSet<>(String::compareTo);
sortedSet.addAll(propertyKeys);
return sortedSet;
}
private SortedSet sortByLabel(List outEdgeLayouts) {
SortedSet sorted = new TreeSet<>(Comparator.comparing(a -> a.label));
sorted.addAll(outEdgeLayouts);
return sorted;
}
public Set propertyKeys() {
return propertyKeys;
}
public String[] allowedOutEdgeLabels() {
return allowedOutEdgeLabels;
}
public String[] allowedInEdgeLabels() {
return allowedInEdgeLabels;
}
public Set edgePropertyKeys(String edgeLabel) {
return edgePropertyKeysByLabel.get(edgeLabel);
}
/* The number fo different IN|OUT edge relations. E.g. a node has AST edges in and out, then we would have 2.
* If in addition it has incoming ref edges it would have 3. */
public int numberOfDifferentAdjacentTypes() {
return outEdgeToOffsetPosition.size() + inEdgeToOffsetPosition.size();
}
/* position for given OUT edge label in OverflowDbNode.edgeOffsets */
public Integer outEdgeToOffsetPosition(String edgeLabel) {
return outEdgeToOffsetPosition.get(edgeLabel);
}
/* position for given IN edge label in OverflowDbNode.edgeOffsets */
public Integer inEdgeToOffsetPosition(String edgeLabel) {
return inEdgeToOffsetPosition.get(edgeLabel);
}
/**
* @return The offset relative to the adjacent vertex element in the
* adjacentVerticesWithProperties array starting from 1. Return -1 if
* key does not exist for given edgeLabel.
*/
public int getOffsetRelativeToAdjacentNodeRef(String edgeLabel, String key) {
return edgeLabelAndKeyToStrideIndex.getOrDefault(new LabelAndKey(edgeLabel, key), -1);
}
class LabelAndKey {
final String label;
final String propertyKey;
LabelAndKey(String label, String propertyKey) {
this.label = label;
this.propertyKey = propertyKey;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LabelAndKey that = (LabelAndKey) o;
return label.equals(that.label) &&
propertyKey.equals(that.propertyKey);
}
@Override
public int hashCode() {
return Objects.hash(label, propertyKey);
}
}
}