001: package org.mvel;
002:
003: import static org.mvel.DataConversion.canConvert;
004: import static org.mvel.Operator.*;
005: import static org.mvel.Soundex.soundex;
006: import org.mvel.ast.ASTNode;
007: import org.mvel.ast.LineLabel;
008: import org.mvel.compiler.CompiledExpression;
009: import org.mvel.debug.Debugger;
010: import org.mvel.debug.DebuggerContext;
011: import org.mvel.integration.VariableResolverFactory;
012: import org.mvel.integration.impl.ClassImportResolverFactory;
013: import org.mvel.util.ASTLinkedList;
014: import org.mvel.util.ExecutionStack;
015: import static org.mvel.util.ParseTools.containsCheck;
016: import static org.mvel.util.PropertyTools.isEmpty;
017: import static org.mvel.util.PropertyTools.similarity;
018: import org.mvel.util.Stack;
019:
020: import static java.lang.String.valueOf;
021: import static java.lang.Thread.currentThread;
022:
023: /**
024: * This class contains the runtime for running compiled MVEL expressions.
025: */
026: @SuppressWarnings({"CaughtExceptionImmediatelyRethrown"})
027: public class MVELRuntime {
028: // private static ThreadLocal<Map<String, Set<Integer>>> threadBreakpoints;
029: // private static ThreadLocal<Debugger> threadDebugger;
030:
031: private static ThreadLocal<DebuggerContext> debuggerContext;
032:
033: /**
034: * Main interpreter.
035: *
036: * @param debugger -
037: * @param expression -
038: * @param ctx -
039: * @param variableFactory -
040: * @return -
041: * @see org.mvel.MVEL
042: */
043: public static Object execute(boolean debugger,
044: final CompiledExpression expression, final Object ctx,
045: VariableResolverFactory variableFactory) {
046: final ASTLinkedList node = new ASTLinkedList(expression
047: .getInstructions().firstNode());
048:
049: if (expression.isImportInjectionRequired()) {
050: // variableFactory = new TypeInjectionResolverFactoryImpl(expression.getParserContext().getImports(), variableFactory);
051: variableFactory = new ClassImportResolverFactory(expression
052: .getParserContext().getParserConfiguration(),
053: variableFactory);
054:
055: }
056:
057: Stack stk = new ExecutionStack();
058: Object v1, v2;
059:
060: ASTNode tk = null;
061: Integer operator;
062:
063: try {
064: while ((tk = node.nextNode()) != null) {
065: if (tk.fields == -1) {
066: /**
067: * This may seem silly and redundant, however, when an MVEL script recurses into a block
068: * or substatement, a new runtime loop is entered. Since the debugger state is not
069: * passed through the AST, it is not possible to forward the state directly. So when we
070: * encounter a debugging symbol, we check the thread local to see if there is are registered
071: * breakpoints. If we find them, we assume that we are debugging.
072: *
073: * The consequence of this of course, is that it's not ideal to compile expressions with
074: * debugging symbols which you plan to use in a production enviroment.
075: */
076: if (!debugger && hasDebuggerContext()) {
077: debugger = true;
078: }
079:
080: /**
081: * If we're not debugging, we'll just skip over this.
082: */
083: if (debugger) {
084: LineLabel label = (LineLabel) tk;
085: DebuggerContext context = debuggerContext.get();
086:
087: try {
088: context.checkBreak(label, variableFactory,
089: expression);
090: } catch (NullPointerException e) {
091: // do nothing for now. this isn't as calus as it seems.
092: }
093: }
094: continue;
095: }
096:
097: if (stk.isEmpty()) {
098: stk.push(tk.getReducedValueAccelerated(ctx, ctx,
099: variableFactory));
100: }
101:
102: if (!tk.isOperator()) {
103: continue;
104: }
105:
106: switch (operator = tk.getOperator()) {
107: case TERNARY:
108: if (!(Boolean) stk.pop()) {
109: //noinspection StatementWithEmptyBody
110: while (node.hasMoreNodes()
111: && !node.nextNode().isOperator(
112: Operator.TERNARY_ELSE))
113: ;
114: }
115: stk.clear();
116: continue;
117:
118: case TERNARY_ELSE:
119: return stk.pop();
120:
121: case END_OF_STMT:
122: /**
123: * If the program doesn't end here then we wipe anything off the stack that remains.
124: * Althought it may seem like intuitive stack optimizations could be leveraged by
125: * leaving hanging values on the stack, trust me it's not a good idea.
126: */
127: if (node.hasMoreNodes()) {
128: stk.clear();
129: }
130:
131: continue;
132: }
133:
134: stk.push(node.nextNode().getReducedValueAccelerated(
135: ctx, ctx, variableFactory), operator);
136:
137: try {
138: while (stk.size() > 1) {
139: operator = (Integer) stk.pop();
140: v1 = stk.pop();
141: v2 = stk.pop();
142:
143: switch (operator) {
144: case CHOR:
145: if (!isEmpty(v2) || !isEmpty(v1)) {
146: stk.clear();
147: stk.push(!isEmpty(v2) ? v2 : v1);
148: } else
149: stk.push(null);
150: break;
151:
152: case INSTANCEOF:
153: if (v1 instanceof Class)
154: stk.push(((Class) v1).isInstance(v2));
155: else
156: stk.push(currentThread()
157: .getContextClassLoader()
158: .loadClass(valueOf(v1))
159: .isInstance(v2));
160:
161: break;
162:
163: case CONVERTABLE_TO:
164: if (v1 instanceof Class)
165: stk.push(canConvert(v2.getClass(),
166: (Class) v1));
167: else
168: stk
169: .push(canConvert(
170: v2.getClass(),
171: currentThread()
172: .getContextClassLoader()
173: .loadClass(
174: valueOf(v1))));
175: break;
176:
177: case CONTAINS:
178: stk.push(containsCheck(v2, v1));
179: break;
180:
181: case BW_AND:
182: stk.push((Integer) v2 & (Integer) v1);
183: break;
184:
185: case BW_OR:
186: stk.push((Integer) v2 | (Integer) v1);
187: break;
188:
189: case BW_XOR:
190: stk.push((Integer) v2 ^ (Integer) v1);
191: break;
192:
193: case BW_SHIFT_LEFT:
194: stk.push((Integer) v2 << (Integer) v1);
195: break;
196:
197: case BW_USHIFT_LEFT:
198: int iv2 = (Integer) v2;
199: if (iv2 < 0)
200: iv2 *= -1;
201: stk.push(iv2 << (Integer) v1);
202: break;
203:
204: case BW_SHIFT_RIGHT:
205: stk.push((Integer) v2 >> (Integer) v1);
206: break;
207:
208: case BW_USHIFT_RIGHT:
209: stk.push((Integer) v2 >>> (Integer) v1);
210: break;
211:
212: case SOUNDEX:
213: stk.push(soundex(valueOf(v1)).equals(
214: soundex(valueOf(v2))));
215: break;
216:
217: case SIMILARITY:
218: stk.push(similarity(valueOf(v1),
219: valueOf(v2)));
220: break;
221: }
222: }
223: } catch (ClassCastException e) {
224: throw new CompileException(
225: "syntax error or incomptable types", e);
226: } catch (Exception e) {
227: throw new CompileException(
228: "failed to subEval expression", e);
229: }
230: }
231:
232: return stk.pop();
233: } catch (NullPointerException e) {
234: if (tk != null && tk.isOperator() && !node.hasMoreNodes()) {
235: throw new CompileException(
236: "incomplete statement: "
237: + tk.getName()
238: + " (possible use of reserved keyword as identifier: "
239: + tk.getName() + ")");
240: } else {
241: throw e;
242: }
243: }
244: }
245:
246: /**
247: * Register a debugger breakpoint.
248: *
249: * @param source - the source file the breakpoint is registered in
250: * @param line - the line number of the breakpoint
251: */
252: public static void registerBreakpoint(String source, int line) {
253: ensureDebuggerContext();
254: debuggerContext.get().registerBreakpoint(source, line);
255: }
256:
257: /**
258: * Remove a specific breakpoint.
259: *
260: * @param source - the source file the breakpoint is registered in
261: * @param line - the line number of the breakpoint to be removed
262: */
263: public static void removeBreakpoint(String source, int line) {
264: if (hasDebuggerContext()) {
265: debuggerContext.get().removeBreakpoint(source, line);
266: }
267: }
268:
269: private static boolean hasDebuggerContext() {
270: return debuggerContext != null && debuggerContext.get() != null;
271: }
272:
273: private static void ensureDebuggerContext() {
274: if (debuggerContext == null)
275: debuggerContext = new ThreadLocal<DebuggerContext>();
276: if (debuggerContext.get() == null)
277: debuggerContext.set(new DebuggerContext());
278: }
279:
280: /**
281: * Reset all the currently registered breakpoints.
282: */
283: public static void clearAllBreakpoints() {
284: if (hasDebuggerContext()) {
285: debuggerContext.get().clearAllBreakpoints();
286: }
287: }
288:
289: public static boolean hasBreakpoints() {
290: return hasDebuggerContext()
291: && debuggerContext.get().hasBreakpoints();
292: }
293:
294: /**
295: * Sets the Debugger instance to handle breakpoints. A debugger may only be registered once per thread.
296: * Calling this method more than once will result in the second and subsequent calls to simply fail silently.
297: * To re-register the Debugger, you must call {@link #resetDebugger}
298: *
299: * @param debugger - debugger instance
300: */
301: public static void setThreadDebugger(Debugger debugger) {
302: ensureDebuggerContext();
303: debuggerContext.get().setDebugger(debugger);
304: }
305:
306: /**
307: * Reset all information registered in the debugger, including the actual attached Debugger and registered
308: * breakpoints.
309: */
310: public static void resetDebugger() {
311: debuggerContext = null;
312: }
313: }
|