Browse Source

sync with upstream up to 454b7d49a3 (inclusive)

pull/142/head
Alexey Makeyev 9 years ago
parent
commit
4e4e2384c3
  1. 272
      json-path/src/main/java/com/jayway/jsonpath/Criteria.java
  2. 41
      json-path/src/main/java/com/jayway/jsonpath/Filter.java
  3. 1
      json-path/src/main/java/com/jayway/jsonpath/JsonPath.java
  4. 148
      json-path/src/main/java/com/jayway/jsonpath/internal/JsonFormatter.java
  5. 862
      json-path/src/main/java/com/jayway/jsonpath/internal/PathCompiler.java
  6. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/Utils.java
  7. 63
      json-path/src/main/java/com/jayway/jsonpath/internal/token/ArrayIndexOperation.java
  8. 282
      json-path/src/main/java/com/jayway/jsonpath/internal/token/ArrayPathToken.java
  9. 86
      json-path/src/main/java/com/jayway/jsonpath/internal/token/ArraySliceOperation.java
  10. 6
      json-path/src/main/java/com/jayway/jsonpath/internal/token/PathToken.java
  11. 5
      json-path/src/main/java/com/jayway/jsonpath/internal/token/PathTokenAppender.java
  12. 47
      json-path/src/main/java/com/jayway/jsonpath/internal/token/PathTokenFactory.java
  13. 24
      json-path/src/main/java/com/jayway/jsonpath/internal/token/PredicatePathToken.java
  14. 4
      json-path/src/main/java/com/jayway/jsonpath/internal/token/PropertyPathToken.java
  15. 25
      json-path/src/main/java/com/jayway/jsonpath/internal/token/RootPathToken.java
  16. 5
      json-path/src/main/java/com/jayway/jsonpath/internal/token/ScanPathToken.java
  17. 5
      json-path/src/main/java/com/jayway/jsonpath/internal/token/WildcardPathToken.java
  18. 178
      json-path/src/test/java/com/jayway/jsonpath/FilterTest.java
  19. 131
      json-path/src/test/java/com/jayway/jsonpath/PathCompilerTest.java
  20. 1
      json-path/src/test/java/com/jayway/jsonpath/PredicateTest.java
  21. 6
      json-path/src/test/java/com/jayway/jsonpath/old/ArraySlicingTest.java
  22. 1
      json-path/src/test/java/com/jayway/jsonpath/old/ComplianceTest.java
  23. 62
      json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java
  24. 2
      json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java
  25. 8
      json-path/src/test/java/com/jayway/jsonpath/old/internal/ArrayPathTokenTest.java
  26. 11
      json-path/src/test/java/com/jayway/jsonpath/old/internal/ScanPathTokenTest.java

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

