+ * Or:
+ *
+ *
+ * List<Object> authors = JsonPath.read(json, "$.store.book[*].author")
+ *
+ *
+ * If the json path returns a single value (is definite):
+ *
+ *
+ * String author = JsonPath.read(json, "$.store.book[1].author")
+ *
+ */
+public class JsonPath {
+
+ private final Path path;
+
+ private JsonPath(String jsonPath, Predicate[] filters) {
+ notNull(jsonPath, "path can not be null");
+ this.path = PathCompiler.compile(jsonPath, filters);
+ }
+
+ /**
+ * Returns the string representation of this JsonPath
+ *
+ * @return path as String
+ */
+ public String getPath() {
+ return this.path.toString();
+ }
+
+ /**
+ * @see JsonPath#isDefinite()
+ */
+ public static boolean isPathDefinite(String path) {
+ return compile(path).isDefinite();
+ }
+
+
+ /**
+ * Checks if a path points to a single item or if it potentially returns multiple items
+ *
+ * a path is considered not definite if it contains a scan fragment ".."
+ * or an array position fragment that is not based on a single index
+ *
+ *
+ * definite path examples are:
+ *
+ * $store.book
+ * $store.book[1].title
+ *
+ * not definite path examples are:
+ *
+ * $..book
+ * $.store.book[*]
+ * $.store.book[1,2]
+ * $.store.book[?(@.category = 'fiction')]
+ *
+ * @return true if path is definite (points to single item)
+ */
+ public boolean isDefinite() {
+ return path.isDefinite();
+ }
+
+ /**
+ * Applies this JsonPath to the provided json document.
+ * Note that the document must be identified as either a List or Map by
+ * the {@link JsonProvider}
+ *
+ * @param jsonObject a container Object
+ * @param expected return type
+ * @return object(s) matched by the given path
+ */
+ @SuppressWarnings({"unchecked"})
+ public T read(Object jsonObject) {
+ return read(jsonObject, Configuration.defaultConfiguration());
+ }
+
+ /**
+ * Applies this JsonPath to the provided json document.
+ * Note that the document must be identified as either a List or Map by
+ * the {@link JsonProvider}
+ *
+ * @param jsonObject a container Object
+ * @param configuration configuration to use
+ * @param expected return type
+ * @return object(s) matched by the given path
+ */
+ @SuppressWarnings("unchecked")
+ public T read(Object jsonObject, Configuration configuration) {
+ boolean optAsPathList = configuration.containsOption(AS_PATH_LIST);
+ boolean optAlwaysReturnList = configuration.containsOption(Option.ALWAYS_RETURN_LIST);
+ boolean optSuppressExceptions = configuration.containsOption(Option.SUPPRESS_EXCEPTIONS);
+
+ try {
+ if (path.isFunctionPath()) {
+ if (optAsPathList || optAlwaysReturnList) {
+ throw new JsonPathException("Options " + AS_PATH_LIST + " and " + ALWAYS_RETURN_LIST + " are not allowed when using path functions!");
+ }
+ return path.evaluate(jsonObject, jsonObject, configuration).getValue(true);
+
+ } else if (optAsPathList) {
+ return (T) path.evaluate(jsonObject, jsonObject, configuration).getPath();
+
+ } else {
+ Object res = path.evaluate(jsonObject, jsonObject, configuration).getValue(false);
+ if (optAlwaysReturnList && path.isDefinite()) {
+ Object array = configuration.jsonProvider().createArray();
+ configuration.jsonProvider().setArrayIndex(array, 0, res);
+ return (T) array;
+ } else {
+ return (T) res;
+ }
+ }
+ } catch (RuntimeException e) {
+ if (!optSuppressExceptions) {
+ throw e;
+ } else {
+ if (optAsPathList) {
+ return (T) configuration.jsonProvider().createArray();
+ } else {
+ if (optAlwaysReturnList) {
+ return (T) configuration.jsonProvider().createArray();
+ } else {
+ return (T) (path.isDefinite() ? null : configuration.jsonProvider().createArray());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the value this path points to in the provided jsonObject
+ *
+ * @param jsonObject a json object
+ * @param configuration configuration to use
+ * @param expected return type
+ * @return the updated jsonObject or the path list to updated objects if option AS_PATH_LIST is set.
+ */
+ public T set(Object jsonObject, Object newVal, Configuration configuration) {
+ notNull(jsonObject, "json can not be null");
+ notNull(configuration, "configuration can not be null");
+ EvaluationContext evaluationContext = path.evaluate(jsonObject, jsonObject, configuration, true);
+ for (PathRef updateOperation : evaluationContext.updateOperations()) {
+ updateOperation.set(newVal, configuration);
+ }
+ return resultByConfiguration(jsonObject, configuration, evaluationContext);
+ }
+
+
+ /**
+ * Replaces the value on the given path with the result of the {@link MapFunction}.
+ *
+ * @param jsonObject a json object
+ * @param mapFunction Converter object to be invoked
+ * @param configuration configuration to use
+ * @return the updated jsonObject or the path list to updated objects if option AS_PATH_LIST is set.
+ */
+ public T map(Object jsonObject, MapFunction mapFunction, Configuration configuration) {
+ notNull(jsonObject, "json can not be null");
+ notNull(configuration, "configuration can not be null");
+ notNull(mapFunction, "mapFunction can not be null");
+ EvaluationContext evaluationContext = path.evaluate(jsonObject, jsonObject, configuration, true);
+ for (PathRef updateOperation : evaluationContext.updateOperations()) {
+ updateOperation.convert(mapFunction, configuration);
+ }
+ return resultByConfiguration(jsonObject, configuration, evaluationContext);
+ }
+
+ /**
+ * Deletes the object this path points to in the provided jsonObject
+ *
+ * @param jsonObject a json object
+ * @param configuration configuration to use
+ * @param expected return type
+ * @return the updated jsonObject or the path list to deleted objects if option AS_PATH_LIST is set.
+ */
+ public T delete(Object jsonObject, Configuration configuration) {
+ notNull(jsonObject, "json can not be null");
+ notNull(configuration, "configuration can not be null");
+ EvaluationContext evaluationContext = path.evaluate(jsonObject, jsonObject, configuration, true);
+ for (PathRef updateOperation : evaluationContext.updateOperations()) {
+ updateOperation.delete(configuration);
+ }
+ return resultByConfiguration(jsonObject, configuration, evaluationContext);
+ }
+
+ /**
+ * Adds a new value to the Array this path points to in the provided jsonObject
+ *
+ * @param jsonObject a json object
+ * @param value the value to add
+ * @param configuration configuration to use
+ * @param expected return type
+ * @return the updated jsonObject or the path list to updated object if option AS_PATH_LIST is set.
+ */
+ public T add(Object jsonObject, Object value, Configuration configuration) {
+ notNull(jsonObject, "json can not be null");
+ notNull(configuration, "configuration can not be null");
+ EvaluationContext evaluationContext = path.evaluate(jsonObject, jsonObject, configuration, true);
+ for (PathRef updateOperation : evaluationContext.updateOperations()) {
+ updateOperation.add(value, configuration);
+ }
+ return resultByConfiguration(jsonObject, configuration, evaluationContext);
+ }
+
+ /**
+ * Adds or updates the Object this path points to in the provided jsonObject with a key with a value
+ *
+ * @param jsonObject a json object
+ * @param key the key to add or update
+ * @param value the new value
+ * @param configuration configuration to use
+ * @param expected return type
+ * @return the updated jsonObject or the path list to updated objects if option AS_PATH_LIST is set.
+ */
+ public T put(Object jsonObject, String key, Object value, Configuration configuration) {
+ notNull(jsonObject, "json can not be null");
+ notEmpty(key, "key can not be null or empty");
+ notNull(configuration, "configuration can not be null");
+ EvaluationContext evaluationContext = path.evaluate(jsonObject, jsonObject, configuration, true);
+ for (PathRef updateOperation : evaluationContext.updateOperations()) {
+ updateOperation.put(key, value, configuration);
+ }
+ return resultByConfiguration(jsonObject, configuration, evaluationContext);
+ }
+
+ public T renameKey(Object jsonObject, String oldKeyName, String newKeyName, Configuration configuration) {
+ notNull(jsonObject, "json can not be null");
+ notEmpty(newKeyName, "newKeyName can not be null or empty");
+ notNull(configuration, "configuration can not be null");
+ EvaluationContext evaluationContext = path.evaluate(jsonObject, jsonObject, configuration, true);
+ for (PathRef updateOperation : evaluationContext.updateOperations()) {
+ updateOperation.renameKey(oldKeyName, newKeyName, configuration);
+ }
+ return resultByConfiguration(jsonObject, configuration, evaluationContext);
+ }
+
+ /**
+ * Applies this JsonPath to the provided json string
+ *
+ * @param json a json string
+ * @param expected return type
+ * @return list of objects matched by the given path
+ */
+ @SuppressWarnings({"unchecked"})
+ public T read(String json) {
+ return read(json, Configuration.defaultConfiguration());
+ }
+
+ /**
+ * Applies this JsonPath to the provided json string
+ *
+ * @param json a json string
+ * @param configuration configuration to use
+ * @param expected return type
+ * @return list of objects matched by the given path
+ */
+ @SuppressWarnings({"unchecked"})
+ public T read(String json, Configuration configuration) {
+ notEmpty(json, "json can not be null or empty");
+ notNull(configuration, "jsonProvider can not be null");
+
+ return read(configuration.jsonProvider().parse(json), configuration);
+ }
+
+ /**
+ * Applies this JsonPath to the provided json URL
+ *
+ * @param jsonURL url to read from
+ * @param expected return type
+ * @return list of objects matched by the given path
+ * @throws IOException
+ */
+ @SuppressWarnings({"unchecked"})
+ public T read(URL jsonURL) throws IOException {
+ return read(jsonURL, Configuration.defaultConfiguration());
+ }
+
+ /**
+ * Applies this JsonPath to the provided json file
+ *
+ * @param jsonFile file to read from
+ * @param expected return type
+ * @return list of objects matched by the given path
+ * @throws IOException
+ */
+ @SuppressWarnings({"unchecked"})
+ public T read(File jsonFile) throws IOException {
+ return read(jsonFile, Configuration.defaultConfiguration());
+ }
+
+
+ /**
+ * Applies this JsonPath to the provided json file
+ *
+ * @param jsonFile file to read from
+ * @param configuration configuration to use
+ * @param expected return type
+ * @return list of objects matched by the given path
+ * @throws IOException
+ */
+ @SuppressWarnings({"unchecked"})
+ public T read(File jsonFile, Configuration configuration) throws IOException {
+ notNull(jsonFile, "json file can not be null");
+ isTrue(jsonFile.exists(), "json file does not exist");
+ notNull(configuration, "jsonProvider can not be null");
+
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(jsonFile);
+ return read(fis, configuration);
+ } finally {
+ Utils.closeQuietly(fis);
+ }
+ }
+
+ /**
+ * Applies this JsonPath to the provided json input stream
+ *
+ * @param jsonInputStream input stream to read from
+ * @param expected return type
+ * @return list of objects matched by the given path
+ * @throws IOException
+ */
+ @SuppressWarnings({"unchecked"})
+ public T read(InputStream jsonInputStream) throws IOException {
+ return read(jsonInputStream, Configuration.defaultConfiguration());
+ }
+
+ /**
+ * Applies this JsonPath to the provided json input stream
+ *
+ * @param jsonInputStream input stream to read from
+ * @param configuration configuration to use
+ * @param expected return type
+ * @return list of objects matched by the given path
+ * @throws IOException
+ */
+ @SuppressWarnings({"unchecked"})
+ public T read(InputStream jsonInputStream, Configuration configuration) throws IOException {
+ notNull(jsonInputStream, "json input stream can not be null");
+ notNull(configuration, "configuration can not be null");
+
+ return read(jsonInputStream, "UTF-8", configuration);
+ }
+
+ /**
+ * Applies this JsonPath to the provided json input stream
+ *
+ * @param jsonInputStream input stream to read from
+ * @param configuration configuration to use
+ * @param expected return type
+ * @return list of objects matched by the given path
+ * @throws IOException
+ */
+ @SuppressWarnings({"unchecked"})
+ public T read(InputStream jsonInputStream, String charset, Configuration configuration) throws IOException {
+ notNull(jsonInputStream, "json input stream can not be null");
+ notNull(charset, "charset can not be null");
+ notNull(configuration, "configuration can not be null");
+
+ try {
+ return read(configuration.jsonProvider().parse(jsonInputStream, charset), configuration);
+ } finally {
+ Utils.closeQuietly(jsonInputStream);
+ }
+ }
+
+ // --------------------------------------------------------
+ //
+ // Static factory methods
+ //
+ // --------------------------------------------------------
+
+ /**
+ * Compiles a JsonPath
+ *
+ * @param jsonPath to compile
+ * @param filters filters to be applied to the filter place holders [?] in the path
+ * @return compiled JsonPath
+ */
+ public static JsonPath compile(String jsonPath, Predicate... filters) {
+ notEmpty(jsonPath, "json can not be null or empty");
+
+ return new JsonPath(jsonPath, filters);
+ }
+
+
+ // --------------------------------------------------------
+ //
+ // Static utility functions
+ //
+ // --------------------------------------------------------
+
+ /**
+ * Creates a new JsonPath and applies it to the provided Json object
+ *
+ * @param json a json object
+ * @param jsonPath the json path
+ * @param filters filters to be applied to the filter place holders [?] in the path
+ * @param expected return type
+ * @return list of objects matched by the given path
+ */
+ @SuppressWarnings({"unchecked"})
+ public static T read(Object json, String jsonPath, Predicate... filters) {
+ return parse(json).read(jsonPath, filters);
+ }
+
+ /**
+ * Creates a new JsonPath and applies it to the provided Json string
+ *
+ * @param json a json string
+ * @param jsonPath the json path
+ * @param filters filters to be applied to the filter place holders [?] in the path
+ * @param expected return type
+ * @return list of objects matched by the given path
+ */
+ @SuppressWarnings({"unchecked"})
+ public static T read(String json, String jsonPath, Predicate... filters) {
+ return new ParseContextImpl().parse(json).read(jsonPath, filters);
+ }
+
+
+ /**
+ * Creates a new JsonPath and applies it to the provided Json object
+ *
+ * @param jsonURL url pointing to json doc
+ * @param jsonPath the json path
+ * @param filters filters to be applied to the filter place holders [?] in the path
+ * @param expected return type
+ * @return list of objects matched by the given path
+ */
+ @SuppressWarnings({"unchecked"})
+ @Deprecated
+ public static T read(URL jsonURL, String jsonPath, Predicate... filters) throws IOException {
+ return new ParseContextImpl().parse(jsonURL).read(jsonPath, filters);
+ }
+
+ /**
+ * Creates a new JsonPath and applies it to the provided Json object
+ *
+ * @param jsonFile json file
+ * @param jsonPath the json path
+ * @param filters filters to be applied to the filter place holders [?] in the path
+ * @param expected return type
+ * @return list of objects matched by the given path
+ */
+ @SuppressWarnings({"unchecked"})
+ public static T read(File jsonFile, String jsonPath, Predicate... filters) throws IOException {
+ return new ParseContextImpl().parse(jsonFile).read(jsonPath, filters);
+ }
+
+ /**
+ * Creates a new JsonPath and applies it to the provided Json object
+ *
+ * @param jsonInputStream json input stream
+ * @param jsonPath the json path
+ * @param filters filters to be applied to the filter place holders [?] in the path
+ * @param expected return type
+ * @return list of objects matched by the given path
+ */
+ @SuppressWarnings({"unchecked"})
+ public static T read(InputStream jsonInputStream, String jsonPath, Predicate... filters) throws IOException {
+ return new ParseContextImpl().parse(jsonInputStream).read(jsonPath, filters);
+ }
+
+
+ // --------------------------------------------------------
+ //
+ // Static Fluent API
+ //
+ // --------------------------------------------------------
+
+
+ /**
+ * Creates a {@link ParseContext} that can be used to parse JSON input. The parse context
+ * is as thread safe as the underlying {@link JsonProvider}. Note that not all JsonProvider are
+ * thread safe.
+ *
+ * @param configuration configuration to use when parsing JSON
+ * @return a parsing context based on given configuration
+ */
+ public static ParseContext using(Configuration configuration) {
+ return new ParseContextImpl(configuration);
+ }
+
+ /**
+ * Creates a {@link ParseContext} that will parse a given JSON input.
+ *
+ * @param provider jsonProvider to use when parsing JSON
+ * @return a parsing context based on given jsonProvider
+ */
+ @Deprecated
+ public static ParseContext using(JsonProvider provider) {
+ return new ParseContextImpl(Configuration.builder().jsonProvider(provider).build());
+ }
+
+ /**
+ * Parses the given JSON input using the default {@link Configuration} and
+ * returns a {@link DocumentContext} for path evaluation
+ *
+ * @param json input
+ * @return a read context
+ */
+ public static DocumentContext parse(Object json) {
+ return new ParseContextImpl().parse(json);
+ }
+
+ /**
+ * Parses the given JSON input using the default {@link Configuration} and
+ * returns a {@link DocumentContext} for path evaluation
+ *
+ * @param json string
+ * @return a read context
+ */
+ public static DocumentContext parse(String json) {
+ return new ParseContextImpl().parse(json);
+ }
+
+ /**
+ * Parses the given JSON input using the default {@link Configuration} and
+ * returns a {@link DocumentContext} for path evaluation
+ *
+ * @param json stream
+ * @return a read context
+ */
+ public static DocumentContext parse(InputStream json) {
+ return new ParseContextImpl().parse(json);
+ }
+
+ /**
+ * Parses the given JSON input using the default {@link Configuration} and
+ * returns a {@link DocumentContext} for path evaluation
+ *
+ * @param json file
+ * @return a read context
+ */
+ public static DocumentContext parse(File json) throws IOException {
+ return new ParseContextImpl().parse(json);
+ }
+
+ /**
+ * Parses the given JSON input using the default {@link Configuration} and
+ * returns a {@link DocumentContext} for path evaluation
+ *
+ * @param json url
+ * @return a read context
+ */
+ @Deprecated
+ public static DocumentContext parse(URL json) throws IOException {
+ return new ParseContextImpl().parse(json);
+ }
+
+ /**
+ * Parses the given JSON input using the provided {@link Configuration} and
+ * returns a {@link DocumentContext} for path evaluation
+ *
+ * @param json input
+ * @return a read context
+ */
+ public static DocumentContext parse(Object json, Configuration configuration) {
+ return new ParseContextImpl(configuration).parse(json);
+ }
+
+ /**
+ * Parses the given JSON input using the provided {@link Configuration} and
+ * returns a {@link DocumentContext} for path evaluation
+ *
+ * @param json input
+ * @return a read context
+ */
+ public static DocumentContext parse(String json, Configuration configuration) {
+ return new ParseContextImpl(configuration).parse(json);
+ }
+
+ /**
+ * Parses the given JSON input using the provided {@link Configuration} and
+ * returns a {@link DocumentContext} for path evaluation
+ *
+ * @param json input
+ * @return a read context
+ */
+ public static DocumentContext parse(InputStream json, Configuration configuration) {
+ return new ParseContextImpl(configuration).parse(json);
+ }
+
+ /**
+ * Parses the given JSON input using the provided {@link Configuration} and
+ * returns a {@link DocumentContext} for path evaluation
+ *
+ * @param json input
+ * @return a read context
+ */
+ public static DocumentContext parse(File json, Configuration configuration) throws IOException {
+ return new ParseContextImpl(configuration).parse(json);
+ }
+
+ /**
+ * Parses the given JSON input using the provided {@link Configuration} and
+ * returns a {@link DocumentContext} for path evaluation
+ *
+ * @param json input
+ * @return a read context
+ */
+ @Deprecated
+ public static DocumentContext parse(URL json, Configuration configuration) throws IOException {
+ return new ParseContextImpl(configuration).parse(json);
+ }
+
+ private T resultByConfiguration(Object jsonObject, Configuration configuration, EvaluationContext evaluationContext) {
+ if(configuration.containsOption(AS_PATH_LIST)){
+ return (T)evaluationContext.getPathList();
+ } else {
+ return (T) jsonObject;
+ }
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/JsonPathException.java b/app/src/main/java/com/jayway/jsonpath/JsonPathException.java
new file mode 100644
index 000000000..37d21ac7a
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/JsonPathException.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+public class JsonPathException extends RuntimeException {
+
+ public JsonPathException() {
+ }
+
+ public JsonPathException(String message) {
+ super(message);
+ }
+
+ public JsonPathException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public JsonPathException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/MapFunction.java b/app/src/main/java/com/jayway/jsonpath/MapFunction.java
new file mode 100644
index 000000000..59e8d7f3f
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/MapFunction.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+
+/**
+ * Returns a new representation for the input value.
+ */
+public interface MapFunction {
+ Object map(Object currentValue, Configuration configuration);
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/Option.java b/app/src/main/java/com/jayway/jsonpath/Option.java
new file mode 100644
index 000000000..412703fdb
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/Option.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+public enum Option {
+
+ /**
+ * returns null for missing leaf.
+ *
+ *
+ *
+ * the path :
+ *
+ * "$[*].bar"
+ *
+ * Without flag ["bar1"] is returned
+ * With flag ["bar1", null] is returned
+ *
+ *
+ */
+ DEFAULT_PATH_LEAF_TO_NULL,
+
+ /**
+ * Makes this implementation more compliant to the Goessner spec. All results are returned as Lists.
+ */
+ ALWAYS_RETURN_LIST,
+
+ /**
+ * Returns a list of path strings representing the path of the evaluation hits
+ */
+ AS_PATH_LIST,
+
+ /**
+ * Suppress all exceptions when evaluating path.
+ *
+ * If an exception is thrown and the option {@link Option#ALWAYS_RETURN_LIST} an empty list is returned.
+ * If an exception is thrown and the option {@link Option#ALWAYS_RETURN_LIST} is not present null is returned.
+ */
+ SUPPRESS_EXCEPTIONS,
+
+ /**
+ * Configures JsonPath to require properties defined in path when an indefinite path is evaluated.
+ *
+ *
+ * Given:
+ *
+ *
+ *
+ * evaluating the path "$[*].b"
+ *
+ * If REQUIRE_PROPERTIES option is present PathNotFoundException is thrown.
+ * If REQUIRE_PROPERTIES option is not present ["b-val"] is returned.
+ */
+ REQUIRE_PROPERTIES
+
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/ParseContext.java b/app/src/main/java/com/jayway/jsonpath/ParseContext.java
new file mode 100644
index 000000000..7aad7292d
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/ParseContext.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * Parses JSON as specified by the used {@link com.jayway.jsonpath.spi.json.JsonProvider}.
+ */
+public interface ParseContext {
+
+ DocumentContext parse(String json);
+
+ DocumentContext parse(Object json);
+
+ DocumentContext parse(InputStream json);
+
+ DocumentContext parse(InputStream json, String charset);
+
+ DocumentContext parse(File json) throws IOException;
+
+ @Deprecated
+ DocumentContext parse(URL json) throws IOException;
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/PathNotFoundException.java b/app/src/main/java/com/jayway/jsonpath/PathNotFoundException.java
new file mode 100644
index 000000000..0b978976e
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/PathNotFoundException.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+public class PathNotFoundException extends InvalidPathException {
+
+ public PathNotFoundException() {
+ }
+
+ public PathNotFoundException(String message) {
+ super(message);
+ }
+
+ public PathNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public PathNotFoundException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/Predicate.java b/app/src/main/java/com/jayway/jsonpath/Predicate.java
new file mode 100644
index 000000000..1d19991ea
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/Predicate.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import com.jayway.jsonpath.spi.mapper.MappingException;
+
+/**
+ *
+ */
+public interface Predicate {
+
+ boolean apply(PredicateContext ctx);
+
+ public interface PredicateContext {
+
+ /**
+ * Returns the current item being evaluated by this predicate
+ * @return current document
+ */
+ Object item();
+
+ /**
+ * Returns the current item being evaluated by this predicate. It will be mapped
+ * to the provided class
+ * @return current document
+ */
+ T item(Class clazz) throws MappingException;
+
+ /**
+ * Returns the root document (the complete JSON)
+ * @return root document
+ */
+ Object root();
+
+ /**
+ * Configuration to use when evaluating
+ * @return configuration
+ */
+ Configuration configuration();
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/ReadContext.java b/app/src/main/java/com/jayway/jsonpath/ReadContext.java
new file mode 100644
index 000000000..45bbd8c87
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/ReadContext.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+public interface ReadContext {
+
+ /**
+ * Returns the configuration used for reading
+ *
+ * @return an immutable configuration
+ */
+ Configuration configuration();
+
+ /**
+ * Returns the JSON model that this context is operating on
+ *
+ * @return json model
+ */
+ T json();
+
+ /**
+ * Returns the JSON model that this context is operating on as a JSON string
+ *
+ * @return json model as string
+ */
+ String jsonString();
+
+ /**
+ * Reads the given path from this context
+ *
+ * @param path path to read
+ * @param filters filters
+ * @param
+ * @return result
+ */
+ T read(String path, Predicate... filters);
+
+ /**
+ * Reads the given path from this context
+ *
+ * @param path path to read
+ * @param type expected return type (will try to map)
+ * @param filters filters
+ * @param
+ * @return result
+ */
+ T read(String path, Class type, Predicate... filters);
+
+ /**
+ * Reads the given path from this context
+ *
+ * @param path path to apply
+ * @param
+ * @return result
+ */
+ T read(JsonPath path);
+
+ /**
+ * Reads the given path from this context
+ *
+ * @param path path to apply
+ * @param type expected return type (will try to map)
+ * @param
+ * @return result
+ */
+ T read(JsonPath path, Class type);
+
+ /**
+ * Reads the given path from this context
+ *
+ * Sample code to create a TypeRef
+ *
+ * TypeRef ref = new TypeRef>() {};
+ *
+ *
+ * @param path path to apply
+ * @param typeRef expected return type (will try to map)
+ * @param
+ * @return result
+ */
+ T read(JsonPath path, TypeRef typeRef);
+
+ /**
+ * Reads the given path from this context
+ *
+ * Sample code to create a TypeRef
+ *
+ * TypeRef ref = new TypeRef>() {};
+ *
+ *
+ * @param path path to apply
+ * @param typeRef expected return type (will try to map)
+ * @param
+ * @return result
+ */
+ T read(String path, TypeRef typeRef);
+
+ /**
+ * Stops evaluation when maxResults limit has been reached
+ * @param maxResults
+ * @return the read context
+ */
+ ReadContext limit(int maxResults);
+
+ /**
+ * Adds listener to the evaluation of this path
+ * @param listener listeners to add
+ * @return the read context
+ */
+ ReadContext withListeners(EvaluationListener... listener);
+
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/TypeRef.java b/app/src/main/java/com/jayway/jsonpath/TypeRef.java
new file mode 100644
index 000000000..fce40e6dd
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/TypeRef.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+
+/**
+ * Used to specify generic type information in {@link com.jayway.jsonpath.ReadContext}
+ *
+ *
+ * TypeRef ref = new TypeRef>() { };
+ *
+ *
+ * @param
+ */
+public abstract class TypeRef implements Comparable> {
+ protected final Type type;
+
+ protected TypeRef()
+ {
+ Type superClass = getClass().getGenericSuperclass();
+ if (superClass instanceof Class>) {
+ throw new IllegalArgumentException("No type info in TypeRef");
+ }
+ type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
+ }
+
+ public Type getType() { return type; }
+
+ /**
+ * The only reason we define this method (and require implementation
+ * of Comparable) is to prevent constructing a
+ * reference without type information.
+ */
+ @Override
+ public int compareTo(TypeRef o) {
+ return 0;
+ }
+}
+
diff --git a/app/src/main/java/com/jayway/jsonpath/ValueCompareException.java b/app/src/main/java/com/jayway/jsonpath/ValueCompareException.java
new file mode 100644
index 000000000..94d840947
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/ValueCompareException.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+public class ValueCompareException extends JsonPathException {
+
+ public ValueCompareException() {
+ }
+
+ /**
+ * Construct the exception with message capturing the classes for two objects.
+ *
+ * @param left first object
+ * @param right second object
+ */
+ public ValueCompareException(final Object left, final Object right) {
+ super(String.format("Can not compare a %1s with a %2s", left.getClass().getName(), right.getClass().getName()));
+ }
+
+ public ValueCompareException(String message) {
+ super(message);
+ }
+
+ public ValueCompareException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/WriteContext.java b/app/src/main/java/com/jayway/jsonpath/WriteContext.java
new file mode 100644
index 000000000..751cbc5d3
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/WriteContext.java
@@ -0,0 +1,168 @@
+/*
+ * 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;
+
+public interface WriteContext {
+
+ /**
+ * Returns the configuration used for reading
+ *
+ * @return an immutable configuration
+ */
+ Configuration configuration();
+
+ /**
+ * Returns the JSON model that this context is operating on
+ *
+ * @return json model
+ */
+ T json();
+
+ /**
+ * Returns the JSON model that this context is operating on as a JSON string
+ *
+ * @return json model as string
+ */
+ String jsonString();
+
+ /**
+ * Set the value a the given path
+ *
+ * @param path path to set
+ * @param newValue new value
+ * @param filters filters
+ * @return a document context
+ */
+ DocumentContext set(String path, Object newValue, Predicate... filters);
+
+ /**
+ * Set the value a the given path
+ *
+ * @param path path to set
+ * @param newValue new value
+ * @return a document context
+ */
+ DocumentContext set(JsonPath path, Object newValue);
+
+ /**
+ * Replaces the value on the given path with the result of the {@link MapFunction}.
+ *
+ * @param path path to be converted set
+ * @param mapFunction Converter object to be invoked
+ * @param filters filters
+ * @return a document context
+ */
+ DocumentContext map(String path, MapFunction mapFunction, Predicate... filters);
+
+ /**
+ * Replaces the value on the given path with the result of the {@link MapFunction}.
+ *
+ * @param path path to be converted set
+ * @param mapFunction Converter object to be invoked (or lambda:))
+ * @return a document context
+ */
+ DocumentContext map(JsonPath path, MapFunction mapFunction);
+
+ /**
+ * Deletes the given path
+ *
+ * @param path path to delete
+ * @param filters filters
+ * @return a document context
+ */
+ DocumentContext delete(String path, Predicate... filters);
+
+ /**
+ * Deletes the given path
+ *
+ * @param path path to delete
+ * @return a document context
+ */
+ DocumentContext delete(JsonPath path);
+
+ /**
+ * Add value to array
+ *
+ *
NOTE: This method changed in Lang version 2.0.
+ * It no longer trims the CharSequence.
+ * That functionality is available in isBlank().
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if the CharSequence is empty or null
+ * @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence)
+ */
+ public static boolean isEmpty(CharSequence cs) {
+ return cs == null || cs.length() == 0;
+ }
+
+ /**
+ * Used by the indexOf(CharSequence methods) as a green implementation of indexOf.
+ *
+ * @param cs the {@code CharSequence} to be processed
+ * @param searchChar the {@code CharSequence} to be searched for
+ * @param start the start index
+ * @return the index where the search sequence was found
+ */
+ static int indexOf(CharSequence cs, CharSequence searchChar, int start) {
+ return cs.toString().indexOf(searchChar.toString(), start);
+ }
+
+
+
+ //---------------------------------------------------------
+ //
+ // Validators
+ //
+ //---------------------------------------------------------
+
+ /**
+ *
Validate that the specified argument is not {@code null};
+ * otherwise throwing an exception with the specified message.
+ *
+ *
Validate.notNull(myObject, "The object must not be null");
+ *
+ * @param the object type
+ * @param object the object to check
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message
+ * @return the validated object (never {@code null} for method chaining)
+ * @throws NullPointerException if the object is {@code null}
+ */
+ public static T notNull(T object, String message, Object... values) {
+ if (object == null) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ return object;
+ }
+
+ /**
+ *
Validate that the argument condition is {@code true}; otherwise
+ * throwing an exception with the specified message. This method is useful when
+ * validating according to an arbitrary boolean expression, such as validating a
+ * primitive number or using your own custom validation expression.
+ *
+ *
Validate.isTrue(i > 0.0, "The value must be greater than zero: %d", i);
+ *
+ *
For performance reasons, the long value is passed as a separate parameter and
+ * appended to the exception message only in the case of an error.
+ *
+ * @param expression the boolean expression to check
+ * @param message
+ * @throws IllegalArgumentException if expression is {@code false}
+ */
+ public static void isTrue(boolean expression, String message) {
+ if (expression == false) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Check if one and only one condition is true; otherwise
+ * throw an exception with the specified message.
+ *
+ * @param message error describing message
+ * @param expressions the boolean expressions to check
+ * @throws IllegalArgumentException if zero or more than one expressions are true
+ */
+ public static void onlyOneIsTrue(final String message, final boolean... expressions) {
+ if (!onlyOneIsTrueNonThrow(expressions)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ public static boolean onlyOneIsTrueNonThrow(final boolean... expressions) {
+ int count = 0;
+ for (final boolean expression : expressions) {
+ if (expression && ++count > 1) {
+ return false;
+ }
+ }
+ return 1 == count;
+ }
+
+ /**
+ *
Validate that the specified argument character sequence is
+ * neither {@code null} nor a length of zero (no characters);
+ * otherwise throwing an exception with the specified message.
+ *
+ *
Validate.notEmpty(myString, "The string must not be empty");
+ *
+ * @param the character sequence type
+ * @param chars the character sequence to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @param values the optional values for the formatted exception message, null array not recommended
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is empty
+ */
+ public static T notEmpty(T chars, String message, Object... values) {
+ if (chars == null) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ if (chars.length() == 0) {
+ throw new IllegalArgumentException(String.format(message, values));
+ }
+ return chars;
+ }
+
+
+ //---------------------------------------------------------
+ //
+ // Converters
+ //
+ //---------------------------------------------------------
+ public static String toString(Object o) {
+ if (null == o) {
+ return null;
+ }
+ return o.toString();
+ }
+
+ private Utils() {
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/filter/Evaluator.java b/app/src/main/java/com/jayway/jsonpath/internal/filter/Evaluator.java
new file mode 100644
index 000000000..d0707d1a4
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/filter/Evaluator.java
@@ -0,0 +1,7 @@
+package com.jayway.jsonpath.internal.filter;
+
+import com.jayway.jsonpath.Predicate;
+
+public interface Evaluator {
+ boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx);
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java b/app/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java
new file mode 100644
index 000000000..cfbfdff7c
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java
@@ -0,0 +1,376 @@
+package com.jayway.jsonpath.internal.filter;
+
+import com.jayway.jsonpath.JsonPathException;
+import com.jayway.jsonpath.Predicate;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.jayway.jsonpath.internal.filter.ValueNodes.PatternNode;
+import static com.jayway.jsonpath.internal.filter.ValueNodes.ValueListNode;
+
+public class EvaluatorFactory {
+
+ private static final Map evaluators = new HashMap();
+
+ static {
+ evaluators.put(RelationalOperator.EXISTS, new ExistsEvaluator());
+ evaluators.put(RelationalOperator.NE, new NotEqualsEvaluator());
+ evaluators.put(RelationalOperator.TSNE, new TypeSafeNotEqualsEvaluator());
+ evaluators.put(RelationalOperator.EQ, new EqualsEvaluator());
+ evaluators.put(RelationalOperator.TSEQ, new TypeSafeEqualsEvaluator());
+ evaluators.put(RelationalOperator.LT, new LessThanEvaluator());
+ evaluators.put(RelationalOperator.LTE, new LessThanEqualsEvaluator());
+ evaluators.put(RelationalOperator.GT, new GreaterThanEvaluator());
+ evaluators.put(RelationalOperator.GTE, new GreaterThanEqualsEvaluator());
+ evaluators.put(RelationalOperator.REGEX, new RegexpEvaluator());
+ evaluators.put(RelationalOperator.SIZE, new SizeEvaluator());
+ evaluators.put(RelationalOperator.EMPTY, new EmptyEvaluator());
+ evaluators.put(RelationalOperator.IN, new InEvaluator());
+ evaluators.put(RelationalOperator.NIN, new NotInEvaluator());
+ evaluators.put(RelationalOperator.ALL, new AllEvaluator());
+ evaluators.put(RelationalOperator.CONTAINS, new ContainsEvaluator());
+ evaluators.put(RelationalOperator.MATCHES, new PredicateMatchEvaluator());
+ evaluators.put(RelationalOperator.TYPE, new TypeEvaluator());
+ evaluators.put(RelationalOperator.SUBSETOF, new SubsetOfEvaluator());
+ evaluators.put(RelationalOperator.ANYOF, new AnyOfEvaluator());
+ evaluators.put(RelationalOperator.NONEOF, new NoneOfEvaluator());
+ }
+
+ 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()){
+ throw new JsonPathException("Failed to evaluate exists expression");
+ }
+ return left.asBooleanNode().getBoolean() == right.asBooleanNode().getBoolean();
+ }
+ }
+
+ private static class NotEqualsEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ return !evaluators.get(RelationalOperator.EQ).evaluate(left, right, ctx);
+ }
+ }
+
+ private static class TypeSafeNotEqualsEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ return !evaluators.get(RelationalOperator.TSEQ).evaluate(left, right, ctx);
+ }
+ }
+
+ private static class EqualsEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ if(left.isJsonNode() && right.isJsonNode()){
+ return left.asJsonNode().equals(right.asJsonNode(), ctx);
+ } else {
+ return left.equals(right);
+ }
+ }
+ }
+
+ private static class TypeSafeEqualsEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ if(!left.getClass().equals(right.getClass())){
+ return false;
+ }
+ return evaluators.get(RelationalOperator.EQ).evaluate(left, right, ctx);
+ }
+ }
+
+ private static class TypeEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ return right.asClassNode().getClazz() == left.type(ctx);
+ }
+ }
+
+ private static class LessThanEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ if(left.isNumberNode() && right.isNumberNode()){
+ return left.asNumberNode().getNumber().compareTo(right.asNumberNode().getNumber()) < 0;
+ } if(left.isStringNode() && right.isStringNode()){
+ return left.asStringNode().getString().compareTo(right.asStringNode().getString()) < 0;
+ }
+ return false;
+ }
+ }
+
+ private static class LessThanEqualsEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ if(left.isNumberNode() && right.isNumberNode()){
+ return left.asNumberNode().getNumber().compareTo(right.asNumberNode().getNumber()) <= 0;
+ } if(left.isStringNode() && right.isStringNode()){
+ return left.asStringNode().getString().compareTo(right.asStringNode().getString()) <= 0;
+ }
+ return false;
+ }
+ }
+
+ private static class GreaterThanEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ if(left.isNumberNode() && right.isNumberNode()){
+ return left.asNumberNode().getNumber().compareTo(right.asNumberNode().getNumber()) > 0;
+ } else if(left.isStringNode() && right.isStringNode()){
+ return left.asStringNode().getString().compareTo(right.asStringNode().getString()) > 0;
+ }
+ return false;
+ }
+ }
+
+ private static class GreaterThanEqualsEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ if(left.isNumberNode() && right.isNumberNode()){
+ return left.asNumberNode().getNumber().compareTo(right.asNumberNode().getNumber()) >= 0;
+ } else if(left.isStringNode() && right.isStringNode()){
+ return left.asStringNode().getString().compareTo(right.asStringNode().getString()) >= 0;
+ }
+ return false;
+ }
+ }
+
+ private static class SizeEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ if (! right.isNumberNode()) {
+ return false;
+ }
+ int expectedSize = right.asNumberNode().getNumber().intValue();
+
+ if(left.isStringNode()){
+ return left.asStringNode().length() == expectedSize;
+ } else if(left.isJsonNode()){
+ return left.asJsonNode().length(ctx) == expectedSize;
+ }
+ return false;
+ }
+ }
+
+ private static class EmptyEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ if(left.isStringNode()){
+ return left.asStringNode().isEmpty() == right.asBooleanNode().getBoolean();
+ } else if(left.isJsonNode()){
+ return left.asJsonNode().isEmpty(ctx) == right.asBooleanNode().getBoolean();
+ }
+ return false;
+ }
+ }
+
+ private static class InEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ ValueListNode valueListNode;
+ if(right.isJsonNode()){
+ ValueNode vn = right.asJsonNode().asValueListNode(ctx);
+ if(vn.isUndefinedNode()){
+ return false;
+ } else {
+ valueListNode = vn.asValueListNode();
+ }
+ } else {
+ valueListNode = right.asValueListNode();
+ }
+ return valueListNode.contains(left);
+ }
+ }
+
+ private static class NotInEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ return !evaluators.get(RelationalOperator.IN).evaluate(left, right, ctx);
+ }
+ }
+
+ private static class AllEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ ValueListNode requiredValues = right.asValueListNode();
+
+ if(left.isJsonNode()){
+ ValueNode valueNode = left.asJsonNode().asValueListNode(ctx); //returns UndefinedNode if conversion is not possible
+ if(valueNode.isValueListNode()){
+ ValueListNode shouldContainAll = valueNode.asValueListNode();
+ for (ValueNode required : requiredValues) {
+ if(!shouldContainAll.contains(required)){
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private static class ContainsEvaluator 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;
+ else {
+ boolean res = valueNode.asValueListNode().contains(right);
+ return res;
+ }
+ }
+ return false;
+ }
+ }
+
+ private static class PredicateMatchEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ return right.asPredicateNode().getPredicate().apply(ctx);
+ }
+ }
+
+ private static class RegexpEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ if(!(left.isPatternNode() ^ right.isPatternNode())){
+ return false;
+ }
+
+ if (left.isPatternNode()) {
+ return matches(left.asPatternNode(), getInput(right));
+ } else {
+ return matches(right.asPatternNode(), getInput(left));
+ }
+ }
+
+ private boolean matches(PatternNode patternNode, String inputToMatch) {
+ return patternNode.getCompiledPattern().matcher(inputToMatch).matches();
+ }
+
+ private String getInput(ValueNode valueNode) {
+ String input = "";
+
+ if (valueNode.isStringNode() || valueNode.isNumberNode()) {
+ input = valueNode.asStringNode().getString();
+ } else if (valueNode.isBooleanNode()) {
+ input = valueNode.asBooleanNode().toString();
+ }
+
+ return input;
+ }
+ }
+
+ 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);
+ }
+ }
+
+ private static class AnyOfEvaluator 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();
+ }
+
+ for (ValueNode leftValueNode : leftValueListNode) {
+ for (ValueNode rightValueNode : rightValueListNode) {
+ if (leftValueNode.equals(rightValueNode)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ private static class NoneOfEvaluator 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();
+ }
+
+ for (ValueNode leftValueNode : leftValueListNode) {
+ for (ValueNode rightValueNode : rightValueListNode) {
+ if (leftValueNode.equals(rightValueNode)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/filter/ExpressionNode.java b/app/src/main/java/com/jayway/jsonpath/internal/filter/ExpressionNode.java
new file mode 100644
index 000000000..f5871c524
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/filter/ExpressionNode.java
@@ -0,0 +1,24 @@
+package com.jayway.jsonpath.internal.filter;
+
+import com.jayway.jsonpath.Predicate;
+
+public abstract class ExpressionNode implements Predicate {
+
+ public static ExpressionNode createExpressionNode(ExpressionNode right, LogicalOperator operator, ExpressionNode left){
+ if(operator == LogicalOperator.AND){
+ if((right instanceof LogicalExpressionNode) && ((LogicalExpressionNode)right).getOperator() == LogicalOperator.AND ){
+ LogicalExpressionNode len = (LogicalExpressionNode) right;
+ return len.append(left);
+ } else {
+ return LogicalExpressionNode.createLogicalAnd(left, right);
+ }
+ } else {
+ if((right instanceof LogicalExpressionNode) && ((LogicalExpressionNode)right).getOperator() == LogicalOperator.OR ){
+ LogicalExpressionNode len = (LogicalExpressionNode) right;
+ return len.append(left);
+ } else {
+ return LogicalExpressionNode.createLogicalOr(left, right);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java b/app/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java
new file mode 100644
index 000000000..30ae7b9f2
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java
@@ -0,0 +1,417 @@
+package com.jayway.jsonpath.internal.filter;
+
+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 java.util.ArrayList;
+import java.util.List;
+
+public class FilterCompiler {
+
+ private static final char DOC_CONTEXT = '$';
+ private static final char EVAL_CONTEXT = '@';
+
+ private static final char OPEN_SQUARE_BRACKET = '[';
+ private static final char CLOSE_SQUARE_BRACKET = ']';
+ private static final char OPEN_PARENTHESIS = '(';
+ private static final char CLOSE_PARENTHESIS = ')';
+ private static final char OPEN_OBJECT = '{';
+ private static final char CLOSE_OBJECT = '}';
+ private static final char OPEN_ARRAY = '[';
+ private static final char CLOSE_ARRAY = ']';
+
+ private static final char SINGLE_QUOTE = '\'';
+ private static final char DOUBLE_QUOTE = '"';
+
+ private static final char SPACE = ' ';
+ private static final char PERIOD = '.';
+
+ private static final char AND = '&';
+ private static final char OR = '|';
+
+ private static final char MINUS = '-';
+ private static final char LT = '<';
+ private static final char GT = '>';
+ private static final char EQ = '=';
+ private static final char TILDE = '~';
+ private static final char TRUE = 't';
+ private static final char FALSE = 'f';
+ private static final char NULL = 'n';
+ private static final char NOT = '!';
+ private static final char PATTERN = '/';
+ private static final char IGNORE_CASE = 'i';
+
+ private CharacterIndex filter;
+
+ public static Filter compile(String filterString) {
+ FilterCompiler compiler = new FilterCompiler(filterString);
+ return new CompiledFilter(compiler.compile());
+ }
+
+ private FilterCompiler(String filterString) {
+ filter = new CharacterIndex(filterString);
+ filter.trim();
+ if (!filter.currentCharIs('[') || !filter.lastCharIs(']')) {
+ throw new InvalidPathException("Filter must start with '[' and end with ']'. " + filterString);
+ }
+ filter.incrementPosition(1);
+ filter.decrementEndPosition(1);
+ filter.trim();
+ if (!filter.currentCharIs('?')) {
+ throw new InvalidPathException("Filter must start with '[?' and end with ']'. " + filterString);
+ }
+ filter.incrementPosition(1);
+ filter.trim();
+ if (!filter.currentCharIs('(') || !filter.lastCharIs(')')) {
+ throw new InvalidPathException("Filter must start with '[?(' and end with ')]'. " + filterString);
+ }
+ }
+
+ public Predicate compile() {
+ try {
+ final ExpressionNode result = readLogicalOR();
+ filter.skipBlanks();
+ if (filter.inBounds()) {
+ throw new InvalidPathException(String.format("Expected end of filter expression instead of: %s",
+ filter.subSequence(filter.position(), filter.length())));
+ }
+
+ return result;
+ } catch (InvalidPathException e){
+ throw e;
+ } catch (Exception e) {
+ throw new InvalidPathException("Failed to parse filter: " + filter + ", error on position: " + filter.position() + ", char: " + filter.currentChar());
+ }
+ }
+
+ private ValueNode readValueNode() {
+ switch (filter.skipBlanks().currentChar()) {
+ case DOC_CONTEXT : return readPath();
+ case EVAL_CONTEXT : return readPath();
+ case NOT:
+ filter.incrementPosition(1);
+ switch (filter.skipBlanks().currentChar()) {
+ case DOC_CONTEXT : return readPath();
+ case EVAL_CONTEXT : return readPath();
+ default: throw new InvalidPathException(String.format("Unexpected character: %c", NOT));
+ }
+ default : return readLiteral();
+ }
+ }
+
+ private ValueNode readLiteral(){
+ switch (filter.skipBlanks().currentChar()){
+ case SINGLE_QUOTE: return readStringLiteral(SINGLE_QUOTE);
+ case DOUBLE_QUOTE: return readStringLiteral(DOUBLE_QUOTE);
+ case TRUE: return readBooleanLiteral();
+ case FALSE: return readBooleanLiteral();
+ case MINUS: return readNumberLiteral();
+ case NULL: return readNullLiteral();
+ case OPEN_OBJECT: return readJsonLiteral();
+ case OPEN_ARRAY: return readJsonLiteral();
+ case PATTERN: return readPattern();
+ default: return readNumberLiteral();
+ }
+ }
+
+ /*
+ * LogicalOR = LogicalAND { '||' LogicalAND }
+ * LogicalAND = LogicalANDOperand { '&&' LogicalANDOperand }
+ * LogicalANDOperand = RelationalExpression | '(' LogicalOR ')' | '!' LogicalANDOperand
+ * RelationalExpression = Value [ RelationalOperator Value ]
+ */
+
+ private ExpressionNode readLogicalOR() {
+ final List ops = new ArrayList();
+ ops.add(readLogicalAND());
+
+ while (true) {
+ int savepoint = filter.position();
+ if (filter.hasSignificantSubSequence(LogicalOperator.OR.getOperatorString())) {
+ ops.add(readLogicalAND());
+ } else {
+ filter.setPosition(savepoint);
+ break;
+ }
+ }
+
+ return 1 == ops.size() ? ops.get(0) : LogicalExpressionNode.createLogicalOr(ops);
+ }
+
+ private ExpressionNode readLogicalAND() {
+ /// @fixme copy-pasted
+ final List ops = new ArrayList();
+ ops.add(readLogicalANDOperand());
+
+ while (true) {
+ int savepoint = filter.position();
+ if (filter.hasSignificantSubSequence(LogicalOperator.AND.getOperatorString())) {
+ ops.add(readLogicalANDOperand());
+ } else {
+ filter.setPosition(savepoint);
+ break;
+ }
+ }
+
+ return 1 == ops.size() ? ops.get(0) : LogicalExpressionNode.createLogicalAnd(ops);
+ }
+
+ private ExpressionNode readLogicalANDOperand() {
+ int savepoint = filter.skipBlanks().position();
+ if (filter.skipBlanks().currentCharIs(NOT)) {
+ filter.readSignificantChar(NOT);
+ switch (filter.skipBlanks().currentChar()) {
+ case DOC_CONTEXT:
+ case EVAL_CONTEXT:
+ filter.setPosition(savepoint);
+ break;
+ default:
+ final ExpressionNode op = readLogicalANDOperand();
+ return LogicalExpressionNode.createLogicalNot(op);
+ }
+ }
+ if (filter.skipBlanks().currentCharIs(OPEN_PARENTHESIS)) {
+ filter.readSignificantChar(OPEN_PARENTHESIS);
+ final ExpressionNode op = readLogicalOR();
+ filter.readSignificantChar(CLOSE_PARENTHESIS);
+ return op;
+ }
+
+ return readExpression();
+ }
+
+ private RelationalExpressionNode readExpression() {
+ ValueNode left = readValueNode();
+ int savepoint = filter.position();
+ try {
+ RelationalOperator operator = readRelationalOperator();
+ ValueNode right = readValueNode();
+ return new RelationalExpressionNode(left, operator, right);
+ }
+ catch (InvalidPathException exc) {
+ filter.setPosition(savepoint);
+ }
+
+ PathNode pathNode = left.asPathNode();
+ left = pathNode.asExistsCheck(pathNode.shouldExists());
+ RelationalOperator operator = RelationalOperator.EXISTS;
+ ValueNode right = left.asPathNode().shouldExists() ? ValueNodes.TRUE : ValueNodes.FALSE;
+ return new RelationalExpressionNode(left, operator, right);
+ }
+
+ private LogicalOperator readLogicalOperator(){
+ int begin = filter.skipBlanks().position();
+ int end = begin+1;
+
+ if(!filter.inBounds(end)){
+ throw new InvalidPathException("Expected boolean literal");
+ }
+ CharSequence logicalOperator = filter.subSequence(begin, end+1);
+ if(!logicalOperator.equals("||") && !logicalOperator.equals("&&")){
+ throw new InvalidPathException("Expected logical operator");
+ }
+ filter.incrementPosition(logicalOperator.length());
+
+ return LogicalOperator.fromString(logicalOperator.toString());
+ }
+
+ private RelationalOperator readRelationalOperator() {
+ int begin = filter.skipBlanks().position();
+
+ if(isRelationalOperatorChar(filter.currentChar())){
+ while (filter.inBounds() && isRelationalOperatorChar(filter.currentChar())) {
+ filter.incrementPosition(1);
+ }
+ } else {
+ while (filter.inBounds() && filter.currentChar() != SPACE) {
+ filter.incrementPosition(1);
+ }
+ }
+
+ CharSequence operator = filter.subSequence(begin, filter.position());
+ return RelationalOperator.fromString(operator.toString());
+ }
+
+ 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);
+ if("null".equals(nullValue.toString())){
+ filter.incrementPosition(nullValue.length());
+ return ValueNode.createNullNode();
+ }
+ }
+ throw new InvalidPathException("Expected value");
+ }
+
+ private JsonNode readJsonLiteral(){
+ int begin = filter.position();
+
+ char openChar = filter.currentChar();
+
+ assert openChar == OPEN_ARRAY || openChar == OPEN_OBJECT;
+
+ char closeChar = openChar == OPEN_ARRAY ? CLOSE_ARRAY : CLOSE_OBJECT;
+
+ int closingIndex = filter.indexOfMatchingCloseChar(filter.position(), openChar, closeChar, true, false);
+ if (closingIndex == -1) {
+ throw new InvalidPathException("String not closed. Expected " + SINGLE_QUOTE + " in " + filter);
+ } else {
+ filter.setPosition(closingIndex + 1);
+ }
+ CharSequence json = filter.subSequence(begin, filter.position());
+ return ValueNode.createJsonNode(json);
+
+ }
+
+ private PatternNode readPattern() {
+ int begin = filter.position();
+ int closingIndex = filter.nextIndexOfUnescaped(PATTERN);
+ if (closingIndex == -1) {
+ throw new InvalidPathException("Pattern not closed. Expected " + PATTERN + " in " + filter);
+ } else {
+ if(filter.inBounds(closingIndex+1)) {
+ int equalSignIndex = filter.nextIndexOf('=');
+ int endIndex = equalSignIndex > closingIndex ? equalSignIndex : filter.nextIndexOfUnescaped(CLOSE_PARENTHESIS);
+ CharSequence flags = filter.subSequence(closingIndex + 1, endIndex);
+ closingIndex += flags.length();
+ }
+ filter.setPosition(closingIndex + 1);
+ }
+ CharSequence pattern = filter.subSequence(begin, filter.position());
+ return ValueNode.createPatternNode(pattern);
+ }
+
+ private StringNode readStringLiteral(char endChar) {
+ int begin = filter.position();
+
+ int closingSingleQuoteIndex = filter.nextIndexOfUnescaped(endChar);
+ if (closingSingleQuoteIndex == -1) {
+ throw new InvalidPathException("String literal does not have matching quotes. Expected " + endChar + " in " + filter);
+ } else {
+ filter.setPosition(closingSingleQuoteIndex + 1);
+ }
+ CharSequence stringLiteral = filter.subSequence(begin, filter.position());
+ return ValueNode.createStringNode(stringLiteral, true);
+ }
+
+ private NumberNode readNumberLiteral() {
+ int begin = filter.position();
+
+ while (filter.inBounds() && filter.isNumberCharacter(filter.position())) {
+ filter.incrementPosition(1);
+ }
+ CharSequence numberLiteral = filter.subSequence(begin, filter.position());
+ return ValueNode.createNumberNode(numberLiteral);
+ }
+
+ private BooleanNode readBooleanLiteral() {
+ int begin = filter.position();
+ int end = filter.currentChar() == TRUE ? filter.position() + 3 : filter.position() + 4;
+
+ if(!filter.inBounds(end)){
+ throw new InvalidPathException("Expected boolean literal");
+ }
+ CharSequence boolValue = filter.subSequence(begin, end+1);
+ if(!boolValue.equals("true") && !boolValue.equals("false")){
+ throw new InvalidPathException("Expected boolean literal");
+ }
+ filter.incrementPosition(boolValue.length());
+
+ return ValueNode.createBooleanNode(boolValue);
+ }
+
+ private PathNode readPath() {
+ char previousSignificantChar = filter.previousSignificantChar();
+ int begin = filter.position();
+
+ filter.incrementPosition(1); //skip $ and @
+ while (filter.inBounds()) {
+ if (filter.currentChar() == OPEN_SQUARE_BRACKET) {
+ int closingSquareBracketIndex = filter.indexOfMatchingCloseChar(filter.position(), OPEN_SQUARE_BRACKET, CLOSE_SQUARE_BRACKET, true, false);
+ if (closingSquareBracketIndex == -1) {
+ throw new InvalidPathException("Square brackets does not match in filter " + filter);
+ } else {
+ filter.setPosition(closingSquareBracketIndex + 1);
+ }
+ }
+ boolean closingFunctionBracket = (filter.currentChar() == CLOSE_PARENTHESIS && currentCharIsClosingFunctionBracket(begin));
+ boolean closingLogicalBracket = (filter.currentChar() == CLOSE_PARENTHESIS && !closingFunctionBracket);
+
+ if (!filter.inBounds() || isRelationalOperatorChar(filter.currentChar()) || filter.currentChar() == SPACE || closingLogicalBracket) {
+ break;
+ } else {
+ filter.incrementPosition(1);
+ }
+ }
+
+ boolean shouldExists = !(previousSignificantChar == NOT);
+ CharSequence path = filter.subSequence(begin, filter.position());
+ return ValueNode.createPathNode(path, false, shouldExists);
+ }
+
+ private boolean expressionIsTerminated(){
+ char c = filter.currentChar();
+ if(c == CLOSE_PARENTHESIS || isLogicalOperatorChar(c)){
+ return true;
+ }
+ c = filter.nextSignificantChar();
+ if(c == CLOSE_PARENTHESIS || isLogicalOperatorChar(c)){
+ return true;
+ }
+ return false;
+ }
+
+ private boolean currentCharIsClosingFunctionBracket(int lowerBound){
+ if(filter.currentChar() != CLOSE_PARENTHESIS){
+ return false;
+ }
+ int idx = filter.indexOfPreviousSignificantChar();
+ if(idx == -1 || filter.charAt(idx) != OPEN_PARENTHESIS){
+ return false;
+ }
+ idx--;
+ while(filter.inBounds(idx) && idx > lowerBound){
+ if(filter.charAt(idx) == PERIOD){
+ return true;
+ }
+ idx--;
+ }
+ return false;
+ }
+
+ private boolean isLogicalOperatorChar(char c) {
+ return c == AND || c == OR;
+ }
+
+ private boolean isRelationalOperatorChar(char c) {
+ return c == LT || c == GT || c == EQ || c == TILDE || c == NOT;
+ }
+
+ private static final class CompiledFilter extends Filter {
+
+ private final Predicate predicate;
+
+ private CompiledFilter(Predicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public boolean apply(Predicate.PredicateContext ctx) {
+ return predicate.apply(ctx);
+ }
+
+ @Override
+ public String toString() {
+ String predicateString = predicate.toString();
+ if(predicateString.startsWith("(")){
+ return "[?" + predicateString + "]";
+ } else {
+ return "[?(" + predicateString + ")]";
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/filter/LogicalExpressionNode.java b/app/src/main/java/com/jayway/jsonpath/internal/filter/LogicalExpressionNode.java
new file mode 100644
index 000000000..57cb38847
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/filter/LogicalExpressionNode.java
@@ -0,0 +1,88 @@
+package com.jayway.jsonpath.internal.filter;
+
+import com.jayway.jsonpath.internal.Utils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class LogicalExpressionNode extends ExpressionNode {
+ protected List chain = new ArrayList();
+ private final LogicalOperator operator;
+
+ public static ExpressionNode createLogicalNot(ExpressionNode op) {
+ return new LogicalExpressionNode(op, LogicalOperator.NOT, null);
+ }
+
+ public static LogicalExpressionNode createLogicalOr(ExpressionNode left,ExpressionNode right){
+ return new LogicalExpressionNode(left, LogicalOperator.OR, right);
+ }
+
+ public static LogicalExpressionNode createLogicalOr(Collection operands){
+ return new LogicalExpressionNode(LogicalOperator.OR, operands);
+ }
+
+ public static LogicalExpressionNode createLogicalAnd(ExpressionNode left,ExpressionNode right){
+ return new LogicalExpressionNode(left, LogicalOperator.AND, right);
+ }
+
+ public static LogicalExpressionNode createLogicalAnd(Collection operands){
+ return new LogicalExpressionNode(LogicalOperator.AND, operands);
+ }
+
+ private LogicalExpressionNode(ExpressionNode left, LogicalOperator operator, ExpressionNode right) {
+ chain.add(left);
+ chain.add(right);
+ this.operator = operator;
+ }
+
+ private LogicalExpressionNode(LogicalOperator operator, Collection operands) {
+ chain.addAll(operands);
+ this.operator = operator;
+ }
+
+ public LogicalExpressionNode and(LogicalExpressionNode other){
+ return createLogicalAnd(this, other);
+ }
+
+ public LogicalExpressionNode or(LogicalExpressionNode other){
+ return createLogicalOr(this, other);
+ }
+
+ public LogicalOperator getOperator() {
+ return operator;
+ }
+
+ public LogicalExpressionNode append(ExpressionNode expressionNode) {
+ chain.add(0, expressionNode);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + Utils.join(" " + operator.getOperatorString() + " ", chain) + ")";
+ }
+
+ @Override
+ public boolean apply(PredicateContext ctx) {
+ if(operator == LogicalOperator.OR){
+ for (ExpressionNode expression : chain) {
+ if(expression.apply(ctx)){
+ return true;
+ }
+ }
+ return false;
+ } else if (operator == LogicalOperator.AND) {
+ for (ExpressionNode expression : chain) {
+ if(!expression.apply(ctx)){
+ return false;
+ }
+ }
+ return true;
+ } else {
+ ExpressionNode expression = chain.get(0);
+ return !expression.apply(ctx);
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/filter/LogicalOperator.java b/app/src/main/java/com/jayway/jsonpath/internal/filter/LogicalOperator.java
new file mode 100644
index 000000000..809a8560f
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/filter/LogicalOperator.java
@@ -0,0 +1,32 @@
+package com.jayway.jsonpath.internal.filter;
+
+import com.jayway.jsonpath.InvalidPathException;
+
+public enum LogicalOperator {
+
+ AND("&&"),
+ NOT("!"),
+ OR("||");
+
+ private final String operatorString;
+
+ LogicalOperator(String operatorString) {
+ this.operatorString = operatorString;
+ }
+
+ public String getOperatorString() {
+ return operatorString;
+ }
+
+ @Override
+ public String toString() {
+ return operatorString;
+ }
+
+ public static LogicalOperator fromString(String operatorString){
+ if(AND.operatorString.equals(operatorString)) return AND;
+ else if(NOT.operatorString.equals(operatorString)) return NOT;
+ else if(OR.operatorString.equals(operatorString)) return OR;
+ else throw new InvalidPathException("Failed to parse operator " + operatorString);
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/filter/PatternFlag.java b/app/src/main/java/com/jayway/jsonpath/internal/filter/PatternFlag.java
new file mode 100644
index 000000000..3ac051288
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/filter/PatternFlag.java
@@ -0,0 +1,48 @@
+package com.jayway.jsonpath.internal.filter;
+
+import java.util.regex.Pattern;
+
+public enum PatternFlag {
+ UNIX_LINES(Pattern.UNIX_LINES, 'd'),
+ CASE_INSENSITIVE(Pattern.CASE_INSENSITIVE, 'i'),
+ COMMENTS(Pattern.COMMENTS, 'x'),
+ MULTILINE(Pattern.MULTILINE, 'm'),
+ DOTALL(Pattern.DOTALL, 's'),
+ UNICODE_CASE(Pattern.UNICODE_CASE, 'u'),
+ UNICODE_CHARACTER_CLASS(Pattern.UNICODE_CHARACTER_CLASS, 'U');
+
+ private final int code;
+ private final char flag;
+
+ private PatternFlag(int code, char flag) {
+ this.code = code;
+ this.flag = flag;
+ }
+
+ public static int parseFlags(char[] flags) {
+ int flagsValue = 0;
+ for (char flag : flags) {
+ flagsValue |= getCodeByFlag(flag);
+ }
+ return flagsValue;
+ }
+
+ public static String parseFlags(int flags) {
+ StringBuilder builder = new StringBuilder();
+ for (PatternFlag patternFlag : PatternFlag.values()) {
+ if ((patternFlag.code & flags) == patternFlag.code) {
+ builder.append(patternFlag.flag);
+ }
+ }
+ return builder.toString();
+ }
+
+ private static int getCodeByFlag(char flag) {
+ for (PatternFlag patternFlag : PatternFlag.values()) {
+ if (patternFlag.flag == flag) {
+ return patternFlag.code;
+ }
+ }
+ return 0;
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/filter/RelationalExpressionNode.java b/app/src/main/java/com/jayway/jsonpath/internal/filter/RelationalExpressionNode.java
new file mode 100644
index 000000000..0be33fe0f
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/filter/RelationalExpressionNode.java
@@ -0,0 +1,42 @@
+package com.jayway.jsonpath.internal.filter;
+
+
+public class RelationalExpressionNode extends ExpressionNode {
+
+ private final ValueNode left;
+ private final RelationalOperator relationalOperator;
+ private final ValueNode right;
+
+ public RelationalExpressionNode(ValueNode left, RelationalOperator relationalOperator, ValueNode right) {
+ this.left = left;
+ this.relationalOperator = relationalOperator;
+ this.right = right;
+ }
+
+ @Override
+ public String toString() {
+ if(relationalOperator == RelationalOperator.EXISTS){
+ return left.toString();
+ } else {
+ return left.toString() + " " + relationalOperator.toString() + " " + right.toString();
+ }
+ }
+
+ @Override
+ public boolean apply(PredicateContext ctx) {
+ ValueNode l = left;
+ ValueNode r = right;
+
+ if(left.isPathNode()){
+ l = left.asPathNode().evaluate(ctx);
+ }
+ if(right.isPathNode()){
+ r = right.asPathNode().evaluate(ctx);
+ }
+ Evaluator evaluator = EvaluatorFactory.createEvaluator(relationalOperator);
+ if(evaluator != null){
+ return evaluator.evaluate(l, r, ctx);
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java b/app/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java
new file mode 100644
index 000000000..84e2aed01
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java
@@ -0,0 +1,56 @@
+package com.jayway.jsonpath.internal.filter;
+
+import com.jayway.jsonpath.InvalidPathException;
+
+public enum RelationalOperator {
+
+ GTE(">="),
+ LTE("<="),
+ EQ("=="),
+
+ /**
+ * Type safe equals
+ */
+ TSEQ("==="),
+ NE("!="),
+
+ /**
+ * Type safe not equals
+ */
+ TSNE("!=="),
+ LT("<"),
+ GT(">"),
+ REGEX("=~"),
+ NIN("NIN"),
+ IN("IN"),
+ CONTAINS("CONTAINS"),
+ ALL("ALL"),
+ SIZE("SIZE"),
+ EXISTS("EXISTS"),
+ TYPE("TYPE"),
+ MATCHES("MATCHES"),
+ EMPTY("EMPTY"),
+ SUBSETOF("SUBSETOF"),
+ ANYOF("ANYOF"),
+ NONEOF("NONEOF");
+
+ private final String operatorString;
+
+ RelationalOperator(String operatorString) {
+ this.operatorString = operatorString;
+ }
+
+ public static RelationalOperator fromString(String operatorString){
+ for (RelationalOperator operator : RelationalOperator.values()) {
+ if(operator.operatorString.equals(operatorString.toUpperCase()) ){
+ return operator;
+ }
+ }
+ throw new InvalidPathException("Filter operator " + operatorString + " is not supported!");
+ }
+
+ @Override
+ public String toString() {
+ return operatorString;
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java b/app/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java
new file mode 100644
index 000000000..d87994ee4
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java
@@ -0,0 +1,216 @@
+package com.jayway.jsonpath.internal.filter;
+
+import java.util.regex.Pattern;
+
+import com.jayway.jsonpath.InvalidPathException;
+import com.jayway.jsonpath.JsonPathException;
+import com.jayway.jsonpath.Predicate;
+import com.jayway.jsonpath.internal.Path;
+import com.jayway.jsonpath.internal.path.PathCompiler;
+import net.minidev.json.parser.JSONParser;
+import static com.jayway.jsonpath.internal.filter.ValueNodes.*;
+
+public abstract class ValueNode {
+
+ public abstract Class> type(Predicate.PredicateContext ctx);
+
+ public boolean isPatternNode() {
+ return false;
+ }
+
+ public PatternNode asPatternNode() {
+ throw new InvalidPathException("Expected regexp node");
+ }
+
+ public boolean isPathNode() {
+ return false;
+ }
+
+ public PathNode asPathNode() {
+ throw new InvalidPathException("Expected path node");
+ }
+
+ public boolean isNumberNode() {
+ return false;
+ }
+
+ public NumberNode asNumberNode() {
+ throw new InvalidPathException("Expected number node");
+ }
+
+ public boolean isStringNode() {
+ return false;
+ }
+
+ public StringNode asStringNode() {
+ throw new InvalidPathException("Expected string node");
+ }
+
+ public boolean isBooleanNode() {
+ return false;
+ }
+
+ public BooleanNode asBooleanNode() {
+ throw new InvalidPathException("Expected boolean node");
+ }
+
+ public boolean isJsonNode() {
+ return false;
+ }
+
+ public JsonNode asJsonNode() {
+ throw new InvalidPathException("Expected json node");
+ }
+
+ public boolean isPredicateNode() {
+ return false;
+ }
+
+ public PredicateNode asPredicateNode() {
+ throw new InvalidPathException("Expected predicate node");
+ }
+
+ public boolean isValueListNode() {
+ return false;
+ }
+
+ public ValueListNode asValueListNode() {
+ throw new InvalidPathException("Expected value list node");
+ }
+
+ public boolean isNullNode() {
+ return false;
+ }
+
+ public NullNode asNullNode() {
+ throw new InvalidPathException("Expected null node");
+ }
+
+ public UndefinedNode asUndefinedNode() {
+ throw new InvalidPathException("Expected undefined node");
+ }
+
+ public boolean isUndefinedNode() {
+ return false;
+ }
+
+ public boolean isClassNode() {
+ return false;
+ }
+
+ public ClassNode asClassNode() {
+ throw new InvalidPathException("Expected class node");
+ }
+
+ private static boolean isPath(Object o) {
+ if(o == null || !(o instanceof String)){
+ return false;
+ }
+ String str = o.toString().trim();
+ if (str.length() <= 0) {
+ return false;
+ }
+ char c0 = str.charAt(0);
+ if(c0 == '@' || c0 == '$'){
+ try {
+ PathCompiler.compile(str);
+ return true;
+ } catch(Exception e){
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isJson(Object o) {
+ if(o == null || !(o instanceof String)){
+ return false;
+ }
+ String str = o.toString().trim();
+ if (str.length() <= 1) {
+ return false;
+ }
+ char c0 = str.charAt(0);
+ char c1 = str.charAt(str.length() - 1);
+ if ((c0 == '[' && c1 == ']') || (c0 == '{' && c1 == '}')){
+ try {
+ new JSONParser(JSONParser.MODE_PERMISSIVE).parse(str);
+ return true;
+ } catch(Exception e){
+ return false;
+ }
+ }
+ return false;
+ }
+
+
+
+ //----------------------------------------------------
+ //
+ // Factory methods
+ //
+ //----------------------------------------------------
+ public static ValueNode toValueNode(Object o){
+ 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);
+ else if(isJson(o)) return createJsonNode(o.toString());
+ else if(o instanceof String) return createStringNode(o.toString(), true);
+ else if(o instanceof Character) return createStringNode(o.toString(), false);
+ else if(o instanceof Number) return createNumberNode(o.toString());
+ else if(o instanceof Boolean) return createBooleanNode(o.toString());
+ else if(o instanceof Pattern) return createPatternNode((Pattern)o);
+ else throw new JsonPathException("Could not determine value type");
+ }
+
+ public static StringNode createStringNode(CharSequence charSequence, boolean escape){
+ return new StringNode(charSequence, escape);
+ }
+
+ public static ClassNode createClassNode(Class> clazz){
+ return new ClassNode(clazz);
+ }
+
+ public static NumberNode createNumberNode(CharSequence charSequence){
+ return new NumberNode(charSequence);
+ }
+
+ public static BooleanNode createBooleanNode(CharSequence charSequence){
+ return Boolean.parseBoolean(charSequence.toString()) ? TRUE : FALSE;
+ }
+
+ public static NullNode createNullNode(){
+ return NULL_NODE;
+ }
+
+ public static JsonNode createJsonNode(CharSequence json) {
+ return new JsonNode(json);
+ }
+
+ public static JsonNode createJsonNode(Object parsedJson) {
+ return new JsonNode(parsedJson);
+ }
+
+ public static PatternNode createPatternNode(CharSequence pattern) {
+ return new PatternNode(pattern);
+ }
+
+ public static PatternNode createPatternNode(Pattern pattern) {
+ return new PatternNode(pattern);
+ }
+
+ public static UndefinedNode createUndefinedNode() {
+ return UNDEFINED;
+ }
+
+ public static PathNode createPathNode(CharSequence path, boolean existsCheck, boolean shouldExists) {
+ return new PathNode(path, existsCheck, shouldExists);
+ }
+
+ public static ValueNode createPathNode(Path path) {
+ return new PathNode(path);
+ }
+
+}
+
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java b/app/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java
new file mode 100644
index 000000000..d221e950e
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java
@@ -0,0 +1,656 @@
+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;
+
+/**
+ * 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;
+ private final String flags;
+
+ PatternNode(CharSequence charSequence) {
+ String tmp = charSequence.toString();
+ int begin = tmp.indexOf('/');
+ int end = tmp.lastIndexOf('/');
+ this.pattern = tmp.substring(begin + 1, end);
+ int flagsIndex = end + 1;
+ this.flags = tmp.length() > flagsIndex ? tmp.substring(flagsIndex) : "";
+ this.compiledPattern = Pattern.compile(pattern, PatternFlag.parseFlags(flags.toCharArray()));
+ }
+
+ PatternNode(Pattern pattern) {
+ this.pattern = pattern.pattern();
+ this.compiledPattern = pattern;
+ this.flags = PatternFlag.parseFlags(pattern.flags());
+ }
+
+
+ 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() {
+
+ 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 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;
+ }
+
+ 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/app/src/main/java/com/jayway/jsonpath/internal/function/ParamType.java b/app/src/main/java/com/jayway/jsonpath/internal/function/ParamType.java
new file mode 100644
index 000000000..20288d475
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/ParamType.java
@@ -0,0 +1,9 @@
+package com.jayway.jsonpath.internal.function;
+
+/**
+ * Created by mgreenwood on 12/11/15.
+ */
+public enum ParamType {
+ JSON,
+ PATH
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java b/app/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java
new file mode 100644
index 000000000..0fe69a3af
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java
@@ -0,0 +1,131 @@
+package com.jayway.jsonpath.internal.function;
+
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.Path;
+import com.jayway.jsonpath.internal.function.latebinding.ILateBindingValue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Defines a parameter as passed to a function with late binding support for lazy evaluation.
+ */
+public class Parameter {
+ private ParamType type;
+ private Path path;
+ private ILateBindingValue lateBinding;
+ private Boolean evaluated = false;
+ private String json;
+
+ public Parameter() {}
+
+ public Parameter(String json) {
+ this.json = json;
+ this.type = ParamType.JSON;
+ }
+
+ public Parameter(Path path) {
+ this.path = path;
+ this.type = ParamType.PATH;
+ }
+
+ public Object getValue() {
+ return lateBinding.get();
+ }
+
+ public void setLateBinding(ILateBindingValue lateBinding) {
+ this.lateBinding = lateBinding;
+ }
+
+ public Path getPath() {
+ return path;
+ }
+
+ public void setEvaluated(Boolean evaluated) {
+ this.evaluated = evaluated;
+ }
+
+ public boolean hasEvaluated() {
+ return evaluated;
+ }
+
+ public ParamType getType() {
+ return type;
+ }
+
+ public void setType(ParamType type) {
+ this.type = type;
+ }
+
+ public void setPath(Path path) {
+ this.path = path;
+ }
+
+ public String getJson() {
+ return json;
+ }
+
+ public void setJson(String json) {
+ this.json = json;
+ }
+
+ /**
+ * Translate the collection of parameters into a collection of values of type T.
+ *
+ * @param type
+ * The type to translate the collection into.
+ *
+ * @param ctx
+ * Context.
+ *
+ * @param parameters
+ * Collection of parameters.
+ *
+ * @param
+ * Type T returned as a List of T.
+ *
+ * @return
+ * List of T either empty or containing contents.
+ */
+ public static List toList(final Class type, final EvaluationContext ctx, final List parameters) {
+ List values = new ArrayList();
+ if (null != parameters) {
+ for (Parameter param : parameters) {
+ consume(type, ctx, values, param.getValue());
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Either consume the object as an array and add each element to the collection, or alternatively add each element
+ *
+ * @param expectedType
+ * the expected class type to consume, if null or not of this type the element is not added to the array.
+ *
+ * @param ctx
+ * the JSON context to determine if this is an array or value.
+ *
+ * @param collection
+ * The collection to append into.
+ *
+ * @param value
+ * The value to evaluate.
+ */
+ public static void consume(Class expectedType, EvaluationContext ctx, Collection collection, Object value) {
+ if (ctx.configuration().jsonProvider().isArray(value)) {
+ for (Object o : ctx.configuration().jsonProvider().toIterable(value)) {
+ if (o != null && expectedType.isAssignableFrom(o.getClass())) {
+ collection.add(o);
+ } else if (o != null && expectedType == String.class) {
+ collection.add(o.toString());
+ }
+ }
+ } else {
+ if (value != null && expectedType.isAssignableFrom(value.getClass())) {
+ collection.add(value);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/PassthruPathFunction.java b/app/src/main/java/com/jayway/jsonpath/internal/function/PassthruPathFunction.java
new file mode 100644
index 000000000..36d7da77a
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/PassthruPathFunction.java
@@ -0,0 +1,19 @@
+package com.jayway.jsonpath.internal.function;
+
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.PathRef;
+
+import java.util.List;
+
+/**
+ * Defines the default behavior which is to return the model that is provided as input as output
+ *
+ * Created by mattg on 6/26/15.
+ */
+public class PassthruPathFunction implements PathFunction {
+
+ @Override
+ public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) {
+ return model;
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/PathFunction.java b/app/src/main/java/com/jayway/jsonpath/internal/function/PathFunction.java
new file mode 100644
index 000000000..ac3a353ba
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/PathFunction.java
@@ -0,0 +1,36 @@
+package com.jayway.jsonpath.internal.function;
+
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.PathRef;
+
+import java.util.List;
+
+/**
+ * Defines the pattern by which a function can be executed over the result set in the particular path
+ * being grabbed. The Function's input is the content of the data from the json path selector and its output
+ * is defined via the functions behavior. Thus transformations in types can take place. Additionally, functions
+ * can accept multiple selectors in order to produce their output.
+ *
+ * Created by matt@mjgreenwood.net on 6/26/15.
+ */
+public interface PathFunction {
+
+ /**
+ * Invoke the function and output a JSON object (or scalar) value which will be the result of executing the path
+ *
+ * @param currentPath
+ * The current path location inclusive of the function name
+ * @param parent
+ * The path location above the current function
+ *
+ * @param model
+ * The JSON model as input to this particular function
+ *
+ * @param ctx
+ * Eval context, state bag used as the path is traversed, maintains the result of executing
+ *
+ * @param parameters
+ * @return result
+ */
+ Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters);
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java b/app/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java
new file mode 100644
index 000000000..6beda0e24
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java
@@ -0,0 +1,78 @@
+package com.jayway.jsonpath.internal.function;
+
+import com.jayway.jsonpath.InvalidPathException;
+import com.jayway.jsonpath.internal.function.json.Append;
+import com.jayway.jsonpath.internal.function.numeric.Average;
+import com.jayway.jsonpath.internal.function.numeric.Max;
+import com.jayway.jsonpath.internal.function.numeric.Min;
+import com.jayway.jsonpath.internal.function.numeric.StandardDeviation;
+import com.jayway.jsonpath.internal.function.numeric.Sum;
+import com.jayway.jsonpath.internal.function.text.Concatenate;
+import com.jayway.jsonpath.internal.function.text.Length;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implements a factory that given a name of the function will return the Function implementation, or null
+ * if the value is not obtained.
+ *
+ * Leverages the function's name in order to determine which function to execute which is maintained internally
+ * here via a static map
+ *
+ */
+public class PathFunctionFactory {
+
+ public static final Map FUNCTIONS;
+
+ static {
+ // New functions should be added here and ensure the name is not overridden
+ Map map = new HashMap();
+
+ // Math Functions
+ map.put("avg", Average.class);
+ map.put("stddev", StandardDeviation.class);
+ map.put("sum", Sum.class);
+ map.put("min", Min.class);
+ map.put("max", Max.class);
+
+ // Text Functions
+ map.put("concat", Concatenate.class);
+
+ // JSON Entity Functions
+ map.put("length", Length.class);
+ map.put("size", Length.class);
+ map.put("append", Append.class);
+
+
+ FUNCTIONS = Collections.unmodifiableMap(map);
+ }
+
+ /**
+ * Returns the function by name or throws InvalidPathException if function not found.
+ *
+ * @see #FUNCTIONS
+ * @see PathFunction
+ *
+ * @param name
+ * The name of the function
+ *
+ * @return
+ * The implementation of a function
+ *
+ * @throws InvalidPathException
+ */
+ public static PathFunction newFunction(String name) throws InvalidPathException {
+ Class functionClazz = FUNCTIONS.get(name);
+ if(functionClazz == null){
+ throw new InvalidPathException("Function with name: " + name + " does not exist.");
+ } else {
+ try {
+ return (PathFunction)functionClazz.newInstance();
+ } catch (Exception e) {
+ throw new InvalidPathException("Function of name: " + name + " cannot be created", e);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/json/Append.java b/app/src/main/java/com/jayway/jsonpath/internal/function/json/Append.java
new file mode 100644
index 000000000..ed39d4a8b
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/json/Append.java
@@ -0,0 +1,31 @@
+package com.jayway.jsonpath.internal.function.json;
+
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.PathRef;
+import com.jayway.jsonpath.internal.function.Parameter;
+import com.jayway.jsonpath.internal.function.PathFunction;
+import com.jayway.jsonpath.spi.json.JsonProvider;
+
+import java.util.List;
+
+/**
+ * Appends JSON structure to the current document so that you can utilize the JSON added thru another function call.
+ * If there are multiple parameters then this function call will add each element that is json to the structure
+ *
+ * Created by mgreenwood on 12/14/15.
+ */
+public class Append implements PathFunction {
+ @Override
+ public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) {
+ JsonProvider jsonProvider = ctx.configuration().jsonProvider();
+ if (parameters != null && parameters.size() > 0) {
+ for (Parameter param : parameters) {
+ if (jsonProvider.isArray(model)) {
+ int len = jsonProvider.length(model);
+ jsonProvider.setArrayIndex(model, len, param.getValue());
+ }
+ }
+ }
+ return model;
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/latebinding/ILateBindingValue.java b/app/src/main/java/com/jayway/jsonpath/internal/function/latebinding/ILateBindingValue.java
new file mode 100644
index 000000000..3edac2449
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/latebinding/ILateBindingValue.java
@@ -0,0 +1,31 @@
+/*
+ * 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.function.latebinding;
+
+/**
+ * Obtain the late binding value at runtime rather than storing the value in the cache thus trashing the cache
+ *
+ */
+public interface ILateBindingValue {
+ /**
+ * Obtain the value of the parameter at runtime using the parameter state and invocation of other late binding values
+ * rather than maintaining cached state which ends up in a global store and won't change as a result of external
+ * reference changes.
+ *
+ * @return
+ * The value of evaluating the context at runtime.
+ */
+ Object get();
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/latebinding/JsonLateBindingValue.java b/app/src/main/java/com/jayway/jsonpath/internal/function/latebinding/JsonLateBindingValue.java
new file mode 100644
index 000000000..a72442931
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/latebinding/JsonLateBindingValue.java
@@ -0,0 +1,43 @@
+/*
+ * 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.function.latebinding;
+
+import com.jayway.jsonpath.internal.function.Parameter;
+import com.jayway.jsonpath.spi.json.JsonProvider;
+
+/**
+ * Defines the JSON document Late binding approach to function arguments.
+ *
+ */
+public class JsonLateBindingValue implements ILateBindingValue {
+ private final JsonProvider jsonProvider;
+ private final Parameter jsonParameter;
+
+ public JsonLateBindingValue(JsonProvider jsonProvider, Parameter jsonParameter) {
+ this.jsonProvider = jsonProvider;
+ this.jsonParameter = jsonParameter;
+ }
+
+ /**
+ * Evaluate the JSON document at the point of need using the JSON parameter and associated document model which may
+ * itself originate from yet another function thus recursively invoking late binding methods.
+ *
+ * @return the late value
+ */
+ @Override
+ public Object get() {
+ return jsonProvider.parse(jsonParameter.getJson());
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/latebinding/PathLateBindingValue.java b/app/src/main/java/com/jayway/jsonpath/internal/function/latebinding/PathLateBindingValue.java
new file mode 100644
index 000000000..2ece40ad9
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/latebinding/PathLateBindingValue.java
@@ -0,0 +1,46 @@
+/*
+ * 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.function.latebinding;
+
+import com.jayway.jsonpath.Configuration;
+import com.jayway.jsonpath.internal.Path;
+
+/**
+ * Defines the contract for late bindings, provides document state and enough context to perform the evaluation at a later
+ * date such that we can operate on a dynamically changing value.
+ *
+ * Acts like a lambda function with references, but since we're supporting JDK 6+, we're left doing this...
+ *
+ */
+public class PathLateBindingValue implements ILateBindingValue {
+ private final Path path;
+ private final Object rootDocument;
+ private final Configuration configuration;
+
+ public PathLateBindingValue(final Path path, final Object rootDocument, final Configuration configuration) {
+ this.path = path;
+ this.rootDocument = rootDocument;
+ this.configuration = configuration;
+ }
+
+ /**
+ * Evaluate the expression at the point of need for Path type expressions
+ *
+ * @return the late value
+ */
+ public Object get() {
+ return path.evaluate(rootDocument, rootDocument, configuration).getValue();
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java b/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java
new file mode 100644
index 000000000..cbc9f7bc9
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java
@@ -0,0 +1,61 @@
+package com.jayway.jsonpath.internal.function.numeric;
+
+import com.jayway.jsonpath.JsonPathException;
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.PathRef;
+import com.jayway.jsonpath.internal.function.Parameter;
+import com.jayway.jsonpath.internal.function.PathFunction;
+
+import java.util.List;
+
+/**
+ * Defines the pattern for processing numerical values via an abstract implementation that iterates over the collection
+ * of JSONArray entities and verifies that each is a numerical value and then passes that along the abstract methods
+ *
+ *
+ * Created by mattg on 6/26/15.
+ */
+public abstract class AbstractAggregation implements PathFunction {
+
+ /**
+ * Defines the next value in the array to the mathmatical function
+ *
+ * @param value
+ * The numerical value to process next
+ */
+ protected abstract void next(Number value);
+
+ /**
+ * Obtains the value generated via the series of next value calls
+ *
+ * @return
+ * A numerical answer based on the input value provided
+ */
+ protected abstract Number getValue();
+
+ @Override
+ public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) {
+ int count = 0;
+ if(ctx.configuration().jsonProvider().isArray(model)){
+
+ Iterable> objects = ctx.configuration().jsonProvider().toIterable(model);
+ for (Object obj : objects) {
+ if (obj instanceof Number) {
+ Number value = (Number) obj;
+ count++;
+ next(value);
+ }
+ }
+ }
+ if (parameters != null) {
+ for (Number value : Parameter.toList(Number.class, ctx, parameters)) {
+ count++;
+ next(value);
+ }
+ }
+ if (count != 0) {
+ return getValue();
+ }
+ throw new JsonPathException("Aggregation function attempted to calculate value using empty array");
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Average.java b/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Average.java
new file mode 100644
index 000000000..f4c6788e7
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Average.java
@@ -0,0 +1,26 @@
+package com.jayway.jsonpath.internal.function.numeric;
+
+/**
+ * Provides the average of a series of numbers in a JSONArray
+ *
+ * Created by mattg on 6/26/15.
+ */
+public class Average extends AbstractAggregation {
+
+ private Double summation = 0d;
+ private Double count = 0d;
+
+ @Override
+ protected void next(Number value) {
+ count++;
+ summation += value.doubleValue();
+ }
+
+ @Override
+ protected Number getValue() {
+ if (count != 0d) {
+ return summation / count;
+ }
+ return 0d;
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Max.java b/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Max.java
new file mode 100644
index 000000000..27570bf65
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Max.java
@@ -0,0 +1,22 @@
+package com.jayway.jsonpath.internal.function.numeric;
+
+/**
+ * Defines the summation of a series of JSONArray numerical values
+ *
+ * Created by mattg on 6/26/15.
+ */
+public class Max extends AbstractAggregation {
+ private Double max = Double.MIN_VALUE;
+
+ @Override
+ protected void next(Number value) {
+ if (max < value.doubleValue()) {
+ max = value.doubleValue();
+ }
+ }
+
+ @Override
+ protected Number getValue() {
+ return max;
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Min.java b/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Min.java
new file mode 100644
index 000000000..3c57e5f2e
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Min.java
@@ -0,0 +1,22 @@
+package com.jayway.jsonpath.internal.function.numeric;
+
+/**
+ * Defines the summation of a series of JSONArray numerical values
+ *
+ * Created by mattg on 6/26/15.
+ */
+public class Min extends AbstractAggregation {
+ private Double min = Double.MAX_VALUE;
+
+ @Override
+ protected void next(Number value) {
+ if (min > value.doubleValue()) {
+ min = value.doubleValue();
+ }
+ }
+
+ @Override
+ protected Number getValue() {
+ return min;
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/StandardDeviation.java b/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/StandardDeviation.java
new file mode 100644
index 000000000..0a83d8a86
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/StandardDeviation.java
@@ -0,0 +1,24 @@
+package com.jayway.jsonpath.internal.function.numeric;
+
+/**
+ * Provides the standard deviation of a series of numbers
+ *
+ * Created by mattg on 6/27/15.
+ */
+public class StandardDeviation extends AbstractAggregation {
+ private Double sumSq = 0d;
+ private Double sum = 0d;
+ private Double count = 0d;
+
+ @Override
+ protected void next(Number value) {
+ sum += value.doubleValue();
+ sumSq += value.doubleValue() * value.doubleValue();
+ count++;
+ }
+
+ @Override
+ protected Number getValue() {
+ return Math.sqrt(sumSq/count - sum*sum/count/count);
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Sum.java b/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Sum.java
new file mode 100644
index 000000000..3996bb43b
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Sum.java
@@ -0,0 +1,20 @@
+package com.jayway.jsonpath.internal.function.numeric;
+
+/**
+ * Defines the summation of a series of JSONArray numerical values
+ *
+ * Created by mattg on 6/26/15.
+ */
+public class Sum extends AbstractAggregation {
+ private Double summation = 0d;
+
+ @Override
+ protected void next(Number value) {
+ summation += value.doubleValue();
+ }
+
+ @Override
+ protected Number getValue() {
+ return summation;
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java b/app/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java
new file mode 100644
index 000000000..d499afef3
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java
@@ -0,0 +1,34 @@
+package com.jayway.jsonpath.internal.function.text;
+
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.PathRef;
+import com.jayway.jsonpath.internal.function.Parameter;
+import com.jayway.jsonpath.internal.function.PathFunction;
+
+import java.util.List;
+
+/**
+ * String function concat - simple takes a list of arguments and/or an array and concatenates them together to form a
+ * single string
+ *
+ */
+public class Concatenate implements PathFunction {
+ @Override
+ public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) {
+ StringBuilder result = new StringBuilder();
+ if(ctx.configuration().jsonProvider().isArray(model)){
+ Iterable> objects = ctx.configuration().jsonProvider().toIterable(model);
+ for (Object obj : objects) {
+ if (obj instanceof String) {
+ result.append(obj.toString());
+ }
+ }
+ }
+ if (parameters != null) {
+ for (String value : Parameter.toList(String.class, ctx, parameters)) {
+ result.append(value);
+ }
+ }
+ return result.toString();
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/function/text/Length.java b/app/src/main/java/com/jayway/jsonpath/internal/function/text/Length.java
new file mode 100644
index 000000000..e70f4e3f1
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/function/text/Length.java
@@ -0,0 +1,26 @@
+package com.jayway.jsonpath.internal.function.text;
+
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.PathRef;
+import com.jayway.jsonpath.internal.function.Parameter;
+import com.jayway.jsonpath.internal.function.PathFunction;
+
+import java.util.List;
+
+/**
+ * Provides the length of a JSONArray Object
+ *
+ * Created by mattg on 6/26/15.
+ */
+public class Length implements PathFunction {
+
+ @Override
+ public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) {
+ if(ctx.configuration().jsonProvider().isArray(model)){
+ return ctx.configuration().jsonProvider().length(model);
+ } else if(ctx.configuration().jsonProvider().isMap(model)){
+ return ctx.configuration().jsonProvider().length(model);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexOperation.java b/app/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexOperation.java
new file mode 100644
index 000000000..cbfa02530
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexOperation.java
@@ -0,0 +1,66 @@
+package com.jayway.jsonpath.internal.path;
+
+import com.jayway.jsonpath.InvalidPathException;
+import com.jayway.jsonpath.internal.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import static java.lang.Character.isDigit;
+
+public class ArrayIndexOperation {
+
+ private final static Pattern COMMA = Pattern.compile("\\s*,\\s*");
+
+ private final List indexes;
+
+ private ArrayIndexOperation(List indexes) {
+ this.indexes = Collections.unmodifiableList(indexes);
+ }
+
+ public List indexes() {
+ return indexes;
+ }
+
+ public boolean isSingleIndexOperation(){
+ return indexes.size() == 1;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ sb.append(Utils.join(",", indexes));
+ sb.append("]");
+
+ return sb.toString();
+ }
+
+ public static ArrayIndexOperation parse(String operation) {
+ //check valid chars
+ for (int i = 0; i < operation.length(); i++) {
+ char c = operation.charAt(i);
+ if (!isDigit(c) && c != ',' && c != ' ' && c != '-') {
+ throw new InvalidPathException("Failed to parse ArrayIndexOperation: " + operation);
+ }
+ }
+ String[] tokens = COMMA.split(operation, -1);
+
+ List tempIndexes = new ArrayList(tokens.length);
+ for (String token : tokens) {
+ tempIndexes.add(parseInteger(token));
+ }
+
+ return new ArrayIndexOperation(tempIndexes);
+ }
+
+ private static Integer parseInteger(String token) {
+ try {
+ return Integer.parseInt(token);
+ } catch (Exception e){
+ throw new InvalidPathException("Failed to parse token in ArrayIndexOperation: " + token, e);
+ }
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java b/app/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java
new file mode 100644
index 000000000..3e4b36e2e
--- /dev/null
+++ b/app/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/app/src/main/java/com/jayway/jsonpath/internal/path/ArrayPathToken.java b/app/src/main/java/com/jayway/jsonpath/internal/path/ArrayPathToken.java
new file mode 100644
index 000000000..d114bab7a
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/path/ArrayPathToken.java
@@ -0,0 +1,50 @@
+/*
+ * 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.InvalidPathException;
+import com.jayway.jsonpath.PathNotFoundException;
+
+import static java.lang.String.format;
+
+public abstract class ArrayPathToken extends PathToken {
+
+ /**
+ * Check if model is non-null and array.
+ * @param currentPath
+ * @param model
+ * @param ctx
+ * @return false if current evaluation call must be skipped, true otherwise
+ * @throws PathNotFoundException if model is null and evaluation must be interrupted
+ * @throws InvalidPathException if model is not an array and evaluation must be interrupted
+ */
+ protected boolean checkArrayModel(String currentPath, Object model, EvaluationContextImpl ctx) {
+ if (model == null){
+ if (! isUpstreamDefinite()) {
+ return false;
+ } else {
+ throw new PathNotFoundException("The path " + currentPath + " is null");
+ }
+ }
+ if (!ctx.jsonProvider().isArray(model)) {
+ if (! isUpstreamDefinite()) {
+ return false;
+ } else {
+ throw new PathNotFoundException(format("Filter: %s can only be applied to arrays. Current context is: %s", toString(), model));
+ }
+ }
+ return true;
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceOperation.java b/app/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceOperation.java
new file mode 100644
index 000000000..185af5121
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceOperation.java
@@ -0,0 +1,86 @@
+package com.jayway.jsonpath.internal.path;
+
+import com.jayway.jsonpath.InvalidPathException;
+
+import static java.lang.Character.isDigit;
+
+public class ArraySliceOperation {
+
+ public enum Operation {
+ SLICE_FROM,
+ SLICE_TO,
+ SLICE_BETWEEN
+ }
+
+ private final Integer from;
+ private final Integer to;
+ private final Operation operation;
+
+ private ArraySliceOperation(Integer from, Integer to, Operation operation) {
+ this.from = from;
+ this.to = to;
+ this.operation = operation;
+ }
+
+ public Integer from() {
+ return from;
+ }
+
+ public Integer to() {
+ return to;
+ }
+
+ public Operation operation() {
+ return operation;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ sb.append(from == null ? "" : from.toString());
+ sb.append(":");
+ sb.append(to == null ? "" : to.toString());
+ sb.append("]");
+
+ return sb.toString();
+ }
+
+ public static ArraySliceOperation parse(String operation){
+ //check valid chars
+ for (int i = 0; i < operation.length(); i++) {
+ char c = operation.charAt(i);
+ if( !isDigit(c) && c != '-' && c != ':'){
+ throw new InvalidPathException("Failed to parse SliceOperation: " + operation);
+ }
+ }
+ String[] tokens = operation.split(":");
+
+ Integer tempFrom = tryRead(tokens, 0);
+ Integer tempTo = tryRead(tokens, 1);
+ 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, tempOperation);
+ }
+
+ private static Integer tryRead(String[] tokens, int idx){
+ if(tokens.length > idx){
+ if(tokens[idx].equals("")){
+ return null;
+ }
+ return Integer.parseInt(tokens[idx]);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceToken.java b/app/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceToken.java
new file mode 100644
index 000000000..e4967a782
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceToken.java
@@ -0,0 +1,104 @@
+/*
+ * 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;
+
+public class ArraySliceToken extends ArrayPathToken {
+
+ 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);
+
+ 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;
+ }
+
+ 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);
+
+ 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/app/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java b/app/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java
new file mode 100644
index 000000000..3f2344a30
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java
@@ -0,0 +1,116 @@
+/*
+ * 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.Configuration;
+import com.jayway.jsonpath.internal.EvaluationAbortException;
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.Path;
+import com.jayway.jsonpath.internal.PathRef;
+import com.jayway.jsonpath.internal.function.ParamType;
+import com.jayway.jsonpath.internal.function.Parameter;
+
+import java.util.Arrays;
+
+public class CompiledPath implements Path {
+
+ private final RootPathToken root;
+
+ private final boolean isRootPath;
+
+
+ public CompiledPath(RootPathToken root, boolean isRootPath) {
+ this.root = invertScannerFunctionRelationship(root);
+ this.isRootPath = isRootPath;
+ }
+
+ @Override
+ public boolean isRootPath() {
+ return isRootPath;
+ }
+
+
+
+ /**
+ * In the event the writer of the path referenced a function at the tail end of a scanner, augment the query such
+ * that the root node is the function and the parameter to the function is the scanner. This way we maintain
+ * relative sanity in the path expression, functions either evaluate scalar values or arrays, they're
+ * not re-entrant nor should they maintain state, they do however take parameters.
+ *
+ * @param path
+ * this is our old root path which will become a parameter (assuming there's a scanner terminated by a function
+ *
+ * @return
+ * A function with the scanner as input, or if this situation doesn't exist just the input path
+ */
+ private RootPathToken invertScannerFunctionRelationship(final RootPathToken path) {
+ if (path.isFunctionPath() && path.next() instanceof ScanPathToken) {
+ PathToken token = path;
+ PathToken prior = null;
+ while (null != (token = token.next()) && !(token instanceof FunctionPathToken)) {
+ prior = token;
+ }
+ // Invert the relationship $..path.function() to $.function($..path)
+ if (token instanceof FunctionPathToken) {
+ prior.setNext(null);
+ path.setTail(prior);
+
+ // Now generate a new parameter from our path
+ Parameter parameter = new Parameter();
+ parameter.setPath(new CompiledPath(path, true));
+ parameter.setType(ParamType.PATH);
+ ((FunctionPathToken)token).setParameters(Arrays.asList(parameter));
+ RootPathToken functionRoot = new RootPathToken('$');
+ functionRoot.setTail(token);
+ functionRoot.setNext(token);
+
+ // Define the function as the root
+ return functionRoot;
+ }
+ }
+ return path;
+ }
+
+ @Override
+ public EvaluationContext evaluate(Object document, Object rootDocument, Configuration configuration, boolean forUpdate) {
+ EvaluationContextImpl ctx = new EvaluationContextImpl(this, rootDocument, configuration, forUpdate);
+ try {
+ PathRef op = ctx.forUpdate() ? PathRef.createRoot(rootDocument) : PathRef.NO_OP;
+ root.evaluate("", op, document, ctx);
+ } catch (EvaluationAbortException abort) {}
+
+ return ctx;
+ }
+
+ @Override
+ public EvaluationContext evaluate(Object document, Object rootDocument, Configuration configuration){
+ return evaluate(document, rootDocument, configuration, false);
+ }
+
+ @Override
+ public boolean isDefinite() {
+ return root.isPathDefinite();
+ }
+
+ @Override
+ public boolean isFunctionPath() {
+ return root.isFunctionPath();
+ }
+
+ @Override
+ public String toString() {
+ return root.toString();
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java b/app/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java
new file mode 100644
index 000000000..3fc7e4873
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java
@@ -0,0 +1,194 @@
+/*
+ * 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.Configuration;
+import com.jayway.jsonpath.EvaluationListener;
+import com.jayway.jsonpath.Option;
+import com.jayway.jsonpath.PathNotFoundException;
+import com.jayway.jsonpath.internal.EvaluationAbortException;
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.Path;
+import com.jayway.jsonpath.internal.PathRef;
+import com.jayway.jsonpath.spi.json.JsonProvider;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+import static com.jayway.jsonpath.internal.Utils.notNull;
+
+/**
+ *
+ */
+public class EvaluationContextImpl implements EvaluationContext {
+
+ private static final EvaluationAbortException ABORT_EVALUATION = new EvaluationAbortException();
+
+ private final Configuration configuration;
+ private final Object valueResult;
+ private final Object pathResult;
+ private final Path path;
+ private final Object rootDocument;
+ private final List updateOperations;
+ private final HashMap documentEvalCache = new HashMap();
+ private final boolean forUpdate;
+ private int resultIndex = 0;
+
+
+ public EvaluationContextImpl(Path path, Object rootDocument, Configuration configuration, boolean forUpdate) {
+ notNull(path, "path can not be null");
+ notNull(rootDocument, "root can not be null");
+ notNull(configuration, "configuration can not be null");
+ this.forUpdate = forUpdate;
+ this.path = path;
+ this.rootDocument = rootDocument;
+ this.configuration = configuration;
+ this.valueResult = configuration.jsonProvider().createArray();
+ this.pathResult = configuration.jsonProvider().createArray();
+ this.updateOperations = new ArrayList();
+ }
+
+ public HashMap documentEvalCache() {
+ return documentEvalCache;
+ }
+
+ public boolean forUpdate(){
+ return forUpdate;
+ }
+
+ public void addResult(String path, PathRef operation, Object model) {
+
+ if(forUpdate) {
+ updateOperations.add(operation);
+ }
+
+ configuration.jsonProvider().setArrayIndex(valueResult, resultIndex, model);
+ configuration.jsonProvider().setArrayIndex(pathResult, resultIndex, path);
+ resultIndex++;
+ if(!configuration().getEvaluationListeners().isEmpty()){
+ int idx = resultIndex - 1;
+ for (EvaluationListener listener : configuration().getEvaluationListeners()) {
+ EvaluationListener.EvaluationContinuation continuation = listener.resultFound(new FoundResultImpl(idx, path, model));
+ if(EvaluationListener.EvaluationContinuation.ABORT == continuation){
+ throw ABORT_EVALUATION;
+ }
+ }
+ }
+ }
+
+
+ public JsonProvider jsonProvider() {
+ return configuration.jsonProvider();
+ }
+
+ public Set
options() {
+ return configuration.getOptions();
+ }
+
+ @Override
+ public Configuration configuration() {
+ return configuration;
+ }
+
+ @Override
+ public Object rootDocument() {
+ return rootDocument;
+ }
+
+ public Collection updateOperations(){
+
+ Collections.sort(updateOperations);
+
+ return Collections.unmodifiableCollection(updateOperations);
+ }
+
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T getValue() {
+ return getValue(true);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T getValue(boolean unwrap) {
+ if (path.isDefinite()) {
+ if(resultIndex == 0){
+ throw new PathNotFoundException("No results for path: " + path.toString());
+ }
+ int len = jsonProvider().length(valueResult);
+ Object value = (len > 0) ? jsonProvider().getArrayIndex(valueResult, len-1) : null;
+ if (value != null && unwrap){
+ value = jsonProvider().unwrap(value);
+ }
+ return (T) value;
+ }
+ return (T)valueResult;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T getPath() {
+ if(resultIndex == 0){
+ throw new PathNotFoundException("No results for path: " + path.toString());
+ }
+ return (T)pathResult;
+ }
+
+ @Override
+ public List getPathList() {
+ List res = new ArrayList();
+ if(resultIndex > 0){
+ Iterable> objects = configuration.jsonProvider().toIterable(pathResult);
+ for (Object o : objects) {
+ res.add((String)o);
+ }
+ }
+ return res;
+ }
+
+ private static class FoundResultImpl implements EvaluationListener.FoundResult {
+
+ private final int index;
+ private final String path;
+ private final Object result;
+
+ private FoundResultImpl(int index, String path, Object result) {
+ this.index = index;
+ this.path = path;
+ this.result = result;
+ }
+
+ @Override
+ public int index() {
+ return index;
+ }
+
+ @Override
+ public String path() {
+ return path;
+ }
+
+ @Override
+ public Object result() {
+ return result;
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java b/app/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java
new file mode 100644
index 000000000..84f2c481e
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java
@@ -0,0 +1,87 @@
+package com.jayway.jsonpath.internal.path;
+
+import com.jayway.jsonpath.internal.PathRef;
+import com.jayway.jsonpath.internal.function.Parameter;
+import com.jayway.jsonpath.internal.function.PathFunction;
+import com.jayway.jsonpath.internal.function.PathFunctionFactory;
+import com.jayway.jsonpath.internal.function.latebinding.JsonLateBindingValue;
+import com.jayway.jsonpath.internal.function.latebinding.PathLateBindingValue;
+
+import java.util.List;
+
+/**
+ * Token representing a Function call to one of the functions produced via the FunctionFactory
+ *
+ * @see PathFunctionFactory
+ *
+ * Created by mattg on 6/27/15.
+ */
+public class FunctionPathToken extends PathToken {
+
+ private final String functionName;
+ private final String pathFragment;
+ private List functionParams;
+
+ public FunctionPathToken(String pathFragment, List parameters) {
+ this.pathFragment = pathFragment + ((parameters != null && parameters.size() > 0) ? "(...)" : "()");
+ if(null != pathFragment){
+ functionName = pathFragment;
+ functionParams = parameters;
+ } else {
+ functionName = null;
+ functionParams = null;
+ }
+ }
+
+ @Override
+ public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
+ PathFunction pathFunction = PathFunctionFactory.newFunction(functionName);
+ evaluateParameters(currentPath, parent, model, ctx);
+ Object result = pathFunction.invoke(currentPath, parent, model, ctx, functionParams);
+ ctx.addResult(currentPath + "." + functionName, parent, result);
+ if (!isLeaf()) {
+ next().evaluate(currentPath, parent, result, ctx);
+ }
+ }
+
+ private void evaluateParameters(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
+
+ if (null != functionParams) {
+ for (Parameter param : functionParams) {
+ if (!param.hasEvaluated()) {
+ switch (param.getType()) {
+ case PATH:
+ param.setLateBinding(new PathLateBindingValue(param.getPath(), ctx.rootDocument(), ctx.configuration()));
+ param.setEvaluated(true);
+ break;
+ case JSON:
+ param.setLateBinding(new JsonLateBindingValue(ctx.configuration().jsonProvider(), param));
+ param.setEvaluated(true);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the actual value by indicating true. If this return was false then we'd return the value in an array which
+ * isn't what is desired - true indicates the raw value is returned.
+ *
+ * @return true if token is definite
+ */
+ @Override
+ public boolean isTokenDefinite() {
+ return true;
+ }
+
+ @Override
+ public String getPathFragment() {
+ return "." + pathFragment;
+ }
+
+
+ public void setParameters(List parameters) {
+ this.functionParams = parameters;
+ }
+}
diff --git a/app/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java b/app/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
new file mode 100644
index 000000000..a118f9ca4
--- /dev/null
+++ b/app/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
@@ -0,0 +1,623 @@
+package com.jayway.jsonpath.internal.path;
+
+import com.jayway.jsonpath.InvalidPathException;
+import com.jayway.jsonpath.Predicate;
+import com.jayway.jsonpath.internal.CharacterIndex;
+import com.jayway.jsonpath.internal.Path;
+import com.jayway.jsonpath.internal.Utils;
+import com.jayway.jsonpath.internal.filter.FilterCompiler;
+import com.jayway.jsonpath.internal.function.ParamType;
+import com.jayway.jsonpath.internal.function.Parameter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import static java.lang.Character.isDigit;
+import static java.util.Arrays.asList;
+
+public class PathCompiler {
+
+ private static final char DOC_CONTEXT = '$';
+ private static final char EVAL_CONTEXT = '@';
+
+ private static final char OPEN_SQUARE_BRACKET = '[';
+ private static final char CLOSE_SQUARE_BRACKET = ']';
+ private static final char OPEN_PARENTHESIS = '(';
+ private static final char CLOSE_PARENTHESIS = ')';
+ private static final char OPEN_BRACE = '{';
+ private static final char CLOSE_BRACE = '}';
+
+ private static final char WILDCARD = '*';
+ private static final char PERIOD = '.';
+ private static final char SPACE = ' ';
+ private static final char TAB = '\t';
+ private static final char CR = '\r';
+ private static final char LF = '\n';
+ private static final char BEGIN_FILTER = '?';
+ private static final char COMMA = ',';
+ private static final char SPLIT = ':';
+ private static final char MINUS = '-';
+ private static final char SINGLE_QUOTE = '\'';
+ private static final char DOUBLE_QUOTE = '"';
+
+ private final LinkedList filterStack;
+ private final CharacterIndex path;
+
+ private PathCompiler(String path, LinkedList filterStack){
+ this(new CharacterIndex(path), filterStack);
+ }
+
+ private PathCompiler(CharacterIndex path, LinkedList filterStack){
+ this.filterStack = filterStack;
+ this.path = path;
+ }
+
+ private Path compile() {
+ RootPathToken root = readContextToken();
+ return new CompiledPath(root, root.getPathFragment().equals("$"));
+ }
+
+ public static Path compile(String path, final Predicate... filters) {
+ try {
+ CharacterIndex ci = new CharacterIndex(path);
+ ci.trim();
+
+ if(!( ci.charAt(0) == DOC_CONTEXT) && !( ci.charAt(0) == EVAL_CONTEXT)){
+ ci = new CharacterIndex("$." + path);
+ ci.trim();
+ }
+ if(ci.lastCharIs('.')){
+ fail("Path must not end with a '.' or '..'");
+ }
+ LinkedList filterStack = new LinkedList(asList(filters));
+ Path p = new PathCompiler(ci, filterStack).compile();
+ return p;
+ } catch (Exception e) {
+ InvalidPathException ipe;
+ if (e instanceof InvalidPathException) {
+ ipe = (InvalidPathException) e;
+ } else {
+ ipe = new InvalidPathException(e);
+ }
+ throw ipe;
+ }
+ }
+
+ private void readWhitespace() {
+ while (path.inBounds()) {
+ char c = path.currentChar();
+ if (!isWhitespace(c)) {
+ break;
+ }
+ path.incrementPosition(1);
+ }
+ }
+
+ private Boolean isPathContext(char c) {
+ return (c == DOC_CONTEXT || c == EVAL_CONTEXT);
+ }
+
+ //[$ | @]
+ private RootPathToken readContextToken() {
+
+ readWhitespace();
+
+ if (!isPathContext(path.currentChar())) {
+ throw new InvalidPathException("Path must start with '$' or '@'");
+ }
+
+ RootPathToken pathToken = PathTokenFactory.createRootPathToken(path.currentChar());
+
+ if (path.currentIsTail()) {
+ return pathToken;
+ }
+
+ path.incrementPosition(1);
+
+ if(path.currentChar() != PERIOD && path.currentChar() != OPEN_SQUARE_BRACKET){
+ fail("Illegal character at position " + path.position() + " expected '.' or '['");
+ }
+
+ PathTokenAppender appender = pathToken.getPathTokenAppender();
+ readNextToken(appender);
+
+ return pathToken;
+ }
+
+ //
+ //
+ //
+ private boolean readNextToken(PathTokenAppender appender) {
+
+ char c = path.currentChar();
+
+ switch (c) {
+ case OPEN_SQUARE_BRACKET:
+ return readBracketPropertyToken(appender) ||
+ readArrayToken(appender) ||
+ readWildCardToken(appender) ||
+ readFilterToken(appender) ||
+ readPlaceholderToken(appender) ||
+ fail("Could not parse token starting at position " + path.position() + ". Expected ?, ', 0-9, * ");
+ case PERIOD:
+ return readDotToken(appender) ||
+ fail("Could not parse token starting at position " + path.position());
+ case WILDCARD:
+ return readWildCardToken(appender) ||
+ fail("Could not parse token starting at position " + path.position());
+ default:
+ return readPropertyOrFunctionToken(appender) ||
+ fail("Could not parse token starting at position " + path.position());
+ }
+ }
+
+ //
+ // . and ..
+ //
+ private boolean readDotToken(PathTokenAppender appender) {
+ if (path.currentCharIs(PERIOD) && path.nextCharIs(PERIOD)) {
+ appender.appendPathToken(PathTokenFactory.crateScanToken());
+ path.incrementPosition(2);
+ } else if (!path.hasMoreCharacters()) {
+ throw new InvalidPathException("Path must not end with a '.");
+ } else {
+ path.incrementPosition(1);
+ }
+ if(path.currentCharIs(PERIOD)){
+ throw new InvalidPathException("Character '.' on position " + path.position() + " is not valid.");
+ }
+ return readNextToken(appender);
+ }
+
+ //
+ // fooBar or fooBar()
+ //
+ private boolean readPropertyOrFunctionToken(PathTokenAppender appender) {
+ if (path.currentCharIs(OPEN_SQUARE_BRACKET) || path.currentCharIs(WILDCARD) || path.currentCharIs(PERIOD) || path.currentCharIs(SPACE)) {
+ return false;
+ }
+ int startPosition = path.position();
+ int readPosition = startPosition;
+ int endPosition = 0;
+
+ boolean isFunction = false;
+
+ while (path.inBounds(readPosition)) {
+ char c = path.charAt(readPosition);
+ if (c == SPACE) {
+ throw new InvalidPathException("Use bracket notion ['my prop'] if your property contains blank characters. position: " + path.position());
+ }
+ else if (c == PERIOD || c == OPEN_SQUARE_BRACKET) {
+ endPosition = readPosition;
+ break;
+ }
+ else if (c == OPEN_PARENTHESIS) {
+ isFunction = true;
+ endPosition = readPosition;
+ break;
+ }
+ readPosition++;
+ }
+ if (endPosition == 0) {
+ endPosition = path.length();
+ }
+
+
+ List functionParameters = null;
+ if (isFunction) {
+ if (path.inBounds(readPosition+1)) {
+ // read the next token to determine if we have a simple no-args function call
+ char c = path.charAt(readPosition + 1);
+ if (c != CLOSE_PARENTHESIS) {
+ path.setPosition(endPosition+1);
+ // parse the arguments of the function - arguments that are inner queries or JSON document(s)
+ String functionName = path.subSequence(startPosition, endPosition).toString();
+ functionParameters = parseFunctionParameters(functionName);
+ } else {
+ path.setPosition(readPosition + 1);
+ }
+ }
+ else {
+ path.setPosition(readPosition);
+ }
+ }
+ else {
+ path.setPosition(endPosition);
+ }
+
+ String property = path.subSequence(startPosition, endPosition).toString();
+ if(isFunction){
+ appender.appendPathToken(PathTokenFactory.createFunctionPathToken(property, functionParameters));
+ } else {
+ appender.appendPathToken(PathTokenFactory.createSinglePropertyPathToken(property, SINGLE_QUOTE));
+ }
+
+ return path.currentIsTail() || readNextToken(appender);
+ }
+
+ /**
+ * Parse the parameters of a function call, either the caller has supplied JSON data, or the caller has supplied
+ * another path expression which must be evaluated and in turn invoked against the root document. In this tokenizer
+ * we're only concerned with parsing the path thus the output of this function is a list of parameters with the Path
+ * set if the parameter is an expression. If the parameter is a JSON document then the value of the cachedValue is
+ * set on the object.
+ *
+ * Sequence for parsing out the parameters:
+ *
+ * This code has its own tokenizer - it does some rudimentary level of lexing in that it can distinguish between JSON block parameters
+ * and sub-JSON blocks - it effectively regex's out the parameters into string blocks that can then be passed along to the appropriate parser.
+ * Since sub-jsonpath expressions can themselves contain other function calls this routine needs to be sensitive to token counting to
+ * determine the boundaries. Since the Path parser isn't aware of JSON processing this uber routine is needed.
+ *
+ * Parameters are separated by COMMAs ','
+ *
+ *