org.yawlfoundation.yawl.worklet.support.ConditionEvaluator.java Source code

Java tutorial

Introduction

Here is the source code for org.yawlfoundation.yawl.worklet.support.ConditionEvaluator.java

Source

/*
 * Copyright (c) 2004-2012 The YAWL Foundation. All rights reserved.
 * The YAWL Foundation is a collaboration of individuals and
 * organisations who are committed to improving workflow technology.
 *
 * This file is part of YAWL. YAWL is free software: you can
 * redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation.
 *
 * YAWL 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. See the GNU Lesser General
 * Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with YAWL. If not, see <http://www.gnu.org/licenses/>.
 */

package org.yawlfoundation.yawl.worklet.support;

import net.sf.saxon.s9api.SaxonApiException;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.jdom2.Document;
import org.jdom2.Element;
import org.yawlfoundation.yawl.util.JDOMUtil;
import org.yawlfoundation.yawl.util.SaxonUtil;
import org.yawlfoundation.yawl.util.StringUtil;
import org.yawlfoundation.yawl.worklet.rdrutil.RdrConditionException;
import org.yawlfoundation.yawl.worklet.rdrutil.RdrConditionFunctions;
import org.yawlfoundation.yawl.worklet.rdrutil.RdrFunctionLoader;

import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

/** ConditionEvaluator is a member class of the Worklet Dynamic Selection
 *  Service. It is used here by the RdrNode class to evaluate the its condition 
 *  and thus allow the rule traversal to occur.
 *
 *  It takes an expression (provided as a String) and evaluates it
 *  to a boolean value. The datalist member is a JDOM Element that, if 
 *  supplied, will be used to retrieve values for any variable names used 
 *  in the expression.
 *
 *  The expression may contain the following operators:
 *      - Arithmetic: * / + -
 *      - Comparison: = != > < >= <=
 *      - Logical:    & | !
 *
 *  The order of precedence observed is:
 *      1.  * /
 *      2.  + -
 *      3. the comparison operators
 *      4. the logical operators
 *
 *  Operands may be numeric literals, string literals or variable names.
 *
 *  Parentheses may be used to group sub-expressions.
 *
 *  An RdrConditionException will be raised if the expression is malformed or
 *  does not evaluate to a boolean value (see getMessage() for the kinds of
 *  things that can go wrong).
 *
 *  @author Michael Adams
 *  v0.8, 04-09/2006
 */

public class ConditionEvaluator {

    // define the operators
    private static final String[] _NumericOps = { "+", "-", "*", "/" };
    private static final String[] _CompareOps = { "=", "!=", ">", ">=", "<", "<=" };
    private static final String[] _BooleanOps = { "&", "|" };
    private static final String[] _UnaryOps = { "+", "-", "!" };
    private static final String[] _AllOps = { "*", "/", "+", "-", ">=", "<=", "<", ">", "!=", "=", "&", "|", "!" };

    private boolean _isDesignTime = false;

    private static final Logger _log = LogManager.getLogger(ConditionEvaluator.class);

    public ConditionEvaluator() {
        // setLogLevel(_log, Level.ERROR);
    }

    /**
     *  Evaluate the condition using the datalist of variables and values.
     *  @param cond - the condition to evaluate
     *  @param data - the datalist of variables and values
     *
     *  @return the boolean result of the evaluation
     */
    public boolean evaluate(String cond, Element data, boolean isDesignTime) throws RdrConditionException {
        if (StringUtil.isNullOrEmpty(cond)) {
            throw new RdrConditionException("Cannot evaluate tree: condition is empty");
        }
        if (data == null) {
            throw new RdrConditionException("Cannot evaluate tree: data element is null");
        }

        _isDesignTime = isDesignTime;
        String result;

        // DEBUG: log received items
        _log.debug("Received condition: {}");
        _log.debug("Data = {}", JDOMUtil.elementToString(data));

        // check if it's an XQuery or cost predicate
        if (cond.startsWith("{") || cond.startsWith("/")) {
            result = evaluateXQuery(cond, data);
        } else {
            result = parseAndEvaluate(cond, data); // evaluate ordinary condition
        }

        // if a boolean result, return it
        if (isBoolean(result))
            return result.equalsIgnoreCase("TRUE");
        else
            throw new RdrConditionException(getMessage(1)); // result not T/F
    }

