Remove slf4j from jsonpath

pull/32/head
atbest 6 years ago
parent 36eb40c813
commit 3538f958ab
  1. 257
      app/src/main/java/com/jayway/jsonpath/Configuration.java
  2. 513
      app/src/main/java/com/jayway/jsonpath/Criteria.java
  3. 18
      app/src/main/java/com/jayway/jsonpath/DocumentContext.java
  4. 63
      app/src/main/java/com/jayway/jsonpath/EvaluationListener.java
  5. 184
      app/src/main/java/com/jayway/jsonpath/Filter.java
  6. 33
      app/src/main/java/com/jayway/jsonpath/InvalidCriteriaException.java
  7. 58
      app/src/main/java/com/jayway/jsonpath/InvalidJsonException.java
  8. 21
      app/src/main/java/com/jayway/jsonpath/InvalidModificationException.java
  9. 34
      app/src/main/java/com/jayway/jsonpath/InvalidPathException.java
  10. 695
      app/src/main/java/com/jayway/jsonpath/JsonPath.java
  11. 33
      app/src/main/java/com/jayway/jsonpath/JsonPathException.java
  12. 23
      app/src/main/java/com/jayway/jsonpath/MapFunction.java
  13. 88
      app/src/main/java/com/jayway/jsonpath/Option.java
  14. 39
      app/src/main/java/com/jayway/jsonpath/ParseContext.java
  15. 33
      app/src/main/java/com/jayway/jsonpath/PathNotFoundException.java
  16. 53
      app/src/main/java/com/jayway/jsonpath/Predicate.java
  17. 124
      app/src/main/java/com/jayway/jsonpath/ReadContext.java
  18. 54
      app/src/main/java/com/jayway/jsonpath/TypeRef.java
  19. 40
      app/src/main/java/com/jayway/jsonpath/ValueCompareException.java
  20. 168
      app/src/main/java/com/jayway/jsonpath/WriteContext.java
  21. 318
      app/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java
  22. 37
      app/src/main/java/com/jayway/jsonpath/internal/DefaultsImpl.java
  23. 13
      app/src/main/java/com/jayway/jsonpath/internal/EvaluationAbortException.java
  24. 73
      app/src/main/java/com/jayway/jsonpath/internal/EvaluationContext.java
  25. 216
      app/src/main/java/com/jayway/jsonpath/internal/JsonContext.java
  26. 116
      app/src/main/java/com/jayway/jsonpath/internal/JsonFormatter.java
  27. 83
      app/src/main/java/com/jayway/jsonpath/internal/ParseContextImpl.java
  28. 64
      app/src/main/java/com/jayway/jsonpath/internal/Path.java
  29. 333
      app/src/main/java/com/jayway/jsonpath/internal/PathRef.java
  30. 406
      app/src/main/java/com/jayway/jsonpath/internal/Utils.java
  31. 7
      app/src/main/java/com/jayway/jsonpath/internal/filter/Evaluator.java
  32. 376
      app/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java
  33. 24
      app/src/main/java/com/jayway/jsonpath/internal/filter/ExpressionNode.java
  34. 417
      app/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java
  35. 88
      app/src/main/java/com/jayway/jsonpath/internal/filter/LogicalExpressionNode.java
  36. 32
      app/src/main/java/com/jayway/jsonpath/internal/filter/LogicalOperator.java
  37. 48
      app/src/main/java/com/jayway/jsonpath/internal/filter/PatternFlag.java
  38. 42
      app/src/main/java/com/jayway/jsonpath/internal/filter/RelationalExpressionNode.java
  39. 56
      app/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java
  40. 216
      app/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java
  41. 656
      app/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java
  42. 9
      app/src/main/java/com/jayway/jsonpath/internal/function/ParamType.java
  43. 131
      app/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java
  44. 19
      app/src/main/java/com/jayway/jsonpath/internal/function/PassthruPathFunction.java
  45. 36
      app/src/main/java/com/jayway/jsonpath/internal/function/PathFunction.java
  46. 78
      app/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java
  47. 31
      app/src/main/java/com/jayway/jsonpath/internal/function/json/Append.java
  48. 31
      app/src/main/java/com/jayway/jsonpath/internal/function/latebinding/ILateBindingValue.java
  49. 43
      app/src/main/java/com/jayway/jsonpath/internal/function/latebinding/JsonLateBindingValue.java
  50. 46
      app/src/main/java/com/jayway/jsonpath/internal/function/latebinding/PathLateBindingValue.java
  51. 61
      app/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java
  52. 26
      app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Average.java
  53. 22
      app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Max.java
  54. 22
      app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Min.java
  55. 24
      app/src/main/java/com/jayway/jsonpath/internal/function/numeric/StandardDeviation.java
  56. 20
      app/src/main/java/com/jayway/jsonpath/internal/function/numeric/Sum.java
  57. 34
      app/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java
  58. 26
      app/src/main/java/com/jayway/jsonpath/internal/function/text/Length.java
  59. 66
      app/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexOperation.java
  60. 52
      app/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java
  61. 50
      app/src/main/java/com/jayway/jsonpath/internal/path/ArrayPathToken.java
  62. 86
      app/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceOperation.java
  63. 104
      app/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceToken.java
  64. 116
      app/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java
  65. 194
      app/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java
  66. 87
      app/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java
  67. 623
      app/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
  68. 221
      app/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java
  69. 5
      app/src/main/java/com/jayway/jsonpath/internal/path/PathTokenAppender.java
  70. 52
      app/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java
  71. 77
      app/src/main/java/com/jayway/jsonpath/internal/path/PredicateContextImpl.java
  72. 108
      app/src/main/java/com/jayway/jsonpath/internal/path/PredicatePathToken.java
  73. 105
      app/src/main/java/com/jayway/jsonpath/internal/path/PropertyPathToken.java
  74. 83
      app/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java
  75. 206
      app/src/main/java/com/jayway/jsonpath/internal/path/ScanPathToken.java
  76. 62
      app/src/main/java/com/jayway/jsonpath/internal/path/WildcardPathToken.java
  77. 22
      app/src/main/java/com/jayway/jsonpath/spi/cache/Cache.java
  78. 37
      app/src/main/java/com/jayway/jsonpath/spi/cache/CacheProvider.java
  79. 112
      app/src/main/java/com/jayway/jsonpath/spi/cache/LRUCache.java
  80. 15
      app/src/main/java/com/jayway/jsonpath/spi/cache/NOOPCache.java
  81. 178
      app/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java
  82. 280
      app/src/main/java/com/jayway/jsonpath/spi/json/GsonJsonProvider.java
  83. 166
      app/src/main/java/com/jayway/jsonpath/spi/json/JsonProvider.java
  84. 94
      app/src/main/java/com/jayway/jsonpath/spi/json/JsonSmartJsonProvider.java
  85. 82
      app/src/main/java/com/jayway/jsonpath/spi/mapper/GsonMappingProvider.java
  86. 263
      app/src/main/java/com/jayway/jsonpath/spi/mapper/JsonSmartMappingProvider.java
  87. 28
      app/src/main/java/com/jayway/jsonpath/spi/mapper/MappingException.java
  88. 45
      app/src/main/java/com/jayway/jsonpath/spi/mapper/MappingProvider.java

@ -0,0 +1,257 @@
/*
* 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.internal.DefaultsImpl;
import com.jayway.jsonpath.spi.json.JsonProvider;
import com.jayway.jsonpath.spi.mapper.MappingProvider;
import java.util.*;
import static com.jayway.jsonpath.internal.Utils.notNull;
import static java.util.Arrays.asList;
/**
* Immutable configuration object
*/
public class Configuration {
private static Defaults DEFAULTS = null;
/**
* Set Default configuration
* @param defaults default configuration settings
*/
public static synchronized void setDefaults(Defaults defaults){
DEFAULTS = defaults;
}
private static Defaults getEffectiveDefaults(){
if (DEFAULTS == null) {
return DefaultsImpl.INSTANCE;
} else {
return DEFAULTS;
}
}
private final JsonProvider jsonProvider;
private final MappingProvider mappingProvider;
private final Set<Option> options;
private final Collection<EvaluationListener> evaluationListeners;
private Configuration(JsonProvider jsonProvider, MappingProvider mappingProvider, EnumSet<Option> options, Collection<EvaluationListener> evaluationListeners) {
notNull(jsonProvider, "jsonProvider can not be null");
notNull(mappingProvider, "mappingProvider can not be null");
notNull(options, "setOptions can not be null");
notNull(evaluationListeners, "evaluationListeners can not be null");
this.jsonProvider = jsonProvider;
this.mappingProvider = mappingProvider;
this.options = Collections.unmodifiableSet(options);
this.evaluationListeners = Collections.unmodifiableCollection(evaluationListeners);
}
/**
* Creates a new Configuration by the provided evaluation listeners to the current listeners
* @param evaluationListener listeners
* @return a new configuration
*/
public Configuration addEvaluationListeners(EvaluationListener... evaluationListener){
return Configuration.builder().jsonProvider(jsonProvider).mappingProvider(mappingProvider).options(options).evaluationListener(evaluationListener).build();
}
/**
* Creates a new Configuration with the provided evaluation listeners
* @param evaluationListener listeners
* @return a new configuration
*/
public Configuration setEvaluationListeners(EvaluationListener... evaluationListener){
return Configuration.builder().jsonProvider(jsonProvider).mappingProvider(mappingProvider).options(options).evaluationListener(evaluationListener).build();
}
/**
* Returns the evaluation listeners registered in this configuration
* @return the evaluation listeners
*/
public Collection<EvaluationListener> getEvaluationListeners(){
return evaluationListeners;
}
/**
* Creates a new Configuration based on the given {@link com.jayway.jsonpath.spi.json.JsonProvider}
* @param newJsonProvider json provider to use in new configuration
* @return a new configuration
*/
public Configuration jsonProvider(JsonProvider newJsonProvider) {
return Configuration.builder().jsonProvider(newJsonProvider).mappingProvider(mappingProvider).options(options).evaluationListener(evaluationListeners).build();
}
/**
* Returns {@link com.jayway.jsonpath.spi.json.JsonProvider} used by this configuration
* @return jsonProvider used
*/
public JsonProvider jsonProvider() {
return jsonProvider;
}
/**
* Creates a new Configuration based on the given {@link com.jayway.jsonpath.spi.mapper.MappingProvider}
* @param newMappingProvider mapping provider to use in new configuration
* @return a new configuration
*/
public Configuration mappingProvider(MappingProvider newMappingProvider) {
return Configuration.builder().jsonProvider(jsonProvider).mappingProvider(newMappingProvider).options(options).evaluationListener(evaluationListeners).build();
}
/**
* Returns {@link com.jayway.jsonpath.spi.mapper.MappingProvider} used by this configuration
* @return mappingProvider used
*/
public MappingProvider mappingProvider() {
return mappingProvider;
}
/**
* Creates a new configuration by adding the new options to the options used in this configuration.
* @param options options to add
* @return a new configuration
*/
public Configuration addOptions(Option... options) {
EnumSet<Option> opts = EnumSet.noneOf(Option.class);
opts.addAll(this.options);
opts.addAll(asList(options));
return Configuration.builder().jsonProvider(jsonProvider).mappingProvider(mappingProvider).options(opts).evaluationListener(evaluationListeners).build();
}
/**
* Creates a new configuration with the provided options. Options in this configuration are discarded.
* @param options
* @return the new configuration instance
*/
public Configuration setOptions(Option... options) {
return Configuration.builder().jsonProvider(jsonProvider).mappingProvider(mappingProvider).options(options).evaluationListener(evaluationListeners).build();
}
/**
* Returns the options used by this configuration
* @return the new configuration instance
*/
public Set<Option> getOptions() {
return options;
}
/**
* Check if this configuration contains the given option
* @param option option to check
* @return true if configurations contains option
*/
public boolean containsOption(Option option){
return options.contains(option);
}
/**
* Creates a new configuration based on default values
* @return a new configuration based on defaults
*/
public static Configuration defaultConfiguration() {
Defaults defaults = getEffectiveDefaults();
return Configuration.builder().jsonProvider(defaults.jsonProvider()).options(defaults.options()).build();
}
/**
* Returns a new ConfigurationBuilder
* @return a builder
*/
public static ConfigurationBuilder builder() {
return new ConfigurationBuilder();
}
/**
* Configuration builder
*/
public static class ConfigurationBuilder {
private JsonProvider jsonProvider;
private MappingProvider mappingProvider;
private EnumSet<Option> options = EnumSet.noneOf(Option.class);
private Collection<EvaluationListener> evaluationListener = new ArrayList<EvaluationListener>();
public ConfigurationBuilder jsonProvider(JsonProvider provider) {
this.jsonProvider = provider;
return this;
}
public ConfigurationBuilder mappingProvider(MappingProvider provider) {
this.mappingProvider = provider;
return this;
}
public ConfigurationBuilder options(Option... flags) {
if(flags.length > 0) {
this.options.addAll(asList(flags));
}
return this;
}
public ConfigurationBuilder options(Set<Option> options) {
this.options.addAll(options);
return this;
}
public ConfigurationBuilder evaluationListener(EvaluationListener... listener){
this.evaluationListener = Arrays.asList(listener);
return this;
}
public ConfigurationBuilder evaluationListener(Collection<EvaluationListener> listeners){
this.evaluationListener = listeners == null ? Collections.<EvaluationListener>emptyList() : listeners;
return this;
}
public Configuration build() {
if (jsonProvider == null || mappingProvider == null) {
final Defaults defaults = getEffectiveDefaults();
if (jsonProvider == null) {
jsonProvider = defaults.jsonProvider();
}
if (mappingProvider == null){
mappingProvider = defaults.mappingProvider();
}
}
return new Configuration(jsonProvider, mappingProvider, options, evaluationListener);
}
}
public interface Defaults {
/**
* Returns the default {@link com.jayway.jsonpath.spi.json.JsonProvider}
* @return default json provider
*/
JsonProvider jsonProvider();
/**
* Returns default setOptions
* @return setOptions
*/
Set<Option> options();
/**
* Returns the default {@link com.jayway.jsonpath.spi.mapper.MappingProvider}
*
* @return default mapping provider
*/
MappingProvider mappingProvider();
}
}

