diff --git a/api/server/src/main/java/org/softwareheritage/graph/App.java b/api/server/src/main/java/org/softwareheritage/graph/App.java --- a/api/server/src/main/java/org/softwareheritage/graph/App.java +++ b/api/server/src/main/java/org/softwareheritage/graph/App.java @@ -56,7 +56,7 @@ // By default, traversal is a forward DFS using all edges String traversal = ctx.queryParam("traversal", "dfs"); String direction = ctx.queryParam("direction", "forward"); - String edges = ctx.queryParam("edges", "all"); + String edges = ctx.queryParam("edges", "*"); ctx.json(new Visit(graph, swhId, edges, traversal, direction)); } catch (IllegalArgumentException e) { diff --git a/api/server/src/main/java/org/softwareheritage/graph/Edges.java b/api/server/src/main/java/org/softwareheritage/graph/Edges.java new file mode 100644 --- /dev/null +++ b/api/server/src/main/java/org/softwareheritage/graph/Edges.java @@ -0,0 +1,47 @@ +package org.softwareheritage.graph; + +import java.util.ArrayList; + +import org.softwareheritage.graph.Node; + +public class Edges { + boolean[][] allowed; + + public Edges(String edgesFmt) { + int nbNodeTypes = Node.Type.values().length; + this.allowed = new boolean[nbNodeTypes][nbNodeTypes]; + // Special values (null, empty, "*") + if (edgesFmt == null || edgesFmt.isEmpty()) { + return; + } + if (edgesFmt.equals("*")) { + for (int i = 0; i < nbNodeTypes; i++) { + for (int j = 0; j < nbNodeTypes; j++) { + allowed[i][j] = true; + } + } + return; + } + + // Format: "src1:dst1,src2:dst2,[...]" + String[] edgeTypes = edgesFmt.split(","); + for (String edgeType : edgeTypes) { + String[] nodeTypes = edgeType.split(":"); + if (nodeTypes.length != 2) { + throw new IllegalArgumentException("Cannot parse edge type: " + edgeType); + } + + ArrayList srcTypes = Node.Type.parse(nodeTypes[0]); + ArrayList dstTypes = Node.Type.parse(nodeTypes[1]); + for (Node.Type srcType : srcTypes) { + for (Node.Type dstType : dstTypes) { + allowed[srcType.ordinal()][dstType.ordinal()] = true; + } + } + } + } + + public boolean isAllowed(Node.Type currentType, Node.Type neighborType) { + return allowed[currentType.ordinal()][neighborType.ordinal()]; + } +} diff --git a/api/server/src/main/java/org/softwareheritage/graph/Node.java b/api/server/src/main/java/org/softwareheritage/graph/Node.java new file mode 100644 --- /dev/null +++ b/api/server/src/main/java/org/softwareheritage/graph/Node.java @@ -0,0 +1,32 @@ +package org.softwareheritage.graph; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Node { + public enum Type { + CNT, + DIR, + REL, + REV, + SNP; + + public static Node.Type fromStr(String strType) { + return Node.Type.valueOf(strType.toUpperCase()); + } + + public static ArrayList parse(String strType) { + ArrayList types = new ArrayList<>(); + + if (strType.equals("*")) { + List nodeTypes = Arrays.asList(Node.Type.values()); + types.addAll(nodeTypes); + } else { + types.add(Node.Type.fromStr(strType)); + } + + return types; + } + } +} diff --git a/api/server/src/main/java/org/softwareheritage/graph/SwhId.java b/api/server/src/main/java/org/softwareheritage/graph/SwhId.java --- a/api/server/src/main/java/org/softwareheritage/graph/SwhId.java +++ b/api/server/src/main/java/org/softwareheritage/graph/SwhId.java @@ -2,11 +2,13 @@ import com.fasterxml.jackson.annotation.JsonValue; +import org.softwareheritage.graph.Node; + public class SwhId { public static final int HASH_LENGTH = 40; String swhId; - String type; + Node.Type type; String hash; // SWH ID format: 'swh:1:type:hash' @@ -19,10 +21,11 @@ throw new IllegalArgumentException("Expected SWH ID format to be 'swh:1:type:hash', got: " + swhId); } - this.type = parts[2]; + String type = parts[2]; if (!type.matches("cnt|dir|rel|rev|snp")) { throw new IllegalArgumentException("Unknown SWH ID type in: " + swhId); } + this.type = Node.Type.fromStr(type); this.hash = parts[3]; if (!hash.matches("[0-9a-f]{" + HASH_LENGTH + "}")) { @@ -49,7 +52,7 @@ return swhId; } - public String getType() { + public Node.Type getType() { return type; } diff --git a/api/server/src/main/java/org/softwareheritage/graph/algo/Visit.java b/api/server/src/main/java/org/softwareheritage/graph/algo/Visit.java --- a/api/server/src/main/java/org/softwareheritage/graph/algo/Visit.java +++ b/api/server/src/main/java/org/softwareheritage/graph/algo/Visit.java @@ -5,14 +5,16 @@ import it.unimi.dsi.big.webgraph.LazyLongIterator; +import org.softwareheritage.graph.Edges; import org.softwareheritage.graph.Graph; +import org.softwareheritage.graph.Node; import org.softwareheritage.graph.SwhId; import org.softwareheritage.graph.SwhPath; public class Visit { Graph graph; boolean useTransposed; - String allowedEdges; + Edges edges; Stack currentPath; ArrayList paths; @@ -26,7 +28,7 @@ this.graph = graph; this.useTransposed = (direction.equals("backward")); - this.allowedEdges = allowedEdges; + this.edges = new Edges(allowedEdges); this.paths = new ArrayList(); this.currentPath = new Stack(); @@ -49,7 +51,9 @@ while (degree-- > 0) { long neighborNodeId = neighbors.nextLong(); - if (isEdgeAllowed(currentNodeId, neighborNodeId)) { + Node.Type currentNodeType = graph.getSwhId(currentNodeId).getType(); + Node.Type neighborNodeType = graph.getSwhId(neighborNodeId).getType(); + if (edges.isAllowed(currentNodeType, neighborNodeType)) { dfs(neighborNodeId); visitedNeighbors++; } @@ -65,16 +69,4 @@ currentPath.pop(); } - - private boolean isEdgeAllowed(long currentNodeId, long neighborNodeId) { - if (allowedEdges.equals("all")) { - return true; - } - - String currentType = graph.getSwhId(currentNodeId).getType(); - String neighborType = graph.getSwhId(neighborNodeId).getType(); - String edgeType = currentType + ":" + neighborType; - String edgeTypeRev = neighborType + ":" + currentType; - return allowedEdges.contains(edgeType) || allowedEdges.contains(edgeTypeRev); - } }