/**
* MVEL 2.0
* Copyright (C) 2007 The Codehaus
* Mike Brock, Dhanji Prasanna, John Graham, Mark Proctor
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mvel2;
import org.mvel2.ast.ASTNode;
import org.mvel2.ast.LineLabel;
import org.mvel2.compiler.CompiledExpression;
import org.mvel2.debug.Debugger;
import org.mvel2.debug.DebuggerContext;
import org.mvel2.integration.VariableResolverFactory;
import org.mvel2.optimizers.OptimizerFactory;
import org.mvel2.util.ErrorUtil;
import org.mvel2.util.ExecutionStack;
import static org.mvel2.Operator.*;
import static org.mvel2.util.PropertyTools.isEmpty;
/**
* This class contains the runtime for running compiled MVEL expressions.
*/
@SuppressWarnings({"CaughtExceptionImmediatelyRethrown"})
public class MVELRuntime {
// public static final ImmutableDefaultFactory IMMUTABLE_DEFAULT_FACTORY = new ImmutableDefaultFactory();
private static ThreadLocal<DebuggerContext> debuggerContext;
/**
* Main interpreter.
*
* @param debugger Run in debug mode
* @param expression The compiled expression object
* @param ctx The root context object
* @param variableFactory The variable factory to be injected
* @return The resultant value
* @see org.mvel2.MVEL
*/
public static Object execute(boolean debugger, final CompiledExpression expression, final Object ctx,
VariableResolverFactory variableFactory) {
Object v1, v2;
ExecutionStack stk = new ExecutionStack();
ASTNode tk = expression.getFirstNode();
Integer operator;
if (tk == null) return null;
try {
do {
if (tk.fields == -1) {
/**
* This may seem silly and redundant, however, when an MVEL script recurses into a block
* or substatement, a new runtime loop is entered. Since the debugger state is not
* passed through the AST, it is not possible to forward the state directly. So when we
* encounter a debugging symbol, we check the thread local to see if there is are registered
* breakpoints. If we find them, we assume that we are debugging.
*
* The consequence of this of course, is that it's not ideal to compileShared expressions with
* debugging symbols which you plan to use in a production enviroment.
*/
if (debugger || (debugger = hasDebuggerContext())) {
try {
debuggerContext.get().checkBreak((LineLabel) tk, variableFactory, expression);
}
catch (NullPointerException e) {
// do nothing for now. this isn't as calus as it seems.
}
}
continue;
}
else if (stk.isEmpty()) {
stk.push(tk.getReducedValueAccelerated(ctx, ctx, variableFactory));
}
if (variableFactory.tiltFlag()) {
return stk.pop();
}
switch (operator = tk.getOperator()) {
case RETURN:
variableFactory.setTiltFlag(true);
return stk.pop();
case NOOP:
continue;
case TERNARY:
if (!stk.popBoolean()) {
//noinspection StatementWithEmptyBody
while (tk.nextASTNode != null && !(tk = tk.nextASTNode).isOperator(TERNARY_ELSE)) ;
}
stk.clear();
continue;
case TERNARY_ELSE:
return stk.pop();
case END_OF_STMT:
/**
* If the program doesn't end here then we wipe anything off the stack that remains.
* Althought it may seem like intuitive stack optimizations could be leveraged by
* leaving hanging values on the stack, trust me it's not a good idea.
*/
if (tk.nextASTNode != null) {
stk.clear();
}
continue;
}
stk.push(tk.nextASTNode.getReducedValueAccelerated(ctx, ctx, variableFactory), operator);
try {
while (stk.isReduceable()) {
if ((Integer) stk.peek() == CHOR) {
stk.pop();
v1 = stk.pop();
v2 = stk.pop();
if (!isEmpty(v2) || !isEmpty(v1)) {
stk.clear();
stk.push(!isEmpty(v2) ? v2 : v1);
}
else stk.push(null);
}
else {
stk.op();
}
}
}
catch (ClassCastException e) {
throw new CompileException("syntax error or incomptable types", new char[0], 0, e);
}
catch (CompileException e) {
throw e;
}
catch (Exception e) {
throw new CompileException("failed to compileShared sub expression", new char[0], 0, e);
}
}
while ((tk = tk.nextASTNode) != null);
return stk.peek();
}
catch (NullPointerException e) {
if (tk != null && tk.isOperator() && tk.nextASTNode != null) {
throw new CompileException("incomplete statement: "
+ tk.getName() + " (possible use of reserved keyword as identifier: " + tk.getName() + ")", tk.getExpr(), tk.getStart());
}
else {
throw e;
}
}
finally {
OptimizerFactory.clearThreadAccessorOptimizer();
}
}
/**
* Register a debugger breakpoint.
*
* @param source - the source file the breakpoint is registered in
* @param line - the line number of the breakpoint
*/
public static void registerBreakpoint(String source, int line) {
ensureDebuggerContext();
debuggerContext.get().registerBreakpoint(source, line);
}
/**
* Remove a specific breakpoint.
*
* @param source - the source file the breakpoint is registered in
* @param line - the line number of the breakpoint to be removed
*/
public static void removeBreakpoint(String source, int line) {
if (hasDebuggerContext()) {
debuggerContext.get().removeBreakpoint(source, line);
}
}
/**
* Tests whether or not a debugger context exist.
*
* @return boolean
*/
private static boolean hasDebuggerContext() {
return debuggerContext != null && debuggerContext.get() != null;
}
/**
* Ensures that debugger context exists.
*/
private static void ensureDebuggerContext() {
if (debuggerContext == null) debuggerContext = new ThreadLocal<DebuggerContext>();
if (debuggerContext.get() == null) debuggerContext.set(new DebuggerContext());
}
/**
* Reset all the currently registered breakpoints.
*/
public static void clearAllBreakpoints() {
if (hasDebuggerContext()) {
debuggerContext.get().clearAllBreakpoints();
}
}
/**
* Tests whether or not breakpoints have been declared.
*
* @return boolean
*/
public static boolean hasBreakpoints() {
return hasDebuggerContext() && debuggerContext.get().hasBreakpoints();
}
/**
* Sets the Debugger instance to handle breakpoints. A debugger may only be registered once per thread.
* Calling this method more than once will result in the second and subsequent calls to simply fail silently.
* To re-register the Debugger, you must call {@link #resetDebugger}
*
* @param debugger - debugger instance
*/
public static void setThreadDebugger(Debugger debugger) {
ensureDebuggerContext();
debuggerContext.get().setDebugger(debugger);
}
/**
* Reset all information registered in the debugger, including the actual attached Debugger and registered
* breakpoints.
*/
public static void resetDebugger() {
debuggerContext = null;
}
}