@ -0,0 +1,513 @@
/*
* 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.internal.Path;
import com.jayway.jsonpath.internal.Utils;
import com.jayway.jsonpath.internal.filter.RelationalExpressionNode;
import com.jayway.jsonpath.internal.filter.RelationalOperator;
import com.jayway.jsonpath.internal.filter.ValueNode;
import com.jayway.jsonpath.internal.filter.ValueNodes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import static com.jayway.jsonpath.internal.Utils.notNull;
import static com.jayway.jsonpath.internal.filter.ValueNodes.PredicateNode;
import static com.jayway.jsonpath.internal.filter.ValueNodes.ValueListNode;
/**
*
*/
@SuppressWarnings("unchecked")
public class Criteria implements Predicate {
private final List<Criteria> criteriaChain;
private ValueNode left;
private RelationalOperator criteriaType;
private ValueNode right;
private Criteria(List<Criteria> criteriaChain, ValueNode left) {
this.left = left;
this.criteriaChain = criteriaChain;
this.criteriaChain.add(this);
}
private Criteria(ValueNode left) {
this(new LinkedList<Criteria>(), left);
}
@Override
public boolean apply(PredicateContext ctx) {
for (RelationalExpressionNode expressionNode : toRelationalExpressionNodes()) {
if(!expressionNode.apply(ctx)){
return false;
}
}
return true;
}
@Override
public String toString() {
return Utils.join(" && ", toRelationalExpressionNodes());
}
private Collection<RelationalExpressionNode> toRelationalExpressionNodes(){
List<RelationalExpressionNode> nodes = new ArrayList<RelationalExpressionNode>(criteriaChain.size());
for (Criteria criteria : criteriaChain) {
nodes.add(new RelationalExpressionNode(criteria.left, criteria.criteriaType, criteria.right));
}
return nodes;
}
/**
* Static factory method to create a Criteria using the provided key
*
* @param key filed name
* @return the new criteria
*/
@Deprecated
//This should be private.It exposes internal classes
public static Criteria where(Path key) {
return new Criteria(ValueNode.createPathNode(key));
}
/**
* Static factory method to create a Criteria using the provided key
*
* @param key filed name
* @return the new criteria
*/
public static Criteria where(String key) {
return new Criteria(ValueNode.toValueNode(prefixPath(key)));
}
/**
* Static factory method to create a Criteria using the provided key
*
* @param key ads new filed to criteria
* @return the criteria builder
*/
public Criteria and(String key) {
checkComplete();
return new Criteria(this.criteriaChain, ValueNode.toValueNode(prefixPath(key)));
}
/**
* Creates a criterion using equality
*
* @param o
* @return the criteria
*/
public Criteria is(Object o) {
this.criteriaType = RelationalOperator.EQ;
this.right = ValueNode.toValueNode(o);
return this;
}
/**
* Creates a criterion using equality
*
* @param o
* @return the criteria
*/
public Criteria eq(Object o) {
return is(o);
}
/**
* Creates a criterion using the <b>!=</b> operator
*
* @param o
* @return the criteria
*/
public Criteria ne(Object o) {
this.criteriaType = RelationalOperator.NE;
this.right = ValueNode.toValueNode(o);
return this;
}
/**
* Creates a criterion using the <b>&lt;</b> operator
*
* @param o
* @return the criteria
*/
public Criteria lt(Object o) {
this.criteriaType = RelationalOperator.LT;
this.right = ValueNode.toValueNode(o);
return this;
}
/**
* Creates a criterion using the <b>&lt;=</b> operator
*
* @param o
* @return the criteria
*/
public Criteria lte(Object o) {
this.criteriaType = RelationalOperator.LTE;
this.right = ValueNode.toValueNode(o);
return this;
}
/**
* Creates a criterion using the <b>&gt;</b> operator
*
* @param o
* @return the criteria
*/
public Criteria gt(Object o) {
this.criteriaType = RelationalOperator.GT;
this.right = ValueNode.toValueNode(o);
return this;
}
/**
* Creates a criterion using the <b>&gt;=</b> operator
*
* @param o
* @return the criteria
*/
public Criteria gte(Object o) {
this.criteriaType = RelationalOperator.GTE;
this.right = ValueNode.toValueNode(o);
return this;
}
/**
* Creates a criterion using a Regex
*
* @param pattern
* @return the criteria
*/
public Criteria regex(Pattern pattern) {
notNull(pattern, "pattern can not be null");
this.criteriaType = RelationalOperator.REGEX;
this.right = ValueNode.toValueNode(pattern);
return this;
}
/**
* The <code>in</code> operator is analogous to the SQL IN modifier, allowing you
* to specify an array of possible matches.
*
* @param o the values to match against
* @return the criteria
*/
public Criteria in(Object... o) {
return in(Arrays.asList(o));
}
/**
* The <code>in</code> operator is analogous to the SQL IN modifier, allowing you
* to specify an array of possible matches.
*
* @param c the collection containing the values to match against
* @return the criteria
*/
public Criteria in(Collection<?> c) {
notNull(c, "collection can not be null");
this.criteriaType = RelationalOperator.IN;
this.right = new ValueListNode(c);
return this;
}
/**
* The <code>contains</code> operator asserts that the provided object is contained
* in the result. The object that should contain the input can be either an object or a String.
*
* @param o that should exists in given collection or
* @return the criteria
*/
public Criteria contains(Object o) {
this.criteriaType = RelationalOperator.CONTAINS;
this.right = ValueNode.toValueNode(o);
return this;
}
/**
* The <code>nin</code> operator is similar to $in except that it selects objects for
* which the specified field does not have any value in the specified array.
*
* @param o the values to match against
* @return the criteria
*/
public Criteria nin(Object... o) {
return nin(Arrays.asList(o));
}
/**
* The <code>nin</code> operator is similar to $in except that it selects objects for
* which the specified field does not have any value in the specified array.
*
* @param c the values to match against
* @return the criteria
*/
public Criteria nin(Collection<?> c) {
notNull(c, "collection can not be null");
this.criteriaType = RelationalOperator.NIN;
this.right = new ValueListNode(c);
return this;
}
/**
* The <code>subsetof</code> operator selects objects for which the specified field is
* an array whose elements comprise a subset of the set comprised by the elements of
* the specified array.
*
* @param o the values to match against
* @return the criteria
*/
public Criteria subsetof(Object... o) {
return subsetof(Arrays.asList(o));
}
/**
* The <code>subsetof</code> operator selects objects for which the specified field is
* an array whose elements comprise a subset of the set comprised by the elements of
* the specified array.
*
* @param c the values to match against
* @return the criteria
*/
public Criteria subsetof(Collection<?> c) {
notNull(c, "collection can not be null");
this.criteriaType = RelationalOperator.SUBSETOF;
this.right = new ValueListNode(c);
return this;
}
/**
* The <code>anyof</code> operator selects objects for which the specified field is
* an array that contain at least an element in the specified array.
*
* @param o the values to match against
* @return the criteria
*/
public Criteria anyof(Object... o) {
return subsetof(Arrays.asList(o));
}
/**
* The <code>anyof</code> operator selects objects for which the specified field is
* an array that contain at least an element in the specified array.
*
* @param c the values to match against
* @return the criteria
*/
public Criteria anyof(Collection<?> c) {
notNull(c, "collection can not be null");
this.criteriaType = RelationalOperator.ANYOF;
this.right = new ValueListNode(c);
return this;
}
/**
* The <code>noneof</code> operator selects objects for which the specified field is
* an array that does not contain any of the elements of the specified array.
*
* @param o the values to match against
* @return the criteria
*/
public Criteria noneof(Object... o) {
return subsetof(Arrays.asList(o));
}
/**
* The <code>noneof</code> operator selects objects for which the specified field is
* an array that does not contain any of the elements of the specified array.
*
* @param c the values to match against
* @return the criteria
*/
public Criteria noneof(Collection<?> c) {
notNull(c, "collection can not be null");
this.criteriaType = RelationalOperator.NONEOF;
this.right = new ValueListNode(c);
return this;
}
/**
* The <code>all</code> operator is similar to $in, but instead of matching any value
* in the specified array all values in the array must be matched.
*
* @param o
* @return the criteria
*/
public Criteria all(Object... o) {
return all(Arrays.asList(o));
}
/**
* The <code>all</code> operator is similar to $in, but instead of matching any value
* in the specified array all values in the array must be matched.
*
* @param c
* @return the criteria
*/
public Criteria all(Collection<?> c) {
notNull(c, "collection can not be null");
this.criteriaType = RelationalOperator.ALL;
this.right = new ValueListNode(c);
return this;
}
/**
* The <code>size</code> operator matches:
* <p/>
* <ol>
* <li>array with the specified number of elements.</li>
* <li>string with given length.</li>
* </ol>
*
* @param size
* @return the criteria
*/
public Criteria size(int size) {
this.criteriaType = RelationalOperator.SIZE;
this.right = ValueNode.toValueNode(size);
return this;
}
/**
* The $type operator matches values based on their Java JSON type.
*
* Supported types are:
*
* List.class
* Map.class
* String.class
* Number.class
* Boolean.class
*
* Other types evaluates to false
*
* @param clazz
* @return the criteria
*/
public Criteria type(Class<?> clazz) {
this.criteriaType = RelationalOperator.TYPE;
this.right = ValueNode.createClassNode(clazz);
return this;
}
/**
* Check for existence (or lack thereof) of a field.
*
* @param shouldExist
* @return the criteria
*/
public Criteria exists(boolean shouldExist) {
this.criteriaType = RelationalOperator.EXISTS;
this.right = ValueNode.toValueNode(shouldExist);
this.left = left.asPathNode().asExistsCheck(shouldExist);
return this;
}
/**
* The <code>notEmpty</code> operator checks that an array or String is not empty.
*
* @return the criteria
*/
@Deprecated
public Criteria notEmpty() {
return empty(false);
}
/**
* The <code>notEmpty</code> operator checks that an array or String is empty.
*
* @param empty should be empty
* @return the criteria
*/
public Criteria empty(boolean empty) {
this.criteriaType = RelationalOperator.EMPTY;
this.right = empty ? ValueNodes.TRUE : ValueNodes.FALSE;
return this;
}
/**
* The <code>matches</code> operator checks that an object matches the given predicate.
*
* @param p
* @return the criteria
*/
public Criteria matches(Predicate p) {
this.criteriaType = RelationalOperator.MATCHES;
this.right = new PredicateNode(p);
return this;
}
/**
* Parse the provided criteria
*
* Deprecated use {@link Filter#parse(String)}
*
* @param criteria
* @return a criteria
*/
@Deprecated
public static Criteria parse(String criteria) {
if(criteria == null){
throw new InvalidPathException("Criteria can not be null");
}
String[] split = criteria.trim().split(" ");
if(split.length == 3){
return create(split[0], split[1], split[2]);
} else if(split.length == 1){
return create(split[0], "EXISTS", "true");
} else {
throw new InvalidPathException("Could not parse criteria");
}
}
/**
* Creates a new criteria
* @param left path to evaluate in criteria
* @param operator operator
* @param right expected value
* @return a new Criteria
*/
@Deprecated
public static Criteria create(String left, String operator, String right) {
Criteria criteria = new Criteria(ValueNode.toValueNode(left));
criteria.criteriaType = RelationalOperator.fromString(operator);
criteria.right = ValueNode.toValueNode(right);
return criteria;
}
private static String prefixPath(String key){
if (!key.startsWith("$") && !key.startsWith("@")) {
key = "@." + key;
}
return key;
}
private void checkComplete(){
boolean complete = (left != null && criteriaType != null && right != null);
if(!complete){
throw new JsonPathException("Criteria build exception. Complete on criteria before defining next.");
}
}
}

@ -0,0 +1,18 @@
/*
* 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 DocumentContext extends ReadContext, WriteContext {
}

@ -0,0 +1,63 @@
/*
* 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;
/**
* A listener that can be registered on a {@link com.jayway.jsonpath.Configuration} that is notified when a
* result is added to the result of this path evaluation.
*/
public interface EvaluationListener {
/**
* Callback invoked when result is found
* @param found the found result
* @return continuation instruction
*/
EvaluationContinuation resultFound(FoundResult found);
enum EvaluationContinuation {
/**
* Evaluation continues
*/
CONTINUE,
/**
* Current result is included but no further evaluation will be performed.
*/
ABORT
}
/**
*
*/
interface FoundResult {
/**
* the index of this result. First result i 0
* @return index
*/
int index();
/**
* The path of this result
* @return path
*/
String path();
/**
* The result object
* @return the result object
*/
Object result();
}
}

@ -0,0 +1,184 @@
/*
* 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.internal.filter.FilterCompiler;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import static java.util.Arrays.asList;
/**
*
*/
public abstract class Filter implements Predicate {
/**
* Creates a new Filter based on given criteria
* @param predicate criteria
* @return a new Filter
*/
public static Filter filter(Predicate predicate) {
return new SingleFilter(predicate);
}
/**
* Create a new Filter based on given list of criteria.
* @param predicates list of criteria all needs to evaluate to true
* @return the filter
*/
public static Filter filter(Collection<Predicate> predicates) {
return new AndFilter(predicates);
}
@Override
public abstract boolean apply(PredicateContext ctx);
public Filter or(final Predicate other){
return new OrFilter(this, other);
}
public Filter and(final Predicate other){
return new AndFilter(this, other);
}
/**
* Parses a filter. The filter must match <code>[?(<filter>)]</code>, white spaces are ignored.
* @param filter filter string to parse
* @return the filter
*/
public static Filter parse(String filter){
return FilterCompiler.compile(filter);
}
private static final class SingleFilter extends Filter {
private final Predicate predicate;
private SingleFilter(Predicate predicate) {
this.predicate = predicate;
}
@Override
public boolean apply(PredicateContext ctx) {
return predicate.apply(ctx);
}
@Override
public String toString() {
String predicateString = predicate.toString();
if(predicateString.startsWith("(")){
return "[?" + predicateString + "]";
} else {
return "[?(" + predicateString + ")]";
}
}
}
private static final class AndFilter extends Filter {
private final Collection<Predicate> predicates;
private AndFilter(Collection<Predicate> predicates) {
this.predicates = predicates;
}
private AndFilter(Predicate left, Predicate right) {
this(asList(left, right));
}
public Filter and(final Predicate other){
Collection<Predicate> newPredicates = new ArrayList<Predicate>(predicates);
newPredicates.add(other);
return new AndFilter(newPredicates);
}
@Override
public boolean apply(PredicateContext ctx) {
for (Predicate predicate : predicates) {
if(!predicate.apply(ctx)){
return false;
}
}
return true;
}
@Override
public String toString() {
Iterator<Predicate> i = predicates.iterator();
StringBuilder sb = new StringBuilder();
sb.append("[?(");
while (i.hasNext()){
String p = i.next().toString();
if(p.startsWith("[?(")){
p = p.substring(3, p.length() - 2);
}
sb.append(p);
if(i.hasNext()){
sb.append(" && ");
}
}
sb.append(")]");
return sb.toString();
}
}
private static final class OrFilter extends Filter {
private final Predicate left;
private final Predicate right;
private OrFilter(Predicate left, Predicate right) {
this.left = left;
this.right = right;
}
public Filter and(final Predicate other){
return new OrFilter(left, new AndFilter(right, other));
}
@Override
public boolean apply(PredicateContext ctx) {
boolean a = left.apply(ctx);
return a || right.apply(ctx);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[?(");
String l = left.toString();
String r = right.toString();
if(l.startsWith("[?(")){
l = l.substring(3, l.length() - 2);
}
if(r.startsWith("[?(")){
r = r.substring(3, r.length() - 2);
}
sb.append(l).append(" || ").append(r);
sb.append(")]");
return sb.toString();
}
}
}

@ -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;
@SuppressWarnings("serial")
public class InvalidCriteriaException extends JsonPathException {
public InvalidCriteriaException() {
}
public InvalidCriteriaException(String message) {
super(message);
}
public InvalidCriteriaException(String message, Throwable cause) {
super(message, cause);
}
public InvalidCriteriaException(Throwable cause) {
super(cause);
}
}

@ -0,0 +1,58 @@
/*
* 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;
@SuppressWarnings("serial")
public class InvalidJsonException extends JsonPathException {
/**
* Problematic JSON if available.
*/
private final String json;
public InvalidJsonException() {
json = null;
}
public InvalidJsonException(String message) {
super(message);
json = null;
}
public InvalidJsonException(String message, Throwable cause) {
super(message, cause);
json = null;
}
public InvalidJsonException(Throwable cause) {
super(cause);
json = null;
}
/**
* Rethrow the exception with the problematic JSON captured.
*/
public InvalidJsonException(final Throwable cause, final String json) {
super(cause);
this.json = json;
}
/**
* @return the problematic JSON if available.
*/
public String getJson() {
return json;
}
}

@ -0,0 +1,21 @@
/*
* 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 InvalidModificationException extends JsonPathException {
public InvalidModificationException(String message) {
super(message);
}
}

@ -0,0 +1,34 @@
/*
* 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;
@SuppressWarnings("serial")
public class InvalidPathException extends JsonPathException {
public InvalidPathException() {
}
public InvalidPathException(String message) {
super(message);
}
public InvalidPathException(String message, Throwable cause) {
super(message, cause);
}
public InvalidPathException(Throwable cause) {
super(cause);
}
}

@ -0,0 +1,695 @@
/*
* 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.internal.*;
import com.jayway.jsonpath.internal.path.PathCompiler;
import com.jayway.jsonpath.spi.json.JsonProvider;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import static com.jayway.jsonpath.Option.ALWAYS_RETURN_LIST;
import static com.jayway.jsonpath.Option.AS_PATH_LIST;
import static com.jayway.jsonpath.internal.Utils.*;
/**
* <p/>
* JsonPath is to JSON what XPATH is to XML, a simple way to extract parts of a given document. JsonPath is
* available in many programming languages such as Javascript, Python and PHP.
* <p/>
* JsonPath allows you to compile a json path string to use it many times or to compile and apply in one
* single on demand operation.
* <p/>
* Given the Json document:
* <p/>
* <pre>
* String json =
* "{
* "store":
* {
* "book":
* [
* {
* "category": "reference",
* "author": "Nigel Rees",
* "title": "Sayings of the Century",
* "price": 8.95
* },
* {
* "category": "fiction",
* "author": "Evelyn Waugh",
* "title": "Sword of Honour",
* "price": 12.99
* }
* ],
* "bicycle":
* {
* "color": "red",
* "price": 19.95
* }
* }
* }";
* </pre>
* <p/>
* A JsonPath can be compiled and used as shown:
* <p/>
* <code>
* JsonPath path = JsonPath.compile("$.store.book[1]");
* <br/>
* List&lt;Object&gt; books = path.read(json);
* </code>
* </p>
* Or:
* <p/>
* <code>
* List&lt;Object&gt; authors = JsonPath.read(json, "$.store.book[*].author")
* </code>
* <p/>
* If the json path returns a single value (is definite):
* </p>
* <code>
* String author = JsonPath.read(json, "$.store.book[1].author")
* </code>
*/
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
* <p/>
* a path is considered <strong>not</strong> definite if it contains a scan fragment ".."
* or an array position fragment that is not based on a single index
* <p/>
* <p/>
* definite path examples are:
* <p/>
* $store.book
* $store.book[1].title
* <p/>
* not definite path examples are:
* <p/>
* $..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 <T> expected return type
* @return object(s) matched by the given path
*/
@SuppressWarnings({"unchecked"})
public <T> 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 <T> expected return type
* @return object(s) matched by the given path
*/
@SuppressWarnings("unchecked")
public <T> 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 <T> expected return type
* @return the updated jsonObject or the path list to updated objects if option AS_PATH_LIST is set.
*/
public <T> 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> 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 <T> expected return type
* @return the updated jsonObject or the path list to deleted objects if option AS_PATH_LIST is set.
*/
public <T> 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 <T> expected return type
* @return the updated jsonObject or the path list to updated object if option AS_PATH_LIST is set.
*/
public <T> 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 <T> expected return type
* @return the updated jsonObject or the path list to updated objects if option AS_PATH_LIST is set.
*/
public <T> 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> 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 <T> expected return type
* @return list of objects matched by the given path
*/
@SuppressWarnings({"unchecked"})
public <T> 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 <T> expected return type
* @return list of objects matched by the given path
*/
@SuppressWarnings({"unchecked"})
public <T> 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 <T> expected return type
* @return list of objects matched by the given path
* @throws IOException
*/
@SuppressWarnings({"unchecked"})
public <T> 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 <T> expected return type
* @return list of objects matched by the given path
* @throws IOException
*/
@SuppressWarnings({"unchecked"})
public <T> 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 <T> expected return type
* @return list of objects matched by the given path
* @throws IOException
*/
@SuppressWarnings({"unchecked"})
public <T> 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 <T> expected return type
* @return list of objects matched by the given path
* @throws IOException
*/
@SuppressWarnings({"unchecked"})
public <T> 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 <T> expected return type
* @return list of objects matched by the given path
* @throws IOException
*/
@SuppressWarnings({"unchecked"})
public <T> 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 <T> expected return type
* @return list of objects matched by the given path
* @throws IOException
*/
@SuppressWarnings({"unchecked"})
public <T> 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 <T> expected return type
* @return list of objects matched by the given path
*/
@SuppressWarnings({"unchecked"})
public static <T> 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 <T> expected return type
* @return list of objects matched by the given path
*/
@SuppressWarnings({"unchecked"})
public static <T> 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 <T> expected return type
* @return list of objects matched by the given path
*/
@SuppressWarnings({"unchecked"})
@Deprecated
public static <T> 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 <T> expected return type
* @return list of objects matched by the given path
*/
@SuppressWarnings({"unchecked"})
public static <T> 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 <T> expected return type
* @return list of objects matched by the given path
*/
@SuppressWarnings({"unchecked"})
public static <T> 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> T resultByConfiguration(Object jsonObject, Configuration configuration, EvaluationContext evaluationContext) {
if(configuration.containsOption(AS_PATH_LIST)){
return (T)evaluationContext.getPathList();
} else {
return (T) jsonObject;
}
}
}

