-
Notifications
You must be signed in to change notification settings - Fork 25.8k
Improve FilterPathBasedFilter performance #79826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ea4ec7a
3a1b0e9
350db73
0fbc98d
a14d028
06981d3
d36f01b
2f0fefb
e1472ce
9575eb0
79b061d
364cdea
07d8dc7
b808311
22f5abb
e2b4ae5
c9a043a
a7253ff
46d26ef
8c06b8f
52d2d4c
de44a3a
c67afa8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,102 +11,183 @@ | |
| import org.elasticsearch.core.Glob; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
|
|
||
| public class FilterPath { | ||
|
|
||
| public static final FilterPath EMPTY = new FilterPath(); | ||
|
|
||
| private final String filter; | ||
| private final String segment; | ||
| private final FilterPath next; | ||
| private final boolean simpleWildcard; | ||
| private final boolean doubleWildcard; | ||
|
|
||
| protected FilterPath(String filter, String segment, FilterPath next) { | ||
| this.filter = filter; | ||
| this.segment = segment; | ||
| this.next = next; | ||
| this.simpleWildcard = (segment != null) && (segment.length() == 1) && (segment.charAt(0) == '*'); | ||
| this.doubleWildcard = (segment != null) && (segment.length() == 2) && (segment.charAt(0) == '*') && (segment.charAt(1) == '*'); | ||
| private static final String WILDCARD = "*"; | ||
| private static final String DOUBLE_WILDCARD = "**"; | ||
|
|
||
| private final Map<String, FilterPath> termsChildren; | ||
| private final FilterPath[] wildcardChildren; | ||
| private final String pattern; | ||
| private final boolean isDoubleWildcard; | ||
| private final boolean isFinalNode; | ||
|
|
||
| private FilterPath(String pattern, boolean isFinalNode, Map<String, FilterPath> termsChildren, FilterPath[] wildcardChildren) { | ||
| this.pattern = pattern; | ||
| this.isFinalNode = isFinalNode; | ||
| this.termsChildren = Collections.unmodifiableMap(termsChildren); | ||
| this.wildcardChildren = wildcardChildren; | ||
| this.isDoubleWildcard = pattern.equals(DOUBLE_WILDCARD); | ||
| } | ||
|
|
||
| private FilterPath() { | ||
| this("<empty>", "", null); | ||
| } | ||
|
|
||
| public FilterPath matchProperty(String name) { | ||
| if ((next != null) && (simpleWildcard || doubleWildcard || Glob.globMatch(segment, name))) { | ||
| return next; | ||
| public boolean hasDoubleWildcard() { | ||
| if (isDoubleWildcard || pattern.contains(DOUBLE_WILDCARD)) { | ||
| return true; | ||
| } | ||
| return null; | ||
| for (FilterPath filterPath : wildcardChildren) { | ||
| if (filterPath.hasDoubleWildcard()) { | ||
| return true; | ||
| } | ||
| } | ||
| for (FilterPath filterPath : termsChildren.values()) { | ||
| if (filterPath.hasDoubleWildcard()) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| public boolean matches() { | ||
| return next == null; | ||
| private String getPattern() { | ||
| return pattern; | ||
| } | ||
|
|
||
| public boolean isDoubleWildcard() { | ||
| return doubleWildcard; | ||
| private boolean isFinalNode() { | ||
| return isFinalNode; | ||
| } | ||
|
|
||
| public boolean hasDoubleWildcard() { | ||
| if (filter == null) { | ||
| /** | ||
| * check if the name matches filter nodes | ||
| * if the name equals the filter node name, the node will add to nextFilters. | ||
| * if the filter node is a final node, it means the name matches the pattern, and return true | ||
| * if the name don't equal a final node, then return false, continue to check the inner filter node | ||
| * if current node is a double wildcard node, the node will also add to nextFilters. | ||
| * @param name the xcontent property name | ||
| * @param nextFilters nextFilters is a List, used to check the inner property of name | ||
| * @return true if the name equal a final node, otherwise return false | ||
| */ | ||
| boolean matches(String name, List<FilterPath> nextFilters) { | ||
| if (nextFilters == null) { | ||
| return false; | ||
| } | ||
| return filter.indexOf("**") >= 0; | ||
| } | ||
|
|
||
| public boolean isSimpleWildcard() { | ||
| return simpleWildcard; | ||
| } | ||
| FilterPath termNode = termsChildren.get(name); | ||
| if (termNode != null) { | ||
| if (termNode.isFinalNode()) { | ||
| return true; | ||
| } else { | ||
| nextFilters.add(termNode); | ||
| } | ||
| } | ||
|
|
||
| for (FilterPath wildcardNode : wildcardChildren) { | ||
| String wildcardPattern = wildcardNode.getPattern(); | ||
| if (Glob.globMatch(wildcardPattern, name)) { | ||
| if (wildcardNode.isFinalNode()) { | ||
| return true; | ||
| } else { | ||
| nextFilters.add(wildcardNode); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public String getSegment() { | ||
| return segment; | ||
| if (isDoubleWildcard) { | ||
| nextFilters.add(this); | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| public FilterPath getNext() { | ||
| return next; | ||
| private static class FilterPathBuilder { | ||
| private class BuildNode { | ||
| private final Map<String, BuildNode> children; | ||
| private final boolean isFinalNode; | ||
|
|
||
| BuildNode(boolean isFinalNode) { | ||
| children = new HashMap<>(); | ||
| this.isFinalNode = isFinalNode; | ||
| } | ||
| } | ||
|
|
||
| private BuildNode root = new BuildNode(false); | ||
|
|
||
| void insert(String filter) { | ||
| insertNode(filter, root); | ||
| } | ||
|
|
||
| FilterPath build() { | ||
| return buildPath("", root); | ||
| } | ||
|
|
||
| void insertNode(String filter, BuildNode node) { | ||
| int end = filter.length(); | ||
| int splitPosition = -1; | ||
| boolean findEscapes = false; | ||
| for (int i = 0; i < end; i++) { | ||
| char c = filter.charAt(i); | ||
| if (c == '.') { | ||
| splitPosition = i; | ||
| break; | ||
| } else if ((c == '\\') && (i + 1 < end) && (filter.charAt(i + 1) == '.')) { | ||
| ++i; | ||
| findEscapes = true; | ||
| } | ||
| } | ||
|
|
||
| if (splitPosition > 0) { | ||
| String field = findEscapes | ||
| ? filter.substring(0, splitPosition).replaceAll("\\\\.", ".") | ||
| : filter.substring(0, splitPosition); | ||
| BuildNode child = node.children.get(field); | ||
| if (child == null) { | ||
| child = new BuildNode(false); | ||
| node.children.put(field, child); | ||
| } | ||
| if (false == child.isFinalNode) { | ||
| insertNode(filter.substring(splitPosition + 1), child); | ||
| } | ||
| } else { | ||
| String field = findEscapes ? filter.replaceAll("\\\\.", ".") : filter; | ||
| node.children.put(field, new BuildNode(true)); | ||
| } | ||
| } | ||
|
|
||
| FilterPath buildPath(String segment, BuildNode node) { | ||
| Map<String, FilterPath> termsChildren = new HashMap<>(); | ||
| List<FilterPath> wildcardChildren = new ArrayList<>(); | ||
| for (Map.Entry<String, BuildNode> entry : node.children.entrySet()) { | ||
| String childName = entry.getKey(); | ||
| BuildNode childNode = entry.getValue(); | ||
| FilterPath childFilterPath = buildPath(childName, childNode); | ||
| if (childName.contains(WILDCARD)) { | ||
| wildcardChildren.add(childFilterPath); | ||
| } else { | ||
| termsChildren.put(childName, childFilterPath); | ||
| } | ||
| } | ||
| return new FilterPath(segment, node.isFinalNode, termsChildren, wildcardChildren.toArray(new FilterPath[0])); | ||
| } | ||
| } | ||
|
|
||
| public static FilterPath[] compile(Set<String> filters) { | ||
| if (filters == null || filters.isEmpty()) { | ||
| return null; | ||
| } | ||
|
|
||
| List<FilterPath> paths = new ArrayList<>(); | ||
| FilterPathBuilder builder = new FilterPathBuilder(); | ||
| for (String filter : filters) { | ||
| if (filter != null) { | ||
| filter = filter.trim(); | ||
| if (filter.length() > 0) { | ||
| paths.add(parse(filter, filter)); | ||
| builder.insert(filter); | ||
| } | ||
| } | ||
| } | ||
| return paths.toArray(new FilterPath[paths.size()]); | ||
| } | ||
|
|
||
| private static FilterPath parse(final String filter, final String segment) { | ||
| int end = segment.length(); | ||
|
|
||
| for (int i = 0; i < end;) { | ||
| char c = segment.charAt(i); | ||
|
|
||
| if (c == '.') { | ||
| String current = segment.substring(0, i).replaceAll("\\\\.", "."); | ||
| return new FilterPath(filter, current, parse(filter, segment.substring(i + 1))); | ||
| } | ||
| ++i; | ||
| if ((c == '\\') && (i < end) && (segment.charAt(i) == '.')) { | ||
| ++i; | ||
| } | ||
| } | ||
| return new FilterPath(filter, segment.replaceAll("\\\\.", "."), EMPTY); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "FilterPath [filter=" + filter + ", segment=" + segment + "]"; | ||
| FilterPath filterPath = builder.build(); | ||
| return Collections.singletonList(filterPath).toArray(new FilterPath[0]); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's probably worth removing the array everywhere. But this is totally the kind of thing I'd do so I could test if my changes were having any effect on performance.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In FilterPathBasedFilter's evaluate method, It may be match more than one FilterPath. so FilterPathBasedFilter Construction method need an array. |
||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.