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