Browse Source

Filter support with Builder

pull/7/merge
Kalle Stenflo 13 years ago
parent
commit
828dfb927b
  1. 434
      json-path/src/main/java/com/jayway/jsonpath/Criteria.java
  2. 52
      json-path/src/main/java/com/jayway/jsonpath/Filter.java
  3. 24
      json-path/src/main/java/com/jayway/jsonpath/InvalidCriteriaException.java
  4. 162
      json-path/src/test/java/com/jayway/jsonpath/FilterTest.java

434
json-path/src/main/java/com/jayway/jsonpath/Criteria.java

@ -0,0 +1,434 @@
package com.jayway.jsonpath;
import javax.lang.model.type.NullType;
import java.util.*;
import java.util.regex.Pattern;
import static java.util.Arrays.asList;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 3/5/12
* Time: 12:08 PM
*/
public class Criteria {
/**
* Custom "not-null" object as we have to be able to work with {@literal null} values as well.
*/
private static final Object NOT_SET = new Object();
private String key;
private List<Criteria> criteriaChain;
private LinkedHashMap<String, Object> criteria = new LinkedHashMap<String, Object>();
private Object isValue = NOT_SET;
public Criteria(String key) {
this.criteriaChain = new ArrayList<Criteria>();
this.criteriaChain.add(this);
this.key = key;
}
protected Criteria(List<Criteria> criteriaChain, String key) {
this.criteriaChain = criteriaChain;
this.criteriaChain.add(this);
this.key = key;
}
public String getKey() {
return this.key;
}
public boolean apply(Map<String, Object> map) {
if (this.criteriaChain.size() == 1) {
return criteriaChain.get(0).singleObjectApply(map);
} else {
for (Criteria c : this.criteriaChain) {
if (!c.singleObjectApply(map)) {
return false;
}
}
return true;
}
}
protected boolean singleObjectApply(Map<String, Object> map) {
boolean not = false;
for (String key : this.criteria.keySet()) {
Object expectedVal = null;
Object actualVal = map.get(this.key);
if (not) {
expectedVal = this.criteria.get(key);
not = false;
} else {
if ("$not".equals(key)) {
not = true;
} else {
expectedVal = this.criteria.get(key);
if ("$gt".equals(key)) {
Number expectedNumber = (Number) expectedVal;
Number actualNumber = (Number) actualVal;
return (actualNumber.doubleValue() > expectedNumber.doubleValue());
} else if ("$gte".equals(key)) {
Number expectedNumber = (Number) expectedVal;
Number actualNumber = (Number) actualVal;
return (actualNumber.doubleValue() >= expectedNumber.doubleValue());
} else if ("$lt".equals(key)) {
Number expectedNumber = (Number) expectedVal;
Number actualNumber = (Number) actualVal;
return (actualNumber.doubleValue() < expectedNumber.doubleValue());
} else if ("$lte".equals(key)) {
Number expectedNumber = (Number) expectedVal;
Number actualNumber = (Number) actualVal;
return (actualNumber.doubleValue() <= expectedNumber.doubleValue());
} else if ("$ne".equals(key)) {
return !expectedVal.equals(actualVal);
} else if ("$in".equals(key)) {
Collection exp = (Collection) expectedVal;
return exp.contains(actualVal);
} else if ("$nin".equals(key)) {
Collection exp = (Collection) expectedVal;
return !exp.contains(actualVal);
} else if ("$all".equals(key)) {
Collection exp = (Collection) expectedVal;
Collection act = (Collection) actualVal;
return act.containsAll(exp);
} else if ("$size".equals(key)) {
int exp = (Integer) expectedVal;
List act = (List) actualVal;
return (act.size() == exp);
} else if ("$exists".equals(key)) {
boolean exp = (Boolean) expectedVal;
boolean act = map.containsKey(this.key);
return act == exp;
} else if ("$type".equals(key)) {
Class<?> exp = (Class<?>) expectedVal;
Class<?> act = map.containsKey(this.key) ? map.get(this.key).getClass() : NullType.class;
return act.equals(exp);
} else if ("$regex".equals(key)) {
Pattern exp = (Pattern) expectedVal;
String act = (String) actualVal;
return exp.matcher(act).matches();
} else if ("$or".equals(key)) {
System.out.println("auch");
}
}
}
}
if (isValue != NOT_SET) {
return isValue.equals(map.get(key));
} else {
}
return true;
}
/**
* Static factory method to create a Criteria using the provided key
*
* @param key
* @return
*/
public static Criteria where(String key) {
return new Criteria(key);
}
/**
* Static factory method to create a Criteria using the provided key
*
* @return
*/
public Criteria and(String key) {
return new Criteria(this.criteriaChain, key);
}
/**
* Creates a criterion using equality
*
* @param o
* @return
*/
public Criteria is(Object o) {
if (isValue != NOT_SET) {
throw new InvalidCriteriaException(
"Multiple 'is' values declared. You need to use 'and' with multiple criteria");
}
if (this.criteria.size() > 0 && "$not".equals(this.criteria.keySet().toArray()[this.criteria.size() - 1])) {
throw new InvalidCriteriaException("Invalid query: 'not' can't be used with 'is' - use 'ne' instead.");
}
this.isValue = o;
return this;
}
/**
* Creates a criterion using the $ne operator
*
* @param o
* @return
*/
public Criteria ne(Object o) {
criteria.put("$ne", o);
return this;
}
/**
* Creates a criterion using the $lt operator
*
* @param o
* @return
*/
public Criteria lt(Object o) {
criteria.put("$lt", o);
return this;
}
/**
* Creates a criterion using the $lte operator
*
* @param o
* @return
*/
public Criteria lte(Object o) {
criteria.put("$lte", o);
return this;
}
/**
* Creates a criterion using the $gt operator
*
* @param o
* @return
*/
public Criteria gt(Object o) {
criteria.put("$gt", o);
return this;
}
/**
* Creates a criterion using the $gte operator
*
* @param o
* @return
*/
public Criteria gte(Object o) {
criteria.put("$gte", o);
return this;
}
/**
* Creates a criterion using the $in operator
*
* @param o the values to match against
* @return
*/
public Criteria in(Object... o) {
if (o.length > 1 && o[1] instanceof Collection) {
throw new InvalidCriteriaException("You can only pass in one argument of type "
+ o[1].getClass().getName());
}
return in(Arrays.asList(o));
}
/**
* Creates a criterion using the $in operator
*
* @param c the collection containing the values to match against
* @return
*/
public Criteria in(Collection<?> c) {
criteria.put("$in", c);
return this;
}
/**
* Creates a criterion using the $nin operator
*
* @param o
* @return
*/
public Criteria nin(Object... o) {
return nin(Arrays.asList(o));
}
public Criteria nin(Collection<?> o) {
criteria.put("$nin", o);
return this;
}
/**
* Creates a criterion using the $all operator
*
* @param o
* @return
*/
public Criteria all(Object... o) {
return all(Arrays.asList(o));
}
public Criteria all(Collection<?> o) {
criteria.put("$all", o);
return this;
}
/**
* Creates a criterion using the $size operator
*
* @param s
* @return
*/
public Criteria size(int s) {
criteria.put("$size", s);
return this;
}
/**
* Creates a criterion using the $exists operator
*
* @param b
* @return
*/
public Criteria exists(boolean b) {
criteria.put("$exists", b);
return this;
}
/**
* Creates a criterion using the $type operator
*
* @param t
* @return
*/
public Criteria type(Class<?> t) {
criteria.put("$type", t);
return this;
}
/**
* Creates a criterion using the $not meta operator which affects the clause directly following
*
* @return
*/
public Criteria not() {
criteria.put("$not", null);
return this;
}
/**
* Creates a criterion using a $regex and $options
*
* @param pattern
* @return
*/
public Criteria regex(Pattern pattern) {
criteria.put("$regex", pattern);
return this;
}
/**
* Creates a criterion using the $mod operator
*
* @param value
* @param remainder
* @return
*/
/*
public Criteria mod(Number value, Number remainder) {
List<Object> l = new ArrayList<Object>();
l.add(value);
l.add(remainder);
criteria.put("$mod", l);
return this;
}
*/
/**
* Creates an 'or' criteria using the $or operator for all of the provided criteria
*
* @param criteria
*/
public Criteria orOperator(Criteria... criteria) {
criteriaChain.add(new Criteria("$or").is(asList(criteria)));
return this;
}
/**
* Creates a 'nor' criteria using the $nor operator for all of the provided criteria
*
* @param criteria
*/
/*
public Criteria norOperator(Criteria... criteria) {
criteriaChain.add(new Criteria("$nor").is(asList(criteria)));
return this;
}*/
/**
* Creates an 'and' criteria using the $and operator for all of the provided criteria
*
* @param criteria
*/
public Criteria andOperator(Criteria... criteria) {
criteriaChain.add(new Criteria("$and").is(asList(criteria)));
return this;
}
}

