001: /*
002: * This file is part of PFIXCORE.
003: *
004: * PFIXCORE is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU Lesser General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * PFIXCORE is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public License
015: * along with PFIXCORE; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package de.schlund.pfixcore.scriptedflow.compiler;
020:
021: import java.io.IOException;
022: import java.util.ArrayList;
023: import java.util.List;
024:
025: import org.w3c.dom.Document;
026: import org.w3c.dom.Element;
027: import org.w3c.dom.NamedNodeMap;
028: import org.w3c.dom.Node;
029: import org.w3c.dom.NodeList;
030: import org.xml.sax.SAXException;
031:
032: import de.schlund.pfixcore.scriptedflow.vm.Instruction;
033: import de.schlund.pfixcore.scriptedflow.vm.JumpInstruction;
034: import de.schlund.pfixcore.scriptedflow.vm.NopInstruction;
035: import de.schlund.pfixcore.scriptedflow.vm.Script;
036: import de.schlund.pfixcore.scriptedflow.vm.pvo.DynamicObject;
037: import de.schlund.pfixcore.scriptedflow.vm.pvo.ListObject;
038: import de.schlund.pfixcore.scriptedflow.vm.pvo.ParamValueObject;
039: import de.schlund.pfixcore.scriptedflow.vm.pvo.StaticObject;
040: import de.schlund.pfixxml.resources.FileResource;
041: import de.schlund.pfixxml.util.Xml;
042:
043: /**
044: * Produces a low-level language Script object as it is understood
045: * by the scripting VM.
046: *
047: * @author Sebastian Marsching <sebastian.marsching@1und1.de>
048: * @see de.schlund.pfixcore.scriptedflow.vm.ScriptVM
049: */
050: public class Compiler {
051: public final static String NS_SCRIPTEDFLOW = "http://pustefix.sourceforge.net/scriptedflow200602";
052:
053: public static Script compile(FileResource scriptFile)
054: throws CompilerException {
055: Document doc;
056: try {
057: doc = Xml.parseMutable(scriptFile);
058: } catch (SAXException e) {
059: throw new CompilerException(
060: "XML parser could not parse file "
061: + scriptFile.toString(), e);
062: } catch (IOException e) {
063: throw new CompilerException(
064: "XML parser could not read file "
065: + scriptFile.toString(), e);
066: }
067:
068: Element root = doc.getDocumentElement();
069: if (!root.getLocalName().equals("scriptedflow")
070: || !root.getNamespaceURI().equals(NS_SCRIPTEDFLOW)) {
071: throw new CompilerException("Input file "
072: + scriptFile.toString()
073: + " is not a scripted flow!");
074: }
075:
076: // Check version
077: String version = root.getAttribute("version");
078:
079: // Assume default version 1.0
080: if (version == null) {
081: version = "1.0";
082: }
083:
084: if (!version.equals("1.0")) {
085: throw new CompilerException("Script file \""
086: + scriptFile.toString() + "\" uses version "
087: + version
088: + " but compiler only supports version 1.0");
089: }
090:
091: // Check name
092: String name = root.getAttribute("name");
093:
094: if (name == null || name.equals("")) {
095: name = "anonymous";
096: }
097:
098: NodeList children = root.getChildNodes();
099:
100: // root block containing all instructions
101: BlockStatement block = blockStatementFromNodeList(null,
102: children);
103: // make sure block ends with exit instruction
104: block.addStatement(new ExitStatement(block));
105:
106: // Optimize code
107: Instruction[] temp = block.getInstructions();
108: temp = removeNops(temp);
109:
110: return new Script(temp, name);
111: }
112:
113: private static Instruction[] removeNops(Instruction[] instructions) {
114: List<Instruction> instr = new ArrayList<Instruction>();
115: for (int i = 0; i < instructions.length; i++) {
116: instr.add(instructions[i]);
117: }
118:
119: for (int i = 0; i < instr.size(); i++) {
120: Instruction in = instr.get(i);
121: if (in instanceof NopInstruction) {
122: // Get following instruction
123: // This is save as we know that
124: // last instruction is never a NOP
125: Instruction in2 = instr.get(i + 1);
126:
127: // Update references
128: for (Instruction ref : instr) {
129: if (ref instanceof JumpInstruction) {
130: JumpInstruction jump = (JumpInstruction) ref;
131: if (jump.getTargetInstruction() == in) {
132: jump.setTargetInstruction(in2);
133: }
134: }
135: }
136:
137: // Delete NOP
138: instr.remove(i);
139:
140: // Decrement to make sure we examine next
141: // instruction (which is now i, not i+1)
142: i--;
143: }
144: }
145:
146: Instruction[] temp = new Instruction[instr.size()];
147: for (int i = 0; i < temp.length; i++) {
148: temp[i] = instr.get(i);
149: }
150:
151: return temp;
152: }
153:
154: private static Statement statementFromElement(Statement parent,
155: Element element) throws CompilerException {
156: if (!element.getNamespaceURI().equals(NS_SCRIPTEDFLOW)) {
157: throw new CompilerException("Namespace "
158: + element.getNamespaceURI()
159: + " cannot be handled by the compiler");
160: }
161: String elementName = element.getLocalName();
162: if (elementName.equals("if")) {
163: return ifStatementFromElement(parent, element);
164: } else if (elementName.equals("while")) {
165: return whileStatementFromElement(parent, element);
166: } else if (elementName.equals("break")) {
167: return breakStatementFromElement(parent, element);
168: } else if (elementName.equals("choose")) {
169: return chooseStatementFromElement(parent, element);
170: } else if (elementName.equals("interactive-request")) {
171: return interactiveRequestStatementFromElement(parent,
172: element);
173: } else if (elementName.equals("virtual-request")) {
174: return virtualRequestStatementFromElement(parent, element);
175: } else if (elementName.equals("set-variable")) {
176: return setVariableStatementFromElement(parent, element);
177: } else if (elementName.equals("exit")) {
178: return exitStatementFromElement(parent, element);
179: } else {
180: throw new CompilerException("Found unknown command \""
181: + elementName + "\"");
182: }
183: }
184:
185: private static void addParametersToStatement(Element element,
186: ParameterizedStatement stmt) throws CompilerException {
187: NodeList list = element.getChildNodes();
188: for (int i = 0; i < list.getLength(); i++) {
189: Node childNode = list.item(i);
190: if (childNode.getNodeType() == Node.ELEMENT_NODE) {
191: Element child = (Element) childNode;
192: if (!child.getNamespaceURI().equals(NS_SCRIPTEDFLOW)) {
193: throw new CompilerException("Namespace "
194: + child.getNamespaceURI()
195: + " cannot be handled by the compiler");
196: }
197: if (child.getLocalName().equals("param")) {
198: String paramName = child.getAttribute("name");
199: if (paramName == null || paramName.length() == 0) {
200: throw new CompilerException(
201: "\"name\" attribute has to be set for \"param\" command");
202: }
203: if (child.getAttributes().getLength() != 1) {
204: throw new CompilerException(
205: "\"param\" command has exactly one attribute");
206: }
207: ParamValueObject paramValue = paramValueObjectFromNodeList(child
208: .getChildNodes());
209: stmt.addParam(paramName, paramValue);
210: } else {
211: throw new CompilerException("Element \""
212: + child.getLocalName()
213: + "\" is not allowed below \""
214: + element.getNodeName() + "\" command");
215: }
216: } else if (childNode.getNodeType() == Node.TEXT_NODE) {
217: if (!childNode.getNodeValue().matches("\\s*")) {
218: throw new CompilerException(
219: "Found illegal text data \""
220: + childNode.getNodeValue() + "\"!");
221: }
222: }
223: }
224: }
225:
226: private static VirtualRequestStatement virtualRequestStatementFromElement(
227: Statement parent, Element element) throws CompilerException {
228: String pagename = element.getAttribute("page");
229: String interactive = element.getAttribute("dointeractive");
230:
231: NamedNodeMap attribs = element.getAttributes();
232: for (int i = 0; i < attribs.getLength(); i++) {
233: Node attnode = attribs.item(i);
234: String name = attnode.getNodeName();
235: if (name.equals("page")) {
236: pagename = attnode.getNodeValue();
237: } else if (name.equals("dointeractive")) {
238: interactive = attnode.getNodeValue();
239: } else {
240: throw new CompilerException(
241: "\"virtual-request\" command only allows \"page\" and "
242: + "\"dointeractive\" attribute");
243: }
244: }
245:
246: VirtualRequestStatement stmt = new VirtualRequestStatement(
247: parent);
248: stmt.setPagename(pagename);
249: stmt.setDointeractive(interactive);
250: addParametersToStatement(element, stmt);
251:
252: return stmt;
253: }
254:
255: private static SetVariableStatement setVariableStatementFromElement(
256: Statement parent, Element element) throws CompilerException {
257: String varname = element.getAttribute("name");
258: if (varname == null || element.getAttributes().getLength() != 1) {
259: throw new CompilerException(
260: "\"name\" attribute has to be set for \"set-variable\" command");
261: }
262:
263: SetVariableStatement stmt = new SetVariableStatement(parent);
264: stmt.setName(varname);
265:
266: NodeList list = element.getChildNodes();
267: stmt.setValue(paramValueObjectFromNodeList(list));
268:
269: return stmt;
270: }
271:
272: private static ParamValueObject paramValueObjectFromNodeList(
273: NodeList list) throws CompilerException {
274: if (list.getLength() == 0) {
275: return new StaticObject("");
276: }
277:
278: if (list.getLength() == 1) {
279: Node node = list.item(0);
280: if (node.getNodeType() == Node.ELEMENT_NODE) {
281: if (!node.getNamespaceURI().equals(NS_SCRIPTEDFLOW)) {
282: throw new CompilerException("Namespace "
283: + node.getNamespaceURI()
284: + " cannot be handled by the compiler");
285: }
286: if (node.getLocalName().equals("value-of")) {
287: return dynamicParamFromElement((Element) node);
288: } else {
289: throw new CompilerException("Element \""
290: + node.getNodeName()
291: + "\" is not allowed below \"param\"");
292: }
293:
294: } else if (node.getNodeType() == Node.TEXT_NODE) {
295: String value = node.getNodeValue();
296: if (isWhitespace(value)) {
297: return new StaticObject("");
298: } else {
299: return new StaticObject(value);
300: }
301: }
302: }
303:
304: ListObject lo = new ListObject();
305: for (int i = 0; i < list.getLength(); i++) {
306: Node node = list.item(i);
307: if (node.getNodeType() == Node.ELEMENT_NODE) {
308: if (!node.getNamespaceURI().equals(NS_SCRIPTEDFLOW)) {
309: throw new CompilerException("Namespace "
310: + node.getNamespaceURI()
311: + " cannot be handled by the compiler");
312: }
313: if (node.getLocalName().equals("value-of")) {
314: lo
315: .addObject(dynamicParamFromElement((Element) node));
316: } else {
317: throw new CompilerException("Element \""
318: + node.getNodeName()
319: + "\" is not allowed below \"param\"");
320: }
321:
322: } else if (node.getNodeType() == Node.TEXT_NODE) {
323: String value = node.getNodeValue();
324: if (!isWhitespace(value)) {
325: lo.addObject(new StaticObject(node.getNodeValue()));
326: }
327: }
328: }
329: return lo;
330: }
331:
332: private static ParamValueObject dynamicParamFromElement(
333: Element element) throws CompilerException {
334: String expr = element.getAttribute("select");
335: if (expr == null || expr.length() == 0) {
336: throw new CompilerException(
337: "\"select\" attribute has to be set on \"value-of\" command");
338: }
339: if (element.getAttributes().getLength() != 1) {
340: throw new CompilerException(
341: "\"value-of\" command has exactly one attribute");
342: }
343: return new DynamicObject(expr);
344: }
345:
346: private static InteractiveRequestStatement interactiveRequestStatementFromElement(
347: Statement parent, Element element) throws CompilerException {
348: if (element.getAttributes() != null
349: && element.getAttributes().getLength() != 0) {
350: throw new CompilerException(
351: "\"interactive-request\" command has no attributes");
352: }
353: InteractiveRequestStatement stmt = new InteractiveRequestStatement(
354: parent);
355:
356: addParametersToStatement(element, stmt);
357:
358: return stmt;
359: }
360:
361: private static ExitStatement exitStatementFromElement(
362: Statement parent, Element element) throws CompilerException {
363: if (element.getAttributes() != null
364: && element.getAttributes().getLength() != 0) {
365: throw new CompilerException(
366: "\"exit\" command has no attributes");
367: }
368: ExitStatement stmt = new ExitStatement(parent);
369:
370: addParametersToStatement(element, stmt);
371:
372: return stmt;
373: }
374:
375: private static ChooseStatement chooseStatementFromElement(
376: Statement parent, Element element) throws CompilerException {
377: if (element.getAttributes() != null
378: && element.getAttributes().getLength() != 0) {
379: throw new CompilerException(
380: "\"choose\" command has no attributes");
381: }
382: ChooseStatement stmt = new ChooseStatement(parent);
383:
384: NodeList list = element.getChildNodes();
385: boolean foundOtherwise = false;
386: for (int i = 0; i < list.getLength(); i++) {
387: Node childNode = list.item(i);
388: if (childNode.getNodeType() == Node.ELEMENT_NODE) {
389: Element child = (Element) childNode;
390: if (!child.getNamespaceURI().equals(NS_SCRIPTEDFLOW)) {
391: throw new CompilerException("Namespace "
392: + child.getNamespaceURI()
393: + " cannot be handled by the compiler");
394: }
395: if (child.getLocalName().equals("when")) {
396: if (foundOtherwise) {
397: throw new CompilerException(
398: "No \"when\" allowed after \"otherwise\"");
399: }
400: String condition = child.getAttribute("test");
401: if (condition == null || condition.length() == 0) {
402: throw new CompilerException(
403: "\"test\" attribute has to be set for \"when\" command");
404: }
405: if (child.getAttributes().getLength() != 1) {
406: throw new CompilerException(
407: "\"when\" command has exactly one attribute");
408: }
409: Statement block = blockStatementFromNodeList(stmt,
410: child.getChildNodes());
411: stmt.addBranch(condition, block);
412: } else if (child.getLocalName().equals("otherwise")) {
413: foundOtherwise = true;
414: if (child.getAttributes() != null
415: && child.getAttributes().getLength() != 0) {
416: throw new CompilerException(
417: "\"otherwise\" command has no attributes");
418: }
419: Statement block = blockStatementFromNodeList(stmt,
420: child.getChildNodes());
421: stmt.addBranch(null, block);
422: } else {
423: throw new CompilerException(
424: "Element \""
425: + child.getLocalName()
426: + "\" is not allowed below \"choose\" command");
427: }
428: } else if (childNode.getNodeType() == Node.TEXT_NODE) {
429: if (!childNode.getNodeValue().matches("\\s*")) {
430: throw new CompilerException(
431: "Found illegal text data \""
432: + childNode.getNodeValue() + "\"!");
433: }
434: }
435: }
436: return stmt;
437: }
438:
439: private static BreakStatement breakStatementFromElement(
440: Statement parent, Element element) throws CompilerException {
441: if (element.getAttributes() != null
442: && element.getAttributes().getLength() != 0) {
443: throw new CompilerException(
444: "\"break\" command has no attributes");
445: }
446: try {
447: return new BreakStatement(parent);
448: } catch (RuntimeException e) {
449: throw new CompilerException(
450: "\"break\" is only allowed below a \"while\" block");
451: }
452: }
453:
454: private static WhileStatement whileStatementFromElement(
455: Statement parent, Element element) throws CompilerException {
456: String condition = element.getAttribute("test");
457: if (condition == null || condition.length() == 0) {
458: throw new CompilerException(
459: "\"test\" attribute has to be set for \"while\" command");
460: }
461: if (element.getAttributes().getLength() != 1) {
462: throw new CompilerException(
463: "\"while\" command has exactly one attribute");
464: }
465: WhileStatement stmt = new WhileStatement(parent);
466: stmt.setCondition(condition);
467: stmt.setChild(blockStatementFromNodeList(stmt, element
468: .getChildNodes()));
469: return stmt;
470: }
471:
472: private static IfStatement ifStatementFromElement(Statement parent,
473: Element element) throws CompilerException {
474: String condition = element.getAttribute("test");
475: if (condition == null || condition.length() == 0) {
476: throw new CompilerException(
477: "\"test\" attribute has to be set for \"if\" command");
478: }
479: if (element.getAttributes().getLength() != 1) {
480: throw new CompilerException(
481: "\"if\" command has exactly one attribute");
482: }
483: IfStatement stmt = new IfStatement(parent);
484: stmt.setCondition(condition);
485: stmt.setChild(blockStatementFromNodeList(stmt, element
486: .getChildNodes()));
487: return stmt;
488: }
489:
490: private static BlockStatement blockStatementFromNodeList(
491: Statement parent, NodeList list) throws CompilerException {
492: BlockStatement block = new BlockStatement(parent);
493: for (int i = 0; i < list.getLength(); i++) {
494: Node child = list.item(i);
495: if (child.getNodeType() == Node.ELEMENT_NODE) {
496: block.addStatement(statementFromElement(block,
497: (Element) child));
498: } else if (child.getNodeType() == Node.TEXT_NODE) {
499: if (!isWhitespace(child.getNodeValue())) {
500: throw new CompilerException(
501: "Found illegal text data \""
502: + child.getNodeValue() + "\"!");
503: }
504: }
505: }
506: return block;
507: }
508:
509: private static boolean isWhitespace(String teststr) {
510: return teststr.matches("\\s*");
511: }
512: }
|