Browse Source

PathCompiler simplifications.

pull/139/head
Kalle Stenflo 9 years ago
parent
commit
454b7d49a3
  1. 260
      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. 766
      json-path/src/main/java/com/jayway/jsonpath/internal/PathCompiler.java
  5. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/Utils.java
  6. 63
      json-path/src/main/java/com/jayway/jsonpath/internal/token/ArrayIndexOperation.java
  7. 181
      json-path/src/main/java/com/jayway/jsonpath/internal/token/ArrayPathToken.java
  8. 86
      json-path/src/main/java/com/jayway/jsonpath/internal/token/ArraySliceOperation.java
  9. 6
      json-path/src/main/java/com/jayway/jsonpath/internal/token/PathToken.java
  10. 5
      json-path/src/main/java/com/jayway/jsonpath/internal/token/PathTokenAppender.java
  11. 47
      json-path/src/main/java/com/jayway/jsonpath/internal/token/PathTokenFactory.java
  12. 24
      json-path/src/main/java/com/jayway/jsonpath/internal/token/PredicatePathToken.java
  13. 4
      json-path/src/main/java/com/jayway/jsonpath/internal/token/PropertyPathToken.java
  14. 25
      json-path/src/main/java/com/jayway/jsonpath/internal/token/RootPathToken.java
  15. 5
      json-path/src/main/java/com/jayway/jsonpath/internal/token/ScanPathToken.java
  16. 5
      json-path/src/main/java/com/jayway/jsonpath/internal/token/WildcardPathToken.java
  17. 178
      json-path/src/test/java/com/jayway/jsonpath/FilterTest.java
  18. 131
      json-path/src/test/java/com/jayway/jsonpath/PathCompilerTest.java
  19. 6
      json-path/src/test/java/com/jayway/jsonpath/old/ArraySlicingTest.java
  20. 1
      json-path/src/test/java/com/jayway/jsonpath/old/ComplianceTest.java
  21. 62
      json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java
  22. 2
      json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java
  23. 8
      json-path/src/test/java/com/jayway/jsonpath/old/internal/ArrayPathTokenTest.java
  24. 11
      json-path/src/test/java/com/jayway/jsonpath/old/internal/ScanPathTokenTest.java

