Alexey Makeyev
9 years ago
26 changed files with 1511 additions and 767 deletions
@ -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); |
||||
} |
||||
|
||||
//
|
||||
//---------------------------------------------
|
||||
static class PathComponentAnalyzer { |
||||
// [1], [1,2, n], [1:], [1:2], [:2]
|
||||
//
|
||||
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; |
||||
} |
||||
|
||||
int expressionBeginIndex = path.position + 1; |
||||
int expressionEndIndex = path.nextIndexOf(expressionBeginIndex, CLOSE_SQUARE_BRACKET); |
||||
|
||||
if (expressionEndIndex == -1) { |
||||
return false; |
||||
} |
||||
|
||||
private final LinkedList<Predicate> filterList; |
||||
private final String pathFragment; |
||||
String expression = path.subSequence(expressionBeginIndex, expressionEndIndex).toString().replace(" ", ""); |
||||
|
||||
PathComponentAnalyzer(String pathFragment, LinkedList<Predicate> filterList) { |
||||
this.pathFragment = pathFragment; |
||||
this.filterList = filterList; |
||||
if ("*".equals(expression)) { |
||||
return false; |
||||
} |
||||
|
||||
static PathToken analyze(String pathFragment, LinkedList<Predicate> filterList) { |
||||
return new PathComponentAnalyzer(pathFragment, filterList).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; |
||||
} |
||||
} |
||||
|
||||
boolean isSliceOperation = expression.contains(":"); |
||||
|
||||
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; |
||||
} |
||||
|
||||
public PathToken analyze() { |
||||
List<String> properties = new ArrayList<String>(); |
||||
|
||||
int startPosition = path.position + 1; |
||||
int readPosition = startPosition; |
||||
int endPosition = 0; |
||||
boolean inProperty = 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()); |
||||
while (path.inBounds(readPosition)) { |
||||
char c = path.charAt(readPosition); |
||||
|
||||
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 (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); |
||||
|
||||
} while (i < pathFragment.length()); |
||||
appender.appendPathToken(PathTokenFactory.createPropertyPathToken(properties)); |
||||
|
||||
throw new InvalidPathException("Could not analyze path component: " + pathFragment); |
||||
return path.currentIsTail() || readNextToken(appender); |
||||
} |
||||
|
||||
//
|
||||
// ..
|
||||
//
|
||||
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); |
||||
} |
||||
|
||||
//"['foo']"
|
||||
private PathToken analyzeProperty() { |
||||
List<String> properties = new ArrayList<String>(); |
||||
StringBuilder buffer = new StringBuilder(); |
||||
private boolean currentCharIs(char c) { |
||||
return (charSequence.charAt(position) == c); |
||||
} |
||||
|
||||
boolean propertyIsOpen = false; |
||||
private boolean nextCharIs(char c) { |
||||
return inBounds(position + 1) && (charSequence.charAt(position + 1) == c); |
||||
} |
||||
|
||||
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 incrementPosition(int charCount) { |
||||
return setPosition(position + charCount); |
||||
} |
||||
|
||||
private int setPosition(int newPosition) { |
||||
position = min(newPosition, charSequence.length() - 1); |
||||
return position; |
||||
} |
||||
|
||||
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++; |
||||
} |
||||
ArrayPathToken.Operation operation = null; |
||||
if (!isOutOfBounds(readPosition)) { |
||||
return charAt(readPosition); |
||||
} else { |
||||
return ' '; |
||||
} |
||||
} |
||||
|
||||
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 currentIsTail() { |
||||
return isOutOfBounds(position + 1); |
||||
} |
||||
|
||||
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); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
} |
@ -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); |
||||
} |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,5 @@
|
||||
package com.jayway.jsonpath.internal.token; |
||||
|
||||
public interface PathTokenAppender { |
||||
PathTokenAppender appendPathToken(PathToken next); |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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']..[*]"); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue