/*
* @(#)JMSSelector.java 1.28 00/11/16
*
* Copyright 1999-2000 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the proprietary information of Sun Microsystems, Inc.
* Use is subject to license terms.
*
*/
package flex.messaging.services.messaging.selector;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.StringTokenizer;
import flex.messaging.MessageException;
import flex.messaging.log.LogCategories;
import flex.messaging.messages.Message;
/**
* Provides JMS selector capability to a MessageConsumer.
*
* Uses a SQL parser written in SQLParser.jj
*
* @author Farrukh Najmi
* @exclude
*/
public class JMSSelector
{
public static final String LOG_CATEGORY = LogCategories.MESSAGE_SELECTOR; // Because we're not always JMS-specific.
private static final int PARSE_FAILURE = 10600;
private static final int BAD_TYPE_COMPARISON = 10601;
private static final int PARSER_ERROR = 10602;
static final boolean debug = false;
SQLParser parser;
String pattern;
Message msg;
/**
* Class Constructor.
*
* @param pattern
*/
public JMSSelector(String pattern)
{
if (pattern == null)
{
pattern = "";
}
//reason for passing the msg is to prevent dependency on com.sun.jms.*
this.msg = null;
this.pattern = pattern;
InputStream stream = new ByteArrayInputStream(pattern.getBytes());
parser = new SQLParser(this, stream);
}
/**
* Returns the pattern used by this selector.
*
*/
public String getPattern()
{
return pattern;
}
/**
* Sets the pattern that will be used by this selector to perform matches
* Must conform to SQL-92 specification for a SQL pattern.
*/
public void setPattern(String p)
{
pattern = p;
InputStream stream = new ByteArrayInputStream(pattern.getBytes());
parser.ReInit(stream);
try
{
match(msg);
}
catch (MessageException me)
{
throw me;
}
catch (Throwable t)
{
throw new MessageException(t);
}
}
/**
* Matches the message against the selector expression.
*
* @param msg The message to match against.
* @return true if the message headers match the selector; otherwise false.
* @exception JMSSelectorException
*/
public boolean match(Message msg)
{
boolean matched = false;
if (pattern.equals(""))
{
matched = true; // No selector
}
else
{
try
{
matched = parser.match(msg);
}
catch (ParseException pex)
{
JMSSelectorException jmse = new JMSSelectorException();
jmse.setMessage(PARSE_FAILURE, new Object[] {pattern, pex.getMessage()});
throw jmse;
}
catch (ClassCastException cce)
{
JMSSelectorException jmse = new JMSSelectorException();
jmse.setMessage(BAD_TYPE_COMPARISON, new Object[] {pattern});
throw jmse;
}
catch (Error err)
{
JMSSelectorException jmse = new JMSSelectorException();
jmse.setMessage(PARSER_ERROR, new Object[] {pattern, err.getMessage()});
throw jmse;
}
finally
{
InputStream stream = new ByteArrayInputStream(pattern.getBytes());
parser.ReInit(stream);
}
}
return matched;
}
/**
* Used to determine selector match in a SQL LIKE expression. For example:
* <pre>
* str LIKE patternStr [ESCAPE escapeChar]
* </pre>
*
* @param patternStr The pattern used in SQL LIKE statement
* @param str The string being compared with patternStr in SQL LIKE statement
* @param escapeChar The escape character used to treat wildcards '_' and '%' as normal
*
* @return <code>true</code> if the string matches the pattern; otherwise false.
*/
boolean matchPattern(String patternStr, String str, char escapeChar)
{
boolean matched = false;
String escapeCharStr = String.valueOf(escapeChar);
String wildCards = "_%";
String delims = wildCards + escapeCharStr;
boolean escaped = false;
int index = 0;
String tok = null;
try
{
if (debug)
{
System.err.println("JMSSelector:matchPattern patternStr = \'" + patternStr + "\' str = \'" + str + "\'");
}
if (str != null)
{
StringTokenizer st = new StringTokenizer(patternStr, delims, true);
// Parse string into a Collection of tokens since we will need
// to peek forward as we
// scan tokens
ArrayList tokens = new ArrayList();
int k = 1;
while (st.hasMoreTokens())
{
tok = st.nextToken();
if (debug)
{
System.err.println(k++ + " matchPattern Token=" + tok);
}
tokens.add(tok);
}
matched = true;
// Iterate over tokens list and match each token with str
int numTokens = tokens.size();
for (int i = 0; i < numTokens; i++)
{
tok = (String)tokens.get(i);
// Token can be a delimeter or actual token
if (tok.equals(escapeCharStr) && (!escaped))
{
// Remember that the next character in patterStr must be
// treated literally
escaped = true;
}
else if (tok.equals("%") && (!escaped))
{
if (i == (numTokens - 1))
{
// wildcard is last character in pattern,
// match entire string.
index = str.length();
}
else if (i != numTokens - 1)
{ // There are more tokens. If not then we have a
// match
// Now scan forward
int _cnt = 0; // count of '_' delimeters
// encountered
++i;
for (; i < numTokens; i++)
{
tok = (String)tokens.get(i);
if (tok.equals(escapeCharStr) && (!escaped))
{
// Remember that the next character in
// patterStr must be treated literally
escaped = true;
}
else if (tok.equals("%") && (!escaped))
{
// % followed by % is same as %
}
else if (tok.equals("_") && (!escaped))
{
++_cnt;
}
else
{
// This is the nextNonDelimTok
int oldIndex = index;
if (i == (numTokens - 1))
{
// Not a general purpose fix for
// wildcard matching bug.
// At least handle case when
// only one wildcard in pattern
// that has a group of characters
// trailing it.
if (str.endsWith(tok))
{
index = str.length() - tok.length();
}
else
{
matched = false;
if (debug)
{
System.err.println("no matched5 for token: '" + tok + "'");
}
}
}
else
{
index = str.indexOf(tok, index);
}
if (index < 0)
{
matched = false;
if (debug)
{
System.err.println("no matched1 for token: '" + tok + "'");
}
}
else
{
// Make sure that we have _cnt
// charecters between old index and new
// index
if (index - oldIndex >= _cnt)
{
index += tok.length();
if (debug)
{
System.err.println("matched1: " + str.substring(0, index));
}
}
else
{
matched = false;
if (debug)
{
System.err.println("no matched 2 for token: '" + tok + "'");
}
}
}
escaped = false;
break;
}
}
}
}
else if (tok.equals("_") && (!escaped))
{
index++;
if (debug)
{
System.err.println("matched2: " + str.substring(0, index));
}
}
else
{
// Compare token read with corresponding string
int tokLen = tok.length();
if (debug)
{
System.err.println(index + " " + tokLen);
}
if (index + tokLen <= str.length())
{
String subStr = null;
try
{
subStr = str.substring(index, index + tokLen);
}
catch (StringIndexOutOfBoundsException e)
{
matched = false;
break;
}
if (!subStr.equalsIgnoreCase(tok))
{
matched = false;
if (debug)
{
System.err.println("no matched3 for token: '" + tok + "'");
}
break;
}
else
{
index = index + tok.length();
if (debug)
{
System.err.println("matched3: " + str.substring(0, index));
}
}
}
else
{
matched = false;
if (debug)
{
System.err.println("no matched4 for token: '" + tok + "'");
}
break;
}
escaped = false;
}
}
}
if (matched && index != str.length())
{
if (debug)
{
System.err.println("no match5(remainder): " + str.substring(index, str.length()));
}
matched = false;
}
if (debug)
{
System.err.println("JMSSelector:matchPattern patternStr = \'" + patternStr + "\' str = \'" + str + "\' matched = " + matched);
}
}
catch (StringIndexOutOfBoundsException e)
{
matched = false;
if (debug)
{
e.printStackTrace();
System.err.println("HANDLED OUTOFBOUNDS JMSSelector:matchPattern patternStr = \'" + patternStr + "\' str = \'" + str + "\' matched = " + matched);
}
}
return matched;
}
/**
* Strip leading and trailing quotes from a String Literal.
* Also, the nested quote character is represented as 2
* consecutive quotes, so replace all occurrances of double
* quotes with single quotes.
*/
String processStringLiteral(String strLiteral) {
//Strip leading and trailing quotes
strLiteral = strLiteral.substring(1, strLiteral.length()-1);
// Replace all occurances of consecutive quotes as single quote.
int index = strLiteral.indexOf("''");
if (index > -1) {
StringBuffer sb = new StringBuffer(strLiteral);
while (index != -1) {
sb.deleteCharAt(index);
index = sb.toString().indexOf("''");
}
strLiteral = sb.toString();
}
return strLiteral;
}
}