From 143f6ff25ded9611dd0bd99dcc27217d6eb50836 Mon Sep 17 00:00:00 2001 From: Kalle Stenflo Date: Tue, 10 Jun 2014 15:29:59 +0200 Subject: [PATCH] Implement the field exists check. --- .../java/com/jayway/jsonpath/Criteria.java | 785 ++++++++---------- .../java/com/jayway/jsonpath/Criteria2.java | 618 -------------- .../main/java/com/jayway/jsonpath/Filter.java | 178 +--- .../java/com/jayway/jsonpath/Filter2.java | 54 -- .../java/com/jayway/jsonpath/JsonPath.java | 14 +- .../java/com/jayway/jsonpath/ReadContext.java | 4 +- .../jayway/jsonpath/internal/JsonReader.java | 4 +- .../spi/compiler/FilterPathToken.java | 9 +- .../internal/spi/compiler/PathCompiler.java | 23 +- .../com/jayway/jsonpath/ComplianceTest.java | 41 + .../java/com/jayway/jsonpath/Filter2Test.java | 4 +- .../java/com/jayway/jsonpath/FilterTest.java | 16 +- .../java/com/jayway/jsonpath/IssuesTest.java | 2 +- 13 files changed, 453 insertions(+), 1299 deletions(-) delete mode 100644 json-path/src/main/java/com/jayway/jsonpath/Criteria2.java delete mode 100644 json-path/src/main/java/com/jayway/jsonpath/Filter2.java diff --git a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java index b3cba106..1f8a93c4 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java @@ -1,361 +1,296 @@ -/* - * 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.spi.compiler.PathCompiler; import com.jayway.jsonpath.spi.compiler.Path; -import com.jayway.jsonpath.internal.Utils; - -import java.util.*; +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.isNumeric; +import static com.jayway.jsonpath.internal.Utils.join; import static com.jayway.jsonpath.internal.Utils.notNull; -import static java.util.Arrays.asList; + /** - * @author Kalle Stenflo + * */ -public class Criteria { +public class Criteria implements Predicate { + private static final Logger logger = LoggerFactory.getLogger(Criteria.class); - 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 Path key; - private final Path key; + private final Path path; + private CriteriaType criteriaType; + private Object expected; 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 = PathCompiler.tokenize(key); - } - - private Criteria(Path path) { - Utils.notNull(path, "path can not be null"); - this.criteriaChain = new ArrayList(); - this.criteriaChain.add(this); - this.key = path; - } - - private Criteria(List criteriaChain, String key) { - Utils.notEmpty(key, "key can not be null or empty"); - this.criteriaChain = criteriaChain; - //this.key = JsonPath.compile(key); - this.criteriaChain.add(this); - //this.key = PathCompiler.compile(key); - this.key = PathCompiler.tokenize(key); - - } - - //JsonPath getKey() { - //Path getKey() { - Path 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(Object map, Configuration configuration) { - for (Criteria c : this.criteriaChain) { - if (!c.singleObjectApply(map, configuration)) { - return false; + private enum CriteriaType { + EQ { + @Override + boolean eval(Object expected, Object actual, Configuration configuration) { + boolean res = (0 == safeCompare(expected, actual, configuration)); + logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res); + return res; } - } - return true; - } - - //private static Object readSafely(JsonPath path, Map map) { - //private static Object readSafely(Path path, Object map) { - private static Object readSafely(Path path, Object map) { - try { - return path.evaluate(map, Configuration.defaultConfiguration()).get(); - //return PathEvaluator.evaluate(path, map, JsonProviderFactory.createProvider(), Collections.EMPTY_SET).getResult(); - //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; - } + }, + NE { + @Override + boolean eval(Object expected, Object actual, Configuration configuration) { + boolean res = (0 != safeCompare(expected, actual, configuration)); + logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res); + return res; } - return false; - } - return predicate.accept((T) singleObjectOrCollection); - } - - private boolean singleObjectApply(Object 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) { + }, + GT { + @Override + boolean eval(Object expected, Object actual, Configuration configuration) { + if ((expected == null) ^ (actual == 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) { + boolean res = (0 > safeCompare(expected, actual, configuration)); + logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res); + return res; + } + }, + GTE { + @Override + boolean eval(Object expected, Object actual, Configuration configuration) { + if ((expected == null) ^ (actual == 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) { + boolean res = (0 >= safeCompare(expected, actual, configuration)); + logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res); + return res; + } + }, + LT { + @Override + boolean eval(Object expected, Object actual, Configuration configuration) { + if ((expected == null) ^ (actual == 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) { + boolean res = (0 < safeCompare(expected, actual, configuration)); + logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res); + return res; + } + }, + LTE { + @Override + boolean eval(Object expected, Object actual, Configuration configuration) { + if ((expected == null) ^ (actual == null)) { return false; } - - final Number expectedNumber = (Number) expectedVal; - return objectOrAnyCollectionItemMatches(actualVal, new Predicate() { - - @Override - public boolean accept(Number value) { - return (value.doubleValue() <= expectedNumber.doubleValue()); + boolean res = (0 <= safeCompare(expected, actual, configuration)); + logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res); + return res; + } + }, + IN { + @Override + boolean eval(Object expected, Object actual, Configuration configuration) { + boolean res = false; + Collection exps = (Collection) expected; + for (Object exp : exps) { + if (0 == safeCompare(exp, actual, configuration)) { + res = true; + break; } - }); - - } else if (CriteriaType.NE.equals(key)) { - - return objectOrAnyCollectionItemMatches(actualVal, new Predicate() { - - @Override - public boolean accept(Object value) { - if (expectedVal == null && value == null) { - return false; + } + logger.debug("[{}] {} [{}] => {}", actual, name(), join(", ", exps), res); + return res; + } + }, + NIN { + @Override + boolean eval(Object expected, Object actual, Configuration configuration) { + 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, Configuration configuration) { + boolean res = true; + Collection exps = (Collection) expected; + if (configuration.getProvider().isArray(actual)) { + for (Object exp : exps) { + boolean found = false; + for (Object check : configuration.getProvider().toIterable(actual)) { + if (0 == safeCompare(exp, check, configuration)) { + found = true; + break; + } } - if (expectedVal == null) { - return true; - } else { - return !expectedVal.equals(value); + if (!found) { + res = false; + break; } } - }); - - } 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; + logger.debug("[{}] {} [{}] => {}", join(", ", configuration.getProvider().toIterable(actual)), name(), join(", ", exps), res); + } else { + res = false; + logger.debug("[{}] {} [{}] => {}", "", name(), join(", ", exps), res); } - boolean empty = false; - if (actualVal instanceof Collection) { - empty = ((List) actualVal).isEmpty(); - } else if (actualVal instanceof String) { - empty = ((String) actualVal).isEmpty(); + return res; + } + }, + SIZE { + @Override + boolean eval(Object expected, Object actual, Configuration configuration) { + int size = (Integer) expected; + boolean res; + if (configuration.getProvider().isArray(actual)) { + int length = configuration.getProvider().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 !empty; - - } else if (CriteriaType.EXISTS.equals(key)) { - if (configuration.getProvider().isArray(map)) { - return false; + return res; + } + }, + EXISTS { + @Override + boolean eval(Object expected, Object actual, Configuration configuration) { + //This must be handled outside + throw new UnsupportedOperationException(); + } + }, + TYPE { + @Override + boolean eval(Object expected, Object actual, Configuration configuration) { + final Class expType = (Class) expected; + final Class actType = actual == null ? null : actual.getClass(); + if (actType != null) { + return expType.isAssignableFrom(actType); } - final boolean exp = (Boolean) expectedVal; - return objectOrAnyCollectionItemMatches(map, new Predicate() { - @Override - public boolean accept(final Object value) { - boolean act = true; - Object res; - try { - Set