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

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

There is a newer version: 7.23.0-alpha1
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.Utils;

import java.util.*;
import java.util.regex.Pattern;

import static java.util.Arrays.asList;
import static com.jayway.jsonpath.internal.Utils.*;

/**
 * @author Kalle Stenflo
 */
public class Criteria {


    private enum CriteriaType {
        GT,
        GTE,
        LT,
        LTE,
        NE,
        IN,
        NIN,
        ALL,
        SIZE,
        EXISTS,
        TYPE,
        REGEX,
        NOT_EMPTY,
        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 JsonPath key;

    private final List criteriaChain;

    private final LinkedHashMap criteria = new LinkedHashMap();

    private Object isValue = NOT_SET;


    private Criteria(String key) {
        Utils.notEmpty(key, "key can not be null or empty");
        this.criteriaChain = new ArrayList();
        this.criteriaChain.add(this);
        this.key = JsonPath.compile(key);
    }

    private Criteria(List criteriaChain, String key) {
        Utils.notEmpty(key, "key can not be null or empty");
        this.criteriaChain = criteriaChain;
        this.criteriaChain.add(this);
        this.key = JsonPath.compile(key);
    }

    JsonPath getKey() {
        return this.key;
    }

    /**
     * Checks if this criteria matches the given map
     *
     * @param map map to check
     * @return true if criteria is a match
     */
    boolean matches(Map map, Configuration configuration) {

        if (this.criteriaChain.size() == 1) {
            return criteriaChain.get(0).singleObjectApply(map, configuration);
        } else {
            for (Criteria c : this.criteriaChain) {
                if (!c.singleObjectApply(map, configuration)) {
                    return false;
                }
            }
            return true;
        }
    }

    private static Object readSafely(JsonPath path, Map map) {
        try {
            return path.read(map);
        } catch (InvalidPathException e) {
            return null;
        }
    }

    private static  boolean objectOrAnyCollectionItemMatches(final Object singleObjectOrCollection,
                                                                final Predicate predicate) {
        if (singleObjectOrCollection instanceof Collection) {
            Iterator it = ((Collection) singleObjectOrCollection).iterator();
            while (it.hasNext()) {
                if (predicate.accept((T) it.next())) {
                    return true;
                }
            }
            return false;
        }
        return predicate.accept((T) singleObjectOrCollection);
    }

