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); } } }