Debugging MVEL.eval()

You may hit a weird result from MVEL.eval() due to type coercion.

http://mvel.codehaus.org/MVEL+2.0+Typing

As the rules of type coercion are not fully documented, you may need to go though source codes to understand what is happening.

Here is a simple (stupid) example,

Boolean result = (Boolean)MVEL.eval("true == \"foo\"");
System.out.println("result = " + result.toString());

The result is

result = true

Let's look into it.

You can get MVEL source codes from:
https://github.com/mvel/mvel/tags
(old) http://svn.codehaus.org/mvel/tags/

org.mvel2.MVELInterpretedRuntime.parseAndExecuteInterpreted() is the main method to execute the MVEL expression.
It's a little bit hard to read though without a help of a tool. Let's use a debugger and set a breakpoint here.

  private Object parseAndExecuteInterpreted() {
    ASTNode tk = null;
    int operator;
    lastWasIdentifier = false;

    try {
      while ((tk = nextToken()) != null) {
...
        switch ((operator = arithmeticFunctionReduction(operator))) {
          case OP_TERMINATE:
            return stk.peek();
          case OP_RESET_FRAME:
            continue;
        }
...
    }
    return stk.peek();
  }

After straying around, you will reach to org.mvel2.math.MathProcessor.doOperations() where the operation takes place.

  public static Object doOperations(Object val1, int operation, Object val2) {
    return doOperations(val1 == null ? DataTypes.OBJECT : __resolveType(val1.getClass()),
        val1, operation,
        val2 == null ? DataTypes.NULL : __resolveType(val2.getClass()), val2);
  }

Call stack will look like this:

Thread [main] (Suspended)	
	MathProcessor.doOperations(Object, int, Object) line: 47	
	ExecutionStack.op(int) line: 165	
	MVELInterpretedRuntime(AbstractParser).reduce() line: 2450	
	MVELInterpretedRuntime(AbstractParser).arithmeticFunctionReduction(int) line: 2410	
	MVELInterpretedRuntime.parseAndExecuteInterpreted() line: 139	
	MVELInterpretedRuntime.parse() line: 47	
	MVEL.eval(String) line: 90	
	Exam01.main(String[]) line: 12	

Now step-in carefully. The execution path depends on the operator and the types of the operands.

  private static Object _doOperations(int type1, Object val1, int operation, int type2, Object val2) {
    if (operation < 20) {
      if (type1 > 49 && type1 == type2) {
        return doOperationsSameType(type1, val1, operation, val2);
      }
      else if ((type1 > 99 && (type2 > 99))
          || (operation != 0 && isNumber(val1) && isNumber(val2))) {
        return doPrimWrapperArithmetic(getNumber(val1, type1),
            operation,
            getNumber(val2, type2), true, box(type2) > box(type1) ? box(type2) : box(type1));
      }
      else if (operation != ADD &&
          (type1 == 15 || type2 == 15) &&
          type1 != type2 && type1 != EMPTY && type2 != EMPTY) {

        return doOperationNonNumeric(type1, convert(val1, Boolean.class), operation, convert(val2, Boolean.class));
      }
...

convert() delegates the job to ConversionHandler. This time, it's org.mvel2.conversion.BooleanCH. BooleanCH implements the conversion logics from every type to Boolean.

public class BooleanCH implements ConversionHandler {
...
  private static Converter stringConverter = new Converter() {
    public Object convert(Object o) {
      return !(((String) o).equalsIgnoreCase("false")
          || (((String) o).equalsIgnoreCase("no"))
          || (((String) o).equalsIgnoreCase("off"))
          || ("0".equals(o))
          || ("".equals(o)));
    }
  };

Now you understand that MVEL coerces "false", "no", "off", "0" and "" to false. Others to true.