@ -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);
}
}

@ -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);
}

@ -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 <code>null</code> for missing leaf.
*
* <pre>
* [
* {
* "foo" : "foo1",
* "bar" : "bar1"
* }
* {
* "foo" : "foo2"
* }
* ]
*</pre>
*
* 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.
* <br/>
* 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 <bold>indefinite</bold> path is evaluated.
*
*
* Given:
*
* <pre>
* [
* {
* "a" : "a-val",
* "b" : "b-val"
* },
* {
* "a" : "a-val",
* }
* ]
* </pre>
*
* 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
}

@ -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;
}

@ -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);
}
}

@ -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> T item(Class<T> clazz) throws MappingException;
/**
* Returns the root document (the complete JSON)
* @return root document
*/
Object root();
/**
* Configuration to use when evaluating
* @return configuration
*/
Configuration configuration();
}
}

@ -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> 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 <T>
* @return result
*/
<T> 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 <T>
* @return result
*/
<T> T read(String path, Class<T> type, Predicate... filters);
/**
* Reads the given path from this context
*
* @param path path to apply
* @param <T>
* @return result
*/
<T> 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 <T>
* @return result
*/
<T> T read(JsonPath path, Class<T> type);
/**
* Reads the given path from this context
*
* Sample code to create a TypeRef
* <code>
* TypeRef ref = new TypeRef<List<Integer>>() {};
* </code>
*
* @param path path to apply
* @param typeRef expected return type (will try to map)
* @param <T>
* @return result
*/
<T> T read(JsonPath path, TypeRef<T> typeRef);
/**
* Reads the given path from this context
*
* Sample code to create a TypeRef
* <code>
* TypeRef ref = new TypeRef<List<Integer>>() {};
* </code>
*
* @param path path to apply
* @param typeRef expected return type (will try to map)
* @param <T>
* @return result
*/
<T> T read(String path, TypeRef<T> 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);
}

@ -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}
*
* <code>
* TypeRef ref = new TypeRef<List<Integer>>() { };
* </code>
*
* @param <T>
*/
public abstract class TypeRef<T> implements Comparable<TypeRef<T>> {
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 <code>Comparable</code>) is to prevent constructing a
* reference without type information.
*/
@Override
public int compareTo(TypeRef<T> o) {
return 0;
}
}

@ -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);
}
}

@ -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> 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
*
* <pre>
* <code>
* List<Integer> array = new ArrayList<Integer>(){{
* add(0);
* add(1);
* }};
*
* JsonPath.parse(array).add("$", 2);
*
* assertThat(array).containsExactly(0,1,2);
* </code>
* </pre>
*
* @param path path to array
* @param value value to add
* @param filters filters
* @return a document context
*/
DocumentContext add(String path, Object value, Predicate... filters);
/**
* Add value to array at the given path
*
* @param path path to array
* @param value value to add
* @return a document context
*/
DocumentContext add(JsonPath path, Object value);
/**
* Add or update the key with a the given value at the given path
*
* @param path path to object
* @param key key to add
* @param value value of key
* @param filters filters
* @return a document context
*/
DocumentContext put(String path, String key, Object value, Predicate... filters);
/**
* Add or update the key with a the given value at the given path
*
* @param path path to array
* @param key key to add
* @param value value of key
* @return a document context
*/
DocumentContext put(JsonPath path, String key, Object value);
/**
* Renames the last key element of a given path.
* @param path The path to the old key. Should be resolved to a map
* or an array including map items.
* @param oldKeyName The old key name.
* @param newKeyName The new key name.
* @param filters filters.
* @return a document content.
*/
DocumentContext renameKey(String path, String oldKeyName, String newKeyName, Predicate... filters);
/**
* Renames the last key element of a given path.
* @param path The path to the old key. Should be resolved to a map
* or an array including map items.
* @param oldKeyName The old key name.
* @param newKeyName The new key name.
* @return a document content.
*/
DocumentContext renameKey(JsonPath path, String oldKeyName, String newKeyName);
}

@ -0,0 +1,318 @@
package com.jayway.jsonpath.internal;
import com.jayway.jsonpath.InvalidPathException;
public class CharacterIndex {
private static final char OPEN_PARENTHESIS = '(';
private static final char CLOSE_PARENTHESIS = ')';
private static final char CLOSE_SQUARE_BRACKET = ']';
private static final char SPACE = ' ';
private static final char ESCAPE = '\\';
private static final char SINGLE_QUOTE = '\'';
private static final char DOUBLE_QUOTE = '"';
private static final char MINUS = '-';
private static final char PERIOD = '.';
private static final char REGEX = '/';
private final CharSequence charSequence;
private int position;
private int endPosition;
public CharacterIndex(CharSequence charSequence) {
this.charSequence = charSequence;
this.position = 0;
this.endPosition = charSequence.length() - 1;
}
public int length() {
return endPosition + 1;
}
public char charAt(int idx) {
return charSequence.charAt(idx);
}
public char currentChar() {
return charSequence.charAt(position);
}
public boolean currentCharIs(char c) {
return (charSequence.charAt(position) == c);
}
public boolean lastCharIs(char c) {
return charSequence.charAt(endPosition) == c;
}
public boolean nextCharIs(char c) {
return inBounds(position + 1) && (charSequence.charAt(position + 1) == c);
}
public int incrementPosition(int charCount) {
return setPosition(position + charCount);
}
public int decrementEndPosition(int charCount) {
return setEndPosition(endPosition - charCount);
}
public int setPosition(int newPosition) {
//position = min(newPosition, charSequence.length() - 1);
position = newPosition;
return position;
}
private int setEndPosition(int newPosition) {
endPosition = newPosition;
return endPosition;
}
public int position(){
return position;
}
public int indexOfClosingSquareBracket(int startPosition) {
int readPosition = startPosition;
while (inBounds(readPosition)) {
if(charAt(readPosition) == CLOSE_SQUARE_BRACKET){
return readPosition;
}
readPosition++;
}
return -1;
}
public int indexOfMatchingCloseChar(int startPosition, char openChar, char closeChar, boolean skipStrings, boolean skipRegex) {
if(charAt(startPosition) != openChar){
throw new InvalidPathException("Expected " + openChar + " but found " + charAt(startPosition));
}
int opened = 1;
int readPosition = startPosition + 1;
while (inBounds(readPosition)) {
if (skipStrings) {
char quoteChar = charAt(readPosition);
if (quoteChar == SINGLE_QUOTE || quoteChar == DOUBLE_QUOTE){
readPosition = nextIndexOfUnescaped(readPosition, quoteChar);
if(readPosition == -1){
throw new InvalidPathException("Could not find matching close quote for " + quoteChar + " when parsing : " + charSequence);
}
readPosition++;
}
}
if (skipRegex) {
if (charAt(readPosition) == REGEX) {
readPosition = nextIndexOfUnescaped(readPosition, REGEX);
if(readPosition == -1){
throw new InvalidPathException("Could not find matching close for " + REGEX + " when parsing regex in : " + charSequence);
}
readPosition++;
}
}
if (charAt(readPosition) == openChar) {
opened++;
}
if (charAt(readPosition) == closeChar) {
opened--;
if (opened == 0) {
return readPosition;
}
}
readPosition++;
}
return -1;
}
public int indexOfClosingBracket(int startPosition, boolean skipStrings, boolean skipRegex) {
return indexOfMatchingCloseChar(startPosition, OPEN_PARENTHESIS, CLOSE_PARENTHESIS, skipStrings, skipRegex);
}
public int indexOfNextSignificantChar(char c) {
return indexOfNextSignificantChar(position, c);
}
public int indexOfNextSignificantChar(int startPosition, char c) {
int readPosition = startPosition + 1;
while (!isOutOfBounds(readPosition) && charAt(readPosition) == SPACE) {
readPosition++;
}
if (charAt(readPosition) == c) {
return readPosition;
} else {
return -1;
}
}
public int nextIndexOf(char c) {
return nextIndexOf(position + 1, c);
}
public int nextIndexOf(int startPosition, char c) {
int readPosition = startPosition;
while (!isOutOfBounds(readPosition)) {
if (charAt(readPosition) == c) {
return readPosition;
}
readPosition++;
}
return -1;
}
public int nextIndexOfUnescaped(char c) {
return nextIndexOfUnescaped(position, c);
}
public int nextIndexOfUnescaped(int startPosition, char c) {
int readPosition = startPosition + 1;
boolean inEscape = false;
while (!isOutOfBounds(readPosition)) {
if(inEscape){
inEscape = false;
} else if('\\' == charAt(readPosition)){
inEscape = true;
} else if (c == charAt(readPosition)){
return readPosition;
}
readPosition ++;
}
return -1;
}
public char charAtOr(int postition, char defaultChar){
if(!inBounds(postition)) return defaultChar;
else return charAt(postition);
}
public boolean nextSignificantCharIs(int startPosition, char c) {
int readPosition = startPosition + 1;
while (!isOutOfBounds(readPosition) && charAt(readPosition) == SPACE) {
readPosition++;
}
return !isOutOfBounds(readPosition) && charAt(readPosition) == c;
}
public boolean nextSignificantCharIs(char c) {
return nextSignificantCharIs(position, c);
}
public char nextSignificantChar() {
return nextSignificantChar(position);
}
public char nextSignificantChar(int startPosition) {
int readPosition = startPosition + 1;
while (!isOutOfBounds(readPosition) && charAt(readPosition) == SPACE) {
readPosition++;
}
if (!isOutOfBounds(readPosition)) {
return charAt(readPosition);
} else {
return ' ';
}
}
public void readSignificantChar(char c) {
if (skipBlanks().currentChar() != c) {
throw new InvalidPathException(String.format("Expected character: %c", c));
}
incrementPosition(1);
}
public boolean hasSignificantSubSequence(CharSequence s) {
skipBlanks();
if (! inBounds(position + s.length() - 1)) {
return false;
}
if (! subSequence(position, position + s.length()).equals(s)) {
return false;
}
incrementPosition(s.length());
return true;
}
public int indexOfPreviousSignificantChar(int startPosition){
int readPosition = startPosition - 1;
while (!isOutOfBounds(readPosition) && charAt(readPosition) == SPACE) {
readPosition--;
}
if (!isOutOfBounds(readPosition)) {
return readPosition;
} else {
return -1;
}
}
public int indexOfPreviousSignificantChar(){
return indexOfPreviousSignificantChar(position);
}
public char previousSignificantChar(int startPosition) {
int previousSignificantCharIndex = indexOfPreviousSignificantChar(startPosition);
if(previousSignificantCharIndex == -1) return ' ';
else return charAt(previousSignificantCharIndex);
}
public char previousSignificantChar() {
return previousSignificantChar(position);
}
public boolean currentIsTail() {
return position >= endPosition;
}
public boolean hasMoreCharacters() {
return inBounds(position + 1);
}
public boolean inBounds(int idx) {
return (idx >= 0) && (idx <= endPosition);
}
public boolean inBounds() {
return inBounds(position);
}
public boolean isOutOfBounds(int idx) {
return !inBounds(idx);
}
public CharSequence subSequence(int start, int end) {
return charSequence.subSequence(start, end);
}
public CharSequence charSequence() {
return charSequence;
}
@Override
public String toString() {
return charSequence.toString();
}
public boolean isNumberCharacter(int readPosition) {
char c = charAt(readPosition);
return Character.isDigit(c) || c == MINUS || c == PERIOD;
}
public CharacterIndex skipBlanks() {
while (inBounds() && position < endPosition && currentChar() == SPACE){
incrementPosition(1);
}
return this;
}
private CharacterIndex skipBlanksAtEnd() {
while (inBounds() && position < endPosition && lastCharIs(SPACE)){
decrementEndPosition(1);
}
return this;
}
public CharacterIndex trim() {
skipBlanks();
skipBlanksAtEnd();
return this;
}
}

@ -0,0 +1,37 @@
package com.jayway.jsonpath.internal;
import com.jayway.jsonpath.Configuration.Defaults;
import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.spi.json.JsonSmartJsonProvider;
import com.jayway.jsonpath.spi.json.JsonProvider;
import com.jayway.jsonpath.spi.mapper.JsonSmartMappingProvider;
import com.jayway.jsonpath.spi.mapper.MappingProvider;
import java.util.EnumSet;
import java.util.Set;
public final class DefaultsImpl implements Defaults {
public static final DefaultsImpl INSTANCE = new DefaultsImpl();
private final MappingProvider mappingProvider = new JsonSmartMappingProvider();
@Override
public JsonProvider jsonProvider() {
return new JsonSmartJsonProvider();
}
@Override
public Set<Option> options() {
return EnumSet.noneOf(Option.class);
}
@Override
public MappingProvider mappingProvider() {
return mappingProvider;
}
private DefaultsImpl() {
}
}

@ -0,0 +1,13 @@
package com.jayway.jsonpath.internal;
public class EvaluationAbortException extends RuntimeException {
private static final long serialVersionUID = 4419305302960432348L;
// this is just a marker exception to abort evaluation, we don't care about
// the stack
@Override
public Throwable fillInStackTrace() {
return this;
}
}

@ -0,0 +1,73 @@
/*
* 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;
import com.jayway.jsonpath.Configuration;
import java.util.Collection;
import java.util.List;
public interface EvaluationContext {
/**
*
* @return the configuration used for this evaluation
*/
Configuration configuration();
/**
* The json document that is evaluated
*
* @return the document
*/
Object rootDocument();
/**
* This method does not adhere to configuration settings. It will return a single object (not wrapped in a List) even if the
* configuration contains the {@link com.jayway.jsonpath.Option#ALWAYS_RETURN_LIST}
*
* @param <T> expected return type
* @return evaluation result
*/
<T> T getValue();
/**
* See {@link com.jayway.jsonpath.internal.EvaluationContext#getValue()}
*
* @param unwrap tells th underlying json provider if primitives should be unwrapped
* @param <T> expected return type
* @return evaluation result
*/
<T> T getValue(boolean unwrap);
/**
* Returns the list of formalized paths that represent the result of the evaluation
* @param <T>
* @return list of paths
*/
<T> T getPath();
/**
* Convenience method to get list of hits as String path representations
*
* @return list of path representations
*/
List<String> getPathList();
Collection<PathRef> updateOperations();
}

