-
Notifications
You must be signed in to change notification settings - Fork 494
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added whitelistNodes and blacklistNodes, to finish up node-specific f…
…iltering for path expanders. (#796)
- Loading branch information
1 parent
030dbf5
commit 4c5a85a
Showing
7 changed files
with
604 additions
and
221 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package apoc.path; | ||
|
||
import org.neo4j.graphdb.Node; | ||
import org.neo4j.graphdb.Path; | ||
import org.neo4j.graphdb.traversal.Evaluation; | ||
import org.neo4j.graphdb.traversal.Evaluator; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
import static org.neo4j.graphdb.traversal.Evaluation.EXCLUDE_AND_CONTINUE; | ||
import static org.neo4j.graphdb.traversal.Evaluation.INCLUDE_AND_CONTINUE; | ||
|
||
// when no commas present, acts as a pathwide label filter | ||
public class LabelSequenceEvaluator implements Evaluator { | ||
private List<LabelMatcherGroup> sequenceMatchers; | ||
|
||
private Evaluation whitelistAllowedEvaluation; | ||
private boolean endNodesOnly; | ||
private boolean filterStartNode; | ||
private boolean beginSequenceAtStart; | ||
private long minLevel = -1; | ||
|
||
public LabelSequenceEvaluator(String labelSequence, boolean filterStartNode, boolean beginSequenceAtStart, int minLevel) { | ||
List<String> labelSequenceList; | ||
|
||
// parse sequence | ||
if (labelSequence != null && !labelSequence.isEmpty()) { | ||
labelSequenceList = Arrays.asList(labelSequence.split(",")); | ||
} else { | ||
labelSequenceList = Collections.emptyList(); | ||
} | ||
|
||
initialize(labelSequenceList, filterStartNode, beginSequenceAtStart, minLevel); | ||
} | ||
|
||
public LabelSequenceEvaluator(List<String> labelSequenceList, boolean filterStartNode, boolean beginSequenceAtStart, int minLevel) { | ||
initialize(labelSequenceList, filterStartNode, beginSequenceAtStart, minLevel); | ||
} | ||
|
||
private void initialize(List<String> labelSequenceList, boolean filterStartNode, boolean beginSequenceAtStart, int minLevel) { | ||
this.filterStartNode = filterStartNode; | ||
this.beginSequenceAtStart = beginSequenceAtStart; | ||
this.minLevel = minLevel; | ||
sequenceMatchers = new ArrayList<>(labelSequenceList.size()); | ||
|
||
for (String labelFilterString : labelSequenceList) { | ||
LabelMatcherGroup matcherGroup = new LabelMatcherGroup().addLabels(labelFilterString.trim()); | ||
sequenceMatchers.add(matcherGroup); | ||
endNodesOnly = endNodesOnly || matcherGroup.isEndNodesOnly(); | ||
} | ||
|
||
// if true for one matcher, need to set true for all matchers | ||
if (endNodesOnly) { | ||
for (LabelMatcherGroup group : sequenceMatchers) { | ||
group.setEndNodesOnly(endNodesOnly); | ||
} | ||
} | ||
|
||
whitelistAllowedEvaluation = endNodesOnly ? EXCLUDE_AND_CONTINUE : INCLUDE_AND_CONTINUE; | ||
} | ||
|
||
@Override | ||
public Evaluation evaluate(Path path) { | ||
int depth = path.length(); | ||
Node node = path.endNode(); | ||
boolean belowMinLevel = depth < minLevel; | ||
|
||
// if start node shouldn't be filtered, exclude/include based on if using termination/endnode filter or not | ||
// minLevel evaluator will separately enforce exclusion if we're below minLevel | ||
if (depth == 0 && (!filterStartNode || !beginSequenceAtStart)) { | ||
return whitelistAllowedEvaluation; | ||
} | ||
|
||
// the user may want the sequence to begin at the start node (default), or the sequence may only apply from the next node on | ||
LabelMatcherGroup matcherGroup = sequenceMatchers.get((beginSequenceAtStart ? depth : depth - 1) % sequenceMatchers.size()); | ||
|
||
return matcherGroup.evaluate(node, belowMinLevel); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package apoc.path; | ||
|
||
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.graphdb.traversal.Evaluators; | ||
|
||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
|
||
/** | ||
* Static factory methods for obtaining node evaluators | ||
*/ | ||
public final class NodeEvaluators { | ||
// non-instantiable | ||
private NodeEvaluators() {}; | ||
|
||
/** | ||
* Returns an evaluator which handles end nodes and terminator nodes | ||
* Returns null if both lists are empty | ||
*/ | ||
public static Evaluator endAndTerminatorNodeEvaluator(List<Node> endNodes, List<Node> terminatorNodes) { | ||
Evaluator endNodeEvaluator = null; | ||
Evaluator terminatorNodeEvaluator = null; | ||
|
||
if (!endNodes.isEmpty()) { | ||
Node[] nodes = endNodes.toArray(new Node[endNodes.size()]); | ||
endNodeEvaluator = Evaluators.includeWhereEndNodeIs(nodes); | ||
} | ||
|
||
if (!terminatorNodes.isEmpty()) { | ||
Node[] nodes = terminatorNodes.toArray(new Node[terminatorNodes.size()]); | ||
terminatorNodeEvaluator = Evaluators.pruneWhereEndNodeIs(nodes); | ||
} | ||
|
||
if (endNodeEvaluator != null || terminatorNodeEvaluator != null) { | ||
return new EndAndTerminatorNodeEvaluator(endNodeEvaluator, terminatorNodeEvaluator); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public static Evaluator whitelistNodeEvaluator(List<Node> whitelistNodes) { | ||
return new WhitelistNodeEvaluator(whitelistNodes); | ||
} | ||
|
||
public static Evaluator blacklistNodeEvaluator(List<Node> blacklistNodes) { | ||
return new BlacklistNodeEvaluator(blacklistNodes); | ||
} | ||
|
||
// The evaluators from pruneWhereEndNodeIs and includeWhereEndNodeIs interfere with each other, this makes them play nice | ||
private static class EndAndTerminatorNodeEvaluator implements Evaluator { | ||
private Evaluator endNodeEvaluator; | ||
private Evaluator terminatorNodeEvaluator; | ||
|
||
public EndAndTerminatorNodeEvaluator(Evaluator endNodeEvaluator, Evaluator terminatorNodeEvaluator) { | ||
this.endNodeEvaluator = endNodeEvaluator; | ||
this.terminatorNodeEvaluator = terminatorNodeEvaluator; | ||
} | ||
|
||
@Override | ||
public Evaluation evaluate(Path path) { | ||
// at least one has to give a thumbs up to include | ||
boolean includes = evalIncludes(endNodeEvaluator, path) || evalIncludes(terminatorNodeEvaluator, path); | ||
// prune = terminatorNodeEvaluator != null && !terminatorNodeEvaluator.evaluate(path).continues() | ||
// negate this to get continues result | ||
boolean continues = terminatorNodeEvaluator == null || terminatorNodeEvaluator.evaluate(path).continues(); | ||
|
||
return Evaluation.of(includes, continues); | ||
} | ||
|
||
private boolean evalIncludes(Evaluator eval, Path path) { | ||
return eval != null && eval.evaluate(path).includes(); | ||
} | ||
} | ||
|
||
private static class BlacklistNodeEvaluator implements Evaluator { | ||
private Set<Node> blacklistSet; | ||
|
||
public BlacklistNodeEvaluator(List<Node> blacklistNodes) { | ||
blacklistSet = new HashSet<>(blacklistNodes); | ||
} | ||
|
||
@Override | ||
public Evaluation evaluate(Path path) { | ||
return blacklistSet.contains(path.endNode()) ? Evaluation.EXCLUDE_AND_PRUNE : Evaluation.INCLUDE_AND_CONTINUE; | ||
} | ||
} | ||
|
||
private static class WhitelistNodeEvaluator implements Evaluator { | ||
private Set<Node> whitelistSet; | ||
|
||
public WhitelistNodeEvaluator(List<Node> whitelistNodes) { | ||
whitelistSet = new HashSet<>(whitelistNodes); | ||
} | ||
|
||
@Override | ||
public Evaluation evaluate(Path path) { | ||
return whitelistSet.contains(path.endNode()) ? Evaluation.INCLUDE_AND_CONTINUE : Evaluation.EXCLUDE_AND_PRUNE; | ||
} | ||
} | ||
} |
Oops, something went wrong.