52
json-path/src/main/java/com/jayway/jsonpath/Filter.java

@ -0,0 +1,52 @@
package com.jayway.jsonpath;
import java.util.*;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 3/5/12
* Time: 12:05 PM
*/
public class Filter {
private HashMap<String, Criteria> criteria = new LinkedHashMap<String, Criteria>();
public Filter(Criteria criteria) {
addCriteria(criteria);
}
public static Filter filter(Criteria criteria) {
return new Filter(criteria);
}
public Filter addCriteria(Criteria criteria) {
Criteria existing = this.criteria.get(criteria.getKey());
String key = criteria.getKey();
if (existing == null) {
this.criteria.put(key, criteria);
} else {
existing.andOperator(criteria);
}
return this;
}
protected List<Criteria> getCriteria() {
return new ArrayList<Criteria>(this.criteria.values());
}
public boolean apply(Map<String, Object> map) {
for (Criteria criterion : getCriteria()) {
if(!criterion.apply(map)){
return false;
}
}
return true;
}
}

24
json-path/src/main/java/com/jayway/jsonpath/InvalidCriteriaException.java

@ -0,0 +1,24 @@
package com.jayway.jsonpath;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 3/5/12
* Time: 12:20 PM
*/
public class InvalidCriteriaException extends RuntimeException{
public InvalidCriteriaException() {
}
public InvalidCriteriaException(String message) {
super(message);
}
public InvalidCriteriaException(String message, Throwable cause) {
super(message, cause);
}
public InvalidCriteriaException(Throwable cause) {
super(cause);
}
}