@ -0,0 +1,216 @@
/*
* 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;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.EvaluationListener;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.MapFunction;
import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.Predicate;
import com.jayway.jsonpath.ReadContext;
import com.jayway.jsonpath.TypeRef;
import com.jayway.jsonpath.spi.cache.Cache;
import com.jayway.jsonpath.spi.cache.CacheProvider;
import java.util.LinkedList;
import java.util.List;
import static com.jayway.jsonpath.JsonPath.compile;
import static com.jayway.jsonpath.internal.Utils.notEmpty;
import static com.jayway.jsonpath.internal.Utils.notNull;
import static java.util.Arrays.asList;
public class JsonContext implements DocumentContext {
private final Configuration configuration;
private final Object json;
JsonContext(Object json, Configuration configuration) {
notNull(json, "json can not be null");
notNull(configuration, "configuration can not be null");
this.configuration = configuration;
this.json = json;
}
@Override
public Configuration configuration() {
return configuration;
}
//------------------------------------------------
//
// ReadContext impl
//
//------------------------------------------------
@Override
public Object json() {
return json;
}
@Override
public String jsonString() {
return configuration.jsonProvider().toJson(json);
}
@Override
public <T> T read(String path, Predicate... filters) {
notEmpty(path, "path can not be null or empty");
return read(pathFromCache(path, filters));
}
@Override
public <T> T read(String path, Class<T> type, Predicate... filters) {
return convert(read(path, filters), type, configuration);
}
@Override
public <T> T read(JsonPath path) {
notNull(path, "path can not be null");
return path.read(json, configuration);
}
@Override
public <T> T read(JsonPath path, Class<T> type) {
return convert(read(path), type, configuration);
}
@Override
public <T> T read(JsonPath path, TypeRef<T> type) {
return convert(read(path), type, configuration);
}
@Override
public <T> T read(String path, TypeRef<T> type) {
return convert(read(path), type, configuration);
}
@Override
public ReadContext limit(int maxResults) {
return withListeners(new LimitingEvaluationListener(maxResults));
}
@Override
public ReadContext withListeners(EvaluationListener... listener) {
return new JsonContext(json, configuration.setEvaluationListeners(listener));
}
private <T> T convert(Object obj, Class<T> targetType, Configuration configuration) {
return configuration.mappingProvider().map(obj, targetType, configuration);
}
private <T> T convert(Object obj, TypeRef<T> targetType, Configuration configuration) {
return configuration.mappingProvider().map(obj, targetType, configuration);
}
@Override
public DocumentContext set(String path, Object newValue, Predicate... filters) {
return set(pathFromCache(path, filters), newValue);
}
@Override
public DocumentContext set(JsonPath path, Object newValue) {
List<String> modified = path.set(json, newValue, configuration.addOptions(Option.AS_PATH_LIST));
return this;
}
@Override
public DocumentContext map(String path, MapFunction mapFunction, Predicate... filters) {
map(pathFromCache(path, filters), mapFunction);
return this;
}
@Override
public DocumentContext map(JsonPath path, MapFunction mapFunction) {
path.map(json, mapFunction, configuration);
return this;
}
@Override
public DocumentContext delete(String path, Predicate... filters) {
return delete(pathFromCache(path, filters));
}
@Override
public DocumentContext delete(JsonPath path) {
List<String> modified = path.delete(json, configuration.addOptions(Option.AS_PATH_LIST));
return this;
}
@Override
public DocumentContext add(String path, Object value, Predicate... filters) {
return add(pathFromCache(path, filters), value);
}
@Override
public DocumentContext add(JsonPath path, Object value) {
List<String> modified = path.add(json, value, configuration.addOptions(Option.AS_PATH_LIST));
return this;
}
@Override
public DocumentContext put(String path, String key, Object value, Predicate... filters) {
return put(pathFromCache(path, filters), key, value);
}
@Override
public DocumentContext renameKey(String path, String oldKeyName, String newKeyName, Predicate... filters) {
return renameKey(pathFromCache(path, filters), oldKeyName, newKeyName);
}
@Override
public DocumentContext renameKey(JsonPath path, String oldKeyName, String newKeyName) {
List<String> modified = path.renameKey(json, oldKeyName, newKeyName, configuration.addOptions(Option.AS_PATH_LIST));
return this;
}
@Override
public DocumentContext put(JsonPath path, String key, Object value) {
List<String> modified = path.put(json, key, value, configuration.addOptions(Option.AS_PATH_LIST));
return this;
}
private JsonPath pathFromCache(String path, Predicate[] filters) {
Cache cache = CacheProvider.getCache();
String cacheKey = Utils.concat(path, new LinkedList<Predicate>(asList(filters)).toString());
JsonPath jsonPath = cache.get(cacheKey);
if (jsonPath == null) {
jsonPath = compile(path, filters);
cache.put(cacheKey, jsonPath);
}
return jsonPath;
}
private final static class LimitingEvaluationListener implements EvaluationListener {
final int limit;
private LimitingEvaluationListener(int limit) {
this.limit = limit;
}
@Override
public EvaluationContinuation resultFound(FoundResult found) {
if (found.index() == limit - 1) {
return EvaluationContinuation.ABORT;
} else {
return EvaluationContinuation.CONTINUE;
}
}
}
}

@ -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;
public class JsonFormatter {
private static final String INDENT = " ";
private static final String NEW_LINE = System.getProperty("line.separator");
private static final int MODE_SINGLE = 100;
private static final int MODE_DOUBLE = 101;
private static final int MODE_ESCAPE_SINGLE = 102;
private static final int MODE_ESCAPE_DOUBLE = 103;
private static final int MODE_BETWEEN = 104;
private static void appendIndent(StringBuilder sb, int count) {
for (; count > 0; --count) sb.append(INDENT);
}
public static String prettyPrint(String input) {
input = input.replaceAll("[\\r\\n]", "");
StringBuilder output = new StringBuilder(input.length() * 2);
int mode = MODE_BETWEEN;
int depth = 0;
for (int i = 0; i < input.length(); ++i) {
char ch = input.charAt(i);
switch (mode) {
case MODE_BETWEEN:
switch (ch) {
case '{':
case '[':
output.append(ch);
output.append(NEW_LINE);
appendIndent(output, ++depth);
break;
case '}':
case ']':
output.append(NEW_LINE);
appendIndent(output, --depth);
output.append(ch);
break;
case ',':
output.append(ch);
output.append(NEW_LINE);
appendIndent(output, depth);
break;
case ':':
output.append(" : ");
break;
case '\'':
output.append(ch);
mode = MODE_SINGLE;
break;
case '"':
output.append(ch);
mode = MODE_DOUBLE;
break;
case ' ':
break;
default:
output.append(ch);
break;
}
break;
case MODE_ESCAPE_SINGLE:
output.append(ch);
mode = MODE_SINGLE;
break;
case MODE_ESCAPE_DOUBLE:
output.append(ch);
mode = MODE_DOUBLE;
break;
case MODE_SINGLE:
output.append(ch);
switch (ch) {
case '\'':
mode = MODE_BETWEEN;
break;
case '\\':
mode = MODE_ESCAPE_SINGLE;
break;
}
break;
case MODE_DOUBLE:
output.append(ch);
switch (ch) {
case '"':
mode = MODE_BETWEEN;
break;
case '\\':
mode = MODE_ESCAPE_DOUBLE;
break;
}
break;
}
}
return output.toString();
}
}

@ -0,0 +1,83 @@
package com.jayway.jsonpath.internal;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.ParseContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import static com.jayway.jsonpath.internal.Utils.notEmpty;
import static com.jayway.jsonpath.internal.Utils.notNull;
public class ParseContextImpl implements ParseContext {
private final Configuration configuration;
public ParseContextImpl() {
this(Configuration.defaultConfiguration());
}
public ParseContextImpl(Configuration configuration) {
this.configuration = configuration;
}
@Override
public DocumentContext parse(Object json) {
notNull(json, "json object can not be null");
return new JsonContext(json, configuration);
}
@Override
public DocumentContext parse(String json) {
notEmpty(json, "json string can not be null or empty");
Object obj = configuration.jsonProvider().parse(json);
return new JsonContext(obj, configuration);
}
@Override
public DocumentContext parse(InputStream json) {
return parse(json, "UTF-8");
}
@Override
public DocumentContext parse(InputStream json, String charset) {
notNull(json, "json input stream can not be null");
notNull(charset, "charset can not be null");
try {
Object obj = configuration.jsonProvider().parse(json, charset);
return new JsonContext(obj, configuration);
} finally {
Utils.closeQuietly(json);
}
}
@Override
public DocumentContext parse(File json) throws IOException {
notNull(json, "json file can not be null");
FileInputStream fis = null;
try {
fis = new FileInputStream(json);
return parse(fis);
} finally {
Utils.closeQuietly(fis);
}
}
@Override
@Deprecated
public DocumentContext parse(URL url) throws IOException {
notNull(url, "url can not be null");
InputStream fis = null;
try {
fis = url.openStream();
return parse(fis);
} finally {
Utils.closeQuietly(fis);
}
}
}

@ -0,0 +1,64 @@
/*
* 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;
import com.jayway.jsonpath.Configuration;
/**
*
*/
public interface Path {
/**
* Evaluates this path
*
* @param document the json document to apply the path on
* @param rootDocument the root json document that started this evaluation
* @param configuration configuration to use
* @return EvaluationContext containing results of evaluation
*/
EvaluationContext evaluate(Object document, Object rootDocument, Configuration configuration);
/**
* Evaluates this path
*
* @param document the json document to apply the path on
* @param rootDocument the root json document that started this evaluation
* @param configuration configuration to use
* @param forUpdate is this a read or a write operation
* @return EvaluationContext containing results of evaluation
*/
EvaluationContext evaluate(Object document, Object rootDocument, Configuration configuration, boolean forUpdate);
/**
*
* @return true id this path is definite
*/
boolean isDefinite();
/**
*
* @return true id this path is a function
*/
boolean isFunctionPath();
/**
*
* @return true id this path is starts with '$' and false if the path starts with '@'
*/
boolean isRootPath();
}

@ -0,0 +1,333 @@
package com.jayway.jsonpath.internal;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.InvalidModificationException;
import com.jayway.jsonpath.MapFunction;
import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.spi.json.JsonProvider;
import java.util.Collection;
public abstract class PathRef implements Comparable<PathRef> {
public static final PathRef NO_OP = new PathRef(null){
@Override
public Object getAccessor() {
return null;
}
@Override
public void set(Object newVal, Configuration configuration) {}
@Override
public void convert(MapFunction mapFunction, Configuration configuration) {}
@Override
public void delete(Configuration configuration) {}
@Override
public void add(Object newVal, Configuration configuration) {}
@Override
public void put(String key, Object newVal, Configuration configuration) {}
@Override
public void renameKey(String oldKeyName, String newKeyName, Configuration configuration) {}
};
protected Object parent;
private PathRef(Object parent) {
this.parent = parent;
}
abstract Object getAccessor();
public abstract void set(Object newVal, Configuration configuration);
public abstract void convert(MapFunction mapFunction, Configuration configuration);
public abstract void delete(Configuration configuration);
public abstract void add(Object newVal, Configuration configuration);
public abstract void put(String key, Object newVal, Configuration configuration);
public abstract void renameKey(String oldKey,String newKeyName, Configuration configuration);
protected void renameInMap(Object targetMap, String oldKeyName, String newKeyName, Configuration configuration){
if(configuration.jsonProvider().isMap(targetMap)){
if(configuration.jsonProvider().getMapValue(targetMap, oldKeyName) == JsonProvider.UNDEFINED){
throw new PathNotFoundException("No results for Key "+oldKeyName+" found in map!");
}
configuration.jsonProvider().setProperty(targetMap, newKeyName, configuration.jsonProvider().getMapValue(targetMap, oldKeyName));
configuration.jsonProvider().removeProperty(targetMap, oldKeyName);
} else {
throw new InvalidModificationException("Can only rename properties in a map");
}
}
protected boolean targetInvalid(Object target){
return target == JsonProvider.UNDEFINED || target == null;
}
@Override
public int compareTo(PathRef o) {
return this.getAccessor().toString().compareTo(o.getAccessor().toString()) * -1;
}
public static PathRef create(Object obj, String property){
return new ObjectPropertyPathRef(obj, property);
}
public static PathRef create(Object obj, Collection<String> properties){
return new ObjectMultiPropertyPathRef(obj, properties);
}
public static PathRef create(Object array, int index){
return new ArrayIndexPathRef(array, index);
}
public static PathRef createRoot(Object root){
return new RootPathRef(root);
}
private static class RootPathRef extends PathRef {
private RootPathRef(Object parent) {
super(parent);
}
@Override
Object getAccessor() {
return "$";
}
@Override
public void set(Object newVal, Configuration configuration) {
throw new InvalidModificationException("Invalid set operation");
}
public void convert(MapFunction mapFunction, Configuration configuration){
throw new InvalidModificationException("Invalid map operation");
}
@Override
public void delete(Configuration configuration) {
throw new InvalidModificationException("Invalid delete operation");
}
@Override
public void add(Object newVal, Configuration configuration) {
if(configuration.jsonProvider().isArray(parent)){
configuration.jsonProvider().setArrayIndex(parent, configuration.jsonProvider().length(parent), newVal);
} else {
throw new InvalidModificationException("Invalid add operation. $ is not an array");
}
}
@Override
public void put(String key, Object newVal, Configuration configuration) {
if(configuration.jsonProvider().isMap(parent)){
configuration.jsonProvider().setProperty(parent, key, newVal);
} else {
throw new InvalidModificationException("Invalid put operation. $ is not a map");
}
}
@Override
public void renameKey(String oldKeyName, String newKeyName, Configuration configuration) {
Object target = parent;
if(targetInvalid(target)){
return;
}
renameInMap(target, oldKeyName, newKeyName, configuration);
}
}
private static class ArrayIndexPathRef extends PathRef {
private int index;
private ArrayIndexPathRef(Object parent, int index) {
super(parent);
this.index = index;
}
public void set(Object newVal, Configuration configuration){
configuration.jsonProvider().setArrayIndex(parent, index, newVal);
}
public void convert(MapFunction mapFunction, Configuration configuration){
Object currentValue = configuration.jsonProvider().getArrayIndex(parent, index);
configuration.jsonProvider().setArrayIndex(parent, index, mapFunction.map(currentValue, configuration));
}
public void delete(Configuration configuration){
configuration.jsonProvider().removeProperty(parent, index);
}
public void add(Object value, Configuration configuration){
Object target = configuration.jsonProvider().getArrayIndex(parent, index);
if(targetInvalid(target)){
return;
}
if(configuration.jsonProvider().isArray(target)){
configuration.jsonProvider().setProperty(target, null, value);
} else {
throw new InvalidModificationException("Can only add to an array");
}
}
public void put(String key, Object value, Configuration configuration){
Object target = configuration.jsonProvider().getArrayIndex(parent, index);
if(targetInvalid(target)){
return;
}
if(configuration.jsonProvider().isMap(target)){
configuration.jsonProvider().setProperty(target, key, value);
} else {
throw new InvalidModificationException("Can only add properties to a map");
}
}
@Override
public void renameKey(String oldKeyName, String newKeyName, Configuration configuration) {
Object target = configuration.jsonProvider().getArrayIndex(parent, index);
if(targetInvalid(target)){
return;
}
renameInMap(target, oldKeyName, newKeyName, configuration);
}
@Override
public Object getAccessor() {
return index;
}
@Override
public int compareTo(PathRef o) {
if(o instanceof ArrayIndexPathRef){
ArrayIndexPathRef pf = (ArrayIndexPathRef) o;
return Integer.compare(pf.index, this.index);
}
return super.compareTo(o);
}
}
private static class ObjectPropertyPathRef extends PathRef {
private String property;
private ObjectPropertyPathRef(Object parent, String property) {
super(parent);
this.property = property;
}
public void set(Object newVal, Configuration configuration){
configuration.jsonProvider().setProperty(parent, property, newVal);
}
@Override
public void convert(MapFunction mapFunction, Configuration configuration) {
Object currentValue = configuration.jsonProvider().getMapValue(parent, property);
configuration.jsonProvider().setProperty(parent, property, mapFunction.map(currentValue, configuration));
}
public void delete(Configuration configuration){
configuration.jsonProvider().removeProperty(parent, property);
}
public void add(Object value, Configuration configuration){
Object target = configuration.jsonProvider().getMapValue(parent, property);
if(targetInvalid(target)){
return;
}
if(configuration.jsonProvider().isArray(target)){
configuration.jsonProvider().setArrayIndex(target, configuration.jsonProvider().length(target), value);
} else {
throw new InvalidModificationException("Can only add to an array");
}
}
public void put(String key, Object value, Configuration configuration){
Object target = configuration.jsonProvider().getMapValue(parent, property);
if(targetInvalid(target)){
return;
}
if(configuration.jsonProvider().isMap(target)){
configuration.jsonProvider().setProperty(target, key, value);
} else {
throw new InvalidModificationException("Can only add properties to a map");
}
}
@Override
public void renameKey(String oldKeyName, String newKeyName, Configuration configuration) {
Object target = configuration.jsonProvider().getMapValue(parent, property);
if(targetInvalid(target)){
return;
}
renameInMap(target, oldKeyName, newKeyName, configuration);
}
@Override
public Object getAccessor() {
return property;
}
}
private static class ObjectMultiPropertyPathRef extends PathRef {
private Collection<String> properties;
private ObjectMultiPropertyPathRef(Object parent, Collection<String> properties) {
super(parent);
this.properties = properties;
}
public void set(Object newVal, Configuration configuration){
for (String property : properties) {
configuration.jsonProvider().setProperty(parent, property, newVal);
}
}
public void convert(MapFunction mapFunction, Configuration configuration) {
for (String property : properties) {
Object currentValue = configuration.jsonProvider().getMapValue(parent, property);
if (currentValue != JsonProvider.UNDEFINED) {
configuration.jsonProvider().setProperty(parent, property, mapFunction.map(currentValue, configuration));
}
}
}
public void delete(Configuration configuration){
for (String property : properties) {
configuration.jsonProvider().removeProperty(parent, property);
}
}
@Override
public void add(Object newVal, Configuration configuration) {
throw new InvalidModificationException("Add can not be performed to multiple properties");
}
@Override
public void put(String key, Object newVal, Configuration configuration) {
throw new InvalidModificationException("Put can not be performed to multiple properties");
}
@Override
public void renameKey(String oldKeyName, String newKeyName, Configuration configuration) {
throw new InvalidModificationException("Rename can not be performed to multiple properties");
}
@Override
public Object getAccessor() {
return Utils.join("&&", properties);
}
}
}

@ -0,0 +1,406 @@
/*
* 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;
import com.jayway.jsonpath.JsonPathException;
import java.io.Closeable;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Iterator;
public final class Utils {
// accept a collection of objects, since all objects have toString()
public static String join(String delimiter, String wrap, Iterable<?> objs) {
Iterator<?> iter = objs.iterator();
if (!iter.hasNext()) {
return "";
}
StringBuilder buffer = new StringBuilder();
buffer.append(wrap).append(iter.next()).append(wrap);
while (iter.hasNext()) {
buffer.append(delimiter).append(wrap).append(iter.next()).append(wrap);
}
return buffer.toString();
}
// accept a collection of objects, since all objects have toString()
public static String join(String delimiter, Iterable<?> objs) {
return join(delimiter, "", objs);
}
public static String concat(CharSequence... strings) {
if (strings.length == 0) {
return "";
}
if (strings.length == 1) {
return strings[0].toString();
}
int length = 0;
// -1 = no result, -2 = multiple results
int indexOfSingleNonEmptyString = -1;
for (int i = 0; i < strings.length; i++) {
CharSequence charSequence = strings[i];
int len = charSequence.length();
length += len;
if (indexOfSingleNonEmptyString != -2 && len > 0) {
if (indexOfSingleNonEmptyString == -1) {
indexOfSingleNonEmptyString = i;
} else {
indexOfSingleNonEmptyString = -2;
}
}
}
if (length == 0) {
return "";
}
if (indexOfSingleNonEmptyString > 0) {
return strings[indexOfSingleNonEmptyString].toString();
}
StringBuilder sb = new StringBuilder(length);
for (CharSequence charSequence : strings) {
sb.append(charSequence);
}
return sb.toString();
}
//---------------------------------------------------------
//
// IO
//
//---------------------------------------------------------
public static void closeQuietly(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (IOException ignore) {
}
}
public static String escape(String str, boolean escapeSingleQuote) {
if (str == null) {
return null;
}
int len = str.length();
StringWriter writer = new StringWriter(len * 2);
for (int i = 0; i < len; i++) {
char ch = str.charAt(i);
// handle unicode
if (ch > 0xfff) {
writer.write("\\u" + hex(ch));
} else if (ch > 0xff) {
writer.write("\\u0" + hex(ch));
} else if (ch > 0x7f) {
writer.write("\\u00" + hex(ch));
} else if (ch < 32) {
switch (ch) {
case '\b':
writer.write('\\');
writer.write('b');
break;
case '\n':
writer.write('\\');
writer.write('n');
break;
case '\t':
writer.write('\\');
writer.write('t');
break;
case '\f':
writer.write('\\');
writer.write('f');
break;
case '\r':
writer.write('\\');
writer.write('r');
break;
default :
if (ch > 0xf) {
writer.write("\\u00" + hex(ch));
} else {
writer.write("\\u000" + hex(ch));
}
break;
}
} else {
switch (ch) {
case '\'':
if (escapeSingleQuote) {
writer.write('\\');
}
writer.write('\'');
break;
case '"':
writer.write('\\');
writer.write('"');
break;
case '\\':
writer.write('\\');
writer.write('\\');
break;
case '/':
writer.write('\\');
writer.write('/');
break;
default :
writer.write(ch);
break;
}
}
}
return writer.toString();
}
public static String unescape(String str) {
if (str == null) {
return null;
}
int len = str.length();
StringWriter writer = new StringWriter(len);
StringBuilder unicode = new StringBuilder(4);
boolean hadSlash = false;
boolean inUnicode = false;
for (int i = 0; i < len; i++) {
char ch = str.charAt(i);
if (inUnicode) {
unicode.append(ch);
if (unicode.length() == 4) {
try {
int value = Integer.parseInt(unicode.toString(), 16);
writer.write((char) value);
unicode.setLength(0);
inUnicode = false;
hadSlash = false;
} catch (NumberFormatException nfe) {
throw new JsonPathException("Unable to parse unicode value: " + unicode, nfe);
}
}
continue;
}
if (hadSlash) {
hadSlash = false;
switch (ch) {
case '\\':
writer.write('\\');
break;
case '\'':
writer.write('\'');
break;
case '\"':
writer.write('"');
break;
case 'r':
writer.write('\r');
break;
case 'f':
writer.write('\f');
break;
case 't':
writer.write('\t');
break;
case 'n':
writer.write('\n');
break;
case 'b':
writer.write('\b');
break;
case 'u':
{
inUnicode = true;
break;
}
default :
writer.write(ch);
break;
}
continue;
} else if (ch == '\\') {
hadSlash = true;
continue;
}
writer.write(ch);
}
if (hadSlash) {
writer.write('\\');
}
return writer.toString();
}
/**
* Returns an upper case hexadecimal <code>String</code> for the given
* character.
*
* @param ch The character to map.
* @return An upper case hexadecimal <code>String</code>
*/
public static String hex(char ch) {
return Integer.toHexString(ch).toUpperCase();
}
/**
* <p>Checks if a CharSequence is empty ("") or null.</p>
* <p/>
* <pre>
* StringUtils.isEmpty(null) = true
* StringUtils.isEmpty("") = true
* StringUtils.isEmpty(" ") = false
* StringUtils.isEmpty("bob") = false
* StringUtils.isEmpty(" bob ") = false
* </pre>
* <p/>
* <p>NOTE: This method changed in Lang version 2.0.
* It no longer trims the CharSequence.
* That functionality is available in isBlank().</p>
*
* @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
//
//---------------------------------------------------------
/**
* <p>Validate that the specified argument is not {@code null};
* otherwise throwing an exception with the specified message.
* <p/>
* <pre>Validate.notNull(myObject, "The object must not be null");</pre>
*
* @param <T> 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> T notNull(T object, String message, Object... values) {
if (object == null) {
throw new IllegalArgumentException(String.format(message, values));
}
return object;
}
/**
* <p>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.</p>
* <p/>
* <pre>Validate.isTrue(i > 0.0, "The value must be greater than zero: %d", i);</pre>
* <p/>
* <p>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.</p>
*
* @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;
}
/**
* <p>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.
* <p/>
* <pre>Validate.notEmpty(myString, "The string must not be empty");</pre>
*
* @param <T> 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 extends CharSequence> 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() {
}
}

@ -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);
}

@ -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<RelationalOperator, Evaluator> evaluators = new HashMap<RelationalOperator, Evaluator>();
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;
}
}
}

@ -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);
}
}
}
}

@ -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<ExpressionNode> ops = new ArrayList<ExpressionNode>();
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<ExpressionNode> ops = new ArrayList<ExpressionNode>();
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 <null> 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 + ")]";
}
}
}
}

@ -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<ExpressionNode> chain = new ArrayList<ExpressionNode>();
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<ExpressionNode> 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<ExpressionNode> 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<ExpressionNode> 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);
}
}
}

@ -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);
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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);
}
}

@ -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<ValueNode> {
private List<ValueNode> nodes = new ArrayList<ValueNode>();
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<ValueNode> 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<ValueNode> 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;
}
}
}
}
}

@ -0,0 +1,9 @@
package com.jayway.jsonpath.internal.function;
/**
* Created by mgreenwood on 12/11/15.
*/
public enum ParamType {
JSON,
PATH
}

@ -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 <T>
* Type T returned as a List of T.
*
* @return
* List of T either empty or containing contents.
*/
public static <T> List<T> toList(final Class<T> type, final EvaluationContext ctx, final List<Parameter> parameters) {
List<T> 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);
}
}
}
}

@ -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<Parameter> parameters) {
return model;
}
}

@ -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<Parameter> parameters);
}

@ -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<String, Class> FUNCTIONS;
static {
// New functions should be added here and ensure the name is not overridden
Map<String, Class> map = new HashMap<String, Class>();
// 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);
}
}
}
}

@ -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<Parameter> 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;
}
}

@ -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();
}

@ -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());
}
}

@ -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();
}
}

@ -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<Parameter> 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");
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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);
}
}

@ -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;
}
}

@ -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<Parameter> 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();
}
}

@ -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<Parameter> 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;
}
}

@ -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<Integer> indexes;
private ArrayIndexOperation(List<Integer> indexes) {
this.indexes = Collections.unmodifiableList(indexes);
}
public List<Integer> 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<Integer> tempIndexes = new ArrayList<Integer>(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);
}
}
}

@ -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();
}
}

@ -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;
}
}

@ -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;
}
}
}

@ -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;
}
}

@ -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();
}
}

@ -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<PathRef> updateOperations;
private final HashMap<Path, Object> documentEvalCache = new HashMap<Path, Object>();
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<PathRef>();
}
public HashMap<Path, Object> 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<Option> options() {
return configuration.getOptions();
}
@Override
public Configuration configuration() {
return configuration;
}
@Override
public Object rootDocument() {
return rootDocument;
}
public Collection<PathRef> updateOperations(){
Collections.sort(updateOperations);
return Collections.unmodifiableCollection(updateOperations);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getValue() {
return getValue(true);
}
@SuppressWarnings("unchecked")
@Override
public <T> 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> T getPath() {
if(resultIndex == 0){
throw new PathNotFoundException("No results for path: " + path.toString());
}
return (T)pathResult;
}
@Override
public List<String> getPathList() {
List<String> res = new ArrayList<String>();
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;
}
}
}

@ -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<Parameter> functionParams;
public FunctionPathToken(String pathFragment, List<Parameter> 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<Parameter> parameters) {
this.functionParams = parameters;
}
}

@ -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<Predicate> filterStack;
private final CharacterIndex path;
private PathCompiler(String path, LinkedList<Predicate> filterStack){
this(new CharacterIndex(path), filterStack);
}
private PathCompiler(CharacterIndex path, LinkedList<Predicate> 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<Predicate> filterStack = new LinkedList<Predicate>(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<Parameter> 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 ','
*
* <pre>
* doc = {"numbers": [1,2,3,4,5,6,7,8,9,10]}
*
* $.sum({10}, $.numbers.avg())
* </pre>
*
* The above is a valid function call, we're first summing 10 + avg of 1...10 (5.5) so the total should be 15.5
*
* @return
* An ordered list of parameters that are to processed via the function. Typically functions either process
* an array of values and/or can consume parameters in addition to the values provided from the consumption of
* an array.
*/
private List<Parameter> parseFunctionParameters(String funcName) {
ParamType type = null;
// Parenthesis starts at 1 since we're marking the start of a function call, the close paren will denote the
// last parameter boundary
Integer groupParen = 1, groupBracket = 0, groupBrace = 0, groupQuote = 0;
Boolean endOfStream = false;
char priorChar = 0;
List<Parameter> parameters = new ArrayList<Parameter>();
StringBuilder parameter = new StringBuilder();
while (path.inBounds() && !endOfStream) {
char c = path.currentChar();
path.incrementPosition(1);
// we're at the start of the stream, and don't know what type of parameter we have
if (type == null) {
if (isWhitespace(c)) {
continue;
}
if (c == OPEN_BRACE || isDigit(c) || DOUBLE_QUOTE == c) {
type = ParamType.JSON;
}
else if (isPathContext(c)) {
type = ParamType.PATH; // read until we reach a terminating comma and we've reset grouping to zero
}
}
switch (c) {
case DOUBLE_QUOTE:
if (priorChar != '\\' && groupQuote > 0) {
if (groupQuote == 0) {
throw new InvalidPathException("Unexpected quote '\"' at character position: " + path.position());
}
groupQuote--;
}
else {
groupQuote++;
}
break;
case OPEN_PARENTHESIS:
groupParen++;
break;
case OPEN_BRACE:
groupBrace++;
break;
case OPEN_SQUARE_BRACKET:
groupBracket++;
break;
case CLOSE_BRACE:
if (0 == groupBrace) {
throw new InvalidPathException("Unexpected close brace '}' at character position: " + path.position());
}
groupBrace--;
break;
case CLOSE_SQUARE_BRACKET:
if (0 == groupBracket) {
throw new InvalidPathException("Unexpected close bracket ']' at character position: " + path.position());
}
groupBracket--;
break;
// In either the close paren case where we have zero paren groups left, capture the parameter, or where
// we've encountered a COMMA do the same
case CLOSE_PARENTHESIS:
groupParen--;
if (0 != groupParen) {
parameter.append(c);
}
case COMMA:
// In this state we've reach the end of a function parameter and we can pass along the parameter string
// to the parser
if ((0 == groupQuote && 0 == groupBrace && 0 == groupBracket
&& ((0 == groupParen && CLOSE_PARENTHESIS == c) || 1 == groupParen))) {
endOfStream = (0 == groupParen);
if (null != type) {
Parameter param = null;
switch (type) {
case JSON:
// parse the json and set the value
param = new Parameter(parameter.toString());
break;
case PATH:
LinkedList<Predicate> predicates = new LinkedList<Predicate>();
PathCompiler compiler = new PathCompiler(parameter.toString(), predicates);
param = new Parameter(compiler.compile());
break;
}
if (null != param) {
parameters.add(param);
}
parameter.delete(0, parameter.length());
type = null;
}
}
break;
}
if (type != null && !(c == COMMA && 0 == groupBrace && 0 == groupBracket && 1 == groupParen)) {
parameter.append(c);
}
priorChar = c;
}
if (0 != groupBrace || 0 != groupParen || 0 != groupBracket) {
throw new InvalidPathException("Arguments to function: '" + funcName + "' are not closed properly.");
}
return parameters;
}
private boolean isWhitespace(char c) {
return (c == SPACE || c == TAB || c == LF || c == CR);
}
//
// [?], [?,?, ..]
//
private boolean readPlaceholderToken(PathTokenAppender appender) {
if (!path.currentCharIs(OPEN_SQUARE_BRACKET)) {
return false;
}
int questionmarkIndex = path.indexOfNextSignificantChar(BEGIN_FILTER);
if (questionmarkIndex == -1) {
return false;
}
char nextSignificantChar = path.nextSignificantChar(questionmarkIndex);
if (nextSignificantChar != CLOSE_SQUARE_BRACKET && nextSignificantChar != COMMA) {
return false;
}
int expressionBeginIndex = path.position() + 1;
int expressionEndIndex = path.nextIndexOf(expressionBeginIndex, CLOSE_SQUARE_BRACKET);
if (expressionEndIndex == -1) {
return false;
}
String expression = path.subSequence(expressionBeginIndex, expressionEndIndex).toString();
String[] tokens = expression.split(",");
if (filterStack.size() < tokens.length) {
throw new InvalidPathException("Not enough predicates supplied for filter [" + expression + "] at position " + path.position());
}
Collection<Predicate> predicates = new ArrayList<Predicate>();
for (String token : tokens) {
token = token != null ? token.trim() : token;
if (!"?".equals(token == null ? "" : token)) {
throw new InvalidPathException("Expected '?' but found " + token);
}
predicates.add(filterStack.pop());
}
appender.appendPathToken(PathTokenFactory.createPredicatePathToken(predicates));
path.setPosition(expressionEndIndex + 1);
return path.currentIsTail() || readNextToken(appender);
}
//
// [?(...)]
//
private boolean readFilterToken(PathTokenAppender appender) {
if (!path.currentCharIs(OPEN_SQUARE_BRACKET) && !path.nextSignificantCharIs(BEGIN_FILTER)) {
return false;
}
int openStatementBracketIndex = path.position();
int questionMarkIndex = path.indexOfNextSignificantChar(BEGIN_FILTER);
if (questionMarkIndex == -1) {
return false;
}
int openBracketIndex = path.indexOfNextSignificantChar(questionMarkIndex, OPEN_PARENTHESIS);
if (openBracketIndex == -1) {
return false;
}
int closeBracketIndex = path.indexOfClosingBracket(openBracketIndex, true, true);
if (closeBracketIndex == -1) {
return false;
}
if (!path.nextSignificantCharIs(closeBracketIndex, CLOSE_SQUARE_BRACKET)) {
return false;
}
int closeStatementBracketIndex = path.indexOfNextSignificantChar(closeBracketIndex, CLOSE_SQUARE_BRACKET);
String criteria = path.subSequence(openStatementBracketIndex, closeStatementBracketIndex + 1).toString();
Predicate predicate = FilterCompiler.compile(criteria);
appender.appendPathToken(PathTokenFactory.createPredicatePathToken(predicate));
path.setPosition(closeStatementBracketIndex + 1);
return path.currentIsTail() || readNextToken(appender);
}
//
// [*]
// *
//
private boolean readWildCardToken(PathTokenAppender appender) {
boolean inBracket = path.currentCharIs(OPEN_SQUARE_BRACKET);
if (inBracket && !path.nextSignificantCharIs(WILDCARD)) {
return false;
}
if (!path.currentCharIs(WILDCARD) && path.isOutOfBounds(path.position() + 1)) {
return false;
}
if (inBracket) {
int wildCardIndex = path.indexOfNextSignificantChar(WILDCARD);
if (!path.nextSignificantCharIs(wildCardIndex, CLOSE_SQUARE_BRACKET)) {
int offset = wildCardIndex + 1;
throw new InvalidPathException("Expected wildcard token to end with ']' on position " + offset);
}
int bracketCloseIndex = path.indexOfNextSignificantChar(wildCardIndex, CLOSE_SQUARE_BRACKET);
path.setPosition(bracketCloseIndex + 1);
} else {
path.incrementPosition(1);
}
appender.appendPathToken(PathTokenFactory.createWildCardPathToken());
return path.currentIsTail() || readNextToken(appender);
}
//
// [1], [1,2, n], [1:], [1:2], [:2]
//
private boolean readArrayToken(PathTokenAppender appender) {
if (!path.currentCharIs(OPEN_SQUARE_BRACKET)) {
return false;
}
char nextSignificantChar = path.nextSignificantChar();
if (!isDigit(nextSignificantChar) && nextSignificantChar != MINUS && nextSignificantChar != SPLIT) {
return false;
}
int expressionBeginIndex = path.position() + 1;
int expressionEndIndex = path.nextIndexOf(expressionBeginIndex, CLOSE_SQUARE_BRACKET);
if (expressionEndIndex == -1) {
return false;
}
String expression = path.subSequence(expressionBeginIndex, expressionEndIndex).toString().trim();
if ("*".equals(expression)) {
return false;
}
//check valid chars
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (!isDigit(c) && c != COMMA && c != MINUS && c != SPLIT && c != SPACE) {
return false;
}
}
boolean isSliceOperation = expression.contains(":");
if (isSliceOperation) {
ArraySliceOperation arraySliceOperation = ArraySliceOperation.parse(expression);
appender.appendPathToken(PathTokenFactory.createSliceArrayPathToken(arraySliceOperation));
} else {
ArrayIndexOperation arrayIndexOperation = ArrayIndexOperation.parse(expression);
appender.appendPathToken(PathTokenFactory.createIndexArrayPathToken(arrayIndexOperation));
}
path.setPosition(expressionEndIndex + 1);
return path.currentIsTail() || readNextToken(appender);
}
//
// ['foo']
//
private boolean readBracketPropertyToken(PathTokenAppender appender) {
if (!path.currentCharIs(OPEN_SQUARE_BRACKET)) {
return false;
}
char potentialStringDelimiter = path.nextSignificantChar();
if (potentialStringDelimiter != SINGLE_QUOTE && potentialStringDelimiter != DOUBLE_QUOTE) {
return false;
}
List<String> properties = new ArrayList<String>();
int startPosition = path.position() + 1;
int readPosition = startPosition;
int endPosition = 0;
boolean inProperty = false;
boolean inEscape = false;
boolean lastSignificantWasComma = false;
while (path.inBounds(readPosition)) {
char c = path.charAt(readPosition);
if(inEscape){
inEscape = false;
} else if('\\' == c){
inEscape = true;
} else if (c == CLOSE_SQUARE_BRACKET && !inProperty) {
if (lastSignificantWasComma){
fail("Found empty property at index "+readPosition);
}
break;
} else if (c == potentialStringDelimiter) {
if (inProperty) {
char nextSignificantChar = path.nextSignificantChar(readPosition);
if (nextSignificantChar != CLOSE_SQUARE_BRACKET && nextSignificantChar != COMMA) {
fail("Property must be separated by comma or Property must be terminated close square bracket at index "+readPosition);
}
endPosition = readPosition;
String prop = path.subSequence(startPosition, endPosition).toString();
properties.add(Utils.unescape(prop));
inProperty = false;
} else {
startPosition = readPosition + 1;
inProperty = true;
lastSignificantWasComma = false;
}
} else if (c == COMMA){
if (lastSignificantWasComma){
fail("Found empty property at index "+readPosition);
}
lastSignificantWasComma = true;
}
readPosition++;
}
if (inProperty){
fail("Property has not been closed - missing closing " + potentialStringDelimiter);
}
int endBracketIndex = path.indexOfNextSignificantChar(endPosition, CLOSE_SQUARE_BRACKET) + 1;
path.setPosition(endBracketIndex);
appender.appendPathToken(PathTokenFactory.createPropertyPathToken(properties, potentialStringDelimiter));
return path.currentIsTail() || readNextToken(appender);
}
public static boolean fail(String message) {
throw new InvalidPathException(message);
}
}

