diff --git a/hugegraph-api/pom.xml b/hugegraph-api/pom.xml
index fc27f55e2c..ee328812d7 100644
--- a/hugegraph-api/pom.xml
+++ b/hugegraph-api/pom.xml
@@ -86,7 +86,7 @@
- 0.39.0.0
+ 0.40.0.0
diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RaysAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RaysAPI.java
index edeccbe34e..c4996a79dd 100644
--- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RaysAPI.java
+++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RaysAPI.java
@@ -72,8 +72,9 @@ public String get(@Context GraphManager manager,
@DefaultValue(DEFAULT_PATHS_LIMIT) long limit) {
LOG.debug("Graph [{}] get rays paths from '{}' with " +
"direction '{}', edge label '{}', max depth '{}', " +
- "max degree '{}' and limit '{}'",
- graph, sourceV, direction, edgeLabel, depth, degree, limit);
+ "max degree '{}', capacity '{}' and limit '{}'",
+ graph, sourceV, direction, edgeLabel, depth, degree,
+ capacity, limit);
Id source = VertexAPI.checkAndParseVertexId(sourceV);
Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RingsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RingsAPI.java
index 3921f3b4e1..814cdbdd76 100644
--- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RingsAPI.java
+++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RingsAPI.java
@@ -64,6 +64,8 @@ public String get(@Context GraphManager manager,
@QueryParam("direction") String direction,
@QueryParam("label") String edgeLabel,
@QueryParam("max_depth") int depth,
+ @QueryParam("source_in_ring")
+ @DefaultValue("true") boolean sourceInRing,
@QueryParam("max_degree")
@DefaultValue(DEFAULT_DEGREE) long degree,
@QueryParam("capacity")
@@ -72,8 +74,10 @@ public String get(@Context GraphManager manager,
@DefaultValue(DEFAULT_PATHS_LIMIT) long limit) {
LOG.debug("Graph [{}] get rings paths reachable from '{}' with " +
"direction '{}', edge label '{}', max depth '{}', " +
- "max degree '{}' and limit '{}'",
- graph, sourceV, direction, edgeLabel, depth, degree, limit);
+ "source in ring '{}', max degree '{}', capacity '{}' " +
+ "and limit '{}'",
+ graph, sourceV, direction, edgeLabel, depth, sourceInRing,
+ degree, capacity, limit);
Id source = VertexAPI.checkAndParseVertexId(sourceV);
Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
@@ -82,8 +86,9 @@ public String get(@Context GraphManager manager,
SubGraphTraverser traverser = new SubGraphTraverser(g);
List paths = traverser.rings(source, dir, edgeLabel,
- depth, degree,
- capacity, limit);
+ depth, sourceInRing,
+ degree, capacity,
+ limit);
return manager.serializer(g).writePaths("rings", paths, false);
}
}
diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java
index 80656b1552..427048464f 100644
--- a/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java
+++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java
@@ -87,10 +87,11 @@ public final class ApiVersion {
*
* version 0.10:
* [0.39] Issue-522: Add profile RESTful API
+ * [0.40] Issue-523: Add source_in_ring args for rings RESTful API
*/
// The second parameter of Version.of() is for IDE running without JAR
- public static final Version VERSION = Version.of(ApiVersion.class, "0.39");
+ public static final Version VERSION = Version.of(ApiVersion.class, "0.40");
public static final void check() {
// Check version of hugegraph-core. Firstly do check from version 0.3
diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java
index 279064fcaa..912471d657 100644
--- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java
+++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java
@@ -27,6 +27,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import javax.ws.rs.core.MultivaluedHashMap;
@@ -379,7 +380,8 @@ public boolean equals(Object object) {
return false;
}
Node other = (Node) object;
- return this.id.equals(other.id);
+ return Objects.equals(this.id, other.id) &&
+ Objects.equals(this.parent, other.parent);
}
}
diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SubGraphTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SubGraphTraverser.java
index 79d8e18a77..1a7d63911c 100644
--- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SubGraphTraverser.java
+++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SubGraphTraverser.java
@@ -20,6 +20,7 @@
package com.baidu.hugegraph.traversal.algorithm;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -28,6 +29,7 @@
import javax.ws.rs.core.MultivaluedMap;
import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import com.baidu.hugegraph.HugeGraph;
import com.baidu.hugegraph.backend.id.Id;
@@ -44,18 +46,20 @@ public SubGraphTraverser(HugeGraph graph) {
public List 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);
+ capacity, limit, false, false);
}
- public List rings(Id sourceV, Directions dir, String label,
- int depth, long degree, long capacity, long limit) {
+ public List 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);
+ capacity, limit, true, sourceInRing);
}
private List subGraphPaths(Id sourceV, Directions dir, String label,
int depth, long degree, long capacity,
- long limit, boolean rings) {
+ long limit, boolean rings,
+ boolean sourceInRing) {
E.checkNotNull(sourceV, "source vertex id");
E.checkNotNull(dir, "direction");
checkPositive(depth, "max depth");
@@ -64,13 +68,13 @@ private List subGraphPaths(Id sourceV, Directions dir, String label,
checkLimit(limit);
Id labelId = this.getEdgeLabelId(label);
- Traverser traverser = new Traverser(sourceV, labelId, degree,
- capacity, limit, rings);
+ Traverser traverser = new Traverser(sourceV, labelId, depth, degree,
+ capacity, limit, rings,
+ sourceInRing);
List paths = new ArrayList<>();
while (true) {
paths.addAll(traverser.forward(dir));
- boolean reachDepth = rings ? --depth <= 0 : depth-- <= 0;
- if (reachDepth || traverser.reachLimit() ||
+ if (--depth <= 0 || traverser.reachLimit() ||
traverser.finished()) {
break;
}
@@ -78,27 +82,47 @@ private List subGraphPaths(Id sourceV, Directions dir, String label,
return paths;
}
+ private static boolean hasMultiEdges(List edges, Id target) {
+ int count = 0;
+ for (Edge edge : edges) {
+ if (((HugeEdge) edge).id().otherVertexId().equals(target)) {
+ if (++count > 1) {
+ return true;
+ }
+ }
+ }
+ assert count == 1;
+ return false;
+ }
+
private class Traverser {
+ private final Id source;
private MultivaluedMap sources = newMultivalueMap();
private Set accessedVertices = newSet();
private final Id label;
+ private int depth;
private final long degree;
private final long capacity;
private final long limit;
private final boolean rings;
+ private final boolean sourceInRing;
private long pathCount;
- public Traverser(Id sourceV, Id label, long degree,
- long capacity, long limit, boolean rings) {
+ 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;
}
@@ -112,7 +136,10 @@ public List forward(Directions direction) {
// Traversal vertices of previous level
for (Map.Entry> entry : this.sources.entrySet()) {
Id vid = entry.getKey();
- edges = edgesOfVertex(vid, direction, this.label, this.degree);
+ // Record edgeList to determine if multiple edges exist
+ List edgeList = IteratorUtils.list(edgesOfVertex(
+ vid, direction, this.label, this.degree));
+ edges = edgeList.iterator();
if (!edges.hasNext()) {
// Reach the end, rays found
@@ -128,33 +155,80 @@ public List forward(Directions direction) {
}
}
}
+
+ int neighborCount = 0;
+ Set currentNeighbors = new HashSet<>();
while (edges.hasNext()) {
+ neighborCount++;
HugeEdge edge = (HugeEdge) edges.next();
Id target = edge.id().otherVertexId();
+ // Avoid deduplicate path
+ if (currentNeighbors.contains(target)) {
+ continue;
+ }
+ currentNeighbors.add(target);
this.accessedVertices.add(target);
- for (Node n : entry.getValue()) {
- if (!n.contains(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, n));
+ newVertices.add(target, new Node(target, node));
continue;
}
- // Rings found and expect rings
- if (this.rings) {
- assert n.contains(target);
- List prePath = n.path();
- prePath.add(target);
- paths.add(new Path(null, prePath));
+
+ // 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(null, node.path()));
this.pathCount++;
if (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 path = node.path();
+ path.add(target);
+ paths.add(new Path(null, path));
+ this.pathCount++;
+ if (reachLimit()) {
+ return paths;
+ }
+ }
+ }
}
}
}
// Re-init sources
this.sources = newVertices;
+ if (!rings && --this.depth <= 0) {
+ for (List list : newVertices.values()) {
+ for (Node n : list) {
+ paths.add(new Path(null, n.path()));
+ }
+ }
+ }
+
return paths;
}