001: /**
002: * MVEL (The MVFLEX Expression Language)
003: *
004: * Copyright (C) 2007 Christopher Brock, MVFLEX/Valhalla Project and the Codehaus
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: *
018: */package org.mvel;
019:
020: import static org.mvel.NodeType.*;
021: import org.mvel.util.ExecutionStack;
022: import org.mvel.util.StringAppender;
023:
024: import java.io.*;
025: import static java.lang.String.valueOf;
026: import static java.lang.System.arraycopy;
027: import java.nio.ByteBuffer;
028: import static java.nio.ByteBuffer.allocateDirect;
029: import java.nio.channels.ReadableByteChannel;
030: import java.util.Collection;
031: import static java.util.Collections.synchronizedMap;
032: import java.util.Iterator;
033: import java.util.Map;
034: import java.util.WeakHashMap;
035:
036: /**
037: * The MVEL Template Interpreter. Naming this an "Interpreter" is not inaccurate. All template expressions
038: * are pre-compiled by the the {@link TemplateCompiler} prior to being processed by this interpreter.<br/>
039: * <br/>
040: * Under normal circumstances, it is completely acceptable to execute the parser/interpreter from the static
041: * convenience methods in this class.
042: *
043: * @author Christopher Brock
044: */
045: public class Interpreter {
046:
047: public static boolean cacheAggressively = false;
048:
049: /**
050: * Evaluates the template expression and returns a String value. This is only a convenience method that
051: * has the same semantics as using <tt>String.valueOf(eval(expr, vars, ctx))</tt>.
052: *
053: * @param template - the template to be evaluated
054: * @param ctx - the virtual root / context of the expression.
055: * @return the resultant value represented in it's equivelant string value.
056: */
057: public static String evalToString(String template, Object ctx) {
058: return valueOf(eval(template, ctx));
059: }
060:
061: /**
062: * Evaluates the template expression and returns a String value. This is only a convenience method that
063: * has the same semantics as using <tt>String.valueOf(eval(expr, vars, ctx))</tt>.
064: *
065: * @param template - the template to be evaluated
066: * @param variables - a map of variables for use in the expression.
067: * @return the resultant value represented in it's equivelant string value.
068: */
069: public static String evalToString(String template, Map variables) {
070: return valueOf(eval(template, variables));
071: }
072:
073: /**
074: * Evaluates the template expression and returns a String value. This is only a convenience method that
075: * has the same semantics as using <tt>String.valueOf(eval(expr, vars, ctx))</tt>.
076: *
077: * @param template - the template to be evaluated
078: * @param ctx - the virtual root / context of the expression.
079: * @param variables - a map of variables for use in the expression.
080: * @return the resultant value represented in it's equivelant string value.
081: */
082: public static String evalToString(String template, Object ctx,
083: Map variables) {
084: return valueOf(eval(template, ctx, variables));
085: }
086:
087: /**
088: * @param template - the template to be evaluated
089: * @param ctx - the virtual root / context of the expression.
090: * @return see description.
091: * @see #eval(String,Object,Map)
092: */
093: public static Object eval(String template, Object ctx) {
094: if (template == null)
095: return null;
096: return new Interpreter(template).execute(ctx, null);
097: }
098:
099: /**
100: * @param template - the template to be evaluated
101: * @param variables - a map of variables for use in the expression.
102: * @return see description.
103: * @see #eval(String,Object,Map)
104: */
105: public static Object eval(String template, Map variables) {
106: return new Interpreter(template).execute(null, variables);
107: }
108:
109: /**
110: * Compiles, interprets and returns the result from a template. The value that this returns is dependant
111: * on whether or not the template actually contains any literal values.<br/>
112: * <br/>
113: * For example, an expression that is simply "<tt>@{foobar}</tt>" will return the value of <tt>foobar</tt>,
114: * not a string value. An expression that only contains a single tag is a defacto expression and is not
115: * considered a template.<br/>
116: * <br/>
117: * An expression such as "<tt>Hello my name is: @{name}</tt>" will return the a String value as it clearly a
118: * template.<br/>
119: *
120: * @param template - the template to be evaluated
121: * @param ctx - the virtual root / context of the expression.
122: * @param variables - a map of variables for use in the expression.
123: * @return see description.
124: */
125: public static Object eval(String template, Object ctx, Map variables) {
126: if (template == null)
127: return null;
128: //noinspection unchecked
129: return new Interpreter(template).execute(ctx, variables);
130: }
131:
132: private char[] expression;
133: private boolean debug = false;
134: private Node[] nodes;
135: private int node = 0;
136:
137: private static Map<CharSequence, char[]> EX_PRECACHE;
138: private static Map<Object, Node[]> EX_NODE_CACHE;
139: private static Map<Object, Serializable> EX_PRECOMP_CACHE;
140:
141: static {
142: configureFactory();
143: }
144:
145: static void configureFactory() {
146: if (MVEL.THREAD_SAFE) {
147: EX_PRECACHE = synchronizedMap(new WeakHashMap<CharSequence, char[]>());
148: EX_NODE_CACHE = synchronizedMap(EX_NODE_CACHE = new WeakHashMap<Object, Node[]>());
149: EX_PRECOMP_CACHE = synchronizedMap(EX_PRECOMP_CACHE = new WeakHashMap<Object, Serializable>());
150: } else {
151: EX_PRECACHE = (new WeakHashMap<CharSequence, char[]>());
152: EX_NODE_CACHE = (EX_NODE_CACHE = new WeakHashMap<Object, Node[]>());
153: EX_PRECOMP_CACHE = (EX_PRECOMP_CACHE = new WeakHashMap<Object, Serializable>());
154: }
155: }
156:
157: private ExecutionStack stack;
158:
159: /**
160: * Creates a new intepreter
161: *
162: * @param template -
163: */
164: public Interpreter(CharSequence template) {
165: if (!EX_PRECACHE.containsKey(template)) {
166: EX_PRECACHE.put(template, this .expression = template
167: .toString().toCharArray());
168: nodes = new TemplateCompiler(this ).compileExpression();
169: EX_NODE_CACHE.put(template, nodes.clone());
170: } else {
171: this .expression = EX_PRECACHE.get(template);
172: try {
173: this .nodes = EX_NODE_CACHE.get(expression).clone();
174: } catch (NullPointerException e) {
175: EX_NODE_CACHE.remove(expression);
176: nodes = new TemplateCompiler(this ).compileExpression();
177: EX_NODE_CACHE.put(expression, nodes.clone());
178: }
179:
180: }
181: cloneAllNodes();
182:
183: }
184:
185: public Interpreter(String expression) {
186: if (!EX_PRECACHE.containsKey(expression)) {
187: EX_PRECACHE.put(expression, this .expression = expression
188: .toCharArray());
189: nodes = new TemplateCompiler(this ).compileExpression();
190: EX_NODE_CACHE.put(expression, nodes.clone());
191: } else {
192: this .expression = EX_PRECACHE.get(expression);
193: try {
194: this .nodes = EX_NODE_CACHE.get(expression).clone();
195: } catch (NullPointerException e) {
196: EX_NODE_CACHE.remove(expression);
197: nodes = new TemplateCompiler(this ).compileExpression();
198: EX_NODE_CACHE.put(expression, nodes.clone());
199: }
200:
201: }
202: cloneAllNodes();
203: }
204:
205: private void cloneAllNodes() {
206: try {
207: for (int i = 0; i < nodes.length; i++) {
208: nodes[i] = nodes[i].clone();
209: }
210: } catch (Exception e) {
211: throw new CompileException("unknown exception", e);
212: }
213: }
214:
215: public Interpreter(char[] expression) {
216: this .expression = expression;
217: }
218:
219: public boolean isDebug() {
220: return debug;
221: }
222:
223: public void setDebug(boolean debug) {
224: this .debug = debug;
225: }
226:
227: public static void parseToStream(File template, Object ctx,
228: Map<String, Object> tokens, OutputStream out)
229: throws IOException {
230: Object result = parse(template, ctx, tokens);
231: CharSequence cs;
232:
233: if (result == null)
234: return;
235: else if (result instanceof CharSequence) {
236: cs = (CharSequence) result;
237: } else {
238: cs = valueOf(result);
239: }
240:
241: OutputStreamWriter writer = new OutputStreamWriter(out);
242:
243: int len = cs.length();
244: for (int i = 0; i < len; i++) {
245: writer.write(cs.charAt(i));
246: }
247: writer.flush();
248: writer.close();
249: }
250:
251: public static Object parse(File file, Object ctx,
252: Map<String, Object> tokens) throws IOException {
253: if (!file.exists())
254: throw new CompileException("cannot find file: "
255: + file.getName());
256:
257: FileInputStream inStream = null;
258: ReadableByteChannel fc = null;
259: try {
260: inStream = new FileInputStream(file);
261: fc = inStream.getChannel();
262: ByteBuffer buf = allocateDirect(10);
263:
264: StringAppender sb = new StringAppender((int) file.length());
265:
266: int read = 0;
267: while (read >= 0) {
268: buf.rewind();
269: read = fc.read(buf);
270: buf.rewind();
271:
272: for (; read > 0; read--) {
273: sb.append((char) buf.get());
274: }
275: }
276:
277: return parse(sb, ctx, tokens);
278:
279: } catch (FileNotFoundException e) {
280: // this can't be thrown, we check for this explicitly.
281: } finally {
282: if (inStream != null)
283: inStream.close();
284: if (fc != null)
285: fc.close();
286: }
287:
288: return null;
289: }
290:
291: public static Object parse(CharSequence expression, Object ctx,
292: Map<String, Object> vars) {
293: if (expression == null)
294: return null;
295: return new Interpreter(expression).execute(ctx, vars);
296: }
297:
298: public static Object parse(String expression, Object ctx,
299: Map<String, Object> vars) {
300: if (expression == null)
301: return null;
302:
303: return new Interpreter(expression).execute(ctx, vars);
304: }
305:
306: public Object execute(Object ctx, Map tokens) {
307: if (nodes == null) {
308: return new String(expression);
309: } else if (nodes.length == 2) {
310: switch (nodes[0].getToken()) {
311: case PROPERTY_EX:
312: //noinspection unchecked
313: // return ExpressionParser.eval(getInternalSegment(nodes[0]), ctx, tokens);
314:
315: if (!cacheAggressively) {
316: char[] seg = new char[expression.length - 3];
317: arraycopy(expression, 2, seg, 0, seg.length);
318:
319: return MVEL.eval(seg, ctx, tokens);
320: } else {
321: String s = new String(expression, 2,
322: expression.length - 3);
323: if (!EX_PRECOMP_CACHE.containsKey(s)) {
324: EX_PRECOMP_CACHE.put(s, MVEL
325: .compileExpression(s));
326: }
327:
328: return MVEL.executeExpression(EX_PRECOMP_CACHE
329: .get(s), ctx, tokens);
330:
331: }
332: case LITERAL:
333: return new String(expression);
334: }
335:
336: return new String(expression);
337: }
338:
339: Object register = null;
340:
341: StringAppender sbuf = new StringAppender(10);
342: Node currNode = null;
343:
344: try {
345: ExpressionParser oParser = new ExpressionParser(ctx, tokens);
346:
347: initStack();
348: pushAndForward();
349:
350: while ((currNode = pop()) != null) {
351: node = currNode.getNode();
352:
353: switch (currNode.getToken()) {
354: case LITERAL: {
355: sbuf.append(register = new String(expression,
356: currNode.getStartPos(), currNode
357: .getEndPos()
358: - currNode.getStartPos()));
359: break;
360: }
361: case PROPERTY_EX: {
362: sbuf.append(valueOf(register = oParser
363: .setExpressionArray(
364: getInternalSegment(currNode))
365: .parse()));
366: break;
367: }
368: case IF:
369: case ELSEIF: {
370: try {
371: if (!((Boolean) oParser.setExpressionArray(
372: getInternalSegment(currNode)).parse())) {
373: exitContext();
374: }
375: } catch (ClassCastException e) {
376: throw new CompileException(
377: "IF expression does not return a boolean: "
378: + new String(
379: getSegment(currNode)));
380: }
381: break;
382: }
383:
384: case FOREACH: {
385: if (currNode.getRegister() == null) {
386: try {
387: currNode
388: .setRegister(((Collection) new ExpressionParser(
389: getForEachSegment(currNode),
390: ctx, tokens).parse())
391: .iterator());
392: } catch (ClassCastException e) {
393: throw new CompileException(
394: "expression for collections does not return a collections object: "
395: + new String(
396: getSegment(currNode)));
397: } catch (NullPointerException e) {
398: throw new CompileException(
399: "null returned for foreach in expression: "
400: + (getForEachSegment(currNode)));
401: }
402: }
403:
404: Iterator iter = (Iterator) currNode.getRegister();
405: if (iter.hasNext()) {
406: push();
407: //noinspection unchecked
408: tokens.put(currNode.getAlias(), iter.next());
409: } else {
410: tokens.remove(currNode.getAlias());
411: exitContext();
412: }
413: break;
414: }
415: case ELSE:
416: case END:
417: if (stack.isEmpty())
418: forwardAndPush();
419: continue;
420: case GOTO:
421: pushNode(currNode.getEndNode());
422: continue;
423: case TERMINUS: {
424: if (nodes.length == 2) {
425: return register;
426: } else {
427: return sbuf.toString();
428: }
429: }
430: }
431:
432: forwardAndPush();
433: }
434: throw new CompileException(
435: "expression did not end properly: expected TERMINUS node");
436: } catch (CompileException e) {
437: throw e;
438: } catch (Exception e) {
439: if (currNode != null) {
440: throw new CompileException(
441: "problem encountered at node ["
442: + currNode.getNode() + "] "
443: + currNode.getToken() + "{"
444: + currNode.getStartPos() + ","
445: + currNode.getEndPos() + "}", e);
446: }
447: throw new CompileException(
448: "unhandled fatal exception (node:" + node + ")", e);
449: }
450: }
451:
452: private void initStack() {
453: stack = new ExecutionStack();
454: }
455:
456: private void push() {
457: push(nodes[node]);
458: }
459:
460: private void push(Node node) {
461: if (node == null)
462: return;
463: stack.push(node);
464: }
465:
466: private void pushNode(int i) {
467: stack.push(nodes[i]);
468: }
469:
470: private void exitContext() {
471: node = nodes[node].getEndNode();
472: }
473:
474: public void forwardAndPush() {
475: node++;
476: push();
477: }
478:
479: private void pushAndForward() {
480: push();
481: node++;
482: }
483:
484: private Node pop() {
485: return (Node) stack.pop();
486: }
487:
488: /**
489: * @param expression -
490: * @param ctx -
491: * @param tokens -
492: * @return -
493: * @deprecated
494: */
495: public static Object getValuePE(String expression, Object ctx,
496: Map<String, Object> tokens) {
497: return new Interpreter(expression).execute(ctx, tokens);
498: }
499:
500: /**
501: * @param expression -
502: * @param ctx -
503: * @param preParseCx -
504: * @param value -
505: * @deprecated
506: */
507: public static void setValuePE(String expression, Object ctx,
508: Object preParseCx, Object value) {
509: PropertyAccessor.set(ctx,
510: valueOf(eval(expression, preParseCx)), value);
511: }
512:
513: public char[] getExpression() {
514: return expression;
515: }
516:
517: public void setExpression(char[] expression) {
518: this .expression = expression;
519: }
520:
521: private char[] getSegment(Node n) {
522: char[] ca = new char[n.getLength()];
523: arraycopy(expression, n.getStartPos(), ca, 0, ca.length);
524: return ca;
525: }
526:
527: private char[] getInternalSegment(Node n) {
528: int start = n.getStartPos();
529: int depth = 1;
530:
531: //noinspection StatementWithEmptyBody
532: while ((expression[start++] != '{'))
533: ;
534:
535: int end = start;
536: while (depth > 0) {
537: switch (expression[++end]) {
538: case '{':
539: depth++;
540: break;
541: case '}':
542: depth--;
543: break;
544: }
545: }
546:
547: char[] ca = new char[end - start];
548: arraycopy(expression, start, ca, 0, ca.length);
549: return ca;
550: }
551:
552: private String getForEachSegment(Node n) {
553: if (n.getAlias() == null)
554: return new String(getInternalSegment(n));
555: else {
556: return n.getName();
557: }
558: }
559:
560: public static boolean isCacheAggressively() {
561: return cacheAggressively;
562: }
563:
564: public static void setCacheAggressively(boolean cacheAggressively) {
565: Interpreter.cacheAggressively = cacheAggressively;
566: }
567: }
|