    boolean singleObjectApply(Map map, final Configuration configuration) {

        for (CriteriaType key : this.criteria.keySet()) {

            final Object actualVal = readSafely(this.key, map);
            final Object expectedVal = this.criteria.get(key);

            if (CriteriaType.GT.equals(key)) {

                if (expectedVal == null || actualVal == null) {
                    return false;
                }

                final Number expectedNumber = (Number) expectedVal;
                return objectOrAnyCollectionItemMatches(actualVal, new Predicate() {

                    @Override
                    public boolean accept(Number value) {
                        return (value.doubleValue() > expectedNumber.doubleValue());
                    }
                });

            } else if (CriteriaType.GTE.equals(key)) {

                if (expectedVal == null || actualVal == null) {
                    return false;
                }

                final Number expectedNumber = (Number) expectedVal;
                return objectOrAnyCollectionItemMatches(actualVal, new Predicate() {

                    @Override
                    public boolean accept(Number value) {
                        return (value.doubleValue() >= expectedNumber.doubleValue());
                    }
                });

            } else if (CriteriaType.LT.equals(key)) {

                if (expectedVal == null || actualVal == null) {
                    return false;
                }

                final Number expectedNumber = (Number) expectedVal;
                return objectOrAnyCollectionItemMatches(actualVal, new Predicate() {

                    @Override
                    public boolean accept(Number value) {
                        return (value.doubleValue() < expectedNumber.doubleValue());
                    }
                });

            } else if (CriteriaType.LTE.equals(key)) {

                if (expectedVal == null || actualVal == null) {
                    return false;
                }

                final Number expectedNumber = (Number) expectedVal;
                return objectOrAnyCollectionItemMatches(actualVal, new Predicate() {

                    @Override
                    public boolean accept(Number value) {
                        return (value.doubleValue() <= expectedNumber.doubleValue());
                    }
                });

            } else if (CriteriaType.NE.equals(key)) {

                return objectOrAnyCollectionItemMatches(actualVal, new Predicate() {

                    @Override
                    public boolean accept(Object value) {
                        if (expectedVal == null && value == null) {
                            return false;
                        }
                        if (expectedVal == null) {
                            return true;
                        } else {
                            return !expectedVal.equals(value);
                        }
                    }
                });

            } else if (CriteriaType.IN.equals(key)) {

                Collection exp = (Collection) expectedVal;

                return exp.contains(actualVal);

            } else if (CriteriaType.NIN.equals(key)) {

                Collection exp = (Collection) expectedVal;

                return !exp.contains(actualVal);
            } else if (CriteriaType.ALL.equals(key)) {

                Collection exp = (Collection) expectedVal;
                Collection act = (Collection) actualVal;

                return act.containsAll(exp);

            } else if (CriteriaType.SIZE.equals(key)) {

                int exp = (Integer) expectedVal;
                List act = (List) actualVal;

                return (act.size() == exp);

            } else if (CriteriaType.NOT_EMPTY.equals(key)) {

                if (actualVal == null) {
                    return false;
                }
                boolean empty = false;
                if (actualVal instanceof Collection) {
                    empty = ((List) actualVal).isEmpty();
                } else if (actualVal instanceof String) {
                    empty = ((String) actualVal).isEmpty();
                }

                return !empty;

            } else if (CriteriaType.EXISTS.equals(key)) {

                final boolean exp = (Boolean) expectedVal;
                return objectOrAnyCollectionItemMatches(map, new Predicate() {
                    @Override
                    public boolean accept(final Object value) {
                        boolean act = true;
                        Object res;
                        try {
                            res = getKey().read(value, configuration.options(Option.THROW_ON_MISSING_PROPERTY));

                            if(configuration.getProvider().isArray(res)){
                                if(getKey().isPathDefinite()){
                                    act = true;
                                } else {
                                    act = (configuration.getProvider().length(res) > 0);
                                }
                            }
                        } catch (InvalidPathException e) {
                            act = false;
                        }
                        return act == exp;
                    }
                });

            } else if (CriteriaType.TYPE.equals(key)) {

                final Class exp = (Class) expectedVal;
                return objectOrAnyCollectionItemMatches(actualVal, new Predicate() {

                    @Override
                    public boolean accept(Object value) {
                        Class act = value == null ? null : value.getClass();
                        if (act == null) {
                            return false;
                        } else {
                            return act.equals(exp);
                        }
                    }
                });

            } else if (CriteriaType.REGEX.equals(key)) {
                final Pattern exp = (Pattern) expectedVal;

                return objectOrAnyCollectionItemMatches(actualVal, new Predicate() {

                    @Override
                    public boolean accept(String value) {
                        return value != null && exp.matcher(value).matches();
                    }
                });


            } else {
                throw new UnsupportedOperationException("Criteria type not supported: " + key.name());
            }
        }
        if (isValue != NOT_SET) {

            if (isValue instanceof Collection) {
                Collection cs = (Collection) isValue;
                for (Criteria crit : cs) {
                    for (Criteria c : crit.criteriaChain) {
                        if (!c.singleObjectApply(map, configuration)) {
                            return false;
                        }
                    }
                }
                return true;
            } else {
                Object actualVal = readSafely(this.key, map);
                return objectOrAnyCollectionItemMatches(actualVal, new Predicate() {
                    @Override
                    public boolean accept(Object value) {
                        if (isValue == null) {
                            return value == null;
                        } else {
                            return isValue.equals(value);
                        }
                    }

                });
            }
        } else {

        }
        return true;
    }


    /**
     * 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(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");
        checkFilterCanBeApplied(CriteriaType.IN);
        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");
        checkFilterCanBeApplied(CriteriaType.NIN);
        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");
        checkFilterCanBeApplied(CriteriaType.ALL);
        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) {
        checkFilterCanBeApplied(CriteriaType.SIZE);
        criteria.put(CriteriaType.SIZE, s);
        return this;
    }

    /**
     * The notEmpty operator checks that an array is not empty.
     *
     * @return
     */
    public Criteria notEmpty() {
        checkFilterCanBeApplied(CriteriaType.NOT_EMPTY);
        criteria.put(CriteriaType.NOT_EMPTY, null);
        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;
    }

    private void checkFilterCanBeApplied(CriteriaType type) {
        if (getKey().getTokenizer().size() > 2) {
            throw new IllegalArgumentException("Cannot use " + type + " filter on a multi-level path expression");
        }
    }


    private interface Predicate {
        boolean accept(T value);
    }

}