parent
36eb40c813
commit
3538f958ab
@ -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><</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><=</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>></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>>=</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<Object> books = path.read(json); |
||||
* </code> |
||||
* </p> |
||||
* Or: |
||||
* <p/> |
||||
* <code> |
||||
* List<Object> 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…
Reference in new issue