@ -0,0 +1,221 @@
/*
* 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.Option;
import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.internal.PathRef;
import com.jayway.jsonpath.internal.Utils;
import com.jayway.jsonpath.internal.function.PathFunction;
import com.jayway.jsonpath.spi.json.JsonProvider;
import java.util.List;
public abstract class PathToken {
private PathToken prev;
private PathToken next;
private Boolean definite = null;
private Boolean upstreamDefinite = null;
PathToken appendTailToken(PathToken next) {
this.next = next;
this.next.prev = this;
return next;
}
void handleObjectProperty(String currentPath, Object model, EvaluationContextImpl ctx, List<String> properties) {
if(properties.size() == 1) {
String property = properties.get(0);
String evalPath = Utils.concat(currentPath, "['", property, "']");
Object propertyVal = readObjectProperty(property, model, ctx);
if(propertyVal == JsonProvider.UNDEFINED){
// Conditions below heavily depend on current token type (and its logic) and are not "universal",
// so this code is quite dangerous (I'd rather rewrite it & move to PropertyPathToken and implemented
// WildcardPathToken as a dynamic multi prop case of PropertyPathToken).
// Better safe than sorry.
assert this instanceof PropertyPathToken : "only PropertyPathToken is supported";
if(isLeaf()) {
if(ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)){
propertyVal = null;
} else {
if(ctx.options().contains(Option.SUPPRESS_EXCEPTIONS) ||
!ctx.options().contains(Option.REQUIRE_PROPERTIES)){
return;
} else {
throw new PathNotFoundException("No results for path: " + evalPath);
}
}
} else {
if (! (isUpstreamDefinite() && isTokenDefinite()) &&
!ctx.options().contains(Option.REQUIRE_PROPERTIES) ||
ctx.options().contains(Option.SUPPRESS_EXCEPTIONS)){
// If there is some indefiniteness in the path and properties are not required - we'll ignore
// absent property. And also in case of exception suppression - so that other path evaluation
// branches could be examined.
return;
} else {
throw new PathNotFoundException("Missing property in path " + evalPath);
}
}
}
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, property) : PathRef.NO_OP;
if (isLeaf()) {
ctx.addResult(evalPath, pathRef, propertyVal);
}
else {
next().evaluate(evalPath, pathRef, propertyVal, ctx);
}
} else {
String evalPath = currentPath + "[" + Utils.join(", ", "'", properties) + "]";
assert isLeaf() : "non-leaf multi props handled elsewhere";
Object merged = ctx.jsonProvider().createMap();
for (String property : properties) {
Object propertyVal;
if(hasProperty(property, model, ctx)) {
propertyVal = readObjectProperty(property, model, ctx);
if(propertyVal == JsonProvider.UNDEFINED){
if(ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)) {
propertyVal = null;
} else {
continue;
}
}
} else {
if(ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)){
propertyVal = null;
} else if (ctx.options().contains(Option.REQUIRE_PROPERTIES)) {
throw new PathNotFoundException("Missing property in path " + evalPath);
} else {
continue;
}
}
ctx.jsonProvider().setProperty(merged, property, propertyVal);
}
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, properties) : PathRef.NO_OP;
ctx.addResult(evalPath, pathRef, merged);
}
}
private static boolean hasProperty(String property, Object model, EvaluationContextImpl ctx) {
return ctx.jsonProvider().getPropertyKeys(model).contains(property);
}
private static Object readObjectProperty(String property, Object model, EvaluationContextImpl ctx) {
return ctx.jsonProvider().getMapValue(model, property);
}
protected void handleArrayIndex(int index, String currentPath, Object model, EvaluationContextImpl ctx) {
String evalPath = Utils.concat(currentPath, "[", String.valueOf(index), "]");
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, index) : PathRef.NO_OP;
int effectiveIndex = index < 0 ? ctx.jsonProvider().length(model) + index : index;
try {
Object evalHit = ctx.jsonProvider().getArrayIndex(model, effectiveIndex);
if (isLeaf()) {
ctx.addResult(evalPath, pathRef, evalHit);
} else {
next().evaluate(evalPath, pathRef, evalHit, ctx);
}
} catch (IndexOutOfBoundsException e) {
}
}
PathToken prev(){
return prev;
}
PathToken next() {
if (isLeaf()) {
throw new IllegalStateException("Current path token is a leaf");
}
return next;
}
boolean isLeaf() {
return next == null;
}
boolean isRoot() {
return prev == null;
}
boolean isUpstreamDefinite() {
if (upstreamDefinite == null) {
upstreamDefinite = isRoot() || prev.isTokenDefinite() && prev.isUpstreamDefinite();
}
return upstreamDefinite;
}
public int getTokenCount() {
int cnt = 1;
PathToken token = this;
while (!token.isLeaf()){
token = token.next();
cnt++;
}
return cnt;
}
public boolean isPathDefinite() {
if(definite != null){
return definite.booleanValue();
}
boolean isDefinite = isTokenDefinite();
if (isDefinite && !isLeaf()) {
isDefinite = next.isPathDefinite();
}
definite = isDefinite;
return isDefinite;
}
@Override
public String toString() {
if (isLeaf()) {
return getPathFragment();
} else {
return getPathFragment() + next().toString();
}
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
public void invoke(PathFunction pathFunction, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
ctx.addResult(currentPath, parent, pathFunction.invoke(currentPath, parent, model, ctx, null));
}
public abstract void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx);
public abstract boolean isTokenDefinite();
protected abstract String getPathFragment();
public void setNext(final PathToken next) {
this.next = next;
}
}

@ -0,0 +1,5 @@
package com.jayway.jsonpath.internal.path;
public interface PathTokenAppender {
PathTokenAppender appendPathToken(PathToken next);
}

@ -0,0 +1,52 @@
package com.jayway.jsonpath.internal.path;
import com.jayway.jsonpath.Predicate;
import com.jayway.jsonpath.internal.function.Parameter;
import java.util.Collection;
import java.util.List;
import static java.util.Collections.singletonList;
public class PathTokenFactory {
public static RootPathToken createRootPathToken(char token) {
return new RootPathToken(token);
}
public static PathToken createSinglePropertyPathToken(String property, char stringDelimiter) {
return new PropertyPathToken(singletonList(property), stringDelimiter);
}
public static PathToken createPropertyPathToken(List<String> properties, char stringDelimiter) {
return new PropertyPathToken(properties, stringDelimiter);
}
public static PathToken createSliceArrayPathToken(final ArraySliceOperation arraySliceOperation) {
return new ArraySliceToken(arraySliceOperation);
}
public static PathToken createIndexArrayPathToken(final ArrayIndexOperation arrayIndexOperation) {
return new ArrayIndexToken(arrayIndexOperation);
}
public static PathToken createWildCardPathToken() {
return new WildcardPathToken();
}
public static PathToken crateScanToken() {
return new ScanPathToken();
}
public static PathToken createPredicatePathToken(Collection<Predicate> predicates) {
return new PredicatePathToken(predicates);
}
public static PathToken createPredicatePathToken(Predicate predicate) {
return new PredicatePathToken(predicate);
}
public static PathToken createFunctionPathToken(String function, List<Parameter> parameters) {
return new FunctionPathToken(function, parameters);
}
}

@ -0,0 +1,77 @@
/*
* 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.Predicate;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.spi.mapper.MappingException;
import java.util.HashMap;
public class PredicateContextImpl implements Predicate.PredicateContext {
private final Object contextDocument;
private final Object rootDocument;
private final Configuration configuration;
private final HashMap<Path, Object> documentPathCache;
public PredicateContextImpl(Object contextDocument, Object rootDocument, Configuration configuration, HashMap<Path, Object> documentPathCache) {
this.contextDocument = contextDocument;
this.rootDocument = rootDocument;
this.configuration = configuration;
this.documentPathCache = documentPathCache;
}
public Object evaluate(Path path){
Object result;
if(path.isRootPath()){
if(documentPathCache.containsKey(path)){
result = documentPathCache.get(path);
} else {
result = path.evaluate(rootDocument, rootDocument, configuration).getValue();
documentPathCache.put(path, result);
}
} else {
result = path.evaluate(contextDocument, rootDocument, configuration).getValue();
}
return result;
}
public HashMap<Path, Object> documentPathCache() {
return documentPathCache;
}
@Override
public Object item() {
return contextDocument;
}
@Override
public <T> T item(Class<T> clazz) throws MappingException {
return configuration().mappingProvider().map(contextDocument, clazz, configuration);
}
@Override
public Object root() {
return rootDocument;
}
@Override
public Configuration configuration() {
return configuration;
}
}

@ -0,0 +1,108 @@
/*
* 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.InvalidPathException;
import com.jayway.jsonpath.Predicate;
import com.jayway.jsonpath.internal.PathRef;
import java.util.Collection;
import java.util.Collections;
import static java.lang.String.format;
import static java.util.Arrays.asList;
/**
*
*/
public class PredicatePathToken extends PathToken {
private final Collection<Predicate> predicates;
PredicatePathToken(Predicate filter) {
this.predicates = Collections.singletonList(filter);
}
PredicatePathToken(Collection<Predicate> predicates) {
this.predicates = predicates;
}
@Override
public void evaluate(String currentPath, PathRef ref, Object model, EvaluationContextImpl ctx) {
if (ctx.jsonProvider().isMap(model)) {
if (accept(model, ctx.rootDocument(), ctx.configuration(), ctx)) {
PathRef op = ctx.forUpdate() ? ref : PathRef.NO_OP;
if (isLeaf()) {
ctx.addResult(currentPath, op, model);
} else {
next().evaluate(currentPath, op, model, ctx);
}
}
} else if (ctx.jsonProvider().isArray(model)){
int idx = 0;
Iterable<?> objects = ctx.jsonProvider().toIterable(model);
for (Object idxModel : objects) {
if (accept(idxModel, ctx.rootDocument(), ctx.configuration(), ctx)) {
handleArrayIndex(idx, currentPath, model, ctx);
}
idx++;
}
} else {
if (isUpstreamDefinite()) {
throw new InvalidPathException(format("Filter: %s can not be applied to primitives. Current context is: %s", toString(), model));
}
}
}
public boolean accept(final Object obj, final Object root, final Configuration configuration, EvaluationContextImpl evaluationContext) {
Predicate.PredicateContext ctx = new PredicateContextImpl(obj, root, configuration, evaluationContext.documentEvalCache());
for (Predicate predicate : predicates) {
try {
if (!predicate.apply(ctx)) {
return false;
}
} catch (InvalidPathException e) {
return false;
}
}
return true;
}
@Override
public String getPathFragment() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for(int i = 0; i < predicates.size(); i++){
if(i != 0){
sb.append(",");
}
sb.append("?");
}
sb.append("]");
return sb.toString();
}
@Override
public boolean isTokenDefinite() {
return false;
}
}

@ -0,0 +1,105 @@
/*
* 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 com.jayway.jsonpath.internal.PathRef;
import com.jayway.jsonpath.internal.Utils;
import java.util.ArrayList;
import java.util.List;
import static com.jayway.jsonpath.internal.Utils.onlyOneIsTrueNonThrow;
/**
*
*/
class PropertyPathToken extends PathToken {
private final List<String> properties;
private final String stringDelimiter;
public PropertyPathToken(List<String> properties, char stringDelimiter) {
if (properties.isEmpty()) {
throw new InvalidPathException("Empty properties");
}
this.properties = properties;
this.stringDelimiter = Character.toString(stringDelimiter);
}
public List<String> getProperties() {
return properties;
}
public boolean singlePropertyCase() {
return properties.size() == 1;
}
public boolean multiPropertyMergeCase() {
return isLeaf() && properties.size() > 1;
}
public boolean multiPropertyIterationCase() {
// Semantics of this case is the same as semantics of ArrayPathToken with INDEX_SEQUENCE operation.
return ! isLeaf() && properties.size() > 1;
}
@Override
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
// Can't assert it in ctor because isLeaf() could be changed later on.
assert onlyOneIsTrueNonThrow(singlePropertyCase(), multiPropertyMergeCase(), multiPropertyIterationCase());
if (!ctx.jsonProvider().isMap(model)) {
if (! isUpstreamDefinite()) {
return;
} else {
String m = model == null ? "null" : model.getClass().getName();
throw new PathNotFoundException(String.format(
"Expected to find an object with property %s in path %s but found '%s'. " +
"This is not a json object according to the JsonProvider: '%s'.",
getPathFragment(), currentPath, m, ctx.configuration().jsonProvider().getClass().getName()));
}
}
if (singlePropertyCase() || multiPropertyMergeCase()) {
handleObjectProperty(currentPath, model, ctx, properties);
return;
}
assert multiPropertyIterationCase();
final List<String> currentlyHandledProperty = new ArrayList<String>(1);
currentlyHandledProperty.add(null);
for (final String property : properties) {
currentlyHandledProperty.set(0, property);
handleObjectProperty(currentPath, model, ctx, currentlyHandledProperty);
}
}
@Override
public boolean isTokenDefinite() {
// in case of leaf multiprops will be merged, so it's kinda definite
return singlePropertyCase() || multiPropertyMergeCase();
}
@Override
public String getPathFragment() {
return new StringBuilder()
.append("[")
.append(Utils.join(",", stringDelimiter, properties))
.append("]").toString();
}
}