    // default runtime call
    public boolean evaluate(String cond, Element data) throws RdrConditionException {
        return evaluate(cond, data, false);
    }

    //==========================================================================//

    /**
     * ERROR MESSAGE LIST
     */

    private String getMessage(int ix) {
        String[] msg = { "Expression string has not yet been initialized", //  0
                "Expression does not evaluate to a boolean value",
                "Expression is invalid - contains mis-ordered tokens",
                "Expression contains unterminated literal string",
                "Expression contains an invalid literal numeric token",
                "Attempted to retrieve numeric value for non-numeric token", //  5
                "Attempted to retrieve boolean value for non-boolean token",
                "Attempted to retrieve string value for non-string token",
                "Invalid numeric comparison operator for operands",
                "Invalid boolean comparison operator for operands",
                "Invalid string comparison operator for operands", // 10
                "Invalid numeric operator for arithmetic operands", "Malformed operators in expression",
                "Could not determine operation type", "DataList element has not yet been initialized",
                "Left and right operands are different data types" }; // 15
        return msg[ix];
    }

    //==========================================================================//

    /**
     * "IS" VALIDATION METHODS
     */

    /** @return true if string = "true" or "false" (case insensitive) */
    private boolean isBoolean(String s) {
        return (s.equalsIgnoreCase("TRUE")) || (s.equalsIgnoreCase("FALSE"));
    }

    /** @return true if whole expression is a single string surrounded by "" */
    private boolean isString(String s) {
        return (s.indexOf('"') == 0) && (s.lastIndexOf('"') == s.length() - 1);
    }

    /** @return true if whole string represents a valid number */
    private boolean isNumber(String s) {
        return (isInteger(s) || isDouble(s));
    }

