apoc.path.LabelMatcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of apoc-core Show documentation
Show all versions of apoc-core Show documentation
Core package for Neo4j Procedures
The newest version!
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apoc.path;
import java.util.*;
/**
* A generic label matcher which evaluates whether or not a node has at least one of the labels added on the matcher.
* String labels can be added on the matcher. The label can optionally be be prefixed with `:`.
* Also handles compound labels (multiple labels separated by `:`), and a node will be matched if it has all of the labels
* in a compound label (order does not matter).
* If the node only has a subset of the compound label, it will only be matched if that subset is in the matcher.
* For example, a LabelMatcher with only `Person:Manager` will only match on nodes with both :Person and :Manager, not just one or the other.
* Any other labels on the matched node would not be relevant and would not affect the match.
* If the LabelMatcher only had `Person:Manager` and `Person:Boss`, then only nodes with both :Person and :Manager, or :Person and :Boss, would match.
* Some nodes that would not match would be: :Person, :Boss, :Manager, :Boss:Manager, but :Boss:Person:HeadHoncho would match fine.
* Also accepts a special `*` label, indicating that the matcher will always return a positive match.
* LabelMatchers hold no context about what a match means, and do not handle labels prefixed with filter symbols (+, -, /, >).
* Please strip these symbols from the start of each label before adding to the matcher.
*/
public class LabelMatcher {
private List labels = new ArrayList<>();
private List> compoundLabels;
private static LabelMatcher ACCEPTS_ALL_LABEL_MATCHER = new LabelMatcher() {
@Override
public boolean matchesLabels(Set nodeLabels) {
return true;
}
@Override
public LabelMatcher addLabel(String label) {
return this; // no-op
}
@Override
public boolean isEmpty() {
return false;
}
};
public LabelMatcher addLabel(String label) {
if ("*".equals(label)) {
return ACCEPTS_ALL_LABEL_MATCHER;
}
if (label.charAt(0) == ':') {
label = label.substring(1);
}
// split any `:` char not preceded by `\`
String[] elements = label.split("(? 1) {
if (compoundLabels == null) {
compoundLabels = new ArrayList<>();
}
List elementsList =
Arrays.stream(elements).map(this::sanitizeLabel).toList();
compoundLabels.add(elementsList);
}
return this;
}
private String sanitizeLabel(String label) {
// from `\:` to `:`
return label.replaceAll("\\\\:", ":");
}
public boolean matchesLabels(Set nodeLabels) {
for (String label : labels) {
if (nodeLabels.contains(label)) {
return true;
}
}
if (compoundLabels != null) {
for (List compoundLabel : compoundLabels) {
if (nodeLabels.containsAll(compoundLabel)) {
return true;
}
}
}
return false;
}
public boolean isEmpty() {
return labels.isEmpty() && (compoundLabels == null || compoundLabels.isEmpty());
}
}