260
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.Path;
import com.jayway.jsonpath.internal.PathCompiler; import com.jayway.jsonpath.internal.PathCompiler;
import com.jayway.jsonpath.internal.Utils;
import com.jayway.jsonpath.internal.token.PredicateContextImpl; import com.jayway.jsonpath.internal.token.PredicateContextImpl;
import com.jayway.jsonpath.spi.json.JsonProvider; import com.jayway.jsonpath.spi.json.JsonProvider;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -24,6 +25,7 @@ import org.slf4j.LoggerFactory;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; 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 Logger logger = LoggerFactory.getLogger(Criteria.class);
private static final String CRITERIA_WRAPPER_CHAR = "¦";
private static final String[] OPERATORS = { private static final String[] OPERATORS = {
CriteriaType.EQ.toString(),
CriteriaType.GTE.toString(), CriteriaType.GTE.toString(),
CriteriaType.LTE.toString(), CriteriaType.LTE.toString(),
CriteriaType.EQ.toString(),
CriteriaType.NE.toString(), CriteriaType.NE.toString(),
CriteriaType.LT.toString(), CriteriaType.LT.toString(),
CriteriaType.GT.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 = '\\'; private static final char BS = '\\';
@ -148,6 +162,34 @@ public class Criteria implements Predicate {
return "<="; 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 { IN {
@Override @Override
boolean eval(Object expected, Object model, PredicateContext ctx) { 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); if (logger.isDebugEnabled()) logger.debug("[{}] {} [{}] => {}", model, name(), join(", ", exps), res);
return res; return res;
} }
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
}, },
NIN { NIN {
@Override @Override
@ -171,6 +218,11 @@ public class Criteria implements Predicate {
if (logger.isDebugEnabled()) logger.debug("[{}] {} [{}] => {}", model, name(), join(", ", nexps), res); if (logger.isDebugEnabled()) logger.debug("[{}] {} [{}] => {}", model, name(), join(", ", nexps), res);
return res; return res;
} }
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
}, },
CONTAINS { CONTAINS {
@Override @Override
@ -193,6 +245,11 @@ public class Criteria implements Predicate {
if (logger.isDebugEnabled()) logger.debug("[{}] {} [{}] => {}", model, name(), expected, res); if (logger.isDebugEnabled()) logger.debug("[{}] {} [{}] => {}", model, name(), expected, res);
return res; return res;
} }
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
}, },
ALL { ALL {
@Override @Override
@ -221,6 +278,11 @@ public class Criteria implements Predicate {
} }
return res; return res;
} }
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
}, },
SIZE { SIZE {
@Override @Override
@ -242,6 +304,11 @@ public class Criteria implements Predicate {
} }
return res; return res;
} }
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
}, },
EXISTS { EXISTS {
@Override @Override
@ -249,6 +316,11 @@ public class Criteria implements Predicate {
//This must be handled outside //This must be handled outside
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public String toString() {
return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
}
}, },
TYPE { TYPE {
@Override @Override
@ -258,33 +330,10 @@ public class Criteria implements Predicate {
return actType != null && expType.isAssignableFrom(actType); 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 @Override
public String toString() { public String toString() {
return "=~"; return CRITERIA_WRAPPER_CHAR + name() + CRITERIA_WRAPPER_CHAR;
} }
}, },
MATCHES { MATCHES {
@ -294,6 +343,11 @@ public class Criteria implements Predicate {
Predicate exp = (Predicate) expected; Predicate exp = (Predicate) expected;
return exp.apply(new PredicateContextImpl(model, ctx.root(), ctx.configuration(), pci.documentPathCache())); 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 { NOT_EMPTY {
@Override @Override
@ -312,38 +366,37 @@ public class Criteria implements Predicate {
} }
return res; 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); abstract boolean eval(Object expected, Object model, PredicateContext ctx);
public static CriteriaType parse(String str) { public static CriteriaType parse(String str) {
if ("==".equals(str)) { for (CriteriaType criteriaType : values()) {
return EQ; if (criteriaType.toString().equals(str)) {
} else if (">".equals(str)) { return criteriaType;
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");
} }
throw new UnsupportedOperationException("CriteriaType " + str + " can not be parsed");
} }
} }
private Criteria(List<Criteria> criteriaChain, Object left) { 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.left = left;
this.criteriaChain = criteriaChain; this.criteriaChain = criteriaChain;
this.criteriaChain.add(this); this.criteriaChain.add(this);
@ -746,21 +799,24 @@ public class Criteria implements Predicate {
public static Criteria parse(String criteria) { public static Criteria parse(String criteria) {
int operatorIndex = -1; int operatorIndex = -1;
String left = ""; String left = "";
String operator = ""; CriteriaType operator = null;
String right = ""; String right = "";
//can not iterate values() because the need to be checked in order eg '>=' before '>'
for (int y = 0; y < OPERATORS.length; y++) { for (int y = 0; y < OPERATORS.length; y++) {
operatorIndex = criteria.indexOf(OPERATORS[y]); operatorIndex = criteria.indexOf(OPERATORS[y]);
if (operatorIndex != -1) { if (operatorIndex != -1) {
operator = OPERATORS[y]; operator = CriteriaType.parse(OPERATORS[y]);
break; break;
} }
} }
if (!operator.isEmpty()) { if (operator != null) {
left = criteria.substring(0, operatorIndex).trim(); left = criteria.substring(0, operatorIndex).trim();
right = criteria.substring(operatorIndex + operator.length()).trim(); right = criteria.substring(operatorIndex + operator.toString().length()).trim();
} else { } else {
left = criteria.trim(); left = criteria.trim();
} }
return Criteria.create(left, operator, right); return Criteria.create(left, operator, right);
} }
@ -770,7 +826,10 @@ public class Criteria implements Predicate {
private static class JsonValue { private static class JsonValue {
final String value; final String value;
volatile Object jsonValue; volatile Object jsonValue;
JsonValue(String value) { this.value = value; }
JsonValue(String value) {
this.value = value;
}
Object parsed(PredicateContext ctx) { Object parsed(PredicateContext ctx) {
if (jsonValue == null) { if (jsonValue == null) {
@ -794,7 +853,7 @@ public class Criteria implements Predicate {
* @param right expected value * @param right expected value
* @return a new Criteria * @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 leftPrepared = left;
Object rightPrepared = right; Object rightPrepared = right;
Path leftPath = null; Path leftPath = null;
@ -828,16 +887,18 @@ public class Criteria implements Predicate {
rightPrepared = rightPath; rightPrepared = rightPath;
} else if (isString(right)) { } else if (isString(right)) {
rightPrepared = right.substring(1, right.length() - 1); rightPrepared = right.substring(1, right.length() - 1);
} else if(Utils.isNumeric(right)){
rightPrepared = new BigDecimal(right);
} else if (isJson(right)) { } else if (isJson(right)) {
rightPrepared = new JsonValue(right); rightPrepared = new JsonValue(right);
} else if (isPattern(right)) { } else if (isPattern(right)) {
rightPrepared = compilePattern(right); rightPrepared = compilePattern(right);
} }
if (leftPath != null && operator.isEmpty()) { if (leftPath != null && (operator == null)) {
return Criteria.where(leftPath).exists(existsCheck); return Criteria.where(leftPath).exists(existsCheck);
} else { } else {
return new Criteria(leftPrepared, CriteriaType.parse(operator), rightPrepared); return new Criteria(leftPrepared, operator, rightPrepared);
} }
} }
@ -854,11 +915,21 @@ public class Criteria implements Predicate {
if (c == BS) { if (c == BS) {
char c2 = s.charAt(++i); char c2 = s.charAt(++i);
switch (c2) { switch (c2) {
case 'b': c2 = '\b'; break; case 'b':
case 'f': c2 = '\f'; break; c2 = '\b';
case 'n': c2 = '\n'; break; break;
case 'r': c2 = '\r'; break; case 'f':
case 't': c2 = '\t'; break; c2 = '\f';
break;
case 'n':
c2 = '\n';
break;
case 'r':
c2 = '\r';
break;
case 't':
c2 = '\t';
break;
case 'u': case 'u':
try { try {
String hex = s.substring(i + 1, i + 5); String hex = s.substring(i + 1, i + 5);
@ -897,6 +968,8 @@ public class Criteria implements Predicate {
return expected.compareTo((String) right); return expected.compareTo((String) right);
} else if (left instanceof Number && right instanceof Number) { } else if (left instanceof Number && right instanceof Number) {
return new BigDecimal(left.toString()).compareTo(new BigDecimal(right.toString())); 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) { } else if (left instanceof String && right instanceof Number) {
return new BigDecimal(left.toString()).compareTo(new BigDecimal(right.toString())); return new BigDecimal(left.toString()).compareTo(new BigDecimal(right.toString()));
} else if (left instanceof String && right instanceof Boolean) { } else if (left instanceof String && right instanceof Boolean) {
@ -924,9 +997,42 @@ public class Criteria implements Predicate {
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(left.toString())
.append(criteriaType.toString()) Iterator<Criteria> i = criteriaChain.iterator();
.append(wrapString(right)); 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(); return sb.toString();
} }
@ -935,11 +1041,29 @@ public class Criteria implements Predicate {
return "null"; return "null";
} }
if (o instanceof String) { if (o instanceof String) {
return "'" + o.toString() + "'"; String s = o.toString();
} else { return "'" + s + "'";
return o.toString(); // 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; package com.jayway.jsonpath;
import com.jayway.jsonpath.internal.Utils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.Stack; import java.util.Stack;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -82,7 +82,7 @@ public abstract class Filter implements Predicate {
@Override @Override
public String toString() { public String toString() {
return predicate.toString(); return "[?(" + predicate.toString() + ")]";
} }
} }
@ -116,7 +116,23 @@ public abstract class Filter implements Predicate {
@Override @Override
public String toString() { 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 @Override
public String toString() { 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 * @param provider jsonProvider to use when parsing JSON
* @return a parsing context based on given jsonProvider * @return a parsing context based on given jsonProvider
*/ */
@Deprecated
public static ParseContext using(JsonProvider provider) { public static ParseContext using(JsonProvider provider) {
return new JsonReader(Configuration.builder().jsonProvider(provider).build()); return new JsonReader(Configuration.builder().jsonProvider(provider).build());
} }

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

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

181
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.InvalidPathException;
import com.jayway.jsonpath.PathNotFoundException; import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.internal.PathRef; import com.jayway.jsonpath.internal.PathRef;
import com.jayway.jsonpath.internal.Utils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.List;
import static java.lang.String.format; import static java.lang.String.format;
/** /**
@ -32,23 +29,17 @@ public class ArrayPathToken extends PathToken {
private static final Logger logger = LoggerFactory.getLogger(ArrayPathToken.class); private static final Logger logger = LoggerFactory.getLogger(ArrayPathToken.class);
public static enum Operation { private final ArraySliceOperation arraySliceOperation;
CONTEXT_SIZE, private final ArrayIndexOperation arrayIndexOperation;
SLICE_TO,
SLICE_FROM,
SLICE_BETWEEN,
INDEX_SEQUENCE,
SINGLE_INDEX;
}
private final List<Integer> criteria; ArrayPathToken(final ArraySliceOperation arraySliceOperation) {
private final Operation operation; this.arraySliceOperation = arraySliceOperation;
private final boolean isDefinite; this.arrayIndexOperation = null;
}
public ArrayPathToken(List<Integer> criteria, Operation operation) { ArrayPathToken(final ArrayIndexOperation arrayIndexOperation) {
this.criteria = criteria; this.arrayIndexOperation = arrayIndexOperation;
this.operation = operation; this.arraySliceOperation = null;
this.isDefinite = (Operation.SINGLE_INDEX == operation || Operation.CONTEXT_SIZE == operation);
} }
@Override @Override
@ -59,35 +50,57 @@ public class ArrayPathToken extends PathToken {
if (!ctx.jsonProvider().isArray(model)) { if (!ctx.jsonProvider().isArray(model)) {
throw new InvalidPathException(format("Filter: %s can only be applied to arrays. Current context is: %s", toString(), model)); throw new InvalidPathException(format("Filter: %s can only be applied to arrays. Current context is: %s", toString(), model));
} }
if(arraySliceOperation != null){
evaluateSliceOperation(currentPath, parent, model, ctx);
} else {
evaluateIndexOperation(currentPath, parent, model, ctx);
}
try { }
int idx;
int input;
int length;
int from;
int to;
switch (operation){ public void evaluateIndexOperation(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
case SINGLE_INDEX:
handleArrayIndex(criteria.get(0), currentPath, model, ctx);
break;
case INDEX_SEQUENCE: if (model == null) {
for (Integer i : criteria) { throw new PathNotFoundException("The path " + currentPath + " is null");
handleArrayIndex(i, currentPath, model, ctx); }
if (!ctx.jsonProvider().isArray(model)) {
throw new InvalidPathException(format("Filter: %s can only be applied to arrays. Current context is: %s", toString(), model));
} }
break;
case CONTEXT_SIZE: if(arrayIndexOperation.isSingleIndexOperation()){
length = ctx.jsonProvider().length(model); handleArrayIndex(arrayIndexOperation.indexes().get(0), currentPath, model, ctx);
idx = length + criteria.get(0); } else {
handleArrayIndex(idx, currentPath, model, ctx); for (Integer index : arrayIndexOperation.indexes()) {
handleArrayIndex(index, currentPath, model, ctx);
}
}
}
public void evaluateSliceOperation(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
if (model == null) {
throw new PathNotFoundException("The path " + currentPath + " is null");
}
if (!ctx.jsonProvider().isArray(model)) {
throw new InvalidPathException(format("Filter: %s can only be applied to arrays. Current context is: %s", toString(), model));
}
switch (arraySliceOperation.operation()) {
case SLICE_FROM:
sliceFrom(arraySliceOperation, currentPath, parent, model, ctx);
break;
case SLICE_BETWEEN:
sliceBetween(arraySliceOperation, currentPath, parent, model, ctx);
break; break;
case SLICE_TO:
sliceTo(arraySliceOperation, currentPath, parent, model, ctx);
break;
}
}
case SLICE_FROM: //[2:] public void sliceFrom(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
input = criteria.get(0); int length = ctx.jsonProvider().length(model);
length = ctx.jsonProvider().length(model); int from = operation.from();
from = input;
if (from < 0) { if (from < 0) {
//calculate slice start from array length //calculate slice start from array length
from = length + from; from = length + from;
@ -102,32 +115,12 @@ public class ArrayPathToken extends PathToken {
for (int i = from; i < length; i++) { for (int i = from; i < length; i++) {
handleArrayIndex(i, currentPath, model, ctx); 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] public void sliceBetween(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
from = criteria.get(0); int length = ctx.jsonProvider().length(model);
to = criteria.get(1); int from = operation.from();
length = ctx.jsonProvider().length(model); int to = operation.to();
to = Math.min(length, to); to = Math.min(length, to);
@ -140,46 +133,42 @@ public class ArrayPathToken extends PathToken {
for (int i = from; i < to; i++) { for (int i = from; i < to; i++) {
handleArrayIndex(i, currentPath, model, ctx); handleArrayIndex(i, currentPath, model, ctx);
} }
break;
} }
} catch (IndexOutOfBoundsException e) {
throw new PathNotFoundException("Index out of bounds when evaluating path " + currentPath); 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 @Override
public String getPathFragment() { public String getPathFragment() {
StringBuilder sb = new StringBuilder(); if(arrayIndexOperation != null){
if (Operation.SINGLE_INDEX == operation || Operation.INDEX_SEQUENCE == operation) { return arrayIndexOperation.toString();
sb.append("[") } else {
.append(Utils.join(",", "", criteria)) return arraySliceOperation.toString();
.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();
} }
@Override @Override
boolean isTokenDefinite() { public boolean isTokenDefinite() {
return isDefinite; if(arrayIndexOperation != null){
return arrayIndexOperation.isSingleIndexOperation();
} else {
return false;
}
} }
} }

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

@ -115,7 +115,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), "]"); String evalPath = Utils.concat(currentPath, "[", String.valueOf(index), "]");
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, index) : PathRef.NO_OP; PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, index) : PathRef.NO_OP;
try { try {
@ -204,8 +204,8 @@ public abstract class PathToken {
public abstract void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx); 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 { public class PredicatePathToken extends PathToken {
private static final String[] FRAGMENTS = {
"[?]",
"[?,?]",
"[?,?,?]",
"[?,?,?,?]",
"[?,?,?,?,?]"
};
private final Collection<Predicate> predicates; private final Collection<Predicate> predicates;
public PredicatePathToken(Predicate filter) { PredicatePathToken(Predicate filter) {
this.predicates = asList(filter); this.predicates = asList(filter);
} }
public PredicatePathToken(Collection<Predicate> predicates) { PredicatePathToken(Collection<Predicate> predicates) {
this.predicates = predicates; this.predicates = predicates;
} }
@ -86,11 +79,20 @@ public class PredicatePathToken extends PathToken {
@Override @Override
public String getPathFragment() { 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 @Override
boolean isTokenDefinite() { public boolean isTokenDefinite() {
return false; return false;
} }

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

@ -23,7 +23,7 @@ import java.util.List;
/** /**
* *
*/ */
public class PropertyPathToken extends PathToken { class PropertyPathToken extends PathToken {
private final List<String> properties; private final List<String> properties;
@ -45,7 +45,7 @@ public class PropertyPathToken extends PathToken {
} }
@Override @Override
boolean isTokenDefinite() { public boolean isTokenDefinite() {
return true; return true;
} }

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

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

@ -24,6 +24,9 @@ import java.util.Collection;
*/ */
public class ScanPathToken extends PathToken { public class ScanPathToken extends PathToken {
ScanPathToken() {
}
@Override @Override
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
@ -98,7 +101,7 @@ public class ScanPathToken extends PathToken {
@Override @Override
boolean isTokenDefinite() { public boolean isTokenDefinite() {
return false; 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 class WildcardPathToken extends PathToken {
WildcardPathToken() {
}
@Override @Override
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
if (ctx.jsonProvider().isMap(model)) { if (ctx.jsonProvider().isMap(model)) {
@ -46,7 +49,7 @@ public class WildcardPathToken extends PathToken {
@Override @Override
boolean isTokenDefinite() { public boolean isTokenDefinite() {
return false; 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.Criteria.where;
import static com.jayway.jsonpath.Filter.filter; 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; import static org.assertj.core.api.Assertions.assertThat;
public class FilterTest extends BaseTest { public class FilterTest extends BaseTest {
@ -252,9 +255,9 @@ public class FilterTest extends BaseTest {
String json = "{\"foo\":" + arr + ", \"bar\":" + nest + "}"; String json = "{\"foo\":" + arr + ", \"bar\":" + nest + "}";
Object tree = Configuration.defaultConfiguration().jsonProvider().parse(json); Object tree = Configuration.defaultConfiguration().jsonProvider().parse(json);
Predicate.PredicateContext context = createPredicateContext(tree); Predicate.PredicateContext context = createPredicateContext(tree);
Filter farr = Filter.parse("[?(@.foo == " + arr + ")]"); Filter farr = parse("[?(@.foo == " + arr + ")]");
Filter fobjF = Filter.parse("[?(@.foo == " + nest + ")]"); Filter fobjF = parse("[?(@.foo == " + nest + ")]");
Filter fobjT = Filter.parse("[?(@.bar == " + nest + ")]"); Filter fobjT = parse("[?(@.bar == " + nest + ")]");
assertThat(farr.apply(context)).isEqualTo(true); assertThat(farr.apply(context)).isEqualTo(true);
assertThat(fobjF.apply(context)).isEqualTo(false); assertThat(fobjF.apply(context)).isEqualTo(false);
assertThat(fobjT.apply(context)).isEqualTo(true); assertThat(fobjT.apply(context)).isEqualTo(true);
@ -424,45 +427,194 @@ public class FilterTest extends BaseTest {
Filter isBar = filter(where("bar").is(true)); Filter isBar = filter(where("bar").is(true));
Filter fooOrBar = filter(where("foo").exists(true)).or(where("bar").exists(true)); Filter fooOrBar = filter(where("foo").is(true)).or(where("bar").is(true));
Filter fooAndBar = filter(where("foo").exists(true)).and(where("bar").exists(true)); Filter fooAndBar = filter(where("foo").is(true)).and(where("bar").is(true));
assertThat(isFoo.or(isBar).apply(createPredicateContext(model))).isTrue(); assertThat(isFoo.or(isBar).apply(createPredicateContext(model))).isTrue();
assertThat(isFoo.and(isBar).apply(createPredicateContext(model))).isFalse(); assertThat(isFoo.and(isBar).apply(createPredicateContext(model))).isFalse();
assertThat(fooOrBar.apply(createPredicateContext(model))).isTrue();
assertThat(fooAndBar.apply(createPredicateContext(model))).isFalse();
} }
@Test @Test
public void a_filter_can_be_parsed() { public void a_filter_can_be_parsed() {
Filter.parse("[?(@.foo)]"); parse("[?(@.foo)]");
Filter.parse("[?(@.foo == 1)]"); parse("[?(@.foo == 1)]");
Filter.parse("[?(@.foo == 1 || @['bar'])]"); parse("[?(@.foo == 1 || @['bar'])]");
Filter.parse("[?(@.foo == 1 && @['bar'])]"); parse("[?(@.foo == 1 && @['bar'])]");
} }
@Test @Test
public void an_invalid_filter_can_not_be_parsed() { public void an_invalid_filter_can_not_be_parsed() {
try { try {
Filter.parse("[?(@.foo == 1)"); parse("[?(@.foo == 1)");
Assertions.fail("expected " + InvalidPathException.class.getName()); Assertions.fail("expected " + InvalidPathException.class.getName());
} catch (InvalidPathException ipe){} } catch (InvalidPathException ipe){}
try { try {
Filter.parse("[?(@.foo == 1) ||]"); parse("[?(@.foo == 1) ||]");
Assertions.fail("expected " + InvalidPathException.class.getName()); Assertions.fail("expected " + InvalidPathException.class.getName());
} catch (InvalidPathException ipe){} } catch (InvalidPathException ipe){}
try { try {
Filter.parse("[(@.foo == 1)]"); parse("[(@.foo == 1)]");
Assertions.fail("expected " + InvalidPathException.class.getName()); Assertions.fail("expected " + InvalidPathException.class.getName());
} catch (InvalidPathException ipe){} } catch (InvalidPathException ipe){}
try { try {
Filter.parse("[?@.foo == 1)]"); parse("[?@.foo == 1)]");
Assertions.fail("expected " + InvalidPathException.class.getName()); Assertions.fail("expected " + InvalidPathException.class.getName());
} catch (InvalidPathException ipe){} } 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']..[*]");
}
}

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)); 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 @Test
public void get_indexes(){ public void get_indexes(){
List<Integer> result = JsonPath.read(JSON_ARRAY, "$[0,1,2]"); 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[?(@.id == 'i4')].x"), hasItem(-6));
assertThat(JsonPath.<List<Integer>>read(json, "$.points[*].x"), hasItems(4, -2, 8, -6, 0, 1)); 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.<List<String>>read(json, "$.points[?(@.z)].id"), hasItems("i2", "i5"));
assertThat(JsonPath.<String>read(json, "$.points[(@.length - 1)].id"), equalTo("i6"));
} }
@Test @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.BaseTest;
import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.Criteria;
import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.Filter; import com.jayway.jsonpath.Filter;
import com.jayway.jsonpath.JsonPath; 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.GsonJsonProvider;
import com.jayway.jsonpath.spi.json.JsonProvider; import com.jayway.jsonpath.spi.json.JsonProvider;
import com.jayway.jsonpath.spi.mapper.GsonMappingProvider; import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
import com.jayway.jsonpath.spi.mapper.MappingException;
import net.minidev.json.JSONAware; import net.minidev.json.JSONAware;
import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.JSONParser;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
@ -23,9 +23,13 @@ import org.junit.Test;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; 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 com.jayway.jsonpath.JsonPath.read;
import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
@ -49,8 +53,6 @@ public class IssuesTest extends BaseTest {
assertEquals(1, fullOnes.size()); assertEquals(1, fullOnes.size());
assertEquals("full", fullOnes.get(0).get("kind")); assertEquals("full", fullOnes.get(0).get("kind"));
} }
@Test @Test
@ -171,7 +173,7 @@ public class IssuesTest extends BaseTest {
" }\n" + " }\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"); Assertions.assertThat(result).containsExactly("SKU-005", "SKU-003");
} }
@ -285,7 +287,7 @@ public class IssuesTest extends BaseTest {
@Test @Test
public void issue_29_b() throws Exception { 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\" } } ] }"; 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); 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); Object read = JsonPath.parse(json).read("$.store.book[?]", filter);
@ -715,4 +717,52 @@ public class IssuesTest extends BaseTest {
System.out.println(keys); 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.Option;
import com.jayway.jsonpath.PathNotFoundException; import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.internal.PathCompiler; import com.jayway.jsonpath.internal.PathCompiler;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.junit.Test; import org.junit.Test;
@ -242,7 +241,6 @@ public class JsonPathTest extends BaseTest {
@Test @Test
public void access_array_by_index_from_tail() throws Exception { 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")); 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 com.jayway.jsonpath.JsonPath.read;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.MapEntry.entry;
public class ArrayPathTokenTest extends TestBase { 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 @Test
public void array_can_select_multiple_indexes() { 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]"); "$['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