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; |
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; |
||||||
|
|
||||||
|
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 { |
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("..")){ |
||||||
LinkedList<Predicate> filterList = new LinkedList<Predicate>(asList(filters)); |
fail("Path must not end wid a scan operation '..'"); |
||||||
|
|
||||||
if (trimmedPath.charAt(0) != '$' && trimmedPath.charAt(0) != '@') { |
|
||||||
trimmedPath = Utils.concat("$.", trimmedPath); |
|
||||||
} |
} |
||||||
|
LinkedList filterStack = new LinkedList<Predicate>(asList(filters)); |
||||||
boolean isRootPath = (trimmedPath.charAt(0) == '$'); |
String cacheKey = Utils.concat(path, filterStack.toString()); |
||||||
|
Path p = cache.get(cacheKey); |
||||||
if (trimmedPath.charAt(0) == '@') { |
if (p == null) { |
||||||
trimmedPath = Utils.concat("$", trimmedPath.substring(1)); |
p = new PathCompiler(path.trim(), filterStack).compile(); |
||||||
|
cache.put(cacheKey, p); |
||||||
} |
} |
||||||
|
return p; |
||||||
if (trimmedPath.length() > 1 && |
} catch (Exception e) { |
||||||
trimmedPath.charAt(1) != '.' && |
InvalidPathException ipe; |
||||||
trimmedPath.charAt(1) != '[') { |
if (e instanceof InvalidPathException) { |
||||||
throw new InvalidPathException("Invalid path " + trimmedPath); |
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); |
private RootPathToken readContextToken() { |
||||||
if (p != null) { |
|
||||||
if (logger.isDebugEnabled()) logger.debug("Using cached path: {}", cacheKey); |
|
||||||
return p; |
|
||||||
} |
|
||||||
|
|
||||||
RootPathToken root = null; |
if (!path.currentCharIs(DOC_CONTEXT) && !path.currentCharIs(EVAL_CONTEXT)) { |
||||||
|
throw new InvalidPathException("Path must start with '$' or '@'"); |
||||||
|
} |
||||||
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; |
|
||||||
|
|
||||||
} else if (positions == 1 && trimmedPath.charAt(i) == '*') { |
RootPathToken pathToken = PathTokenFactory.createRootPathToken(path.currentChar()); |
||||||
fragment = new String("[*]"); |
PathTokenAppender appender = pathToken.getPathTokenAppender(); |
||||||
} else { |
|
||||||
assertValidFieldChars(trimmedPath, i, positions); |
|
||||||
|
|
||||||
fragment = Utils.concat(PROPERTY_OPEN, trimmedPath.substring(i, i + positions), PROPERTY_CLOSE); |
if (path.currentIsTail()) { |
||||||
} |
return pathToken; |
||||||
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)); |
|
||||||
} |
|
||||||
|
|
||||||
} 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) { |
private boolean readDotSeparatorToken(PathTokenAppender appender) { |
||||||
char c = s.charAt(i); |
if (!path.currentCharIs('.') || path.nextCharIs('.')) { |
||||||
|
return false; |
||||||
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@\\$_\\-]*)$"); |
if (!path.hasMoreCharacters()) { |
||||||
} |
throw new InvalidPathException("Path must not end with a '."); |
||||||
i++; |
|
||||||
} |
} |
||||||
*/ |
// 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; |
// fooBar
|
||||||
while (index < s.length()) { |
//
|
||||||
char current = s.charAt(index); |
private boolean readPropertyToken(PathTokenAppender appender) { |
||||||
if (current == PERIOD || current == BRACKET_OPEN || current == SPACE) { |
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; |
break; |
||||||
} |
} |
||||||
index++; |
readPosition++; |
||||||
skipCount++; |
} |
||||||
|
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
|
if (!path.currentCharIs(OPEN_SQUARE_BRACKET)) { |
||||||
index++; |
return false; |
||||||
skipCount++; |
} |
||||||
|
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()) { |
int expressionBeginIndex = path.position + 1; |
||||||
char current = s.charAt(index); |
int expressionEndIndex = path.nextIndexOf(expressionBeginIndex, CLOSE_SQUARE_BRACKET); |
||||||
|
|
||||||
index++; |
if (expressionEndIndex == -1) { |
||||||
skipCount++; |
return false; |
||||||
|
} |
||||||
|
|
||||||
if (current == BRACKET_CLOSE && nestedBrackets == 0) { |
String expression = path.subSequence(expressionBeginIndex, expressionEndIndex).toString(); |
||||||
break; |
|
||||||
} |
String[] tokens = expression.split(","); |
||||||
if (current == BRACKET_OPEN) { |
|
||||||
nestedBrackets++; |
if (filterStack.size() < tokens.length) { |
||||||
} |
throw new InvalidPathException("Not enough predicates supplied for filter [" + expression + "] at position " + path.position); |
||||||
if (current == BRACKET_CLOSE) { |
} |
||||||
nestedBrackets--; |
|
||||||
|
Collection<Predicate> predicates = new ArrayList<Predicate>(); |
||||||
|
for (String token : tokens) { |
||||||
|
token = token != null ? token.trim() : token; |
||||||
|
if (!"?".equals(token == null ? "" : token)) { |
||||||
|
throw new InvalidPathException("Expected '?' but found " + token); |
||||||
} |
} |
||||||
|
predicates.add(filterStack.pop()); |
||||||
} |
} |
||||||
return skipCount; |
|
||||||
|
appender.appendPathToken(PathTokenFactory.createPredicatePathToken(predicates)); |
||||||
|
|
||||||
|
path.setPosition(expressionEndIndex + 1); |
||||||
|
|
||||||
|
return path.currentIsTail() || readNextToken(appender); |
||||||
} |
} |
||||||
|
|
||||||
|
//
|
||||||
|
// [?(...)]
|
||||||
|
//
|
||||||
|
private boolean readFilterToken(PathTokenAppender appender) { |
||||||
|
if (!path.currentCharIs(OPEN_SQUARE_BRACKET) && !path.nextSignificantCharIs(QUESTIONMARK)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
int openStatementBracketIndex = path.position; |
||||||
|
int questionMarkIndex = path.indexOfNextSignificantChar(QUESTIONMARK); |
||||||
|
if (questionMarkIndex == -1) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
int openBracketIndex = path.indexOfNextSignificantChar(questionMarkIndex, OPEN_BRACKET); |
||||||
|
if (openBracketIndex == -1) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
int closeBracketIndex = path.indexOfClosingBracket(openBracketIndex + 1, true); |
||||||
|
if (closeBracketIndex == -1) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (!path.nextSignificantCharIs(closeBracketIndex, CLOSE_SQUARE_BRACKET)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
int closeStatementBracketIndex = path.indexOfNextSignificantChar(closeBracketIndex, CLOSE_SQUARE_BRACKET); |
||||||
|
|
||||||
|
String criteria = path.subSequence(openStatementBracketIndex, closeStatementBracketIndex + 1).toString(); |
||||||
|
|
||||||
|
appender.appendPathToken(PathTokenFactory.createPredicatePathToken(Filter.parse(criteria))); |
||||||
|
|
||||||
|
path.setPosition(closeStatementBracketIndex + 1); |
||||||
|
|
||||||
|
return path.currentIsTail() || readNextToken(appender); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
//---------------------------------------------
|
|
||||||
//
|
//
|
||||||
|
// [*]
|
||||||
|
// *
|
||||||
//
|
//
|
||||||
|
private boolean readWildCardToken(PathTokenAppender appender) { |
||||||
|
|
||||||
|
boolean inBracket = path.currentCharIs(OPEN_SQUARE_BRACKET); |
||||||
|
|
||||||
|
if (inBracket && !path.nextSignificantCharIs(WILDCARD)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (!path.currentCharIs(WILDCARD) && path.isOutOfBounds(path.position + 1)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (inBracket) { |
||||||
|
int wildCardIndex = path.indexOfNextSignificantChar(WILDCARD); |
||||||
|
if (!path.nextSignificantCharIs(wildCardIndex, CLOSE_SQUARE_BRACKET)) { |
||||||
|
throw new InvalidPathException("Expected wildcard token to end with ']' on position " + wildCardIndex + 1); |
||||||
|
} |
||||||
|
int bracketCloseIndex = path.indexOfNextSignificantChar(wildCardIndex, CLOSE_SQUARE_BRACKET); |
||||||
|
path.setPosition(bracketCloseIndex + 1); |
||||||
|
} else { |
||||||
|
path.incrementPosition(1); |
||||||
|
} |
||||||
|
|
||||||
|
appender.appendPathToken(PathTokenFactory.createWildCardPathToken()); |
||||||
|
|
||||||
|
return path.currentIsTail() || readNextToken(appender); |
||||||
|
} |
||||||
|
|
||||||
//
|
//
|
||||||
//---------------------------------------------
|
// [1], [1,2, n], [1:], [1:2], [:2]
|
||||||
static class PathComponentAnalyzer { |
//
|
||||||
|
private boolean readArrayToken(PathTokenAppender appender) { |
||||||
|
|
||||||
private static final Pattern FILTER_PATTERN = Pattern.compile("^\\[\\s*\\?\\s*[,\\s*\\?]*?\\s*]$"); //[?] or [?, ?, ...]
|
if (!path.currentCharIs(OPEN_SQUARE_BRACKET)) { |
||||||
private int i; |
return false; |
||||||
private char current; |
} |
||||||
|
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; |
String expression = path.subSequence(expressionBeginIndex, expressionEndIndex).toString().replace(" ", ""); |
||||||
private final String pathFragment; |
|
||||||
|
|
||||||
PathComponentAnalyzer(String pathFragment, LinkedList<Predicate> filterList) { |
if ("*".equals(expression)) { |
||||||
this.pathFragment = pathFragment; |
return false; |
||||||
this.filterList = filterList; |
|
||||||
} |
} |
||||||
|
|
||||||
static PathToken analyze(String pathFragment, LinkedList<Predicate> filterList) { |
//check valid chars
|
||||||
return new PathComponentAnalyzer(pathFragment, filterList).analyze(); |
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(); |
while (path.inBounds(readPosition)) { |
||||||
else if ("..".equals(pathFragment)) return new ScanPathToken(); |
char c = path.charAt(readPosition); |
||||||
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 (c == CLOSE_SQUARE_BRACKET) { |
||||||
final int criteriaCount = Utils.countMatches(pathFragment, "?"); |
if (inProperty) { |
||||||
List<Predicate> filters = new ArrayList<Predicate>(criteriaCount); |
throw new InvalidPathException("Expected property to be closed at position " + readPosition); |
||||||
for (int i = 0; i < criteriaCount; i++) { |
} |
||||||
filters.add(filterList.poll()); |
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; |
int endBracketIndex = path.indexOfNextSignificantChar(endPosition, CLOSE_SQUARE_BRACKET) + 1; |
||||||
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; |
|
||||||
} |
|
||||||
|
|
||||||
|
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) != '['){ |
private static class CharacterIndex { |
||||||
start--; |
|
||||||
} |
|
||||||
|
|
||||||
int mem = -1; |
private final CharSequence charSequence; |
||||||
int curr = start; |
private int position; |
||||||
boolean inProp = false; |
|
||||||
int openSquareBracket = 0; |
private CharacterIndex(CharSequence charSequence) { |
||||||
int openBrackets = 0; |
this.charSequence = charSequence; |
||||||
while(end == 0){ |
this.position = 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 int length() { |
||||||
|
return charSequence.length(); |
||||||
|
} |
||||||
|
|
||||||
|
private char charAt(int idx) { |
||||||
|
return charSequence.charAt(idx); |
||||||
|
} |
||||||
|
|
||||||
|
private char currentChar() { |
||||||
|
return charSequence.charAt(position); |
||||||
|
} |
||||||
|
|
||||||
//"['foo']"
|
private boolean currentCharIs(char c) { |
||||||
private PathToken analyzeProperty() { |
return (charSequence.charAt(position) == c); |
||||||
List<String> properties = new ArrayList<String>(); |
} |
||||||
StringBuilder buffer = new StringBuilder(); |
|
||||||
|
|
||||||
boolean propertyIsOpen = false; |
private boolean nextCharIs(char c) { |
||||||
|
return inBounds(position + 1) && (charSequence.charAt(position + 1) == c); |
||||||
|
} |
||||||
|
|
||||||
while (current != ']') { |
private int incrementPosition(int charCount) { |
||||||
switch (current) { |
return setPosition(position + charCount); |
||||||
case '\'': |
} |
||||||
if (propertyIsOpen) { |
|
||||||
properties.add(buffer.toString()); |
private int setPosition(int newPosition) { |
||||||
buffer.setLength(0); |
position = min(newPosition, charSequence.length() - 1); |
||||||
propertyIsOpen = false; |
return position; |
||||||
} else { |
} |
||||||
propertyIsOpen = true; |
|
||||||
} |
private int indexOfClosingBracket(int startPosition, boolean skipStrings) { |
||||||
break; |
int opened = 1; |
||||||
default: |
int readPosition = startPosition; |
||||||
if (propertyIsOpen) { |
while (inBounds(readPosition)) { |
||||||
buffer.append(current); |
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(); |
if (charAt(readPosition) == OPEN_BRACKET) { |
||||||
buffer.setLength(0); |
opened++; |
||||||
if (!function.equals("size") && !function.equals("length")) { |
|
||||||
throw new InvalidPathException("Invalid function: @." + function + ". Supported functions are: [(@.length - n)] and [(@.size() - n)]"); |
|
||||||
} |
} |
||||||
while (current != ')') { |
if (charAt(readPosition) == CLOSE_BRACKET) { |
||||||
if (current == ' ') { |
opened--; |
||||||
current = pathFragment.charAt(++i); |
if(opened == 0){ |
||||||
continue; |
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 == '-') { |
public int nextIndexOf(int startPosition, char c) { |
||||||
|
int readPosition = startPosition; |
||||||
switch (current) { |
while (!isOutOfBounds(readPosition)) { |
||||||
case ' ': |
if (charAt(readPosition) == c) { |
||||||
break; |
return readPosition; |
||||||
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); |
|
||||||
} |
} |
||||||
|
readPosition++; |
||||||
} |
} |
||||||
if (buffer.length() > 0) { |
return -1; |
||||||
numbers.add(Integer.parseInt(buffer.toString())); |
} |
||||||
|
|
||||||
|
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; |
return !isOutOfBounds(readPosition) && charAt(readPosition) == c; |
||||||
|
} |
||||||
if (logger.isTraceEnabled()) { |
|
||||||
logger.debug("numbers are : {}", numbers.toString()); |
public boolean nextSignificantCharIs(char c) { |
||||||
logger.debug("sequence is singleNumber : {}", singleIndex); |
return nextSignificantCharIs(position, c); |
||||||
logger.debug("sequence is numberSequence : {}", indexSequence); |
} |
||||||
logger.debug("sequence is sliceFrom : {}", sliceFrom); |
|
||||||
logger.debug("sequence is sliceTo : {}", sliceTo); |
public char nextSignificantChar() { |
||||||
logger.debug("sequence is sliceBetween : {}", sliceBetween); |
return nextSignificantChar(position); |
||||||
logger.debug("sequence is contextFetch : {}", contextSize); |
} |
||||||
logger.debug("---------------------------------------------"); |
|
||||||
|
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; |
private boolean currentIsTail() { |
||||||
else if (indexSequence) operation = ArrayPathToken.Operation.INDEX_SEQUENCE; |
return isOutOfBounds(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; |
private boolean hasMoreCharacters() { |
||||||
else if (contextSize) operation = ArrayPathToken.Operation.CONTEXT_SIZE; |
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