edgePropertyOption(Direction direction,
Edge edge,
int blockOffset,
String key) {
V value = edgeProperty(direction, edge, blockOffset, key);
return Optional.ofNullable(value);
}
public P edgeProperty(Direction direction,
Edge edge,
int blockOffset,
String key) {
AdjacentNodes adjacentNodesTmp = this.adjacentNodes;
int propertyPosition = getEdgePropertyIndex(adjacentNodesTmp, direction, edge.label(), key, blockOffset);
if (propertyPosition == -1) {
return null;
}
return (P) adjacentNodesTmp.nodesWithEdgeProperties[propertyPosition];
}
public synchronized void setEdgeProperty(Direction direction,
String edgeLabel,
String key,
V value,
int blockOffset) {
AdjacentNodes adjacentNodesTmp = this.adjacentNodes;
int propertyPosition = getEdgePropertyIndex(adjacentNodesTmp, direction, edgeLabel, key, blockOffset);
if (propertyPosition == -1) {
throw new RuntimeException("Edge " + edgeLabel + " does not support property `" + key + "`.");
}
adjacentNodesTmp.nodesWithEdgeProperties[propertyPosition] = value;
/* marking as dirty *after* we updated - if node gets serialized before we finish, it'll be marked as dirty */
this.markAsDirty();
}
public void removeEdgeProperty(Direction direction, String edgeLabel, String key, int blockOffset) {
setEdgeProperty(direction, edgeLabel, key, null, blockOffset);
}
private int calcAdjacentNodeIndex(AdjacentNodes adjacentNodesTmp,
Direction direction,
String edgeLabel,
int blockOffset) {
int offsetPos = getPositionInEdgeOffsets(direction, edgeLabel);
if (offsetPos == -1) {
return -1;
}
int start = startIndex(adjacentNodesTmp, offsetPos);
return start + blockOffset;
}
/**
* Return -1 if there exists no edge property for the provided argument combination.
*/
private int getEdgePropertyIndex(AdjacentNodes adjacentNodesTmp,
Direction direction,
String label,
String key,
int blockOffset) {
int adjacentNodeIndex = calcAdjacentNodeIndex(adjacentNodesTmp, direction, label, blockOffset);
if (adjacentNodeIndex == -1) {
return -1;
}
int propertyOffset = layoutInformation().getEdgePropertyOffsetRelativeToAdjacentNodeRef(label, key);
if (propertyOffset == -1) {
return -1;
}
return adjacentNodeIndex + propertyOffset;
}
@Override
protected Edge addEdgeImpl(String label, Node inNode, Object... keyValues) {
final NodeRef inNodeRef = (NodeRef) inNode;
NodeRef thisNodeRef = ref;
int outBlockOffset = storeAdjacentNode(Direction.OUT, label, inNodeRef, keyValues);
int inBlockOffset = inNodeRef.get().storeAdjacentNode(Direction.IN, label, thisNodeRef, keyValues);
Edge dummyEdge = instantiateDummyEdge(label, thisNodeRef, inNodeRef);
dummyEdge.setOutBlockOffset(outBlockOffset);
dummyEdge.setInBlockOffset(inBlockOffset);
return dummyEdge;
}
@Override
protected Edge addEdgeImpl(String label, Node inNode, Map keyValues) {
return addEdge(label, inNode, PropertyHelper.toKeyValueArray(keyValues));
}
@Override
protected void addEdgeSilentImpl(String label, Node inNode, Object... keyValues) {
final NodeRef inNodeRef = (NodeRef) inNode;
NodeRef thisNodeRef = ref;
storeAdjacentNode(Direction.OUT, label, inNodeRef, keyValues);
inNodeRef.get().storeAdjacentNode(Direction.IN, label, thisNodeRef, keyValues);
}
@Override
protected void addEdgeSilentImpl(String label, Node inNode, Map keyValues) {
addEdgeSilent(label, inNode, PropertyHelper.toKeyValueArray(keyValues));
}
/* adjacent OUT nodes (all labels) */
@Override
public Iterator out() {
return createAdjacentNodeIterator(Direction.OUT, ALL_LABELS);
}
/* adjacent OUT nodes for given labels */
@Override
public Iterator out(String... edgeLabels) {
return createAdjacentNodeIterator(Direction.OUT, edgeLabels);
}
/* adjacent IN nodes (all labels) */
@Override
public Iterator in() {
final MultiIterator multiIterator = new MultiIterator<>();
for (String label : layoutInformation().allowedInEdgeLabels()) {
multiIterator.addIterator(in(label));
}
return multiIterator;
}
/* adjacent IN nodes for given labels */
@Override
public Iterator in(String... edgeLabels) {
return createAdjacentNodeIterator(Direction.IN, edgeLabels);
}
/* adjacent OUT/IN nodes (all labels) */
@Override
public Iterator both() {
final MultiIterator multiIterator = new MultiIterator<>();
multiIterator.addIterator(out());
multiIterator.addIterator(in());
return multiIterator;
}
/* adjacent OUT/IN nodes for given labels */
@Override
public Iterator both(String... edgeLabels) {
final MultiIterator multiIterator = new MultiIterator<>();
multiIterator.addIterator(out(edgeLabels));
multiIterator.addIterator(in(edgeLabels));
return multiIterator;
}
/* adjacent OUT edges (all labels) */
@Override
public Iterator outE() {
final MultiIterator multiIterator = new MultiIterator<>();
for (String label : layoutInformation().allowedOutEdgeLabels()) {
multiIterator.addIterator(outE(label));
}
return multiIterator;
}
/* adjacent OUT edges for given labels */
@Override
public Iterator outE(String... edgeLabels) {
return createDummyEdgeIterator(Direction.OUT, edgeLabels);
}
/* adjacent IN edges (all labels) */
@Override
public Iterator inE() {
final MultiIterator multiIterator = new MultiIterator<>();
for (String label : layoutInformation().allowedInEdgeLabels()) {
multiIterator.addIterator(inE(label));
}
return multiIterator;
}
/* adjacent IN edges for given labels */
@Override
public Iterator inE(String... edgeLabels) {
return createDummyEdgeIterator(Direction.IN, edgeLabels);
}
/* adjacent OUT/IN edges (all labels) */
@Override
public Iterator bothE() {
final MultiIterator multiIterator = new MultiIterator<>();
multiIterator.addIterator(outE());
multiIterator.addIterator(inE());
return multiIterator;
}
/* adjacent OUT/IN edges for given labels */
@Override
public Iterator bothE(String... edgeLabels) {
final MultiIterator multiIterator = new MultiIterator<>();
multiIterator.addIterator(outE(edgeLabels));
multiIterator.addIterator(inE(edgeLabels));
return multiIterator;
}
protected int outEdgeCount() {
int count = 0;
AdjacentNodes adjacentNodesTmp = this.adjacentNodes;
for (String label : layoutInformation().allowedOutEdgeLabels()) {
int offsetPos = getPositionInEdgeOffsets(Direction.OUT, label);
if (offsetPos != -1) {
int start = startIndex(adjacentNodesTmp, offsetPos);
int length = blockLength(adjacentNodesTmp, offsetPos);
int strideSize = getStrideSize(label);
int exclusiveEnd = start + length;
for (int i = start;
i < adjacentNodesTmp.nodesWithEdgeProperties.length && i < exclusiveEnd;
i += strideSize) {
if (adjacentNodesTmp.nodesWithEdgeProperties[i] != null) {
count++;
}
}
}
}
return count;
}
/**
* If there are multiple edges between the same two nodes with the same label, we use the
* `occurrence` to differentiate between those edges. Both nodes use the same occurrence
* index for the same edge.
*
* @return the occurrence for a given edge, calculated by counting the number times the given
* adjacent node occurred between the start of the edge-specific block and the blockOffset
*/
protected final int blockOffsetToOccurrence(Direction direction,
String label,
NodeRef otherNode,
int blockOffset) {
AdjacentNodes adjacentNodesTmp = this.adjacentNodes;
int offsetPos = getPositionInEdgeOffsets(direction, label);
int start = startIndex(adjacentNodesTmp, offsetPos);
int strideSize = getStrideSize(label);
Object[] adjacentNodesWithEdgeProperties = adjacentNodesTmp.nodesWithEdgeProperties;
int occurrenceCount = -1;
for (int i = start; i <= start + blockOffset; i += strideSize) {
final NodeRef adjacentNodeWithProperty = (NodeRef) adjacentNodesWithEdgeProperties[i];
if (adjacentNodeWithProperty != null &&
adjacentNodeWithProperty.id() == otherNode.id()) {
occurrenceCount++;
}
}
if (occurrenceCount == -1)
throw new RuntimeException("unable to calculate occurrenceCount");
else
return occurrenceCount;
}
/**
* @param direction OUT or IN
* @param label the edge label
* @param occurrence if there are multiple edges between the same two nodes with the same label,
* this is used to differentiate between those edges.
* Both nodes use the same occurrence index in their `adjacentNodesWithEdgeProperties` array for the same edge.
* @return the index into `adjacentNodesWithEdgeProperties`
*/
protected final int occurrenceToBlockOffset(Direction direction,
String label,
NodeRef adjacentNode,
int occurrence) {
AdjacentNodes adjacentNodesTmp = this.adjacentNodes;
int offsetPos = getPositionInEdgeOffsets(direction, label);
int start = startIndex(adjacentNodesTmp, offsetPos);
int length = blockLength(adjacentNodesTmp, offsetPos);
int strideSize = getStrideSize(label);
Object[] adjacentNodesWithEdgeProperties = adjacentNodesTmp.nodesWithEdgeProperties;
int currentOccurrence = 0;
int exclusiveEnd = start + length;
for (int i = start; i < exclusiveEnd; i += strideSize) {
final NodeRef adjacentNodeWithProperty = (NodeRef) adjacentNodesWithEdgeProperties[i];
if (adjacentNodeWithProperty != null &&
adjacentNodeWithProperty.id() == adjacentNode.id()) {
if (currentOccurrence == occurrence) {
int adjacentNodeIndex = i - start;
return adjacentNodeIndex;
} else {
currentOccurrence++;
}
}
}
throw new RuntimeException("Unable to find occurrence " + occurrence + " of "
+ label + " edge to node " + adjacentNode.id());
}
/**
* Removes an 'edge', i.e. in reality it removes the information about the adjacent node from
* `adjacentNodesWithEdgeProperties`. The corresponding elements will be set to `null`, i.e. we'll have holes.
* Note: this decrements the `offset` of the following edges in the same block by one, but that's ok because the only
* thing that matters is that the offset is identical for both connected nodes (assuming thread safety).
*
* @param blockOffset must have been initialized
*/
protected final synchronized void removeEdge(Direction direction, String label, int blockOffset) {
AdjacentNodes adjacentNodesTmp = this.adjacentNodes;
int offsetPos = getPositionInEdgeOffsets(direction, label);
int start = startIndex(adjacentNodesTmp, offsetPos) + blockOffset;
int strideSize = getStrideSize(label);
Object[] adjacentNodesWithEdgeProperties = adjacentNodesTmp.nodesWithEdgeProperties;
for (int i = start; i < start + strideSize; i++) {
adjacentNodesWithEdgeProperties[i] = null;
}
/* marking as dirty *after* we updated - if node gets serialized before we finish, it'll be marked as dirty */
this.markAsDirty();
}
private Iterator createDummyEdgeIterator(Direction direction, String... labels) {
if (labels.length == 1) {
return createDummyEdgeIteratorForSingleLabel(adjacentNodes, direction, labels[0]);
} else {
final String[] labelsToFollow =
labels.length == 0
? allowedLabelsByDirection(direction)
: labels;
final MultiIterator multiIterator = new MultiIterator<>();
for (String label : labelsToFollow) {
multiIterator.addIterator(createDummyEdgeIteratorForSingleLabel(adjacentNodes, direction, label));
}
return multiIterator;
}
}
private Iterator createDummyEdgeIteratorForSingleLabel(
AdjacentNodes adjacentNodesTmp, Direction direction, String label) {
int offsetPos = getPositionInEdgeOffsets(direction, label);
if (offsetPos != -1) {
int start = startIndex(adjacentNodesTmp, offsetPos);
int length = blockLength(adjacentNodesTmp, offsetPos);
int strideSize = getStrideSize(label);
return new DummyEdgeIterator(
adjacentNodesTmp.nodesWithEdgeProperties, start, start + length, strideSize, direction, label, ref);
} else {
return Collections.emptyIterator();
}
}
private final Iterator createAdjacentNodeIterator(Direction direction, String... labels) {
if (labels.length == 1) {
return createAdjacentNodeIteratorByOffSet(getPositionInEdgeOffsets(direction, labels[0]));
} else {
final String[] labelsToFollow =
labels.length == 0
? allowedLabelsByDirection(direction)
: labels;
final MultiIterator multiIterator = new MultiIterator<>();
for (String label : labelsToFollow) {
multiIterator.addIterator(createAdjacentNodeIteratorByOffSet(getPositionInEdgeOffsets(direction, label)));
}
return multiIterator;
}
}
/* Simplify hoisting of string lookups.
* n.b. `final` so that the JIT compiler can inline it */
public final Iterator createAdjacentNodeIteratorByOffSet(int offsetPos) {
AdjacentNodes adjacentNodesTmp = this.adjacentNodes;
if (offsetPos != -1) {
int start = startIndex(adjacentNodesTmp, offsetPos);
int length = blockLength(adjacentNodesTmp, offsetPos);
int strideSize = layoutInformation().getEdgePropertyCountByOffsetPos(offsetPos) + 1;
return new ArrayOffsetIterator<>(adjacentNodesTmp.nodesWithEdgeProperties, start, start + length, strideSize);
} else {
return Collections.emptyIterator();
}
}
private final String[] allowedLabelsByDirection(Direction direction) {
if (direction.equals(Direction.OUT))
return layoutInformation().allowedOutEdgeLabels();
else if (direction.equals(Direction.IN))
return layoutInformation().allowedInEdgeLabels();
else throw new UnsupportedOperationException(direction.toString());
}
public synchronized int storeAdjacentNode(Direction direction,
String edgeLabel,
NodeRef adjacentNode,
Object... edgeKeyValues) {
int blockOffset = storeAdjacentNode(direction, edgeLabel, adjacentNode);
/* set edge properties */
for (int i = 0; i < edgeKeyValues.length; i = i + 2) {
String key = (String) edgeKeyValues[i];
Object value = edgeKeyValues[i + 1];
setEdgeProperty(direction, edgeLabel, key, value, blockOffset);
}
/* marking as dirty *after* we updated - if node gets serialized before we finish, it'll be marked as dirty */
this.markAsDirty();
return blockOffset;
}
//implicitly synchronized -- caller already holds monitor
private final int storeAdjacentNode(Direction direction, String edgeLabel, NodeRef nodeRef) {
AdjacentNodes tmp = this.adjacentNodes; //load acquire
int offsetPos = getPositionInEdgeOffsets(direction, edgeLabel);
if (offsetPos == -1) {
throw new RuntimeException(
String.format("Edge with type='%s' with direction='%s' not supported by nodeType='%s'" , edgeLabel, direction, label()));
}
int start = startIndex(tmp, offsetPos);
int length = blockLength(tmp, offsetPos);
int strideSize = getStrideSize(edgeLabel);
Object[] adjacentNodesWithEdgeProperties = tmp.nodesWithEdgeProperties;
int edgeOffsetLengthB2 = tmp.offsetLengths() >> 1;
int insertAt = start + length;
if (adjacentNodesWithEdgeProperties.length <= insertAt
|| adjacentNodesWithEdgeProperties[insertAt] != null
|| (offsetPos + 1 < edgeOffsetLengthB2 && insertAt >= startIndex(tmp, offsetPos + 1))) {
// space already occupied - grow adjacentNodesWithEdgeProperties array, leaving some room for more elements
tmp = growAdjacentNodesWithEdgeProperties(tmp, offsetPos, strideSize, insertAt, length);
}
tmp.nodesWithEdgeProperties[insertAt] = nodeRef;
// update edgeOffset length to include the newly inserted element
tmp = tmp.setOffset(2 * offsetPos + 1, length + strideSize);
this.adjacentNodes = tmp; //store release
int blockOffset = length;
return blockOffset;
}
public int startIndex(AdjacentNodes adjacentNodesTmp, int offsetPosition) {
return adjacentNodesTmp.getOffset(2 * offsetPosition);
}
/**
* @return number of elements reserved in `adjacentNodesWithEdgeProperties` for a given edge label
* includes space for the node ref and all properties
*/
public final int getStrideSize(String edgeLabel) {
int sizeForNodeRef = 1;
Set allowedPropertyKeys = layoutInformation().edgePropertyKeys(edgeLabel);
return sizeForNodeRef + allowedPropertyKeys.size();
}
/**
* @return The position in edgeOffsets array. -1 if the edge label is not supported
*/
private final int getPositionInEdgeOffsets(Direction direction, String label) {
final Integer positionOrNull;
if (direction == Direction.OUT) {
positionOrNull = layoutInformation().outEdgeToOffsetPosition(label);
} else {
positionOrNull = layoutInformation().inEdgeToOffsetPosition(label);
}
if (positionOrNull != null) {
return positionOrNull;
} else {
return -1;
}
}
/**
* Returns the length of an edge type block in the adjacentNodesWithEdgeProperties array.
* Length means number of index positions.
*/
public final int blockLength(AdjacentNodes adjacentNodesTmp, int offsetPosition) {
return adjacentNodesTmp.getOffset(2 * offsetPosition + 1);
}
/**
* grow the adjacentNodesWithEdgeProperties array
*
* preallocates more space than immediately necessary, so we don't need to grow the array every time
* (tradeoff between performance and memory).
* grows with the square root of the double of the current capacity.
*/
private final AdjacentNodes growAdjacentNodesWithEdgeProperties(
AdjacentNodes adjacentNodesOld, int offsetPos, int strideSize, int insertAt, int currentLength) {
int growthEmptyFactor = 2;
int additionalEntriesCount = (currentLength + strideSize) * growthEmptyFactor;
Object[] nodesWithEdgePropertiesOld = adjacentNodesOld.nodesWithEdgeProperties;
int newSize = nodesWithEdgePropertiesOld.length + additionalEntriesCount;
Object[] nodesWithEdgePropertiesNew = new Object[newSize];
System.arraycopy(nodesWithEdgePropertiesOld, 0, nodesWithEdgePropertiesNew, 0, insertAt);
System.arraycopy(nodesWithEdgePropertiesOld, insertAt, nodesWithEdgePropertiesNew, insertAt + additionalEntriesCount, nodesWithEdgePropertiesOld.length - insertAt);
AdjacentNodes res = new AdjacentNodes(nodesWithEdgePropertiesNew, adjacentNodesOld.offsets);
// Increment all following start offsets by `additionalEntriesCount`.
int until = res.offsetLengths();
for (int i = offsetPos + 1; 2 * i < until; i++) {
res = res.setOffset(2 * i, res.getOffset(2 * i) + additionalEntriesCount);
}
return res;
}
/**
* instantiate and return a dummy edge, which doesn't really exist in the graph
*/
public final Edge instantiateDummyEdge(String label, NodeRef outNode, NodeRef inNode) {
final EdgeFactory edgeFactory = ref.graph.edgeFactoryByLabel.get(label);
if (edgeFactory == null)
throw new IllegalArgumentException("specializedEdgeFactory for label=" + label + " not found - please register on startup!");
return edgeFactory.createEdge(ref.graph, outNode, inNode);
}
/**
* Trims the node to save storage: shrinks overallocations
* */
public synchronized long trim() {
AdjacentNodes adjacentNodesOld = this.adjacentNodes;
int newSize = 0;
int until = adjacentNodesOld.offsetLengths();
for (int offsetPos = 0; 2 * offsetPos < until; offsetPos++) {
int length = blockLength(adjacentNodesOld, offsetPos);
newSize += length;
}
Object[] nodesWithEdgePropertiesNew = new Object[newSize];
AdjacentNodes res = new AdjacentNodes(nodesWithEdgePropertiesNew, new byte[until]);
int off = 0;
for(int offsetPos = 0; 2*offsetPos < until; offsetPos++){
int start = startIndex(adjacentNodesOld, offsetPos);
int length = blockLength(adjacentNodesOld, offsetPos);
System.arraycopy(adjacentNodesOld.nodesWithEdgeProperties, start, nodesWithEdgePropertiesNew, off, length);
res = res.setOffset(2 * offsetPos, off);
res = res.setOffset(2 * offsetPos + 1, length);
off += length;
}
int oldSize = adjacentNodesOld.nodesWithEdgeProperties.length;
this.adjacentNodes = res;
return (long) newSize + (((long) oldSize) << 32);
}
public final boolean isDirty() {
return dirty;
}
@Override
public int hashCode() {
/* NodeRef compares by id. We need the hash computation to be fast and allocation-free; but we don't need it
* very strong. Plain java would use id ^ (id>>>32) ; we do a little bit of mixing.
* The style (shift-xor 33 and multiply) is similar to murmur3; the multiply constant is randomly chosen odd number.
* Feel free to change this.
* */
long tmp = (id() ^ (id() >>> 33) ^ 0xc89f69faaa76b9b7L) * 0xa3ceded266465a8dL;
return ((int) tmp) ^ ((int) (tmp >>> 32));
}
@Override
public boolean equals(final Object obj) {
return (this == obj) || ( (obj instanceof NodeDb) && id() == ((Node) obj).id() );
}
}