com.ovea.tajin.json.Criteria Maven / Gradle / Ivy
The newest version!
/**
* Copyright (C) 2011 Ovea
*
* 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.ovea.tajin.json;
import java.util.*;
import java.util.regex.Pattern;
import static java.util.Arrays.asList;
import static org.apache.commons.lang.Validate.notEmpty;
import static org.apache.commons.lang.Validate.notNull;
/**
* @author Kalle Stenflo
*/
public class Criteria {
private enum CriteriaType {
GT,
GTE,
LT,
LTE,
NE,
IN,
NIN,
ALL,
SIZE,
EXISTS,
TYPE,
REGEX,
OR
}
/**
* 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 final String key;
private final List criteriaChain;
private final LinkedHashMap criteria = new LinkedHashMap<>();
private Object isValue = NOT_SET;
private Criteria(String key) {
notEmpty(key, "key can not be null or empty");
this.criteriaChain = new ArrayList<>();
this.criteriaChain.add(this);
this.key = key;
}
private Criteria(List criteriaChain, String key) {
notEmpty(key, "key can not be null or empty");
this.criteriaChain = criteriaChain;
this.criteriaChain.add(this);
this.key = key;
}
public String getKey() {
return this.key;
}
/**
* Checks if this criteria matches the given map
*
* @param map map to check
* @return true if criteria is a match
*/
public boolean matches(JSONObject 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;
}
}
boolean singleObjectApply(JSONObject map) {
for (CriteriaType key : this.criteria.keySet()) {
JSONType actualVal = map.opt(this.key);
Object expectedVal = this.criteria.get(key);
if (CriteriaType.GT.equals(key)) {
if (expectedVal == null || actualVal == null || actualVal.isNull()) {
return false;
}
Number expectedNumber = (Number) expectedVal;
Number actualNumber = actualVal.asDouble();
return (actualNumber.doubleValue() > expectedNumber.doubleValue());
} else if (CriteriaType.GTE.equals(key)) {
if (expectedVal == null || actualVal == null ||actualVal.isNull()) {
return false;
}
Number expectedNumber = (Number) expectedVal;
Number actualNumber = actualVal.asDouble();
return (actualNumber.doubleValue() >= expectedNumber.doubleValue());
} else if (CriteriaType.LT.equals(key)) {
if (expectedVal == null || actualVal == null ||actualVal.isNull()) {
return false;
}
Number expectedNumber = (Number) expectedVal;
Number actualNumber = actualVal.asDouble();
return (actualNumber.doubleValue() < expectedNumber.doubleValue());
} else if (CriteriaType.LTE.equals(key)) {
if (expectedVal == null || actualVal == null ||actualVal.isNull()) {
return false;
}
Number expectedNumber = (Number) expectedVal;
Number actualNumber = actualVal.asDouble();
return (actualNumber.doubleValue() <= expectedNumber.doubleValue());
} else if (CriteriaType.NE.equals(key)) {
JSONType t = JSON.valueOf(expectedVal);
if(actualVal == null) actualVal = JSON.NULL;
return !(t.isNull() && actualVal.isNull()) && (t.isNull() || !t.equals(actualVal));
} else if (CriteriaType.IN.equals(key)) {
Iterable exp = (Iterable) expectedVal;
for (Object o : exp) {
if (actualVal.equals(JSON.valueOf(o))) {
return true;
}
}
return false;
} else if (CriteriaType.NIN.equals(key)) {
Iterable exp = (Iterable) expectedVal;
for (Object o : exp) {
if (actualVal.equals(JSON.valueOf(o))) {
return false;
}
}
return true;
} else if (CriteriaType.ALL.equals(key)) {
Collection exp = (Collection) expectedVal;
List acts = actualVal.asArray().toList();
for (Object o : exp) {
if(!acts.contains(JSON.valueOf(o))) {
return false;
}
}
return true;
} else if (CriteriaType.SIZE.equals(key)) {
int exp = (Integer) expectedVal;
return (actualVal.asArray().size() == exp);
} else if (CriteriaType.EXISTS.equals(key)) {
boolean exp = (Boolean) expectedVal;
boolean act = map.has(this.key);
return act == exp;
} else if (CriteriaType.TYPE.equals(key)) {
Class> exp = (Class>) expectedVal;
Class> act = null;
if (map.has(this.key)) {
JSONType actVal = map.get(this.key);
if (!actVal.isNull()) {
act = actVal.asValue().getClass();
}
}
return act != null && act.equals(exp);
} else if (CriteriaType.REGEX.equals(key)) {
Pattern exp = (Pattern) expectedVal;
String act = actualVal.asString();
return act != null && exp.matcher(act).matches();
} else {
throw new UnsupportedOperationException("Criteria type not supported: " + key.name());
}
}
if (isValue != NOT_SET) {
if (isValue instanceof Iterable) {
Iterable cs = (Iterable) isValue;
for (Criteria crit : cs) {
for (Criteria c : crit.criteriaChain) {
if (!c.singleObjectApply(map)) {
return false;
}
}
}
return true;
} else {
if (isValue == null) {
return (map.get(key) == null) || map.get(key).equals(JSON.NULL);
} else {
return JSON.valueOf(isValue).equals(map.get(key));
}
}
} else {
}
return true;
}
/**
* Static factory method to create a Criteria using the provided key
*
* @param key filed name
* @return the new criteria
*/
static Criteria where(String key) {
return new Criteria(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) {
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 equality
*
* @param o
* @return
*/
public Criteria eq(Object o) {
return is(o);
}
/**
* Creates a criterion using the != operator
*
* @param o
* @return
*/
public Criteria ne(Object o) {
criteria.put(CriteriaType.NE, o);
return this;
}
/**
* Creates a criterion using the < operator
*
* @param o
* @return
*/
public Criteria lt(Object o) {
criteria.put(CriteriaType.LT, o);
return this;
}
/**
* Creates a criterion using the <= operator
*
* @param o
* @return
*/
public Criteria lte(Object o) {
criteria.put(CriteriaType.LTE, o);
return this;
}
/**
* Creates a criterion using the > operator
*
* @param o
* @return
*/
public Criteria gt(Object o) {
criteria.put(CriteriaType.GT, o);
return this;
}
/**
* Creates a criterion using the >= operator
*
* @param o
* @return
*/
public Criteria gte(Object o) {
criteria.put(CriteriaType.GTE, o);
return this;
}
/**
* The in
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
*/
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));
}
/**
* The in
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
*/
public Criteria in(Collection> c) {
notNull(c, "collection can not be null");
criteria.put(CriteriaType.IN, c);
return this;
}
/**
* The nin
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
*/
public Criteria nin(Object... o) {
return nin(Arrays.asList(o));
}
/**
* The nin
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
*/
public Criteria nin(Collection> c) {
notNull(c, "collection can not be null");
criteria.put(CriteriaType.NIN, c);
return this;
}
/**
* The all
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
*/
public Criteria all(Object... o) {
return all(Arrays.asList(o));
}
/**
* The all
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
*/
public Criteria all(Collection> c) {
notNull(c, "collection can not be null");
criteria.put(CriteriaType.ALL, c);
return this;
}
/**
* The size
operator matches any array with the specified number of elements.
*
* @param s
* @return
*/
public Criteria size(int s) {
criteria.put(CriteriaType.SIZE, s);
return this;
}
/**
* Check for existence (or lack thereof) of a field.
*
* @param b
* @return
*/
public Criteria exists(boolean b) {
criteria.put(CriteriaType.EXISTS, b);
return this;
}
/**
* The $type operator matches values based on their Java type.
*
* @param t
* @return
*/
public Criteria type(Class> t) {
notNull(t, "type can not be null");
criteria.put(CriteriaType.TYPE, t);
return this;
}
/**
* Creates a criterion using a Regex
*
* @param pattern
* @return
*/
public Criteria regex(Pattern pattern) {
notNull(pattern, "pattern can not be null");
criteria.put(CriteriaType.REGEX, pattern);
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;
}
}