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.MVEL.compileExpression;
021: import static org.mvel.MVEL.executeExpression;
022: import static org.mvel.NodeType.*;
023: import org.mvel.TemplateCompiler.IncludeRef;
024: import org.mvel.TemplateCompiler.IncludeRefParam;
025: import org.mvel.util.ExecutionStack;
026: import org.mvel.util.StringAppender;
027:
028: import java.io.*;
029: import static java.lang.String.valueOf;
030: import java.nio.ByteBuffer;
031: import static java.nio.ByteBuffer.allocateDirect;
032: import java.nio.channels.ReadableByteChannel;
033: import java.util.*;
034: import static java.util.Collections.synchronizedMap;
035:
036: /**
037: * The MVEL Template Interpreter. 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 TemplateInterpreter {
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 TemplateInterpreter(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: //noinspection unchecked
107: return new TemplateInterpreter(template).execute(null,
108: variables);
109: }
110:
111: /**
112: * Compiles, interprets and returns the result from a template. The value that this returns is dependant
113: * on whether or not the template actually contains any literal values.<br/>
114: * <br/>
115: * For example, an expression that is simply "<tt>@{foobar}</tt>" will return the value of <tt>foobar</tt>,
116: * not a string value. An expression that only contains a single tag is a defacto expression and is not
117: * considered a template.<br/>
118: * <br/>
119: * An expression such as "<tt>Hello my name is: @{name}</tt>" will return the a String value as it clearly a
120: * template.<br/>
121: *
122: * @param template - the template to be evaluated
123: * @param ctx - the virtual root / context of the expression.
124: * @param variables - a map of variables for use in the expression.
125: * @return see description.
126: */
127: public static Object eval(String template, Object ctx, Map variables) {
128: if (template == null)
129: return null;
130: //noinspection unchecked
131: return new TemplateInterpreter(template)
132: .execute(ctx, variables);
133: }
134:
135: private char[] expression;
136: private boolean debug = false;
137: private Node[] nodes;
138: private int node = 0;
139:
140: private static final Map<CharSequence, char[]> EX_PRECACHE;
141: private static final Map<Object, Node[]> EX_NODE_CACHE;
142: private static final Map<Object, Serializable> EX_PRECOMP_CACHE;
143: private static boolean CACHE_DISABLE = false;
144:
145: static {
146: if (MVEL.THREAD_SAFE) {
147: EX_PRECACHE = synchronizedMap(new WeakHashMap<CharSequence, char[]>());
148: EX_NODE_CACHE = synchronizedMap(new WeakHashMap<Object, Node[]>());
149: EX_PRECOMP_CACHE = synchronizedMap(new WeakHashMap<Object, Serializable>());
150: } else if (MVEL.WEAK_CACHE || MVEL.NO_JIT) {
151: EX_PRECACHE = (new WeakHashMap<CharSequence, char[]>());
152: EX_NODE_CACHE = (new WeakHashMap<Object, Node[]>());
153: EX_PRECOMP_CACHE = (new WeakHashMap<Object, Serializable>());
154: } else {
155: EX_PRECACHE = (new HashMap<CharSequence, char[]>());
156: EX_NODE_CACHE = (new HashMap<Object, Node[]>());
157: EX_PRECOMP_CACHE = (new HashMap<Object, Serializable>());
158: }
159: }
160:
161: static void configureFactory() {
162: }
163:
164: private ExecutionStack stack;
165: private ExecutionStack localStack;
166:
167: /**
168: * Creates a new intepreter
169: *
170: * @param template -
171: */
172: public TemplateInterpreter(CharSequence template) {
173: if (CACHE_DISABLE) {
174: nodes = new TemplateCompiler(this ).compileExpression();
175: } else if ((this .expression = EX_PRECACHE.get(template)) == null) {
176: EX_PRECACHE.put(template, this .expression = template
177: .toString().toCharArray());
178: EX_NODE_CACHE.put(template, nodes = new TemplateCompiler(
179: this ).compileExpression());
180: } else {
181: if ((this .nodes = EX_NODE_CACHE.get(expression)) == null) {
182: EX_NODE_CACHE.put(expression,
183: nodes = new TemplateCompiler(this )
184: .compileExpression());
185: }
186: }
187: this .nodes = cloneAll(nodes);
188: }
189:
190: public TemplateInterpreter(String template) {
191: if (CACHE_DISABLE) {
192: this .expression = template.toCharArray();
193: nodes = new TemplateCompiler(this ).compileExpression();
194: } else if ((this .expression = EX_PRECACHE.get(template)) == null) {
195: EX_PRECACHE.put(template, this .expression = template
196: .toCharArray());
197: EX_NODE_CACHE.put(template, nodes = new TemplateCompiler(
198: this ).compileExpression());
199: } else {
200: if ((this .nodes = EX_NODE_CACHE.get(expression)) == null) {
201: EX_NODE_CACHE.put(expression,
202: nodes = new TemplateCompiler(this )
203: .compileExpression());
204: }
205: }
206: this .nodes = cloneAll(nodes);
207: }
208:
209: private Node[] cloneAll(Node[] nodes) {
210: Node[] newNodes = new Node[nodes.length];
211:
212: try {
213: int i = 0;
214: for (Node n : nodes) {
215: newNodes[i++] = n.clone();
216: }
217: } catch (CloneNotSupportedException e) {
218:
219: }
220:
221: return newNodes;
222: }
223:
224: public TemplateInterpreter(char[] expression) {
225: this .expression = expression;
226: }
227:
228: public boolean isDebug() {
229: return debug;
230: }
231:
232: public void setDebug(boolean debug) {
233: this .debug = debug;
234: }
235:
236: public static void parseToStream(File template, Object ctx,
237: Map tokens, OutputStream out) throws IOException {
238:
239: //noinspection unchecked
240: Object result = parse(template, ctx, tokens);
241: CharSequence cs;
242:
243: if (result == null)
244: return;
245: else if (result instanceof CharSequence) {
246: cs = (CharSequence) result;
247: } else {
248: cs = valueOf(result);
249: }
250:
251: OutputStreamWriter writer = new OutputStreamWriter(out);
252:
253: int len = cs.length();
254: for (int i = 0; i < len; i++) {
255: writer.write(cs.charAt(i));
256: }
257: writer.flush();
258: writer.close();
259: }
260:
261: public static Object parse(File file, Object ctx, Map tokens)
262: throws IOException {
263: return parse(file, ctx, tokens, null);
264: }
265:
266: public static Object parse(File file, Object ctx, Map tokens,
267: TemplateRegistry registry) throws IOException {
268:
269: if (!file.exists())
270: throw new CompileException("cannot find file: "
271: + file.getName());
272:
273: FileInputStream inStream = null;
274: ReadableByteChannel fc = null;
275: try {
276: inStream = new FileInputStream(file);
277: fc = inStream.getChannel();
278: ByteBuffer buf = allocateDirect(10);
279:
280: StringAppender sb = new StringAppender((int) file.length());
281:
282: int read = 0;
283: while (read >= 0) {
284: buf.rewind();
285: read = fc.read(buf);
286: buf.rewind();
287:
288: for (; read > 0; read--) {
289: sb.append((char) buf.get());
290: }
291: }
292:
293: //noinspection unchecked
294: return parse(sb, ctx, tokens, registry);
295:
296: } catch (FileNotFoundException e) {
297: // this can't be thrown, we check for this explicitly.
298: } finally {
299: if (inStream != null)
300: inStream.close();
301: if (fc != null)
302: fc.close();
303: }
304:
305: return null;
306: }
307:
308: public static Object parse(CharSequence expression, Object ctx,
309: Map vars) {
310: return parse(expression, ctx, vars, null);
311: }
312:
313: public static Object parse(CharSequence expression, Object ctx,
314: Map vars, TemplateRegistry registry) {
315: if (expression == null)
316: return null;
317: //noinspection unchecked
318: return new TemplateInterpreter(expression).execute(ctx, vars,
319: registry);
320: }
321:
322: public static Object parse(String expression, Object ctx, Map vars) {
323: return parse(expression, ctx, vars, null);
324: }
325:
326: public static Object parse(String expression, Object ctx, Map vars,
327: TemplateRegistry registry) {
328: if (expression == null)
329: return null;
330:
331: //noinspection unchecked
332: return new TemplateInterpreter(expression).execute(ctx, vars,
333: registry);
334: }
335:
336: public Object execute(Object ctx, Map tokens) {
337: return execute(ctx, tokens, null);
338: }
339:
340: public Object execute(Object ctx, Map tokens,
341: TemplateRegistry registry) {
342:
343: if (nodes == null) {
344: return new String(expression);
345: } else if (nodes.length == 2) {
346: /**
347: * This is an optimization for property expressions.
348: */
349: switch (nodes[0].getToken()) {
350: case PROPERTY_EX:
351: //noinspection unchecked
352: if (CACHE_DISABLE || !cacheAggressively) {
353: char[] seg = new char[expression.length - 3];
354: // arraycopy(expression, 2, seg, 0, seg.length);
355: for (int i = 0; i < seg.length; i++)
356: seg[i] = expression[i + 2];
357:
358: return MVEL.eval(seg, ctx, tokens);
359: } else {
360: String s = new String(expression, 2,
361: expression.length - 3);
362: if (!EX_PRECOMP_CACHE.containsKey(s)) {
363: synchronized (EX_PRECOMP_CACHE) {
364: EX_PRECOMP_CACHE.put(s,
365: compileExpression(s));
366: return executeExpression(EX_PRECOMP_CACHE
367: .get(s), ctx, tokens);
368: }
369: } else {
370: return executeExpression(EX_PRECOMP_CACHE
371: .get(s), ctx, tokens);
372: }
373:
374: }
375: case LITERAL:
376: return new String(expression);
377:
378: }
379:
380: return new String(expression);
381: }
382:
383: Object register = null;
384:
385: StringAppender sbuf = new StringAppender(10);
386: Node currNode = null;
387:
388: try {
389: //noinspection unchecked
390: MVELInterpretedRuntime oParser = new MVELInterpretedRuntime(
391: ctx, tokens);
392:
393: initStack();
394: pushAndForward();
395:
396: while ((currNode = pop()) != null) {
397: node = currNode.getNode();
398:
399: switch (currNode.getToken()) {
400: case LITERAL: {
401: sbuf.append(register = new String(expression,
402: currNode.getStartPos(), currNode
403: .getEndPos()
404: - currNode.getStartPos()));
405: break;
406: }
407: case PROPERTY_EX: {
408: sbuf.append(valueOf(register = oParser
409: .setExpressionArray(
410: getInternalSegment(currNode))
411: .parse()));
412: break;
413: }
414: case IF:
415: case ELSEIF: {
416: try {
417: if (!((Boolean) oParser.setExpressionArray(
418: getInternalSegment(currNode)).parse())) {
419: exitContext();
420: }
421: } catch (ClassCastException e) {
422: throw new CompileException(
423: "IF expression does not return a boolean: "
424: + new String(
425: getSegment(currNode)));
426: }
427: break;
428: }
429: case FOREACH: {
430:
431: if (tokens == null) {
432: tokens = new HashMap();
433: }
434:
435: ForeachContext foreachContext;
436: String[] names;
437: String[] aliases;
438:
439: if (!(localStack.peek() instanceof ForeachContext)) {
440:
441: // create a clone of the context
442: foreachContext = ((ForeachContext) currNode
443: .getRegister()).clone();
444: names = foreachContext.getNames();
445: aliases = foreachContext.getAliases();
446:
447: try {
448: Iterator[] iters = new Iterator[names.length];
449: for (int i = 0; i < names.length; i++) {
450: //noinspection unchecked
451: Object listObject = new MVELInterpretedRuntime(
452: names[i], ctx, tokens).parse();
453: if (listObject instanceof Object[]) {
454: listObject = Arrays
455: .asList((Object[]) listObject);
456: }
457:
458: iters[i] = ((Collection) listObject)
459: .iterator(); // this throws null pointer exception in thread race
460: }
461:
462: // set the newly created iterators into the context
463: foreachContext.setIterators(iters);
464:
465: // push the context onto the local stack.
466: localStack.push(foreachContext);
467: } catch (ClassCastException e) {
468: throw new CompileException(
469: "expression for collections does not return a collections object: "
470: + new String(
471: getSegment(currNode)),
472: e);
473: } catch (NullPointerException e) {
474: throw new CompileException(
475: "null returned for foreach in expression: "
476: + (getForEachSegment(currNode)),
477: e);
478: }
479: } else {
480: foreachContext = (ForeachContext) localStack
481: .peek();
482: // names = foreachContext.getNames();
483: aliases = foreachContext.getAliases();
484: }
485:
486: Iterator[] iters = foreachContext.getItererators();
487:
488: if (iters[0].hasNext()) {
489: push();
490:
491: //noinspection unchecked
492: for (int i = 0; i < iters.length; i++) {
493:
494: //noinspection unchecked
495: tokens.put(aliases[i], iters[i].next());
496: }
497:
498: int c;
499: tokens.put("i0", c = foreachContext.getCount());
500:
501: if (c != 0) {
502: sbuf.append(foreachContext.getSeperator());
503: }
504: //noinspection unchecked
505: foreachContext.incrementCount();
506: } else {
507: for (int i = 0; i < iters.length; i++) {
508: tokens.remove(aliases[i]);
509: }
510: // foreachContext.setIterators(null);
511: // foreachContext.setCount(0);
512: localStack.pop();
513: exitContext();
514: }
515: break;
516: }
517: case ELSE:
518: case END:
519: if (stack.isEmpty())
520: forwardAndPush();
521: continue;
522: case GOTO:
523: pushNode(currNode.getEndNode());
524: continue;
525: case TERMINUS: {
526: if (nodes.length != 2) {
527: return sbuf.toString();
528: } else {
529: return register;
530: }
531: }
532: case INCLUDE_BY_REF: {
533: IncludeRef includeRef = (IncludeRef) nodes[node]
534: .getRegister();
535:
536: IncludeRefParam[] params = includeRef.getParams();
537: Map<String, Object> vars = new HashMap<String, Object>(
538: params.length * 2);
539: for (IncludeRefParam param : params) {
540: vars.put(param.getIdentifier(), MVEL.eval(param
541: .getValue(), ctx, tokens));
542: }
543:
544: if (registry == null) {
545: throw new CompileException(
546: "No TemplateRegistry specified, cannot load template='"
547: + includeRef.getName() + "'");
548: }
549: String template = registry.getTemplate(includeRef
550: .getName());
551:
552: if (template == null) {
553: throw new CompileException(
554: "Template does not exist in the TemplateRegistry, cannot load template='"
555: + includeRef.getName() + "'");
556: }
557:
558: sbuf.append(TemplateInterpreter.parse(template,
559: ctx, vars, registry));
560: }
561: }
562:
563: forwardAndPush();
564: }
565: throw new CompileException(
566: "expression did not end properly: expected TERMINUS node");
567: } catch (CompileException e) {
568: throw e;
569: } catch (Exception e) {
570: if (currNode != null) {
571: throw new CompileException(
572: "problem encountered at node ["
573: + currNode.getNode() + "] "
574: + currNode.getToken() + "{"
575: + currNode.getStartPos() + ","
576: + currNode.getEndPos() + "}: "
577: + e.getMessage(), e);
578: }
579: throw new CompileException(
580: "unhandled fatal exception (node:" + node + ")", e);
581: }
582:
583: }
584:
585: private void initStack() {
586: stack = new ExecutionStack();
587: localStack = new ExecutionStack();
588: }
589:
590: private void push() {
591: push(nodes[node]);
592: }
593:
594: private void push(Node node) {
595: if (node == null)
596: return;
597: stack.push(node);
598: }
599:
600: private void pushNode(int i) {
601: stack.push(nodes[i]);
602: }
603:
604: private void exitContext() {
605: node = nodes[node].getEndNode() - 1;
606: }
607:
608: public void forwardAndPush() {
609: node++;
610: push();
611: }
612:
613: private void pushAndForward() {
614: push();
615: node++;
616: }
617:
618: private Node pop() {
619: return (Node) stack.pop();
620: }
621:
622: /**
623: * @param expression -
624: * @param ctx -
625: * @param tokens -
626: * @return -
627: * @deprecated
628: */
629: public static Object getValuePE(String expression, Object ctx,
630: Map tokens) {
631: return new TemplateInterpreter(expression).execute(ctx, tokens);
632: }
633:
634: /**
635: * @param expression -
636: * @param ctx -
637: * @param preParseCx -
638: * @param value -
639: * @deprecated
640: */
641: public static void setValuePE(String expression, Object ctx,
642: Object preParseCx, Object value) {
643: PropertyAccessor.set(ctx,
644: valueOf(eval(expression, preParseCx)), value);
645: }
646:
647: public char[] getExpression() {
648: return expression;
649: }
650:
651: public void setExpression(char[] expression) {
652: this .expression = expression;
653: }
654:
655: private char[] getSegment(Node n) {
656: char[] ca = new char[n.getLength()];
657: //arraycopy(expression, n.getStartPos(), ca, 0, ca.length);
658:
659: int o0 = n.getStartPos();
660: for (int i = 0; i < ca.length; i++)
661: ca[i] = expression[i + o0];
662:
663: return ca;
664: }
665:
666: private char[] getInternalSegment(Node n) {
667: int start = n.getStartPos();
668: int depth = 1;
669:
670: //noinspection StatementWithEmptyBody
671: while ((expression[start++] != '{'))
672: ;
673:
674: int end = start;
675: while (depth > 0) {
676: switch (expression[++end]) {
677: case '}':
678: depth--;
679: break;
680: case '{':
681: depth++;
682: break;
683: }
684: }
685:
686: // char[] ca = new char[end - start];
687: // arraycopy(expression, start, ca, 0, ca.length);
688: // return ca;
689:
690: char[] ca = new char[end - start];
691: for (int i = 0; i < ca.length; i++)
692: ca[i] = expression[i + start];
693:
694: return ca;
695: }
696:
697: private String getForEachSegment(Node n) {
698: if (n.getAlias() == null)
699: return new String(getInternalSegment(n));
700: else {
701: return n.getName();
702: }
703: }
704:
705: public static boolean isCacheAggressively() {
706: return cacheAggressively;
707: }
708:
709: public static void setCacheAggressively(boolean cacheAggressively) {
710: TemplateInterpreter.cacheAggressively = cacheAggressively;
711: }
712:
713: public static void setDisableCache(boolean disableCache) {
714: CACHE_DISABLE = disableCache;
715: }
716: }
|