/**
* Copyright (C) 2010-2011 J.W.Marsden
*
* 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 cc.plural.jsonij.jpath;
import java.io.IOException;
import cc.plural.jsonij.ConstantUtility;
import cc.plural.jsonij.JPath;
import cc.plural.jsonij.ThreadSafeJSONParser;
import cc.plural.jsonij.Value;
import cc.plural.jsonij.jpath.ExpressionPredicate.ExpressionPredicateCombineOperator;
import cc.plural.jsonij.jpath.ExpressionPredicate.ExpressionPredicateOperator;
import cc.plural.jsonij.jpath.ExpressionPredicate.FunctionExpressionPredicateCondition;
import cc.plural.jsonij.jpath.ExpressionPredicate.OperatorExpressionPredicateCondition;
import cc.plural.jsonij.jpath.functions.FunctionArgument;
import cc.plural.jsonij.parser.BaseReaderParser;
import cc.plural.jsonij.parser.ParserException;
import cc.plural.jsonij.parser.ReaderParser;
import static cc.plural.jsonij.Constants.ALL_CHAR;
import static cc.plural.jsonij.Constants.AND;
import static cc.plural.jsonij.Constants.COLON_CHAR;
import static cc.plural.jsonij.Constants.COMMA_CHAR;
import static cc.plural.jsonij.Constants.CURRENT_ELEMENT_CHAR;
import static cc.plural.jsonij.Constants.EQUAL;
import static cc.plural.jsonij.Constants.EXPRESSION_CHAR;
import static cc.plural.jsonij.Constants.GREATER;
import static cc.plural.jsonij.Constants.LAST_CHAR;
import static cc.plural.jsonij.Constants.LAST_PREDICATE;
import static cc.plural.jsonij.Constants.LEFT_PARENTHESIS;
import static cc.plural.jsonij.Constants.LEFT_SQUARE_BRACKET;
import static cc.plural.jsonij.Constants.LESS;
import static cc.plural.jsonij.Constants.MINUS;
import static cc.plural.jsonij.Constants.OR;
import static cc.plural.jsonij.Constants.PERIOD_CHAR;
import static cc.plural.jsonij.Constants.REVERSE_SOLIDUS;
import static cc.plural.jsonij.Constants.RIGHT_PARENTHESIS;
import static cc.plural.jsonij.Constants.RIGHT_SQUARE_BRACKET;
import static cc.plural.jsonij.Constants.SOLIDUS_CHAR;
/**
*
* @author openecho
*/
public class JPathParser {
public JPath<Component> parse(String jPath) throws ParserException {
JPathReader target = new JPathReader(jPath);
return parse(target);
}
public JPath<Component> parse(JPathReader target) throws ParserException {
JPath<Component> path = new JPath<Component>();
while (target.peek() != -1) {
if (target.peek() == SOLIDUS_CHAR) {
target.read();
if(target.peek() == SOLIDUS_CHAR) {
target.read();
path.add(new SearchComponent());
}
} else if (target.peek() == LEFT_SQUARE_BRACKET) {
PredicateComponent predicate = readPredicate(target);
if (predicate != null) {
path.add(predicate);
}
} else {
KeyComponent key = readKey(target);
if (key != null) {
path.add(key);
}
}
}
return path;
}
public KeyComponent readKey(JPathReader target) throws ParserException {
StringBuilder keyBuilder = new StringBuilder();
while (true) {
if (target.peek() == -1) {
break;
} else if (target.peek() == SOLIDUS_CHAR) {
break;
} else if (target.peek() == LEFT_SQUARE_BRACKET) {
break;
} else if (target.peek() == REVERSE_SOLIDUS) {
target.read();
keyBuilder.append((char) target.read());
} else {
keyBuilder.append((char) target.read());
}
}
return new KeyComponent(keyBuilder.toString());
}
public PredicateComponent readPredicate(JPathReader target) throws ParserException {
if (target.peek() == -1) {
return null;
}
if (target.peek() == LEFT_SQUARE_BRACKET) {
target.read();
} else {
throw new JPathParserException("invalidStringExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) '[', (char) target.peek());
}
StringBuilder predicateComponentBuilder = null;
if(target.peek() == ALL_CHAR) {
target.read();
if (target.peek() == RIGHT_SQUARE_BRACKET) {
target.read();
}
return new AllPredicate();
} else if(ConstantUtility.isNumeric(target.peek())) {
/**
* Most likely case, first character is a digit and this is a SimpleIndex.
**/
predicateComponentBuilder = new StringBuilder();
predicateComponentBuilder.append((char) target.read());
while (ConstantUtility.isNumeric(target.peek())) {
predicateComponentBuilder.append((char) target.read());
}
if (target.peek() == RIGHT_SQUARE_BRACKET) {
target.read();
return new SimpleIndexPredicate(Integer.parseInt(predicateComponentBuilder.toString()));
} else if(target.peek() == COMMA_CHAR) {
// Union Predicate
UnionPredicate unionPredicate = new UnionPredicate();
int index = -1;
while(ConstantUtility.isNumeric(target.peek()) || target.peek() == COMMA_CHAR) {
if(target.peek() == COMMA_CHAR) {
index = Integer.parseInt(predicateComponentBuilder.toString());
unionPredicate.addIndex(index);
target.read();
predicateComponentBuilder = new StringBuilder();
} else if(ConstantUtility.isNumeric(target.peek())) {
predicateComponentBuilder.append((char) target.read());
}
}
if(target.peek() == RIGHT_SQUARE_BRACKET) {
index = Integer.parseInt(predicateComponentBuilder.toString());
unionPredicate.addIndex(index);
target.read();
return unionPredicate;
} else {
throw new JPathParserException("invalidStringExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) ']', (char) target.peek());
}
} else if(target.peek() == COLON_CHAR) {
// Start End Step Predicate
int start = Integer.parseInt(predicateComponentBuilder.toString());
int end = -1;
int step = 1;
StartEndStepPredicate startEndStepPredicate = new StartEndStepPredicate();
if(target.peek() == RIGHT_SQUARE_BRACKET) {
int num = Integer.parseInt(predicateComponentBuilder.toString());
target.read();
return startEndStepPredicate;
} else {
throw new JPathParserException("invalidStringExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) ']', (char) target.peek());
}
} else {
throw new JPathParserException("invalidStringExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) ']', (char) target.peek());
}
} else if (target.peek() == LAST_CHAR) {
/**
* Check for $
**/
target.read();
if (target.peek() == RIGHT_SQUARE_BRACKET) {
target.read();
return LastIndexPredicate.LAST;
} else if (target.peek() == MINUS) {
target.read();
if (ConstantUtility.isDigit(target.peek())) {
predicateComponentBuilder = new StringBuilder();
predicateComponentBuilder.append((char) target.read());
while (ConstantUtility.isDigit(target.peek())) {
predicateComponentBuilder.append((char) target.read());
}
if (target.peek() == RIGHT_SQUARE_BRACKET) {
target.read();
return new LastIndexPredicate(Integer.parseInt(predicateComponentBuilder.toString()));
} else {
throw new JPathParserException("invalidStringExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) ']', (char) target.peek());
}
} else {
throw new UnsupportedOperationException("Not a digit in LastIndex.");
}
} else {
throw new JPathParserException("invalidValue", target.getLineNumber(), target.getPositionNumber(), (char) ']', (char) target.peek());
}
} else if (target.peek() == LAST_PREDICATE.charAt(0)) {
/**
* Check for last() predicate string
**/
for (int i = 0; i < LAST_PREDICATE.length(); i++) {
if (target.peek() == LAST_PREDICATE.charAt(i)) {
target.read();
} else {
throw new JPathParserException("invalidValue", target.getLineNumber(), target.getPositionNumber(), LAST_PREDICATE, (char) target.peek());
}
}
if (target.peek() == RIGHT_SQUARE_BRACKET) {
target.read();
return LastIndexPredicate.LAST;
} else if (target.peek() == MINUS) {
target.read();
if (ConstantUtility.isDigit(target.peek())) {
predicateComponentBuilder = new StringBuilder();
predicateComponentBuilder.append((char) target.read());
while (ConstantUtility.isDigit(target.peek())) {
predicateComponentBuilder.append((char) target.read());
}
if (target.peek() == RIGHT_SQUARE_BRACKET) {
target.read();
return new LastIndexPredicate(Integer.parseInt(predicateComponentBuilder.toString()));
} else {
throw new JPathParserException("invalidStringExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) ']', (char) target.peek());
}
} else {
throw new UnsupportedOperationException("Not a digit in LastIndex.");
}
} else {
throw new JPathParserException("invalidPathExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) ']', (char) target.peek());
}
} else if(target.peek() == COLON_CHAR) {
// Start:End:Step
int start = 0;
int end = 0;
int step = 1;
} else if(target.peek() == EXPRESSION_CHAR) {
ExpressionPredicate expressionPredicate = new ExpressionPredicate();
target.read();
if (target.peek() == LEFT_PARENTHESIS) {
target.read();
StringBuilder expressionStringBuilder = new StringBuilder();
char c;
while (target.peek() != RIGHT_PARENTHESIS) {
if(target.peek() == -1) {
throw new JPathParserException("invalidPathFoundEndExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) ')', (char) target.peek());
}
target.skipWhitepace(expressionStringBuilder);
ExpressionPredicateCombineOperator op = null;
if(target.peek() == AND) {
while(target.peek() == AND) {
expressionStringBuilder.append((char) target.read());
}
op = ExpressionPredicateCombineOperator.AND;
} else if(target.peek() == OR) {
while(target.peek() == OR) {
expressionStringBuilder.append((char) target.read());
}
op = ExpressionPredicateCombineOperator.OR;
}
target.skipWhitepace(expressionStringBuilder);
if (target.peek() == CURRENT_ELEMENT_CHAR) {
expressionStringBuilder.append((char) target.read());
if(target.peek() == PERIOD_CHAR) {
expressionStringBuilder.append((char) target.read());
target.skipWhitepace(expressionStringBuilder);
String attribute = null;
String value = null;
predicateComponentBuilder = new StringBuilder();
while(target.peek() != RIGHT_PARENTHESIS && target.peek() != LESS && target.peek() != GREATER && target.peek() != EQUAL) {
c = (char) target.read();
expressionStringBuilder.append(c);
predicateComponentBuilder.append(c);
}
attribute = predicateComponentBuilder.toString().trim();
ExpressionPredicateOperator expressionPredicateOperator = null;
if(target.peek() == RIGHT_PARENTHESIS) {
expressionStringBuilder.append((char) target.read());
expressionPredicateOperator = ExpressionPredicateOperator.NOT_NULL;
} else if(target.peek() == EQUAL) {
expressionStringBuilder.append((char) target.read());
expressionPredicateOperator = ExpressionPredicateOperator.EQUAL;
} else if(target.peek() == LESS) {
expressionStringBuilder.append((char) target.read());
if(target.peek() == EQUAL) {
expressionStringBuilder.append((char) target.read());
expressionPredicateOperator = ExpressionPredicateOperator.LESS_EQUAL;
} else {
expressionPredicateOperator = ExpressionPredicateOperator.LESS;
}
} else if(target.peek() == GREATER) {
expressionStringBuilder.append((char) target.read());
if(target.peek() == EQUAL) {
expressionStringBuilder.append((char) target.read());
expressionPredicateOperator = ExpressionPredicateOperator.GREATER_EQUAL;
} else {
expressionPredicateOperator = ExpressionPredicateOperator.GREATER;
}
}
target.skipWhitepace(expressionStringBuilder);
predicateComponentBuilder = new StringBuilder();
while(target.peek() != RIGHT_PARENTHESIS && target.peek() != AND && target.peek() != OR) {
if(target.peek() == -1) {
throw new JPathParserException("invalidPathFoundEndExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) ')', (char) target.peek());
}
c = (char) target.read();
expressionStringBuilder.append(c);
predicateComponentBuilder.append(c);
}
value = predicateComponentBuilder.toString().trim();
ThreadSafeJSONParser jsonParser = new ThreadSafeJSONParser();
Value jsonValue = jsonParser.parseValue(value);
target.skipWhitepace(expressionStringBuilder);
OperatorExpressionPredicateCondition predicateCondition = new OperatorExpressionPredicateCondition(attribute, expressionPredicateOperator, jsonValue);
if(op != null) {
predicateCondition.setCombine(op);
op = null;
}
expressionPredicate.conditions().add(predicateCondition);
}
} else if(jPathFunctionParseCheck(target.peek())) {
// Read Function Name
StringBuilder functionNameBuilder = new StringBuilder();
while(target.peek() != -1 && target.peek() != '(') {
c = (char) target.read();
expressionStringBuilder.append(c);
functionNameBuilder.append(c);
}
c = (char) target.read();
expressionStringBuilder.append(c);
target.skipWhitepace(expressionStringBuilder);
// Read Function Arguments
StringBuilder argumentsBuilder = new StringBuilder();
while(target.peek() != -1 && target.peek() != ')') {
c = (char) target.read();
expressionStringBuilder.append(c);
argumentsBuilder.append(c);
}
c = (char) target.read();
expressionStringBuilder.append(c);
FunctionArgument[] argumentArray = FunctionArgument.parseStringToArguments(argumentsBuilder.toString().trim());
FunctionExpressionPredicateCondition predicateCondition = new FunctionExpressionPredicateCondition(functionNameBuilder.toString(), argumentArray);
expressionPredicate.conditions().add(predicateCondition);
} else {
throw new JPathParserException("expression1", 0, target.getPositionNumber(), expressionStringBuilder.toString());
}
}
// Read Left Parenthesis
target.read();
expressionPredicate.setExpression(expressionStringBuilder.toString());
if(target.peek() == RIGHT_SQUARE_BRACKET) {
expressionStringBuilder.append((char) target.read());
}
return expressionPredicate;
} else {
throw new JPathParserException("invalidStringExpecting1", target.getLineNumber(), target.getPositionNumber(), (char) '(', (char) target.peek());
}
}
throw new UnsupportedOperationException("Invalid Predicate.");
}
public boolean jPathFunctionParseCheck(int i) {
if(i == 'r') {
return true;
}
return false;
}
public static class JPathReader extends BaseReaderParser implements ReaderParser {
String jPathString;
int index;
public JPathReader(String jpathString) {
this.jPathString = jpathString;
index = 0;
}
public String getJPath() {
return jPathString;
}
@Override
public int readNext() throws ParserException {
if(index < jPathString.length()) {
return jPathString.charAt(index++);
} else {
return -1;
}
}
public void skipWhitepace() throws IOException, ParserException {
while(ConstantUtility.isWhiteSpace(peek())) {
read();
}
}
public void skipWhitepace(StringBuilder appender) throws ParserException {
while(ConstantUtility.isWhiteSpace(peek())) {
appender.append((char) read());
}
}
}
}