    /** @return true if whole string represents a valid integer */
    private boolean isInteger(String s) {
        try {
            Integer.parseInt(s);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    /** @return true if whole string represents a valid double */
    private boolean isDouble(String s) {
        try {
            Double.parseDouble(s);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    private String getType(String value) {
        if (isBoolean(value))
            return "boolean";
        if (isNumber(value))
            return "numeric";
        return "string";
    }

    /** @return true if operator is a unary */
    private boolean isUnaryOperator(String s) {
        return ((s.startsWith("-")) || (s.startsWith("+")));
    }

    /** @return true if expression is a literal value (string or numeric) */
    private boolean isLiteralValue(String s, Element data) {
        return isString(s) || isBoolean(s) || isNumber(s) || !isVarName(s, data);
    }

    /** @return true if expression is the name of a child of the _datalist
     *          Element (i.e. is the name of an item of data) */
    private boolean isVarName(String s, Element data) {
        Element var = data != null ? data.getChild(s) : null;
        return (var != null);
    }

    /** @return true if expression is a registered function name in the
     *  RdrConditionFunctions class */
    private boolean isFunctionName(String s) {
        return s != null && (isCostFunctionName(s) || RdrConditionFunctions.isRegisteredFunction(s)
                || RdrFunctionLoader.getNames().contains(s));
    }

    private boolean isFunctionCall(String s) {
        return s.endsWith("]");
    }

    private boolean isCostExpression(String s) {
        return s.startsWith("cost[") || s.startsWith("cheapestVariant[") || s.startsWith("dearestVariant[");
    }

    private boolean isCostFunctionName(String s) {
        return s.startsWith("cost") || s.startsWith("cheapestVariant") || s.startsWith("dearestVariant");
    }

    /** @return true if expression is of the leftop/operator/rightop kind */
    private boolean isSimpleExpression(String s) {

        if (isString(s) || (s.length() == 0))
            return false;

        // ignore leading sign
        if (s.startsWith("+") || s.startsWith("-"))
            s = s.substring(1);

        // look for an operator
        for (String _AllOp : _AllOps)
            if (s.indexOf(_AllOp) > 0)
                return true;

        return false; //no ops found
    }

    /** @return true if 'op' is a valid numeric operator */
    private boolean isNumericOp(String op) {
        return isInArray(op, _NumericOps);
    }

    /** @return true if 'op' is a valid boolean operator */
    private boolean isBooleanOp(String op) {
        return isInArray(op, _BooleanOps);
    }

    /** @return true if 'op' is a valid operator */
    private boolean isOperator(String op) {
        return isInArray(op, _AllOps);
    }

    /** @return true if 's' is a member element of array 'a' */
    private boolean isInArray(String s, String[] a) {

        for (String element : a) {
            if (s.compareTo(element) == 0)
                return true;
        }
        return false;
    }

    /** @return true if 'c' is one of '0'-'9' or '.' */
    private boolean isDigitOrDot(char c) {
        return Character.isDigit(c) || (c == '.');
    }

    private boolean isNumeric(char c) {
        return isDigitOrDot(c) || (c == '-') || (c == '+');
    }

    /** @return true if 'c' is one of 'a'-'z' or 'A'-'Z' or '_' */
    private boolean isLetterOrUScore(char c) {
        return Character.isLetter(c) || (c == '_');
    }

    /** @return true if 'c' is one of 'a'-'z', 'A'-'Z', '_', '0'-'9' or '.' */
    private boolean isValidVarNameChar(char c) {
        return Character.isDigit(c) || isLetterOrUScore(c);
    }

    /** @return true if 'c' is a valid operator character */
    private boolean isOperator(char c) {
        char[] opChar = { '*', '/', '+', '-', '>', '<', '!', '=', '&', '|', '!' };
        for (char anOpChar : opChar)
            if (c == anOpChar)
                return true;

        return false;
    }

    private boolean isFunctionArgumentDelimiter(String s, int fadPos) {
        if (fadPos == 0)
            return false;
        int tmp = fadPos - 1;

        // find start of token preceding the '('
        while ((tmp > 0) && isValidVarNameChar(s.charAt(tmp)))
            tmp--;
        return isFunctionName(s.substring(tmp, fadPos));
    }

    /** replace ()'s with []'s where they represent function argument delimiters */
    private String maskArgumentDelimiters(String s, int fadPos) throws RdrConditionException {
        char[] array = s.toCharArray();
        int counter = 0;
        for (int i = fadPos; i < s.length(); i++) {
            char c = array[i];
            if (c == '(') {
                array[i] = '[';
                counter++;
            } else if (c == ')') {
                array[i] = ']';
                counter--;
            }
            if (counter == 0)
                break;
        }
        if (counter == 0)
            return new String(array);

        throw new RdrConditionException("Invalid expression: unbalanced parentheses");
    }

    //==========================================================================//

    /**
     *  STRING MANIPULATION METHODS
     */

    /** removes the double quotes from around a string */
    private String deQuote(String s) {
        return s.substring(1, s.length() - 1);
    }

    /** replaces all signs fronting numeric values with placeholding '@' 's */
    private String maskUnaryOps(String s) {

        StringBuilder sb = new StringBuilder(s);
        String opSet = "*/+-><=", unSet = "+-";
        int unPos = -1, j;

        // mask any leading + or -
        if (unSet.indexOf(sb.charAt(0)) > -1)
            sb.setCharAt(0, '@');

        for (int i = 0; i < unSet.length(); i++) { // for + and -
            unPos = sb.indexOf(unSet.substring(i, i + 1), unPos + 1);

            while ((unPos > -1) && (unPos < sb.length())) {
                j = unPos - 1;
                while ((j > 0) && (sb.charAt(j) == ' '))
                    --j; //go back thru wspace

                // if sign is preceded by another sign, mask it
                if (opSet.indexOf(sb.charAt(j)) > -1)
                    sb.setCharAt(unPos, '@');
                unPos = sb.indexOf(unSet.substring(i, i + 1), unPos + 1);
            }
        }
        return sb.toString();
    }

    /** returns a parenthesised sub expression within a string expression */
    private String extractSubExpr(String s) {
        int parCount = 1, start, i;

        start = Pos('(', s); // find opening '('
        if (start == -1)
            return "";

        for (i = start + 1; i < s.length(); i++) { // find matching ')'
            if (s.charAt(i) == '(')
                parCount++;
            else if (s.charAt(i) == ')')
                parCount--;

            if (parCount == 0)
                break;
        }

        // return operand with the enclosing parentheses
        return s.substring(start, i + 1);
    }

    /** removes and returns an embedded string literal from an expression */
    private String extractString(String s) {
        int start = s.indexOf('"');
        int end = s.indexOf('"', start + 1);

        if ((start == -1) || (end == -1))
            return ""; // 0 or one quote only

        return s.substring(start, end + 1);
    }

    /** replaces the first instance of "cut" in "s" with "paste" */
    private String replaceStr(String s, String cut, String paste) {
        StringBuilder b = new StringBuilder(s);
        int insPos = b.indexOf(cut);
        b.delete(insPos, insPos + cut.length());
        b.insert(insPos, paste);
        return b.toString();
    }

    //==========================================================================//

    /**
     *  PARSING METHODS
     */

    private String evaluateXQuery(String expr, Element data) throws RdrConditionException {
        try {
            if (expr.startsWith("{"))
                expr = deQuote(expr); // remove braces
            String query = String.format("boolean(%s)", expr);
            return SaxonUtil.evaluateQuery(query, new Document(data.clone()));
        } catch (SaxonApiException sae) {
            throw new RdrConditionException("Invalid XPath expression (" + expr + ").");
        }
    }

    /** returns the index position of 'c' in 's', or -1 if not found */
    private int Pos(char c, String s) {
        return s.indexOf(c);
    }

    /** returns the index in 'a' of string 's', or -1 if not found */
    private int indexOfArray(String[] a, String s) {
        for (int i = 0; i < a.length; i++) {
            if (s.compareTo(a[i]) == 0)
                return i;
        }
        return -1;
    }

    /** finds the position of the left most operator for a level of precedence
     *  @param lowerBound, upperBound - the range of ops to search for
     **/

    private int findLeftMostOp(int lowerBound, int upperBound, String[] s) {

        int foundPos, leftMostOpPos = 10000;
        boolean found = false;

        for (int i = lowerBound; i <= upperBound; i++) {
            foundPos = indexOfArray(s, _AllOps[i]);
            if ((foundPos > -1) && (foundPos < leftMostOpPos)) {
                leftMostOpPos = foundPos;
                found = true;
            }
        }
        if (found)
            return leftMostOpPos;
        else
            return -1;
    }

    /** finds the position of next operator in an expression,
     *  in order of precedence */
    private int findNextOperator(String[] s) {

        int op = findLeftMostOp(0, 1, s);
        if (op < 0)
            op = findLeftMostOp(2, 3, s);
        if (op < 0)
            op = findLeftMostOp(4, 7, s);
        if (op < 0)
            op = findLeftMostOp(8, 9, s);
        if (op < 0)
            op = findLeftMostOp(10, 12, s);
        return op;
    }

    /**
     * replaces a subexpression with its evaluated result
     * @param a - the array of tokens for the whole expression
     * @param ix - the index of the operator token
     * @param val - the evaluated value of the subexpression
     */
    private String[] reduceTokens(String[] a, int ix, String val) {
        String[] result = new String[a.length - 2]; // replace 3 tokens with 1
        int i, j;

        for (i = 0; i < ix; i++)
            result[i] = a[i]; // copy array up to op index

        result[--i] = val; // replace with simple val
        for (j = i + 1; j < result.length; j++)
            result[j] = a[j + 2]; //copy rest
        return result;
    }

    /** Splits an expression into its token parts 
     *  pre: any parenthesised sub expressions have been evaluated to 
     *       simple values    
     */
    private String[] tokenize(String s) throws RdrConditionException {
        Vector<String> v = new Vector<String>();
        int ix = 0, ln = s.length();
        String token;
        String[] result;

        while (ix < ln) {

            // unary ops???

            ix = skipWhitespace(s, ix);
            if (ix == ln)
                break;

            // get next token
            if (s.charAt(ix) == '"') { // literal string
                token = getLiteralString(s, ix);
            } else if (isDigitOrDot(s.charAt(ix))) { // literal number
                token = getLiteralNumber(s, ix);
            } else if (isLetterOrUScore(s.charAt(ix))) { // var or function name
                token = getVarName(s, ix);
            } else if (isOperator(s.charAt(ix))) { // operator
                token = getOperator(s, ix);
            } else {
                String msg = "Expression contains an invalid token at char " + ix;
                throw new RdrConditionException(msg);
            }
            v.addElement(token);
            ix += token.length();
        }

        result = new String[v.size()];
        v.copyInto(result);

        // make sure tokens are ordered correctly
        if (validateTokenization(result))
            return result;
        else
            throw new RdrConditionException(getMessage(2));

    }

    /** skip any whitespace by incrementing the start char over it */
    private int skipWhitespace(String s, int start) {
        while ((start < s.length()) && Character.isWhitespace(s.charAt(start)))
            start++;
        return start;
    }

    /** returns an embedded literal string of the form "abc"
     *  pre: s[start] == quote char
     */
    private String getLiteralString(String s, int start) throws RdrConditionException {
        int tmp = s.indexOf('"', start + 1);

        // no ending quote
        if (tmp == -1)
            throw new RdrConditionException(getMessage(3));

        return s.substring(start, tmp + 1);
    }

    /** returns a literal number from start position in s */
    private String getLiteralNumber(String s, int start) throws RdrConditionException {
        int tmp = start + 1;
        String result;
        while ((tmp < s.length()) && isDigitOrDot(s.charAt(tmp)))
            tmp++;
        result = s.substring(start, tmp);
        if (!isNumber(result))
            throw new RdrConditionException(getMessage(4));
        return result;
    }

    /** returns a variable or function name from start position in expression */
    private String getVarName(String s, int start) throws RdrConditionException {
        String result;
        int tmp = start + 1;

        while ((tmp < s.length()) && isValidVarNameChar(s.charAt(tmp)))
            tmp++;
        result = s.substring(start, tmp);
        if (isFunctionName(result)) { // read arguments also
            tmp = findCloserIndex(s, tmp, '[', ']');
            tmp++; // add one more for the ']'
        }

        if (tmp > 0)
            return s.substring(start, tmp);
        throw new RdrConditionException("Invalid expression: unbalanced parentheses");
    }

    private int findCloserIndex(String s, int from, char left, char right) {
        int counter = 0;
        for (int i = from; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == left)
                counter++;
            else if (c == right)
                counter--;
            if (counter == 0)
                return i;
        }
        return -1;
    }

    /** returns an operator from start position in expression */
    private String getOperator(String s, int start) {
        int tmp = start + 1;
        while ((tmp < s.length()) && isOperator(s.charAt(tmp)))
            tmp++;
        return s.substring(start, tmp);
    }

    /** @return true if alltokens are in a valid order (term op term ...) */
    private boolean validateTokenization(String[] a) {

        //a.length must be odd
        if ((a.length % 2) == 0)
            return false;

        //every even element must not be an op
        for (int i = 0; i < a.length; i += 2)
            if (isOperator(a[i]))
                return false;

        //every odd element must be an op
        for (int i = 1; i < a.length; i += 2)
            if (!isOperator(a[i]))
                return false;

        //all checks out!
        return true;
    }

    //==========================================================================//

    /**
     *  EVALUATION METHODS
     */

    /** parses and evaluates expression 's' using operator precedence */
    private String parseAndEvaluate(String s, Element data) throws RdrConditionException {

        String subExpr, ans;
        String[] tokens;
        int opIndex;
        boolean negation = false;
        int parIndex = s.indexOf('(');

        while (parIndex > -1) {

            // special case if () are part of a cost expression or function call
            if (isFunctionArgumentDelimiter(s, parIndex)) {
                s = maskArgumentDelimiters(s, parIndex);
            } else {
                // evaluate parenthesised sub-expressions first
                subExpr = extractSubExpr(s); // get ( subexpr )
                String internal = deQuote(subExpr);

                // recurse if required
                ans = (internal.isEmpty()) ? internal : parseAndEvaluate(internal, data);
                s = replaceStr(s, subExpr, ans); // insert result
            }
            parIndex = s.indexOf('(');
        }
        if (s.charAt(0) == '!') {
            negation = true;
            s = s.substring(1);
        }

        // break expression tokens into a string array
        tokens = tokenize(s.trim()); // ( ) any have been removed

        if (_log.isDebugEnabled())
            for (int i = 0; i < tokens.length; i++)
                _log.debug("token {} = {}", i, tokens[i]);

        opIndex = findNextOperator(tokens);

        // while the expression has more operators, evaluate a part
        while (opIndex > -1) {
            ans = evalExpression(tokens[opIndex - 1], tokens[opIndex], tokens[opIndex + 1], data);
            tokens = reduceTokens(tokens, opIndex, ans);

            if (_log.isDebugEnabled())
                for (int i = 0; i < tokens.length; i++)
                    _log.debug("token {} = {}", i, tokens[i]);

            opIndex = findNextOperator(tokens);
        }

        // one token left - can be boolean string or single (boolean) function call
        if (isFunctionCall(tokens[0]))
            tokens[0] = _isDesignTime ? "true" : evalFunction(tokens[0], data);
        if (negation) {
            tokens[0] = tokens[0].equalsIgnoreCase("true") ? "false" : "true";
        }

        return tokens[0]; // 'true' or 'false' if all went well!
    }

    /** evaluates an expression and returns the result
     *  @param lOp - the left operand
     *  @param operator - as the name implies
     *  @param rOp - the right operand
     */
    private String evalExpression(String lOp, String operator, String rOp, Element data)
            throws RdrConditionException {

        // if either op is a function call, replace it with its evaluation
        if (isFunctionCall(lOp))
            lOp = getFunctionResult(lOp, data);
        if (isFunctionCall(rOp))
            rOp = getFunctionResult(rOp, data);

        // if either op is a varname, replace it with its value
        if (!isLiteralValue(lOp, data))
            lOp = getVarValue(lOp, data);
        if (!isLiteralValue(rOp, data))
            rOp = getVarValue(rOp, data);

        // make sure any data variables used contain valid data
        if ((lOp.equals("undefined")) || (rOp.equals("undefined")) || (lOp.length() == 0) || (rOp.length() == 0)) {
            throw new RdrConditionException(getMessage(12));
        }

        // make sure the two operands are the same data type
        if (!getType(lOp).equals(getType(rOp))) {
            throw new RdrConditionException(getMessage(15) + ". Left = " + lOp + ", Right = " + rOp);
        }

        // evaluate depending on the operator data types
        if (isNumber(lOp))
            return doNumericOperation(lOp, operator, rOp);
        else if (isBoolean(lOp))
            return doBooleanOperation(lOp, operator, rOp);
        else
            return doStringOperation(lOp, operator, rOp);
    }

    /**
     * Evaluates a function embedded in a condition
     * PRE: the function is a member of the ceFunctions class
     * @param func the function to call
     * @return the result of the function
     */
    private String evalFunction(String func, Element data) throws RdrConditionException {

        // special case: cost expression
        if (isCostExpression(func)) {
            String exp = func.replace('[', '(').replace(']', ')');
            return new CostPredicateEvaluator().evaluate(exp, data);
        }

        String funcName, varName, varValue, result;
        Map<String, String> args = new HashMap<String, String>();

        // strip out arguments & get their values
        String[] argList = parseArgsList(func);
        for (String arg : argList) {
            varName = arg.trim();
            varValue = getVarValue(varName, data);
            args.put(varName, varValue);
        }

        // extract function name
        funcName = func.substring(0, func.indexOf('['));

        // run function
        if (RdrConditionFunctions.isRegisteredFunction(funcName)) {
            result = RdrConditionFunctions.execute(funcName, args);
        } else {
            result = RdrFunctionLoader.execute(funcName, args);
        }
        if (result == null)
            result = "null";
        return result;
    }

    /** translates a bad result to 'undefined' */
    private String getFunctionResult(String func, Element data) throws RdrConditionException {
        return clarifyResult(evalFunction(func, data), null);
    }

    /** retrieves the value for a variable or function from the datalist Element */
    private String getVarValue(String var, Element data) {
        _log.debug("in getVarValue, var = {}", var);

        // var "this" refers to the workitem associated with the task named in this rule
        String result = null;
        if (var.equalsIgnoreCase("this")) {
            result = getThisData(data);
        } else {
            Element varElement = data.getChild(var);
            if (varElement != null) {
                result = varElement.getText();
                String type = varElement.getAttributeValue("type");
                result = clarifyResult(result, type);
            }
        }
        return result;
    }

    private String clarifyResult(String result, String type) {
        if (result == null || result.equals("null")) {
            result = "undefined";
        } else if (result.length() == 0 && type != null) {
            if (type.equals("string")) {
                result = "\"" + result + "\"";
            } else if (type.equals("boolean")) {
                result = "false";
            } else {
                result = "0"; // assuming numeric
            }
        }
        return result;
    }

    /** extracts the names of arguments from a function call */
    private String[] parseArgsList(String list) {
        int start = list.indexOf('[');
        int end = findCloserIndex(list, start, '[', ']');
        String result = list.substring(start + 1, end); // remove [ ]
        return result.split(",");
    }

    /** get the value of the 'this' argument */
    private String getThisData(Element data) {
        String result = null;
        Element eThis = data.getChild("process_info").getChild("workItemRecord");
        if (eThis != null)
            result = JDOMUtil.elementToString(eThis);
        return result;
    }

    /**
     * If the value is a string, en-quote it
     * At this level, a value can be either a number, boolean or string
     */
    private String formatVarValue(String val) {
        if (isNumber(val) || isBoolean(val))
            return val;

        // only need to format a string literal
        return "\"" + val + "\"";
    }

    /** Convert operands to numbers and perform operation */
    private String doNumericOperation(String l, String op, String r) throws RdrConditionException {
        if (l.equals("nodata"))
            l = "0";
        if (r.equals("nodata"))
            r = "0";
        double dLeft = Double.parseDouble(l);
        double dRight = Double.parseDouble(r);

        if (isNumericOp(op))
            return doArithmeticOperation(dLeft, op, dRight);
        else
            return doNumericComparison(dLeft, op, dRight);

    }

    /** performs the comparison and returns "true" or "false" */
    private String doNumericComparison(double l, String op, double r) throws RdrConditionException {
        if (op.compareTo("=") == 0)
            return String.valueOf(l == r);
        if (op.compareTo(">") == 0)
            return String.valueOf(l > r);
        if (op.compareTo(">=") == 0)
            return String.valueOf(l >= r);
        if (op.compareTo("<") == 0)
            return String.valueOf(l < r);
        if (op.compareTo("<=") == 0)
            return String.valueOf(l <= r);
        if (op.compareTo("!=") == 0)
            return String.valueOf(l != r);
        throw new RdrConditionException(getMessage(8)); // error if gets here
    }

    /** performs the operation and returns result as a string */
    private String doArithmeticOperation(double l, String op, double r) throws RdrConditionException {
        if (op.compareTo("+") == 0)
            return String.valueOf(l + r);
        if (op.compareTo("-") == 0)
            return String.valueOf(l - r);
        if (op.compareTo("*") == 0)
            return String.valueOf(l * r);
        if (op.compareTo("/") == 0)
            return String.valueOf(l / r);
        throw new RdrConditionException(getMessage(11)); // error if gets here
    }

    /** performs the operation and returns "true" or "false" */
    private String doBooleanOperation(String l, String op, String r) throws RdrConditionException {
        // convert string operands to boolean
        boolean bLeft = (l.equalsIgnoreCase("TRUE"));
        boolean bRight = (r.equalsIgnoreCase("TRUE"));

        if (op.compareTo("=") == 0)
            return String.valueOf(bLeft == bRight);
        if (op.compareTo("!=") == 0)
            return String.valueOf(bLeft != bRight);
        if (op.compareTo("&") == 0)
            return String.valueOf(bLeft && bRight);
        if (op.compareTo("|") == 0)
            return String.valueOf(bLeft || bRight);
        throw new RdrConditionException(getMessage(9)); // error if gets here
    }

    /** performs the comparison and returns "true" or "false" */
    private String doStringOperation(String l, String op, String r) throws RdrConditionException {
        if (isString(l))
            l = deQuote(l);
        if (isString(r))
            r = deQuote(r);
        if (l.equals("nodata"))
            l = "";
        if (r.equals("nodata"))
            r = "";
        if (op.compareTo("=") == 0)
            return String.valueOf(l.compareTo(r) == 0);
        if (op.compareTo("!=") == 0)
            return String.valueOf(l.compareTo(r) != 0);
        throw new RdrConditionException(getMessage(10)); // error if gets here
    }

    private static void setLogLevel(Logger logger, Level level) {
        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
        Configuration config = ctx.getConfiguration();
        LoggerConfig loggerConfig = config.getLoggerConfig(logger.getName());
        loggerConfig.setLevel(level);
        ctx.updateLoggers();
    }

    /*********************************************************************/

    public static void main(String args[]) {
        // unit testing
        //  String s = "(Name = JOHN)";
        String s = "-50 > 20";
        Element e = new Element("testElement");
        e.setAttribute("nval", "17");
        e.setAttribute("sval", "\"apples\"");
        e.setAttribute("bval", "true");
        Element d = new Element("Age");
        d.setText("30");
        e.addContent(d);

        ConditionEvaluator t = new ConditionEvaluator();

        try {
            //          t.p(t.evalExpression("27", "!=", "26")) ;
            s = "cost(case()) > 5";
            boolean b = t.evaluate(s, e);
            t.p("expression: " + s + ", returns: " + b);
        } catch (RdrConditionException re) {
            re.printStackTrace();
        }

    }

    private void p(String s) {
        System.out.println(s);
    }

    private void p(boolean b) {
        System.out.println(b);
    }

} // end class ConditionEvaluator