blob: 25e03996ebc1aad34d5cdead5a7ca836aa9f5fb4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.traversal.algorithm;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.hugegraph.HugeGraph;
import org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.structure.HugeEdge;
import org.apache.hugegraph.type.define.Directions;
import org.apache.hugegraph.util.E;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import jakarta.ws.rs.core.MultivaluedMap;
public class SubGraphTraverser extends HugeTraverser {
public SubGraphTraverser(HugeGraph graph) {
super(graph);
}
private static boolean hasMultiEdges(List<Edge> edges, Id target) {
boolean hasOutEdge = false;
boolean hasInEdge = false;
for (Edge edge : edges) {
if (((HugeEdge) edge).id().otherVertexId().equals(target)) {
if (((HugeEdge) edge).direction() == Directions.OUT) {
hasOutEdge = true;
} else {
hasInEdge = true;
}
if (hasOutEdge && hasInEdge) {
return true;
}
}
}
return false;
}
public PathSet rays(Id sourceV, Directions dir, String label, int depth,
long degree, long capacity, long limit) {
return this.subGraphPaths(sourceV, dir, label, depth, degree, capacity,
limit, false, false);
}
public PathSet rings(Id sourceV, Directions dir, String label, int depth,
boolean sourceInRing, long degree, long capacity,
long limit) {
return this.subGraphPaths(sourceV, dir, label, depth, degree, capacity,
limit, true, sourceInRing);
}
private PathSet subGraphPaths(Id sourceV, Directions dir, String label,
int depth, long degree, long capacity,
long limit, boolean rings,
boolean sourceInRing) {
E.checkNotNull(sourceV, "source vertex id");
this.checkVertexExist(sourceV, "source vertex");
E.checkNotNull(dir, "direction");
checkPositive(depth, "max depth");
checkDegree(degree);
checkCapacity(capacity);
checkLimit(limit);
Id labelId = this.getEdgeLabelId(label);
Traverser traverser = new Traverser(sourceV, labelId, depth, degree,
capacity, limit, rings,
sourceInRing);
PathSet paths = new PathSet();
do {
paths.addAll(traverser.forward(dir));
} while (--depth > 0 && !traverser.reachLimit() &&
!traverser.finished());
this.vertexIterCounter.addAndGet(traverser.accessedVertices.size());
this.edgeIterCounter.addAndGet(traverser.edgeCount);
paths.setEdges(traverser.edgeRecord.getEdges(paths));
return paths;
}
private static class RingPath extends Path {
public RingPath(Id crosspoint, List<Id> vertices) {
super(crosspoint, vertices);
}
@Override
public int hashCode() {
int hashCode = 0;
for (Id id : this.vertices()) {
hashCode ^= id.hashCode();
}
return hashCode;
}
/**
* Compares the specified object with this path for equality.
* Returns <tt>true</tt> if other path is equal to or
* reversed of this path.
*
* @param other the object to be compared
* @return <tt>true</tt> if the specified object is equal to or
* reversed of this path
*/
@Override
public boolean equals(Object other) {
if (!(other instanceof RingPath)) {
return false;
}
List<Id> vertices = this.vertices();
List<Id> otherVertices = ((Path) other).vertices();
if (vertices.equals(otherVertices)) {
return true;
}
if (vertices.size() != otherVertices.size()) {
return false;
}
for (int i = 0, size = vertices.size(); i < size; i++) {
int j = size - i - 1;
if (!vertices.get(i).equals(otherVertices.get(j))) {
return false;
}
}
return true;
}
}
private class Traverser {
private final Id source;
private final Id label;
private final long degree;
private final long capacity;
private final long limit;
private final boolean rings;
private final boolean sourceInRing;
private final Set<Id> accessedVertices = newIdSet();
private final EdgeRecord edgeRecord;
private MultivaluedMap<Id, Node> sources = newMultivalueMap();
private int depth;
private long pathCount;
private long edgeCount;
public Traverser(Id sourceV, Id label, int depth, long degree,
long capacity, long limit, boolean rings,
boolean sourceInRing) {
this.source = sourceV;
this.sources.add(sourceV, new Node(sourceV));
this.accessedVertices.add(sourceV);
this.label = label;
this.depth = depth;
this.degree = degree;
this.capacity = capacity;
this.limit = limit;
this.rings = rings;
this.sourceInRing = sourceInRing;
this.pathCount = 0L;
this.edgeCount = 0L;
this.edgeRecord = new EdgeRecord(false);
}
/**
* Search forward from source
*/
public PathSet forward(Directions direction) {
PathSet paths = new PathSet();
MultivaluedMap<Id, Node> newVertices = newMultivalueMap();
Iterator<Edge> edges;
// Traversal vertices of previous level
for (Map.Entry<Id, List<Node>> entry : this.sources.entrySet()) {
Id vid = entry.getKey();
// Record edgeList to determine if multiple edges exist
List<Edge> edgeList = IteratorUtils.list(edgesOfVertex(
vid, direction, this.label, this.degree));
edges = edgeList.iterator();
if (!edges.hasNext()) {
// Reach the end, rays found
if (this.rings) {
continue;
}
for (Node n : entry.getValue()) {
// Store rays
paths.add(new Path(n.path()));
this.pathCount++;
if (this.reachLimit()) {
return paths;
}
}
}
int neighborCount = 0;
Set<Id> currentNeighbors = newIdSet();
while (edges.hasNext()) {
neighborCount++;
HugeEdge edge = (HugeEdge) edges.next();
this.edgeCount += 1L;
Id target = edge.id().otherVertexId();
this.edgeRecord.addEdge(vid, target, edge);
// Avoid deduplicate path
if (currentNeighbors.contains(target)) {
continue;
}
currentNeighbors.add(target);
this.accessedVertices.add(target);
for (Node node : entry.getValue()) {
// No ring, continue
if (!node.contains(target)) {
// Add node to next start-nodes
newVertices.add(target, new Node(target, node));
continue;
}
// Rays found if it's fake ring like:
// path is pattern: A->B<-A && A is only neighbor of B
boolean uniqueEdge = neighborCount == 1 &&
!edges.hasNext();
boolean bothBack = target.equals(node.parent().id()) &&
direction == Directions.BOTH;
if (!this.rings && bothBack && uniqueEdge) {
paths.add(new Path(node.path()));
this.pathCount++;
if (this.reachLimit()) {
return paths;
}
}
// Actual rings found
if (this.rings) {
boolean ringsFound = false;
// 1. sourceInRing is false, or
// 2. sourceInRing is true and target == source
if (!sourceInRing || target.equals(this.source)) {
if (!target.equals(node.parent().id())) {
ringsFound = true;
} else if (direction != Directions.BOTH) {
ringsFound = true;
} else if (hasMultiEdges(edgeList, target)) {
ringsFound = true;
}
}
if (ringsFound) {
List<Id> path = node.path();
path.add(target);
paths.add(new RingPath(null, path));
this.pathCount++;
if (this.reachLimit()) {
return paths;
}
}
}
}
}
}
// Re-init sources
this.sources = newVertices;
if (!this.rings && --this.depth <= 0) {
for (List<Node> list : newVertices.values()) {
for (Node n : list) {
paths.add(new Path(n.path()));
this.pathCount++;
if (this.reachLimit()) {
return paths;
}
}
}
}
return paths;
}
private boolean reachLimit() {
checkCapacity(this.capacity, this.accessedVertices.size(),
this.rings ? "rings" : "rays");
return this.limit != NO_LIMIT && this.pathCount >= this.limit;
}
private boolean finished() {
return this.sources.isEmpty();
}
}
}