<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.whx.neo4j.procedure</groupId>
<artifactId>Neo4jProcedure</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Neo4jProcedure</name>
<url>http://maven.apache.org</url>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j</artifactId>
<version>4.4.6</version>
</dependency>
</dependencies>
</project>
package com.whx.neo4j.procedure.controlflow.path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.traversal.Evaluators;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.graphdb.traversal.Traverser;
import org.neo4j.graphdb.traversal.Uniqueness;
import org.neo4j.internal.helpers.collection.Iterables;
import org.neo4j.logging.Log;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;
import com.whx.neo4j.procedure.enums.EdgeTypes;
import com.whx.neo4j.procedure.evaluators.CyclePathEvaluator;
import com.whx.neo4j.procedure.filters.CompletePathFileter;
import com.whx.neo4j.procedure.filters.CyclePathFileter;
import com.whx.neo4j.procedure.result.PathResult;
import com.whx.neo4j.procedure.utils.ProcedureUtils;
public class ControlFlowPathTraversal {
@Context
public Transaction tx;
@Context
public Log log;
/**
*
* @param start
* @param end
* @return
* @throws Exception
*/
@Procedure("control.flow.path.sample")
public Stream<PathResult> doTraversal(@Name("start") Object start) throws Exception{
Node startNode = tx.getNodeById(4);
TraversalDescription td = tx.traversalDescription()
.depthFirst()
.relationships(EdgeTypes.ControlFlow, Direction.OUTGOING)
.uniqueness(Uniqueness.RELATIONSHIP_PATH);
Traverser traverser = td.traverse(startNode);
return Iterables.stream(traverser).map( PathResult::new );
}
/**
*
* @param start
* @param end
* @return
* @throws Exception
*/
@Procedure("control.flow.path")
public Stream<PathResult> doTraversal(@Name("start") Object start,
@Name("end") Object end) throws Exception{
List<Node> startNodes = objectToNodes(start);
List<Node> endNodes = objectToNodes(end);
Stream<Path> traversal = doTraversal(null
, startNodes.get(0)
, endNodes.get(0)
, EdgeTypes.ControlFlow
, Direction.OUTGOING
, Uniqueness.RELATIONSHIP_PATH);
return traversal.map( PathResult::new);
}
/**
*
* @param srcPath
* @param startNode
* @param endNode
* @param edge
* @param direction
* @param uniqueness
* @return
*/
public Stream<Path> doTraversal(
Path srcPath,
Node startNode,
Node endNode,
EdgeTypes edge,
Direction direction,
Uniqueness uniqueness){
if(null == srcPath) {
System.out.println("Traversal Start");
}else {
ProcedureUtils.showPath(srcPath, "Cycle Traversal Start");
}
TraversalDescription td = tx.traversalDescription()
.depthFirst()
.relationships(edge, direction);
// minLevel: 1
int minLevel = 1;
td = td.evaluator(Evaluators.fromDepth(minLevel));
//
td = td.evaluator(Evaluators.fromDepth(minLevel));
// Uniqueness
td = td.uniqueness(uniqueness);
// Cycle Path Check
td = td.evaluator(new CyclePathEvaluator(endNode));
// do traverse
Traverser traverser = td.traverse(startNode);
List<Path> traverseredPathList = Iterables.stream(traverser).collect(Collectors.toList());
ProcedureUtils.showPaths(traverseredPathList, "Traversered");
// Cycle Path Extra
CyclePathFileter<Path> cyclePathFileter = new CyclePathFileter<Path>(endNode);
List<Path> cyclePathList = traverseredPathList.stream().filter(cyclePathFileter).collect(Collectors.toList());
if(cyclePathList.size() == 0) {
if(null == srcPath) {
ProcedureUtils.showPaths(traverseredPathList, "Traversal End");
}else {
ProcedureUtils.showPaths(traverseredPathList, "Cycle Traversal End");
}
return traverseredPathList.stream();
}
ProcedureUtils.showPaths(cyclePathList, "Cycle");
// Cycle Path Traversal Again
final List<Path> subPathList = new ArrayList<>();
cyclePathList.stream().forEach(path -> {
if(path.endNode().getId() == endNode.getId()) {
return;
}
Stream<Path> traversal = doTraversal(path, path.endNode(), endNode, edge, direction, uniqueness);
traversal.forEach(subPath -> {
Path combinePath = ProcedureUtils.combine(path, subPath);
subPathList.add(combinePath);
});
});
ProcedureUtils.showPaths(subPathList, "Sub");
CompletePathFileter<Path> completePathFileter = new CompletePathFileter<Path>(endNode);
List<Path> result = traverseredPathList.stream().filter(completePathFileter).collect(Collectors.toList());
ProcedureUtils.showPaths(result, "Complete");
result.addAll(subPathList);
ProcedureUtils.showPaths(result, "ALL");
if(null == srcPath) {
System.out.println("Traversal End");
}else {
ProcedureUtils.showPath(srcPath, "Cycle Traversal End");
}
return result.stream();
}
/**
*
* @param start
* @return
* @throws Exception
*/
private List<Node> objectToNodes(Object start) throws Exception {
if (start == null) return Collections.emptyList();
if (start instanceof Node) {
return Collections.singletonList((Node) start);
}
if (start instanceof Number) {
return Collections.singletonList(tx.getNodeById(((Number) start).longValue()));
}
if (start instanceof List) {
List list = (List) start;
if (list.isEmpty()) return Collections.emptyList();
Object first = list.get(0);
if (first instanceof Node) return (List<Node>)list;
if (first instanceof Number) {
List<Node> nodes = new ArrayList<>();
for (Number n : ((List<Number>)list)) nodes.add(tx.getNodeById(n.longValue()));
return nodes;
}
}
throw new Exception("Unsupported data type for start parameter a Node or an Identifier (long) of a Node must be given!");
}
}
package com.whx.neo4j.procedure.enums;
import org.neo4j.graphdb.RelationshipType;
public enum EdgeTypes implements RelationshipType {
ControlFlow
}
package com.whx.neo4j.procedure.evaluators;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.traversal.Evaluation;
import org.neo4j.graphdb.traversal.Evaluator;
import org.neo4j.internal.helpers.collection.Iterables;
import com.whx.neo4j.procedure.utils.ProcedureUtils;
public class CyclePathEvaluator implements Evaluator {
private Node endNode;
public CyclePathEvaluator(Node endNode) {
this.endNode = endNode;
}
@Override
public Evaluation evaluate(Path path) {
ProcedureUtils.showPath(path, "Cycle Path Evaluator");
if(endNode.getId() == path.endNode().getId()) {
return Evaluation.INCLUDE_AND_CONTINUE;
}
// Relationships count
long relationshipCount = path.length();
// Nodes count
long nodeCount = Iterables.stream(path.nodes()).distinct().count();
if(nodeCount <= relationshipCount) {
// Cycle Path
if(path.startNode().getId() == path.endNode().getId()) {
return Evaluation.EXCLUDE_AND_CONTINUE;
}
return Evaluation.INCLUDE_AND_CONTINUE;
}
return Evaluation.EXCLUDE_AND_CONTINUE;
}
}
package com.whx.neo4j.procedure.filters;
import java.util.function.Predicate;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
public class CompletePathFileter<T> implements Predicate<T> {
private Node endNode;
public CompletePathFileter(Node endNode) {
super();
this.endNode = endNode;
}
@Override
public boolean test(T t) {
if(!(t instanceof Path)) {
throw new IllegalArgumentException("CompletePathFileter Error!!!");
}
Path path = (Path) t;
if(path.endNode().getId() == endNode.getId()) {
return true;
}
return false;
}
}
package com.whx.neo4j.procedure.filters;
import java.util.function.Predicate;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.internal.helpers.collection.Iterables;
public class CyclePathFileter<T> implements Predicate<T> {
private Node endNode;
public CyclePathFileter(Node endNode) {
super();
this.endNode = endNode;
}
@Override
public boolean test(T t) {
if(!(t instanceof Path)) {
throw new IllegalArgumentException("CyclePathFileter Error!!!");
}
Path path = (Path) t;
if(path.endNode().getId() == endNode.getId()) {
return false;
}
// Relationships count
long relationshipCount = path.length();
// Nodes count
long nodeCount = Iterables.stream(path.nodes()).distinct().count();
if(nodeCount <= relationshipCount) {
return true;
}
if(nodeCount == (relationshipCount + 1)) {
return false;
}
throw new IllegalArgumentException("CyclePathFileter Error!!!");
}
}
package com.whx.neo4j.procedure.result;
import org.neo4j.graphdb.Path;
public class PathResult {
public Path path;
public PathResult(Path path) {
this.path = path;
}
}
package com.whx.neo4j.procedure.utils;
import java.util.List;
import org.neo4j.graphalgo.impl.util.PathImpl;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.Relationship;
public class ProcedureUtils {
/**
*
* @param first
* @param second
* @return
*/
public static Path combine(Path first, Path second) {
if (first == null) return second;
if (second == null) return first;
if (first.endNode().getId() != second.startNode().getId())
throw new IllegalArgumentException("Paths don't connect on their end and start-nodes "+first+ " with "+second);
PathImpl.Builder builder = new PathImpl.Builder(first.startNode());
for (Relationship rel : first.relationships()) builder = builder.push(rel);
for (Relationship rel : second.relationships()) builder = builder.push(rel);
return builder.build();
}
/**
*
* @param paths
* @param prefix
*/
public static void showPaths(List<Path> paths, String prefix) {
paths.stream().forEach(path -> {
showPath(path, prefix);
});
}
/**
*
* @param path
* @param prefix
*/
public static void showPath(Path path, String prefix) {
StringBuilder result = new StringBuilder();
result.append(prefix).append(" ").append("Path\t");
path.nodes().forEach(node -> {
String text = (String) node.getProperty("text");
result.append(text)
.append("->");
});
System.out.println(result.toString());
}
}