@ -16,6 +16,7 @@ package com.jayway.jsonpath;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.PathCompiler;
import com.jayway.jsonpath.internal.Utils;
import com.jayway.jsonpath.internal.token.PredicateContextImpl;
import com.jayway.jsonpath.spi.json.JsonProvider;
import org.slf4j.Logger;
@ -24,6 +25,7 @@ import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
@ -40,14 +42,26 @@ public class Criteria implements Predicate {
private static final Logger logger = LoggerFactory.getLogger(Criteria.class);
private static final String CRITERIA_WRAPPER_CHAR = "¦";
private static final String[] OPERATORS = {
CriteriaType.EQ.toString(),
CriteriaType.GTE.toString(),
CriteriaType.LTE.toString(),
CriteriaType.EQ.toString(),
CriteriaType.NE.toString(),
CriteriaType.LT.toString(),
CriteriaType.GT.toString(),
CriteriaType.REGEX.toString()
CriteriaType.REGEX.toString(),
CriteriaType.NIN.toString(),
CriteriaType.IN.toString(),
CriteriaType.CONTAINS.toString(),
CriteriaType.ALL.toString(),
CriteriaType.SIZE.toString(),
CriteriaType.EXISTS.toString(),
CriteriaType.TYPE.toString(),
CriteriaType.MATCHES.toString(),
CriteriaType.NOT_EMPTY.toString(),
};
private static final char BS = '\\';
@ -148,6 +162,34 @@ public class Criteria implements Predicate {
return "<=";
}
},
REGEX {
@Override
boolean eval(Object expected, Object model, PredicateContext ctx) {
boolean res = false;
Pattern pattern;
Object target;
if (model instanceof Pattern) {
pattern = (Pattern) model;
target = expected;
} else {
pattern = (Pattern) expected;
target = model;
}
if (target != null) {
res = pattern.matcher(target.toString()).matches();
}
if (logger.isDebugEnabled())
logger.debug("[{}] {} [{}] => {}", model == null ? "null" : model.toString(), name(), expected == null ? "null" : expected.toString(), res);
return res;
}
@Override
public String toString() {
return "=~";
}
},
IN {
@Override
boolean eval(Object expected, Object model, PredicateContext ctx) {
@ -162,6 +204,11 @@ public class Criteria implements Predicate {
if (logger.isDebugEnabled()) logger.debug("[{}] {} [{}] => {}", model, name(), join(", ", exps), res);
return res;
}
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
},
NIN {
@Override
@ -171,6 +218,11 @@ public class Criteria implements Predicate {
if (logger.isDebugEnabled()) logger.debug("[{}] {} [{}] => {}", model, name(), join(", ", nexps), res);
return res;
}
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
},
CONTAINS {
@Override
@ -183,16 +235,21 @@ public class Criteria implements Predicate {
break;
}
}
} else if(model instanceof String){
if(isNullish(expected) || !(expected instanceof String)){
} else if (model instanceof String) {
if (isNullish(expected) || !(expected instanceof String)) {
res = false;
} else {
res = ((String) model).contains((String)expected);
res = ((String) model).contains((String) expected);
}
}
if (logger.isDebugEnabled()) logger.debug("[{}] {} [{}] => {}", model, name(), expected, res);
return res;
}
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
},
ALL {
@Override
@ -221,6 +278,11 @@ public class Criteria implements Predicate {
}
return res;
}
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
},
SIZE {
@Override
@ -242,6 +304,11 @@ public class Criteria implements Predicate {
}
return res;
}
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
},
EXISTS {
@Override
@ -249,6 +316,11 @@ public class Criteria implements Predicate {
//This must be handled outside
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
},
TYPE {
@Override
@ -258,33 +330,10 @@ public class Criteria implements Predicate {
return actType != null && expType.isAssignableFrom(actType);
}
},
REGEX {
@Override
boolean eval(Object expected, Object model, PredicateContext ctx) {
boolean res = false;
Pattern pattern;
Object target;
if (model instanceof Pattern) {
pattern = (Pattern) model;
target = expected;
} else {
pattern = (Pattern) expected;
target = model;
}
if (target != null) {
res = pattern.matcher(target.toString()).matches();
}
if (logger.isDebugEnabled())
logger.debug("[{}] {} [{}] => {}", model == null ? "null" : model.toString(), name(), expected == null ? "null" : expected.toString(), res);
return res;
}
@Override
public String toString() {
return "=~";
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
},
MATCHES {
@ -294,6 +343,11 @@ public class Criteria implements Predicate {
Predicate exp = (Predicate) expected;
return exp.apply(new PredicateContextImpl(model, ctx.root(), ctx.configuration(), pci.documentPathCache()));
}
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
},
NOT_EMPTY {
@Override
@ -312,38 +366,37 @@ public class Criteria implements Predicate {
}
return res;
}
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
},
NOOP {
@Override
boolean eval(Object expected, Object model, PredicateContext ctx) {
return true;
}
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
};
abstract boolean eval(Object expected, Object model, 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 if ("=~".equals(str)) {
return REGEX;
} else {
throw new UnsupportedOperationException("CriteriaType " + str + " can not be parsed");
for (CriteriaType criteriaType : values()) {
if (criteriaType.toString().equals(str)) {
return criteriaType;
}
}
throw new UnsupportedOperationException("CriteriaType " + str + " can not be parsed");
}
}
private Criteria(List<Criteria> criteriaChain, Object left) {
/*
if(left instanceof Path) {
if (!((Path)left).isDefinite()) {
throw new InvalidCriteriaException("A criteria path must be definite. The path " + left.toString() + " is not!");
}
}*/
this.left = left;
this.criteriaChain = criteriaChain;
this.criteriaChain.add(this);
@ -746,21 +799,24 @@ public class Criteria implements Predicate {
public static Criteria parse(String criteria) {
int operatorIndex = -1;
String left = "";
String operator = "";
CriteriaType operator = null;
String right = "";
//can not iterate values() because the need to be checked in order eg '>=' before '>'
for (int y = 0; y < OPERATORS.length; y++) {
operatorIndex = criteria.indexOf(OPERATORS[y]);
if (operatorIndex != -1) {
operator = OPERATORS[y];
operator = CriteriaType.parse(OPERATORS[y]);
break;
}
}
if (!operator.isEmpty()) {
if (operator != null) {
left = criteria.substring(0, operatorIndex).trim();
right = criteria.substring(operatorIndex + operator.length()).trim();
right = criteria.substring(operatorIndex + operator.toString().length()).trim();
} else {
left = criteria.trim();
}
return Criteria.create(left, operator, right);
}
@ -770,7 +826,10 @@ public class Criteria implements Predicate {
private static class JsonValue {
final String value;
volatile Object jsonValue;
JsonValue(String value) { this.value = value; }
JsonValue(String value) {
this.value = value;
}
Object parsed(PredicateContext ctx) {
if (jsonValue == null) {
@ -794,7 +853,7 @@ public class Criteria implements Predicate {
* @param right expected value
* @return a new Criteria
*/
public static Criteria create(String left, String operator, String right) {
private static Criteria create(String left, CriteriaType operator, String right) {
Object leftPrepared = left;
Object rightPrepared = right;
Path leftPath = null;
@ -828,16 +887,18 @@ public class Criteria implements Predicate {
rightPrepared = rightPath;
} else if (isString(right)) {
rightPrepared = right.substring(1, right.length() - 1);
} else if(Utils.isNumeric(right)){
rightPrepared = new BigDecimal(right);
} else if (isJson(right)) {
rightPrepared = new JsonValue(right);
} else if (isPattern(right)) {
rightPrepared = compilePattern(right);
}
if (leftPath != null && operator.isEmpty()) {
if (leftPath != null && (operator == null)) {
return Criteria.where(leftPath).exists(existsCheck);
} else {
return new Criteria(leftPrepared, CriteriaType.parse(operator), rightPrepared);
return new Criteria(leftPrepared, operator, rightPrepared);
}
}
@ -846,7 +907,7 @@ public class Criteria implements Predicate {
}
private static String unescape(String s) {
if (s.indexOf(BS) == - 1)
if (s.indexOf(BS) == -1)
return s;
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
@ -854,15 +915,25 @@ public class Criteria implements Predicate {
if (c == BS) {
char c2 = s.charAt(++i);
switch (c2) {
case 'b': c2 = '\b'; break;
case 'f': c2 = '\f'; break;
case 'n': c2 = '\n'; break;
case 'r': c2 = '\r'; break;
case 't': c2 = '\t'; break;
case 'b':
c2 = '\b';
break;
case 'f':
c2 = '\f';
break;
case 'n':
c2 = '\n';
break;
case 'r':
c2 = '\r';
break;
case 't':
c2 = '\t';
break;
case 'u':
try {
String hex = s.substring(i + 1, i + 5);
c2 = (char)Integer.parseInt(hex, 16);
c2 = (char) Integer.parseInt(hex, 16);
i += 4;
} catch (Exception e) {
throw new ValueCompareException("\\u parse failed", e);
@ -897,6 +968,8 @@ public class Criteria implements Predicate {
return expected.compareTo((String) right);
} else if (left instanceof Number && right instanceof Number) {
return new BigDecimal(left.toString()).compareTo(new BigDecimal(right.toString()));
} else if (left instanceof Number && right instanceof BigDecimal) {
return new BigDecimal(left.toString()).compareTo((BigDecimal)right);
} else if (left instanceof String && right instanceof Number) {
return new BigDecimal(left.toString()).compareTo(new BigDecimal(right.toString()));
} else if (left instanceof String && right instanceof Boolean) {
@ -924,9 +997,42 @@ public class Criteria implements Predicate {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(left.toString())
.append(criteriaType.toString())
.append(wrapString(right));
Iterator<Criteria> i = criteriaChain.iterator();
while (i.hasNext()) {
Criteria c = i.next();
if (CriteriaType.NOT_EMPTY == c.criteriaType) {
sb.append(c.left.toString())
.append(" ")
.append(c.criteriaType.toString());
} else if (CriteriaType.EXISTS == c.criteriaType) {
Boolean exists = (Boolean)c.right;
if(exists.booleanValue()){
sb.append(c.left.toString());
} else {
sb.append("!").append(c.left.toString());
}
} else if (CriteriaType.TYPE == c.criteriaType) {
Class tp = (Class) c.right;
sb.append(c.left.toString())
.append(" ")
.append(c.criteriaType.toString())
.append(" ")
.append(tp.getCanonicalName());
} else {
sb.append(c.left.toString())
.append(" ")
.append(c.criteriaType.toString())
.append(" ")
.append(wrapString(c.right));
}
if (i.hasNext()) {
sb.append(" && ");
}
}
return sb.toString();
}
@ -935,11 +1041,29 @@ public class Criteria implements Predicate {
return "null";
}
if (o instanceof String) {
return "'" + o.toString() + "'";
} else {
return o.toString();
String s = o.toString();
return "'" + s + "'";
// if(Utils.isNumeric(s)){
// return s;
// } else {
// return "'" + s + "'";
// }
}
if (o instanceof Collection) {
StringBuilder sb = new StringBuilder();
sb.append("[");
Iterator i = ((Collection) o).iterator();
while (i.hasNext()) {
sb.append(wrapString(i.next()));
if (i.hasNext()) {
sb.append(",");
}
}
sb.append("]");
return sb.toString();
}
}
return o.toString();
}
}

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

@ -14,12 +14,12 @@
*/
package com.jayway.jsonpath;
import com.jayway.jsonpath.internal.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Stack;
import java.util.regex.Pattern;
@ -82,7 +82,7 @@ public abstract class Filter implements Predicate {
@Override
public String toString() {
return predicate.toString();
return "[?(" + predicate.toString() + ")]";
}
}
@ -116,7 +116,23 @@ public abstract class Filter implements Predicate {
@Override
public String toString() {
return "(" + Utils.join(" && ", predicates) + ")";
Iterator<Predicate> i = predicates.iterator();
StringBuilder sb = new StringBuilder();
sb.append("[?(");
while (i.hasNext()){
String p = i.next().toString();
if(p.startsWith("[?(")){
p = p.substring(3, p.length() - 2);
}
sb.append(p);
if(i.hasNext()){
sb.append(" && ");
}
}
sb.append(")]");
return sb.toString();
}
}
@ -142,8 +158,25 @@ public abstract class Filter implements Predicate {
@Override
public String toString() {
return "(" + left.toString() + " || " + right.toString() + ")";
StringBuilder sb = new StringBuilder();
sb.append("[?(");
String l = left.toString();
String r = right.toString();
if(l.startsWith("[?(")){
l = l.substring(3, l.length() - 2);
}
if(r.startsWith("[?(")){
r = r.substring(3, r.length() - 2);
}
sb.append(l).append(" || ").append(r);
sb.append(")]");
return sb.toString();
}
}

1
json-path/src/main/java/com/jayway/jsonpath/JsonPath.java

@ -528,6 +528,7 @@ public class JsonPath {
* @param provider jsonProvider to use when parsing JSON
* @return a parsing context based on given jsonProvider
*/
@Deprecated
public static ParseContext using(JsonProvider provider) {
return new JsonReader(Configuration.builder().jsonProvider(provider).build());
}

148
json-path/src/main/java/com/jayway/jsonpath/internal/JsonFormatter.java

@ -20,11 +20,11 @@ public class JsonFormatter {
private static final String NEW_LINE = System.getProperty("line.separator");
private static final int MODE_SINGLE = 100;
private static final int MODE_DOUBLE = 101;
private static final int MODE_ESCAPE_SINGLE = 102;
private static final int MODE_ESCAPE_DOUBLE = 103;
private static final int MODE_BETWEEN = 104;
private static final int MODE_SINGLE = 100;
private static final int MODE_DOUBLE = 101;
private static final int MODE_ESCAPE_SINGLE = 102;
private static final int MODE_ESCAPE_DOUBLE = 103;
private static final int MODE_BETWEEN = 104;
private static void appendIndent(StringBuilder sb, int count) {
for (; count > 0; --count) sb.append(INDENT);
@ -35,80 +35,80 @@ public class JsonFormatter {
input = input.replaceAll("[\\r\\n]", "");
StringBuilder output = new StringBuilder(input.length() * 2);
int mode = MODE_BETWEEN;
int mode = MODE_BETWEEN;
int depth = 0;
for (int i = 0; i < input.length(); ++i) {
char ch = input.charAt(i);
switch (mode) {
case MODE_BETWEEN:
switch (ch) {
case '{':
case '[':
output.append(ch);
output.append(NEW_LINE);
appendIndent(output, ++depth);
break;
case '}':
case ']':
output.append(NEW_LINE);
appendIndent(output, --depth);
output.append(ch);
break;
case ',':
output.append(ch);
output.append(NEW_LINE);
appendIndent(output, depth);
break;
case ':':
output.append(" : ");
break;
case '\'':
output.append(ch);
mode = MODE_SINGLE;
break;
case '"':
output.append(ch);
mode = MODE_DOUBLE;
break;
case ' ':
break;
default:
output.append(ch);
break;
}
break;
case MODE_ESCAPE_SINGLE:
output.append(ch);
mode = MODE_SINGLE;
break;
case MODE_ESCAPE_DOUBLE:
output.append(ch);
mode = MODE_DOUBLE;
break;
case MODE_SINGLE:
output.append(ch);
switch (ch) {
case '\'':
mode = MODE_BETWEEN;
break;
case '\\':
mode = MODE_ESCAPE_SINGLE;
break;
}
break;
case MODE_DOUBLE:
output.append(ch);
switch (ch) {
case '"':
mode = MODE_BETWEEN;
break;
case '\\':
mode = MODE_ESCAPE_DOUBLE;
break;
}
break;
switch (mode) {
case MODE_BETWEEN:
switch (ch) {
case '{':
case '[':
output.append(ch);
output.append(NEW_LINE);
appendIndent(output, ++depth);
break;
case '}':
case ']':
output.append(NEW_LINE);
appendIndent(output, --depth);
output.append(ch);
break;
case ',':
output.append(ch);
output.append(NEW_LINE);
appendIndent(output, depth);
break;
case ':':
output.append(" : ");
break;
case '\'':
output.append(ch);
mode = MODE_SINGLE;
break;
case '"':
output.append(ch);
mode = MODE_DOUBLE;
break;
case ' ':
break;
default:
output.append(ch);
break;
}
break;
case MODE_ESCAPE_SINGLE:
output.append(ch);
mode = MODE_SINGLE;
break;
case MODE_ESCAPE_DOUBLE:
output.append(ch);
mode = MODE_DOUBLE;
break;
case MODE_SINGLE:
output.append(ch);
switch (ch) {
case '\'':
mode = MODE_BETWEEN;
break;
case '\\':
mode = MODE_ESCAPE_SINGLE;
break;
}
break;
case MODE_DOUBLE:
output.append(ch);
switch (ch) {
case '"':
mode = MODE_BETWEEN;
break;
case '\\':
mode = MODE_ESCAPE_DOUBLE;
break;
}
break;
}
}
return output.toString();

862
json-path/src/main/java/com/jayway/jsonpath/internal/PathCompiler.java

@ -1,512 +1,576 @@
/*
* 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.internal;
import com.jayway.jsonpath.Filter;
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.Predicate;
import com.jayway.jsonpath.internal.token.ArrayPathToken;
import com.jayway.jsonpath.internal.token.PathToken;
import com.jayway.jsonpath.internal.token.PredicatePathToken;
import com.jayway.jsonpath.internal.token.PropertyPathToken;
import com.jayway.jsonpath.internal.token.ArrayIndexOperation;
import com.jayway.jsonpath.internal.token.ArraySliceOperation;
import com.jayway.jsonpath.internal.token.PathTokenAppender;
import com.jayway.jsonpath.internal.token.PathTokenFactory;
import com.jayway.jsonpath.internal.token.RootPathToken;
import com.jayway.jsonpath.internal.token.ScanPathToken;
import com.jayway.jsonpath.internal.token.WildcardPathToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import static com.jayway.jsonpath.internal.Utils.notEmpty;
import static java.lang.Character.isDigit;
import static java.lang.Math.min;
import static java.util.Arrays.asList;
public class PathCompiler {
private static final Logger logger = LoggerFactory.getLogger(PathCompiler.class);
private static final String PROPERTY_OPEN = "['";
private static final String PROPERTY_CLOSE = "']";
private static final char DOCUMENT = '$';
private static final char ANY = '*';
private static final char DOC_CONTEXT = '$';
private static final char EVAL_CONTEXT = '@';
private static final char OPEN_SQUARE_BRACKET = '[';
private static final char CLOSE_SQUARE_BRACKET = ']';
private static final char OPEN_BRACKET = '(';
private static final char CLOSE_BRACKET = ')';
private static final char WILDCARD = '*';
private static final char PERIOD = '.';
private static final char BRACKET_OPEN = '[';
private static final char BRACKET_CLOSE = ']';
private static final char SPACE = ' ';
private static final char QUESTIONMARK = '?';
private static final char COMMA = ',';
private static final char SPLIT = ':';
private static final char MINUS = '-';
private static final char ESCAPE = '\\';
private static final char TICK = '\'';
private static final Cache cache = new Cache(200);
private final LinkedList<Predicate> filterStack;
private final CharacterIndex path;
private PathCompiler(String path, LinkedList<Predicate> filterStack) {
this.filterStack = filterStack;
this.path = new CharacterIndex(path);
}
public static Path compile(final String path, final Predicate... filters) {
private Path compile() {
RootPathToken root = readContextToken();
return new CompiledPath(root, root.getPathFragment().equals("$"));
}
notEmpty(path, "Path may not be null empty");
public static Path compile(String path, final Predicate... filters) {
try {
String trimmedPath = path.trim();
path = path.trim();
if (trimmedPath.endsWith("..")) {
throw new InvalidPathException("A path can not end with a scan.");
if(!path.startsWith("$") && !path.startsWith("@")){
path = "$." + path;
}
LinkedList<Predicate> filterList = new LinkedList<Predicate>(asList(filters));
if (trimmedPath.charAt(0) != '$' && trimmedPath.charAt(0) != '@') {
trimmedPath = Utils.concat("$.", trimmedPath);
if(path.endsWith("..")){
fail("Path must not end wid a scan operation '..'");
}
boolean isRootPath = (trimmedPath.charAt(0) == '$');
if (trimmedPath.charAt(0) == '@') {
trimmedPath = Utils.concat("$", trimmedPath.substring(1));
LinkedList filterStack = new LinkedList<Predicate>(asList(filters));
String cacheKey = Utils.concat(path, filterStack.toString());
Path p = cache.get(cacheKey);
if (p == null) {
p = new PathCompiler(path.trim(), filterStack).compile();
cache.put(cacheKey, p);
}
if (trimmedPath.length() > 1 &&
trimmedPath.charAt(1) != '.' &&
trimmedPath.charAt(1) != '[') {
throw new InvalidPathException("Invalid path " + trimmedPath);
return p;
} catch (Exception e) {
InvalidPathException ipe;
if (e instanceof InvalidPathException) {
ipe = (InvalidPathException) e;
} else {
ipe = new InvalidPathException(e);
}
throw ipe;
}
}
String cacheKey = Utils.concat(trimmedPath, Boolean.toString(isRootPath), filterList.toString());
Path p = cache.get(cacheKey);
if (p != null) {
if (logger.isDebugEnabled()) logger.debug("Using cached path: {}", cacheKey);
return p;
}
//[$ | @]
private RootPathToken readContextToken() {
RootPathToken root = null;
int i = 0;
int positions;
String fragment = "";
do {
char current = trimmedPath.charAt(i);
switch (current) {
case SPACE:
throw new InvalidPathException("Space not allowed in path");
case DOCUMENT:
fragment = "$";
i++;
break;
case BRACKET_OPEN:
positions = fastForwardUntilClosed(trimmedPath, i);
fragment = trimmedPath.substring(i, i + positions);
i += positions;
break;
case PERIOD:
i++;
if ( i < trimmedPath.length() && trimmedPath.charAt(i) == PERIOD) {
//This is a deep scan
fragment = "..";
i++;
} else {
positions = fastForward(trimmedPath, i);
if (positions == 0) {
continue;
if (!path.currentCharIs(DOC_CONTEXT) && !path.currentCharIs(EVAL_CONTEXT)) {
throw new InvalidPathException("Path must start with '$' or '@'");
}
} else if (positions == 1 && trimmedPath.charAt(i) == '*') {
fragment = new String("[*]");
} else {
assertValidFieldChars(trimmedPath, i, positions);
RootPathToken pathToken = PathTokenFactory.createRootPathToken(path.currentChar());
PathTokenAppender appender = pathToken.getPathTokenAppender();
fragment = Utils.concat(PROPERTY_OPEN, trimmedPath.substring(i, i + positions), PROPERTY_CLOSE);
}
i += positions;
}
break;
case ANY:
fragment = new String("[*]");
i++;
break;
default:
positions = fastForward(trimmedPath, i);
fragment = Utils.concat(PROPERTY_OPEN, trimmedPath.substring(i, i + positions), PROPERTY_CLOSE);
i += positions;
break;
}
if (root == null) {
root = (RootPathToken) PathComponentAnalyzer.analyze(fragment, filterList);
} else {
root.append(PathComponentAnalyzer.analyze(fragment, filterList));
}
if (path.currentIsTail()) {
return pathToken;
}
} while (i < trimmedPath.length());
path.incrementPosition(1);
Path pa = new CompiledPath(root, isRootPath);
if(path.currentChar() != PERIOD && path.currentChar() != OPEN_SQUARE_BRACKET){
fail("Illegal character at position " + path.position + " expected '.' or '[");
}
cache.put(cacheKey, pa);
readNextToken(appender);
return pa;
return pathToken;
}
} catch (Exception ex){
throw new InvalidPathException(ex);
//
//
//
private boolean readNextToken(PathTokenAppender appender) {
char c = path.currentChar();
switch (c) {
case OPEN_SQUARE_BRACKET:
return readBracketPropertyToken(appender) ||
readArrayToken(appender) ||
readWildCardToken(appender) ||
readFilterToken(appender) ||
readPlaceholderToken(appender) ||
fail("Could not parse bracket statement at position " + path.position);
case PERIOD:
return readDotSeparatorToken(appender) ||
readScanToken(appender) ||
fail("Could not parse token at position " + path.position);
case WILDCARD:
return readWildCardToken(appender) ||
fail("Could not parse token at position " + path.position);
default:
return readPropertyToken(appender) ||
fail("Could not parse token at position " + path.position);
}
}
private static void assertValidFieldChars(String s, int start, int positions) {
/*
int i = start;
while (i < start + positions) {
char c = s.charAt(i);
if (!Character.isLetterOrDigit(c) && c != '-' && c != '_' && c != '$' && c != '@') {
throw new InvalidPathException("Invalid field name! Use bracket notation if your filed names does not match pattern: ([a-zA-Z@][a-zA-Z0-9@\\$_\\-]*)$");
}
i++;
//
// .
//
private boolean readDotSeparatorToken(PathTokenAppender appender) {
if (!path.currentCharIs('.') || path.nextCharIs('.')) {
return false;
}
if (!path.hasMoreCharacters()) {
throw new InvalidPathException("Path must not end with a '.");
}
*/
// if (path.nextSignificantCharIs('[')) {
// throw new InvalidPathException("A bracket may not follow a '.");
// }
path.incrementPosition(1);
return readNextToken(appender);
}
private static int fastForward(String s, int index) {
int skipCount = 0;
while (index < s.length()) {
char current = s.charAt(index);
if (current == PERIOD || current == BRACKET_OPEN || current == SPACE) {
//
// fooBar
//
private boolean readPropertyToken(PathTokenAppender appender) {
if (path.currentCharIs(OPEN_SQUARE_BRACKET) || path.currentCharIs(WILDCARD) || path.currentCharIs(PERIOD) || path.currentCharIs(SPACE)) {
return false;
}
int startPosition = path.position;
int readPosition = startPosition;
int endPosition = 0;
while (path.inBounds(readPosition)) {
char c = path.charAt(readPosition);
if (c == SPACE) {
throw new InvalidPathException("Use bracket notion ['my prop'] if your property contains blank characters. position: " + path.position);
}
if (c == PERIOD || c == OPEN_SQUARE_BRACKET) {
endPosition = readPosition;
break;
}
index++;
skipCount++;
readPosition++;
}
if (endPosition == 0) {
endPosition = path.length();
}
return skipCount;
path.setPosition(endPosition);
String property = path.subSequence(startPosition, endPosition).toString();
appender.appendPathToken(PathTokenFactory.createSinglePropertyPathToken(property));
return path.currentIsTail() || readNextToken(appender);
}
private static int fastForwardUntilClosed(String s, int index) {
int skipCount = 0;
int nestedBrackets = 0;
//
// [?], [?,?, ..]
//
private boolean readPlaceholderToken(PathTokenAppender appender) {
//First char is always '[' no need to check it
index++;
skipCount++;
if (!path.currentCharIs(OPEN_SQUARE_BRACKET)) {
return false;
}
int questionmarkIndex = path.indexOfNextSignificantChar(QUESTIONMARK);
if (questionmarkIndex == -1) {
return false;
}
char nextSignificantChar = path.nextSignificantChar(questionmarkIndex);
if (nextSignificantChar != CLOSE_SQUARE_BRACKET && nextSignificantChar != COMMA) {
return false;
}
while (index < s.length()) {
char current = s.charAt(index);
int expressionBeginIndex = path.position + 1;
int expressionEndIndex = path.nextIndexOf(expressionBeginIndex, CLOSE_SQUARE_BRACKET);
index++;
skipCount++;
if (expressionEndIndex == -1) {
return false;
}
if (current == BRACKET_CLOSE && nestedBrackets == 0) {
break;
}
if (current == BRACKET_OPEN) {
nestedBrackets++;
}
if (current == BRACKET_CLOSE) {
nestedBrackets--;
String expression = path.subSequence(expressionBeginIndex, expressionEndIndex).toString();
String[] tokens = expression.split(",");
if (filterStack.size() < tokens.length) {
throw new InvalidPathException("Not enough predicates supplied for filter [" + expression + "] at position " + path.position);
}
Collection<Predicate> predicates = new ArrayList<Predicate>();
for (String token : tokens) {
token = token != null ? token.trim() : token;
if (!"?".equals(token == null ? "" : token)) {
throw new InvalidPathException("Expected '?' but found " + token);
}
predicates.add(filterStack.pop());
}
return skipCount;
appender.appendPathToken(PathTokenFactory.createPredicatePathToken(predicates));
path.setPosition(expressionEndIndex + 1);
return path.currentIsTail() || readNextToken(appender);
}
//
// [?(...)]
//
private boolean readFilterToken(PathTokenAppender appender) {
if (!path.currentCharIs(OPEN_SQUARE_BRACKET) && !path.nextSignificantCharIs(QUESTIONMARK)) {
return false;
}
int openStatementBracketIndex = path.position;
int questionMarkIndex = path.indexOfNextSignificantChar(QUESTIONMARK);
if (questionMarkIndex == -1) {
return false;
}
int openBracketIndex = path.indexOfNextSignificantChar(questionMarkIndex, OPEN_BRACKET);
if (openBracketIndex == -1) {
return false;
}
int closeBracketIndex = path.indexOfClosingBracket(openBracketIndex + 1, true);
if (closeBracketIndex == -1) {
return false;
}
if (!path.nextSignificantCharIs(closeBracketIndex, CLOSE_SQUARE_BRACKET)) {
return false;
}
int closeStatementBracketIndex = path.indexOfNextSignificantChar(closeBracketIndex, CLOSE_SQUARE_BRACKET);
String criteria = path.subSequence(openStatementBracketIndex, closeStatementBracketIndex + 1).toString();
appender.appendPathToken(PathTokenFactory.createPredicatePathToken(Filter.parse(criteria)));
//---------------------------------------------
path.setPosition(closeStatementBracketIndex + 1);
return path.currentIsTail() || readNextToken(appender);
}
//
// [*]
// *
//
private boolean readWildCardToken(PathTokenAppender appender) {
boolean inBracket = path.currentCharIs(OPEN_SQUARE_BRACKET);
if (inBracket && !path.nextSignificantCharIs(WILDCARD)) {
return false;
}
if (!path.currentCharIs(WILDCARD) && path.isOutOfBounds(path.position + 1)) {
return false;
}
if (inBracket) {
int wildCardIndex = path.indexOfNextSignificantChar(WILDCARD);
if (!path.nextSignificantCharIs(wildCardIndex, CLOSE_SQUARE_BRACKET)) {
throw new InvalidPathException("Expected wildcard token to end with ']' on position " + wildCardIndex + 1);
}
int bracketCloseIndex = path.indexOfNextSignificantChar(wildCardIndex, CLOSE_SQUARE_BRACKET);
path.setPosition(bracketCloseIndex + 1);
} else {
path.incrementPosition(1);
}
appender.appendPathToken(PathTokenFactory.createWildCardPathToken());
return path.currentIsTail() || readNextToken(appender);
}
//
// [1], [1,2, n], [1:], [1:2], [:2]
//
//---------------------------------------------
static class PathComponentAnalyzer {
private boolean readArrayToken(PathTokenAppender appender) {
private static final Pattern FILTER_PATTERN = Pattern.compile("^\\[\\s*\\?\\s*[,\\s*\\?]*?\\s*]$"); //[?] or [?, ?, ...]
private int i;
private char current;
if (!path.currentCharIs(OPEN_SQUARE_BRACKET)) {
return false;
}
char nextSignificantChar = path.nextSignificantChar();
if (!isDigit(nextSignificantChar) && nextSignificantChar != MINUS && nextSignificantChar != SPLIT) {
return false;
}
private final LinkedList<Predicate> filterList;
private final String pathFragment;
int expressionBeginIndex = path.position + 1;
int expressionEndIndex = path.nextIndexOf(expressionBeginIndex, CLOSE_SQUARE_BRACKET);
PathComponentAnalyzer(String pathFragment, LinkedList<Predicate> filterList) {
this.pathFragment = pathFragment;
this.filterList = filterList;
if (expressionEndIndex == -1) {
return false;
}
static PathToken analyze(String pathFragment, LinkedList<Predicate> filterList) {
return new PathComponentAnalyzer(pathFragment, filterList).analyze();
String expression = path.subSequence(expressionBeginIndex, expressionEndIndex).toString().replace(" ", "");
if ("*".equals(expression)) {
return false;
}
public PathToken analyze() {
//check valid chars
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (!isDigit(c) && c != COMMA && c != MINUS && c != SPLIT) {
return false;
}
}
if ("$".equals(pathFragment)) return new RootPathToken();
else if ("..".equals(pathFragment)) return new ScanPathToken();
else if ("[*]".equals(pathFragment)) return new WildcardPathToken();
else if (".*".equals(pathFragment)) return new WildcardPathToken();
else if ("[?]".equals(pathFragment)) return new PredicatePathToken(filterList.poll());
boolean isSliceOperation = expression.contains(":");
else if (FILTER_PATTERN.matcher(pathFragment).matches()) {
final int criteriaCount = Utils.countMatches(pathFragment, "?");
List<Predicate> filters = new ArrayList<Predicate>(criteriaCount);
for (int i = 0; i < criteriaCount; i++) {
filters.add(filterList.poll());
if (isSliceOperation) {
ArraySliceOperation arraySliceOperation = ArraySliceOperation.parse(expression);
appender.appendPathToken(PathTokenFactory.createSliceArrayPathToken(arraySliceOperation));
} else {
ArrayIndexOperation arrayIndexOperation = ArrayIndexOperation.parse(expression);
appender.appendPathToken(PathTokenFactory.createIndexArrayPathToken(arrayIndexOperation));
}
path.setPosition(expressionEndIndex + 1);
return path.currentIsTail() || readNextToken(appender);
}
//
// ['foo']
//
private boolean readBracketPropertyToken(PathTokenAppender appender) {
if (!path.currentCharIs(OPEN_SQUARE_BRACKET) || !path.nextSignificantCharIs(TICK)) {
return false;
}
List<String> properties = new ArrayList<String>();
int startPosition = path.position + 1;
int readPosition = startPosition;
int endPosition = 0;
boolean inProperty = false;
while (path.inBounds(readPosition)) {
char c = path.charAt(readPosition);
if (c == CLOSE_SQUARE_BRACKET) {
if (inProperty) {
throw new InvalidPathException("Expected property to be closed at position " + readPosition);
}
break;
} else if (c == TICK) {
if (inProperty) {
endPosition = readPosition;
properties.add(path.subSequence(startPosition, endPosition).toString());
inProperty = false;
} else {
startPosition = readPosition + 1;
inProperty = true;
}
return new PredicatePathToken(filters);
}
readPosition++;
}
this.i = 0;
do {
current = pathFragment.charAt(i);
switch (current) {
case '?':
return analyzeCriteriaSequence4();
case '\'':
return analyzeProperty();
default:
if (Character.isDigit(current) || current == ':' || current == '-' || current == '@') {
return analyzeArraySequence();
}
i++;
break;
}
int endBracketIndex = path.indexOfNextSignificantChar(endPosition, CLOSE_SQUARE_BRACKET) + 1;
path.setPosition(endBracketIndex);
appender.appendPathToken(PathTokenFactory.createPropertyPathToken(properties));
} while (i < pathFragment.length());
return path.currentIsTail() || readNextToken(appender);
}
throw new InvalidPathException("Could not analyze path component: " + pathFragment);
//
// ..
//
private boolean readScanToken(PathTokenAppender appender) {
if (!path.currentCharIs(PERIOD) || !path.nextCharIs(PERIOD)) {
return false;
}
appender.appendPathToken(PathTokenFactory.crateScanToken());
path.incrementPosition(2);
return readNextToken(appender);
}
public PathToken analyzeCriteriaSequence4() {
int[] bounds = findFilterBounds();
i = bounds[1];
return new PredicatePathToken(Filter.parse(pathFragment.substring(bounds[0], bounds[1])));
}
public static boolean fail(String message) {
throw new InvalidPathException(message);
}
int[] findFilterBounds(){
int end = 0;
int start = i;
while(pathFragment.charAt(start) != '['){
start--;
}
private static class CharacterIndex {
int mem = -1;
int curr = start;
boolean inProp = false;
int openSquareBracket = 0;
int openBrackets = 0;
while(end == 0){
char c = pathFragment.charAt(curr);
switch (c){
case '(':
if(!inProp) openBrackets++;
break;
case ')':
if(!inProp) openBrackets--;
break;
case '[':
if(!inProp) openSquareBracket++;
break;
case ']':
if(!inProp){
openSquareBracket--;
if(openBrackets == 0){
end = curr + 1;
}
}
break;
case '\\':
if (mem == '\\') { // escaped backslash, skip it
mem = -1;
curr++;
continue;
}
break;
case '\'':
if(mem == '\\') {
break;
}
inProp = !inProp;
break;
default:
break;
}
mem = c;
curr++;
}
if(openBrackets != 0 || openSquareBracket != 0){
throw new InvalidPathException("Filter brackets are not balanced");
}
return new int[]{start, end};
private final CharSequence charSequence;
private int position;
private CharacterIndex(CharSequence charSequence) {
this.charSequence = charSequence;
this.position = 0;
}
private int length() {
return charSequence.length();
}
private char charAt(int idx) {
return charSequence.charAt(idx);
}
private char currentChar() {
return charSequence.charAt(position);
}
private boolean currentCharIs(char c) {
return (charSequence.charAt(position) == c);
}
private boolean nextCharIs(char c) {
return inBounds(position + 1) && (charSequence.charAt(position + 1) == c);
}
//"['foo']"
private PathToken analyzeProperty() {
List<String> properties = new ArrayList<String>();
StringBuilder buffer = new StringBuilder();
private int incrementPosition(int charCount) {
return setPosition(position + charCount);
}
boolean propertyIsOpen = false;
private int setPosition(int newPosition) {
position = min(newPosition, charSequence.length() - 1);
return position;
}
while (current != ']') {
switch (current) {
case '\'':
if (propertyIsOpen) {
properties.add(buffer.toString());
buffer.setLength(0);
propertyIsOpen = false;
} else {
propertyIsOpen = true;
}
break;
default:
if (propertyIsOpen) {
buffer.append(current);
private int indexOfClosingBracket(int startPosition, boolean skipStrings) {
int opened = 1;
int readPosition = startPosition;
while (inBounds(readPosition)) {
if (skipStrings) {
if (charAt(readPosition) == TICK) {
boolean escaped = false;
while (inBounds(readPosition)) {
readPosition++;
if (escaped) {
escaped = false;
continue;
}
if (charAt(readPosition) == ESCAPE) {
escaped = true;
continue;
}
if (charAt(readPosition) == TICK) {
readPosition++;
break;
}
}
break;
}
current = pathFragment.charAt(++i);
}
return new PropertyPathToken(properties);
}
//"[-1:]" sliceFrom
//"[:1]" sliceTo
//"[0:5]" sliceBetween
//"[1]"
//"[1,2,3]"
//"[(@.length - 1)]"
private PathToken analyzeArraySequence() {
StringBuilder buffer = new StringBuilder();
List<Integer> numbers = new ArrayList<Integer>();
boolean contextSize = (current == '@');
boolean sliceTo = false;
boolean sliceFrom = false;
boolean sliceBetween = false;
boolean indexSequence = false;
boolean singleIndex = false;
if (contextSize) {
current = pathFragment.charAt(++i);
current = pathFragment.charAt(++i);
while (current != '-') {
if (current == ' ' || current == '(' || current == ')') {
current = pathFragment.charAt(++i);
continue;
}
buffer.append(current);
current = pathFragment.charAt(++i);
}
String function = buffer.toString();
buffer.setLength(0);
if (!function.equals("size") && !function.equals("length")) {
throw new InvalidPathException("Invalid function: @." + function + ". Supported functions are: [(@.length - n)] and [(@.size() - n)]");
if (charAt(readPosition) == OPEN_BRACKET) {
opened++;
}
while (current != ')') {
if (current == ' ') {
current = pathFragment.charAt(++i);
continue;
if (charAt(readPosition) == CLOSE_BRACKET) {
opened--;
if(opened == 0){
return readPosition;
}
buffer.append(current);
current = pathFragment.charAt(++i);
}
readPosition++;
}
return -1;
}
} else {
public int indexOfNextSignificantChar(char c) {
return indexOfNextSignificantChar(position, c);
}
public int indexOfNextSignificantChar(int startPosition, char c) {
int readPosition = startPosition + 1;
while (!isOutOfBounds(readPosition) && charAt(readPosition) == SPACE) {
readPosition++;
}
if (charAt(readPosition) == c) {
return readPosition;
} else {
return -1;
}
}
while (Character.isDigit(current) || current == ',' || current == ' ' || current == ':' || current == '-') {
switch (current) {
case ' ':
break;
case ':':
if (buffer.length() == 0) {
//this is a tail slice [:12]
sliceTo = true;
current = pathFragment.charAt(++i);
while (Character.isDigit(current) || current == ' ' || current == '-') {
if (current != ' ') {
buffer.append(current);
}
current = pathFragment.charAt(++i);
}
numbers.add(Integer.parseInt(buffer.toString()));
buffer.setLength(0);
} else {
//we now this starts with [12:???
numbers.add(Integer.parseInt(buffer.toString()));
buffer.setLength(0);
current = pathFragment.charAt(++i);
//this is a tail slice [:12]
while (Character.isDigit(current) || current == ' ' || current == '-') {
if (current != ' ') {
buffer.append(current);
}
current = pathFragment.charAt(++i);
}
if (buffer.length() == 0) {
sliceFrom = true;
} else {
sliceBetween = true;
numbers.add(Integer.parseInt(buffer.toString()));
buffer.setLength(0);
}
}
break;
case ',':
numbers.add(Integer.parseInt(buffer.toString()));
buffer.setLength(0);
indexSequence = true;
break;
default:
buffer.append(current);
break;
}
if (current == ']') {
break;
}
current = pathFragment.charAt(++i);
public int nextIndexOf(int startPosition, char c) {
int readPosition = startPosition;
while (!isOutOfBounds(readPosition)) {
if (charAt(readPosition) == c) {
return readPosition;
}
readPosition++;
}
if (buffer.length() > 0) {
numbers.add(Integer.parseInt(buffer.toString()));
return -1;
}
public boolean nextSignificantCharIs(int startPosition, char c) {
int readPosition = startPosition + 1;
while (!isOutOfBounds(readPosition) && charAt(readPosition) == SPACE) {
readPosition++;
}
singleIndex = (numbers.size() == 1) && !sliceTo && !sliceFrom && !contextSize;
if (logger.isTraceEnabled()) {
logger.debug("numbers are : {}", numbers.toString());
logger.debug("sequence is singleNumber : {}", singleIndex);
logger.debug("sequence is numberSequence : {}", indexSequence);
logger.debug("sequence is sliceFrom : {}", sliceFrom);
logger.debug("sequence is sliceTo : {}", sliceTo);
logger.debug("sequence is sliceBetween : {}", sliceBetween);
logger.debug("sequence is contextFetch : {}", contextSize);
logger.debug("---------------------------------------------");
return !isOutOfBounds(readPosition) && charAt(readPosition) == c;
}
public boolean nextSignificantCharIs(char c) {
return nextSignificantCharIs(position, c);
}
public char nextSignificantChar() {
return nextSignificantChar(position);
}
public char nextSignificantChar(int startPosition) {
int readPosition = startPosition + 1;
while (!isOutOfBounds(readPosition) && charAt(readPosition) == SPACE) {
readPosition++;
}
if (!isOutOfBounds(readPosition)) {
return charAt(readPosition);
} else {
return ' ';
}
ArrayPathToken.Operation operation = null;
}
private boolean currentIsTail() {
return isOutOfBounds(position + 1);
}
if (singleIndex) operation = ArrayPathToken.Operation.SINGLE_INDEX;
else if (indexSequence) operation = ArrayPathToken.Operation.INDEX_SEQUENCE;
else if (sliceFrom) operation = ArrayPathToken.Operation.SLICE_FROM;
else if (sliceTo) operation = ArrayPathToken.Operation.SLICE_TO;
else if (sliceBetween) operation = ArrayPathToken.Operation.SLICE_BETWEEN;
else if (contextSize) operation = ArrayPathToken.Operation.CONTEXT_SIZE;
private boolean hasMoreCharacters() {
return inBounds(position + 1);
}
assert operation != null;
private boolean inBounds(int idx) {
return (idx >= 0) && (idx < charSequence.length());
}
return new ArrayPathToken(numbers, operation);
private boolean isOutOfBounds(int idx) {
return !inBounds(idx);
}
private CharSequence subSequence(int start, int end) {
return charSequence.subSequence(start, end);
}
}
}
}

2
json-path/src/main/java/com/jayway/jsonpath/internal/Utils.java

@ -145,7 +145,7 @@ public final class Utils {
}
public static boolean isNumeric(String str) {
if (str == null) {
if (str == null || str.trim().isEmpty()) {
return false;
}
int sz = str.length();

63
json-path/src/main/java/com/jayway/jsonpath/internal/token/ArrayIndexOperation.java

@ -0,0 +1,63 @@
package com.jayway.jsonpath.internal.token;
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.internal.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static java.lang.Character.isDigit;
public class ArrayIndexOperation {
private final List<Integer> indexes;
private ArrayIndexOperation(List<Integer> indexes) {
this.indexes = Collections.unmodifiableList(indexes);
}
public List<Integer> indexes() {
return indexes;
}
public boolean isSingleIndexOperation(){
return indexes.size() == 1;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(Utils.join(",", indexes));
sb.append("]");
return sb.toString();
}
public static ArrayIndexOperation parse(String operation) {
//check valid chars
for (int i = 0; i < operation.length(); i++) {
char c = operation.charAt(i);
if (!isDigit(c) && c != ',') {
throw new InvalidPathException("Failed to parse ArrayIndexOperation: " + operation);
}
}
String[] tokens = operation.split(",");
List<Integer> tempIndexes = new ArrayList<Integer>();
for (String token : tokens) {
tempIndexes.add(parseInteger(token));
}
return new ArrayIndexOperation(tempIndexes);
}
private static Integer parseInteger(String token) {
try {
return Integer.parseInt(token);
} catch (Exception e){
throw new InvalidPathException("Failed to parse token in ArrayIndexOperation: " + token, e);
}
}
}

282
json-path/src/main/java/com/jayway/jsonpath/internal/token/ArrayPathToken.java

@ -17,12 +17,9 @@ package com.jayway.jsonpath.internal.token;
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.internal.PathRef;
import com.jayway.jsonpath.internal.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import static java.lang.String.format;
/**
@ -32,164 +29,161 @@ public class ArrayPathToken extends PathToken {
private static final Logger logger = LoggerFactory.getLogger(ArrayPathToken.class);
public static enum Operation {
CONTEXT_SIZE,
SLICE_TO,
SLICE_FROM,
SLICE_BETWEEN,
INDEX_SEQUENCE,
SINGLE_INDEX;
}
private final ArraySliceOperation arraySliceOperation;
private final ArrayIndexOperation arrayIndexOperation;
private final List<Integer> criteria;
private final Operation operation;
private final boolean isDefinite;
ArrayPathToken(final ArraySliceOperation arraySliceOperation) {
this.arraySliceOperation = arraySliceOperation;
this.arrayIndexOperation = null;
}
public ArrayPathToken(List<Integer> criteria, Operation operation) {
this.criteria = criteria;
this.operation = operation;
this.isDefinite = (Operation.SINGLE_INDEX == operation || Operation.CONTEXT_SIZE == operation);
ArrayPathToken(final ArrayIndexOperation arrayIndexOperation) {
this.arrayIndexOperation = arrayIndexOperation;
this.arraySliceOperation = null;
}
@Override
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
if(model == null){
if (! isUpstreamDefinite()) {
return;
} else {
throw new PathNotFoundException("The path " + currentPath + " is null");
}
if (! checkArrayModel(currentPath, model, ctx))
return;
if(arraySliceOperation != null){
evaluateSliceOperation(currentPath, parent, model, ctx);
} else {
evaluateIndexOperation(currentPath, parent, model, ctx);
}
if (!ctx.jsonProvider().isArray(model)) {
if (! isUpstreamDefinite()) {
return;
} else {
throw new InvalidPathException(format("Filter: %s can only be applied to arrays. Current context is: %s", toString(), model));
}
public void evaluateIndexOperation(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
if (! checkArrayModel(currentPath, model, ctx))
return;
if(arrayIndexOperation.isSingleIndexOperation()){
handleArrayIndex(arrayIndexOperation.indexes().get(0), currentPath, model, ctx);
} else {
for (Integer index : arrayIndexOperation.indexes()) {
handleArrayIndex(index, currentPath, model, ctx);
}
}
}
try {
int idx;
int input;
int length;
int from;
int to;
switch (operation){
case SINGLE_INDEX:
handleArrayIndex(criteria.get(0), currentPath, model, ctx);
break;
case INDEX_SEQUENCE:
for (Integer i : criteria) {
handleArrayIndex(i, currentPath, model, ctx);
}
break;
case CONTEXT_SIZE:
length = ctx.jsonProvider().length(model);
idx = length + criteria.get(0);
handleArrayIndex(idx, currentPath, model, ctx);
break;
case SLICE_FROM: //[2:]
input = criteria.get(0);
length = ctx.jsonProvider().length(model);
from = input;
if (from < 0) {
//calculate slice start from array length
from = length + from;
}
from = Math.max(0, from);
logger.debug("Slice from index on array with length: {}. From index: {} to: {}. Input: {}", length, from, length - 1, toString());
if (length == 0 || from >= length) {
return;
}
for (int i = from; i < length; i++) {
handleArrayIndex(i, currentPath, model, ctx);
}
break;
case SLICE_TO : //[:2]
input = criteria.get(0);
length = ctx.jsonProvider().length(model);
to = input;
if (to < 0) {
//calculate slice end from array length
to = length + to;
}
to = Math.min(length, to);
logger.debug("Slice to index on array with length: {}. From index: 0 to: {}. Input: {}", length, to, toString());
if (length == 0) {
return;
}
for (int i = 0; i < to; i++) {
handleArrayIndex(i, currentPath, model, ctx);
}
break;
case SLICE_BETWEEN : //[2:4]
from = criteria.get(0);
to = criteria.get(1);
length = ctx.jsonProvider().length(model);
to = Math.min(length, to);
if (from >= to || length == 0) {
return;
}
logger.debug("Slice between indexes on array with length: {}. From index: {} to: {}. Input: {}", length, from, to, toString());
for (int i = from; i < to; i++) {
handleArrayIndex(i, currentPath, model, ctx);
}
break;
}
} catch (IndexOutOfBoundsException e) {
if (isUpstreamDefinite()) {
throw new PathNotFoundException("Index out of bounds when evaluating path " + currentPath);
}
public void evaluateSliceOperation(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
if (! checkArrayModel(currentPath, model, ctx))
return;
switch (arraySliceOperation.operation()) {
case SLICE_FROM:
sliceFrom(arraySliceOperation, currentPath, parent, model, ctx);
break;
case SLICE_BETWEEN:
sliceBetween(arraySliceOperation, currentPath, parent, model, ctx);
break;
case SLICE_TO:
sliceTo(arraySliceOperation, currentPath, parent, model, ctx);
break;
}
}
public void sliceFrom(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
int length = ctx.jsonProvider().length(model);
int from = operation.from();
if (from < 0) {
//calculate slice start from array length
from = length + from;
}
from = Math.max(0, from);
logger.debug("Slice from index on array with length: {}. From index: {} to: {}. Input: {}", length, from, length - 1, toString());
if (length == 0 || from >= length) {
return;
}
for (int i = from; i < length; i++) {
handleArrayIndex(i, currentPath, model, ctx);
}
}
public void sliceBetween(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
int length = ctx.jsonProvider().length(model);
int from = operation.from();
int to = operation.to();
to = Math.min(length, to);
if (from >= to || length == 0) {
return;
}
logger.debug("Slice between indexes on array with length: {}. From index: {} to: {}. Input: {}", length, from, to, toString());
for (int i = from; i < to; i++) {
handleArrayIndex(i, currentPath, model, ctx);
}
}
public void sliceTo(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
int length = ctx.jsonProvider().length(model);
if (length == 0) {
return;
}
int to = operation.to();
if (to < 0) {
//calculate slice end from array length
to = length + to;
}
to = Math.min(length, to);
logger.debug("Slice to index on array with length: {}. From index: 0 to: {}. Input: {}", length, to, toString());
for (int i = 0; i < to; i++) {
handleArrayIndex(i, currentPath, model, ctx);
}
}
@Override
public String getPathFragment() {
StringBuilder sb = new StringBuilder();
if (Operation.SINGLE_INDEX == operation || Operation.INDEX_SEQUENCE == operation) {
sb.append("[")
.append(Utils.join(",", "", criteria))
.append("]");
} else if (Operation.CONTEXT_SIZE == operation) {
sb.append("[@.size()")
.append(criteria.get(0))
.append("]");
} else if (Operation.SLICE_FROM == operation) {
sb.append("[")
.append(criteria.get(0))
.append(":]");
} else if (Operation.SLICE_TO == operation) {
sb.append("[:")
.append(criteria.get(0))
.append("]");
} else if (Operation.SLICE_BETWEEN == operation) {
sb.append("[")
.append(criteria.get(0))
.append(":")
.append(criteria.get(1))
.append("]");
} else
sb.append("NOT IMPLEMENTED");
return sb.toString();
if(arrayIndexOperation != null){
return arrayIndexOperation.toString();
} else {
return arraySliceOperation.toString();
}
}
@Override
boolean isTokenDefinite() {
return isDefinite;
public boolean isTokenDefinite() {
if(arrayIndexOperation != null){
return arrayIndexOperation.isSingleIndexOperation();
} else {
return false;
}
}
/**
* Check if model is non-null and array.
* @param currentPath
* @param model
* @param ctx
* @return false if current evaluation call must be skipped, true otherwise
* @throws PathNotFoundException if model is null and evaluation must be interrupted
* @throws InvalidPathException if model is not an array and evaluation must be interrupted
*/
protected boolean checkArrayModel(String currentPath, Object model, EvaluationContextImpl ctx) {
if (model == null){
if (! isUpstreamDefinite()) {
return false;
} else {
throw new PathNotFoundException("The path " + currentPath + " is null");
}
}
if (!ctx.jsonProvider().isArray(model)) {
if (! isUpstreamDefinite()) {
return false;
} else {
throw new InvalidPathException(format("Filter: %s can only be applied to arrays. Current context is: %s", toString(), model));
}
}
return true;
}
}

86
json-path/src/main/java/com/jayway/jsonpath/internal/token/ArraySliceOperation.java

@ -0,0 +1,86 @@
package com.jayway.jsonpath.internal.token;
import com.jayway.jsonpath.InvalidPathException;
import static java.lang.Character.isDigit;
public class ArraySliceOperation {
public enum Operation {
SLICE_FROM,
SLICE_TO,
SLICE_BETWEEN
}
private final Integer from;
private final Integer to;
private final Operation operation;
private ArraySliceOperation(Integer from, Integer to, Operation operation) {
this.from = from;
this.to = to;
this.operation = operation;
}
public Integer from() {
return from;
}
public Integer to() {
return to;
}
public Operation operation() {
return operation;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(from == null ? "" : from.toString());
sb.append(":");
sb.append(to == null ? "" : to.toString());
sb.append("]");
return sb.toString();
}
public static ArraySliceOperation parse(String operation){
//check valid chars
for (int i = 0; i < operation.length(); i++) {
char c = operation.charAt(i);
if( !isDigit(c) && c != '-' && c != ':'){
throw new InvalidPathException("Failed to parse SliceOperation: " + operation);
}
}
String[] tokens = operation.split(":");
Integer tempFrom = tryRead(tokens, 0);
Integer tempTo = tryRead(tokens, 1);
Operation tempOperpation;
if(tempFrom != null && tempTo == null){
tempOperpation = Operation.SLICE_FROM;
} else if(tempFrom != null && tempTo != null){
tempOperpation = Operation.SLICE_BETWEEN;
} else if(tempFrom == null && tempTo != null){
tempOperpation = Operation.SLICE_TO;
} else {
throw new InvalidPathException("Failed to parse SliceOperation: " + operation);
}
return new ArraySliceOperation(tempFrom, tempTo, tempOperpation);
}
private static Integer tryRead(String[] tokens, int idx){
if(tokens.length > idx){
if(tokens[idx].equals("")){
return null;
}
return Integer.parseInt(tokens[idx]);
} else {
return null;
}
}
}

6
json-path/src/main/java/com/jayway/jsonpath/internal/token/PathToken.java

@ -122,7 +122,7 @@ public abstract class PathToken {
}
void handleArrayIndex(int index, String currentPath, Object model, EvaluationContextImpl ctx) {
protected void handleArrayIndex(int index, String currentPath, Object model, EvaluationContextImpl ctx) {
String evalPath = Utils.concat(currentPath, "[", String.valueOf(index), "]");
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, index) : PathRef.NO_OP;
try {
@ -206,8 +206,8 @@ public abstract class PathToken {
public abstract void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx);
abstract boolean isTokenDefinite();
public abstract boolean isTokenDefinite();
abstract String getPathFragment();
protected abstract String getPathFragment();
}

5
json-path/src/main/java/com/jayway/jsonpath/internal/token/PathTokenAppender.java

@ -0,0 +1,5 @@
package com.jayway.jsonpath.internal.token;
public interface PathTokenAppender {
PathTokenAppender appendPathToken(PathToken next);
}

47
json-path/src/main/java/com/jayway/jsonpath/internal/token/PathTokenFactory.java

@ -0,0 +1,47 @@
package com.jayway.jsonpath.internal.token;
import com.jayway.jsonpath.Predicate;
import java.util.Collection;
import java.util.List;
import static java.util.Collections.singletonList;
public class PathTokenFactory {
public static RootPathToken createRootPathToken(char token) {
return new RootPathToken(token);
}
public static PathToken createSinglePropertyPathToken(String property) {
return new PropertyPathToken(singletonList(property));
}
public static PathToken createPropertyPathToken(List<String> properties) {
return new PropertyPathToken(properties);
}
public static PathToken createSliceArrayPathToken(final ArraySliceOperation arraySliceOperation) {
return new ArrayPathToken(arraySliceOperation);
}
public static PathToken createIndexArrayPathToken(final ArrayIndexOperation arrayIndexOperation) {
return new ArrayPathToken(arrayIndexOperation);
}
public static PathToken createWildCardPathToken() {
return new WildcardPathToken();
}
public static PathToken crateScanToken() {
return new ScanPathToken();
}
public static PathToken createPredicatePathToken(Collection<Predicate> predicates) {
return new PredicatePathToken(predicates);
}
public static PathToken createPredicatePathToken(Predicate predicate) {
return new PredicatePathToken(predicate);
}
}

24
json-path/src/main/java/com/jayway/jsonpath/internal/token/PredicatePathToken.java

@ -29,21 +29,14 @@ import static java.util.Arrays.asList;
*/
public class PredicatePathToken extends PathToken {
private static final String[] FRAGMENTS = {
"[?]",
"[?,?]",
"[?,?,?]",
"[?,?,?,?]",
"[?,?,?,?,?]"
};
private final Collection<Predicate> predicates;
public PredicatePathToken(Predicate filter) {
PredicatePathToken(Predicate filter) {
this.predicates = asList(filter);
}
public PredicatePathToken(Collection<Predicate> predicates) {
PredicatePathToken(Collection<Predicate> predicates) {
this.predicates = predicates;
}
@ -88,11 +81,20 @@ public class PredicatePathToken extends PathToken {
@Override
public String getPathFragment() {
return FRAGMENTS[predicates.size() - 1];
StringBuilder sb = new StringBuilder();
sb.append("[");
for(int i = 0; i < predicates.size(); i++){
if(i != 0){
sb.append(",");
}
sb.append("?");
}
sb.append("]");
return sb.toString();
}
@Override
boolean isTokenDefinite() {
public boolean isTokenDefinite() {
return false;
}

4
json-path/src/main/java/com/jayway/jsonpath/internal/token/PropertyPathToken.java

@ -83,7 +83,7 @@ public class PropertyPathToken extends PathToken {
}
@Override
boolean isTokenDefinite() {
public boolean isTokenDefinite() {
// in case of leaf multiprops will be merged, so it's kinda definite
return singlePropertyCase() || multiPropertyMergeCase();
}
@ -92,7 +92,7 @@ public class PropertyPathToken extends PathToken {
public String getPathFragment() {
return new StringBuilder()
.append("[")
.append(Utils.join(", ", "'", properties))
.append(Utils.join(",", "'", properties))
.append("]").toString();
}
}

25
json-path/src/main/java/com/jayway/jsonpath/internal/token/RootPathToken.java

@ -23,8 +23,11 @@ public class RootPathToken extends PathToken {
private PathToken tail;
private int tokenCount;
private final String rootToken;
public RootPathToken() {
RootPathToken(char rootToken) {
this.rootToken = Character.toString(rootToken);;
this.tail = this;
this.tokenCount = 1;
}
@ -40,23 +43,35 @@ public class RootPathToken extends PathToken {
return this;
}
public PathTokenAppender getPathTokenAppender(){
return new PathTokenAppender(){
@Override
public PathTokenAppender appendPathToken(PathToken next) {
append(next);
return this;
}
};
}
@Override
public void evaluate(String currentPath, PathRef pathRef, Object model, EvaluationContextImpl ctx) {
if (isLeaf()) {
PathRef op = ctx.forUpdate() ? pathRef : PathRef.NO_OP;
ctx.addResult("$", op, model);
ctx.addResult(rootToken, op, model);
} else {
next().evaluate("$", pathRef, model, ctx);
next().evaluate(rootToken, pathRef, model, ctx);
}
}
@Override
public String getPathFragment() {
return "$";
return rootToken;
}
@Override
boolean isTokenDefinite() {
public boolean isTokenDefinite() {
return true;
}
}

5
json-path/src/main/java/com/jayway/jsonpath/internal/token/ScanPathToken.java

@ -25,6 +25,9 @@ import java.util.Collection;
*/
public class ScanPathToken extends PathToken {
public ScanPathToken() {
}
@Override
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
@ -99,7 +102,7 @@ public class ScanPathToken extends PathToken {
@Override
boolean isTokenDefinite() {
public boolean isTokenDefinite() {
return false;
}

5
json-path/src/main/java/com/jayway/jsonpath/internal/token/WildcardPathToken.java

@ -25,6 +25,9 @@ import static java.util.Arrays.asList;
*/
public class WildcardPathToken extends PathToken {
public WildcardPathToken() {
}
@Override
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
if (ctx.jsonProvider().isMap(model)) {
@ -46,7 +49,7 @@ public class WildcardPathToken extends PathToken {
@Override
boolean isTokenDefinite() {
public boolean isTokenDefinite() {
return false;
}

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

@ -10,6 +10,9 @@ import java.util.regex.Pattern;
import static com.jayway.jsonpath.Criteria.where;
import static com.jayway.jsonpath.Filter.filter;
import static com.jayway.jsonpath.Filter.parse;
import static java.lang.System.out;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
public class FilterTest extends BaseTest {
@ -252,9 +255,9 @@ public class FilterTest extends BaseTest {
String json = "{\"foo\":" + arr + ", \"bar\":" + nest + "}";
Object tree = Configuration.defaultConfiguration().jsonProvider().parse(json);
Predicate.PredicateContext context = createPredicateContext(tree);
Filter farr = Filter.parse("[?(@.foo == " + arr + ")]");
Filter fobjF = Filter.parse("[?(@.foo == " + nest + ")]");
Filter fobjT = Filter.parse("[?(@.bar == " + nest + ")]");
Filter farr = parse("[?(@.foo == " + arr + ")]");
Filter fobjF = parse("[?(@.foo == " + nest + ")]");
Filter fobjT = parse("[?(@.bar == " + nest + ")]");
assertThat(farr.apply(context)).isEqualTo(true);
assertThat(fobjF.apply(context)).isEqualTo(false);
assertThat(fobjT.apply(context)).isEqualTo(true);
@ -424,45 +427,194 @@ public class FilterTest extends BaseTest {
Filter isBar = filter(where("bar").is(true));
Filter fooOrBar = filter(where("foo").exists(true)).or(where("bar").exists(true));
Filter fooAndBar = filter(where("foo").exists(true)).and(where("bar").exists(true));
Filter fooOrBar = filter(where("foo").is(true)).or(where("bar").is(true));
Filter fooAndBar = filter(where("foo").is(true)).and(where("bar").is(true));
assertThat(isFoo.or(isBar).apply(createPredicateContext(model))).isTrue();
assertThat(isFoo.and(isBar).apply(createPredicateContext(model))).isFalse();
assertThat(fooOrBar.apply(createPredicateContext(model))).isTrue();
assertThat(fooAndBar.apply(createPredicateContext(model))).isFalse();
}
@Test
public void a_filter_can_be_parsed() {
Filter.parse("[?(@.foo)]");
Filter.parse("[?(@.foo == 1)]");
Filter.parse("[?(@.foo == 1 || @['bar'])]");
Filter.parse("[?(@.foo == 1 && @['bar'])]");
parse("[?(@.foo)]");
parse("[?(@.foo == 1)]");
parse("[?(@.foo == 1 || @['bar'])]");
parse("[?(@.foo == 1 && @['bar'])]");
}
@Test
public void an_invalid_filter_can_not_be_parsed() {
try {
Filter.parse("[?(@.foo == 1)");
parse("[?(@.foo == 1)");
Assertions.fail("expected " + InvalidPathException.class.getName());
} catch (InvalidPathException ipe){}
try {
Filter.parse("[?(@.foo == 1) ||]");
parse("[?(@.foo == 1) ||]");
Assertions.fail("expected " + InvalidPathException.class.getName());
} catch (InvalidPathException ipe){}
try {
Filter.parse("[(@.foo == 1)]");
parse("[(@.foo == 1)]");
Assertions.fail("expected " + InvalidPathException.class.getName());
} catch (InvalidPathException ipe){}
try {
Filter.parse("[?@.foo == 1)]");
parse("[?@.foo == 1)]");
Assertions.fail("expected " + InvalidPathException.class.getName());
} catch (InvalidPathException ipe){}
}
@Test
public void a_gte_filter_can_be_serialized() {
System.out.println(filter(where("a").gte(1)).toString());
assertThat(filter(where("a").gte(1)).toString()).isEqualTo(parse("[?(@['a'] >= 1)]").toString());
}
@Test
public void a_lte_filter_can_be_serialized() {
assertThat(filter(where("a").lte(1)).toString()).isEqualTo("[?(@['a'] <= 1)]");
}
@Test
public void a_eq_filter_can_be_serialized() {
assertThat(filter(where("a").eq(1)).toString()).isEqualTo("[?(@['a'] == 1)]");
}
@Test
public void a_ne_filter_can_be_serialized() {
assertThat(filter(where("a").ne(1)).toString()).isEqualTo("[?(@['a'] != 1)]");
}
@Test
public void a_lt_filter_can_be_serialized() {
assertThat(filter(where("a").lt(1)).toString()).isEqualTo("[?(@['a'] < 1)]");
}
@Test
public void a_gt_filter_can_be_serialized() {
assertThat(filter(where("a").gt(1)).toString()).isEqualTo("[?(@['a'] > 1)]");
}
@Test
public void a_regex_filter_can_be_serialized() {
assertThat(filter(where("a").regex(Pattern.compile("/.*?/i"))).toString()).isEqualTo("[?(@['a'] =~ /.*?/i)]");
}
@Test
public void a_nin_filter_can_be_serialized() {
assertThat(filter(where("a").nin(1)).toString()).isEqualTo("[?(@['a'] ¦NIN¦ [1])]");
}
@Test
public void a_in_filter_can_be_serialized() {
assertThat(filter(where("a").in("a")).toString()).isEqualTo("[?(@['a'] ¦IN¦ ['a'])]");
}
@Test
public void a_contains_filter_can_be_serialized() {
assertThat(filter(where("a").contains("a")).toString()).isEqualTo("[?(@['a'] ¦CONTAINS¦ 'a')]");
}
@Test
public void a_all_filter_can_be_serialized() {
assertThat(filter(where("a").all("a", "b")).toString()).isEqualTo("[?(@['a'] ¦ALL¦ ['a','b'])]");
}
@Test
public void a_size_filter_can_be_serialized() {
assertThat(filter(where("a").size(5)).toString()).isEqualTo("[?(@['a'] ¦SIZE¦ 5)]");
}
@Test
public void a_exists_filter_can_be_serialized() {
assertThat(filter(where("a").exists(true)).toString()).isEqualTo("[?(@['a'])]");
}
@Test
public void a_not_exists_filter_can_be_serialized() {
assertThat(filter(where("a").exists(false)).toString()).isEqualTo("[?(!@['a'])]");
}
@Test
public void a_type_filter_can_be_serialized() {
assertThat(filter(where("a").type(String.class)).toString()).isEqualTo("[?(@['a'] ¦TYPE¦ java.lang.String)]");
}
@Test
public void a_matches_filter_can_be_serialized() {
Filter a = filter(where("x").eq(1000));
assertThat(filter(where("a").matches(a)).toString()).isEqualTo("[?(@['a'] ¦MATCHES¦ [?(@['x'] == 1000)])]");
}
@Test
public void a_not_empty_filter_can_be_serialized() {
assertThat(filter(where("a").notEmpty()).toString()).isEqualTo("[?(@['a'] ¦NOT_EMPTY¦)]");
}
@Test
public void and_filter_can_be_serialized() {
assertThat(filter(where("a").eq(1).and("b").eq(2)).toString()).isEqualTo("[?(@['a'] == 1 && @['b'] == 2)]");
}
@Test
public void in_string_filter_can_be_serialized() {
assertThat(filter(where("a").in("1","2")).toString()).isEqualTo("[?(@['a'] ¦IN¦ ['1','2'])]");
}
@Test
public void a_deep_path_filter_can_be_serialized() {
assertThat(filter(where("a.b.c").in("1","2")).toString()).isEqualTo("[?(@['a']['b']['c'] ¦IN¦ ['1','2'])]");
}
@Test
public void a_doc_ref_filter_can_be_serialized() {
assertThat(parse("[?(@.display-price <= $.max-price)]").toString()).isEqualTo("[?(@['display-price'] <= $['max-price'])]");
}
@Test
public void and_combined_filters_can_be_serialized() {
Filter a = filter(where("a").eq(1));
Filter b = filter(where("b").eq(2));
Filter c = a.and(b);
assertThat(c.toString()).isEqualTo("[?(@['a'] == 1 && @['b'] == 2)]");
}
@Test
public void or_combined_filters_can_be_serialized() {
Filter a = filter(where("a").eq(1));
Filter b = filter(where("b").eq(2));
Filter c = a.or(b);
assertThat(c.toString()).isEqualTo("[?(@['a'] == 1 || @['b'] == 2)]");
}
@Test
public void a_____() {
// :2
// 1:2
// -2:
//2:
out.println(asList(":2".split(":"))); //[, 2]
out.println(asList("1:2".split(":"))); //[1, 2]
out.println(asList("-2:".split(":"))); //[-2]
out.println(asList("2:".split(":"))); //[2]
out.println(asList(":2".split(":")).get(0).equals("")); //true
}
}

131
json-path/src/test/java/com/jayway/jsonpath/PathCompilerTest.java

@ -0,0 +1,131 @@
package com.jayway.jsonpath;
import org.junit.Ignore;
import org.junit.Test;
import static com.jayway.jsonpath.internal.PathCompiler.compile;
import static org.assertj.core.api.Assertions.assertThat;
public class PathCompilerTest {
@Ignore("Backward compatibility <= 2.0.0")
@Test(expected = InvalidPathException.class)
public void a_path_must_start_with_$_or_at() {
compile("x");
}
@Ignore("Backward compatibility <= 2.0.0")
@Test(expected = InvalidPathException.class)
public void a_square_bracket_may_not_follow_a_period() {
compile("$.[");
}
@Test(expected = InvalidPathException.class)
public void a_root_path_must_be_followed_by_period_or_bracket() {
compile("$X");
}
@Test
public void a_root_path_can_be_compiled() {
assertThat(compile("$").toString()).isEqualTo("$");
assertThat(compile("@").toString()).isEqualTo("@");
}
@Test(expected = InvalidPathException.class)
public void a_path_may_not_end_with_period() {
assertThat(compile("$.").toString());
assertThat(compile("$.prop.").toString());
}
@Test(expected = InvalidPathException.class)
public void a_path_may_not_end_with_scan() {
assertThat(compile("$..").toString());
assertThat(compile("$.prop..").toString());
}
@Test
public void a_property_token_can_be_compiled() {
assertThat(compile("$.prop").toString()).isEqualTo("$['prop']");
assertThat(compile("$.1prop").toString()).isEqualTo("$['1prop']");
assertThat(compile("$.@prop").toString()).isEqualTo("$['@prop']");
}
@Test
public void a_bracket_notation_property_token_can_be_compiled() {
assertThat(compile("$['prop']").toString()).isEqualTo("$['prop']");
assertThat(compile("$['1prop']").toString()).isEqualTo("$['1prop']");
assertThat(compile("$['@prop']").toString()).isEqualTo("$['@prop']");
assertThat(compile("$[ '@prop' ]").toString()).isEqualTo("$['@prop']");
}
@Test
public void a_multi_property_token_can_be_compiled() {
assertThat(compile("$['prop0', 'prop1']").toString()).isEqualTo("$['prop0','prop1']");
assertThat(compile("$[ 'prop0' , 'prop1' ]").toString()).isEqualTo("$['prop0','prop1']");
}
@Test
public void a_property_chain_can_be_compiled() {
assertThat(compile("$.abc").toString()).isEqualTo("$['abc']");
assertThat(compile("$.aaa.bbb").toString()).isEqualTo("$['aaa']['bbb']");
assertThat(compile("$.aaa.bbb.ccc").toString()).isEqualTo("$['aaa']['bbb']['ccc']");
}
@Test(expected = InvalidPathException.class)
public void a_property_may_not_contain_blanks() {
assertThat(compile("$.foo bar").toString());
}
@Test
public void a_wildcard_can_be_compiled() {
assertThat(compile("$.*").toString()).isEqualTo("$[*]");
assertThat(compile("$[*]").toString()).isEqualTo("$[*]");
assertThat(compile("$[ * ]").toString()).isEqualTo("$[*]");
}
@Test
public void a_wildcard_can_follow_a_property() {
assertThat(compile("$.prop[*]").toString()).isEqualTo("$['prop'][*]");
assertThat(compile("$['prop'][*]").toString()).isEqualTo("$['prop'][*]");
}
@Test
public void an_array_index_path_can_be_compiled() {
assertThat(compile("$[1]").toString()).isEqualTo("$[1]");
assertThat(compile("$[1,2,3]").toString()).isEqualTo("$[1,2,3]");
assertThat(compile("$[ 1 , 2 , 3 ]").toString()).isEqualTo("$[1,2,3]");
}
@Test
public void an_array_slice_path_can_be_compiled() {
assertThat(compile("$[-1:]").toString()).isEqualTo("$[-1:]");
assertThat(compile("$[1:2]").toString()).isEqualTo("$[1:2]");
assertThat(compile("$[:2]").toString()).isEqualTo("$[:2]");
}
@Test
public void an_inline_criteria_can_be_parsed() {
assertThat(compile("$[?(@.foo == 'bar')]").toString()).isEqualTo("$[?]");
}
@Test
public void a_placeholder_criteria_can_be_parsed() {
Predicate p = new Predicate() {
@Override
public boolean apply(PredicateContext ctx) {
return false;
}
};
assertThat(compile("$[?]", p).toString()).isEqualTo("$[?]");
assertThat(compile("$[?,?]", p, p).toString()).isEqualTo("$[?,?]");
assertThat(compile("$[?,?,?]", p, p, p).toString()).isEqualTo("$[?,?,?]");
}
@Test
public void a_scan_token_can_be_parsed() {
assertThat(compile("$..['prop']..[*]").toString()).isEqualTo("$..['prop']..[*]");
}
}

1
json-path/src/test/java/com/jayway/jsonpath/PredicateTest.java

@ -111,7 +111,6 @@ public class PredicateTest extends BaseTest {
assertThat(result).isEmpty();
}
@Ignore("not ready yet (requires compiler reimplementation)")
@Test
public void issue_predicate_can_have_square_bracket_in_prop() {
String json = "{\n"

6
json-path/src/test/java/com/jayway/jsonpath/old/ArraySlicingTest.java

@ -83,12 +83,6 @@ public class ArraySlicingTest {
assertThat(result, Matchers.contains(7, 8, 13, 20));
}
@Test
public void get_from_tail_length(){
Integer result = JsonPath.read(JSON_ARRAY, "$[(@.length -3)]");
assertEquals(8, result.intValue());
}
@Test
public void get_indexes(){
List<Integer> result = JsonPath.read(JSON_ARRAY, "$[0,1,2]");

1
json-path/src/test/java/com/jayway/jsonpath/old/ComplianceTest.java

@ -73,7 +73,6 @@ public class ComplianceTest {
assertThat(JsonPath.<List<Integer>>read(json, "$.points[?(@.id == 'i4')].x"), hasItem(-6));
assertThat(JsonPath.<List<Integer>>read(json, "$.points[*].x"), hasItems(4, -2, 8, -6, 0, 1));
assertThat(JsonPath.<List<String>>read(json, "$.points[?(@.z)].id"), hasItems("i2", "i5"));
assertThat(JsonPath.<String>read(json, "$.points[(@.length - 1)].id"), equalTo("i6"));
}
@Test

62
json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java

@ -2,7 +2,6 @@ package com.jayway.jsonpath.old;
import com.jayway.jsonpath.BaseTest;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.Criteria;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.Filter;
import com.jayway.jsonpath.JsonPath;
@ -15,6 +14,7 @@ import com.jayway.jsonpath.internal.Utils;
import com.jayway.jsonpath.spi.json.GsonJsonProvider;
import com.jayway.jsonpath.spi.json.JsonProvider;
import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
import com.jayway.jsonpath.spi.mapper.MappingException;
import net.minidev.json.JSONAware;
import net.minidev.json.parser.JSONParser;
import org.assertj.core.api.Assertions;
@ -23,9 +23,13 @@ import org.junit.Test;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.jayway.jsonpath.Criteria.PredicateContext;
import static com.jayway.jsonpath.Criteria.where;
import static com.jayway.jsonpath.Filter.filter;
import static com.jayway.jsonpath.JsonPath.read;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
@ -49,8 +53,6 @@ public class IssuesTest extends BaseTest {
assertEquals(1, fullOnes.size());
assertEquals("full", fullOnes.get(0).get("kind"));
}
@Test
@ -171,7 +173,7 @@ public class IssuesTest extends BaseTest {
" }\n" +
"]";
List<String> result = read(json, "$.[?(@.compatible == true)].sku");
List<String> result = read(json, "$[?(@.compatible == true)].sku");
Assertions.assertThat(result).containsExactly("SKU-005", "SKU-003");
}
@ -285,7 +287,7 @@ public class IssuesTest extends BaseTest {
@Test
public void issue_29_b() throws Exception {
String json = "{\"list\": [ { \"a\":\"atext\", \"b\":{ \"b-a\":\"batext\", \"b-b\":\"bbtext\" } }, { \"a\":\"atext2\", \"b\":{ \"b-a\":\"batext2\", \"b-b\":\"bbtext2\" } } ] }";
List<String> result = read(json, "$.list[?]", Filter.filter(Criteria.where("b.b-a").eq("batext2")));
List<String> result = read(json, "$.list[?]", filter(where("b.b-a").eq("batext2")));
assertTrue(result.size() == 1);
}
@ -545,7 +547,7 @@ public class IssuesTest extends BaseTest {
"}";
Filter filter = Filter.filter(Criteria.where("authors[*].lastName").contains("Waugh"));
Filter filter = filter(where("authors[*].lastName").contains("Waugh"));
Object read = JsonPath.parse(json).read("$.store.book[?]", filter);
@ -715,4 +717,52 @@ public class IssuesTest extends BaseTest {
System.out.println(keys);
}
@Test
public void issue_129() throws Exception {
final Map<String, Integer> match = new HashMap<String, Integer>();
match.put("a", 1);
match.put("b", 2);
Map<String, Integer> noMatch = new HashMap<String, Integer>();
noMatch.put("a", -1);
noMatch.put("b", -2);
Filter orig = filter(where("a").eq(1).and("b").eq(2));
String filterAsString = orig.toString();
Filter parsed = Filter.parse(filterAsString);
Assertions.assertThat(orig.apply(createPredicateContext(match))).isTrue();
Assertions.assertThat(parsed.apply(createPredicateContext(match))).isTrue();
Assertions.assertThat(orig.apply(createPredicateContext(noMatch))).isFalse();
Assertions.assertThat(parsed.apply(createPredicateContext(noMatch))).isFalse();
}
private PredicateContext createPredicateContext(final Map<String, Integer> map){
return new PredicateContext() {
@Override
public Object item() {
return map;
}
@Override
public <T> T item(Class<T> clazz) throws MappingException {
return (T)map;
}
@Override
public Object root() {
return map;
}
@Override
public Configuration configuration() {
return Configuration.defaultConfiguration();
}
};
}
}

2
json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java

@ -7,7 +7,6 @@ import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.internal.PathCompiler;
import org.assertj.core.api.Assertions;
import org.junit.Test;
@ -242,7 +241,6 @@ public class JsonPathTest extends BaseTest {
@Test
public void access_array_by_index_from_tail() throws Exception {
assertThat(JsonPath.<List<String>>read(DOCUMENT, "$..book[(@.length-1)].author"), hasItems("J. R. R. Tolkien"));
assertThat(JsonPath.<List<String>>read(DOCUMENT, "$..book[1:].author"), hasItems("Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"));
}

8
json-path/src/test/java/com/jayway/jsonpath/old/internal/ArrayPathTokenTest.java

@ -8,18 +8,10 @@ import java.util.Map;
import static com.jayway.jsonpath.JsonPath.read;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.MapEntry.entry;
public class ArrayPathTokenTest extends TestBase {
@Test
public void array_can_select_single_index_by_context_length() {
Map<String, Object> result = read(ARRAY, "$[(@.length-1)]");
assertThat(result).contains(entry("foo", "foo-val-6"));
}
@Test
public void array_can_select_multiple_indexes() {

11
json-path/src/test/java/com/jayway/jsonpath/old/internal/ScanPathTokenTest.java

@ -200,16 +200,5 @@ public class ScanPathTokenTest {
"$['store']['book'][2]");
}
@Test
public void a_document_can_be_scanned_for_array_indexes() {
List<String> result = PathCompiler.compile("$..[(@.length - 1)]").evaluate(DOCUMENT, DOCUMENT, Configuration.defaultConfiguration()).getPathList();
assertThat(result).containsOnly(
"$['store']['bicycle']['items'][5]",
"$['store']['bicycle']['items'][0][2]",
"$['store']['book'][2]");
}
}

Loading…
Cancel
Save