From f6336650f204439fbc9d925af42f5629d27dba49 Mon Sep 17 00:00:00 2001 From: Elias Ross Date: Fri, 10 Nov 2017 10:52:30 -0800 Subject: [PATCH 1/2] 2.3 merge update: Also fix for quote evaluation This is a problem in JsonPath where the left-hand side string value was being quoted by mistake. Issue 410 on github: https://github.com/json-path/JsonPath/issues/ Also fix issue 409 as well. This should improve performance a bit when parsing. There are casts because the compiler in IntelliJ was confused as to what overloaded method to call. Casts don't really hurt. --- .../jsonpath/internal/CharacterIndex.java | 9 ++-- .../internal/filter/FilterCompiler.java | 12 ++--- .../jsonpath/internal/filter/ValueNode.java | 11 ++--- .../com/jayway/jsonpath/MultiPropTest.java | 3 +- .../java/com/jayway/jsonpath/OptionsTest.java | 24 +++++----- .../com/jayway/jsonpath/PathCompilerTest.java | 44 +++++++++++++++++++ .../com/jayway/jsonpath/ReturnTypeTest.java | 8 ++-- .../com/jayway/jsonpath/old/IssuesTest.java | 14 +++--- .../com/jayway/jsonpath/old/JsonPathTest.java | 2 +- 9 files changed, 85 insertions(+), 42 deletions(-) diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java b/json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java index 1aa28679..378d5c4b 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java @@ -220,16 +220,17 @@ public class CharacterIndex { incrementPosition(1); } - public void readSignificantSubSequence(CharSequence s) { + public boolean hasSignificantSubSequence(CharSequence s) { skipBlanks(); if (! inBounds(position + s.length() - 1)) { - throw new InvalidPathException(String.format("End of string reached while expecting: %s", s)); + return false; } if (! subSequence(position, position + s.length()).equals(s)) { - throw new InvalidPathException(String.format("Expected: %s", s)); + return false; } incrementPosition(s.length()); + return true; } public int indexOfPreviousSignificantChar(int startPosition){ @@ -314,4 +315,4 @@ public class CharacterIndex { skipBlanksAtEnd(); return this; } -} \ No newline at end of file +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java index 67b512e4..f6b9f2d3 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java @@ -132,11 +132,9 @@ public class FilterCompiler { while (true) { int savepoint = filter.position(); - try { - filter.readSignificantSubSequence(LogicalOperator.OR.getOperatorString()); + if (filter.hasSignificantSubSequence(LogicalOperator.OR.getOperatorString())) { ops.add(readLogicalAND()); - } - catch (InvalidPathException exc) { + } else { filter.setPosition(savepoint); break; } @@ -152,11 +150,9 @@ public class FilterCompiler { while (true) { int savepoint = filter.position(); - try { - filter.readSignificantSubSequence(LogicalOperator.AND.getOperatorString()); + if (filter.hasSignificantSubSequence(LogicalOperator.AND.getOperatorString())) { ops.add(readLogicalANDOperand()); - } - catch (InvalidPathException exc) { + } else { filter.setPosition(savepoint); break; } 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 d1619c45..23afe785 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 @@ -402,18 +402,19 @@ public abstract class ValueNode { private boolean useSingleQuote = true; private StringNode(CharSequence charSequence, boolean escape) { - if(charSequence.length() > 1){ + if (escape && charSequence.length() > 1) { char open = charSequence.charAt(0); char close = charSequence.charAt(charSequence.length()-1); - - if(open == '\'' && close == '\''){ + if (open == '\'' && close == '\'') { charSequence = charSequence.subSequence(1, charSequence.length()-1); - } else if(open == '"' && close == '"'){ + } else if (open == '"' && close == '"') { charSequence = charSequence.subSequence(1, charSequence.length()-1); useSingleQuote = false; } + string = Utils.unescape(charSequence.toString()); + } else { + string = charSequence.toString(); } - string = escape ? Utils.unescape(charSequence.toString()) : charSequence.toString(); } @Override diff --git a/json-path/src/test/java/com/jayway/jsonpath/MultiPropTest.java b/json-path/src/test/java/com/jayway/jsonpath/MultiPropTest.java index fc5567a8..de5824b9 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/MultiPropTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/MultiPropTest.java @@ -3,6 +3,7 @@ package com.jayway.jsonpath; import org.junit.Test; import java.util.HashMap; +import java.util.List; import java.util.Map; import static com.jayway.jsonpath.JsonPath.using; @@ -114,7 +115,7 @@ public class MultiPropTest { final Configuration conf = Configuration.defaultConfiguration().addOptions(Option.REQUIRE_PROPERTIES); final String json = "{\"a\": {\"v\": 5}, \"b\": {\"v\": 4}, \"c\": {\"v\": 1}}"; - assertThat(using(conf).parse(json).read("$['a', 'c'].v")).asList().containsOnly(5, 1); + assertThat((List)using(conf).parse(json).read("$['a', 'c'].v")).asList().containsOnly(5, 1); assertEvaluationThrows(json, "$['d', 'a', 'c', 'm'].v", PathNotFoundException.class, conf); } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/OptionsTest.java b/json-path/src/test/java/com/jayway/jsonpath/OptionsTest.java index 1093ff7a..873b3b6a 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/OptionsTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/OptionsTest.java @@ -21,7 +21,7 @@ public class OptionsTest extends BaseTest { Configuration conf = Configuration.defaultConfiguration(); - assertThat(using(conf).parse("{\"foo\" : \"bar\"}").read("$.baz")).isNull(); + assertThat((String)using(conf).parse("{\"foo\" : \"bar\"}").read("$.baz")).isNull(); } @Test @@ -37,7 +37,7 @@ public class OptionsTest extends BaseTest { Configuration conf = Configuration.defaultConfiguration(); - assertThat(using(conf).parse("{\"foo\" : \"bar\"}").read("$.foo")).isInstanceOf(String.class); + assertThat((String)using(conf).parse("{\"foo\" : \"bar\"}").read("$.foo")).isInstanceOf(String.class); } @Test @@ -45,11 +45,11 @@ public class OptionsTest extends BaseTest { Configuration conf = Configuration.builder().options(ALWAYS_RETURN_LIST).build(); - assertThat(using(conf).parse("{\"foo\" : \"bar\"}").read("$.foo")).isInstanceOf(List.class); + assertThat((List)using(conf).parse("{\"foo\" : \"bar\"}").read("$.foo")).isInstanceOf(List.class); - assertThat(using(conf).parse("{\"foo\": null}").read("$.foo")).isInstanceOf(List.class); + assertThat((List)using(conf).parse("{\"foo\": null}").read("$.foo")).isInstanceOf(List.class); - assertThat(using(conf).parse("{\"foo\": [1, 4, 8]}").read("$.foo")).asList() + assertThat((List)using(conf).parse("{\"foo\": [1, 4, 8]}").read("$.foo")).asList() .containsExactly(Arrays.asList(1, 4, 8)); } @@ -61,7 +61,7 @@ public class OptionsTest extends BaseTest { assertThat(result).hasSize(1); assertThat(result.get(0)).isNull(); - assertThat(using(conf).parse("{\"bar\": {\"foo\": [1, 4, 8]}}").read("$..foo")).asList() + assertThat((List)using(conf).parse("{\"bar\": {\"foo\": [1, 4, 8]}}").read("$..foo")).asList() .containsExactly(Arrays.asList(1, 4, 8)); } @@ -69,7 +69,7 @@ public class OptionsTest extends BaseTest { public void a_path_evaluation_is_returned_as_VALUE_by_default() { Configuration conf = Configuration.defaultConfiguration(); - assertThat(using(conf).parse("{\"foo\" : \"bar\"}").read("$.foo")).isEqualTo("bar"); + assertThat((String)using(conf).parse("{\"foo\" : \"bar\"}").read("$.foo")).isEqualTo("bar"); } @Test @@ -142,13 +142,13 @@ public class OptionsTest extends BaseTest { public void issue_suppress_exceptions_does_not_break_indefinite_evaluation() { Configuration conf = Configuration.builder().options(SUPPRESS_EXCEPTIONS).build(); - assertThat(using(conf).parse("{\"foo2\": [5]}").read("$..foo2[0]")).asList().containsOnly(5); - assertThat(using(conf).parse("{\"foo\" : {\"foo2\": [5]}}").read("$..foo2[0]")).asList().containsOnly(5); - assertThat(using(conf).parse("[null, [{\"foo\" : {\"foo2\": [5]}}]]").read("$..foo2[0]")).asList().containsOnly(5); + assertThat((List)using(conf).parse("{\"foo2\": [5]}").read("$..foo2[0]")).asList().containsOnly(5); + assertThat((List)using(conf).parse("{\"foo\" : {\"foo2\": [5]}}").read("$..foo2[0]")).asList().containsOnly(5); + assertThat((List)using(conf).parse("[null, [{\"foo\" : {\"foo2\": [5]}}]]").read("$..foo2[0]")).asList().containsOnly(5); - assertThat(using(conf).parse("[null, [{\"foo\" : {\"foo2\": [5]}}]]").read("$..foo.foo2[0]")).asList().containsOnly(5); + assertThat((List)using(conf).parse("[null, [{\"foo\" : {\"foo2\": [5]}}]]").read("$..foo.foo2[0]")).asList().containsOnly(5); - assertThat(using(conf).parse("{\"aoo\" : {}, \"foo\" : {\"foo2\": [5]}, \"zoo\" : {}}").read("$[*].foo2[0]")).asList().containsOnly(5); + assertThat((List)using(conf).parse("{\"aoo\" : {}, \"foo\" : {\"foo2\": [5]}, \"zoo\" : {}}").read("$[*].foo2[0]")).asList().containsOnly(5); } @Test diff --git a/json-path/src/test/java/com/jayway/jsonpath/PathCompilerTest.java b/json-path/src/test/java/com/jayway/jsonpath/PathCompilerTest.java index 699ba631..e8b7bfe6 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/PathCompilerTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/PathCompilerTest.java @@ -1,5 +1,6 @@ package com.jayway.jsonpath; +import com.jayway.jsonpath.internal.ParseContextImpl; import org.junit.Ignore; import org.junit.Test; @@ -236,6 +237,49 @@ public class PathCompilerTest { assertThat(result).hasSize(1); } + @Test + public void issue_predicate_can_have_double_quotes() { + String json = "{\n" + + " \"logs\": [\n" + + " {\n" + + " \"message\": \"\\\"it\\\"\",\n" + + " }\n" + + " ]\n" + + "}"; + List result = JsonPath.read(json, "$.logs[?(@.message == '\"it\"')].message"); + assertThat(result).containsExactly("\"it\""); + } + + @Test + public void issue_predicate_can_have_single_quotes() { + String json = "{\n" + + " \"logs\": [\n" + + " {\n" + + " \"message\": \"'it'\",\n" + + " }\n" + + " ]\n" + + "}"; + DocumentContext parse = JsonPath.parse(json); + JsonPath compile = JsonPath.compile("$.logs[?(@.message == \"'it'\")].message"); + List result = parse.read(compile); + assertThat(result).containsExactly("'it'"); + } + + @Test + public void issue_predicate_can_have_single_quotes_escaped() { + String json = "{\n" + + " \"logs\": [\n" + + " {\n" + + " \"message\": \"'it'\",\n" + + " }\n" + + " ]\n" + + "}"; + DocumentContext parse = JsonPath.parse(json); + JsonPath compile = JsonPath.compile("$.logs[?(@.message == '\\'it\\'')].message"); + List result = parse.read(compile); + assertThat(result).containsExactly("'it'"); + } + @Test public void issue_predicate_can_have_square_bracket_in_prop() { String json = "{\n" diff --git a/json-path/src/test/java/com/jayway/jsonpath/ReturnTypeTest.java b/json-path/src/test/java/com/jayway/jsonpath/ReturnTypeTest.java index 144817a3..c9b3fb3b 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/ReturnTypeTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/ReturnTypeTest.java @@ -18,7 +18,7 @@ public class ReturnTypeTest extends BaseTest { @Test public void assert_strings_can_be_read() { - assertThat(reader.read("$.string-property")).isEqualTo("string-value"); + assertThat((String)reader.read("$.string-property")).isEqualTo("string-value"); } @Test @@ -28,17 +28,17 @@ public class ReturnTypeTest extends BaseTest { @Test public void assert_longs_can_be_read() { - assertThat(reader.read("$.long-max-property")).isEqualTo(Long.MAX_VALUE); + assertThat((Long)reader.read("$.long-max-property")).isEqualTo(Long.MAX_VALUE); } @Test public void assert_boolean_values_can_be_read() { - assertThat(reader.read("$.boolean-property")).isEqualTo(true); + assertThat((Boolean)reader.read("$.boolean-property")).isEqualTo(true); } @Test public void assert_null_values_can_be_read() { - assertThat(reader.read("$.null-property")).isNull(); + assertThat((String)reader.read("$.null-property")).isNull(); } @Test diff --git a/json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java b/json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java index d66313bb..9dc4d5dc 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java @@ -431,7 +431,7 @@ public class IssuesTest extends BaseTest { "]"; - assertEquals(1, read(json, "$[0].a")); + assertEquals(Integer.valueOf(1), read(json, "$[0].a")); } @Test(expected = PathNotFoundException.class) @@ -471,9 +471,9 @@ public class IssuesTest extends BaseTest { String json = "{\"test\":null}"; - assertThat(read(json, "test")).isNull(); + assertThat((String)read(json, "test")).isNull(); - assertThat(JsonPath.using(Configuration.defaultConfiguration().setOptions(Option.SUPPRESS_EXCEPTIONS)).parse(json).read("nonExistingProperty")).isNull(); + assertThat((String)JsonPath.using(Configuration.defaultConfiguration().setOptions(Option.SUPPRESS_EXCEPTIONS)).parse(json).read("nonExistingProperty")).isNull(); try { read(json, "nonExistingProperty"); @@ -498,7 +498,7 @@ public class IssuesTest extends BaseTest { public void issue_45() { String json = "{\"rootkey\":{\"sub.key\":\"value\"}}"; - assertThat(read(json, "rootkey['sub.key']")).isEqualTo("value"); + assertThat((String)read(json, "rootkey['sub.key']")).isEqualTo("value"); } @Test @@ -508,7 +508,7 @@ public class IssuesTest extends BaseTest { String json = "{\"a\": {}}"; Configuration configuration = Configuration.defaultConfiguration().setOptions(Option.SUPPRESS_EXCEPTIONS); - assertThat(JsonPath.using(configuration).parse(json).read("a.x")).isNull(); + assertThat((String)JsonPath.using(configuration).parse(json).read("a.x")).isNull(); try { read(json, "a.x"); @@ -1018,8 +1018,8 @@ public class IssuesTest extends BaseTest { DocumentContext doc = JsonPath.parse(json).set("$.jsonArr[1].name", "Jayway"); - assertThat(doc.read("$.jsonArr[0].name")).isEqualTo("nOne"); - assertThat(doc.read("$.jsonArr[1].name")).isEqualTo("Jayway"); + assertThat((String)doc.read("$.jsonArr[0].name")).isEqualTo("nOne"); + assertThat((String)doc.read("$.jsonArr[1].name")).isEqualTo("Jayway"); } @Test diff --git a/json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java b/json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java index 60304267..81c6203d 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java @@ -133,7 +133,7 @@ public class JsonPathTest extends BaseTest { Assertions.fail("Expected PathNotFoundException"); } catch (PathNotFoundException e) { } - Assertions.assertThat(JsonPath.read(json, "$.data2.passes[0].id")).isEqualTo("1"); + Assertions.assertThat((String)JsonPath.read(json, "$.data2.passes[0].id")).isEqualTo("1"); } @Test From bb0853fb5e1e5e503b1d0e589b49e8ccb9f2dacc Mon Sep 17 00:00:00 2001 From: Elias Ross Date: Thu, 30 Nov 2017 10:43:21 -0800 Subject: [PATCH 2/2] Various warning clean-ups from IntelliJ Split ValueNode into a separate class to avoid possible race-condition in class loading. Modified ArrayPathToken to be super-class of ArrayIndex/ArraySlice token because IntelliJ warned about null pointer exception possibilities. Removed redundant boolean checks. Removed use of StringBuffer -> StringBuilder. Removed dangling ; --- .../java/com/jayway/jsonpath/Criteria.java | 16 +- .../jsonpath/internal/CharacterIndex.java | 2 +- .../jsonpath/internal/DefaultsImpl.java | 2 +- .../com/jayway/jsonpath/internal/PathRef.java | 2 +- .../com/jayway/jsonpath/internal/Utils.java | 8 +- .../internal/filter/EvaluatorFactory.java | 13 +- .../internal/filter/FilterCompiler.java | 21 +- .../jsonpath/internal/filter/ValueNode.java | 653 +---------------- .../jsonpath/internal/filter/ValueNodes.java | 662 ++++++++++++++++++ .../internal/function/text/Concatenate.java | 2 +- .../internal/path/ArrayIndexToken.java | 52 ++ .../internal/path/ArrayPathToken.java | 138 +--- .../internal/path/ArraySliceOperation.java | 18 +- .../internal/path/ArraySliceToken.java | 114 +++ .../jsonpath/internal/path/CompiledPath.java | 2 +- .../jsonpath/internal/path/PathCompiler.java | 4 +- .../internal/path/PathTokenFactory.java | 4 +- .../internal/path/PredicatePathToken.java | 3 +- .../jsonpath/internal/path/RootPathToken.java | 2 +- .../internal/path/WildcardPathToken.java | 4 +- .../jsonpath/spi/cache/CacheProvider.java | 4 +- .../spi/json/AbstractJsonProvider.java | 2 +- .../java/com/jayway/jsonpath/FilterTest.java | 3 +- 23 files changed, 893 insertions(+), 838 deletions(-) create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceToken.java 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 92f9b2bc..ea32460e 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java @@ -28,7 +28,9 @@ import java.util.List; import java.util.regex.Pattern; import static com.jayway.jsonpath.internal.Utils.notNull; - +import com.jayway.jsonpath.internal.filter.ValueNodes; +import static com.jayway.jsonpath.internal.filter.ValueNodes.ValueListNode; +import static com.jayway.jsonpath.internal.filter.ValueNodes.PredicateNode; /** * @@ -225,7 +227,7 @@ public class Criteria implements Predicate { public Criteria in(Collection c) { notNull(c, "collection can not be null"); this.criteriaType = RelationalOperator.IN; - this.right = new ValueNode.ValueListNode(c); + this.right = new ValueListNode(c); return this; } @@ -263,7 +265,7 @@ public class Criteria implements Predicate { public Criteria nin(Collection c) { notNull(c, "collection can not be null"); this.criteriaType = RelationalOperator.NIN; - this.right = new ValueNode.ValueListNode(c); + this.right = new ValueListNode(c); return this; } @@ -290,7 +292,7 @@ public class Criteria implements Predicate { public Criteria subsetof(Collection c) { notNull(c, "collection can not be null"); this.criteriaType = RelationalOperator.SUBSETOF; - this.right = new ValueNode.ValueListNode(c); + this.right = new ValueListNode(c); return this; } @@ -315,7 +317,7 @@ public class Criteria implements Predicate { public Criteria all(Collection c) { notNull(c, "collection can not be null"); this.criteriaType = RelationalOperator.ALL; - this.right = new ValueNode.ValueListNode(c); + this.right = new ValueListNode(c); return this; } @@ -389,7 +391,7 @@ public class Criteria implements Predicate { */ public Criteria empty(boolean empty) { this.criteriaType = RelationalOperator.EMPTY; - this.right = empty ? ValueNode.TRUE : ValueNode.FALSE; + this.right = empty ? ValueNodes.TRUE : ValueNodes.FALSE; return this; } @@ -401,7 +403,7 @@ public class Criteria implements Predicate { */ public Criteria matches(Predicate p) { this.criteriaType = RelationalOperator.MATCHES; - this.right = new ValueNode.PredicateNode(p); + this.right = new PredicateNode(p); return this; } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java b/json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java index 378d5c4b..bc152776 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java @@ -172,7 +172,7 @@ public class CharacterIndex { inEscape = false; } else if('\\' == charAt(readPosition)){ inEscape = true; - } else if (c == charAt(readPosition) && !inEscape){ + } else if (c == charAt(readPosition)){ return readPosition; } readPosition ++; diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/DefaultsImpl.java b/json-path/src/main/java/com/jayway/jsonpath/internal/DefaultsImpl.java index d4919ca8..900bd807 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/DefaultsImpl.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/DefaultsImpl.java @@ -32,6 +32,6 @@ public final class DefaultsImpl implements Defaults { } private DefaultsImpl() { - }; + } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java b/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java index 1b42be31..2dce79d7 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java @@ -212,7 +212,7 @@ public abstract class PathRef implements Comparable { public int compareTo(PathRef o) { if(o instanceof ArrayIndexPathRef){ ArrayIndexPathRef pf = (ArrayIndexPathRef) o; - return Integer.valueOf(pf.index).compareTo(this.index); + return Integer.compare(pf.index, this.index); } return super.compareTo(o); } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/Utils.java b/json-path/src/main/java/com/jayway/jsonpath/internal/Utils.java index 976c0209..ad7900fa 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/Utils.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/Utils.java @@ -24,8 +24,8 @@ import java.util.Iterator; public final class Utils { // accept a collection of objects, since all objects have toString() - public static String join(String delimiter, String wrap, Iterable objs) { - Iterator iter = objs.iterator(); + public static String join(String delimiter, String wrap, Iterable objs) { + Iterator iter = objs.iterator(); if (!iter.hasNext()) { return ""; } @@ -38,7 +38,7 @@ public final class Utils { } // accept a collection of objects, since all objects have toString() - public static String join(String delimiter, Iterable objs) { + public static String join(String delimiter, Iterable objs) { return join(delimiter, "", objs); } @@ -175,7 +175,7 @@ public final class Utils { } int len = str.length(); StringWriter writer = new StringWriter(len); - StringBuffer unicode = new StringBuffer(4); + StringBuilder unicode = new StringBuilder(4); boolean hadSlash = false; boolean inUnicode = false; for (int i = 0; i < len; i++) { 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 ac47274a..0669e7e8 100644 --- 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 @@ -2,6 +2,7 @@ package com.jayway.jsonpath.internal.filter; import com.jayway.jsonpath.JsonPathException; import com.jayway.jsonpath.Predicate; +import static com.jayway.jsonpath.internal.filter.ValueNodes.*; import java.util.HashMap; import java.util.Map; @@ -168,7 +169,7 @@ public class EvaluatorFactory { private static class InEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - ValueNode.ValueListNode valueListNode; + ValueListNode valueListNode; if(right.isJsonNode()){ ValueNode vn = right.asJsonNode().asValueListNode(ctx); if(vn.isUndefinedNode()){ @@ -193,12 +194,12 @@ public class EvaluatorFactory { private static class AllEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - ValueNode.ValueListNode requiredValues = right.asValueListNode(); + ValueListNode requiredValues = right.asValueListNode(); if(left.isJsonNode()){ ValueNode valueNode = left.asJsonNode().asValueListNode(ctx); //returns UndefinedNode if conversion is not possible if(valueNode.isValueListNode()){ - ValueNode.ValueListNode shouldContainAll = valueNode.asValueListNode(); + ValueListNode shouldContainAll = valueNode.asValueListNode(); for (ValueNode required : requiredValues) { if(!shouldContainAll.contains(required)){ return false; @@ -249,7 +250,7 @@ public class EvaluatorFactory { } } - private boolean matches(ValueNode.PatternNode patternNode, String inputToMatch) { + private boolean matches(PatternNode patternNode, String inputToMatch) { return patternNode.getCompiledPattern().matcher(inputToMatch).matches(); } @@ -269,7 +270,7 @@ public class EvaluatorFactory { private static class SubsetOfEvaluator implements Evaluator { @Override public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) { - ValueNode.ValueListNode rightValueListNode; + ValueListNode rightValueListNode; if(right.isJsonNode()){ ValueNode vn = right.asJsonNode().asValueListNode(ctx); if(vn.isUndefinedNode()){ @@ -280,7 +281,7 @@ public class EvaluatorFactory { } else { rightValueListNode = right.asValueListNode(); } - ValueNode.ValueListNode leftValueListNode; + ValueListNode leftValueListNode; if(left.isJsonNode()){ ValueNode vn = left.asJsonNode().asValueListNode(ctx); if(vn.isUndefinedNode()){ diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java index f6b9f2d3..107d2364 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java @@ -4,13 +4,14 @@ import com.jayway.jsonpath.Filter; import com.jayway.jsonpath.InvalidPathException; import com.jayway.jsonpath.Predicate; import com.jayway.jsonpath.internal.CharacterIndex; +import static com.jayway.jsonpath.internal.filter.ValueNodes.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; -public class FilterCompiler { +public class FilterCompiler { private static final Logger logger = LoggerFactory.getLogger(FilterCompiler.class); private static final char DOC_CONTEXT = '$'; @@ -197,10 +198,10 @@ public class FilterCompiler { filter.setPosition(savepoint); } - ValueNode.PathNode pathNode = left.asPathNode(); + PathNode pathNode = left.asPathNode(); left = pathNode.asExistsCheck(pathNode.shouldExists()); RelationalOperator operator = RelationalOperator.EXISTS; - ValueNode right = left.asPathNode().shouldExists() ? ValueNode.TRUE : ValueNode.FALSE; + ValueNode right = left.asPathNode().shouldExists() ? ValueNodes.TRUE : ValueNodes.FALSE; return new RelationalExpressionNode(left, operator, right); } @@ -239,7 +240,7 @@ public class FilterCompiler { return RelationalOperator.fromString(operator.toString()); } - private ValueNode.NullNode readNullLiteral() { + private NullNode readNullLiteral() { int begin = filter.position(); if(filter.currentChar() == NULL && filter.inBounds(filter.position() + 3)){ CharSequence nullValue = filter.subSequence(filter.position(), filter.position() + 4); @@ -252,7 +253,7 @@ public class FilterCompiler { throw new InvalidPathException("Expected value"); } - private ValueNode.JsonNode readJsonLiteral(){ + private JsonNode readJsonLiteral(){ int begin = filter.position(); char openChar = filter.currentChar(); @@ -273,7 +274,7 @@ public class FilterCompiler { } - private ValueNode.PatternNode readPattern() { + private PatternNode readPattern() { int begin = filter.position(); int closingIndex = filter.nextIndexOfUnescaped(PATTERN); if (closingIndex == -1) { @@ -289,7 +290,7 @@ public class FilterCompiler { return ValueNode.createPatternNode(pattern); } - private ValueNode.StringNode readStringLiteral(char endChar) { + private StringNode readStringLiteral(char endChar) { int begin = filter.position(); int closingSingleQuoteIndex = filter.nextIndexOfUnescaped(endChar); @@ -303,7 +304,7 @@ public class FilterCompiler { return ValueNode.createStringNode(stringLiteral, true); } - private ValueNode.NumberNode readNumberLiteral() { + private NumberNode readNumberLiteral() { int begin = filter.position(); while (filter.inBounds() && filter.isNumberCharacter(filter.position())) { @@ -314,7 +315,7 @@ public class FilterCompiler { return ValueNode.createNumberNode(numberLiteral); } - private ValueNode.BooleanNode readBooleanLiteral() { + private BooleanNode readBooleanLiteral() { int begin = filter.position(); int end = filter.currentChar() == TRUE ? filter.position() + 3 : filter.position() + 4; @@ -331,7 +332,7 @@ public class FilterCompiler { return ValueNode.createBooleanNode(boolValue); } - private ValueNode.PathNode readPath() { + private PathNode readPath() { char previousSignificantChar = filter.previousSignificantChar(); int begin = filter.position(); 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 23afe785..d87994ee 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 @@ -1,40 +1,17 @@ package com.jayway.jsonpath.internal.filter; -import com.jayway.jsonpath.Configuration; +import java.util.regex.Pattern; + import com.jayway.jsonpath.InvalidPathException; import com.jayway.jsonpath.JsonPathException; -import com.jayway.jsonpath.Option; -import com.jayway.jsonpath.PathNotFoundException; import com.jayway.jsonpath.Predicate; import com.jayway.jsonpath.internal.Path; -import com.jayway.jsonpath.internal.Utils; import com.jayway.jsonpath.internal.path.PathCompiler; -import com.jayway.jsonpath.internal.path.PredicateContextImpl; -import com.jayway.jsonpath.spi.json.JsonProvider; - import net.minidev.json.parser.JSONParser; -import net.minidev.json.parser.ParseException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; +import static com.jayway.jsonpath.internal.filter.ValueNodes.*; public abstract class ValueNode { - public static final NullNode NULL_NODE = new NullNode(); - public static final BooleanNode TRUE = new BooleanNode("true"); - public static final BooleanNode FALSE = new BooleanNode("false"); - public static final UndefinedNode UNDEFINED = new UndefinedNode(); - - public abstract Class type(Predicate.PredicateContext ctx); public boolean isPatternNode() { @@ -174,7 +151,7 @@ public abstract class ValueNode { // //---------------------------------------------------- public static ValueNode toValueNode(Object o){ - if(o == null) return ValueNode.NULL_NODE; + if(o == null) return NULL_NODE; if(o instanceof ValueNode) return (ValueNode)o; if(o instanceof Class) return createClassNode((Class)o); else if(isPath(o)) return new PathNode(o.toString(), false, false); @@ -235,625 +212,5 @@ public abstract class ValueNode { return new PathNode(path); } - //---------------------------------------------------- - // - // ValueNode Implementations - // - //---------------------------------------------------- - public static class PatternNode extends ValueNode { - private final String pattern; - private final Pattern compiledPattern; - - private PatternNode(CharSequence charSequence) { - String tmp = charSequence.toString(); - int begin = tmp.indexOf('/'); - int end = tmp.lastIndexOf('/'); - int flags = tmp.endsWith("/i") ? Pattern.CASE_INSENSITIVE : 0; - this.pattern = tmp.substring(begin + 1, end); - this.compiledPattern = Pattern.compile(pattern, flags); - } - - public PatternNode(Pattern pattern) { - this.pattern = pattern.pattern(); - this.compiledPattern = pattern; - } - - - public Pattern getCompiledPattern() { - return compiledPattern; - } - - @Override - public Class type(Predicate.PredicateContext ctx) { - return Void.TYPE; - } - - public boolean isPatternNode() { - return true; - } - - public PatternNode asPatternNode() { - return this; - } - - @Override - public String toString() { - - String flags = ""; - if((compiledPattern.flags() & Pattern.CASE_INSENSITIVE) == Pattern.CASE_INSENSITIVE){ - flags = "i"; - } - if(!pattern.startsWith("/")){ - return "/" + pattern + "/" + flags; - } else { - return pattern; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof PatternNode)) return false; - - PatternNode that = (PatternNode) o; - - return !(compiledPattern != null ? !compiledPattern.equals(that.compiledPattern) : that.compiledPattern != null); - - } - } - - public static class JsonNode extends ValueNode { - private final Object json; - private final boolean parsed; - - private JsonNode(CharSequence charSequence) { - json = charSequence.toString(); - parsed = false; - } - - public JsonNode(Object parsedJson) { - json = parsedJson; - parsed = true; - } - - @Override - public Class type(Predicate.PredicateContext ctx) { - if(isArray(ctx)) return List.class; - else if(isMap(ctx)) return Map.class; - else if(parse(ctx) instanceof Number) return Number.class; - else if(parse(ctx) instanceof String) return String.class; - else if(parse(ctx) instanceof Boolean) return Boolean.class; - else return Void.class; - } - - public boolean isJsonNode() { - return true; - } - - public JsonNode asJsonNode() { - return this; - } - - public ValueNode asValueListNode(Predicate.PredicateContext ctx){ - if(!isArray(ctx)){ - return UNDEFINED; - } else { - return new ValueListNode(Collections.unmodifiableList((List) parse(ctx))); - } - } - - public Object parse(Predicate.PredicateContext ctx){ - try { - return parsed ? json : new JSONParser(JSONParser.MODE_PERMISSIVE).parse(json.toString()); - } catch (ParseException e) { - throw new IllegalArgumentException(e); - } - } - - public boolean isParsed() { - return parsed; - } - - public Object getJson() { - return json; - } - - public boolean isArray(Predicate.PredicateContext ctx) { - return parse(ctx) instanceof List; - } - - public boolean isMap(Predicate.PredicateContext ctx) { - return parse(ctx) instanceof Map; - } - - public int length(Predicate.PredicateContext ctx) { - return isArray(ctx) ? ((List) parse(ctx)).size() : -1; - } - - public boolean isEmpty(Predicate.PredicateContext ctx) { - if (isArray(ctx) || isMap(ctx)) return ((Collection) parse(ctx)).size() == 0; - else if((parse(ctx) instanceof String)) return ((String)parse(ctx)).length() == 0; - return true; - } - - @Override - public String toString() { - return json.toString(); - } - - public boolean equals(JsonNode jsonNode, Predicate.PredicateContext ctx) { - if (this == jsonNode) return true; - return !(json != null ? !json.equals(jsonNode.parse(ctx)) : jsonNode.json != null); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof JsonNode)) return false; - - JsonNode jsonNode = (JsonNode) o; - - return !(json != null ? !json.equals(jsonNode.json) : jsonNode.json != null); - } - } - - public static class StringNode extends ValueNode { - private final String string; - private boolean useSingleQuote = true; - - private StringNode(CharSequence charSequence, boolean escape) { - if (escape && charSequence.length() > 1) { - char open = charSequence.charAt(0); - char close = charSequence.charAt(charSequence.length()-1); - if (open == '\'' && close == '\'') { - charSequence = charSequence.subSequence(1, charSequence.length()-1); - } else if (open == '"' && close == '"') { - charSequence = charSequence.subSequence(1, charSequence.length()-1); - useSingleQuote = false; - } - string = Utils.unescape(charSequence.toString()); - } else { - string = charSequence.toString(); - } - } - - @Override - public NumberNode asNumberNode() { - BigDecimal number = null; - try { - number = new BigDecimal(string); - } catch (NumberFormatException nfe){ - return NumberNode.NAN; - } - return new NumberNode(number); - } - - public String getString() { - return string; - } - - public int length(){ - return getString().length(); - } - - public boolean isEmpty(){ - return getString().isEmpty(); - } - - public boolean contains(String str) { - return getString().contains(str); - } - - @Override - public Class type(Predicate.PredicateContext ctx) { - return String.class; - } - - public boolean isStringNode() { - return true; - } - - public StringNode asStringNode() { - return this; - } - - @Override - public String toString() { - String quote = useSingleQuote ? "'" : "\""; - return quote + Utils.escape(string, true) + quote; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof StringNode) && !(o instanceof NumberNode)) return false; - - StringNode that = ((ValueNode) o).asStringNode(); - - return !(string != null ? !string.equals(that.getString()) : that.getString() != null); - - } - } - - public static class NumberNode extends ValueNode { - - public static NumberNode NAN = new NumberNode((BigDecimal)null); - - private final BigDecimal number; - - private NumberNode(BigDecimal number) { - this.number = number; - } - private NumberNode(CharSequence num) { - number = new BigDecimal(num.toString()); - } - - @Override - public StringNode asStringNode() { - return new StringNode(number.toString(), false); - } - - public BigDecimal getNumber() { - return number; - } - - @Override - public Class type(Predicate.PredicateContext ctx) { - return Number.class; - } - - public boolean isNumberNode() { - return true; - } - - public NumberNode asNumberNode() { - return this; - } - - @Override - public String toString() { - return number.toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof NumberNode) && !(o instanceof StringNode)) return false; - - NumberNode that = ((ValueNode)o).asNumberNode(); - - if(that == NumberNode.NAN){ - return false; - } else { - return number.compareTo(that.number) == 0; - } - } - } - - public static class BooleanNode extends ValueNode { - private final Boolean value; - - private BooleanNode(CharSequence boolValue) { - value = Boolean.parseBoolean(boolValue.toString()); - } - - @Override - public Class type(Predicate.PredicateContext ctx) { - return Boolean.class; - } - - public boolean isBooleanNode() { - return true; - } - - public BooleanNode asBooleanNode() { - return this; - } - - public boolean getBoolean() { - return value; - } - - @Override - public String toString() { - return value.toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof BooleanNode)) return false; - - BooleanNode that = (BooleanNode) o; - - return !(value != null ? !value.equals(that.value) : that.value != null); - } - } - - public static class ClassNode extends ValueNode { - private final Class clazz; - - private ClassNode(Class clazz) { - this.clazz = clazz; - } - - @Override - public Class type(Predicate.PredicateContext ctx) { - return Class.class; - } - - public boolean isClassNode() { - return true; - } - - public ClassNode asClassNode() { - return this; - } - - public Class getClazz() { - return clazz; - } - - @Override - public String toString() { - return clazz.getName(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ClassNode)) return false; - - ClassNode that = (ClassNode) o; - - return !(clazz != null ? !clazz.equals(that.clazz) : that.clazz != null); - } - } - - public static class NullNode extends ValueNode { - - private NullNode() {} - - @Override - public Class type(Predicate.PredicateContext ctx) { - return Void.class; - } - - @Override - public boolean isNullNode() { - return true; - } - - @Override - public NullNode asNullNode() { - return this; - } - - @Override - public String toString() { - return "null"; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof NullNode)) return false; - - return true; - } - } - - public static class UndefinedNode extends ValueNode { - - @Override - public Class type(Predicate.PredicateContext ctx) { - return Void.class; - } - - public UndefinedNode asUndefinedNode() { - return this; - } - - public boolean isUndefinedNode() { - return true; - } - - @Override - public boolean equals(Object o) { - return false; - } - } - - public static class PredicateNode extends ValueNode { - - private final Predicate predicate; - - public PredicateNode(Predicate predicate) { - this.predicate = predicate; - } - - public Predicate getPredicate() { - return predicate; - } - - public PredicateNode asPredicateNode() { - return this; - } - - @Override - public Class type(Predicate.PredicateContext ctx) { - return Void.class; - } - - public boolean isPredicateNode() { - return true; - } - - @Override - public boolean equals(Object o) { - return false; - } - - @Override - public String toString() { - return predicate.toString(); - } - } - - public static class ValueListNode extends ValueNode implements Iterable { - - private List nodes = new ArrayList(); - - public ValueListNode(Collection values) { - for (Object value : values) { - nodes.add(toValueNode(value)); - } - } - - public boolean contains(ValueNode node){ - return nodes.contains(node); - } - - public boolean subsetof(ValueListNode right) { - for (ValueNode leftNode : nodes) { - if (!right.nodes.contains(leftNode)) { - return false; - } - } - return true; - } - - public List getNodes() { - return Collections.unmodifiableList(nodes); - } - - @Override - public Class type(Predicate.PredicateContext ctx) { - return List.class; - } - - public boolean isValueListNode() { - return true; - } - - public ValueListNode asValueListNode() { - return this; - } - - @Override - public String toString() { - return "[" + Utils.join(",", nodes) + "]"; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ValueListNode)) return false; - - ValueListNode that = (ValueListNode) o; - - return !(that != null ? !nodes.equals(that.nodes) : that.nodes != null); - } - - @Override - public Iterator iterator() { - return nodes.iterator(); - } - } - - public static class PathNode extends ValueNode { - - private static final Logger logger = LoggerFactory.getLogger(PathNode.class); - - private final Path path; - private final boolean existsCheck; - private final boolean shouldExist; - - PathNode(Path path) { - this(path, false, false); - } - - PathNode(CharSequence charSequence, boolean existsCheck, boolean shouldExist) { - this(PathCompiler.compile(charSequence.toString()), existsCheck, shouldExist); - } - - PathNode(Path path, boolean existsCheck, boolean shouldExist) { - this.path = path; - this.existsCheck = existsCheck; - this.shouldExist = shouldExist; - logger.trace("PathNode {} existsCheck: {}", path, existsCheck); - } - - public Path getPath() { - return path; - } - - public boolean isExistsCheck() { - return existsCheck; - } - - public boolean shouldExists() { - return shouldExist; - } - - @Override - public Class type(Predicate.PredicateContext ctx) { - return Void.class; - } - - public boolean isPathNode() { - return true; - } - - public PathNode asPathNode() { - return this; - } - - public PathNode asExistsCheck(boolean shouldExist) { - return new PathNode(path, true, shouldExist); - } - - @Override - public String toString() { - return existsCheck && ! shouldExist ? Utils.concat("!" , path.toString()) : path.toString(); - } - - public ValueNode evaluate(Predicate.PredicateContext ctx) { - if (isExistsCheck()) { - try { - Configuration c = Configuration.builder().jsonProvider(ctx.configuration().jsonProvider()).options(Option.REQUIRE_PROPERTIES).build(); - Object result = path.evaluate(ctx.item(), ctx.root(), c).getValue(false); - return result == JsonProvider.UNDEFINED ? ValueNode.FALSE : ValueNode.TRUE; - } catch (PathNotFoundException e) { - return ValueNode.FALSE; - } - } else { - try { - Object res; - if (ctx instanceof PredicateContextImpl) { - //This will use cache for document ($) queries - PredicateContextImpl ctxi = (PredicateContextImpl) ctx; - res = ctxi.evaluate(path); - } else { - Object doc = path.isRootPath() ? ctx.root() : ctx.item(); - res = path.evaluate(doc, ctx.root(), ctx.configuration()).getValue(); - } - res = ctx.configuration().jsonProvider().unwrap(res); - - if (res instanceof Number) return ValueNode.createNumberNode(res.toString()); - else if (res instanceof BigDecimal) return ValueNode.createNumberNode(res.toString()); - else if (res instanceof String) return ValueNode.createStringNode(res.toString(), false); - else if (res instanceof Boolean) return ValueNode.createBooleanNode(res.toString()); - else if (res == null) return ValueNode.NULL_NODE; - else if (ctx.configuration().jsonProvider().isArray(res)) return ValueNode.createJsonNode(ctx.configuration().mappingProvider().map(res, List.class, ctx.configuration())); - else if (ctx.configuration().jsonProvider().isMap(res)) return ValueNode.createJsonNode(ctx.configuration().mappingProvider().map(res, Map.class, ctx.configuration())); - else throw new JsonPathException("Could not convert " + res.toString() + " to a ValueNode"); - } catch (PathNotFoundException e) { - return ValueNode.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 new file mode 100644 index 00000000..531868db --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java @@ -0,0 +1,662 @@ +package com.jayway.jsonpath.internal.filter; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +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.internal.Path; +import com.jayway.jsonpath.internal.Utils; +import com.jayway.jsonpath.internal.path.PathCompiler; +import com.jayway.jsonpath.internal.path.PredicateContextImpl; +import com.jayway.jsonpath.spi.json.JsonProvider; +import net.minidev.json.parser.JSONParser; +import net.minidev.json.parser.ParseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Moved these nodes out of the ValueNode abstract class. + * This is to avoid this possible issue: + * + * Classes that refer to their own subclasses in their static initializers or in static fields. + * Such references can cause JVM-level deadlocks in multithreaded environment, when + * one thread tries to load superclass and another thread tries to load subclass at the same time. + */ +public interface ValueNodes { + + NullNode NULL_NODE = new NullNode(); + BooleanNode TRUE = new BooleanNode("true"); + BooleanNode FALSE = new BooleanNode("false"); + UndefinedNode UNDEFINED = new UndefinedNode(); + + //---------------------------------------------------- + // + // ValueNode Implementations + // + //---------------------------------------------------- + class PatternNode extends ValueNode { + private final String pattern; + private final Pattern compiledPattern; + + PatternNode(CharSequence charSequence) { + String tmp = charSequence.toString(); + int begin = tmp.indexOf('/'); + int end = tmp.lastIndexOf('/'); + int flags = tmp.endsWith("/i") ? Pattern.CASE_INSENSITIVE : 0; + this.pattern = tmp.substring(begin + 1, end); + this.compiledPattern = Pattern.compile(pattern, flags); + } + + PatternNode(Pattern pattern) { + this.pattern = pattern.pattern(); + this.compiledPattern = pattern; + } + + + Pattern getCompiledPattern() { + return compiledPattern; + } + + @Override + public Class type(Predicate.PredicateContext ctx) { + return Void.TYPE; + } + + public boolean isPatternNode() { + return true; + } + + public PatternNode asPatternNode() { + return this; + } + + @Override + public String toString() { + + String flags = ""; + if((compiledPattern.flags() & Pattern.CASE_INSENSITIVE) == Pattern.CASE_INSENSITIVE){ + flags = "i"; + } + if(!pattern.startsWith("/")){ + return "/" + pattern + "/" + flags; + } else { + return pattern; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PatternNode)) return false; + + PatternNode that = (PatternNode) o; + + return !(compiledPattern != null ? !compiledPattern.equals(that.compiledPattern) : that.compiledPattern != null); + + } + } + + class JsonNode extends ValueNode { + private final Object json; + private final boolean parsed; + + JsonNode(CharSequence charSequence) { + json = charSequence.toString(); + parsed = false; + } + + JsonNode(Object parsedJson) { + json = parsedJson; + parsed = true; + } + + @Override + public Class type(Predicate.PredicateContext ctx) { + if(isArray(ctx)) return List.class; + else if(isMap(ctx)) return Map.class; + else if(parse(ctx) instanceof Number) return Number.class; + else if(parse(ctx) instanceof String) return String.class; + else if(parse(ctx) instanceof Boolean) return Boolean.class; + else return Void.class; + } + + public boolean isJsonNode() { + return true; + } + + public JsonNode asJsonNode() { + return this; + } + + public ValueNode asValueListNode(Predicate.PredicateContext ctx){ + if(!isArray(ctx)){ + return UNDEFINED; + } else { + return new ValueListNode(Collections.unmodifiableList((List) parse(ctx))); + } + } + + public Object parse(Predicate.PredicateContext ctx){ + try { + return parsed ? json : new JSONParser(JSONParser.MODE_PERMISSIVE).parse(json.toString()); + } catch (ParseException e) { + throw new IllegalArgumentException(e); + } + } + + public boolean isParsed() { + return parsed; + } + + public Object getJson() { + return json; + } + + public boolean isArray(Predicate.PredicateContext ctx) { + return parse(ctx) instanceof List; + } + + public boolean isMap(Predicate.PredicateContext ctx) { + return parse(ctx) instanceof Map; + } + + public int length(Predicate.PredicateContext ctx) { + return isArray(ctx) ? ((List) parse(ctx)).size() : -1; + } + + public boolean isEmpty(Predicate.PredicateContext ctx) { + if (isArray(ctx) || isMap(ctx)) return ((Collection) parse(ctx)).size() == 0; + else if((parse(ctx) instanceof String)) return ((String)parse(ctx)).length() == 0; + return true; + } + + @Override + public String toString() { + return json.toString(); + } + + public boolean equals(JsonNode jsonNode, Predicate.PredicateContext ctx) { + if (this == jsonNode) return true; + return !(json != null ? !json.equals(jsonNode.parse(ctx)) : jsonNode.json != null); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof JsonNode)) return false; + + JsonNode jsonNode = (JsonNode) o; + + return !(json != null ? !json.equals(jsonNode.json) : jsonNode.json != null); + } + } + + class StringNode extends ValueNode { + private final String string; + private boolean useSingleQuote = true; + + StringNode(CharSequence charSequence, boolean escape) { + if (escape && charSequence.length() > 1) { + char open = charSequence.charAt(0); + char close = charSequence.charAt(charSequence.length()-1); + if (open == '\'' && close == '\'') { + charSequence = charSequence.subSequence(1, charSequence.length()-1); + } else if (open == '"' && close == '"') { + charSequence = charSequence.subSequence(1, charSequence.length()-1); + useSingleQuote = false; + } + string = Utils.unescape(charSequence.toString()); + } else { + string = charSequence.toString(); + } + } + + @Override + public NumberNode asNumberNode() { + BigDecimal number = null; + try { + number = new BigDecimal(string); + } catch (NumberFormatException nfe){ + return NumberNode.NAN; + } + return new NumberNode(number); + } + + public String getString() { + return string; + } + + public int length(){ + return getString().length(); + } + + public boolean isEmpty(){ + return getString().isEmpty(); + } + + public boolean contains(String str) { + return getString().contains(str); + } + + @Override + public Class type(Predicate.PredicateContext ctx) { + return String.class; + } + + public boolean isStringNode() { + return true; + } + + public StringNode asStringNode() { + return this; + } + + @Override + public String toString() { + String quote = useSingleQuote ? "'" : "\""; + return quote + Utils.escape(string, true) + quote; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof StringNode) && !(o instanceof NumberNode)) return false; + + StringNode that = ((ValueNode) o).asStringNode(); + + return !(string != null ? !string.equals(that.getString()) : that.getString() != null); + + } + } + + class NumberNode extends ValueNode { + + public static NumberNode NAN = new NumberNode((BigDecimal)null); + + private final BigDecimal number; + + NumberNode(BigDecimal number) { + this.number = number; + } + NumberNode(CharSequence num) { + number = new BigDecimal(num.toString()); + } + + @Override + public StringNode asStringNode() { + return new StringNode(number.toString(), false); + } + + public BigDecimal getNumber() { + return number; + } + + @Override + public Class type(Predicate.PredicateContext ctx) { + return Number.class; + } + + public boolean isNumberNode() { + return true; + } + + public NumberNode asNumberNode() { + return this; + } + + @Override + public String toString() { + return number.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof NumberNode) && !(o instanceof StringNode)) return false; + + NumberNode that = ((ValueNode)o).asNumberNode(); + + if(that == NumberNode.NAN){ + return false; + } else { + return number.compareTo(that.number) == 0; + } + } + } + + class BooleanNode extends ValueNode { + private final Boolean value; + + private BooleanNode(CharSequence boolValue) { + value = Boolean.parseBoolean(boolValue.toString()); + } + + @Override + public Class type(Predicate.PredicateContext ctx) { + return Boolean.class; + } + + public boolean isBooleanNode() { + return true; + } + + public BooleanNode asBooleanNode() { + return this; + } + + public boolean getBoolean() { + return value; + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BooleanNode)) return false; + + BooleanNode that = (BooleanNode) o; + + return !(value != null ? !value.equals(that.value) : that.value != null); + } + } + + class ClassNode extends ValueNode { + private final Class clazz; + + ClassNode(Class clazz) { + this.clazz = clazz; + } + + @Override + public Class type(Predicate.PredicateContext ctx) { + return Class.class; + } + + public boolean isClassNode() { + return true; + } + + public ClassNode asClassNode() { + return this; + } + + public Class getClazz() { + return clazz; + } + + @Override + public String toString() { + return clazz.getName(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ClassNode)) return false; + + ClassNode that = (ClassNode) o; + + return !(clazz != null ? !clazz.equals(that.clazz) : that.clazz != null); + } + } + + class NullNode extends ValueNode { + + private NullNode() {} + + @Override + public Class type(Predicate.PredicateContext ctx) { + return Void.class; + } + + @Override + public boolean isNullNode() { + return true; + } + + @Override + public NullNode asNullNode() { + return this; + } + + @Override + public String toString() { + return "null"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof NullNode)) return false; + + return true; + } + } + + class UndefinedNode extends ValueNode { + + @Override + public Class type(Predicate.PredicateContext ctx) { + return Void.class; + } + + public UndefinedNode asUndefinedNode() { + return this; + } + + public boolean isUndefinedNode() { + return true; + } + + @Override + public boolean equals(Object o) { + return false; + } + } + + class PredicateNode extends ValueNode { + + private final Predicate predicate; + + public PredicateNode(Predicate predicate) { + this.predicate = predicate; + } + + public Predicate getPredicate() { + return predicate; + } + + public PredicateNode asPredicateNode() { + return this; + } + + @Override + public Class type(Predicate.PredicateContext ctx) { + return Void.class; + } + + public boolean isPredicateNode() { + return true; + } + + @Override + public boolean equals(Object o) { + return false; + } + + @Override + public String toString() { + return predicate.toString(); + } + } + + class ValueListNode extends ValueNode implements Iterable { + + private List nodes = new ArrayList(); + + public ValueListNode(Collection values) { + for (Object value : values) { + nodes.add(toValueNode(value)); + } + } + + public boolean contains(ValueNode node){ + return nodes.contains(node); + } + + public boolean subsetof(ValueListNode right) { + for (ValueNode leftNode : nodes) { + if (!right.nodes.contains(leftNode)) { + return false; + } + } + return true; + } + + public List getNodes() { + return Collections.unmodifiableList(nodes); + } + + @Override + public Class type(Predicate.PredicateContext ctx) { + return List.class; + } + + public boolean isValueListNode() { + return true; + } + + public ValueListNode asValueListNode() { + return this; + } + + @Override + public String toString() { + return "[" + Utils.join(",", nodes) + "]"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ValueListNode)) return false; + + ValueListNode that = (ValueListNode) o; + + return nodes.equals(that.nodes); + } + + @Override + public Iterator iterator() { + return nodes.iterator(); + } + } + + class PathNode extends ValueNode { + + private static final Logger logger = LoggerFactory.getLogger(PathNode.class); + + private final Path path; + private final boolean existsCheck; + private final boolean shouldExist; + + PathNode(Path path) { + this(path, false, false); + } + + PathNode(CharSequence charSequence, boolean existsCheck, boolean shouldExist) { + this(PathCompiler.compile(charSequence.toString()), existsCheck, shouldExist); + } + + PathNode(Path path, boolean existsCheck, boolean shouldExist) { + this.path = path; + this.existsCheck = existsCheck; + this.shouldExist = shouldExist; + logger.trace("PathNode {} existsCheck: {}", path, existsCheck); + } + + public Path getPath() { + return path; + } + + public boolean isExistsCheck() { + return existsCheck; + } + + public boolean shouldExists() { + return shouldExist; + } + + @Override + public Class type(Predicate.PredicateContext ctx) { + return Void.class; + } + + public boolean isPathNode() { + return true; + } + + public PathNode asPathNode() { + return this; + } + + public PathNode asExistsCheck(boolean shouldExist) { + return new PathNode(path, true, shouldExist); + } + + @Override + public String toString() { + return existsCheck && ! shouldExist ? Utils.concat("!" , path.toString()) : path.toString(); + } + + public ValueNode evaluate(Predicate.PredicateContext ctx) { + if (isExistsCheck()) { + try { + Configuration c = Configuration.builder().jsonProvider(ctx.configuration().jsonProvider()).options(Option.REQUIRE_PROPERTIES).build(); + Object result = path.evaluate(ctx.item(), ctx.root(), c).getValue(false); + return result == JsonProvider.UNDEFINED ? FALSE : TRUE; + } catch (PathNotFoundException e) { + return FALSE; + } + } else { + try { + Object res; + if (ctx instanceof PredicateContextImpl) { + //This will use cache for document ($) queries + PredicateContextImpl ctxi = (PredicateContextImpl) ctx; + res = ctxi.evaluate(path); + } else { + Object doc = path.isRootPath() ? ctx.root() : ctx.item(); + res = path.evaluate(doc, ctx.root(), ctx.configuration()).getValue(); + } + res = ctx.configuration().jsonProvider().unwrap(res); + + if (res instanceof Number) return ValueNode.createNumberNode(res.toString()); + else if (res instanceof String) return ValueNode.createStringNode(res.toString(), false); + else if (res instanceof Boolean) return ValueNode.createBooleanNode(res.toString()); + else if (res == null) return NULL_NODE; + else if (ctx.configuration().jsonProvider().isArray(res)) return ValueNode.createJsonNode(ctx.configuration().mappingProvider().map(res, List.class, ctx.configuration())); + else if (ctx.configuration().jsonProvider().isMap(res)) return ValueNode.createJsonNode(ctx.configuration().mappingProvider().map(res, Map.class, ctx.configuration())); + else throw new JsonPathException("Could not convert " + res.toString() + " to a ValueNode"); + } catch (PathNotFoundException e) { + return UNDEFINED; + } + } + } + + + } +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java index 6386d6e2..d499afef 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java @@ -15,7 +15,7 @@ import java.util.List; public class Concatenate implements PathFunction { @Override public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) { - StringBuffer result = new StringBuffer(); + StringBuilder result = new StringBuilder(); if(ctx.configuration().jsonProvider().isArray(model)){ Iterable objects = ctx.configuration().jsonProvider().toIterable(model); for (Object obj : objects) { diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java new file mode 100644 index 00000000..3e4b36e2 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java @@ -0,0 +1,52 @@ +/* + * Copyright 2011 the original author or authors. + * 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 com.jayway.jsonpath.internal.path; + +import com.jayway.jsonpath.internal.PathRef; + +import static java.lang.String.format; + +public class ArrayIndexToken extends ArrayPathToken { + + private final ArrayIndexOperation arrayIndexOperation; + + ArrayIndexToken(final ArrayIndexOperation arrayIndexOperation) { + this.arrayIndexOperation = arrayIndexOperation; + } + + @Override + public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { + if (!checkArrayModel(currentPath, model, ctx)) + return; + if (arrayIndexOperation.isSingleIndexOperation()) { + handleArrayIndex(arrayIndexOperation.indexes().get(0), currentPath, model, ctx); + } else { + for (Integer index : arrayIndexOperation.indexes()) { + handleArrayIndex(index, currentPath, model, ctx); + } + } + } + + @Override + public String getPathFragment() { + return arrayIndexOperation.toString(); + } + + @Override + public boolean isTokenDefinite() { + return arrayIndexOperation.isSingleIndexOperation(); + } + +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayPathToken.java index 05d29d76..15a0f2da 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayPathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayPathToken.java @@ -22,143 +22,7 @@ import org.slf4j.LoggerFactory; import static java.lang.String.format; -/** - * - */ -public class ArrayPathToken extends PathToken { - - private static final Logger logger = LoggerFactory.getLogger(ArrayPathToken.class); - - private final ArraySliceOperation arraySliceOperation; - private final ArrayIndexOperation arrayIndexOperation; - - ArrayPathToken(final ArraySliceOperation arraySliceOperation) { - this.arraySliceOperation = arraySliceOperation; - this.arrayIndexOperation = null; - } - - ArrayPathToken(final ArrayIndexOperation arrayIndexOperation) { - this.arrayIndexOperation = arrayIndexOperation; - this.arraySliceOperation = null; - } - - @Override - public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { - if (! checkArrayModel(currentPath, model, ctx)) - return; - if(arraySliceOperation != null){ - evaluateSliceOperation(currentPath, parent, model, ctx); - } else { - evaluateIndexOperation(currentPath, parent, model, ctx); - } - - } - - public void evaluateIndexOperation(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { - - if (! checkArrayModel(currentPath, model, ctx)) - return; - - if(arrayIndexOperation.isSingleIndexOperation()){ - handleArrayIndex(arrayIndexOperation.indexes().get(0), currentPath, model, ctx); - } else { - for (Integer index : arrayIndexOperation.indexes()) { - handleArrayIndex(index, currentPath, model, ctx); - } - } - } - - public void evaluateSliceOperation(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { - - if (! checkArrayModel(currentPath, model, ctx)) - return; - - switch (arraySliceOperation.operation()) { - case SLICE_FROM: - sliceFrom(arraySliceOperation, currentPath, parent, model, ctx); - break; - case SLICE_BETWEEN: - sliceBetween(arraySliceOperation, currentPath, parent, model, ctx); - break; - case SLICE_TO: - sliceTo(arraySliceOperation, currentPath, parent, model, ctx); - break; - } - } - - public void sliceFrom(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { - int length = ctx.jsonProvider().length(model); - int from = operation.from(); - if (from < 0) { - //calculate slice start from array length - from = length + from; - } - from = Math.max(0, from); - - logger.debug("Slice from index on array with length: {}. From index: {} to: {}. Input: {}", length, from, length - 1, toString()); - - if (length == 0 || from >= length) { - return; - } - for (int i = from; i < length; i++) { - handleArrayIndex(i, currentPath, model, ctx); - } - } - - public void sliceBetween(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { - int length = ctx.jsonProvider().length(model); - int from = operation.from(); - int to = operation.to(); - - to = Math.min(length, to); - - if (from >= to || length == 0) { - return; - } - - logger.debug("Slice between indexes on array with length: {}. From index: {} to: {}. Input: {}", length, from, to, toString()); - - for (int i = from; i < to; i++) { - handleArrayIndex(i, currentPath, model, ctx); - } - } - - public void sliceTo(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { - int length = ctx.jsonProvider().length(model); - if (length == 0) { - return; - } - int to = operation.to(); - if (to < 0) { - //calculate slice end from array length - to = length + to; - } - to = Math.min(length, to); - - logger.debug("Slice to index on array with length: {}. From index: 0 to: {}. Input: {}", length, to, toString()); - - for (int i = 0; i < to; i++) { - handleArrayIndex(i, currentPath, model, ctx); - } - } - - @Override - public String getPathFragment() { - if(arrayIndexOperation != null){ - return arrayIndexOperation.toString(); - } else { - return arraySliceOperation.toString(); - } - } - - @Override - public boolean isTokenDefinite() { - if(arrayIndexOperation != null){ - return arrayIndexOperation.isSingleIndexOperation(); - } else { - return false; - } - } +public abstract class ArrayPathToken extends PathToken { /** * Check if model is non-null and array. diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceOperation.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceOperation.java index e6dcb8c1..185af512 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceOperation.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceOperation.java @@ -58,19 +58,19 @@ public class ArraySliceOperation { Integer tempFrom = tryRead(tokens, 0); Integer tempTo = tryRead(tokens, 1); - Operation tempOperpation; - - if(tempFrom != null && tempTo == null){ - tempOperpation = Operation.SLICE_FROM; - } else if(tempFrom != null && tempTo != null){ - tempOperpation = Operation.SLICE_BETWEEN; - } else if(tempFrom == null && tempTo != null){ - tempOperpation = Operation.SLICE_TO; + Operation tempOperation; + + if (tempFrom != null && tempTo == null) { + tempOperation = Operation.SLICE_FROM; + } else if (tempFrom != null) { + tempOperation = Operation.SLICE_BETWEEN; + } else if (tempTo != null) { + tempOperation = Operation.SLICE_TO; } else { throw new InvalidPathException("Failed to parse SliceOperation: " + operation); } - return new ArraySliceOperation(tempFrom, tempTo, tempOperpation); + return new ArraySliceOperation(tempFrom, tempTo, tempOperation); } private static Integer tryRead(String[] tokens, int idx){ diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceToken.java new file mode 100644 index 00000000..af43e7eb --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceToken.java @@ -0,0 +1,114 @@ +/* + * Copyright 2011 the original author or authors. + * 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 com.jayway.jsonpath.internal.path; + +import com.jayway.jsonpath.internal.PathRef; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ArraySliceToken extends ArrayPathToken { + + private static final Logger logger = LoggerFactory.getLogger(ArraySliceToken.class); + + private final ArraySliceOperation operation; + + ArraySliceToken(final ArraySliceOperation operation) { + this.operation = operation; + } + + @Override + public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { + if (!checkArrayModel(currentPath, model, ctx)) + return; + switch (operation.operation()) { + case SLICE_FROM: + sliceFrom(currentPath, parent, model, ctx); + break; + case SLICE_BETWEEN: + sliceBetween(currentPath, parent, model, ctx); + break; + case SLICE_TO: + sliceTo(currentPath, parent, model, ctx); + break; + } + } + + private void sliceFrom(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { + int length = ctx.jsonProvider().length(model); + int from = operation.from(); + if (from < 0) { + //calculate slice start from array length + from = length + from; + } + from = Math.max(0, from); + + logger.debug("Slice from index on array with length: {}. From index: {} to: {}. Input: {}", length, from, length - 1, toString()); + + if (length == 0 || from >= length) { + return; + } + for (int i = from; i < length; i++) { + handleArrayIndex(i, currentPath, model, ctx); + } + } + + private void sliceBetween(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { + int length = ctx.jsonProvider().length(model); + int from = operation.from(); + int to = operation.to(); + + to = Math.min(length, to); + + if (from >= to || length == 0) { + return; + } + + logger.debug("Slice between indexes on array with length: {}. From index: {} to: {}. Input: {}", length, from, to, toString()); + + for (int i = from; i < to; i++) { + handleArrayIndex(i, currentPath, model, ctx); + } + } + + private void sliceTo(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { + int length = ctx.jsonProvider().length(model); + if (length == 0) { + return; + } + int to = operation.to(); + if (to < 0) { + //calculate slice end from array length + to = length + to; + } + to = Math.min(length, to); + + logger.debug("Slice to index on array with length: {}. From index: 0 to: {}. Input: {}", length, to, toString()); + + for (int i = 0; i < to; i++) { + handleArrayIndex(i, currentPath, model, ctx); + } + } + + @Override + public String getPathFragment() { + return operation.toString(); + } + + @Override + public boolean isTokenDefinite() { + return false; + } + +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java index 383fde89..131e068f 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java @@ -97,7 +97,7 @@ public class CompiledPath implements Path { try { PathRef op = ctx.forUpdate() ? PathRef.createRoot(rootDocument) : PathRef.NO_OP; root.evaluate("", op, document, ctx); - } catch (EvaluationAbortException abort){}; + } catch (EvaluationAbortException abort) {} return ctx; } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java index 0317311e..e11c4c02 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java @@ -275,7 +275,7 @@ public class PathCompiler { Boolean endOfStream = false; char priorChar = 0; List parameters = new ArrayList(); - StringBuffer parameter = new StringBuffer(); + StringBuilder parameter = new StringBuilder(); while (path.inBounds() && !endOfStream) { char c = path.currentChar(); path.incrementPosition(1); @@ -580,7 +580,7 @@ public class PathCompiler { } break; } else if (c == potentialStringDelimiter) { - if (inProperty && !inEscape) { + if (inProperty) { char nextSignificantChar = path.nextSignificantChar(readPosition); if (nextSignificantChar != CLOSE_SQUARE_BRACKET && nextSignificantChar != COMMA) { fail("Property must be separated by comma or Property must be terminated close square bracket at index "+readPosition); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java index 995c73e4..b9dacb6d 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java @@ -23,11 +23,11 @@ public class PathTokenFactory { } public static PathToken createSliceArrayPathToken(final ArraySliceOperation arraySliceOperation) { - return new ArrayPathToken(arraySliceOperation); + return new ArraySliceToken(arraySliceOperation); } public static PathToken createIndexArrayPathToken(final ArrayIndexOperation arrayIndexOperation) { - return new ArrayPathToken(arrayIndexOperation); + return new ArrayIndexToken(arrayIndexOperation); } public static PathToken createWildCardPathToken() { diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PredicatePathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PredicatePathToken.java index af668634..ee67fc1e 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PredicatePathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PredicatePathToken.java @@ -20,6 +20,7 @@ import com.jayway.jsonpath.Predicate; import com.jayway.jsonpath.internal.PathRef; import java.util.Collection; +import java.util.Collections; import static java.lang.String.format; import static java.util.Arrays.asList; @@ -33,7 +34,7 @@ public class PredicatePathToken extends PathToken { private final Collection predicates; PredicatePathToken(Predicate filter) { - this.predicates = asList(filter); + this.predicates = Collections.singletonList(filter); } PredicatePathToken(Collection predicates) { diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java index 349af9f7..2d443ec1 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java @@ -27,7 +27,7 @@ public class RootPathToken extends PathToken { RootPathToken(char rootToken) { - this.rootToken = Character.toString(rootToken);; + this.rootToken = Character.toString(rootToken); this.tail = this; this.tokenCount = 1; } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/WildcardPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/WildcardPathToken.java index 37d02ec9..7f3ccec1 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/WildcardPathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/WildcardPathToken.java @@ -14,6 +14,8 @@ */ package com.jayway.jsonpath.internal.path; +import java.util.Collections; + import com.jayway.jsonpath.Option; import com.jayway.jsonpath.PathNotFoundException; import com.jayway.jsonpath.internal.PathRef; @@ -32,7 +34,7 @@ public class WildcardPathToken extends PathToken { public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { if (ctx.jsonProvider().isMap(model)) { for (String property : ctx.jsonProvider().getPropertyKeys(model)) { - handleObjectProperty(currentPath, model, ctx, asList(property)); + handleObjectProperty(currentPath, model, ctx, Collections.singletonList(property)); } } else if (ctx.jsonProvider().isArray(model)) { for (int idx = 0; idx < ctx.jsonProvider().length(model); idx++) { diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/cache/CacheProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/CacheProvider.java index 82d6f18f..fe5ced53 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/cache/CacheProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/CacheProvider.java @@ -6,7 +6,6 @@ import static com.jayway.jsonpath.internal.Utils.notNull; public class CacheProvider { private static Cache cache; - private static boolean cachingEnabled; public static void setCache(Cache cache){ notNull(cache, "Cache may not be null"); @@ -16,7 +15,6 @@ public class CacheProvider { } else { CacheProvider.cache = cache; } - cachingEnabled = !(CacheProvider.cache instanceof NOOPCache); } } @@ -36,4 +34,4 @@ public class CacheProvider { return new LRUCache(400); //return new NOOPCache(); } -} \ No newline at end of file +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java index 56cfb47c..df60a629 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java @@ -162,7 +162,7 @@ public abstract class AbstractJsonProvider implements JsonProvider { * @return an Iterable that iterates over the entries of an array */ @SuppressWarnings("unchecked") - public Iterable toIterable(Object obj) { + public Iterable toIterable(Object obj) { if (isArray(obj)) return ((Iterable) obj); else diff --git a/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java b/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java index e786ae83..74116b47 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java @@ -279,7 +279,8 @@ public class FilterTest extends BaseTest { Filter farr = parse("[?(@.foo == " + arr + ")]"); //Filter fobjF = parse("[?(@.foo == " + nest + ")]"); //Filter fobjT = parse("[?(@.bar == " + nest + ")]"); - assertThat(farr.apply(context)).isEqualTo(true); + boolean apply = farr.apply(context); + assertThat(apply).isEqualTo(true); //assertThat(fobjF.apply(context)).isEqualTo(false); //assertThat(fobjT.apply(context)).isEqualTo(true); }