package er.extensions.foundation;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.webobjects.eocontrol.EOQualifier;
import com.webobjects.foundation.NSKeyValueCodingAdditions;
/**
* Parses a boolean expression and evaluates that boolean expression against a valueProvider object that returns boolean values
* for the variable symbols found in the boolean expression.
*
* The valueProvider object must implement NSKeyValueCodingAdditions interface.
*
* Acceptable boolean expressions can use the words AND, OR and NOT in upper, lower or mixed case. Parentheses can be
* used as needed to group elements of the expression.
*
* Variable symbols in the boolean expression can use characters and formatting of typical keys or keyPaths.
*
* All other words besides AND, OR and NOT are assumed to be variables (aka keyPaths) which resolve to Boolean values when
* valueForKeyPath is invoked on the valueProvider object (which can be a NSDictionary of variable values or any object that
* implements {@link NSKeyValueCodingAdditions})
*
* For example
* Expression: <code>(canViewPerson AND canEditPerson) OR (isTheBoss AND NOT account.isAccountDisabled)</code>
*
* @author kieran
*
*/
public class ERXBooleanExpressionParser {
private static Pattern KEYPATH_TOKEN_PATTERN = Pattern.compile("\\w+([.]\\w*)*");
private final static String[] BOOLEAN_WORDS = new String[] { "AND", "OR", "NOT" };
private final String expression;
public ERXBooleanExpressionParser(String expression) {
this.expression = expression;
}
/**
* @return the result of the boolean expression when evaluated with valueProvider
*/
public boolean evaluateWithObject(NSKeyValueCodingAdditions valueProvider) {
return qualifier().evaluateWithObject(valueProvider);
}
private String _qualifierFormatForBooleanExpression;
/** @return the boolean expression converted to a EOQualifier qualifier format */
private String qualifierFormatForBooleanExpression() {
if ( _qualifierFormatForBooleanExpression == null ) {
StringBuffer sb = new StringBuffer();
Matcher matcher = KEYPATH_TOKEN_PATTERN.matcher(expression);
while( matcher.find()) {
String token = matcher.group();
// Leave boolean word operators alone
if (!isBooleanWordToken(token)) {
String replacement = "(" + matcher.group() + " = 'true')";
matcher.appendReplacement(sb, replacement);
}
}
matcher.appendTail(sb);
_qualifierFormatForBooleanExpression = sb.toString();
}
return _qualifierFormatForBooleanExpression;
}
private EOQualifier _qualifier;
/** @return the EOQualifier resulting from the boolean expression. Leaving this as public since it is useful for debugging the expression morphing in WOComponents */
public EOQualifier qualifier() {
if ( _qualifier == null ) {
_qualifier = EOQualifier.qualifierWithQualifierFormat(qualifierFormatForBooleanExpression(), null);
}
return _qualifier;
}
private boolean isBooleanWordToken(String token) {
// Case-insensitive match
token = token.toUpperCase();
for (int i = 0; i < BOOLEAN_WORDS.length; i++) {
if (BOOLEAN_WORDS[i].equals(token)) return true;
}
return false;
}
@Override
public String toString() {
return expression;
}
public String toDebugString() {
StringBuilder b = new StringBuilder();
b.append("Expression: ").append(expression);
b.append("qualifier format: ").append(qualifierFormatForBooleanExpression());
b.append("EOQualifier: ").append(qualifier());
return b.toString();
}
}