@ -0,0 +1,83 @@
/*
* 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 RootPathToken extends PathToken {
private PathToken tail;
private int tokenCount;
private final String rootToken;
RootPathToken(char rootToken) {
this.rootToken = Character.toString(rootToken);
this.tail = this;
this.tokenCount = 1;
}
@Override
public int getTokenCount() {
return tokenCount;
}
public RootPathToken append(PathToken next) {
this.tail = tail.appendTailToken(next);
this.tokenCount++;
return this;
}
public PathTokenAppender getPathTokenAppender(){
return new PathTokenAppender(){
@Override
public PathTokenAppender appendPathToken(PathToken next) {
append(next);
return this;
}
};
}
@Override
public void evaluate(String currentPath, PathRef pathRef, Object model, EvaluationContextImpl ctx) {
if (isLeaf()) {
PathRef op = ctx.forUpdate() ? pathRef : PathRef.NO_OP;
ctx.addResult(rootToken, op, model);
} else {
next().evaluate(rootToken, pathRef, model, ctx);
}
}
@Override
public String getPathFragment() {
return rootToken;
}
@Override
public boolean isTokenDefinite() {
return true;
}
public boolean isFunctionPath() {
return (tail instanceof FunctionPathToken);
}
public void setTail(PathToken token) {
this.tail = token;
}
}

@ -0,0 +1,206 @@
/*
* 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.Option;
import com.jayway.jsonpath.internal.PathRef;
import com.jayway.jsonpath.spi.json.JsonProvider;
import java.util.Collection;
/**
*
*/
public class ScanPathToken extends PathToken {
ScanPathToken() {
}
@Override
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
PathToken pt = next();
walk(pt, currentPath, parent, model, ctx, createScanPredicate(pt, ctx));
}
public static void walk(PathToken pt, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx, Predicate predicate) {
if (ctx.jsonProvider().isMap(model)) {
walkObject(pt, currentPath, parent, model, ctx, predicate);
} else if (ctx.jsonProvider().isArray(model)) {
walkArray(pt, currentPath, parent, model, ctx, predicate);
}
}
public static void walkArray(PathToken pt, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx, Predicate predicate) {
if (predicate.matches(model)) {
if (pt.isLeaf()) {
pt.evaluate(currentPath, parent, model, ctx);
} else {
PathToken next = pt.next();
Iterable<?> models = ctx.jsonProvider().toIterable(model);
int idx = 0;
for (Object evalModel : models) {
String evalPath = currentPath + "[" + idx + "]";
next.evaluate(evalPath, parent, evalModel, ctx);
idx++;
}
}
}
Iterable<?> models = ctx.jsonProvider().toIterable(model);
int idx = 0;
for (Object evalModel : models) {
String evalPath = currentPath + "[" + idx + "]";
walk(pt, evalPath, PathRef.create(model, idx), evalModel, ctx, predicate);
idx++;
}
}
public static void walkObject(PathToken pt, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx, Predicate predicate) {
if (predicate.matches(model)) {
pt.evaluate(currentPath, parent, model, ctx);
}
Collection<String> properties = ctx.jsonProvider().getPropertyKeys(model);
for (String property : properties) {
String evalPath = currentPath + "['" + property + "']";
Object propertyModel = ctx.jsonProvider().getMapValue(model, property);
if (propertyModel != JsonProvider.UNDEFINED) {
walk(pt, evalPath, PathRef.create(model, property), propertyModel, ctx, predicate);
}
}
}
private static Predicate createScanPredicate(final PathToken target, final EvaluationContextImpl ctx) {
if (target instanceof PropertyPathToken) {
return new PropertyPathTokenPredicate(target, ctx);
} else if (target instanceof ArrayPathToken) {
return new ArrayPathTokenPredicate(ctx);
} else if (target instanceof WildcardPathToken) {
return new WildcardPathTokenPredicate();
} else if (target instanceof PredicatePathToken) {
return new FilterPathTokenPredicate(target, ctx);
} else {
return FALSE_PREDICATE;
}
}
@Override
public boolean isTokenDefinite() {
return false;
}
@Override
public String getPathFragment() {
return "..";
}
private interface Predicate {
boolean matches(Object model);
}
private static final Predicate FALSE_PREDICATE = new Predicate() {
@Override
public boolean matches(Object model) {
return false;
}
};
private static final class FilterPathTokenPredicate implements Predicate {
private final EvaluationContextImpl ctx;
private PredicatePathToken predicatePathToken;
private FilterPathTokenPredicate(PathToken target, EvaluationContextImpl ctx) {
this.ctx = ctx;
predicatePathToken = (PredicatePathToken) target;
}
@Override
public boolean matches(Object model) {
return predicatePathToken.accept(model, ctx.rootDocument(), ctx.configuration(), ctx);
}
}
private static final class WildcardPathTokenPredicate implements Predicate {
@Override
public boolean matches(Object model) {
return true;
}
}
private static final class ArrayPathTokenPredicate implements Predicate {
private final EvaluationContextImpl ctx;
private ArrayPathTokenPredicate(EvaluationContextImpl ctx) {
this.ctx = ctx;
}
@Override
public boolean matches(Object model) {
return ctx.jsonProvider().isArray(model);
}
}
private static final class PropertyPathTokenPredicate implements Predicate {
private final EvaluationContextImpl ctx;
private PropertyPathToken propertyPathToken;
private PropertyPathTokenPredicate(PathToken target, EvaluationContextImpl ctx) {
this.ctx = ctx;
propertyPathToken = (PropertyPathToken) target;
}
@Override
public boolean matches(Object model) {
if (! ctx.jsonProvider().isMap(model)) {
return false;
}
//
// The commented code below makes it really hard understand, use and predict the result
// of deep scanning operations. It might be correct but was decided to be
// left out until the behavior of REQUIRE_PROPERTIES is more strictly defined
// in a deep scanning scenario. For details read conversation in commit
// https://github.com/jayway/JsonPath/commit/1a72fc078deb16995e323442bfb681bd715ce45a#commitcomment-14616092
//
// if (ctx.options().contains(Option.REQUIRE_PROPERTIES)) {
// // Have to require properties defined in path when an indefinite path is evaluated,
// // so have to go there and search for it.
// return true;
// }
if (! propertyPathToken.isTokenDefinite()) {
// It's responsibility of PropertyPathToken code to handle indefinite scenario of properties,
// so we'll allow it to do its job.
return true;
}
if (propertyPathToken.isLeaf() && ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)) {
// In case of DEFAULT_PATH_LEAF_TO_NULL missing properties is not a problem.
return true;
}
Collection<String> keys = ctx.jsonProvider().getPropertyKeys(model);
return keys.containsAll(propertyPathToken.getProperties());
}
}
}

@ -0,0 +1,62 @@
/*
* 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 java.util.Collections;
import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.internal.PathRef;
import static java.util.Arrays.asList;
/**
*
*/
public class WildcardPathToken extends PathToken {
WildcardPathToken() {
}
@Override
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
if (ctx.jsonProvider().isMap(model)) {
for (String property : ctx.jsonProvider().getPropertyKeys(model)) {
handleObjectProperty(currentPath, model, ctx, Collections.singletonList(property));
}
} else if (ctx.jsonProvider().isArray(model)) {
for (int idx = 0; idx < ctx.jsonProvider().length(model); idx++) {
try {
handleArrayIndex(idx, currentPath, model, ctx);
} catch (PathNotFoundException p){
if(ctx.options().contains(Option.REQUIRE_PROPERTIES)){
throw p;
}
}
}
}
}
@Override
public boolean isTokenDefinite() {
return false;
}
@Override
public String getPathFragment() {
return "[*]";
}
}

@ -0,0 +1,22 @@
package com.jayway.jsonpath.spi.cache;
import com.jayway.jsonpath.InvalidJsonException;
import com.jayway.jsonpath.JsonPath;
public interface Cache {
/**
* Get the Cached JsonPath
* @param key cache key to lookup the JsonPath
* @return JsonPath
*/
JsonPath get(String key);
/**
* Add JsonPath to the cache
* @param key cache key to store the JsonPath
* @param value JsonPath to be cached
* @throws InvalidJsonException
*/
void put(String key, JsonPath value);
}

@ -0,0 +1,37 @@
package com.jayway.jsonpath.spi.cache;
import com.jayway.jsonpath.JsonPathException;
import static com.jayway.jsonpath.internal.Utils.notNull;
public class CacheProvider {
private static Cache cache;
public static void setCache(Cache cache){
notNull(cache, "Cache may not be null");
synchronized (CacheProvider.class){
if(CacheProvider.cache != null){
throw new JsonPathException("Cache provider must be configured before cache is accessed.");
} else {
CacheProvider.cache = cache;
}
}
}
public static Cache getCache() {
if(CacheProvider.cache == null){
synchronized (CacheProvider.class){
if(CacheProvider.cache == null){
CacheProvider.cache = getDefaultCache();
}
}
}
return CacheProvider.cache;
}
private static Cache getDefaultCache(){
return new LRUCache(400);
//return new NOOPCache();
}
}

@ -0,0 +1,112 @@
/*
* 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.spi.cache;
import com.jayway.jsonpath.JsonPath;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
public class LRUCache implements Cache {
private final ReentrantLock lock = new ReentrantLock();
private final Map<String, JsonPath> map = new ConcurrentHashMap<String, JsonPath>();
private final Deque<String> queue = new LinkedList<String>();
private final int limit;
public LRUCache(int limit) {
this.limit = limit;
}
public void put(String key, JsonPath value) {
JsonPath oldValue = map.put(key, value);
if (oldValue != null) {
removeThenAddKey(key);
} else {
addKey(key);
}
if (map.size() > limit) {
map.remove(removeLast());
}
}
public JsonPath get(String key) {
JsonPath jsonPath = map.get(key);
if(jsonPath != null){
removeThenAddKey(key);
}
return jsonPath;
}
private void addKey(String key) {
lock.lock();
try {
queue.addFirst(key);
} finally {
lock.unlock();
}
}
private String removeLast() {
lock.lock();
try {
final String removedKey = queue.removeLast();
return removedKey;
} finally {
lock.unlock();
}
}
private void removeThenAddKey(String key) {
lock.lock();
try {
queue.removeFirstOccurrence(key);
queue.addFirst(key);
} finally {
lock.unlock();
}
}
private void removeFirstOccurrence(String key) {
lock.lock();
try {
queue.removeFirstOccurrence(key);
} finally {
lock.unlock();
}
}
public JsonPath getSilent(String key) {
return map.get(key);
}
public void remove(String key) {
removeFirstOccurrence(key);
map.remove(key);
}
public int size() {
return map.size();
}
public String toString() {
return map.toString();
}
}

@ -0,0 +1,15 @@
package com.jayway.jsonpath.spi.cache;
import com.jayway.jsonpath.JsonPath;
public class NOOPCache implements Cache {
@Override
public JsonPath get(String key) {
return null;
}
@Override
public void put(String key, JsonPath value) {
}
}

@ -0,0 +1,178 @@
/*
* 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.spi.json;
import com.jayway.jsonpath.JsonPathException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public abstract class AbstractJsonProvider implements JsonProvider {
/**
* checks if object is an array
*
* @param obj object to check
* @return true if obj is an array
*/
public boolean isArray(Object obj) {
return (obj instanceof List);
}
/**
* Extracts a value from an array
*
* @param obj an array
* @param idx index
* @return the entry at the given index
*/
public Object getArrayIndex(Object obj, int idx) {
return ((List) obj).get(idx);
}
@Deprecated
public final Object getArrayIndex(Object obj, int idx, boolean unwrap){
return getArrayIndex(obj, idx);
}
public void setArrayIndex(Object array, int index, Object newValue) {
if (!isArray(array)) {
throw new UnsupportedOperationException();
} else {
List l = (List) array;
if (index == l.size()){
l.add(newValue);
}else {
l.set(index, newValue);
}
}
}
/**
* Extracts a value from an map
*
* @param obj a map
* @param key property key
* @return the map entry or {@link com.jayway.jsonpath.spi.json.JsonProvider#UNDEFINED} for missing properties
*/
public Object getMapValue(Object obj, String key){
Map m = (Map) obj;
if(!m.containsKey(key)){
return JsonProvider.UNDEFINED;
} else {
return m.get(key);
}
}
/**
* Sets a value in an object
*
* @param obj an object
* @param key a String key
* @param value the value to set
*/
@SuppressWarnings("unchecked")
public void setProperty(Object obj, Object key, Object value) {
if (isMap(obj))
((Map) obj).put(key.toString(), value);
else {
throw new JsonPathException("setProperty operation cannot be used with " + obj!=null?obj.getClass().getName():"null");
}
}
/**
* Removes a value in an object or array
*
* @param obj an array or an object
* @param key a String key or a numerical index to remove
*/
@SuppressWarnings("unchecked")
public void removeProperty(Object obj, Object key) {
if (isMap(obj))
((Map) obj).remove(key.toString());
else {
List list = (List) obj;
int index = key instanceof Integer ? (Integer) key : Integer.parseInt(key.toString());
list.remove(index);
}
}
/**
* checks if object is a map (i.e. no array)
*
* @param obj object to check
* @return true if the object is a map
*/
public boolean isMap(Object obj) {
return (obj instanceof Map);
}
/**
* Returns the keys from the given object
*
* @param obj an object
* @return the keys for an object
*/
@SuppressWarnings("unchecked")
public Collection<String> getPropertyKeys(Object obj) {
if (isArray(obj)) {
throw new UnsupportedOperationException();
} else {
return ((Map) obj).keySet();
}
}
/**
* Get the length of an array or object
*
* @param obj an array or an object
* @return the number of entries in the array or object
*/
public int length(Object obj) {
if (isArray(obj)) {
return ((List) obj).size();
} else if (isMap(obj)){
return getPropertyKeys(obj).size();
} else if(obj instanceof String){
return ((String)obj).length();
}
throw new JsonPathException("length operation cannot be applied to " + obj!=null?obj.getClass().getName():"null");
}
/**
* Converts given array to an {@link Iterable}
*
* @param obj an array
* @return an Iterable that iterates over the entries of an array
*/
@SuppressWarnings("unchecked")
public Iterable<?> toIterable(Object obj) {
if (isArray(obj))
return ((Iterable) obj);
else
throw new JsonPathException("Cannot iterate over " + obj!=null?obj.getClass().getName():"null");
}
@Override
public Object unwrap(Object obj) {
return obj;
}
}

@ -0,0 +1,280 @@
/*
* 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.spi.json;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.jayway.jsonpath.InvalidJsonException;
import com.jayway.jsonpath.JsonPathException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class GsonJsonProvider extends AbstractJsonProvider {
private static final JsonParser PARSER = new JsonParser();
private final Gson gson;
/**
* Initializes the {@code GsonJsonProvider} using the default {@link Gson} object.
*/
public GsonJsonProvider() {
this(new Gson());
}
/**
* Initializes the {@code GsonJsonProvider} using a customized {@link Gson} object.
*
* @param gson the customized Gson object.
*/
public GsonJsonProvider(final Gson gson) {
this.gson = gson;
}
public Object unwrap(final Object o) {
if (o == null) {
return null;
}
if (!(o instanceof JsonElement)) {
return o;
}
JsonElement e = (JsonElement) o;
if (e.isJsonNull()) {
return null;
} else if (e.isJsonPrimitive()) {
JsonPrimitive p = e.getAsJsonPrimitive();
if (p.isString()) {
return p.getAsString();
} else if (p.isBoolean()) {
return p.getAsBoolean();
} else if (p.isNumber()) {
return unwrapNumber(p.getAsNumber());
}
}
return o;
}
private static boolean isPrimitiveNumber(final Number n) {
return n instanceof Integer ||
n instanceof Double ||
n instanceof Long ||
n instanceof BigDecimal ||
n instanceof BigInteger;
}
private static Number unwrapNumber(final Number n) {
Number unwrapped;
if (!isPrimitiveNumber(n)) {
BigDecimal bigDecimal = new BigDecimal(n.toString());
if (bigDecimal.scale() <= 0) {
if (bigDecimal.compareTo(new BigDecimal(Integer.MAX_VALUE)) <= 0) {
unwrapped = bigDecimal.intValue();
} else if (bigDecimal.compareTo(new BigDecimal(Long.MAX_VALUE)) <= 0){
unwrapped = bigDecimal.longValue();
} else {
unwrapped = bigDecimal;
}
} else {
final double doubleValue = bigDecimal.doubleValue();
if (BigDecimal.valueOf(doubleValue).compareTo(bigDecimal) != 0) {
unwrapped = bigDecimal;
} else {
unwrapped = doubleValue;
}
}
} else {
unwrapped = n;
}
return unwrapped;
}
@Override
public Object parse(final String json) throws InvalidJsonException {
return PARSER.parse(json);
}
@Override
public Object parse(final InputStream jsonStream, final String charset) throws InvalidJsonException {
try {
return PARSER.parse(new InputStreamReader(jsonStream, charset));
} catch (UnsupportedEncodingException e) {
throw new JsonPathException(e);
}
}
@Override
public String toJson(final Object obj) {
return gson.toJson(obj);
}
@Override
public Object createArray() {
return new JsonArray();
}
@Override
public Object createMap() {
return new JsonObject();
}
@Override
public boolean isArray(final Object obj) {
return (obj instanceof JsonArray || obj instanceof List);
}
@Override
public Object getArrayIndex(final Object obj, final int idx) {
return toJsonArray(obj).get(idx);
}
@Override
public void setArrayIndex(final Object array, final int index, final Object newValue) {
if (!isArray(array)) {
throw new UnsupportedOperationException();
} else {
JsonArray arr = toJsonArray(array);
if (index == arr.size()) {
arr.add(createJsonElement(newValue));
} else {
arr.set(index, createJsonElement(newValue));
}
}
}
@Override
public Object getMapValue(final Object obj, final String key) {
JsonObject jsonObject = toJsonObject(obj);
Object o = jsonObject.get(key);
if (!jsonObject.has(key)) {
return UNDEFINED;
} else {
return unwrap(o);
}
}
@Override
public void setProperty(final Object obj, final Object key, final Object value) {
if (isMap(obj)) {
toJsonObject(obj).add(key.toString(), createJsonElement(value));
} else {
JsonArray array = toJsonArray(obj);
int index;
if (key != null) {
index = key instanceof Integer ? (Integer) key : Integer.parseInt(key.toString());
} else {
index = array.size();
}
if (index == array.size()) {
array.add(createJsonElement(value));
} else {
array.set(index, createJsonElement(value));
}
}
}
@SuppressWarnings("unchecked")
public void removeProperty(final Object obj, final Object key) {
if (isMap(obj)) {
toJsonObject(obj).remove(key.toString());
} else {
JsonArray array = toJsonArray(obj);
int index = key instanceof Integer ? (Integer) key : Integer.parseInt(key.toString());
array.remove(index);
}
}
@Override
public boolean isMap(final Object obj) {
// return (obj instanceof JsonObject || obj instanceof Map);
return (obj instanceof JsonObject);
}
@Override
public Collection<String> getPropertyKeys(final Object obj) {
List<String> keys = new ArrayList<String>();
for (Map.Entry<String, JsonElement> entry : toJsonObject(obj).entrySet()) {
keys.add(entry.getKey());
}
return keys;
}
@Override
public int length(final Object obj) {
if (isArray(obj)) {
return toJsonArray(obj).size();
} else if (isMap(obj)) {
return toJsonObject(obj).entrySet().size();
} else {
if (obj instanceof JsonElement) {
JsonElement element = toJsonElement(obj);
if (element.isJsonPrimitive()) {
return element.toString().length();
}
}
}
throw new JsonPathException("length operation can not applied to " + obj != null ? obj.getClass().getName()
: "null");
}
@Override
public Iterable<?> toIterable(final Object obj) {
JsonArray arr = toJsonArray(obj);
List<Object> values = new ArrayList<Object>(arr.size());
for (Object o : arr) {
values.add(unwrap(o));
}
return values;
}
private JsonElement createJsonElement(final Object o) {
return gson.toJsonTree(o);
}
private JsonArray toJsonArray(final Object o) {
return (JsonArray) o;
}
private JsonObject toJsonObject(final Object o) {
return (JsonObject) o;
}
private JsonElement toJsonElement(final Object o) {
return (JsonElement) o;
}
}

