diff --git a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java index d634a2a4..44d28159 100755 --- a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java @@ -157,6 +157,94 @@ public class Criteria implements Predicate { return this; } + /** + * Creates a criterion using the <all; operator + * + * @param o + * @return the criteria + */ + public Criteria ltall(Object... o) { + return ltall(Arrays.asList(o)); + } + + /** + * Creates a criterion using the <all; operator + * + * @param c + * @return the criteria + */ + public Criteria ltall(Collection c) { + this.criteriaType = RelationalOperator.LTALL; + this.right = new ValueListNode(c); + return this; + } + + /** + * Creates a criterion using the <eall; operator + * + * @param o + * @return the criteria + */ + public Criteria lteall(Object... o) { + return lteall(Arrays.asList(o)); + } + + /** + * Creates a criterion using the <eall; operator + * + * @param c + * @return the criteria + */ + public Criteria lteall(Collection c) { + this.criteriaType = RelationalOperator.LTEALL; + this.right = new ValueListNode(c); + return this; + } + + /** + * Creates a criterion using the <any; operator + * + * @param o + * @return the criteria + */ + public Criteria ltany(Object... o) { + return ltany(Arrays.asList(o)); + } + + /** + * Creates a criterion using the <any; operator + * + * @param c + * @return the criteria + */ + public Criteria ltany(Collection c) { + this.criteriaType = RelationalOperator.LTANY; + this.right = new ValueListNode(c); + return this; + } + + /** + * Creates a criterion using the <eany; operator + * + * @param o + * @return the criteria + */ + public Criteria lteany(Object... o) { + return lteany(Arrays.asList(o)); + } + + /** + * Creates a criterion using the <eany; operator + * + * @param c + * @return the criteria + */ + public Criteria lteany(Collection c) { + this.criteriaType = RelationalOperator.LTEANY; + this.right = new ValueListNode(c); + return this; + } + /** * Creates a criterion using the <= operator * @@ -181,6 +269,94 @@ public class Criteria implements Predicate { return this; } + /** + * Creates a criterion using the >all; operator + * + * @param o + * @return the criteria + */ + public Criteria gtall(Object... o) { + return gtall(Arrays.asList(o)); + } + + /** + * Creates a criterion using the >all; operator + * + * @param c + * @return the criteria + */ + public Criteria gtall(Collection c) { + this.criteriaType = RelationalOperator.GTALL; + this.right = new ValueListNode(c); + return this; + } + + /** + * Creates a criterion using the >eall; operator + * + * @param o + * @return the criteria + */ + public Criteria gteall(Object... o) { + return gteall(Arrays.asList(o)); + } + + /** + * Creates a criterion using the >eall; operator + * + * @param c + * @return the criteria + */ + public Criteria gteall(Collection c) { + this.criteriaType = RelationalOperator.GTEALL; + this.right = new ValueListNode(c); + return this; + } + + /** + * Creates a criterion using the >any; operator + * + * @param o + * @return the criteria + */ + public Criteria gtany(Object... o) { + return gtany(Arrays.asList(o)); + } + + /** + * Creates a criterion using the >any; operator + * + * @param c + * @return the criteria + */ + public Criteria gtany(Collection c) { + this.criteriaType = RelationalOperator.GTANY; + this.right = new ValueListNode(c); + return this; + } + + /** + * Creates a criterion using the >eany; operator + * + * @param o + * @return the criteria + */ + public Criteria gteany(Object... o) { + return gteany(Arrays.asList(o)); + } + + /** + * Creates a criterion using the >eany; operator + * + * @param c + * @return the criteria + */ + public Criteria gteany(Collection c) { + this.criteriaType = RelationalOperator.GTEANY; + this.right = new ValueListNode(c); + return this; + } + /** * Creates a criterion using the >= operator * @@ -304,7 +480,7 @@ public class Criteria implements Predicate { * @return the criteria */ public Criteria anyof(Object... o) { - return subsetof(Arrays.asList(o)); + return anyof(Arrays.asList(o)); } /** @@ -371,6 +547,371 @@ public class Criteria implements Predicate { return this; } + /** + * Creates a criterion using the &dateeq; operator + * + * @param o + * @return the criteria + */ + public Criteria dateeq(Object o) { + this.criteriaType = RelationalOperator.DATEEQ; + this.right = ValueNode.toValueNode(o); + return this; + } + + /** + * Creates a criterion using the &dayeq; operator + * + * @param o + * @return the criteria + */ + public Criteria dayeq(Object o) { + this.criteriaType = RelationalOperator.DAYEQ; + this.right = ValueNode.toValueNode(o); + return this; + } + + /** + * Creates a criterion using the &montheq; operator + * + * @param o + * @return the criteria + */ + public Criteria montheq(Object o) { + this.criteriaType = RelationalOperator.MONTHEQ; + this.right = ValueNode.toValueNode(o); + return this; + } + + /** + * Creates a criterion using the &yeareq; operator + * + * @param o + * @return the criteria + */ + public Criteria yeareq(Object o) { + this.criteriaType = RelationalOperator.YEAREQ; + this.right = ValueNode.toValueNode(o); + return this; + } + + /** + * Creates a criterion using the &dayin; operator + * + * @param o + * @return the criteria + */ + public Criteria dayin(Object... o) { + return dayin(Arrays.asList(o)); + } + + /** + * Creates a criterion using the &dayin; operator + * + * @param c + * @return the criteria + */ + public Criteria dayin(Collection c) { + this.criteriaType = RelationalOperator.DAYIN; + this.right = new ValueListNode(c); + return this; + } + + /** + * Creates a criterion using the &monthin; operator + * + * @param o + * @return the criteria + */ + public Criteria monthin(Object... o) { + return monthin(Arrays.asList(o)); + } + + /** + * Creates a criterion using the &monthin; operator + * + * @param c + * @return the criteria + */ + public Criteria monthin(Collection c) { + this.criteriaType = RelationalOperator.MONTHIN; + this.right = new ValueListNode(c); + return this; + } + + /** + * Creates a criterion using the &yearin; operator + * + * @param o + * @return the criteria + */ + public Criteria yearin(Object... o) { + return yearin(Arrays.asList(o)); + } + + /** + * Creates a criterion using the &yearin; operator + * + * @param c + * @return the criteria + */ + public Criteria yearin(Collection c) { + this.criteriaType = RelationalOperator.YEARIN; + this.right = new ValueListNode(c); + return this; + } + + /** + * Creates a criterion using the &before; operator + * + * @param o + * @return the criteria + */ + public Criteria before(Object o) { + this.criteriaType = RelationalOperator.BEFORE; + this.right = ValueNode.toValueNode(o); + return this; + } + + /** + * Creates a criterion using the &after; operator + * + * @param o + * @return the criteria + */ + public Criteria after(Object o) { + this.criteriaType = RelationalOperator.AFTER; + this.right = ValueNode.toValueNode(o); + return this; + } + + /** + * Creates a criterion using the &houreq; operator + * + * @param o + * @return the criteria + */ + public Criteria houreq(Object o) { + this.criteriaType = RelationalOperator.HOUREQ; + this.right = ValueNode.toValueNode(o); + return this; + } + + /** + * Creates a criterion using the &hourin; operator + * + * @param o + * @return the criteria + */ + public Criteria hourin(Object... o) { + return hourin(Arrays.asList(o)); + } + + /** + * Creates a criterion using the &hourin; operator + * + * @param c + * @return the criteria + */ + public Criteria hourin(Collection c) { + this.criteriaType = RelationalOperator.HOURIN; + this.right = new ValueListNode(c); + return this; + } + + /** + * Creates a criterion using the &timebefore; operator + * + * @param o + * @return the criteria + */ + public Criteria timebefore(Object o) { + this.criteriaType = RelationalOperator.TIMEBEFORE; + this.right = ValueNode.toValueNode(o); + return this; + } + + /** + * Creates a criterion using the &timeafter; operator + * + * @param o + * @return the criteria + */ + public Criteria timeafter(Object o) { + this.criteriaType = RelationalOperator.TIMEAFTER; + this.right = ValueNode.toValueNode(o); + return this; + } + + /** + * The allmatch operator selects objects for which the specified field is + * an array whose elements comprise a subset of the set comprised by the elements of + * the specified array. + * + * @param o the values to match against + * @return the criteria + */ + public Criteria allmatch(Object... o) { + return allmatch(Arrays.asList(o)); + } + + /** + * The allmatch operator selects objects for which the specified field is + * an array whose elements comprise a subset of the set comprised by the elements of + * the specified array. + * + * @param c the values to match against + * @return the criteria + */ + public Criteria allmatch(Collection c) { + notNull(c, "collection can not be null"); + this.criteriaType = RelationalOperator.ALLMATCH; + this.right = new ValueListNode(c); + return this; + } + + /** + * The anymatch operator selects objects for which the specified field is + * an array whose elements comprise a subset of the set comprised by the elements of + * the specified array. + * + * @param o the values to match against + * @return the criteria + */ + public Criteria anymatch(Object... o) { + return anymatch(Arrays.asList(o)); + } + + /** + * The anymatch operator selects objects for which the specified field is + * an array whose elements comprise a subset of the set comprised by the elements of + * the specified array. + * + * @param c the values to match against + * @return the criteria + */ + public Criteria anymatch(Collection c) { + notNull(c, "collection can not be null"); + this.criteriaType = RelationalOperator.ANYMATCH; + this.right = new ValueListNode(c); + return this; + } + + /** + * The nonematch operator selects objects for which the specified field is + * an array whose elements comprise a subset of the set comprised by the elements of + * the specified array. + * + * @param o the values to match against + * @return the criteria + */ + public Criteria nonematch(Object... o) { + return nonematch(Arrays.asList(o)); + } + + /** + * The nonematch operator selects objects for which the specified field is + * an array whose elements comprise a subset of the set comprised by the elements of + * the specified array. + * + * @param c the values to match against + * @return the criteria + */ + public Criteria nonematch(Collection c) { + notNull(c, "collection can not be null"); + this.criteriaType = RelationalOperator.NONEMATCH; + this.right = new ValueListNode(c); + return this; + } + + /** + * The exactmatch operator selects objects for which the specified field is + * an array whose elements comprise a subset of the set comprised by the elements of + * the specified array. + * + * @param o the values to match against + * @return the criteria + */ + public Criteria exactmatch(Object... o) { + return exactmatch(Arrays.asList(o)); + } + + /** + * The exactmatch operator selects objects for which the specified field is + * an array whose elements comprise a subset of the set comprised by the elements of + * the specified array. + * + * @param c the values to match against + * @return the criteria + */ + public Criteria exactmatch(Collection c) { + notNull(c, "collection can not be null"); + this.criteriaType = RelationalOperator.EXACTMATCH; + this.right = new ValueListNode(c); + return this; + } + + /** + * Creates a criterion using the &windowin; operator + * + * @param o + * @return the criteria + */ + public Criteria windowin(Object o) { + this.criteriaType = RelationalOperator.WINDOWIN; + this.right = ValueNode.toValueNode(o); + return this; + } + + /** + * Creates a criterion using the &windowout; operator + * + * @param o + * @return the criteria + */ + public Criteria windowout(Object o) { + this.criteriaType = RelationalOperator.WINDOWOUT; + this.right = ValueNode.toValueNode(o); + return this; + } + + /** + * Creates a criterion using the &windowtimein; operator + * + * @param o + * @return the criteria + */ + public Criteria windowtimein(Object o) { + this.criteriaType = RelationalOperator.WINDOWTIMEIN; + this.right = ValueNode.toValueNode(o); + return this; + } + + /** + * Creates a criterion using the &windowtimeout; operator + * + * @param o + * @return the criteria + */ + public Criteria windowtimeout(Object o) { + this.criteriaType = RelationalOperator.WINDOWTIMEOUT; + this.right = ValueNode.toValueNode(o); + return this; + } + + /** + * The notcontains operator asserts that the provided object is not contained + * in the result. The object that should contain the input can be either an object or a String. + * + * @param o that should not exists in given collection or + * @return the criteria + */ + public Criteria notcontains(Object o) { + this.criteriaType = RelationalOperator.NOTCONTAINS; + this.right = ValueNode.toValueNode(o); + return this; + } + /** * The size operator matches: *

diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java index 0ee59c72..040cf8ac 100755 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java @@ -1,8 +1,16 @@ package com.jayway.jsonpath.internal.filter; +import com.jayway.jsonpath.InvalidPathException; import com.jayway.jsonpath.JsonPathException; import com.jayway.jsonpath.Predicate; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.Map; @@ -35,16 +43,46 @@ public class EvaluatorFactory { evaluators.put(RelationalOperator.SUBSETOF, new SubsetOfEvaluator()); evaluators.put(RelationalOperator.ANYOF, new AnyOfEvaluator()); evaluators.put(RelationalOperator.NONEOF, new NoneOfEvaluator()); + evaluators.put(RelationalOperator.GTALL, new GreaterThanAllEvaluator()); + evaluators.put(RelationalOperator.GTEALL, new GreaterThanEqualsAllEvaluator()); + evaluators.put(RelationalOperator.GTANY, new GreaterThanAnyEvaluator()); + evaluators.put(RelationalOperator.GTEANY, new GreaterThanEqualsAnyEvaluator()); + evaluators.put(RelationalOperator.LTALL, new LessThanAllEvaluator()); + evaluators.put(RelationalOperator.LTEALL, new LessThanEqualsAllEvaluator()); + evaluators.put(RelationalOperator.LTANY, new LessThanAnyEvaluator()); + evaluators.put(RelationalOperator.LTEANY, new LessThanEqualsAnyEvaluator()); + evaluators.put(RelationalOperator.DATEEQ, new DateMatchEvaluator()); + evaluators.put(RelationalOperator.DAYEQ, new DayMatchEvaluator()); + evaluators.put(RelationalOperator.DAYIN, new DayInEvaluator()); + evaluators.put(RelationalOperator.MONTHEQ, new MonthMatchEvaluator()); + evaluators.put(RelationalOperator.MONTHIN, new MonthInEvaluator()); + evaluators.put(RelationalOperator.YEAREQ, new YearMatchEvaluator()); + evaluators.put(RelationalOperator.YEARIN, new YearInEvaluator()); + evaluators.put(RelationalOperator.BEFORE, new BeforeEvaluator()); + evaluators.put(RelationalOperator.AFTER, new AfterEvaluator()); + evaluators.put(RelationalOperator.HOUREQ, new HourMatchEvaluator()); + evaluators.put(RelationalOperator.HOURIN, new HourInEvaluator()); + evaluators.put(RelationalOperator.TIMEBEFORE, new TimeBeforeEvaluator()); + evaluators.put(RelationalOperator.TIMEAFTER, new TimeAfterEvaluator()); + evaluators.put(RelationalOperator.NOTCONTAINS, new NotContainsEvaluator()); + evaluators.put(RelationalOperator.ALLMATCH, new AllMatchEvaluator()); + evaluators.put(RelationalOperator.ANYMATCH, new AnyMatchEvaluator()); + evaluators.put(RelationalOperator.NONEMATCH, new NoneMatchEvaluator()); + evaluators.put(RelationalOperator.EXACTMATCH, new ExactMatchEvaluator()); + evaluators.put(RelationalOperator.WINDOWIN, new WindowInEvaluator()); + evaluators.put(RelationalOperator.WINDOWOUT, new WindowOutEvaluator()); + evaluators.put(RelationalOperator.WINDOWTIMEIN, new WindowTimeInEvaluator()); + evaluators.put(RelationalOperator.WINDOWTIMEOUT, new WindowTimeOutEvaluator()); } - public static Evaluator createEvaluator(RelationalOperator operator){ + public static Evaluator createEvaluator(RelationalOperator operator) { return evaluators.get(operator); } private static class ExistsEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - if(!left.isBooleanNode() && !right.isBooleanNode()){ + if (!left.isBooleanNode() && !right.isBooleanNode()) { throw new JsonPathException("Failed to evaluate exists expression"); } return left.asBooleanNode().getBoolean() == right.asBooleanNode().getBoolean(); @@ -68,7 +106,7 @@ public class EvaluatorFactory { private static class EqualsEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - if(left.isJsonNode() && right.isJsonNode()){ + if (left.isJsonNode() && right.isJsonNode()) { return left.asJsonNode().equals(right.asJsonNode(), ctx); } else { return left.equals(right); @@ -79,7 +117,7 @@ public class EvaluatorFactory { private static class TypeSafeEqualsEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - if(!left.getClass().equals(right.getClass())){ + if (!left.getClass().equals(right.getClass())) { return false; } return evaluators.get(RelationalOperator.EQ).evaluate(left, right, ctx); @@ -96,70 +134,378 @@ public class EvaluatorFactory { private static class LessThanEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - if(left.isNumberNode() && right.isNumberNode()){ + if (left.isNumberNode() && right.isNumberNode()) { return left.asNumberNode().getNumber().compareTo(right.asNumberNode().getNumber()) < 0; - } if(left.isStringNode() && right.isStringNode()){ + } + if (left.isStringNode() && right.isStringNode()) { return left.asStringNode().getString().compareTo(right.asStringNode().getString()) < 0; - } if (left.isOffsetDateTimeNode() && right.isOffsetDateTimeNode()){ //workaround for issue: https://github.com/json-path/JsonPath/issues/613 + } + if (left.isOffsetDateTimeNode() && right.isOffsetDateTimeNode()) { //workaround for issue: https://github.com/json-path/JsonPath/issues/613 return left.asOffsetDateTimeNode().getDate().compareTo(right.asOffsetDateTimeNode().getDate()) < 0; } return false; } } + private static class LessThanAllEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueListNode leftListNode = left.isValueListNode() ? left.asValueListNode() : null; + ValueListNode rightListNode = right.isValueListNode() ? right.asValueListNode() : null; + if (leftListNode == null) { + leftListNode = convertJsonNodeToListNode(left, ctx); + } + if (rightListNode == null) { + rightListNode = convertJsonNodeToListNode(right, ctx); + } + if (leftListNode == null || rightListNode == null) { + return false; + } + if (leftListNode.getNodes().get(0).isNumberNode() && rightListNode.getNodes().get(0).isNumberNode()) { + BigDecimal smallest = leftListNode.getNodes().stream().map(node -> + node.asNumberNode().getNumber()).min(Comparator.naturalOrder()).get(); + return rightListNode.getNodes().stream().allMatch(node -> node.asNumberNode() + .getNumber().compareTo(smallest) < 0); + } + return false; + } + + private ValueListNode convertJsonNodeToListNode(ValueNode node, Predicate.PredicateContext ctx) { + ValueListNode valueListNode = null; + ValueNode vn = node.isNumberNode() || node.isStringNode() ? + new ValueListNode(Collections.singleton(node.asNumberNode())) : + node.asJsonNode().asValueListNode(ctx); + if (!vn.isUndefinedNode()) { + valueListNode = vn.asValueListNode(); + } + return valueListNode; + } + } + + private static class LessThanAnyEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueListNode leftListNode = left.isValueListNode() ? left.asValueListNode() : null; + ValueListNode rightListNode = right.isValueListNode() ? right.asValueListNode() : null; + if (leftListNode == null) { + leftListNode = convertJsonNodeToListNode(left, ctx); + } + if (rightListNode == null) { + rightListNode = convertJsonNodeToListNode(right, ctx); + } + if (leftListNode == null || rightListNode == null) { + return false; + } + if (leftListNode.getNodes().get(0).isNumberNode() && rightListNode.getNodes().get(0).isNumberNode()) { + boolean res = false; + for (ValueNode field : leftListNode.getNodes()) { + for (ValueNode value : rightListNode.getNodes()) { + if (field.asNumberNode().getNumber().compareTo(value.asNumberNode().getNumber()) < 0) { + res = true; + break; + } + } + } + return res; + } + return false; + } + + private ValueListNode convertJsonNodeToListNode(ValueNode node, Predicate.PredicateContext ctx) { + ValueListNode valueListNode = null; + ValueNode vn = node.isNumberNode() || node.isStringNode() ? + new ValueListNode(Collections.singleton(node.asNumberNode())) : + node.asJsonNode().asValueListNode(ctx); + if (!vn.isUndefinedNode()) { + valueListNode = vn.asValueListNode(); + } + return valueListNode; + } + } + private static class LessThanEqualsEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - if(left.isNumberNode() && right.isNumberNode()){ + if (left.isNumberNode() && right.isNumberNode()) { return left.asNumberNode().getNumber().compareTo(right.asNumberNode().getNumber()) <= 0; - } if(left.isStringNode() && right.isStringNode()){ + } + if (left.isStringNode() && right.isStringNode()) { return left.asStringNode().getString().compareTo(right.asStringNode().getString()) <= 0; - } if (left.isOffsetDateTimeNode() && right.isOffsetDateTimeNode()){ //workaround for issue: https://github.com/json-path/JsonPath/issues/613 + } + if (left.isOffsetDateTimeNode() && right.isOffsetDateTimeNode()) { //workaround for issue: https://github.com/json-path/JsonPath/issues/613 return left.asOffsetDateTimeNode().getDate().compareTo(right.asOffsetDateTimeNode().getDate()) <= 0; } return false; } } + private static class LessThanEqualsAllEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueListNode leftListNode = left.isValueListNode() ? left.asValueListNode() : null; + ValueListNode rightListNode = right.isValueListNode() ? right.asValueListNode() : null; + if (leftListNode == null) { + leftListNode = convertJsonNodeToListNode(left, ctx); + } + if (rightListNode == null) { + rightListNode = convertJsonNodeToListNode(right, ctx); + } + if (leftListNode == null || rightListNode == null) { + return false; + } + if (leftListNode.getNodes().get(0).isNumberNode() && rightListNode.getNodes().get(0).isNumberNode()) { + BigDecimal smallest = leftListNode.getNodes().stream().map(node -> + node.asNumberNode().getNumber()).min(Comparator.naturalOrder()).get(); + return rightListNode.getNodes().stream().allMatch(node -> node.asNumberNode() + .getNumber().compareTo(smallest) <= 0); + } + return false; + } + + private ValueListNode convertJsonNodeToListNode(ValueNode node, Predicate.PredicateContext ctx) { + ValueListNode valueListNode = null; + ValueNode vn = node.isNumberNode() || node.isStringNode() ? + new ValueListNode(Collections.singleton(node.asNumberNode())) : + node.asJsonNode().asValueListNode(ctx); + if (!vn.isUndefinedNode()) { + valueListNode = vn.asValueListNode(); + } + return valueListNode; + } + } + + private static class LessThanEqualsAnyEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueListNode leftListNode = left.isValueListNode() ? left.asValueListNode() : null; + ValueListNode rightListNode = right.isValueListNode() ? right.asValueListNode() : null; + if (leftListNode == null) { + leftListNode = convertJsonNodeToListNode(left, ctx); + } + if (rightListNode == null) { + rightListNode = convertJsonNodeToListNode(right, ctx); + } + if (leftListNode == null || rightListNode == null) { + return false; + } + if (leftListNode.getNodes().get(0).isNumberNode() && rightListNode.getNodes().get(0).isNumberNode()) { + boolean res = false; + for (ValueNode field : leftListNode.getNodes()) { + for (ValueNode value : rightListNode.getNodes()) { + if (field.asNumberNode().getNumber().compareTo(value.asNumberNode().getNumber()) <= 0) { + res = true; + break; + } + } + } + return res; + } + return false; + } + + private ValueListNode convertJsonNodeToListNode(ValueNode node, Predicate.PredicateContext ctx) { + ValueListNode valueListNode = null; + ValueNode vn = node.isNumberNode() || node.isStringNode() ? + new ValueListNode(Collections.singleton(node.asNumberNode())) : + node.asJsonNode().asValueListNode(ctx); + if (!vn.isUndefinedNode()) { + valueListNode = vn.asValueListNode(); + } + return valueListNode; + } + } + private static class GreaterThanEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - if(left.isNumberNode() && right.isNumberNode()){ + if (left.isNumberNode() && right.isNumberNode()) { return left.asNumberNode().getNumber().compareTo(right.asNumberNode().getNumber()) > 0; - } else if(left.isStringNode() && right.isStringNode()){ + } else if (left.isStringNode() && right.isStringNode()) { return left.asStringNode().getString().compareTo(right.asStringNode().getString()) > 0; - } else if (left.isOffsetDateTimeNode() && right.isOffsetDateTimeNode()){ //workaround for issue: https://github.com/json-path/JsonPath/issues/613 + } else if (left.isOffsetDateTimeNode() && right.isOffsetDateTimeNode()) { //workaround for issue: https://github.com/json-path/JsonPath/issues/613 return left.asOffsetDateTimeNode().getDate().compareTo(right.asOffsetDateTimeNode().getDate()) > 0; } return false; } } + private static class GreaterThanAllEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueListNode leftListNode = left.isValueListNode() ? left.asValueListNode() : null; + ValueListNode rightListNode = right.isValueListNode() ? right.asValueListNode() : null; + if (leftListNode == null) { + leftListNode = convertJsonNodeToListNode(left, ctx); + } + if (rightListNode == null) { + rightListNode = convertJsonNodeToListNode(right, ctx); + } + if (leftListNode == null || rightListNode == null) { + return false; + } + if (leftListNode.getNodes().get(0).isNumberNode() && rightListNode.getNodes().get(0).isNumberNode()) { + BigDecimal largest = leftListNode.getNodes().stream().map(node -> + node.asNumberNode().getNumber()).max(Comparator.naturalOrder()).get(); + return rightListNode.getNodes().stream().allMatch(node -> node.asNumberNode() + .getNumber().compareTo(largest) < 0); + } + return false; + } + + private ValueListNode convertJsonNodeToListNode(ValueNode node, Predicate.PredicateContext ctx) { + ValueListNode valueListNode = null; + ValueNode vn = node.isNumberNode() || node.isStringNode() ? + new ValueListNode(Collections.singleton(node.asNumberNode())) : + node.asJsonNode().asValueListNode(ctx); + if (!vn.isUndefinedNode()) { + valueListNode = vn.asValueListNode(); + } + return valueListNode; + } + } + + private static class GreaterThanAnyEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueListNode leftListNode = left.isValueListNode() ? left.asValueListNode() : null; + ValueListNode rightListNode = right.isValueListNode() ? right.asValueListNode() : null; + if (leftListNode == null) { + leftListNode = convertJsonNodeToListNode(left, ctx); + } + if (rightListNode == null) { + rightListNode = convertJsonNodeToListNode(right, ctx); + } + if (leftListNode == null || rightListNode == null) { + return false; + } + if (leftListNode.getNodes().get(0).isNumberNode() && rightListNode.getNodes().get(0).isNumberNode()) { + boolean res = false; + for (ValueNode field : leftListNode.getNodes()) { + for (ValueNode value : rightListNode.getNodes()) { + if (field.asNumberNode().getNumber().compareTo(value.asNumberNode().getNumber()) > 0) { + res = true; + break; + } + } + } + return res; + } + return false; + } + + private ValueListNode convertJsonNodeToListNode(ValueNode node, Predicate.PredicateContext ctx) { + ValueListNode valueListNode = null; + ValueNode vn = node.isNumberNode() || node.isStringNode() ? + new ValueListNode(Collections.singleton(node.asNumberNode())) : + node.asJsonNode().asValueListNode(ctx); + if (!vn.isUndefinedNode()) { + valueListNode = vn.asValueListNode(); + } + return valueListNode; + } + } + private static class GreaterThanEqualsEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - if(left.isNumberNode() && right.isNumberNode()){ + if (left.isNumberNode() && right.isNumberNode()) { return left.asNumberNode().getNumber().compareTo(right.asNumberNode().getNumber()) >= 0; - } else if(left.isStringNode() && right.isStringNode()){ + } else if (left.isStringNode() && right.isStringNode()) { return left.asStringNode().getString().compareTo(right.asStringNode().getString()) >= 0; - } else if (left.isOffsetDateTimeNode() && right.isOffsetDateTimeNode()){ //workaround for issue: https://github.com/json-path/JsonPath/issues/613 + } else if (left.isOffsetDateTimeNode() && right.isOffsetDateTimeNode()) { //workaround for issue: https://github.com/json-path/JsonPath/issues/613 return left.asOffsetDateTimeNode().getDate().compareTo(right.asOffsetDateTimeNode().getDate()) >= 0; } return false; } } + private static class GreaterThanEqualsAllEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueListNode leftListNode = left.isValueListNode() ? left.asValueListNode() : null; + ValueListNode rightListNode = right.isValueListNode() ? right.asValueListNode() : null; + if (leftListNode == null) { + leftListNode = convertJsonNodeToListNode(left, ctx); + } + if (rightListNode == null) { + rightListNode = convertJsonNodeToListNode(right, ctx); + } + if (leftListNode == null || rightListNode == null) { + return false; + } + if (leftListNode.getNodes().get(0).isNumberNode() && rightListNode.getNodes().get(0).isNumberNode()) { + BigDecimal largest = leftListNode.getNodes().stream().map(node -> + node.asNumberNode().getNumber()).max(Comparator.naturalOrder()).get(); + return rightListNode.getNodes().stream().allMatch(node -> node.asNumberNode() + .getNumber().compareTo(largest) <= 0); + } + return false; + } + + private ValueListNode convertJsonNodeToListNode(ValueNode node, Predicate.PredicateContext ctx) { + ValueListNode valueListNode = null; + ValueNode vn = node.isNumberNode() || node.isStringNode() ? + new ValueListNode(Collections.singleton(node.asNumberNode())) : + node.asJsonNode().asValueListNode(ctx); + if (!vn.isUndefinedNode()) { + valueListNode = vn.asValueListNode(); + } + return valueListNode; + } + } + + private static class GreaterThanEqualsAnyEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueListNode leftListNode = left.isValueListNode() ? left.asValueListNode() : null; + ValueListNode rightListNode = right.isValueListNode() ? right.asValueListNode() : null; + if (leftListNode == null) { + leftListNode = convertJsonNodeToListNode(left, ctx); + } + if (rightListNode == null) { + rightListNode = convertJsonNodeToListNode(right, ctx); + } + if (leftListNode == null || rightListNode == null) { + return false; + } + if (leftListNode.getNodes().get(0).isNumberNode() && rightListNode.getNodes().get(0).isNumberNode()) { + boolean res = false; + for (ValueNode field : leftListNode.getNodes()) { + for (ValueNode value : rightListNode.getNodes()) { + if (field.asNumberNode().getNumber().compareTo(value.asNumberNode().getNumber()) >= 0) { + res = true; + break; + } + } + } + return res; + } + return false; + } + + private ValueListNode convertJsonNodeToListNode(ValueNode node, Predicate.PredicateContext ctx) { + ValueListNode valueListNode = null; + ValueNode vn = node.isNumberNode() || node.isStringNode() ? + new ValueListNode(Collections.singleton(node.asNumberNode())) : + node.asJsonNode().asValueListNode(ctx); + if (!vn.isUndefinedNode()) { + valueListNode = vn.asValueListNode(); + } + return valueListNode; + } + } + private static class SizeEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - if (! right.isNumberNode()) { + if (!right.isNumberNode()) { return false; } int expectedSize = right.asNumberNode().getNumber().intValue(); - if(left.isStringNode()){ + if (left.isStringNode()) { return left.asStringNode().length() == expectedSize; - } else if(left.isJsonNode()){ + } else if (left.isJsonNode()) { return left.asJsonNode().length(ctx) == expectedSize; } return false; @@ -169,9 +515,9 @@ public class EvaluatorFactory { private static class EmptyEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - if(left.isStringNode()){ + if (left.isStringNode()) { return left.asStringNode().isEmpty() == right.asBooleanNode().getBoolean(); - } else if(left.isJsonNode()){ + } else if (left.isJsonNode()) { return left.asJsonNode().isEmpty(ctx) == right.asBooleanNode().getBoolean(); } return false; @@ -182,9 +528,9 @@ public class EvaluatorFactory { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { ValueListNode valueListNode; - if(right.isJsonNode()){ + if (right.isJsonNode()) { ValueNode vn = right.asJsonNode().asValueListNode(ctx); - if(vn.isUndefinedNode()){ + if (vn.isUndefinedNode()) { return false; } else { valueListNode = vn.asValueListNode(); @@ -208,12 +554,12 @@ public class EvaluatorFactory { public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { ValueListNode requiredValues = right.asValueListNode(); - if(left.isJsonNode()){ + if (left.isJsonNode()) { ValueNode valueNode = left.asJsonNode().asValueListNode(ctx); //returns UndefinedNode if conversion is not possible - if(valueNode.isValueListNode()){ + if (valueNode.isValueListNode()) { ValueListNode shouldContainAll = valueNode.asValueListNode(); for (ValueNode required : requiredValues) { - if(!shouldContainAll.contains(required)){ + if (!shouldContainAll.contains(required)) { return false; } } @@ -227,14 +573,29 @@ public class EvaluatorFactory { private static class ContainsEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - if(left.isStringNode() && right.isStringNode()){ + if (left.isStringNode() && right.isStringNode()) { return left.asStringNode().contains(right.asStringNode().getString()); - } else if(left.isJsonNode()){ + } else if (left.isJsonNode()) { + ValueNode valueNode = left.asJsonNode().asValueListNode(ctx); + if (valueNode.isUndefinedNode()) return false; + else { + return valueNode.asValueListNode().contains(right); + } + } + return false; + } + } + + private static class NotContainsEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + if (left.isStringNode() && right.isStringNode()) { + return !(left.asStringNode().contains(right.asStringNode().getString())); + } else if (left.isJsonNode()) { ValueNode valueNode = left.asJsonNode().asValueListNode(ctx); - if(valueNode.isUndefinedNode()) return false; + if (valueNode.isUndefinedNode()) return false; else { - boolean res = valueNode.asValueListNode().contains(right); - return res; + return !(valueNode.asValueListNode().contains(right)); } } return false; @@ -251,7 +612,7 @@ public class EvaluatorFactory { private static class RegexpEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - if(!(left.isPatternNode() ^ right.isPatternNode())){ + if (!(left.isPatternNode() ^ right.isPatternNode())) { return false; } @@ -280,33 +641,33 @@ public class EvaluatorFactory { } private static class SubsetOfEvaluator implements Evaluator { - @Override - public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - ValueListNode rightValueListNode; - if(right.isJsonNode()){ - ValueNode vn = right.asJsonNode().asValueListNode(ctx); - if(vn.isUndefinedNode()){ - return false; - } else { - rightValueListNode = vn.asValueListNode(); - } - } else { - rightValueListNode = right.asValueListNode(); - } - ValueListNode leftValueListNode; - if(left.isJsonNode()){ - ValueNode vn = left.asJsonNode().asValueListNode(ctx); - if(vn.isUndefinedNode()){ - return false; - } else { - leftValueListNode = vn.asValueListNode(); - } - } else { - leftValueListNode = left.asValueListNode(); - } - return leftValueListNode.subsetof(rightValueListNode); - } - } + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueListNode rightValueListNode; + if (right.isJsonNode()) { + ValueNode vn = right.asJsonNode().asValueListNode(ctx); + if (vn.isUndefinedNode()) { + return false; + } else { + rightValueListNode = vn.asValueListNode(); + } + } else { + rightValueListNode = right.asValueListNode(); + } + ValueListNode leftValueListNode; + if (left.isJsonNode()) { + ValueNode vn = left.asJsonNode().asValueListNode(ctx); + if (vn.isUndefinedNode()) { + return false; + } else { + leftValueListNode = vn.asValueListNode(); + } + } else { + leftValueListNode = left.asValueListNode(); + } + return leftValueListNode.subsetof(rightValueListNode); + } + } private static class AnyOfEvaluator implements Evaluator { @Override @@ -381,4 +742,354 @@ public class EvaluatorFactory { return true; } } + + private static class DateMatchEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ZonedDateTime compDate = left.asDateNode().getDate(); + ZonedDateTime evalDate = right.asDateNode().getDate().withZoneSameLocal(compDate.getZone()); + return compDate.toLocalDate().compareTo(evalDate.toLocalDate()) == 0; + } + } + + private static class MonthMatchEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + try { + ZonedDateTime compDate = left.asDateNode().getDate(); + ZonedDateTime evalDate = right.asDateNode().getDate().withZoneSameLocal(compDate.getZone()); + return compDate.getMonth().compareTo(evalDate.getMonth()) == 0; + } catch (InvalidPathException e) { + Month month; + if (right.isStringNode()) { + month = Month.valueOf(right.asStringNode().getString()); + } else { + month = Month.of(right.asNumberNode().getNumber().intValue()); + } + return left.asDateNode().getDate().getMonth() == month; + } + } + } + + private static class MonthInEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueNodes.DateNode leftListNode = left.isDateNode() ? left.asDateNode() : null; + ValueListNode rightListNode = right.isValueListNode() ? right.asValueListNode() : null; + if (rightListNode == null) { + rightListNode = convertJsonNodeToListNode(right, ctx); + } + if (leftListNode == null || rightListNode == null) { + return false; + } + ZonedDateTime compDate = leftListNode.getDate(); + int month = compDate.getMonthValue(); + if (rightListNode.getNodes().get(0).isDateNode()) { + return rightListNode.getNodes().stream().anyMatch(node -> + node.asDateNode().getDate().withZoneSameLocal(compDate.getZone()).getMonthValue() == month); + } else if (rightListNode.getNodes().get(0).isStringNode()) { + return rightListNode.getNodes().stream().anyMatch(node -> + Month.valueOf(node.asStringNode().getString()).getValue() == month); + } else if (rightListNode.getNodes().get(0).isNumberNode()) { + return rightListNode.getNodes().stream().anyMatch(node -> + Month.of(node.asNumberNode().getNumber().intValue()).getValue() == month); + } + return false; + } + + private ValueListNode convertJsonNodeToListNode(ValueNode node, Predicate.PredicateContext ctx) { + ValueListNode valueListNode = null; + ValueNode vn; + try { + vn = new ValueListNode(Collections.singleton(node.asDateNode())); + } catch (InvalidPathException e) { + if (node.isStringNode()) { + vn = new ValueListNode(Collections.singleton(node.asStringNode())); + } else if (node.isJsonNode()) { + vn = node.asJsonNode().asValueListNode(ctx); + } else { + vn = new ValueListNode(Collections.singleton(node.asNumberNode())); + } + } + if (!vn.isUndefinedNode()) { + valueListNode = vn.asValueListNode(); + } + return valueListNode; + } + } + + private static class DayMatchEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + try { + ZonedDateTime compDate = left.asDateNode().getDate(); + ZonedDateTime evalDate = right.asDateNode().getDate().withZoneSameLocal(compDate.getZone()); + return compDate.getDayOfMonth() == evalDate.getDayOfMonth(); + } catch (InvalidPathException e) { + return left.asDateNode().getDate().getDayOfMonth() == right.asNumberNode().getNumber().intValue(); + } + } + } + + private static class DayInEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueNodes.DateNode leftListNode = left.isDateNode() ? left.asDateNode() : null; + ValueListNode rightListNode = right.isValueListNode() ? right.asValueListNode() : null; + if (rightListNode == null) { + rightListNode = convertJsonNodeToListNode(right, ctx); + } + if (leftListNode == null || rightListNode == null) { + return false; + } + ZonedDateTime compDate = leftListNode.getDate(); + int day = compDate.getDayOfMonth(); + if (rightListNode.getNodes().get(0).isDateNode()) { + return rightListNode.getNodes().stream().anyMatch(node -> + node.asDateNode().getDate().withZoneSameLocal(compDate.getZone()).getDayOfMonth() == day); + } + return rightListNode.getNodes().stream() + .anyMatch(node -> node.asNumberNode().getNumber().intValue() == day); + } + + private ValueListNode convertJsonNodeToListNode(ValueNode node, Predicate.PredicateContext ctx) { + ValueListNode valueListNode = null; + ValueNode vn; + try { + vn = new ValueListNode(Collections.singleton(node.asDateNode())); + } catch (InvalidPathException e) { + if (node.isStringNode()) { + vn = new ValueListNode(Collections.singleton(node.asStringNode())); + } else if (node.isJsonNode()) { + vn = node.asJsonNode().asValueListNode(ctx); + } else { + vn = new ValueListNode(Collections.singleton(node.asNumberNode())); + } + } + if (!vn.isUndefinedNode()) { + valueListNode = vn.asValueListNode(); + } + return valueListNode; + } + } + + private static class YearMatchEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + try { + ZonedDateTime compDate = left.asDateNode().getDate(); + ZonedDateTime evalDate = right.asDateNode().getDate().withZoneSameLocal(compDate.getZone()); + return compDate.getYear() == evalDate.getYear(); + } catch (InvalidPathException e) { + return left.asDateNode().getDate().getYear() == right.asNumberNode().getNumber().intValue(); + } + } + } + + private static class YearInEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueNodes.DateNode leftListNode = left.isDateNode() ? left.asDateNode() : null; + ValueListNode rightListNode = right.isValueListNode() ? right.asValueListNode() : null; + if (rightListNode == null) { + rightListNode = convertJsonNodeToListNode(right, ctx); + } + if (leftListNode == null || rightListNode == null) { + return false; + } + ZonedDateTime compDate = leftListNode.getDate(); + int year = compDate.getYear(); + if (rightListNode.getNodes().get(0).isDateNode()) { + return rightListNode.getNodes().stream().anyMatch(node -> + node.asDateNode().getDate().withZoneSameLocal(compDate.getZone()).getYear() == year); + } + return rightListNode.getNodes().stream() + .anyMatch(node -> node.asNumberNode().getNumber().intValue() == year); + } + + private ValueListNode convertJsonNodeToListNode(ValueNode node, Predicate.PredicateContext ctx) { + ValueListNode valueListNode = null; + ValueNode vn; + try { + vn = new ValueListNode(Collections.singleton(node.asDateNode())); + } catch (InvalidPathException e) { + if (node.isStringNode()) { + vn = new ValueListNode(Collections.singleton(node.asStringNode())); + } else if (node.isJsonNode()) { + vn = node.asJsonNode().asValueListNode(ctx); + } else { + vn = new ValueListNode(Collections.singleton(node.asNumberNode())); + } + } + if (!vn.isUndefinedNode()) { + valueListNode = vn.asValueListNode(); + } + return valueListNode; + } + } + + private static class BeforeEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ZonedDateTime compDate = left.asDateNode().getDate(); + ZonedDateTime evalDate = right.asDateNode().getDate().withZoneSameLocal(compDate.getZone()); + return compDate.isBefore(evalDate); + } + } + + private static class AfterEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ZonedDateTime compDate = left.asDateNode().getDate(); + ZonedDateTime evalDate = right.asDateNode().getDate().withZoneSameLocal(compDate.getZone()); + return compDate.isAfter(evalDate); + } + } + + private static class HourMatchEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + try { + ZonedDateTime compDate = left.asDateNode().getDate(); + ZonedDateTime evalDate = right.asDateNode().getDate().withZoneSameLocal(compDate.getZone()); + return compDate.getHour() == evalDate.getHour(); + } catch (InvalidPathException e) { + return left.asDateNode().getDate().getHour() == right.asNumberNode().getNumber().intValue(); + } + } + } + + private static class HourInEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ValueNodes.DateNode leftListNode = left.isDateNode() ? left.asDateNode() : null; + ValueListNode rightListNode = right.isValueListNode() ? right.asValueListNode() : null; + if (rightListNode == null) { + rightListNode = convertJsonNodeToListNode(right, ctx); + } + if (leftListNode == null || rightListNode == null) { + return false; + } + ZonedDateTime compDate = leftListNode.getDate(); + int hour = compDate.getHour(); + if (rightListNode.getNodes().get(0).isDateNode()) { + return rightListNode.getNodes().stream().anyMatch(node -> + node.asDateNode().getDate().withZoneSameLocal(compDate.getZone()).getHour() == hour); + } + return rightListNode.getNodes().stream() + .anyMatch(node -> node.asNumberNode().getNumber().intValue() == hour); + } + + private ValueListNode convertJsonNodeToListNode(ValueNode node, Predicate.PredicateContext ctx) { + ValueListNode valueListNode = null; + ValueNode vn; + try { + vn = new ValueListNode(Collections.singleton(node.asDateNode())); + } catch (InvalidPathException e) { + if (node.isStringNode()) { + vn = new ValueListNode(Collections.singleton(node.asStringNode())); + } else if (node.isJsonNode()) { + vn = node.asJsonNode().asValueListNode(ctx); + } else { + vn = new ValueListNode(Collections.singleton(node.asNumberNode())); + } + } + if (!vn.isUndefinedNode()) { + valueListNode = vn.asValueListNode(); + } + return valueListNode; + } + } + + private static class TimeBeforeEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ZonedDateTime date = left.asDateNode().getDate(); + String[] time = right.asStringNode().getString().split(":"); + ZonedDateTime evalDate = date.with(LocalTime.of(Integer.parseInt(time[0]), Integer.parseInt(time[1]))); + return date.isBefore(evalDate); + } + } + + private static class TimeAfterEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ZonedDateTime date = left.asDateNode().getDate(); + String[] time = right.asStringNode().getString().split(":"); + ZonedDateTime evalDate = date.with(LocalTime.of(Integer.parseInt(time[0]), Integer.parseInt(time[1]))); + return date.isAfter(evalDate); + } + } + + private static class AllMatchEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + return evaluators.get(RelationalOperator.SUBSETOF).evaluate(right, left, ctx); + } + } + + private static class AnyMatchEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + return evaluators.get(RelationalOperator.ANYOF).evaluate(right, left, ctx); + } + } + + private static class NoneMatchEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + return evaluators.get(RelationalOperator.NONEOF).evaluate(right, left, ctx); + } + } + + private static class ExactMatchEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + return evaluators.get(RelationalOperator.ALL).evaluate(right, left, ctx); + } + } + + private static class WindowInEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ZonedDateTime date = left.asDateNode().getDate(); + ZonedDateTime now = ZonedDateTime.now(date.getZone()); + int window = right.asNumberNode().getNumber().intValue(); + LocalDate finalDate = now.toLocalDate().minusDays(window); + return date.toLocalDate().isAfter(finalDate); + } + } + + private static class WindowOutEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ZonedDateTime date = left.asDateNode().getDate(); + ZonedDateTime now = ZonedDateTime.now(date.getZone()); + int window = right.asNumberNode().getNumber().intValue(); + LocalDate finalDate = date.toLocalDate().minusDays(window); + return finalDate.isAfter(now.toLocalDate()); + } + } + + private static class WindowTimeInEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ZonedDateTime date = left.asDateNode().getDate(); + ZonedDateTime now = ZonedDateTime.now(date.getZone()); + int window = right.asNumberNode().getNumber().intValue(); + ZonedDateTime finalDate = now.minusDays(window); + return date.isAfter(finalDate); + } + } + + private static class WindowTimeOutEvaluator implements Evaluator { + @Override + public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { + ZonedDateTime date = left.asDateNode().getDate(); + ZonedDateTime now = ZonedDateTime.now(date.getZone()); + int window = right.asNumberNode().getNumber().intValue(); + ZonedDateTime finalDate = date.minusDays(window); + return finalDate.isAfter(now); + } + } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java index 50986945..932162c1 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java @@ -34,7 +34,37 @@ public enum RelationalOperator { EMPTY("EMPTY"), SUBSETOF("SUBSETOF"), ANYOF("ANYOF"), - NONEOF("NONEOF"); + NONEOF("NONEOF"), + GTALL("GTALL"), + GTEALL("GTEALL"), + GTANY("GTANY"), + GTEANY("GTEANY"), + LTALL("LTALL"), + LTEALL("LTEALL"), + LTANY("LTANY"), + LTEANY("LTEANY"), + DATEEQ("DATEEQ"), + MONTHEQ("MONTHEQ"), + DAYEQ("DAYEQ"), + YEAREQ("YEAREQ"), + MONTHIN("MONTHIN"), + DAYIN("DAYIN"), + YEARIN("YEARIN"), + BEFORE("BEFORE"), + AFTER("AFTER"), + HOUREQ("HOUREQ"), + HOURIN("HOURIN"), + TIMEBEFORE("TIMEBEFORE"), + TIMEAFTER("TIMEAFTER"), + NOTCONTAINS("NOTCONTAINS"), + ALLMATCH("ALLMATCH"), + ANYMATCH("ANYMATCH"), + NONEMATCH("NONEMATCH"), + EXACTMATCH("EXACTMATCH"), + WINDOWIN("WINDOWIN"), + WINDOWOUT("WINDOWOUT"), + WINDOWTIMEIN("WINDOWTIMEIN"), + WINDOWTIMEOUT("WINDOWTIMEOUT"); private final String operatorString; diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java index cdf0dd97..e4da1bde 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java @@ -8,6 +8,8 @@ import com.jayway.jsonpath.internal.path.PathCompiler; import net.minidev.json.parser.JSONParser; import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.regex.Pattern; import static com.jayway.jsonpath.internal.filter.ValueNodes.*; @@ -113,6 +115,13 @@ public abstract class ValueNode { throw new InvalidPathException("Expected offsetDateTime node"); } + public boolean isDateNode(){ + return false; + } + + public DateNode asDateNode(){ + throw new InvalidPathException("Expected date node"); + } private static boolean isPath(Object o) { if(o == null || !(o instanceof String)){ @@ -163,7 +172,7 @@ public abstract class ValueNode { // //---------------------------------------------------- public static ValueNode toValueNode(Object o){ - + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm:ss VV"); if(o == null) return NULL_NODE; if(o instanceof ValueNode) return (ValueNode)o; if(o instanceof Class) return createClassNode((Class)o); @@ -175,6 +184,7 @@ public abstract class ValueNode { else if(o instanceof Boolean) return createBooleanNode(o.toString()); else if(o instanceof Pattern) return createPatternNode((Pattern)o); else if (o instanceof OffsetDateTime) return createOffsetDateTimeNode(o.toString()); //workaround for issue: https://github.com/json-path/JsonPath/issues/613 + else if (o instanceof ZonedDateTime) return createDateNode(((ZonedDateTime) o).format(formatter)); else throw new JsonPathException("Could not determine value type"); } @@ -220,6 +230,9 @@ public abstract class ValueNode { return new OffsetDateTimeNode(charSequence); } + public static DateNode createDateNode(String date) { + return new DateNode(date); + } public static UndefinedNode createUndefinedNode() { return UNDEFINED; diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java index 3f459a8c..404ebedf 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java @@ -2,14 +2,12 @@ package com.jayway.jsonpath.internal.filter; import java.math.BigDecimal; import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.*; import java.util.regex.Pattern; -import com.jayway.jsonpath.Configuration; -import com.jayway.jsonpath.JsonPathException; -import com.jayway.jsonpath.Option; -import com.jayway.jsonpath.PathNotFoundException; -import com.jayway.jsonpath.Predicate; +import com.jayway.jsonpath.*; import com.jayway.jsonpath.internal.Path; import com.jayway.jsonpath.internal.Utils; import com.jayway.jsonpath.internal.path.PathCompiler; @@ -227,6 +225,17 @@ public interface ValueNodes { return new NumberNode(number); } + @Override + public DateNode asDateNode() { + DateNode node; + try { + node = new DateNode(string); + } catch (IllegalArgumentException e) { + throw new InvalidPathException("Expected Timestamp format: dd MMM yyyy HH:mm:ss VV"); + } + return node; + } + public String getString() { return string; } @@ -379,7 +388,54 @@ public interface ValueNodes { } } + class DateNode extends ValueNode { + + private final ZonedDateTime dateTime; + private final static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm:ss VV"); + + DateNode(ZonedDateTime dateTime) { + this.dateTime = dateTime; + } + + DateNode(String date) { + dateTime = ZonedDateTime.parse(date, formatter); + } + + @Override + public StringNode asStringNode() { + return new StringNode(dateTime.format(formatter), false); + } + + public ZonedDateTime getDate() { + return dateTime; + } + + @Override + public Class type(Predicate.PredicateContext ctx) { + return DateNode.class; + } + + public boolean isDateNode() { + return true; + } + + public DateNode asDateNode() { + return this; + } + + @Override + public String toString() { + return dateTime.toString(); + } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof OffsetDateTimeNode) && !(o instanceof StringNode)) return false; + DateNode that = ((ValueNode) o).asDateNode(); + return dateTime.compareTo(that.dateTime) == 0; + } + } class BooleanNode extends ValueNode { private final Boolean value; diff --git a/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java b/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java index 37e37f79..0980e154 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java @@ -91,6 +91,34 @@ public class FilterParseTest { assertThat(filter).isEqualTo(parsed); } + @Test + public void a_ltall_filter_can_be_serialized() { + String filter = filter(where("a").ltall(1)).toString(); + String parsed = parse("[?(@['a'] LTALL [1])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_lteall_filter_can_be_serialized() { + String filter = filter(where("a").lteall(1)).toString(); + String parsed = parse("[?(@['a'] LTEALL [1])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_ltany_filter_can_be_serialized() { + String filter = filter(where("a").ltany(1)).toString(); + String parsed = parse("[?(@['a'] LTANY [1])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_lteany_filter_can_be_serialized() { + String filter = filter(where("a").lteany(1)).toString(); + String parsed = parse("[?(@['a'] LTEANY [1])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + @Test public void a_gt_filter_can_be_serialized() { @@ -100,6 +128,34 @@ public class FilterParseTest { assertThat(filter).isEqualTo(parsed); } + @Test + public void a_gtall_filter_can_be_serialized() { + String filter = filter(where("a").gtall(1)).toString(); + String parsed = parse("[?(@['a'] GTALL [1])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_gteall_filter_can_be_serialized() { + String filter = filter(where("a").gteall(1)).toString(); + String parsed = parse("[?(@['a'] GTEALL [1])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_gtany_filter_can_be_serialized() { + String filter = filter(where("a").gtany(1)).toString(); + String parsed = parse("[?(@['a'] GTANY [1])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_gteany_filter_can_be_serialized() { + String filter = filter(where("a").gteany(1)).toString(); + String parsed = parse("[?(@['a'] GTEANY [1])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + @Test public void a_nin_filter_can_be_serialized() { String filter = filter(where("a").nin(1)).toString(); @@ -125,6 +181,13 @@ public class FilterParseTest { assertThat(filter).isEqualTo(parsed); } + @Test + public void a_notcontains_filter_can_be_serialized() { + String filter = filter(where("a").notcontains("a")).toString(); + String parsed = parse("[?(@['a'] NOTCONTAINS 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } + @Test public void a_all_filter_can_be_serialized() { @@ -152,6 +215,15 @@ public class FilterParseTest { assertThat(filter).isEqualTo(parsed); } + @Test + public void a_subsetof_objectrange_filter_can_be_serialized() { + + String filter = filter(where("a").subsetof("a")).toString(); + String parsed = parse("[?(@['a'] SUBSETOF ['a'])]").toString(); + + assertThat(filter).isEqualTo(parsed); + } + @Test public void a_anyof_filter_can_be_serialized() { @@ -161,6 +233,15 @@ public class FilterParseTest { assertThat(filter).isEqualTo(parsed); } + @Test + public void a_anyof_objectrange_filter_can_be_serialized() { + + String filter = filter(where("a").anyof("a")).toString(); + String parsed = parse("[?(@['a'] ANYOF ['a'])]").toString(); + + assertThat(filter).isEqualTo(parsed); + } + @Test public void a_noneof_filter_can_be_serialized() { @@ -170,6 +251,15 @@ public class FilterParseTest { assertThat(filter).isEqualTo(parsed); } + @Test + public void a_noneof_objectrange_filter_can_be_serialized() { + + String filter = filter(where("a").noneof("a")).toString(); + String parsed = parse("[?(@['a'] NONEOF ['a'])]").toString(); + + assertThat(filter).isEqualTo(parsed); + } + @Test public void a_exists_filter_can_be_serialized() { @@ -275,4 +365,179 @@ public class FilterParseTest { assertThat(filter).isEqualTo(parsed); } + + @Test + public void a_allmatch_filter_can_be_serialized() { + String filter = filter(where("a").allmatch(Collections.emptyList())).toString(); + String parsed = parse("[?(@['a'] ALLMATCH [])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_anymatch_filter_can_be_serialized() { + String filter = filter(where("a").anymatch(Collections.emptyList())).toString(); + String parsed = parse("[?(@['a'] ANYMATCH [])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_nonematch_filter_can_be_serialized() { + String filter = filter(where("a").nonematch(Collections.emptyList())).toString(); + String parsed = parse("[?(@['a'] NONEMATCH [])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_exactmatch_filter_can_be_serialized() { + String filter = filter(where("a").exactmatch(Collections.emptyList())).toString(); + String parsed = parse("[?(@['a'] EXACTMATCH [])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_allmatch_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").allmatch("a")).toString(); + String parsed = parse("[?(@['a'] ALLMATCH ['a'])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_anymatch_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").anymatch("a")).toString(); + String parsed = parse("[?(@['a'] ANYMATCH ['a'])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_nonematch_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").nonematch("a")).toString(); + String parsed = parse("[?(@['a'] NONEMATCH ['a'])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_exactmatch_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").exactmatch("a")).toString(); + String parsed = parse("[?(@['a'] EXACTMATCH ['a'])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_dateeq_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").dateeq("a")).toString(); + String parsed = parse("[?(@['a'] DATEEQ 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_dayeq_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").dayeq("a")).toString(); + String parsed = parse("[?(@['a'] DAYEQ 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_dayin_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").dayin("a")).toString(); + String parsed = parse("[?(@['a'] DAYIN ['a'])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_montheq_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").montheq("a")).toString(); + String parsed = parse("[?(@['a'] MONTHEQ 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_monthin_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").monthin("a")).toString(); + String parsed = parse("[?(@['a'] MONTHIN ['a'])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_yeareq_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").yeareq("a")).toString(); + String parsed = parse("[?(@['a'] YEAREQ 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_yearin_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").yearin("a")).toString(); + String parsed = parse("[?(@['a'] YEARIN ['a'])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_before_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").before("a")).toString(); + String parsed = parse("[?(@['a'] BEFORE 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_after_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").after("a")).toString(); + String parsed = parse("[?(@['a'] AFTER 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_houreq_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").houreq("a")).toString(); + String parsed = parse("[?(@['a'] HOUREQ 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_hourin_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").hourin("a")).toString(); + String parsed = parse("[?(@['a'] HOURIN ['a'])]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_timebefore_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").timebefore("a")).toString(); + String parsed = parse("[?(@['a'] TIMEBEFORE 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_timeafter_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").timeafter("a")).toString(); + String parsed = parse("[?(@['a'] TIMEAFTER 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_windowin_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").windowin("a")).toString(); + String parsed = parse("[?(@['a'] WINDOWIN 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_windowout_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").windowout("a")).toString(); + String parsed = parse("[?(@['a'] WINDOWOUT 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_windowtimein_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").windowtimein("a")).toString(); + String parsed = parse("[?(@['a'] WINDOWTIMEIN 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } + + @Test + public void a_windowtimeout_objectrange_filter_can_be_serialized() { + String filter = filter(where("a").windowtimeout("a")).toString(); + String parsed = parse("[?(@['a'] WINDOWTIMEOUT 'a')]").toString(); + assertThat(filter).isEqualTo(parsed); + } }