All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jayway.jsonpath.Criteria Maven / Gradle / Ivy

There is a newer version: 2.9.0
Show newest version
/*
 * 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.PathCompiler;
import com.jayway.jsonpath.internal.token.PredicateContextImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
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.join;
import static com.jayway.jsonpath.internal.Utils.notNull;


/**
 *
 */
@SuppressWarnings("unchecked")
public class Criteria implements Predicate {

    private static final Logger logger = LoggerFactory.getLogger(Criteria.class);

    private final Path path;
    private CriteriaType criteriaType;
    private Object expected;

    private final List criteriaChain;

    private static enum CriteriaType {
        EQ {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                boolean res = (0 == safeCompare(expected, actual));
                logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
                return res;
            }
        },
        NE {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                boolean res = (0 != safeCompare(expected, actual));
                logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
                return res;
            }
        },
        GT {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                if ((expected == null) ^ (actual == null)) {
                    return false;
                }
                boolean res = (0 > safeCompare(expected, actual));
                logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
                return res;
            }
        },
        GTE {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                if ((expected == null) ^ (actual == null)) {
                    return false;
                }
                boolean res = (0 >= safeCompare(expected, actual));
                logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
                return res;
            }
        },
        LT {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                if ((expected == null) ^ (actual == null)) {
                    return false;
                }
                boolean res = (0 < safeCompare(expected, actual));
                logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
                return res;
            }
        },
        LTE {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                if ((expected == null) ^ (actual == null)) {
                    return false;
                }
                boolean res = (0 <= safeCompare(expected, actual));
                logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
                return res;
            }
        },
        IN {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                boolean res = false;
                Collection exps = (Collection) expected;
                for (Object exp : exps) {
                    if (0 == safeCompare(exp, actual)) {
                        res = true;
                        break;
                    }
                }
                logger.debug("[{}] {} [{}] => {}", actual, name(), join(", ", exps), res);
                return res;
            }
        },
        NIN {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                Collection nexps = (Collection) expected;
                boolean res = !nexps.contains(actual);
                logger.debug("[{}] {} [{}] => {}", actual, name(), join(", ", nexps), res);
                return res;
            }
        },
        ALL {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                boolean res = true;
                Collection exps = (Collection) expected;
                if (ctx.configuration().jsonProvider().isArray(actual)) {
                    for (Object exp : exps) {
                        boolean found = false;
                        for (Object check : ctx.configuration().jsonProvider().toIterable(actual)) {
                            if (0 == safeCompare(exp, check)) {
                                found = true;
                                break;
                            }
                        }
                        if (!found) {
                            res = false;
                            break;
                        }
                    }
                    logger.debug("[{}] {} [{}] => {}", join(", ", ctx.configuration().jsonProvider().toIterable(actual)), name(), join(", ", exps), res);
                } else {
                    res = false;
                    logger.debug("[{}] {} [{}] => {}", "", name(), join(", ", exps), res);
                }
                return res;
            }
        },
        SIZE {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                int size = (Integer) expected;
                boolean res;
                if (ctx.configuration().jsonProvider().isArray(actual)) {
                    int length = ctx.configuration().jsonProvider().length(actual);
                    res = (length == size);
                    logger.debug("Array with size {} {} {} => {}", length, name(), size, res);
                } else if (actual instanceof String) {
                    int length = ((String) actual).length();
                    res = length == size;
                    logger.debug("String with length {} {} {} => {}", length, name(), size, res);
                } else {
                    res = false;
                    logger.debug("{} {} {} => {}", actual == null ? "null" : actual.getClass().getName(), name(), size, res);
                }
                return res;
            }
        },
        EXISTS {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                //This must be handled outside
                throw new UnsupportedOperationException();
            }
        },
        TYPE {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                final Class expType = (Class) expected;
                final Class actType = actual == null ? null : actual.getClass();

                return actType != null && expType.isAssignableFrom(actType);
            }
        },
        REGEX {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                boolean res = false;
                final Pattern pattern = (Pattern) expected;
                if (actual != null && actual instanceof String) {
                    res = pattern.matcher(actual.toString()).matches();
                }
                logger.debug("[{}] {} [{}] => {}", actual, name(), expected.toString(), res);
                return res;
            }
        },
        MATCHES {
            @Override
            boolean eval(Object expected, final Object actual, final PredicateContext ctx) {
                Predicate exp = (Predicate) expected;
                return exp.apply(new PredicateContextImpl(actual, ctx.root(), ctx.configuration()));
            }
        },
        NOT_EMPTY {
            @Override
            boolean eval(Object expected, Object actual, PredicateContext ctx) {
                boolean res = false;
                if (actual != null) {
                    if (ctx.configuration().jsonProvider().isArray(actual)) {
                        int len = ctx.configuration().jsonProvider().length(actual);
                        res = (0 != len);
                        logger.debug("array length = {} {} => {}", len, name(), res);
                    } else if (actual instanceof String) {
                        int len = ((String) actual).length();
                        res = (0 != len);
                        logger.debug("string length = {} {} => {}", len, name(), res);
                    }
                }
                return res;
            }
        };

        abstract boolean eval(Object expected, Object actual, PredicateContext ctx);

        public static CriteriaType parse(String str) {
            if ("==".equals(str)) {
                return EQ;
            } else if (">".equals(str)) {
                return GT;
            } else if (">=".equals(str)) {
                return GTE;
            } else if ("<".equals(str)) {
                return LT;
            } else if ("<=".equals(str)) {
                return LTE;
            } else if ("!=".equals(str)) {
                return NE;
            } else {
                throw new UnsupportedOperationException("CriteriaType " + str + " can not be parsed");
            }
        }
    }

    private Criteria(List criteriaChain, Path path) {
        if (!path.isDefinite()) {
            throw new InvalidCriteriaException("A criteria path must be definite. The path " + path.toString() + " is not!");
        }
        this.path = path;
        this.criteriaChain = criteriaChain;
        this.criteriaChain.add(this);
    }

    private Criteria(Path path) {
        this(new LinkedList(), path);
    }

    private Criteria(Path path, CriteriaType criteriaType, Object expected) {
        this(new LinkedList(), path);
        this.criteriaType = criteriaType;
        this.expected = expected;
    }


    @Override
    public boolean apply(PredicateContext ctx) {
        for (Criteria criteria : criteriaChain) {
            if (!criteria.eval(ctx)) {
                return false;
            }
        }
        return true;
    }

    private boolean eval(PredicateContext ctx) {
        if (CriteriaType.EXISTS == criteriaType) {
            boolean exists = ((Boolean) expected);
            try {
                Configuration c = Configuration.builder().jsonProvider(ctx.configuration().jsonProvider()).options().build();
                path.evaluate(ctx.item(), ctx.root(), c).getValue();
                return exists;
            } catch (PathNotFoundException e) {
                return !exists;
            }
        } else {
            try {
                final Object actual = path.evaluate(ctx.item(), ctx.root(), ctx.configuration()).getValue();

                Object expectedVal = expected;
                if(expected instanceof Path){
                    Path expectedPath = (Path) expected;
                    Object doc = expectedPath.isRootPath()?ctx.root():ctx.item();
                    expectedVal = expectedPath.evaluate(doc, ctx.root(), ctx.configuration()).getValue();
                }

                return criteriaType.eval(expectedVal, actual, ctx);
            } catch (ValueCompareException e) {
                return false;
            } catch (PathNotFoundException e) {
                return false;
            }
        }
    }


    /**
     * Static factory method to create a Criteria using the provided key
     *
     * @param key filed name
     * @return the new criteria
     */
    public static Criteria where(Path key) {
        return new Criteria(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 where(PathCompiler.compile(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, PathCompiler.compile(key));
    }

    /**
     * Creates a criterion using equality
     *
     * @param o
     * @return the criteria
     */
    public Criteria is(Object o) {
        this.criteriaType = CriteriaType.EQ;
        this.expected = 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 != operator
     *
     * @param o
     * @return the criteria
     */
    public Criteria ne(Object o) {
        this.criteriaType = CriteriaType.NE;
        this.expected = o;
        return this;
    }

    /**
     * Creates a criterion using the < operator
     *
     * @param o
     * @return the criteria
     */
    public Criteria lt(Object o) {
        this.criteriaType = CriteriaType.LT;
        this.expected = o;
        return this;
    }

    /**
     * Creates a criterion using the <= operator
     *
     * @param o
     * @return the criteria
     */
    public Criteria lte(Object o) {
        this.criteriaType = CriteriaType.LTE;
        this.expected = o;
        return this;
    }

    /**
     * Creates a criterion using the > operator
     *
     * @param o
     * @return the criteria
     */
    public Criteria gt(Object o) {
        this.criteriaType = CriteriaType.GT;
        this.expected = o;
        return this;
    }

    /**
     * Creates a criterion using the >= operator
     *
     * @param o
     * @return the criteria
     */
    public Criteria gte(Object o) {
        this.criteriaType = CriteriaType.GTE;
        this.expected = 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 = CriteriaType.REGEX;
        this.expected = pattern;
        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 the criteria
     */
    public Criteria in(Object... o) {
        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 the criteria
     */
    public Criteria in(Collection c) {
        notNull(c, "collection can not be null");
        this.criteriaType = CriteriaType.IN;
        this.expected = 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 the criteria
     */
    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 the criteria
     */
    public Criteria nin(Collection c) {
        notNull(c, "collection can not be null");
        this.criteriaType = CriteriaType.NIN;
        this.expected = 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 the criteria
     */
    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 the criteria
     */
    public Criteria all(Collection c) {
        notNull(c, "collection can not be null");
        this.criteriaType = CriteriaType.ALL;
        this.expected = c;
        return this;
    }

    /**
     * The size operator matches:
     * 

*

    *
  1. array with the specified number of elements.
  2. *
  3. string with given length.
  4. *
* * @param size * @return the criteria */ public Criteria size(int size) { this.criteriaType = CriteriaType.SIZE; this.expected = size; return this; } /** * Check for existence (or lack thereof) of a field. * * @param b * @return the criteria */ public Criteria exists(boolean b) { this.criteriaType = CriteriaType.EXISTS; this.expected = b; return this; } /** * The $type operator matches values based on their Java type. * * @param t * @return the criteria */ public Criteria type(Class t) { notNull(t, "type can not be null"); this.criteriaType = CriteriaType.TYPE; this.expected = t; return this; } /** * The notEmpty operator checks that an array or String is not empty. * * @return the criteria */ public Criteria notEmpty() { this.criteriaType = CriteriaType.NOT_EMPTY; this.expected = null; return this; } /** * The matches operator checks that an object matches the given predicate. * * @param p * @return the criteria */ public Criteria matches(Predicate p) { this.criteriaType = CriteriaType.MATCHES; this.expected = p; return this; } /** * Creates a new criteria * @param path path to evaluate in criteria * @param operator operator * @param expected expected value * @return a new Criteria */ public static Criteria create(String path, String operator, String expected) { if (!expected.isEmpty() && expected.charAt(0) == '\'' && expected.charAt(expected.length() - 1) == '\'') { expected = expected.substring(1, expected.length() - 1); } Path p = PathCompiler.compile(path); if (("$".equals(path) || "@".equals(path) )&& (operator == null || operator.isEmpty()) && (expected == null || expected.isEmpty())) { return new Criteria(p, CriteriaType.NE, null); } else if (operator.isEmpty()) { return Criteria.where(path).exists(true); } else { if(expected.startsWith("$") || expected.startsWith("@")){ Path compile = PathCompiler.compile(expected); if(!compile.isDefinite()){ throw new InvalidPathException("the predicate path: " + expected + " is not definite"); } return new Criteria(p, CriteriaType.parse(operator), compile); } else { return new Criteria(p, CriteriaType.parse(operator), expected); } } } private static int safeCompare(Object expected, Object providerParsed) throws ValueCompareException { if(expected == providerParsed){ return 0; } boolean expNullish = isNullish(expected); boolean provNullish = isNullish(providerParsed); if (expNullish && !provNullish) { return -1; } else if (!expNullish && provNullish) { return 1; } else if (expNullish && provNullish) { return 0; } else if (expected instanceof String && providerParsed instanceof String) { return ((String) expected).compareTo((String) providerParsed); } else if (expected instanceof Number && providerParsed instanceof Number) { return new BigDecimal(expected.toString()).compareTo(new BigDecimal(providerParsed.toString())); } else if (expected instanceof String && providerParsed instanceof Number) { return new BigDecimal(expected.toString()).compareTo(new BigDecimal(providerParsed.toString())); } else if (expected instanceof String && providerParsed instanceof Boolean) { Boolean e = Boolean.valueOf((String) expected); Boolean a = (Boolean) providerParsed; return e.compareTo(a); } else if (expected instanceof Boolean && providerParsed instanceof Boolean) { Boolean e = (Boolean) expected; Boolean a = (Boolean) providerParsed; return e.compareTo(a); } else { logger.debug("Can not compare a {} with a {}", expected.getClass().getName(), providerParsed.getClass().getName()); throw new ValueCompareException(); } } private static boolean isNullish(Object o) { return (o == null || ((o instanceof String) && ("null".equals(o)))); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(path.toString()) .append("|") .append(criteriaType.name()) .append("|") .append(expected) .append("|"); return sb.toString(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy