diff --git a/api/server/src/main/java/org/softwareheritage/graph/Edges.java b/api/server/src/main/java/org/softwareheritage/graph/AllowedEdges.java similarity index 89% rename from api/server/src/main/java/org/softwareheritage/graph/Edges.java rename to api/server/src/main/java/org/softwareheritage/graph/AllowedEdges.java index bafa19a..72bcdf7 100644 --- a/api/server/src/main/java/org/softwareheritage/graph/Edges.java +++ b/api/server/src/main/java/org/softwareheritage/graph/AllowedEdges.java @@ -1,47 +1,48 @@ package org.softwareheritage.graph; import java.util.ArrayList; import org.softwareheritage.graph.Node; -public class Edges { +public class AllowedEdges { + // First dimension is source node type, second dimension is destination node type boolean[][] allowed; - public Edges(String edgesFmt) { + public AllowedEdges(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/algo/Visit.java b/api/server/src/main/java/org/softwareheritage/graph/algo/Visit.java index d69cf62..9d74926 100644 --- a/api/server/src/main/java/org/softwareheritage/graph/algo/Visit.java +++ b/api/server/src/main/java/org/softwareheritage/graph/algo/Visit.java @@ -1,98 +1,98 @@ package org.softwareheritage.graph.algo; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.Stack; import it.unimi.dsi.big.webgraph.LazyLongIterator; -import org.softwareheritage.graph.Edges; +import org.softwareheritage.graph.AllowedEdges; import org.softwareheritage.graph.Graph; import org.softwareheritage.graph.Node; import org.softwareheritage.graph.SwhId; import org.softwareheritage.graph.SwhPath; public class Visit { public enum OutputFmt { ONLY_NODES, ONLY_PATHS, NODES_AND_PATHS } Graph graph; boolean useTransposed; - Edges edges; + AllowedEdges edges; // LinkedHashSet is necessary to preserve insertion order LinkedHashSet nodes; ArrayList paths; Stack currentPath; public Visit(Graph graph, SwhId src, String edgesFmt, String direction, OutputFmt output) { if (!direction.matches("forward|backward")) { throw new IllegalArgumentException("Unknown traversal direction: " + direction); } this.graph = graph; this.useTransposed = (direction.equals("backward")); - this.edges = new Edges(edgesFmt); + this.edges = new AllowedEdges(edgesFmt); this.nodes = new LinkedHashSet(); this.paths = new ArrayList(); this.currentPath = new Stack(); long nodeId = graph.getNodeId(src); if (output == OutputFmt.ONLY_NODES) { dfsOutputOnlyNodes(nodeId); } else { dfs(nodeId); } } public LinkedHashSet getNodes() { return nodes; } public ArrayList getPaths() { return paths; } private void dfs(long currentNodeId) { nodes.add(graph.getSwhId(currentNodeId)); currentPath.push(currentNodeId); long degree = graph.degree(currentNodeId, useTransposed); LazyLongIterator neighbors = graph.neighbors(currentNodeId, useTransposed); long visitedNeighbors = 0; while (degree-- > 0) { long neighborNodeId = neighbors.nextLong(); Node.Type currentNodeType = graph.getSwhId(currentNodeId).getType(); Node.Type neighborNodeType = graph.getSwhId(neighborNodeId).getType(); if (edges.isAllowed(currentNodeType, neighborNodeType)) { dfs(neighborNodeId); visitedNeighbors++; } } if (visitedNeighbors == 0) { SwhPath path = new SwhPath(); for (long nodeId : currentPath) { path.add(graph.getSwhId(nodeId)); } paths.add(path); } currentPath.pop(); } private void dfsOutputOnlyNodes(long currentNodeId) { nodes.add(graph.getSwhId(currentNodeId)); long degree = graph.degree(currentNodeId, useTransposed); LazyLongIterator neighbors = graph.neighbors(currentNodeId, useTransposed); while (degree-- > 0) { long neighborNodeId = neighbors.nextLong(); Node.Type currentNodeType = graph.getSwhId(currentNodeId).getType(); Node.Type neighborNodeType = graph.getSwhId(neighborNodeId).getType(); if (edges.isAllowed(currentNodeType, neighborNodeType)) { dfsOutputOnlyNodes(neighborNodeId); } } } } diff --git a/api/server/src/main/java/org/softwareheritage/graph/algo/Walk.java b/api/server/src/main/java/org/softwareheritage/graph/algo/Walk.java index 71ba73b..0467f9b 100644 --- a/api/server/src/main/java/org/softwareheritage/graph/algo/Walk.java +++ b/api/server/src/main/java/org/softwareheritage/graph/algo/Walk.java @@ -1,148 +1,148 @@ package org.softwareheritage.graph.algo; import java.util.Stack; import java.util.Queue; import java.util.LinkedList; import it.unimi.dsi.big.webgraph.LazyLongIterator; import it.unimi.dsi.bits.LongArrayBitVector; import it.unimi.dsi.fastutil.longs.LongBigArrays; -import org.softwareheritage.graph.Edges; +import org.softwareheritage.graph.AllowedEdges; import org.softwareheritage.graph.Graph; import org.softwareheritage.graph.Node; import org.softwareheritage.graph.SwhId; import org.softwareheritage.graph.SwhPath; public class Walk { Graph graph; boolean useTransposed; - Edges edges; + AllowedEdges edges; SwhPath path; long[][] nodeParent; public Walk( Graph graph, SwhId src, String dstFmt, String edgesFmt, String direction, String traversal) { if (!traversal.matches("dfs|bfs")) { throw new IllegalArgumentException("Unknown traversal algorithm: " + traversal); } if (!direction.matches("forward|backward")) { throw new IllegalArgumentException("Unknown traversal direction: " + direction); } this.graph = graph; this.useTransposed = (direction.equals("backward")); - this.edges = new Edges(edgesFmt); + this.edges = new AllowedEdges(edgesFmt); this.path = new SwhPath(); this.nodeParent = LongBigArrays.newBigArray(graph.getNbNodes()); LongBigArrays.fill(nodeParent, -1); long srcNodeId = graph.getNodeId(src); long dstNodeId; if (traversal.equals("dfs")) { dstNodeId = dfs(srcNodeId, dstFmt); } else { dstNodeId = bfs(srcNodeId, dstFmt); } if (dstNodeId == -1) { throw new IllegalArgumentException("Unable to find destination point: " + dstFmt); } else { backtracking(srcNodeId, dstNodeId); } } public SwhPath getPath() { return path; } private void backtracking(long srcNodeId, long dstNodeId) { long currentNodeId = dstNodeId; while (currentNodeId != srcNodeId) { path.add(graph.getSwhId(currentNodeId)); currentNodeId = LongBigArrays.get(nodeParent, currentNodeId); } path.add(graph.getSwhId(srcNodeId)); path.reverse(); } private long dfs(long srcNodeId, String dstFmt) { Stack stack = new Stack(); LongArrayBitVector visited = LongArrayBitVector.ofLength(graph.getNbNodes()); stack.push(srcNodeId); visited.set(srcNodeId); while (!stack.isEmpty()) { long currentNodeId = stack.pop(); SwhId currentSwhId = graph.getSwhId(currentNodeId); if (isDestinationNode(currentSwhId, dstFmt)) { return currentNodeId; } long degree = graph.degree(currentNodeId, useTransposed); LazyLongIterator neighbors = graph.neighbors(currentNodeId, useTransposed); while (degree-- > 0) { long neighborNodeId = neighbors.nextLong(); SwhId neighborSwhId = graph.getSwhId(neighborNodeId); if (!visited.getBoolean(neighborNodeId) && edges.isAllowed(currentSwhId.getType(), neighborSwhId.getType())) { stack.push(neighborNodeId); visited.set(neighborNodeId); LongBigArrays.set(nodeParent, neighborNodeId, currentNodeId); } } } return -1; } private long bfs(long srcNodeId, String dstFmt) { Queue queue = new LinkedList(); LongArrayBitVector visited = LongArrayBitVector.ofLength(graph.getNbNodes()); queue.add(srcNodeId); visited.set(srcNodeId); while (!queue.isEmpty()) { long currentNodeId = queue.poll(); SwhId currentSwhId = graph.getSwhId(currentNodeId); if (isDestinationNode(currentSwhId, dstFmt)) { return currentNodeId; } long degree = graph.degree(currentNodeId, useTransposed); LazyLongIterator neighbors = graph.neighbors(currentNodeId, useTransposed); while (degree-- > 0) { long neighborNodeId = neighbors.nextLong(); SwhId neighborSwhId = graph.getSwhId(neighborNodeId); if (!visited.getBoolean(neighborNodeId) && edges.isAllowed(currentSwhId.getType(), neighborSwhId.getType())) { queue.add(neighborNodeId); visited.set(neighborNodeId); LongBigArrays.set(nodeParent, neighborNodeId, currentNodeId); } } } return -1; } private boolean isDestinationNode(SwhId swhId, String dstFmt) { // dstFmt is either a SwhId... if (swhId.toString().equals(dstFmt)) { return true; } // ...or a Node.Type try { Node.Type dstType = Node.Type.fromStr(dstFmt); return (swhId.getType().equals(dstType)); } catch (IllegalArgumentException e) { return false; } } } diff --git a/api/server/src/test/java/org/softwareheritage/graph/EdgesTest.java b/api/server/src/test/java/org/softwareheritage/graph/EdgesTest.java index 72d2342..337d509 100644 --- a/api/server/src/test/java/org/softwareheritage/graph/EdgesTest.java +++ b/api/server/src/test/java/org/softwareheritage/graph/EdgesTest.java @@ -1,112 +1,112 @@ package org.softwareheritage.graph; import java.util.ArrayList; import org.junit.Test; import static org.junit.Assert.assertTrue; -import org.softwareheritage.graph.Edges; +import org.softwareheritage.graph.AllowedEdges; import org.softwareheritage.graph.Node; public class EdgesTest { class EdgeType { Node.Type src; Node.Type dst; public EdgeType(Node.Type src, Node.Type dst) { this.src = src; this.dst = dst; } @Override public boolean equals(Object otherObj) { if (otherObj == this) return true; if (!(otherObj instanceof EdgeType)) return false; EdgeType other = (EdgeType) otherObj; return src == other.src && dst == other.dst; } } - void assertEdgeRestriction(Edges edges, ArrayList expectedAllowed) { + void assertEdgeRestriction(AllowedEdges edges, ArrayList expectedAllowed) { Node.Type[] nodeTypes = Node.Type.values(); for (Node.Type src : nodeTypes) { for (Node.Type dst : nodeTypes) { EdgeType edge = new EdgeType(src, dst); boolean isAllowed = edges.isAllowed(src, dst); boolean isExpected = false; for (EdgeType expected : expectedAllowed) { if (expected.equals(edge)) { isExpected = true; break; } } assertTrue("Edge type: " + src + " -> " + dst, isAllowed == isExpected); } } } @Test public void dirToDirDirToCntEdges() { - Edges edges = new Edges("dir:dir,dir:cnt"); + AllowedEdges edges = new AllowedEdges("dir:dir,dir:cnt"); ArrayList expected = new ArrayList<>(); expected.add(new EdgeType(Node.Type.DIR, Node.Type.DIR)); expected.add(new EdgeType(Node.Type.DIR, Node.Type.CNT)); assertEdgeRestriction(edges, expected); } @Test public void relToRevRevToRevRevToDirEdges() { - Edges edges = new Edges("rel:rev,rev:rev,rev:dir"); + AllowedEdges edges = new AllowedEdges("rel:rev,rev:rev,rev:dir"); ArrayList expected = new ArrayList<>(); expected.add(new EdgeType(Node.Type.REL, Node.Type.REV)); expected.add(new EdgeType(Node.Type.REV, Node.Type.REV)); expected.add(new EdgeType(Node.Type.REV, Node.Type.DIR)); assertEdgeRestriction(edges, expected); } @Test public void revToAllDirToDirEdges() { - Edges edges = new Edges("rev:*,dir:dir"); + AllowedEdges edges = new AllowedEdges("rev:*,dir:dir"); ArrayList expected = new ArrayList<>(); for (Node.Type dst : Node.Type.values()) { expected.add(new EdgeType(Node.Type.REV, dst)); } expected.add(new EdgeType(Node.Type.DIR, Node.Type.DIR)); assertEdgeRestriction(edges, expected); } @Test public void allToCntEdges() { - Edges edges = new Edges("*:cnt"); + AllowedEdges edges = new AllowedEdges("*:cnt"); ArrayList expected = new ArrayList<>(); for (Node.Type src : Node.Type.values()) { expected.add(new EdgeType(src, Node.Type.CNT)); } assertEdgeRestriction(edges, expected); } @Test public void allEdges() { - Edges edges = new Edges("*:*"); - Edges edges2 = new Edges("*"); + AllowedEdges edges = new AllowedEdges("*:*"); + AllowedEdges edges2 = new AllowedEdges("*"); ArrayList expected = new ArrayList<>(); for (Node.Type src : Node.Type.values()) { for (Node.Type dst : Node.Type.values()) { expected.add(new EdgeType(src, dst)); } } assertEdgeRestriction(edges, expected); assertEdgeRestriction(edges2, expected); } @Test public void noEdges() { - Edges edges = new Edges(""); - Edges edges2 = new Edges(null); + AllowedEdges edges = new AllowedEdges(""); + AllowedEdges edges2 = new AllowedEdges(null); ArrayList expected = new ArrayList<>(); assertEdgeRestriction(edges, expected); assertEdgeRestriction(edges2, expected); } }