package org.objectstyle.wolips.eomodeler.core.model.qualifier;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class EOQualifierParser {
private static final int NONE = 0;
private static final int IN_SINGLE_QUOTE = 1;
private static final int IN_DOUBLE_QUOTE = 2;
private static final int ESCAPED = 3;
private static final int IN_NUMBER = 4;
private static final int IN_OPERATOR = 5;
private static final int IN_KEYWORD = 6;
private static final int IN_VARIABLE = 7;
private static final int IN_BINDING_KEY = 8;
private static Set<String> OPERATORS;
private static Set<String> SELECTORS;
private List<Token> _tokens;
private String _qualifierString;
private int _offset;
private int _tokenStartOffset;
private int _length;
private int _tokenNum;
static {
EOQualifierParser.OPERATORS = new HashSet<String>();
EOQualifierParser.OPERATORS.add("=");
EOQualifierParser.OPERATORS.add(">");
EOQualifierParser.OPERATORS.add("<");
EOQualifierParser.OPERATORS.add("==");
EOQualifierParser.OPERATORS.add(">=");
EOQualifierParser.OPERATORS.add("<=");
EOQualifierParser.OPERATORS.add("<>");
EOQualifierParser.OPERATORS.add("contains");
EOQualifierParser.OPERATORS.add("like");
EOQualifierParser.OPERATORS.add("caseinsensitivelike");
EOQualifierParser.SELECTORS = new HashSet<String>();
EOQualifierParser.SELECTORS.add("isEqualTo");
EOQualifierParser.SELECTORS.add("isNotEqualTo");
EOQualifierParser.SELECTORS.add("isLessThan");
EOQualifierParser.SELECTORS.add("isGreaterThan");
EOQualifierParser.SELECTORS.add("isLessThanOrEqualTo");
EOQualifierParser.SELECTORS.add("isGreaterThanOrEqualTo");
EOQualifierParser.SELECTORS.add("doesContain");
EOQualifierParser.SELECTORS.add("isLike");
EOQualifierParser.SELECTORS.add("isCaseInsensitiveLike");
}
protected String caseCorrectedSelectorName(String possibleSelector) {
for (String selector : EOQualifierParser.SELECTORS) {
if (selector.equalsIgnoreCase(possibleSelector)) {
return selector;
}
}
return null;
}
protected String caseCorrectedOperatorName(String possibleOperator) {
for (String operator : EOQualifierParser.OPERATORS) {
if (operator.equalsIgnoreCase(possibleOperator)) {
return operator;
}
}
return null;
}
public synchronized EOQualifier parseQualifier(String qualifierString) throws ParseException {
_tokens = new LinkedList<Token>();
_qualifierString = qualifierString;
_length = _qualifierString.length();
_offset = 0;
_tokenStartOffset = -1;
_tokenNum = 0;
tokenize(false);
EOQualifier qualifier = qualifierForTokens(0, true);
return qualifier;
}
protected EOQualifier qualifierForTokens(int depth, boolean allowAggregateQualifiers) throws ParseException {
EOQualifier lqualifier = null;
Token lvalue = popToken();
if (lvalue instanceof OpenParenToken) {
lqualifier = qualifierForTokens(depth + 1, true);
} else if (lvalue instanceof KeywordToken || lvalue instanceof KeypathToken) {
if ("TRUEPREDICATE".equalsIgnoreCase(lvalue.getValue())) {
lqualifier = new EOTruePredicate();
} else {
Token operator = popToken();
if (operator instanceof OperatorToken || operator instanceof KeywordToken) {
EOQualifier.Comparison selector = new EOQualifier.Comparison(operator.getValue());
Token rvalue = popToken();
if (rvalue instanceof NamedVariableToken) {
lqualifier = new EOKeyValueQualifier(lvalue.getValue(), selector, new EONamedQualifierVariable(rvalue.getValue()));
} else if (rvalue instanceof VariableToken || rvalue instanceof NamedVariableToken) {
lqualifier = new EOKeyValueQualifier(lvalue.getValue(), selector, new EOQualifierVariable(rvalue.getValue()));
} else if (rvalue instanceof NumberToken) {
lqualifier = new EOKeyValueQualifier(lvalue.getValue(), selector, ((NumberToken) rvalue).toNumber());
} else if (rvalue instanceof LiteralToken) {
String value = rvalue.getValue();
if (value != null) {
value = value.replaceAll("\\\\(.)", "$1");
}
lqualifier = new EOKeyValueQualifier(lvalue.getValue(), selector, value);
} else if (rvalue instanceof KeywordToken || rvalue instanceof KeypathToken) {
lqualifier = new EOKeyComparisonQualifier(lvalue.getValue(), selector, rvalue.getValue());
} else if (rvalue == null) {
throw new ParseException("Expected a qualifier after " + lvalue + " " + operator + " at offset " + lvalue.getOffset() + ".", lvalue.getOffset());
} else {
throw new ParseException(lvalue + ", " + operator + ", " + rvalue + " is an invalid token sequence at offset " + lvalue.getOffset() + ".", lvalue.getOffset());
}
} else if (operator == null) {
throw new ParseException("Expected an operator after " + lvalue + " at offset " + lvalue.getOffset() + ".", lvalue.getOffset());
} else {
throw new ParseException(lvalue + ", " + operator + " is an invalid token sequence at offset " + lvalue.getOffset() + ".", lvalue.getOffset());
}
}
} else if (lvalue instanceof NotToken) {
lqualifier = new EONotQualifier(qualifierForTokens(depth + 1, false));
} else if (lvalue != null) {
throw new ParseException("Invalid token " + lvalue + " at offset " + lvalue.getOffset() + ".", lvalue.getOffset());
}
EOQualifier qualifier;
if (lvalue == null) {
qualifier = null;
} else {
Token nextToken = popToken();
if (nextToken == null) {
qualifier = lqualifier;
} else if (nextToken instanceof CloseParenToken) {
if (depth > 0) {
qualifier = lqualifier;
} else {
throw new ParseException("Invalid close paren at offset " + nextToken.getOffset() + ".", nextToken.getOffset());
}
} else if (!allowAggregateQualifiers) {
qualifier = lqualifier;
pushToken(nextToken);
} else if (nextToken instanceof AndToken) {
EOQualifier rqualifier = qualifierForTokens(depth, true);
if (rqualifier == null) {
throw new ParseException("'and' requires a second qualifier at offset " + nextToken.getOffset() + ".", nextToken.getOffset());
}
qualifier = new EOAndQualifier(lqualifier, rqualifier);
} else if (nextToken instanceof OrToken) {
EOQualifier rqualifier = qualifierForTokens(depth, true);
if (rqualifier == null) {
throw new ParseException("'or' requires a second qualifier at offset " + nextToken.getOffset() + ".", nextToken.getOffset());
}
qualifier = new EOOrQualifier(lqualifier, rqualifier);
} else {
throw new ParseException("Illegal token " + nextToken + " at offset " + nextToken.getOffset() + ".", nextToken.getOffset());
}
}
return qualifier;
}
protected void pushToken(Token token) {
_tokens.add(0, token);
//_tokenNum--;
}
protected Token popToken() {
Token token = null;
if (_tokenNum < _tokens.size()) {
token = _tokens.get(_tokenNum++);
}
return token;
}
protected void tokenize(boolean parentInParen) throws ParseException {
int groupStartOffset = _offset;
boolean inParen = parentInParen;
int state = EOQualifierParser.NONE;
_tokenStartOffset = -1;
int previousState = EOQualifierParser.NONE;
while (_offset < _length) {
char ch = _qualifierString.charAt(_offset++);
if (state == EOQualifierParser.ESCAPED) {
state = previousState;
} else if (ch == '\\') {
if (state == EOQualifierParser.IN_DOUBLE_QUOTE || state == EOQualifierParser.IN_SINGLE_QUOTE) {
previousState = state;
state = EOQualifierParser.ESCAPED;
} else {
throw new ParseException("Backslash in invalid state " + state + " at offset " + _offset + ".", _offset);
}
} else if (ch == '"') {
if (state == EOQualifierParser.IN_DOUBLE_QUOTE) {
endPendingToken(state);
state = EOQualifierParser.NONE;
} else if (state == EOQualifierParser.IN_SINGLE_QUOTE) {
// ignore
} else {
endPendingToken(state);
_tokenStartOffset = _offset + 1;
state = EOQualifierParser.IN_DOUBLE_QUOTE;
}
} else if (ch == '\'') {
if (state == EOQualifierParser.IN_SINGLE_QUOTE) {
endPendingToken(state);
state = EOQualifierParser.NONE;
} else if (state == EOQualifierParser.IN_DOUBLE_QUOTE) {
// ignore
} else {
endPendingToken(state);
_tokenStartOffset = _offset + 1;
state = EOQualifierParser.IN_SINGLE_QUOTE;
}
} else if (state != EOQualifierParser.IN_SINGLE_QUOTE && state != EOQualifierParser.IN_DOUBLE_QUOTE) {
if (state == EOQualifierParser.IN_VARIABLE) {
if (ch == 's' || ch == 'd' || ch == 'f' || ch == 'f' || ch == '@' || ch == 'K' || ch == '%') {
_tokens.add(new VariableToken(_offset - 2, "%" + ch));
state = EOQualifierParser.NONE;
_tokenStartOffset = -1;
} else {
throw new ParseException("Unknown variable %" + ch + " at offset " + (_offset - 1) + ".", (_offset - 1));
}
} else if (ch == '%') {
endPendingToken(state);
_tokenStartOffset = _offset;
state = EOQualifierParser.IN_VARIABLE;
} else if (ch == '$') {
endPendingToken(state);
_tokenStartOffset = _offset;
state = EOQualifierParser.IN_BINDING_KEY;
} else if (ch == '(') {
endPendingToken(state);
_tokens.add(new OpenParenToken(_offset - 1));
tokenize(true);
} else if (ch == ')') {
if (!inParen) {
throw new ParseException("Close paren without open paren at offset " + (_offset - 1) + ".", (_offset - 1));
}
endPendingToken(state);
_tokens.add(new CloseParenToken(_offset - 1));
state = EOQualifierParser.NONE;
inParen = false;
break;
} else if (Character.isWhitespace(ch)) {
endPendingToken(state);
state = EOQualifierParser.NONE;
// _tokens.add(new Whitespace(ch));
} else if (ch == '<' || ch == '>' || ch == '=') {
if (state != EOQualifierParser.IN_OPERATOR) {
endPendingToken(state);
_tokenStartOffset = _offset;
state = EOQualifierParser.IN_OPERATOR;
}
} else if (Character.isJavaIdentifierStart(ch)) {
if (state == EOQualifierParser.IN_NUMBER) {
throw new ParseException("Unexpected character " + ch + " at offset " + (_offset - 1) + ".", (_offset - 1));
} else if (state == EOQualifierParser.IN_KEYWORD) {
// IGNORE
} else if (state == EOQualifierParser.IN_BINDING_KEY) {
// IGNORE
} else if (state == EOQualifierParser.IN_OPERATOR) {
endPendingToken(state);
state = EOQualifierParser.IN_KEYWORD;
_tokenStartOffset = _offset;
} else if (state == EOQualifierParser.NONE) {
state = EOQualifierParser.IN_KEYWORD;
_tokenStartOffset = _offset;
} else {
throw new ParseException("Unexpected character " + ch + " at offset " + (_offset - 1) + ".", (_offset - 1));
}
} else if ((Character.isJavaIdentifierPart(ch) || ch == '.') && (state == EOQualifierParser.IN_KEYWORD || state == EOQualifierParser.IN_BINDING_KEY)) {
// OK
} else if (Character.isDigit(ch) || ch == '.' || ch == '-' || ch == ':') {
if (state == EOQualifierParser.IN_NUMBER) {
// IGNORE
} else if (state == EOQualifierParser.IN_KEYWORD) {
// IGNORE
} else if (state == EOQualifierParser.IN_BINDING_KEY) {
// IGNORE
} else if (state == EOQualifierParser.IN_OPERATOR) {
endPendingToken(state);
state = EOQualifierParser.IN_NUMBER;
_tokenStartOffset = _offset;
} else if (state == EOQualifierParser.NONE) {
state = EOQualifierParser.IN_NUMBER;
_tokenStartOffset = _offset;
} else {
throw new ParseException("Unexpected number " + ch + " at offset " + (_offset - 1) + ".", (_offset - 1));
}
} else {
throw new ParseException("Unexpected character " + ch + " at offset " + (_offset - 1) + ".", (_offset - 1));
}
}
}
if (state == EOQualifierParser.IN_SINGLE_QUOTE) {
throw new ParseException("Missing closing ' starting at offset " + _tokenStartOffset + ".", _tokenStartOffset);
} else if (state == EOQualifierParser.IN_DOUBLE_QUOTE) {
throw new ParseException("Missing closing \" starting at offset " + _tokenStartOffset + ".", _tokenStartOffset);
} else if (state == EOQualifierParser.ESCAPED) {
throw new ParseException("Backslash found without escaped character at offset " + (_offset - 1) + ".", (_offset - 1));
} else if (state == EOQualifierParser.IN_VARIABLE) {
throw new ParseException("Percent found without variable character at offset " + (_offset - 1) + ".", (_offset - 1));
} else if (inParen) {
throw new ParseException("Missing closing paren starting at offset " + groupStartOffset + ".", groupStartOffset);
}
if (state == EOQualifierParser.IN_KEYWORD || state == EOQualifierParser.IN_NUMBER || state == EOQualifierParser.IN_BINDING_KEY) {
_offset++;
}
endPendingToken(state);
}
protected void endPendingToken(int state) throws ParseException {
if (_tokenStartOffset != -1) {
int startOffset = _tokenStartOffset - 1;
String value = _qualifierString.substring(startOffset, _offset - 1);
Token token;
if (state == EOQualifierParser.IN_DOUBLE_QUOTE || state == EOQualifierParser.IN_SINGLE_QUOTE) {
token = new LiteralToken(_tokenStartOffset - 1, value);
} else if (state == EOQualifierParser.IN_OPERATOR) {
String operator = caseCorrectedOperatorName(value);
if (operator != null) {
token = new OperatorToken(startOffset, value);
} else {
throw new ParseException("Unknown operator " + value + " at offset " + (_tokenStartOffset - 1) + ".", _tokenStartOffset - 1);
}
} else if (state == EOQualifierParser.IN_NUMBER) {
token = new NumberToken(startOffset, value);
} else if (state == EOQualifierParser.IN_BINDING_KEY) {
if (value == null || value.length() == 0) {
throw new ParseException("A variable has no name at offset " + (_tokenStartOffset - 1) + ".", _tokenStartOffset - 1);
}
token = new NamedVariableToken(startOffset, value.substring(1));
} else if (state == EOQualifierParser.IN_KEYWORD) {
String operator = caseCorrectedOperatorName(value);
String selector = caseCorrectedSelectorName(value);
if (operator != null) {
token = new OperatorToken(startOffset, operator);
} else if (selector != null) {
token = new SelectorToken(startOffset, selector);
} else if (EOQualifierParser.OPERATORS.contains(value.toLowerCase())) {
token = new OperatorToken(startOffset, value);
} else if (value.equalsIgnoreCase("and")) {
token = new AndToken(startOffset);
} else if (value.equalsIgnoreCase("or")) {
token = new OrToken(startOffset);
} else if (value.equalsIgnoreCase("not")) {
token = new NotToken(startOffset);
} else if (value.equalsIgnoreCase("null") || value.equalsIgnoreCase("nil")) {
token = new LiteralToken(startOffset, null);
} else if (value.contains(".")) {
token = new KeypathToken(startOffset, value);
} else {
token = new KeywordToken(startOffset, value);
}
} else {
token = new Token(startOffset, value);
}
_tokens.add(token);
_tokenStartOffset = -1;
}
}
protected static class Token {
private int _offset;
private String _value;
public Token(int offset, String value) {
_offset = offset;
_value = value;
}
public int getOffset() {
return _offset;
}
public String getValue() {
return _value;
}
@Override
public String toString() {
return "[Token: " + _value + "]";
}
}
protected static class AndToken extends Token {
public AndToken(int offset) {
super(offset, "and");
}
@Override
public String toString() {
return "[And]";
}
}
protected static class OrToken extends Token {
public OrToken(int offset) {
super(offset, "or");
}
@Override
public String toString() {
return "[Or]";
}
}
protected static class NotToken extends Token {
public NotToken(int offset) {
super(offset, "not");
}
@Override
public String toString() {
return "[Not]";
}
}
protected static class KeywordToken extends Token {
public KeywordToken(int offset, String value) {
super(offset, value);
}
@Override
public String toString() {
return "[Keyword: " + getValue() + "]";
}
}
protected static class OpenParenToken extends OperatorToken {
public OpenParenToken(int offset) {
super(offset, "(");
}
@Override
public String toString() {
return "[OpenParen]";
}
}
protected static class CloseParenToken extends OperatorToken {
public CloseParenToken(int offset) {
super(offset, ")");
}
@Override
public String toString() {
return "[CloseParen]";
}
}
protected static class SelectorToken extends OperatorToken {
public SelectorToken(int offset, String value) {
super(offset, value);
}
@Override
public String toString() {
return "[Selector: " + getValue() + "]";
}
}
protected static class KeypathToken extends Token {
public KeypathToken(int offset, String value) {
super(offset, value);
}
@Override
public String toString() {
return "[Keypath: " + getValue() + "]";
}
}
protected static class LiteralToken extends Token {
public LiteralToken(int offset, String value) {
super(offset, value);
}
@Override
public String toString() {
return "[Literal: " + getValue() + "]";
}
}
protected static class OperatorToken extends Token {
public OperatorToken(int offset, String value) {
super(offset, value);
}
@Override
public String toString() {
return "[Operator: " + getValue() + "]";
}
}
protected static class NumberToken extends Token {
public NumberToken(int offset, String value) {
super(offset, value);
}
public Number toNumber() {
Number number;
String value = getValue();
if (value.contains(".")) {
number = new BigDecimal(value);
} else {
number = Integer.parseInt(value);
}
return number;
}
@Override
public String toString() {
return "[Number: " + getValue() + "]";
}
}
protected static class VariableToken extends Token {
public VariableToken(int offset, String value) {
super(offset, value);
}
@Override
public String toString() {
return "[Variable: " + getValue() + "]";
}
}
protected static class NamedVariableToken extends Token {
public NamedVariableToken(int offset, String value) {
super(offset, value);
}
@Override
public String toString() {
return "[NamedVariable: " + getValue() + "]";
}
}
public static void main(String[] args) {
try {
EOQualifierParser parser = new EOQualifierParser();
// EOQualifier q = parser.parseQualifier("this is \"mike'\"");
// EOQualifier q = parser.parseQualifier("name = %@ and
// person.firstName == \"mi\\ke\" and age = 5");
// EOQualifier q = parser.parseQualifier("not name = %@ or (age like
// 'Test*') and (age like 'T?est') and person.firstName == \"mi'ke\"
// and (lastName caseinsensitiveLike 'schrag' or (age = 5))
// or(age>10) and (name<0.10) or (somevar isAnagramOf: 'test')");
// System.out.println("EOQualifierParser.main: " + q);
EOQualifier q1 = parser.parseQualifier("a = b and not (status = $notStatus) and voucherID = $voucherID");
System.out.println("EOQualifierParser.main: " + q1);
//EOQualifier q = parser.parseQualifier("status = $status and not (status = $notStatus) and voucherID = $voucherID");
//System.out.println("EOQualifierParser.main: " + q);
} catch (Throwable t) {
t.printStackTrace(System.out);
}
}
}