/** * 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.templates; import org.mvel2.CompileException; import org.mvel2.ParserContext; import org.mvel2.templates.res.*; import org.mvel2.templates.util.TemplateTools; import org.mvel2.util.ExecutionStack; import org.mvel2.util.ParseTools; import java.io.File; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import static org.mvel2.util.ParseTools.balancedCaptureWithLineAccounting; import static org.mvel2.util.ParseTools.subset; /** * The TemplateCompiler class is used for pre-compiling MVEL Templates. To execute a compiled template see * {@link TemplateRuntime} * * @author Mike Brock */ @SuppressWarnings({"ManualArrayCopy"}) public class TemplateCompiler { private char[] template; private int length; private int start; private int cursor; private int lastTextRangeEnding; private int line; private int colStart; private boolean codeCache = false; private Map<String, Class<? extends Node>> customNodes; private ParserContext parserContext; private static final Map<String, Integer> OPCODES = new HashMap<String, Integer>(); static { OPCODES.put("if", Opcodes.IF); OPCODES.put("else", Opcodes.ELSE); OPCODES.put("elseif", Opcodes.ELSE); OPCODES.put("end", Opcodes.END); OPCODES.put("foreach", Opcodes.FOREACH); OPCODES.put("includeNamed", Opcodes.INCLUDE_NAMED); OPCODES.put("include", Opcodes.INCLUDE_FILE); OPCODES.put("comment", Opcodes.COMMENT); OPCODES.put("code", Opcodes.CODE); OPCODES.put("eval", Opcodes.EVAL); OPCODES.put("declare", Opcodes.DECLARE); OPCODES.put("stop", Opcodes.STOP); } public CompiledTemplate compile() { return new CompiledTemplate(template, compileFrom(null, new ExecutionStack())); } public Node compileFrom(Node root, ExecutionStack stack) { line = 1; Node n = root; if (root == null) { n = root = new TextNode(0, 0); } IfNode last; Integer opcode; String name; int x; try { while (cursor < length) { switch (template[cursor]) { case '\n': line++; colStart = cursor + 1; break; case '@': case '$': if (isNext(template[cursor])) { start = ++cursor; (n = markTextNode(n)).setEnd(n.getEnd() + 1); start = lastTextRangeEnding = ++cursor; continue; } if ((x = captureOrbToken()) != -1) { start = x; switch ((opcode = OPCODES.get(name = new String(capture()))) == null ? 0 : opcode) { case Opcodes.IF: /** * Capture any residual text node, and push the if statement on the nesting stack. */ stack.push(n = markTextNode(n).next = codeCache ? new CompiledIfNode(start, name, template, captureOrbInternal(), start, parserContext) : new IfNode(start, name, template, captureOrbInternal(), start)); n.setTerminus(new TerminalNode()); break; case Opcodes.ELSE: if (!stack.isEmpty() && stack.peek() instanceof IfNode) { markTextNode(n).next = (last = (IfNode) stack.pop()).getTerminus(); last.demarcate(last.getTerminus(), template); last.next = n = codeCache ? new CompiledIfNode(start, name, template, captureOrbInternal(), start, parserContext) : new IfNode(start, name, template, captureOrbInternal(), start); n.setTerminus(last.getTerminus()); stack.push(n); } break; case Opcodes.FOREACH: stack.push( n = markTextNode(n).next = codeCache ? new CompiledForEachNode(start, name, template, captureOrbInternal(), start, parserContext) : new ForEachNode(start, name, template, captureOrbInternal(), start) ); n.setTerminus(new TerminalNode()); break; case Opcodes.INCLUDE_FILE: n = markTextNode(n).next = codeCache ? new CompiledIncludeNode(start, name, template, captureOrbInternal(), start = cursor + 1, parserContext) : new IncludeNode(start, name, template, captureOrbInternal(), start = cursor + 1); break; case Opcodes.INCLUDE_NAMED: n = markTextNode(n).next = codeCache ? new CompiledNamedIncludeNode(start, name, template, captureOrbInternal(), start = cursor + 1, parserContext) : new NamedIncludeNode(start, name, template, captureOrbInternal(), start = cursor + 1); break; case Opcodes.CODE: n = markTextNode(n) .next = codeCache ? new CompiledCodeNode(start, name, template, captureOrbInternal(), start = cursor + 1, parserContext) : new CodeNode(start, name, template, captureOrbInternal(), start = cursor + 1); break; case Opcodes.EVAL: n = markTextNode(n).next = codeCache ? new CompiledEvalNode(start, name, template, captureOrbInternal(), start = cursor + 1, parserContext) : new EvalNode(start, name, template, captureOrbInternal(), start = cursor + 1); break; case Opcodes.COMMENT: n = markTextNode(n) .next = new CommentNode(start, name, template, captureOrbInternal(), start = cursor + 1); break; case Opcodes.DECLARE: stack.push(n = markTextNode(n).next = codeCache ? new CompiledDeclareNode(start, name, template, captureOrbInternal(), start = cursor + 1, parserContext) : new DeclareNode(start, name, template, captureOrbInternal(), start = cursor + 1)); n.setTerminus(new TerminalNode()); break; case Opcodes.END: n = markTextNode(n); Node end = (Node) stack.pop(); Node terminal = end.getTerminus(); terminal.setCStart(captureOrbInternal()); terminal.setEnd((lastTextRangeEnding = start) - 1); terminal.calculateContents(template); if (end.demarcate(terminal, template)) n = n.next = terminal; else n = terminal; break; default: if (name.length() == 0) { n = markTextNode(n).next = codeCache ? new CompiledExpressionNode(start, name, template, captureOrbInternal(), start = cursor + 1, parserContext) : new ExpressionNode(start, name, template, captureOrbInternal(), start = cursor + 1); } else if (customNodes != null && customNodes.containsKey(name)) { Class<? extends Node> customNode = customNodes.get(name); try { (n = markTextNode(n).next = (customNode.newInstance())).setBegin(start); n.setName(name); n.setCStart(captureOrbInternal()); n.setCEnd(start = cursor + 1); n.setEnd(n.getCEnd()); n.setContents(subset(template, n.getCStart(), n.getCEnd() - n.getCStart() - 1)); if (n.isOpenNode()) { stack.push(n); } } catch (InstantiationException e) { throw new RuntimeException("unable to instantiate custom node class: " + customNode.getName()); } catch (IllegalAccessException e) { throw new RuntimeException("unable to instantiate custom node class: " + customNode.getName()); } } else { throw new RuntimeException("unknown token type: " + name); } } } break; } cursor++; } } catch (RuntimeException e) { CompileException ce = new CompileException(e.getMessage(), template, cursor, e); ce.setExpr(template); if (e instanceof CompileException) { CompileException ce2 = (CompileException) e; if (ce2.getCursor() != -1) { ce.setCursor(ce2.getCursor()); if (ce2.getColumn() == -1) ce.setColumn(ce.getCursor() - colStart); else ce.setColumn(ce2.getColumn()); } } ce.setLineNumber(line); throw ce; } if (!stack.isEmpty()) { CompileException ce = new CompileException("unclosed @" + ((Node) stack.peek()).getName() + "{} block. expected @end{}", template, cursor); ce.setColumn(cursor - colStart); ce.setLineNumber(line); throw ce; } if (start < template.length) { n = n.next = new TextNode(start, template.length); } n.next = new EndNode(); n = root; do { if (n.getLength() != 0) { break; } } while ((n = n.getNext()) != null); if (n != null && n.getLength() == template.length - 1) { if (n instanceof ExpressionNode) { return codeCache ? new CompiledTerminalExpressionNode(n, parserContext) : new TerminalExpressionNode(n); } else { return n; } } return root; } // Parse Utilities Start Here private boolean isNext(char c) { return cursor != length && template[cursor + 1] == c; } private int captureOrbToken() { int newStart = ++cursor; while ((cursor != length) && ParseTools.isIdentifierPart(template[cursor])) cursor++; if (cursor != length && template[cursor] == '{') return newStart; return -1; } private int captureOrbInternal() { try { ParserContext pCtx = new ParserContext(); cursor = balancedCaptureWithLineAccounting(template, start = cursor, length, '{', pCtx); line += pCtx.getLineCount(); int ret = start + 1; start = cursor + 1; return ret; } catch (CompileException e) { e.setLineNumber(line); e.setColumn((cursor - colStart) + 1); throw e; } } private char[] capture() { char[] newChar = new char[cursor - start]; for (int i = 0; i < newChar.length; i++) { newChar[i] = template[i + start]; } return newChar; } private Node markTextNode(Node n) { int s = (n.getEnd() > lastTextRangeEnding ? n.getEnd() : lastTextRangeEnding); if (s < start) { return n.next = new TextNode(s, lastTextRangeEnding = start - 1); } return n; } public ParserContext getParserContext() { return parserContext; } public static CompiledTemplate compileTemplate(String template) { return new TemplateCompiler(template, true, ParserContext.create()).compile(); } public static CompiledTemplate compileTemplate(char[] template) { return new TemplateCompiler(template, true, ParserContext.create()).compile(); } public static CompiledTemplate compileTemplate(CharSequence template) { return new TemplateCompiler(template, true, ParserContext.create()).compile(); } public static CompiledTemplate compileTemplate(String template, ParserContext context) { return new TemplateCompiler(template, true, context).compile(); } public static CompiledTemplate compileTemplate(char[] template, ParserContext context) { return new TemplateCompiler(template, true, context).compile(); } public static CompiledTemplate compileTemplate(CharSequence template, ParserContext context) { return new TemplateCompiler(template, true, context).compile(); } public static CompiledTemplate compileTemplate(String template, Map<String, Class<? extends Node>> customNodes) { return new TemplateCompiler(template, customNodes, true, ParserContext.create()).compile(); } public static CompiledTemplate compileTemplate(char[] template, Map<String, Class<? extends Node>> customNodes) { return new TemplateCompiler(template, customNodes, true, ParserContext.create()).compile(); } public static CompiledTemplate compileTemplate(CharSequence template, Map<String, Class<? extends Node>> customNodes) { return new TemplateCompiler(template, customNodes, true, ParserContext.create()).compile(); } public static CompiledTemplate compileTemplate(String template, Map<String, Class<? extends Node>> customNodes, ParserContext context) { return new TemplateCompiler(template, customNodes, true, context).compile(); } public static CompiledTemplate compileTemplate(char[] template, Map<String, Class<? extends Node>> customNodes, ParserContext context) { return new TemplateCompiler(template, customNodes, true, context).compile(); } public static CompiledTemplate compileTemplate(CharSequence template, Map<String, Class<? extends Node>> customNodes, ParserContext context) { return new TemplateCompiler(template, customNodes, true, context).compile(); } public static CompiledTemplate compileTemplate(InputStream stream) { return compileTemplate(stream, ParserContext.create()); } public static CompiledTemplate compileTemplate(InputStream stream, ParserContext context) { return compileTemplate(stream, null, context); } public static CompiledTemplate compileTemplate(InputStream stream, Map<String, Class<? extends Node>> customNodes) { return new TemplateCompiler(TemplateTools.readStream(stream), customNodes, true, ParserContext.create()).compile(); } public static CompiledTemplate compileTemplate(InputStream stream, Map<String, Class<? extends Node>> customNodes, ParserContext context) { return new TemplateCompiler(TemplateTools.readStream(stream), customNodes, true, context).compile(); } public static CompiledTemplate compileTemplate(File file) { return compileTemplate(file, ParserContext.create()); } public static CompiledTemplate compileTemplate(File file, ParserContext context) { return compileTemplate(file, null, context); } public static CompiledTemplate compileTemplate(File file, Map<String, Class<? extends Node>> customNodes) { return new TemplateCompiler(TemplateTools.readInFile(file), customNodes, true, ParserContext.create()).compile(); } public static CompiledTemplate compileTemplate(File file, Map<String, Class<? extends Node>> customNodes, ParserContext context) { return new TemplateCompiler(TemplateTools.readInFile(file), customNodes, true, context).compile(); } public TemplateCompiler(String template) { this.length = (this.template = template.toCharArray()).length; } public TemplateCompiler(char[] template) { this.length = (this.template = template).length; } public TemplateCompiler(String template, boolean codeCache) { this.length = (this.template = template.toCharArray()).length; this.codeCache = codeCache; } public TemplateCompiler(char[] template, boolean codeCache) { this.length = (this.template = template).length; this.codeCache = codeCache; } public TemplateCompiler(char[] template, boolean codeCache, ParserContext context) { this.length = (this.template = template).length; this.codeCache = codeCache; this.parserContext = context; } public TemplateCompiler(CharSequence sequence) { this.length = (this.template = sequence.toString().toCharArray()).length; } public TemplateCompiler(CharSequence sequence, boolean codeCache) { this.length = (this.template = sequence.toString().toCharArray()).length; this.codeCache = codeCache; } public TemplateCompiler(CharSequence sequence, boolean codeCache, ParserContext context) { this.length = (this.template = sequence.toString().toCharArray()).length; this.codeCache = codeCache; this.parserContext = context; } public TemplateCompiler(String template, Map<String, Class<? extends Node>> customNodes) { this.length = (this.template = template.toCharArray()).length; this.customNodes = customNodes; } public TemplateCompiler(char[] template, Map<String, Class<? extends Node>> customNodes) { this.length = (this.template = template).length; this.customNodes = customNodes; } public TemplateCompiler(CharSequence sequence, Map<String, Class<? extends Node>> customNodes) { this.length = (this.template = sequence.toString().toCharArray()).length; this.customNodes = customNodes; } public TemplateCompiler(String template, Map<String, Class<? extends Node>> customNodes, boolean codeCache) { this.length = (this.template = template.toCharArray()).length; this.customNodes = customNodes; this.codeCache = codeCache; } public TemplateCompiler(char[] template, Map<String, Class<? extends Node>> customNodes, boolean codeCache) { this.length = (this.template = template).length; this.customNodes = customNodes; this.codeCache = codeCache; } public TemplateCompiler(CharSequence sequence, Map<String, Class<? extends Node>> customNodes, boolean codeCache) { this.length = (this.template = sequence.toString().toCharArray()).length; this.customNodes = customNodes; this.codeCache = codeCache; } public TemplateCompiler(String template, Map<String, Class<? extends Node>> customNodes, boolean codeCache, ParserContext context) { this.length = (this.template = template.toCharArray()).length; this.customNodes = customNodes; this.codeCache = codeCache; this.parserContext = context; } public TemplateCompiler(char[] template, Map<String, Class<? extends Node>> customNodes, boolean codeCache, ParserContext context) { this.length = (this.template = template).length; this.customNodes = customNodes; this.codeCache = codeCache; this.parserContext = context; } public TemplateCompiler(CharSequence sequence, Map<String, Class<? extends Node>> customNodes, boolean codeCache, ParserContext context) { this.length = (this.template = sequence.toString().toCharArray()).length; this.customNodes = customNodes; this.codeCache = codeCache; this.parserContext = context; } }