@ -0,0 +1,166 @@
/*
* 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.spi.json;
import com.jayway.jsonpath.InvalidJsonException;
import java.io.InputStream;
import java.util.Collection;
public interface JsonProvider {
static final Object UNDEFINED = new Object();
/**
* Parse the given json string
* @param json json string to parse
* @return Object representation of json
* @throws InvalidJsonException
*/
Object parse(String json) throws InvalidJsonException;
/**
* Parse the given json string
* @param jsonStream input stream to parse
* @param charset charset to use
* @return Object representation of json
* @throws InvalidJsonException
*/
Object parse(InputStream jsonStream, String charset) throws InvalidJsonException;
/**
* Convert given json object to a json string
* @param obj object to transform
* @return json representation of object
*/
String toJson(Object obj);
/**
* Creates a provider specific json array
* @return new array
*/
Object createArray();
/**
* Creates a provider specific json object
* @return new object
*/
Object createMap();
/**
* checks if object is an array
*
* @param obj object to check
* @return true if obj is an array
*/
boolean isArray(Object obj);
/**
* Get the length of an json array, json object or a json string
*
* @param obj an array or object or a string
* @return the number of entries in the array or object
*/
int length(Object obj);
/**
* Converts given array to an {@link Iterable}
*
* @param obj an array
* @return an Iterable that iterates over the entries of an array
*/
Iterable<?> toIterable(Object obj);
/**
* Returns the keys from the given object
*
* @param obj an object
* @return the keys for an object
*/
Collection<String> getPropertyKeys(Object obj);
/**
* Extracts a value from an array anw unwraps provider specific data type
*
* @param obj an array
* @param idx index
* @return the entry at the given index
*/
Object getArrayIndex(Object obj, int idx);
/**
* Extracts a value from an array
*
* @param obj an array
* @param idx index
* @param unwrap should provider specific data type be unwrapped
* @return the entry at the given index
*/
@Deprecated
Object getArrayIndex(Object obj, int idx, boolean unwrap);
/**
* Sets a value in an array. If the array is too small, the provider is supposed to enlarge it.
*
* @param array an array
* @param idx index
* @param newValue the new value
*/
void setArrayIndex(Object array, int idx, Object newValue);
/**
* Extracts a value from an map
*
* @param obj a map
* @param key property key
* @return the map entry or {@link com.jayway.jsonpath.spi.json.JsonProvider#UNDEFINED} for missing properties
*/
Object getMapValue(Object obj, String key);
/**
* Sets a value in an object
*
* @param obj an object
* @param key a String key
* @param value the value to set
*/
void setProperty(Object obj, Object key, Object value);
/**
* Removes a value in an object or array
*
* @param obj an array or an object
* @param key a String key or a numerical index to remove
*/
void removeProperty(Object obj, Object key);
/**
* checks if object is a map (i.e. no array)
*
* @param obj object to check
* @return true if the object is a map
*/
boolean isMap(Object obj);
/**
* Extracts a value from a wrapper object. For JSON providers that to not wrap
* values, this will usually be the object itself.
*
* @param obj a value holder object
* @return the unwrapped value.
*/
Object unwrap(Object obj);
}

@ -0,0 +1,94 @@
/*
* 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.spi.json;
import com.jayway.jsonpath.InvalidJsonException;
import com.jayway.jsonpath.JsonPathException;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
import net.minidev.json.JSONStyle;
import net.minidev.json.JSONValue;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;
import net.minidev.json.writer.JsonReaderI;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;
public class JsonSmartJsonProvider extends AbstractJsonProvider {
private final int parseMode;
private final JsonReaderI<?> mapper;
public JsonSmartJsonProvider() {
this(JSONParser.MODE_PERMISSIVE, JSONValue.defaultReader.DEFAULT_ORDERED);
}
public JsonSmartJsonProvider(int parseMode){
this(parseMode, JSONValue.defaultReader.DEFAULT_ORDERED);
}
public JsonSmartJsonProvider(int parseMode, JsonReaderI<?> mapper){
this.parseMode = parseMode;
this.mapper = mapper;
}
public Object createArray() {
return mapper.createArray();
}
public Object createMap() {
return mapper.createObject();
}
public Object parse(String json) {
try {
return createParser().parse(json, mapper);
} catch (ParseException e) {
throw new InvalidJsonException(e);
}
}
@Override
public Object parse(InputStream jsonStream, String charset) throws InvalidJsonException {
try {
return createParser().parse(new InputStreamReader(jsonStream, charset), mapper);
} catch (ParseException e) {
throw new InvalidJsonException(e);
} catch (UnsupportedEncodingException e) {
throw new JsonPathException(e);
}
}
@Override
public String toJson(Object obj) {
if (obj instanceof Map) {
return JSONObject.toJSONString((Map<String, ?>) obj, JSONStyle.LT_COMPRESS);
} else if (obj instanceof List) {
return JSONArray.toJSONString((List<?>) obj, JSONStyle.LT_COMPRESS);
} else {
throw new UnsupportedOperationException(obj.getClass().getName() + " can not be converted to JSON");
}
}
private JSONParser createParser() {
return new JSONParser(parseMode);
}
}

@ -0,0 +1,82 @@
/*
* 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.spi.mapper;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPathException;
import com.jayway.jsonpath.TypeRef;
import java.util.concurrent.Callable;
public class GsonMappingProvider implements MappingProvider {
private final Callable<Gson> factory;
public GsonMappingProvider(final Gson gson) {
this(new Callable<Gson>() {
@Override
public Gson call() {
return gson;
}
});
}
public GsonMappingProvider(Callable<Gson> factory) {
this.factory = factory;
}
public GsonMappingProvider() {
super();
try {
Class.forName("com.google.gson.Gson");
this.factory = new Callable<Gson>() {
@Override
public Gson call() {
return new Gson();
}
};
} catch (ClassNotFoundException e) {
throw new JsonPathException("Gson not found on path", e);
}
}
@Override
public <T> T map(Object source, Class<T> targetType, Configuration configuration) {
if(source == null){
return null;
}
try {
return factory.call().getAdapter(targetType).fromJsonTree((JsonElement) source);
} catch (Exception e){
throw new MappingException(e);
}
}
@Override
public <T> T map(Object source, TypeRef<T> targetType, Configuration configuration) {
if(source == null){
return null;
}
try {
return (T) factory.call().getAdapter(TypeToken.get(targetType.getType())).fromJsonTree((JsonElement) source);
} catch (Exception e){
throw new MappingException(e);
}
}
}

@ -0,0 +1,263 @@
/*
* 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.spi.mapper;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.TypeRef;
import net.minidev.json.JSONValue;
import net.minidev.json.writer.JsonReader;
import net.minidev.json.writer.JsonReaderI;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.concurrent.Callable;
public class JsonSmartMappingProvider implements MappingProvider {
private static JsonReader DEFAULT = new JsonReader();
static {
DEFAULT.registerReader(Long.class, new LongReader());
DEFAULT.registerReader(long.class, new LongReader());
DEFAULT.registerReader(Integer.class, new IntegerReader());
DEFAULT.registerReader(int.class, new IntegerReader());
DEFAULT.registerReader(Double.class, new DoubleReader());
DEFAULT.registerReader(double.class, new DoubleReader());
DEFAULT.registerReader(Float.class, new FloatReader());
DEFAULT.registerReader(float.class, new FloatReader());
DEFAULT.registerReader(BigDecimal.class, new BigDecimalReader());
DEFAULT.registerReader(String.class, new StringReader());
DEFAULT.registerReader(Date.class, new DateReader());
DEFAULT.registerReader(BigInteger.class, new BigIntegerReader());
DEFAULT.registerReader(boolean.class, new BooleanReader());
}
private final Callable<JsonReader> factory;
public JsonSmartMappingProvider(final JsonReader jsonReader) {
this(new Callable<JsonReader>() {
@Override
public JsonReader call() {
return jsonReader;
}
});
}
public JsonSmartMappingProvider(Callable<JsonReader> factory) {
this.factory = factory;
}
public JsonSmartMappingProvider() {
this(DEFAULT);
}
@Override
public <T> T map(Object source, Class<T> targetType, Configuration configuration) {
if(source == null){
return null;
}
if (targetType.isAssignableFrom(source.getClass())) {
return (T) source;
}
try {
if(!configuration.jsonProvider().isMap(source) && !configuration.jsonProvider().isArray(source)){
return factory.call().getMapper(targetType).convert(source);
}
String s = configuration.jsonProvider().toJson(source);
return (T) JSONValue.parse(s, targetType);
} catch (Exception e) {
throw new MappingException(e);
}
}
@Override
public <T> T map(Object source, TypeRef<T> targetType, Configuration configuration) {
throw new UnsupportedOperationException("Json-smart provider does not support TypeRef! Use a Jackson or Gson based provider");
}
private static class StringReader extends JsonReaderI<String> {
public StringReader() {
super(null);
}
public String convert(Object src) {
if(src == null){
return null;
}
return src.toString();
}
}
private static class IntegerReader extends JsonReaderI<Integer> {
public IntegerReader() {
super(null);
}
public Integer convert(Object src) {
if(src == null){
return null;
}
if(Integer.class.isAssignableFrom(src.getClass())){
return (Integer) src;
} else if (Long.class.isAssignableFrom(src.getClass())) {
return ((Long) src).intValue();
} else if (Double.class.isAssignableFrom(src.getClass())) {
return ((Double) src).intValue();
} else if (BigDecimal.class.isAssignableFrom(src.getClass())) {
return ((BigDecimal) src).intValue();
} else if (Float.class.isAssignableFrom(src.getClass())) {
return ((Float) src).intValue();
} else if (String.class.isAssignableFrom(src.getClass())) {
return Integer.valueOf(src.toString());
}
throw new MappingException("can not map a " + src.getClass() + " to " + Integer.class.getName());
}
}
private static class LongReader extends JsonReaderI<Long> {
public LongReader() {
super(null);
}
public Long convert(Object src) {
if(src == null){
return null;
}
if(Long.class.isAssignableFrom(src.getClass())){
return (Long) src;
} else if (Integer.class.isAssignableFrom(src.getClass())) {
return ((Integer) src).longValue();
} else if (Double.class.isAssignableFrom(src.getClass())) {
return ((Double) src).longValue();
} else if (BigDecimal.class.isAssignableFrom(src.getClass())) {
return ((BigDecimal) src).longValue();
} else if (Float.class.isAssignableFrom(src.getClass())) {
return ((Float) src).longValue();
} else if (String.class.isAssignableFrom(src.getClass())) {
return Long.valueOf(src.toString());
}
throw new MappingException("can not map a " + src.getClass() + " to " + Long.class.getName());
}
}
private static class DoubleReader extends JsonReaderI<Double> {
public DoubleReader() {
super(null);
}
public Double convert(Object src) {
if(src == null){
return null;
}
if(Double.class.isAssignableFrom(src.getClass())){
return (Double) src;
} else if (Integer.class.isAssignableFrom(src.getClass())) {
return ((Integer) src).doubleValue();
} else if (Long.class.isAssignableFrom(src.getClass())) {
return ((Long) src).doubleValue();
} else if (BigDecimal.class.isAssignableFrom(src.getClass())) {
return ((BigDecimal) src).doubleValue();
} else if (Float.class.isAssignableFrom(src.getClass())) {
return ((Float) src).doubleValue();
} else if (String.class.isAssignableFrom(src.getClass())) {
return Double.valueOf(src.toString());
}
throw new MappingException("can not map a " + src.getClass() + " to " + Double.class.getName());
}
}
private static class FloatReader extends JsonReaderI<Float> {
public FloatReader() {
super(null);
}
public Float convert(Object src) {
if(src == null){
return null;
}
if(Float.class.isAssignableFrom(src.getClass())){
return (Float) src;
} else if (Integer.class.isAssignableFrom(src.getClass())) {
return ((Integer) src).floatValue();
} else if (Long.class.isAssignableFrom(src.getClass())) {
return ((Long) src).floatValue();
} else if (BigDecimal.class.isAssignableFrom(src.getClass())) {
return ((BigDecimal) src).floatValue();
} else if (Double.class.isAssignableFrom(src.getClass())) {
return ((Double) src).floatValue();
} else if (String.class.isAssignableFrom(src.getClass())) {
return Float.valueOf(src.toString());
}
throw new MappingException("can not map a " + src.getClass() + " to " + Float.class.getName());
}
}
private static class BigDecimalReader extends JsonReaderI<BigDecimal> {
public BigDecimalReader() {
super(null);
}
public BigDecimal convert(Object src) {
if(src == null){
return null;
}
return new BigDecimal(src.toString());
}
}
private static class BigIntegerReader extends JsonReaderI<BigInteger> {
public BigIntegerReader() {
super(null);
}
public BigInteger convert(Object src) {
if(src == null){
return null;
}
return new BigInteger(src.toString());
}
}
private static class DateReader extends JsonReaderI<Date> {
public DateReader() {
super(null);
}
public Date convert(Object src) {
if(src == null){
return null;
}
if(Date.class.isAssignableFrom(src.getClass())){
return (Date) src;
} else if(Long.class.isAssignableFrom(src.getClass())){
return new Date((Long) src);
} else if(String.class.isAssignableFrom(src.getClass())){
try {
return DateFormat.getInstance().parse(src.toString());
} catch (ParseException e) {
throw new MappingException(e);
}
}
throw new MappingException("can not map a " + src.getClass() + " to " + Date.class.getName());
}
}
private static class BooleanReader extends JsonReaderI<Boolean> {
public BooleanReader() {
super(null);
}
public Boolean convert(Object src) {
if(src == null){
return null;
}
if (Boolean.class.isAssignableFrom(src.getClass())) {
return (Boolean) src;
}
throw new MappingException("can not map a " + src.getClass() + " to " + Boolean.class.getName());
}
}
}

@ -0,0 +1,28 @@
/*
* 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.spi.mapper;
import com.jayway.jsonpath.JsonPathException;
public class MappingException extends JsonPathException {
public MappingException(Throwable cause) {
super(cause);
}
public MappingException(String message) {
super(message);
}
}

@ -0,0 +1,45 @@
/*
* 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.spi.mapper;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.TypeRef;
/**
* Maps object between different Types
*/
public interface MappingProvider {
/**
*
* @param source object to map
* @param targetType the type the source object should be mapped to
* @param configuration current configuration
* @param <T> the mapped result type
* @return return the mapped object
*/
<T> T map(Object source, Class<T> targetType, Configuration configuration);
/**
*
* @param source object to map
* @param targetType the type the source object should be mapped to
* @param configuration current configuration
* @param <T> the mapped result type
* @return return the mapped object
*/
<T> T map(Object source, TypeRef<T> targetType, Configuration configuration);
}
Loading…
Cancel
Save