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.