162
json-path/src/test/java/com/jayway/jsonpath/FilterTest.java

@ -0,0 +1,162 @@
package com.jayway.jsonpath;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import static com.jayway.jsonpath.Criteria.where;
import static com.jayway.jsonpath.Filter.filter;
import static java.util.Arrays.asList;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 3/5/12
* Time: 12:27 PM
*/
public class FilterTest {
public final static String DOCUMENT =
"{ \"store\": {\n" +
" \"book\": [ \n" +
" { \"category\": \"reference\",\n" +
" \"author\": \"Nigel Rees\",\n" +
" \"title\": \"Sayings of the Century\",\n" +
" \"price\": 8.95\n" +
" },\n" +
" { \"category\": \"fiction\",\n" +
" \"author\": \"Evelyn Waugh\",\n" +
" \"title\": \"Sword of Honour\",\n" +
" \"price\": 12.99\n" +
" },\n" +
" { \"category\": \"fiction\",\n" +
" \"author\": \"Herman Melville\",\n" +
" \"title\": \"Moby Dick\",\n" +
" \"isbn\": \"0-553-21311-3\",\n" +
" \"price\": 8.99\n" +
" },\n" +
" { \"category\": \"fiction\",\n" +
" \"author\": \"J. R. R. Tolkien\",\n" +
" \"title\": \"The Lord of the Rings\",\n" +
" \"isbn\": \"0-395-19395-8\",\n" +
" \"price\": 22.99\n" +
" }\n" +
" ],\n" +
" \"bicycle\": {\n" +
" \"color\": \"red\",\n" +
" \"price\": 19.95,\n" +
" \"foo:bar\": \"fooBar\",\n" +
" \"dot.notation\": \"new\"\n" +
" }\n" +
" }\n" +
"}";
@Test
public void a_single_is_filter_can_be_built_and_applied() throws Exception {
Map<String, Object> check = JsonPath.read(DOCUMENT, "store.book[0]");
Filter filter = Filter.filter(Criteria.where("category").is("reference"));
assertTrue(filter.apply(check));
}
@Test
public void multiple_filters_can_be_built_and_applied() throws Exception {
Map<String, Object> check = JsonPath.read(DOCUMENT, "store.book[0]");
Filter filter = Filter.filter(Criteria
.where("category").is("reference")
.and("author").is("Nigel Rees")
.and("price").gt(8)
.and("price").gte(8)
.and("price").lt(10)
.and("price").lte(10)
.and("title").ne("is not")
.and("title").in("is not", "Sayings of the Century")
.and("title").nin("is not this", "is not that")
);
assertTrue(filter.apply(check));
}
@Test
public void all_filters_evaluates() throws Exception {
Map<String, Object> check = new HashMap<String, Object>();
check.put("items", asList(1, 2, 3));
assertTrue(filter(where("items").all(1, 2, 3)).apply(check));
assertFalse(filter(where("items").all(1, 2, 3, 4)).apply(check));
}
@Test
public void size_filters_evaluates() throws Exception {
Map<String, Object> check = new HashMap<String, Object>();
check.put("items", asList(1, 2, 3));
assertTrue(filter(where("items").size(3)).apply(check));
assertFalse(filter(where("items").size(2)).apply(check));
}
@Test
public void exists_filters_evaluates() throws Exception {
Map<String, Object> check = new HashMap<String, Object>();
check.put("foo", "foo");
assertTrue(filter(where("foo").exists(true)).apply(check));
assertFalse(filter(where("foo").exists(false)).apply(check));
assertTrue(filter(where("bar").exists(false)).apply(check));
assertFalse(filter(where("bar").exists(true)).apply(check));
}
@Test
public void type_filters_evaluates() throws Exception {
Map<String, Object> check = new HashMap<String, Object>();
check.put("string", "foo");
check.put("int", 1);
check.put("long", 1L);
check.put("double", 1.12D);
assertTrue(filter(where("string").type(String.class)).apply(check));
assertFalse(filter(where("string").type(Number.class)).apply(check));
assertTrue(filter(where("int").type(Integer.class)).apply(check));
assertFalse(filter(where("int").type(Long.class)).apply(check));
assertTrue(filter(where("long").type(Long.class)).apply(check));
assertFalse(filter(where("long").type(Integer.class)).apply(check));
assertTrue(filter(where("double").type(Double.class)).apply(check));
assertFalse(filter(where("double").type(Integer.class)).apply(check));
}
@Test
public void pattern_filters_evaluates() throws Exception {
Map<String, Object> check = new HashMap<String, Object>();
check.put("string", "kalle");
assertTrue(filter(where("string").regex(Pattern.compile(".alle"))).apply(check));
assertFalse(filter(where("string").regex(Pattern.compile("KALLE"))).apply(check));
assertTrue(filter(where("string").regex(Pattern.compile("KALLE", Pattern.CASE_INSENSITIVE))).apply(check));
}
/*
@Test
public void or_filters_evaluates() throws Exception {
Map<String, Object> check = new HashMap<String, Object>();
check.put("string", "kalle");
assertTrue(filter(where("string").is("x").orOperator(where("string").is("kalle"))).apply(check));
}*/
}
Loading…
Cancel
Save