/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
* Free SoftwareFoundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.jms.selector;
import com.caucho.util.CharBuffer;
import com.caucho.util.IntMap;
import com.caucho.util.L10N;
import javax.jms.InvalidSelectorException;
import javax.jms.JMSException;
import java.util.Locale;
import java.util.logging.Logger;
/**
* Parsing the selector.
*/
public class SelectorParser {
static final Logger log
= Logger.getLogger(SelectorParser.class.getName());
static final L10N L = new L10N(SelectorParser.class);
static final int TRUE = 1;
static final int FALSE = TRUE + 1;
static final int NULL = FALSE + 1;
static final int INTEGER = NULL + 1;
static final int DOUBLE = INTEGER + 1;
static final int LONG = DOUBLE + 1;
static final int STRING = LONG + 1;
static final int IDENTIFIER = STRING + 1;
static final int EQ = IDENTIFIER + 1;
static final int NE = EQ + 1;
static final int LT = NE + 1;
static final int LE = LT + 1;
static final int GT = LE + 1;
static final int GE = GT + 1;
static final int NOT = GE + 1;
static final int AND = NOT + 1;
static final int OR = AND + 1;
static final int BETWEEN = OR + 1;
static final int LIKE = BETWEEN + 1;
static final int ESCAPE = LIKE + 1;
static final int IN = ESCAPE + 1;
static final int IS = IN + 1;
private static IntMap _reserved;
private String _query;
private int _parseIndex;
private int _token = -1;
private String _lexeme;
private CharBuffer _cb = new CharBuffer();
public Selector parse(String query)
throws JMSException
{
_query = query;
_parseIndex = 0;
if (peekToken() == -1)
return null;
Selector selector = parseExpr();
if (! selector.isUnknown() && ! selector.isBoolean())
throw new InvalidSelectorException(L.l("selector '{0}' must be a boolean",
selector));
return selector;
}
private Selector parseExpr()
throws JMSException
{
return parseOr();
}
private Selector parseOr()
throws JMSException
{
Selector left = parseAnd();
while (true) {
int token = peekToken();
switch (token) {
case OR:
scanToken();
left = new OrSelector(left, parseAnd());
break;
default:
return left;
}
}
}
private Selector parseAnd()
throws JMSException
{
Selector left = parseCmp();
while (true) {
int token = peekToken();
switch (token) {
case AND:
scanToken();
left = new BooleanBinarySelector(token, left, parseCmp());
break;
default:
return left;
}
}
}
/**
* Parses a comparison expression.
*
* <pre>
* cmp-expr ::= add-expr '=' add-expr
* ::= add-expr
* </pre>
*
* @return the parsed expression
*/
private Selector parseCmp()
throws JMSException
{
int token = peekToken();
boolean isNot = false;
if (token == NOT) {
scanToken();
isNot = true;
token = peekToken();
}
Selector left = parseAdd();
token = peekToken();
if (token == NOT) {
isNot = ! isNot;
scanToken();
token = peekToken();
}
if (token >= EQ && token <= GE) {
scanToken();
left = new BooleanBinarySelector(token, left, parseAdd());
}
else if (token == BETWEEN) {
scanToken();
Selector low = parseAdd();
token = scanToken();
if (token != AND)
throw error("BETWEEN needs AND");
Selector high = parseAdd();
left = new BetweenSelector(left, low, high);
}
else if (token == LIKE) {
scanToken();
token = scanToken();
if (token != STRING)
throw error("LIKE needs string pattern");
String pattern = _lexeme;
char escape = '\\';
if (peekToken() == ESCAPE) {
scanToken();
token = scanToken();
if (token != STRING)
throw error("ESCAPE needs string pattern");
if (_lexeme.length() > 0)
escape = _lexeme.charAt(0);
}
left = new LikeSelector(left, pattern, escape);
}
else if (token == IN) {
scanToken();
InSelector inSelector = new InSelector(left);
if (scanToken() != '(')
throw error("IN needs `('");
while ((token = scanToken()) == STRING) {
inSelector.addValue(_lexeme);
if (peekToken() == ',')
scanToken();
}
if (token != ')')
throw error("IN needs `)'");
scanToken();
left = inSelector;
}
else if (token == IS) {
scanToken();
if (peekToken() == NOT) {
isNot = ! isNot;
scanToken();
}
if ((token = scanToken()) != NULL)
throw error("IS needs NULL");
left = new UnarySelector(NULL, left);
}
if (isNot)
return new UnarySelector(NOT, left);
else
return left;
}
private Selector parseAdd()
throws JMSException
{
Selector left = parseMul();
while (true) {
int token = peekToken();
switch (token) {
case '+':
case '-':
scanToken();
left = new NumericBinarySelector(token, left, parseMul());
break;
default:
return left;
}
}
}
private Selector parseMul()
throws JMSException
{
Selector left = parseUnary();
while (true) {
int token = peekToken();
switch (token) {
case '*':
case '/':
scanToken();
left = new NumericBinarySelector(token, left, parseUnary());
break;
default:
return left;
}
}
}
private Selector parseUnary()
throws JMSException
{
int token = peekToken();
switch (token) {
case '+':
scanToken();
return new UnarySelector(token, parseUnary());
case '-':
scanToken();
// jms/21ei
if (peekToken() != '-' && peekToken() != '+')
return parseTerm(true);
else
return new UnarySelector('-', parseUnary());
default:
return parseTerm(false);
}
}
private Selector parseTerm(boolean hasSign)
throws JMSException
{
Selector value = null;
String prefix;
int token = scanToken();
switch (token) {
case TRUE:
value = new BooleanLiteralSelector(true);
break;
case FALSE:
value = new BooleanLiteralSelector(false);
break;
case IDENTIFIER:
value = IdentifierSelector.create(_lexeme);
break;
case STRING:
value = new LiteralSelector(_lexeme);
break;
case INTEGER:
if (hasSign)
return new LiteralSelector(Long.decode("-" + _lexeme));
else
return new LiteralSelector(Long.decode(_lexeme));
case LONG:
if (hasSign)
return new LiteralSelector(Long.decode("-" + _lexeme));
else
return new LiteralSelector(Long.decode(_lexeme));
case DOUBLE:
if (hasSign)
return new LiteralSelector(new Double("-" + _lexeme));
else
return new LiteralSelector(new Double(_lexeme));
case '(':
value = parseExpr();
if (scanToken() != ')')
throw error("expected ')'");
break;
default:
throw error("unknown token: " + token);
}
if (hasSign)
return new UnarySelector('-', value);
else
return value;
}
/**
* Peeks the next token
*
* @return integer code for the token
*/
private int peekToken()
throws JMSException
{
if (_token > 0)
return _token;
_token = scanToken();
return _token;
}
/**
* Scan the next token. If the lexeme is a string, its string
* representation is in "lexeme".
*
* @return integer code for the token
*/
private int scanToken()
throws JMSException
{
if (_token > 0) {
int value = _token;
_token = -1;
return value;
}
int sign = 1;
int ch;
for (ch = read(); Character.isWhitespace((char) ch); ch = read()) {
}
switch (ch) {
case -1:
case '(':
case ')':
case '*':
case '/':
case '+':
case '-':
case ',':
return ch;
case '=':
return EQ;
case '<':
if ((ch = read()) == '=')
return LE;
else if (ch == '>')
return NE;
else {
unread(ch);
return LT;
}
case '>':
if ((ch = read()) == '=')
return GE;
else {
unread(ch);
return GT;
}
}
if (Character.isJavaIdentifierStart((char) ch)) {
_cb.clear();
for (; ch > 0 && Character.isJavaIdentifierPart((char) ch); ch = read())
_cb.append((char) ch);
unread(ch);
_lexeme = _cb.toString();
String lower = _lexeme.toLowerCase(Locale.ENGLISH);
int token = _reserved.get(lower);
if (token > 0)
return token;
else
return IDENTIFIER;
}
else if (ch >= '0' && ch <= '9' || ch == '.') {
_cb.clear();
int type = INTEGER;
if (sign < 0)
_cb.append('-');
for (; '0' <= ch && ch <= '9'; ch = read())
_cb.append((char) ch);
if ((ch == 'x' || ch == 'X')
&& _cb.length() == 1 && _cb.charAt(0) == '0') {
_cb.append('x');
for (ch = read();
'0' <= ch && ch <= '9'
|| 'a' <= ch && ch <= 'f'
|| 'A' <= ch && ch <= 'F';
ch = read()) {
_cb.append((char) ch);
}
_lexeme = _cb.toString();
if (ch == 'l' || ch == 'L')
return LONG;
else {
unread(ch);
return INTEGER;
}
}
if (ch == '.') {
type = DOUBLE;
_cb.append('.');
for (ch = read(); ch >= '0' && ch <= '9'; ch = read())
_cb.append((char) ch);
}
if (ch == 'e' || ch == 'E') {
type = DOUBLE;
_cb.append('e');
if ((ch = read()) == '+' || ch == '-') {
_cb.append((char) ch);
ch = read();
}
if (! (ch >= '0' && ch <= '9'))
throw error(L.l("exponent needs digits at {0}",
charName(ch)));
for (; ch >= '0' && ch <= '9'; ch = read())
_cb.append((char) ch);
}
if (ch == 'F' || ch == 'D' || ch == 'f' || ch == 'd')
type = DOUBLE;
else if (ch == 'L' || ch == 'l') {
type = LONG;
}
else
unread(ch);
_lexeme = _cb.toString();
return type;
}
// else if (ch == '\'' || ch == '\"') {
else if (ch == '\'') {
int end = ch;
_cb.clear();
for (ch = read(); ch >= 0; ch = read()) {
if (ch == end) {
int ch1;
if ((ch1 = read()) == end)
_cb.append((char) end);
else {
unread(ch1);
break;
}
}
else
_cb.append((char) ch);
}
if (ch < 0)
throw error(L.l("unexpected end of selector"));
_lexeme = _cb.toString();
return STRING;
}
throw error(L.l("unexpected char at {0}", "" + (char) ch));
}
/**
* Returns the next character.
*/
private int read()
{
if (_parseIndex < _query.length())
return _query.charAt(_parseIndex++);
else
return -1;
}
/**
* Unread the last character.
*/
private void unread(int ch)
{
if (ch >= 0)
_parseIndex--;
}
/**
* Creates an error.
*/
public JMSException error(String msg)
{
msg += "\nin \"" + _query + "\"";
return new InvalidSelectorException(msg);
}
/**
* Returns the name for a character
*/
private String charName(int ch)
{
if (ch < 0)
return L.l("end of query");
else
return String.valueOf((char) ch);
}
static {
_reserved = new IntMap();
_reserved.put("true", TRUE);
_reserved.put("false", FALSE);
_reserved.put("and", AND);
_reserved.put("or", OR);
_reserved.put("not", NOT);
_reserved.put("null", NULL);
_reserved.put("is", IS);
_reserved.put("in", IN);
_reserved.put("like", LIKE);
_reserved.put("escape", ESCAPE);
_reserved.put("between", BETWEEN);
}
}