Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.
eg: test match these patten: test , te* ,tes* .

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,26 +59,15 @@ public FilterPathBasedFilter(Set<String> filters, boolean inclusive) {
*/
private TokenFilter evaluate(String name, FilterPath[] filterPaths) {
if (filterPaths != null) {
List<FilterPath> nextFilters = null;

List<FilterPath> nextFilters = new ArrayList<>();
for (FilterPath filter : filterPaths) {
FilterPath next = filter.matchProperty(name);
if (next != null) {
if (next.matches()) {
return MATCHING;
} else {
if (nextFilters == null) {
nextFilters = new ArrayList<>();
}
if (filter.isDoubleWildcard()) {
nextFilters.add(filter);
}
nextFilters.add(next);
}
boolean matches = filter.matches(name, nextFilters);
if (matches) {
return MATCHING;
}
}

if ((nextFilters != null) && (nextFilters.isEmpty() == false)) {
if (nextFilters.isEmpty() == false) {
return new FilterPathBasedFilter(nextFilters.toArray(new FilterPath[nextFilters.size()]), inclusive);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.test.ESTestCase;

import java.util.Collections;
import java.util.Arrays;
import java.util.stream.Collectors;

import static org.hamcrest.Matchers.equalTo;

Expand Down Expand Up @@ -67,6 +68,11 @@ public void testInclusiveFilters() throws Exception {
assertResult(SAMPLE, "**.l", true, "{'h':{'i':{'j':{'k':{'l':'l_value'}}}}}");

assertResult(SAMPLE, "**.*2", true, "{'e':[{'f2':'f2_value'},{'g2':'g2_value'}]}");

assertResult(SAMPLE, "h.i.j.k.l,h.i.j.k.l.m", true, "{'h':{'i':{'j':{'k':{'l':'l_value'}}}}}");
assertResult(SAMPLE, "a,b,c,d,e.f1,e.f2,e.g1,e.g2,h.i.j.k.l", true, SAMPLE);
assertResult(SAMPLE, "", true, "");
assertResult(SAMPLE, "h.", true, "");
}

public void testExclusiveFilters() throws Exception {
Expand Down Expand Up @@ -278,6 +284,16 @@ public void testExclusiveFilters() throws Exception {
+ "{'g1':'g1_value'}],'h':{'i':{'j':{'k':{'l':'l_value'}}}}}"
);

assertResult(
SAMPLE,
"h.i.j.k.l,h.i.j.k.l.m",
false,
"{'a':0,'b':true,'c':'c_value','d':[0,1,2],'e':[{'f1':'f1_value','f2':'f2_value'}," + "{'g1':'g1_value','g2':'g2_value'}]}"
);

assertResult(SAMPLE, "a,b,c,d,e.f1,e.f2,e.g1,e.g2,h.i.j.k.l", false, "");
assertResult(SAMPLE, "", false, SAMPLE);
assertResult(SAMPLE, "h.", false, SAMPLE);
}

public void testInclusiveFiltersWithDots() throws Exception {
Expand All @@ -295,7 +311,7 @@ private void assertResult(String input, String filter, boolean inclusive, String
try (
FilteringGeneratorDelegate generator = new FilteringGeneratorDelegate(
JSON_FACTORY.createGenerator(os),
new FilterPathBasedFilter(Collections.singleton(filter), inclusive),
new FilterPathBasedFilter(Arrays.asList(filter.split(",")).stream().collect(Collectors.toSet()), inclusive),
true,
true
)
Expand Down
Loading