You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
511 lines
15 KiB
511 lines
15 KiB
/* |
|
* 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 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; |
|
|
|
|
|
/** |
|
* |
|
*/ |
|
@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 ValueNode.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 ValueNode.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 ValueNode.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 ValueNode.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 ValueNode.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 ValueNode.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 ? ValueNode.TRUE : ValueNode.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 ValueNode.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."); |
|
} |
|
} |
|
|
|
}
|
|
|