package context.arch.enactor;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import context.arch.discoverer.ComponentDescriptions;
import context.arch.discoverer.component.NonConstantAttributeElement;
import context.arch.discoverer.component.NonConstantAttributeNameElement;
import context.arch.discoverer.query.ANDQueryItem;
import context.arch.discoverer.query.AbstractQueryItem;
import context.arch.discoverer.query.BooleanQueryItem;
import context.arch.discoverer.query.ElseQueryItem;
import context.arch.discoverer.query.NOTQueryItem;
import context.arch.discoverer.query.ORQueryItem;
import context.arch.discoverer.query.RuleQueryItem;
import context.arch.discoverer.query.comparison.AttributeComparison;
import context.arch.storage.Attribute;
import context.arch.storage.AttributeNameValue;
/**
* Parses a bracket-based grammar used in XML files to describe queries.
* TODO: should use JavaCC or similar parsers to support more intuitive grammars.
* @author Brian Y. Lim
*
*/
public class QueryParser {
private Map<String, Comparable<?>> constVars;
private Map<String, AbstractQueryItem<?, ?>> queries;
private ComponentDescriptions widgetStub;
private String fullText;
public QueryParser(String fullText, Map<String, Comparable<?>> constVars, Map<String, AbstractQueryItem<?, ?>> queries, ComponentDescriptions widgetStub) {
this.fullText = fullText;
this.constVars = constVars;
this.queries = queries;
this.widgetStub = widgetStub;
}
protected AbstractQueryItem<?, ?> parseQuery() {
return parseQuery(stripBrackets(fullText));
}
private static String stripBrackets(String text) {
return text.substring(
text.indexOf("(") + 1,
text.lastIndexOf(")")
).trim();
}
/**
*
* @param text stripped out of brackets
* @return
*/
protected AbstractQueryItem<?, ?> parseQuery(String text) {
//System.out.println("text = |" + text + "|");
String op = text.split("\\s")[0]; // get word before white space
//System.out.println("op = |" + op + "|");
int opLenth = op.length();
if (isBooleanOp(op)) {
String[] args = getBooleanArgs(text.substring(opLenth + 1));
return parseBooleanQuery(op, args);
}
else if (isNOTOp(op)) {
// expect only one argument
String arg = text.substring(opLenth + 1);
return parseNOTQuery(op, arg);
}
else if (isQueryOp(op)) {
// expect only one argument
String arg = text.substring(opLenth + 1); // name of query
return queries.get(arg);
}
else { // isTerminalOp(op)
// would just have arguments, so can split
String[] args = text.substring(opLenth + 1).split(" ", 2); // expect only 2 arguments
return parseAttributeRuleQuery(op, args);
}
/*
* cannot be a const var, an attribute, or a literal (string or number)
* since that would have been processed in terminal op
*/
// TODO: can't just split by ',' since may be nested!
}
/**
*
* @param text takes in text between outer (...)
* @return
*/
protected String[] getBooleanArgs(String text) {
/*
* Assumes each arg has an op
*/
int openCount = 0, closeCount = 0;
int index = 0;
int OPEN_INDEX = -1;
int CLOSE_INDEX = -1;
List<String> args = new ArrayList<String>();
for (char c : text.toCharArray()) {
if (c == '(') {
openCount++;
// first open
if (openCount == 1 && closeCount == 0) {
OPEN_INDEX = index;
}
}
else if (c == ')') {
closeCount++;
// balanced, so properly closed
if (openCount == closeCount) {
CLOSE_INDEX = index;
args.add(text.substring(OPEN_INDEX+1, CLOSE_INDEX));
// reset
openCount = closeCount = 0;
OPEN_INDEX = -1;
}
}
//System.out.println("c = " + c + ", index = " + index + ", openCount = " + openCount + ", closeCount = " + closeCount + ", OPEN_INDEX = " + OPEN_INDEX + ", CLOSE_INDEX = " + CLOSE_INDEX);
if (openCount > 0) {
}
// increment
index++;
}
return args.toArray(new String[args.size()]);
}
protected boolean isBooleanOp(String op) {
return op.equals("OR") ||
op.equals("AND") ||
op.equals("ELSE");
}
protected boolean isNOTOp(String op) {
return op.equals("NOT");
}
protected boolean isQueryOp(String op) {
return op.equals("QUERY");
}
protected boolean isTerminalOp(String op) {
return true; // TODO delete this useless test
}
protected BooleanQueryItem parseBooleanQuery(String op, String[] args) {
/*
* Boolean queries
*/
if (op.equals("OR")) {
ORQueryItem query = new ORQueryItem();
for (String arg : args) {
query.add(parseQuery(arg));
}
return query;
}
else if (op.equals("AND")) {
ANDQueryItem query = new ANDQueryItem();
for (String arg : args) {
query.add(parseQuery(arg));
}
return query;
}
else if (op.equals("ELSE")) {
ElseQueryItem query = new ElseQueryItem();
for (String arg : args) {
query.add(parseQuery(arg));
}
return query;
}
return null;
}
protected NOTQueryItem parseNOTQuery(String op, String arg) {
NOTQueryItem query = new NOTQueryItem(
parseQuery(stripBrackets(arg)));
return query;
}
/**
* ...
* Assume RuleQueryItem regarding a non-constant attribute, i.e. with
* NonConstantAttributeElement, and a corresponding AbstractComparison
* @param op
* @param args {attributeName, comparisonValue}. comparisonValue may be null, or missing, then RuleQueryItem would just track for changes
*/
@SuppressWarnings("unchecked")
protected <T extends Comparable<? super T>> RuleQueryItem<?, ?> parseAttributeRuleQuery(String op, String[] args) {
String attName = args[0].trim();
String comparisonValueStr = args.length == 2 ? args[1].trim() : null;
T comparisonValue;
AttributeComparison comparison = null;
// assume non-constant attribute
Attribute<?> att = widgetStub.mergeComponentDescriptions().getNonConstantAttribute(attName);
if (att == null) {
throw new RuntimeException("Attribute " + attName + " not found among non-constant attributes");
}
if (comparisonValueStr != null && comparisonValueStr.length() > 0) {
// comparison to a constant variable
if (constVars.containsKey(comparisonValueStr)) { // refers to const var name
// retrieve var value
comparisonValue = (T) constVars.get(comparisonValueStr);
}
// comparison to a String literal
else if (att.isType(String.class)) {
try {
comparisonValue = (T) URLDecoder.decode(comparisonValueStr, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
comparisonValue = (T) "";
}
}
/*
* TODO: Need to properly support querying about other ComponentDescription elements too, but in another method
*/
// comparison to a Comparable that needs to be parsed
else {
// cast comparison value to the type of the att
comparisonValue = AttributeNameValue.valueOf((Class<T>) att.getType(), comparisonValueStr);
}
/*
* Iterate through possibilities of AttributeComparison
*/
if (op.equals("EQUAL")) { comparison = new AttributeComparison(AttributeComparison.Comparison.EQUAL); }
else if (op.equals("DIFFERENT")) { comparison = new AttributeComparison(AttributeComparison.Comparison.DIFFERENT); }
else if (op.equals("GREATER")) { comparison = new AttributeComparison(AttributeComparison.Comparison.GREATER); }
else if (op.equals("GREATER_EQUAL")) { comparison = new AttributeComparison(AttributeComparison.Comparison.GREATER_EQUAL); }
else if (op.equals("LESS")) { comparison = new AttributeComparison(AttributeComparison.Comparison.LESS); }
else if (op.equals("LESS_EQUAL")) { comparison = new AttributeComparison(AttributeComparison.Comparison.LESS_EQUAL); }
if (comparison == null) { // not yet matched
// TODO: more comparison types? extensibility?
new RuntimeException("op: " + op).printStackTrace();
}
RuleQueryItem<?, ?> query = RuleQueryItem.instance(
new NonConstantAttributeElement(AttributeNameValue.instance(attName, comparisonValue)),
comparison
);
return query;
}
// no comparison value
else {
// would just check if attribute with name changes
RuleQueryItem<?, ?> query = RuleQueryItem.instance(
new NonConstantAttributeNameElement(attName)
);
return query;
}
}
}