iristk.flow.FlowCompiler.java Source code

Java tutorial

Introduction

Here is the source code for iristk.flow.FlowCompiler.java

Source

/*******************************************************************************
 * Copyright (c) 2014 Gabriel Skantze.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     Gabriel Skantze - initial API and implementation
 ******************************************************************************/
package iristk.flow;

import iristk.util.FileFinder;
import iristk.util.ListMap;
import iristk.util.RegExp;
import iristk.util.Replacer;
import iristk.xml.XmlUtils;
import iristk.xml.XmlMarshaller.XMLLocation;
import iristk.xml.flow.Block;
import iristk.xml.flow.Onevent;
import iristk.xml.flow.Ontime;
import iristk.xml.flow.Expr;
import iristk.xml.flow.Reentry;
import iristk.xml.flow.Select;
import iristk.xml.flow.Onentry;
import iristk.xml.flow.Onexit;
import iristk.xml.flow.Param;
import iristk.xml.flow.Random;
import iristk.xml.flow.Repeat;
import iristk.xml.flow.Var;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;

import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;

import org.apache.commons.lang.StringEscapeUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.Locator;

import java.lang.String;
import java.lang.Object;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;

public class FlowCompiler {

    private static final String stringPattern = "\".*?\"";
    //protected String classPackage;
    //protected String className;
    protected CodeStream code;
    private boolean useUniqueNames = false;
    private static int uniqueNameSuffix = 0;
    private FlowXml flowXml = new FlowXml();
    private String currentState = null;
    private int varnameCount = 0;
    private int currentLineNumber = 0;

    public FlowCompiler(File xmlFile) throws FlowCompilerException {
        flowXml.read(xmlFile);
    }

    public FlowCompiler(iristk.xml.flow.Flow xmlFlow) throws FlowCompilerException {
        flowXml.read(xmlFlow);
    }

    public void compileToFile(File srcFile) throws FlowCompilerException {
        try {
            FileOutputStream fstream = new FileOutputStream(srcFile);
            compileToStream(fstream);
            fstream.close();
        } catch (FileNotFoundException e) {
            throw new FlowCompilerException(e.getMessage());
        } catch (IOException e) {
            throw new FlowCompilerException(e.getMessage());
        }
    }

    public void compileToStream(OutputStream out) throws FlowCompilerException {
        if (useUniqueNames) {
            uniqueNameSuffix++;
        }
        code = new CodeStream(out);

        String flowName = getLocalFlowName();

        code.println("package " + flowXml.getPackage() + ";");
        code.println();

        printImports();
        for (String imp : flowXml.getImportedClasses()) {
            code.println("import " + imp + ";");
        }
        code.println();

        code.println("public class " + flowName + " extends " + flowXml.getExtends() + " {");
        code.println();

        printFlowContents(flowXml, flowName, true);

        code.println("}");
    }

    /*
    private void printInitTimers() {
       code.println("@Override");
       code.println("public void initTimers(FlowRunner flowRunner) {");
       for (iristk.xml.flow.State state : flowReader.getStates()) {
     for (ActionSequenceType trigger : state.getTrigger()) {
        if (trigger instanceof Ontime) {
           Ontime ontime = (Ontime) trigger;
           String interval = ontime.getInterval();
           if (interval != null) {
              if (interval.contains("-")) {
                 String[] cols = interval.split("-");
                 int min = Integer.parseInt(cols[0]);
                 int max = Integer.parseInt(cols[1]);
                 code.println("new EventClock(flowRunner, " + min + ", " + max + ", \"timer_" + trigger.hashCode() + "\");");
              } else {
                 int time = Integer.parseInt(interval);
                 code.println("new EventClock(flowRunner, " + time + ", \"timer_" + trigger.hashCode() + "\");");
              }
           }
        } 
     }
       }
       code.println("}");
       code.println();
    }
        
     */

    private void printFlowContents(FlowXml flowReader, String flowName, boolean topFlow)
            throws FlowCompilerException {

        printVariableDeclarations(flowReader.getVariables(), flowReader.getParameters());
        code.println();
        printInit(flowReader.getVariables());
        code.println();
        printVariableAccessors(flowReader.getVariables(), flowReader.getParameters(), !topFlow);
        code.println();

        if (topFlow && flowReader.getParameters().size() > 0) {
            String paramlist = "";
            String paramnamelist = "";
            for (Param param : flowReader.getParameters()) {
                if (paramlist.length() > 0) {
                    paramlist += ", ";
                    paramnamelist += ", ";
                }
                paramlist += param.getType() + " " + param.getName();
                paramnamelist += param.getName();
            }
            code.println("public " + flowName + "(" + paramlist + ") {");
            for (Param param : flowReader.getParameters()) {
                code.println("this." + param.getName() + " = " + param.getName() + ";");
            }
            code.println("initVariables();");
            code.println("}");
        } else {
            code.println("public " + flowName + "() {");
            code.println("initVariables();");
            code.println("}");
        }
        code.println();

        if (flowReader.getInitial() != null) {
            code.println("@Override");
            code.println("public State getInitialState() {return new " + flowReader.getInitial() + "();}");
            code.println();
        }

        //List<String> publicStates = new ArrayList<>();
        //for (iristk.xml.flow.State state : flowReader.getStates()) {
        //   if (state.isPublic()) {
        //      publicStates.add("\"" + state.getId() + "\"");
        //   }
        //}

        //code.println("public static String[] getPublicStates() {");
        //code.println("return new String[] {" + StringUtils.join(publicStates.toArray(new String[0]), ", ") + "};");
        //code.println("}");

        //printInitTimers();

        printStates(flowReader.getStates());
        printSubFlows(flowReader.getSubFlows());

        code.println();
    }

    private void printSubFlows(List<FlowXml> subFlows) throws FlowCompilerException {
        for (FlowXml flowReader : subFlows) {
            code.println((flowReader.isPublic() ? "public" : "private") + (flowReader.isStatic() ? " static" : "")
                    + " class " + flowReader.getName() + " extends " + flowReader.getExtends() + " {");
            code.println();

            printFlowContents(flowReader, flowReader.getName(), false);

            code.println("}");
        }
    }

    private boolean stateExists(String name) {
        for (iristk.xml.flow.State state : flowXml.getStates()) {
            if (state.getId().equals(name)) {
                return true;
            }
        }
        return false;
    }

    private void printStates(List<iristk.xml.flow.State> states) throws FlowCompilerException {
        for (iristk.xml.flow.State state : states) {
            currentState = state.getId();
            String ext = "State";
            if (state.getExtends() != null) {
                ext = state.getExtends();
            }
            code.println();
            String decl = "";
            if (state.isPublic() || flowXml.getInitial().equals(state.getId())) {
                decl += "public";
            } else {
                decl += "private";
            }
            if (state.isStatic()) {
                decl += " static";
            }
            decl += " class " + currentState + " extends " + ext;
            if (flowXml.getInitial() != null && flowXml.getInitial().equals(state.getId())) {
                decl += " implements Initial";
            }
            code.println(decl + " {");
            code.println();
            code.println("final State currentState = this;");
            if (state.getParam() != null) {
                printParameters(state.getParam());
            }

            if (state.getVar() != null) {
                printVariables(state.getVar());
                code.println();
            }

            code.println("@Override");
            code.println("public void setFlowThread(FlowRunner.FlowThread flowThread) {");
            code.println("super.setFlowThread(flowThread);");
            for (Object trigger : state.getTrigger()) {
                if (trigger instanceof Ontime) {
                    Ontime ontime = (Ontime) trigger;
                    String interval = ontime.getInterval();
                    if (interval != null) {
                        if (interval.contains(",")) {// new usage, in case dev wants to have e.g "x , 400-x"
                            //Assuming interval is now well-formed and resolves to (int, int)
                            code.println("flowThread.addEventClock(" + interval + ", \"timer_" + trigger.hashCode()
                                    + "\");");

                        } else if (interval.contains("-")) {
                            String[] cols = interval.split("-");
                            try {
                                int min = Integer.parseInt(cols[0]);
                                int max = Integer.parseInt(cols[1]);
                                code.println("flowThread.addEventClock(" + min + ", " + max + ", \"timer_"
                                        + trigger.hashCode() + "\");");
                            } catch (NumberFormatException e) {//In case developer uses variable names instead of an integer.
                                code.println("flowThread.addEventClock(" + cols[0] + ", " + cols[1] + ", \"timer_"
                                        + trigger.hashCode() + "\");");
                            }
                            //code.println("new EventClock(flowRunner, " + min + ", " + max + ", \"timer_" + trigger.hashCode() + "\");");
                        } else {
                            try {
                                int time = Integer.parseInt(interval);
                                code.println("flowThread.addEventClock(" + time + ", " + time + ", \"timer_"
                                        + trigger.hashCode() + "\");");
                                //code.println("new EventClock(flowRunner, " + time + ", \"timer_" + trigger.hashCode() + "\");");
                            } catch (NumberFormatException e) {//In case developer uses variable names instead of an integer.
                                code.println("flowThread.addEventClock(" + interval + ", " + interval + ", \"timer_"
                                        + trigger.hashCode() + "\");");
                            }
                        }
                    } //if interval !null
                } //if instanceof ontime 
            }
            code.println("}");
            code.println();

            printOnEntry(state.getTrigger());
            code.println();
            printEventTriggers(state.getTrigger());
            printOnExit(state.getTrigger());
            code.println();
            code.println("}");
            code.println();
        }
    }

    protected void printImports() {
        code.println("import java.util.List;");
        code.println("import java.io.File;");
        code.println("import iristk.xml.XmlMarshaller.XMLLocation;");
        code.println("import iristk.system.Event;");
        code.println("import iristk.flow.*;");
        code.println("import iristk.util.Record;");
        code.println("import static iristk.util.Converters.*;");
        code.println("import static iristk.flow.State.*;");
    }

    private void printVariables(List<Var> vars) throws FlowCompilerException {
        for (Var var : vars) {
            code.println("public " + variable(var));
        }
    }

    private void printVariableDeclarations(List<Var> vars, List<Param> params) throws FlowCompilerException {
        for (Param param : params) {
            code.println("private " + param.getType() + " " + param.getName() + ";");
        }
        for (Var var : vars) {
            code.println("private " + var.getType() + " " + var.getName() + ";");
        }
    }

    private void printInit(List<Var> vars) throws FlowCompilerException {
        code.println("private void initVariables() {");
        for (Var var : vars) {
            String ass = variableAssignment(var);
            if (!ass.equals(";"))
                code.println(var.getName() + ass);
        }
        code.println("}");
    }

    private String variableAssignment(Var var) throws FlowCompilerException {
        String line = "";
        if (var.getValue() != null) {
            if (var.getValue().contains("#")) {
                line += " = " + getExternalVar(var) + ";";
            } else {
                String type = var.getType();
                if (type == null)
                    type = "String";
                line += " = " + convertType(type, formatAttrExpr(var.getValue())) + ";";
                if (var.getContent() != null && var.getContent().size() > 0) {
                    line += "{\n" + formatExec(XmlUtils.nodesToString(var.getContent())) + "\n}";
                }
            }
        } else if (var.getContent() != null && var.getContent().size() > 0) {
            line += " = " + createExpression(var.getContent(), var);
        } else {
            line += ";";
        }
        return line;
    }

    private String variable(Var var) throws FlowCompilerException {
        return var.getType() + " " + var.getName() + variableAssignment(var);
    }

    private String getExternalVar(Var var) {
        String flowName = var.getValue().substring(0, var.getValue().indexOf("#"));
        String varName = var.getValue().substring(var.getValue().indexOf("#") + 1);
        return flowName + ".getFlow(flowPool).get" + ucFirst(varName) + "()";
    }

    private void printVariableAccessors(List<Var> vars, List<Param> params, boolean paramSetters) {
        for (Var var : vars) {
            code.println("public " + var.getType() + " get" + ucFirst(var.getName()) + "() {");
            code.println("return this." + var.getName() + ";");
            code.println("}");
            code.println();
            code.println("public void set" + ucFirst(var.getName()) + "(" + var.getType() + " value) {");
            code.println("this." + var.getName() + " = value;");
            code.println("}");
            code.println();
        }
        for (Param param : params) {
            code.println("public " + param.getType() + " get" + ucFirst(param.getName()) + "() {");
            code.println("return this." + param.getName() + ";");
            code.println("}");
            code.println();
            if (paramSetters) {
                code.println("public void set" + ucFirst(param.getName()) + "(" + param.getType() + " value) {");
                code.println("this." + param.getName() + " = value;");
                code.println("}");
                code.println();
            }
        }
        code.println("@Override");
        code.println("public Object getVariable(String name) {");
        for (Var var : vars) {
            code.println("if (name.equals(\"" + var.getName() + "\")) return this." + var.getName() + ";");
        }
        for (Param param : params) {
            code.println("if (name.equals(\"" + param.getName() + "\")) return this." + param.getName() + ";");
        }
        code.println("return null;");
        code.println("}");
        code.println();
    }

    private void printParameters(List<Param> params) throws FlowCompilerException {
        for (Param param : params) {
            String var = "public " + param.getType() + " " + param.getName() + " = ";
            if (param.getDefault() != null)
                var += convertType(param.getType(), formatAttrExpr(param.getDefault()));
            else {
                var += "null";
            }
            var += ";";
            code.println(var);
        }
        code.println();
        for (Param param : params) {
            code.println("public void set" + ucFirst(param.getName()) + "(Object value) {");
            code.println("if (value != null) {");
            code.println(param.getName() + " = " + convertType(param.getType(), "value") + ";");
            code.println("params.put(\"" + param.getName() + "\", value);");
            code.println("}");
            code.println("}");
            code.println();
        }
    }

    private String convertType(String type, String value) {
        if (type.equals("String")) {
            return "asString(" + value + ")";
        } else if (type.equals("Record")) {
            return "asRecord(" + value + ")";
        } else if (type.equals("Boolean")) {
            return "asBoolean(" + value + ")";
        } else if (type.equals("Long")) {
            return "asLong(" + value + ")";
        } else if (type.equals("Integer") || type.equals("int")) {
            return "asInteger(" + value + ")";
        } else if (type.equals("Float") || type.equals("float")) {
            return "asFloat(" + value + ")";
        } else if (type.equals("Double") || type.equals("double")) {
            return "asDouble(" + value + ")";
        } else if (type.equals("List")) {
            return "asList(" + value + ")";
        } else if (type.equals("Object")) {
            return value;
        } else {
            return "(" + type + ") " + value;
        }
    }

    private void printOnEntry(List<?> eventHandlers) throws FlowCompilerException {
        for (Object trigger : eventHandlers) {
            if (trigger instanceof Ontime) {
                String afterentry = ((Ontime) trigger).getAfterentry();
                if (afterentry != null) {
                    code.println("iristk.util.DelayedEvent timer_" + trigger.hashCode() + ";");
                }
            }
        }

        code.println("@Override");
        code.println("public void onentry() throws Exception {");
        code.println("int eventResult;");
        code.println("Event event = new Event(\"state.enter\");");

        for (Object trigger : eventHandlers) {
            if (trigger instanceof Ontime) {
                String afterentry = ((Ontime) trigger).getAfterentry();
                if (afterentry != null) {
                    code.println("if (timer_" + trigger.hashCode() + " != null) timer_" + trigger.hashCode()
                            + ".forget();");
                    code.println("timer_" + trigger.hashCode() + " = flowThread.raiseEvent(new Event(\"timer_"
                            + trigger.hashCode() + "\"), " + formatAttrExpr(afterentry)
                            + ", new FlowEventInfo(currentState, event, " + location(flowXml.getLocation(trigger))
                            + "));");
                    code.println("forgetOnExit(timer_" + trigger.hashCode() + ");");
                }
            }
        }

        for (Object eventHandler : eventHandlers) {
            if (eventHandler instanceof Onentry) {
                printLocation(eventHandler);
                Onentry onentry = (Onentry) eventHandler;
                code.println("try {");
                code.println("EXECUTION: {");
                code.println("int count = getCount(" + eventHandler.hashCode() + ") + 1;");
                code.println("incrCount(" + eventHandler.hashCode() + ");");
                printActions(onentry.getAny(), onentry, null);
                code.println("}");
                code.println("} catch (Exception e) {");
                code.println("throw new FlowException(e, currentState, event, "
                        + location(flowXml.getLocation(onentry)) + ");");
                code.println("}");
                break;
            }
        }

        code.println("}");
        return;
    }

    private void printOnExit(List<?> eventHandlers) throws FlowCompilerException {
        for (Object eventHandler : eventHandlers) {
            if (eventHandler instanceof Onexit) {
                printLocation(eventHandler);
                Onexit onexit = (Onexit) eventHandler;
                code.println("@Override");
                code.println("public void onexit() {");
                code.println("int eventResult;");
                code.println("Event event = new Event(\"state.exit\");");
                if (onexit != null) {
                    code.println("EXECUTION: {");
                    printActions(onexit.getAny(), onexit, null);
                    code.println("}");
                }
                code.println("super.onexit();");
                code.println("}");
                return;
            }
        }
    }

    private void printEventTriggers(List<?> triggers) throws FlowCompilerException {
        code.println("@Override");
        code.println("public int onFlowEvent(Event event) throws Exception {");
        code.println("int eventResult;");
        code.println("int count;");
        for (Object trigger : triggers) {
            if (trigger instanceof Onevent) {
                printLocation(trigger);
                code.println("try {");
                code.println("count = getCount(" + trigger.hashCode() + ") + 1;");
                Onevent onEventElem = (Onevent) trigger;
                if (onEventElem.getName() != null)
                    code.println("if (event.triggers(\"" + onEventElem.getName() + "\")) {");
                if (onEventElem.getCond() != null)
                    code.println("if (" + formatCondExpr(onEventElem.getCond()) + ") {");

                code.println("incrCount(" + trigger.hashCode() + ");");

                code.println("eventResult = EVENT_CONSUMED;");
                code.println("EXECUTION: {");
                printActions(onEventElem.getAny(), trigger, null);
                code.println("}");
                code.println("if (eventResult != EVENT_IGNORED) return eventResult;");

                //for (int i = 0; i < onEventElem.getOtherAttributes().keySet().size(); i++) 
                //   code.println("}");
                if (onEventElem.getCond() != null)
                    code.println("}");
                if (onEventElem.getName() != null)
                    code.println("}");
                code.println("} catch (Exception e) {");
                code.println("throw new FlowException(e, currentState, event, "
                        + location(flowXml.getLocation(trigger)) + ");");
                code.println("}");

            } else if (trigger instanceof Ontime) {
                printLocation(trigger);
                Ontime onTimeElem = (Ontime) trigger;
                code.println("count = getCount(" + trigger.hashCode() + ") + 1;");
                code.println("if (event.triggers(\"timer_" + trigger.hashCode() + "\")) {");
                code.println("incrCount(" + trigger.hashCode() + ");");
                code.println("eventResult = EVENT_CONSUMED;");
                code.println("EXECUTION: {");
                printActions(onTimeElem.getAny(), trigger, null);
                code.println("}");
                code.println("if (eventResult != EVENT_IGNORED) return eventResult;");
                code.println("}");
            }
        }
        code.println("eventResult = super.onFlowEvent(event);");
        code.println("if (eventResult != EVENT_IGNORED) return eventResult;");
        code.println("eventResult = callerHandlers(event);");
        code.println("if (eventResult != EVENT_IGNORED) return eventResult;");
        code.println("return EVENT_IGNORED;");
        code.println("}");
    }

    private String ucFirst(String text) {
        return text.substring(0, 1).toUpperCase() + text.substring(1);
    }

    private String listToString(List<?> list) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            if (i > 0)
                result.append(", ");
            result.append(list.get(i));
        }
        return result.toString();
    }

    /*
    private List<String> processString(List<?> children) throws FlowCompilerException {
       List<String> result = new ArrayList<String>();
       for (Object child : children) {
     if (child instanceof String) {
        String str = child.toString().trim();
        if (str.length() > 0)
           result.add("\"" + str + "\"");
     } else if (child instanceof Expr) {
        Expr expr = (Expr) child;
        result.add(formatExpr(expr.getValue()));
     } else if (child instanceof Block) {
        Block item = (Block) child;
        if (item.getProb() != null || item.getCond() != null) {
           result.add("str(" + formatCondExpr(item.getCond()) + ", " + item.getProb() + ", " + concatFun(processString(item.getContent())) + ")");
        } else {
           result.add(concatFun(processString(item.getContent())));
        }
     } else if (child instanceof Select || child instanceof Random) {
        String options = (child instanceof Select) ? ((Select)child).getList() : ((Random)child).getList();
        if (options == null) {
           List<Object> actionGroup = (child instanceof Select) ? ((Select)child).getAny() : ((Random)child).getAny();
           if (actionGroup.size() > 0) {
              options = listToString(processString(actionGroup));
           }
        }
        if (options != null) {
           if (child instanceof Random)
              result.add("randstr(" + options  + ")");
           else
              result.add("firststr(" + options  + ")");
        }
     } else if (child instanceof Element) {
        result.add(concatFun(processElement(child)));
     } else if (child instanceof Text) {
        String str = ((Text)child).getNodeValue().trim();
        if (str.length() > 0)
           result.add("\"" + str + "\"");
     }
       }
       return result;
    }
        
    private String concatFun(List<?> nodes) {
       if (nodes.size() == 0)
     return "\"\"";
       else if (nodes.size() == 1)
     return nodes.get(0).toString();
       else
     return "concat(" + listToString(nodes) + ")";
    }
        
    private List<String> processElement(Object node) throws FlowCompilerException {
       ArrayList<String> result = new ArrayList<String>();
       if (node instanceof String) {
     String str = ((String)node).trim();
     if (str.length() > 0)
        result.add("\"" + str + "\"");
       } else if (node instanceof Text) {
     String str = ((Text) node).getTextContent().trim();
     if (str.length() > 0)
        result.add("\"" + str + "\"");
       } else if (node instanceof Element) {
     try {
        Object o = flowXml.unmarshal((Element)node);
        List<Object> objects = new ArrayList<Object>();
        objects.add(o);
        //result.addAll(processString(objects));
     } catch (FlowCompilerException e) {
        String estring = "";
        Element en = (Element)node;
        estring += "\"<" + en.getLocalName();
        for (int j = 0; j < en.getAttributes().getLength(); j++) {
           Attr attr = (Attr) en.getAttributes().item(j);
           if (!(attr.getNamespaceURI() != null && attr.getNamespaceURI().equals("http://www.w3.org/2000/xmlns/")) && !attr.getLocalName().equals("xmlns") && !attr.getLocalName().equals("xsi")) {
              estring += " " + attr.getLocalName() + "=\\\"" + attr.getValue() + "\\\"";
           }
        }
        if (en.getChildNodes().getLength() == 0) {
           estring += "/>\"";
           result.add(estring);
        } else {
           estring += ">\"";
           result.add(estring);
           for (int i = 0; i < en.getChildNodes().getLength(); i++) {
              result.addAll(processElement(en.getChildNodes().item(i))); 
           }
           result.add("\"</" + en.getLocalName() + ">\"");
        }
     }
       }
       return result;
    }
        
        
     */

    private String varname(String prefix) {
        return prefix + (varnameCount++);
    }

    private void printLocation(Object obj) {
        if (obj != null) {
            try {
                Method m = obj.getClass().getMethod("sourceLocation", null);
                Locator loc = (Locator) m.invoke(obj, null);
                if (loc != null && loc.getLineNumber() != -1) {
                    currentLineNumber = loc.getLineNumber();
                }
                code.println("// Line: " + currentLineNumber);
            } catch (NoSuchMethodException e) {
            } catch (SecurityException e) {
            } catch (IllegalAccessException e) {
            } catch (IllegalArgumentException e) {
            } catch (InvocationTargetException e) {
            }
        }
    }

    protected void printAction(Object action, Object parent, String exprContext) throws FlowCompilerException {
        if (action instanceof JAXBElement<?>)
            action = ((JAXBElement<?>) action).getValue();
        printLocation(action);
        if (action instanceof iristk.xml.flow.Goto) {
            iristk.xml.flow.Goto gotoAction = (iristk.xml.flow.Goto) action;
            if (exprContext != null)
                throw new FlowCompilerException("<goto> not allowed in expression",
                        gotoAction.sourceLocation().getLineNumber());
            String stateVar = "";
            if (gotoAction.getState() != null) {
                if (!stateExists(gotoAction.getState()))
                    throw new FlowCompilerException("State " + gotoAction.getState() + " does not exist",
                            gotoAction.sourceLocation().getLineNumber());
                stateVar = varname("state");
                code.println(stateClass(gotoAction.getState()) + " " + stateVar + " = "
                        + newState(gotoAction.getState()) + ";");
                printSetStateParameters(stateVar,
                        getParameters(gotoAction.getOtherAttributes(), gotoAction.getContent(), gotoAction));
            } else if (gotoAction.getExpr() != null) {
                stateVar = gotoAction.getExpr();
            } else {
                throw new FlowCompilerException("Goto must either have a 'state' or 'expr' attribute",
                        gotoAction.sourceLocation().getLineNumber());
            }
            code.println(
                    "flowThread.gotoState(" + stateVar + ", currentState, new FlowEventInfo(currentState, event, "
                            + location(flowXml.getLocation(action)) + "));");
            code.println("eventResult = EVENT_ABORTED;");
            code.println("break EXECUTION;");
        } else if (action instanceof iristk.xml.flow.Run) {
            iristk.xml.flow.Run runAction = (iristk.xml.flow.Run) action;
            if (exprContext != null)
                throw new FlowCompilerException("<run> not allowed in expression",
                        runAction.sourceLocation().getLineNumber());
            String stateVar = varname("state");
            code.println(stateClass(runAction.getState()) + " " + stateVar + " = " + newState(runAction.getState())
                    + ";");
            printSetStateParameters(stateVar,
                    getParameters(runAction.getOtherAttributes(), runAction.getContent(), runAction));
            code.println("FlowRunner.FlowThread " + stateVar + "Thread = flowThread.runState(" + stateVar
                    + ", new FlowEventInfo(currentState, event, " + location(flowXml.getLocation(action)) + "));");
        } else if (action instanceof iristk.xml.flow.Call) {
            iristk.xml.flow.Call callAction = (iristk.xml.flow.Call) action;
            if (exprContext != null)
                throw new FlowCompilerException("<call> not allowed in expression",
                        callAction.sourceLocation().getLineNumber());
            String stateVar = varname("state");
            code.println(stateClass(callAction.getState()) + " " + stateVar + " = "
                    + newState(callAction.getState()) + ";");
            printSetStateParameters(stateVar,
                    getParameters(callAction.getOtherAttributes(), callAction.getContent(), callAction));
            code.println("if (!flowThread.callState(" + stateVar + ", new FlowEventInfo(currentState, event, "
                    + location(flowXml.getLocation(action)) + "))) {");
            code.println("eventResult = EVENT_ABORTED;");
            code.println("break EXECUTION;");
            code.println("}");
        } else if (action instanceof iristk.xml.flow.Return) {
            iristk.xml.flow.Return returnAction = (iristk.xml.flow.Return) action;
            if (exprContext != null)
                throw new FlowCompilerException("<return> not allowed in expression",
                        returnAction.sourceLocation().getLineNumber());
            if (returnAction.getEvent() != null || returnAction.getCopy() != null) {
                String returnEvent = varname("returnEvent");
                printInitEvent(returnEvent, returnAction.getCopy(), returnAction.getEvent(),
                        getParameters(returnAction.getOtherAttributes(), returnAction.getContent(), returnAction));
                //code.println("flowThread.raiseEvent(" + returnEvent + ", new FlowEventInfo(currentState, event, " + locationConstructor(flowReader.getLocation(action)) + ");");
                code.println("flowThread.returnFromCall(this, " + returnEvent
                        + ", new FlowEventInfo(currentState, event, " + location(flowXml.getLocation(action))
                        + "));");
            } else {
                code.println("flowThread.returnFromCall(this, null, new FlowEventInfo(currentState, event, "
                        + location(flowXml.getLocation(action)) + "));");
            }
            code.println("eventResult = EVENT_ABORTED;");
            code.println("break EXECUTION;");
        } else if (action instanceof Reentry) {
            //code.println("flowThread.raiseEvent(new EntryEvent(), new FlowEventInfo(currentState, event, " + locationConstructor(flowReader.getLocation(action)) + ");");
            Reentry reentryAction = (Reentry) action;
            if (exprContext != null)
                throw new FlowCompilerException("<reentry> not allowed in expression",
                        reentryAction.sourceLocation().getLineNumber());
            code.println("flowThread.reentryState(this, new FlowEventInfo(currentState, event, "
                    + location(flowXml.getLocation(action)) + "));");
            code.println("eventResult = EVENT_ABORTED;");
            code.println("break EXECUTION;");
        } else if (action instanceof iristk.xml.flow.Block) {
            //System.out.println("BLOCK " + currentLineNumber);
            iristk.xml.flow.Block blockAction = (iristk.xml.flow.Block) action;
            String cond = "";
            if (blockAction.getCond() != null) {
                cond = "if (" + formatCondExpr(blockAction.getCond()) + ") ";
            }
            code.println(cond + "{");
            printActions(blockAction.getContent(), action, exprContext);
            code.println("}");
        } else if (action instanceof iristk.xml.flow.Wait) {
            iristk.xml.flow.Wait waitAction = (iristk.xml.flow.Wait) action;
            if (exprContext != null)
                throw new FlowCompilerException("<wait> not allowed in expression",
                        waitAction.sourceLocation().getLineNumber());

            String waitvar = varname("waitState");
            code.println(DialogFlow.class.getName() + ".wait " + waitvar + " = new " + DialogFlow.class.getName()
                    + ".wait();");
            code.println(waitvar + ".setMsec(" + waitAction.getMsec() + ");");
            code.println("if (!flowThread.callState(" + waitvar + ", new FlowEventInfo(currentState, event, "
                    + location(flowXml.getLocation(action)) + "))) {");
            code.println("eventResult = EVENT_ABORTED;");
            code.println("break EXECUTION;");
            code.println("}");
        } else if (action instanceof Random) {
            Random randomAction = (Random) action;
            if (randomAction.getList() != null) {
                if (exprContext == null) {
                    throw new FlowCompilerException("<random list=\"...\"/> only allowed in expressions",
                            randomAction.sourceLocation().getLineNumber());
                }
                code.println(exprContext + ".append(randstr(" + randomAction.getList() + "));");
            } else {
                int tot = 0;
                for (Object child : randomAction.getAny()) {
                    int inc = 1;
                    if (child instanceof JAXBElement)
                        child = ((JAXBElement) child).getValue();
                    if (child instanceof Block && ((Block) child).getWeight() != null)
                        inc = ((Block) child).getWeight();
                    tot += inc;
                }
                int n = 0;
                int lastn = 0;
                String chosenVar = varname("chosen");
                String matchingVar = varname("matching");
                code.println("boolean " + chosenVar + " = false;");
                code.println("boolean " + matchingVar + " = true;");
                code.println("while (!" + chosenVar + " && " + matchingVar + ") {");
                String randVar = varname("rand");
                String model = "iristk.util.RandomList.RandomModel." + randomAction.getModel().toUpperCase();
                code.println("int " + randVar + " = random(" + randomAction.hashCode() + ", " + tot + ", " + model
                        + ");");
                code.println(matchingVar + " = false;");
                for (Object child : randomAction.getAny()) {
                    if (n > 0)
                        code.println("}");
                    int inc = 1;
                    String cond = "true";
                    List<Object> actions = new ArrayList<>();
                    if (child instanceof JAXBElement)
                        child = ((JAXBElement) child).getValue();
                    if (child instanceof Block) {
                        Block block = (Block) child;
                        if (block.getWeight() != null)
                            inc = block.getWeight();
                        if (block.getCond() != null)
                            cond = formatCondExpr(block.getCond());
                        actions.addAll(block.getContent());
                    } else {
                        actions.add(child);
                    }
                    n += inc;
                    code.println("if (" + cond + ") {");
                    code.println(matchingVar + " = true;");
                    code.println("if (" + randVar + " >= " + lastn + " && " + randVar + " < " + n + ") {");
                    code.println(chosenVar + " = true;");
                    printActions(actions, randomAction, exprContext);
                    code.println("}");
                    lastn = n;
                }
                code.println("}");
                code.println("}");
            }
        } else if (action instanceof Select) {
            Select select = (Select) action;
            String label = varname("SELECTION");
            code.println(label + ":");
            code.println("{");
            for (Object child : select.getAny()) {
                String cond = "";
                List<Object> actions = new ArrayList<>();
                if (child instanceof JAXBElement)
                    child = ((JAXBElement) child).getValue();
                if (child instanceof Block) {
                    Block block = (Block) child;
                    if (block.getCond() != null)
                        cond = "if (" + formatCondExpr(block.getCond()) + ") ";
                    actions.addAll(block.getContent());
                } else {
                    actions.add(child);
                }
                code.println(cond + "{");
                printActions(actions, select, exprContext);
                code.println("break " + label + ";");
                code.println("}");
                if (cond.equals(""))
                    break;
            }
            code.println("}");
        } else if (action instanceof iristk.xml.flow.Raise) {
            iristk.xml.flow.Raise raiseAction = (iristk.xml.flow.Raise) action;
            if (exprContext != null)
                throw new FlowCompilerException("<raise> not allowed in expression",
                        raiseAction.sourceLocation().getLineNumber());

            String raiseEvent = varname("raiseEvent");
            printInitEvent(raiseEvent, raiseAction.getCopy(), raiseAction.getEvent(),
                    getParameters(raiseAction.getOtherAttributes(), raiseAction.getContent(), raiseAction));
            if (raiseAction.getDelay() != null) {
                String delayedEvent = "flowThread.raiseEvent(" + raiseEvent + ", "
                        + formatAttrExpr(raiseAction.getDelay()) + ", new FlowEventInfo(currentState, event, "
                        + location(flowXml.getLocation(action)) + "))";
                if (raiseAction.isForgetOnExit()) {
                    code.println("forgetOnExit(" + delayedEvent + ");");
                } else {
                    code.println(delayedEvent + ";");
                }
            } else {
                code.println("if (flowThread.raiseEvent(" + raiseEvent + ", new FlowEventInfo(currentState, event, "
                        + location(flowXml.getLocation(action)) + ")) == State.EVENT_ABORTED) {");
                code.println("eventResult = EVENT_ABORTED;");
                code.println("break EXECUTION;");
                code.println("}");
            }
        } else if (action instanceof iristk.xml.flow.Send) {
            iristk.xml.flow.Send sendAction = (iristk.xml.flow.Send) action;
            if (exprContext != null)
                throw new FlowCompilerException("<send> not allowed in expression",
                        sendAction.sourceLocation().getLineNumber());
            String sendEvent = varname("sendEvent");
            printInitEvent(sendEvent, sendAction.getCopy(), sendAction.getEvent(),
                    getParameters(sendAction.getOtherAttributes(), sendAction.getContent(), sendAction));
            if (sendAction.getDelay() != null)
                code.println("flowRunner.sendEvent(" + sendEvent + ", " + formatAttrExpr(sendAction.getDelay())
                        + ", new FlowEventInfo(currentState, event, " + location(flowXml.getLocation(action))
                        + "));");
            else
                code.println("flowRunner.sendEvent(" + sendEvent + ", new FlowEventInfo(currentState, event, "
                        + location(flowXml.getLocation(action)) + "));");
            if (sendAction.getBindId() != null) {
                code.println(sendAction.getBindId() + " = " + sendEvent + ".getId();");
            }
        } else if (action instanceof iristk.xml.flow.If) {
            iristk.xml.flow.If ifAction = (iristk.xml.flow.If) action;
            code.println("if (" + formatCondExpr(ifAction.getCond()) + ") {");
            printActions(ifAction.getContent(), ifAction, exprContext);
            code.println("}");
        } else if (action instanceof iristk.xml.flow.Else) {
            code.println("} else {");
        } else if (action instanceof iristk.xml.flow.Elseif) {
            iristk.xml.flow.Elseif eifAction = (iristk.xml.flow.Elseif) action;
            code.println("} else if (" + formatCondExpr(eifAction.getCond()) + ") {");
        } else if (action instanceof iristk.xml.flow.Propagate) {
            iristk.xml.flow.Propagate propagateAction = (iristk.xml.flow.Propagate) action;
            if (exprContext != null)
                throw new FlowCompilerException("<propagate> not allowed in expression",
                        propagateAction.sourceLocation().getLineNumber());

            code.println("eventResult = EVENT_IGNORED;");
            code.println("break EXECUTION;");
        } else if (action instanceof Repeat) {
            Repeat repeat = (Repeat) action;
            String handler = repeat.getHandler();
            if (handler == null) {
                handler = varname("handler");
            }
            code.println("{");
            if (repeat.getTimes() != null) {
                code.println("RepeatHandler " + handler + " = new RepeatHandler("
                        + formatAttrExpr(repeat.getTimes()) + ");");
                code.println("while (" + handler + ".getPosition() < " + handler + ".getLength()) {");
            } else if (repeat.getWhile() != null) {
                code.println("RepeatHandler " + handler + " = new RepeatHandler();");
                code.println("while (" + formatAttrExpr(repeat.getWhile()) + ") {");
            } else if (repeat.getList() != null) {
                code.println("RepeatHandler " + handler + " = new RepeatHandler(" + formatAttrExpr(repeat.getList())
                        + ");");
                code.println("while (" + handler + ".getPosition() < " + handler + ".getLength()) {");
            }
            printActions(repeat.getContent(), repeat, exprContext);
            code.println(handler + ".next();");
            code.println("}");
            code.println("}");
        } else if (action instanceof Var) {
            Var varAction = (Var) action;
            if (exprContext != null)
                throw new FlowCompilerException("<var> not allowed in expression",
                        varAction.sourceLocation().getLineNumber());
            code.println(variable((Var) action));
        } else if (action instanceof iristk.xml.flow.Exec) {
            code.println(formatExec(((iristk.xml.flow.Exec) action).getValue().trim()));
        } else if (action instanceof iristk.xml.flow.Log) {
            code.println("log(" + createExpression(((iristk.xml.flow.Log) action).getContent(), action) + ");");
        } else if (action instanceof iristk.xml.flow.Expr) {
            if (exprContext == null) {
                throw new FlowCompilerException("<expr> not allowed", currentLineNumber);
            } else {
                code.println(exprContext + ".append(" + formatExpr(((Expr) action).getValue()) + ");");
            }
        } else if (action instanceof Element) {//perhaps implement variable name here
            Element elem = (Element) action;
            if (exprContext == null) {
                if (elem.getNamespaceURI().equals("iristk.flow") || elem.getPrefix() == null) {
                    throw new FlowCompilerException("Bad element: <" + elem.getLocalName() + ">",
                            currentLineNumber);
                }
                String stateVar = varname("state");
                code.println(stateClass(elem) + " " + stateVar + " = " + newState(elem) + ";");
                printSetStateParameters(stateVar, getParameters(elem));

                code.println("if (!flowThread.callState(" + stateVar + ", new FlowEventInfo(currentState, event, "
                        + location(flowXml.getLocation(parent)) + "))) {");
                code.println("eventResult = EVENT_ABORTED;");
                code.println("break EXECUTION;");
                code.println("}");
            } else {
                try {
                    Object o = flowXml.unmarshal(elem);
                    printAction(o, parent, exprContext);
                } catch (FlowCompilerException e) {
                    code.println(exprContext + ".append(" + createExpression(elem) + ");");
                }
            }
        } else if (action instanceof Text) {
            printAction(((Text) action).getTextContent(), parent, exprContext);
        } else if (action instanceof String) {
            String str = action.toString().trim();
            if (str.length() > 0) {
                if (exprContext == null) {
                    throw new FlowCompilerException("Text node not allowed: " + str, currentLineNumber);
                } else {
                    code.println(exprContext + ".append(\"" + str.replaceAll("\\n", " ") + "\");");
                }
            }
        } else {
            throw new FlowCompilerException("Could not parse " + action, currentLineNumber);
        }
    }

    private String createExpression(List<Object> content, Object parent) throws FlowCompilerException {
        String varName = varname("string");
        code.println("StringCreator " + varName + " = new StringCreator();");
        printActions(content, parent, varName);
        return varName + ".toString()";
    }

    private String createExpression(Element en) throws FlowCompilerException {
        String estring = "";
        estring += "<" + en.getLocalName();
        for (int j = 0; j < en.getAttributes().getLength(); j++) {
            Attr attr = (Attr) en.getAttributes().item(j);
            if (!(attr.getNamespaceURI() != null && attr.getNamespaceURI().equals("http://www.w3.org/2000/xmlns/"))
                    && !attr.getLocalName().equals("xmlns") && !attr.getLocalName().equals("xsi")) {
                estring += " " + attr.getLocalName() + "=\\\"" + attr.getValue() + "\\\"";
            }
        }
        if (en.getChildNodes().getLength() == 0) {
            estring += "/>";
            return "\"" + estring + "\"";
        } else {
            String varName = varname("string");
            code.println("StringCreator " + varName + " = new StringCreator();");
            List<Object> children = new ArrayList<>();
            estring += ">";
            children.add(estring);
            for (int i = 0; i < en.getChildNodes().getLength(); i++) {
                children.add(en.getChildNodes().item(i));
            }
            children.add("</" + en.getLocalName() + ">");
            printActions(children, en, varName);
            return varName + ".toString()";
        }
    }

    private String location(XMLLocation location) {
        if (location == null)
            return "null";
        else
            return "new XMLLocation(new File(\""
                    + StringEscapeUtils.escapeJava(location.getFile().getAbsolutePath()) + "\"), "
                    + location.getLineNumber() + ", " + location.getColumnNumber() + ")";
    }

    private String newState(String state) throws FlowCompilerException {
        if (state.contains("#")) {
            String flowName = state.substring(0, state.indexOf("#"));
            String stateName = state.substring(state.indexOf("#") + 1);
            if (flowName.equals("this")) {
                return "new " + stateName + "()";
            }
            for (Var var : flowXml.getVariables()) {
                if (var.getName().equals(flowName))
                    return flowName + ".new " + stateName + "()";
            }
            for (Param param : flowXml.getParameters()) {
                if (param.getName().equals(flowName))
                    return flowName + ".new " + stateName + "()";
            }
            return "new " + flowName + "." + stateName + "()";
        } else {
            return "new " + state + "()";
        }
    }

    private String newState(Element elem) {
        if (elem.getPrefix().equals("this")) {
            return "new " + elem.getLocalName() + "()";
        }
        for (Var var : flowXml.getVariables()) {
            if (var.getName().equals(elem.getPrefix()))
                return elem.getPrefix() + ".new " + elem.getLocalName() + "()";
        }
        for (Param param : flowXml.getParameters()) {
            if (param.getName().equals(elem.getPrefix()))
                return elem.getPrefix() + ".new " + elem.getLocalName() + "()";
        }
        return "new " + elem.getNamespaceURI() + "." + elem.getLocalName() + "()";
    }

    private String stateClass(String state) throws FlowCompilerException {
        if (state.contains("#")) {
            String flowName = state.substring(0, state.indexOf("#"));
            String stateName = state.substring(state.indexOf("#") + 1);
            for (Var var : flowXml.getVariables()) {
                if (var.getName().equals(flowName))
                    return var.getType() + "." + stateName;
            }
            for (Param param : flowXml.getParameters()) {
                if (param.getName().equals(flowName))
                    return param.getType() + "." + stateName;
            }
            throw new FlowCompilerException("Cannot resolve " + state);
        } else {
            return state;
        }
    }

    private String stateClass(Element elem) {
        return elem.getNamespaceURI() + "." + elem.getLocalName();
    }

    private Map<String, String> getParameters(Element elem) throws FlowCompilerException {
        NamedNodeMap attributes = elem.getAttributes();
        NodeList childNodes = elem.getChildNodes();
        Map<QName, String> otherAttributes = new HashMap<>();
        for (int i = 0; i < attributes.getLength(); i++) {
            if (attributes.item(i).getNamespaceURI() == null
                    || !attributes.item(i).getNamespaceURI().equals("http://www.w3.org/2000/xmlns/")) {
                otherAttributes.put(new QName("iristk.flow.param", attributes.item(i).getLocalName()),
                        attributes.item(i).getNodeValue());
            }
        }
        ArrayList<Object> content = new ArrayList<>();
        for (int i = 0; i < childNodes.getLength(); i++) {
            content.add(childNodes.item(i));
        }
        return getParameters(otherAttributes, content, elem);
    }

    private Map<String, String> getParameters(Map<QName, String> attributes, List<Object> content, Object parent)
            throws FlowCompilerException {
        Map<String, String> result = new HashMap<String, String>();
        for (QName attr : attributes.keySet()) {
            if (attr.getNamespaceURI() != null && attr.getNamespaceURI().equals("http://www.w3.org/2000/xmlns/"))
                continue;
            String name = attr.getLocalPart();
            String value = formatAttrExpr(attributes.get(attr));
            result.put(name, value);
        }
        if (content.size() > 0) {
            ListMap<String, String> paramList = new ListMap<>();
            for (Object child : content) {
                if (child instanceof Element) {
                    Element elem = (Element) child;
                    if (elem.getNamespaceURI().equals("iristk.flow.param")) {
                        String key = elem.getLocalName();
                        List<Object> paramChildren = new ArrayList<Object>();
                        for (int j = 0; j < elem.getChildNodes().getLength(); j++) {
                            paramChildren.add(elem.getChildNodes().item(j));
                        }
                        String text = createExpression(paramChildren, elem);
                        paramList.add(key, text);
                    }
                }
            }
            if (paramList.size() == 0) {
                paramList.add("text", createExpression(content, parent));
            }
            for (String key : paramList.keySet()) {
                if (paramList.get(key).size() > 1) {
                    result.put(key, "java.util.Arrays.asList(" + listToString(paramList.get(key)) + ")");
                } else {
                    result.put(key, paramList.get(key).get(0));
                }
            }
        }
        return result;
    }

    private void printSetStateParameters(String stateName, Map<String, String> paramMap)
            throws FlowCompilerException {
        for (String name : paramMap.keySet()) {
            code.println(stateName + ".set" + ucFirst(name) + "(" + paramMap.get(name) + ");");
        }
    }

    private void printInitEvent(String varName, String copy, String eventName, Map<String, String> paramMap)
            throws FlowCompilerException {
        //if (copy != null) {
        //   code.println("Event copy = " + formatAttrExpr(copy) + ";");
        //}
        if (eventName != null)
            code.println("Event " + varName + " = new Event(\"" + eventName + "\");");
        else if (copy != null)
            code.println("Event " + varName + " = new Event(" + formatAttrExpr(copy) + ".getName());");
        else
            throw new FlowCompilerException("Must have either copy or event parameter set when sending event");
        if (copy != null)
            code.println(varName + ".copyParams(" + formatAttrExpr(copy) + ");");
        for (String name : paramMap.keySet()) {
            code.println(varName + ".putIfNotNull(\"" + name + "\", " + paramMap.get(name) + ");");
        }
    }

    private void printActions(List<Object> list, Object parent, String exprContext) throws FlowCompilerException {
        for (Object action : list) {
            printAction(action, parent, exprContext);
        }
    }

    private static String replaceIgnoreStrings(String expr, String pattern, final String repl) {
        return new Replacer(pattern, stringPattern) {
            @Override
            public String replace(Matcher matcher) {
                return repl;
            }
        }.replaceAll(expr);
    }

    private String formatCondExpr(String cond) throws FlowCompilerException {
        if (cond == null)
            return null;
        cond = formatAttrExpr(cond);
        cond = replaceIgnoreStrings(cond, " and ", " && ");
        cond = replaceIgnoreStrings(cond, " or ", " || ");
        return cond;
        //return "makeBool(" + cond + ")";
    }

    private String formatAttrExpr(String expr) throws FlowCompilerException {
        if (expr == null)
            return null;
        // ' => " (if not preceded by \)
        expr = expr.replaceAll("(?<!\\\\)'", "\"");
        // \' => ' 
        expr = expr.replaceAll("\\\\'", "'");
        // \ => \\ (if not followed by ") 
        expr = expr.replaceAll("\\\\(?!\")", "\\\\\\\\");
        expr = formatExpr(expr);
        return expr;
    }

    private String formatExec(String exec) throws FlowCompilerException {
        exec = formatExpr(exec);
        if (!exec.endsWith(";"))
            return exec + ";";
        else
            return exec;
    }

    public static String formatRecordExpr(String expr) throws FlowCompilerException {
        try {
            // return new Replacer("([A-Za-z0-9_]+)(\\?)?:([A-Za-z0-9_\\:\\.\\(\\)\\*]*)( *=(?!=)[^;]*)?", stringPattern) {
            return new Replacer("(\\?)?:([A-Za-z0-9_\\:\\.\\(\\)\\*]*)( *=(?!=)[^;]*)?", stringPattern) {
                @Override
                public String replace(Matcher matcher) {
                    //String recordVar = matcher.group(1);
                    String hasSign = matcher.group(1);
                    String getStr = matcher.group(2);
                    String assignStr = matcher.group(3);
                    String[] split = Replacer.paraSplit(getStr);
                    getStr = split[0];
                    String rest = split[1];
                    boolean dynamic = false;
                    if (getStr.endsWith("(")) {
                        dynamic = true;
                        getStr = getStr.substring(0, getStr.length() - 1);
                    }
                    int para = 0;
                    String getExpr = "";
                    String getPart = "";
                    getStr += ":";
                    for (int i = 0; i < getStr.length(); i++) {
                        String c = getStr.substring(i, i + 1);
                        if (c.equals("(")) {
                            getPart += c;
                            para++;
                        } else if (c.equals(")")) {
                            getPart += c;
                            para--;
                        } else if (c.equals(":") && para == 0) {
                            if (getPart.startsWith("("))
                                getPart = getPart.substring(1, getPart.length() - 1);
                            else
                                getPart = "\"" + getPart + "\"";
                            if (getExpr.length() == 0)
                                getExpr = getPart;
                            else
                                getExpr += " + \":\" + " + getPart;
                            getPart = "";
                        } else {
                            getPart += c;
                        }
                    }
                    getExpr = "\"\" + " + getExpr;
                    getExpr = getExpr.replaceAll("\" \\+ \"", "");
                    if (assignStr != null && rest.length() == 0) {
                        String put = assignStr.trim().substring(1).trim();
                        return ".putIfNotNull(" + getExpr + ", " + put + ")";
                    } else if (dynamic) {
                        return ".getDynamic(" + getExpr + ", " + rest;
                    } else if (hasSign != null) {
                        //String sign = boolSign;
                        //if (boolSign.equals("?")) sign = "";
                        //if (getExpr.contains("*"))
                        return ".has(" + getExpr + ")" + rest;
                        //else
                        //   return sign + "makeBool(" + recordVar + ".get(" + getExpr + "))" + rest;
                    } else {
                        return ".get(" + getExpr + ")" + rest;
                    }
                }
            }.replaceIter(expr);
        } catch (RuntimeException e) {
            throw new FlowCompilerException(e.getMessage());
        }
    }

    public static String formatExpr(String expr) throws FlowCompilerException {
        expr = formatRecordExpr(expr);
        expr = new Replacer(" *:=([^;]*)", stringPattern) {
            @Override
            public String replace(Matcher matcher) {
                return ".putAll(" + matcher.group(1).trim() + ")";
            }
        }.replaceIter(expr);
        expr = formatEqExpr(expr);
        return expr;
    }

    private static int findExpr(String expr, int pos, int dir) {
        int para = 0;
        boolean inQuote = false;
        boolean hasChar = false;
        int i = pos;
        for (; i > 0 && i < expr.length(); i += dir) {
            String c = expr.substring(i, i + 1);
            if (c.equals("(")) {
                para += dir;
            } else if (c.equals(")")) {
                para -= dir;
            } else if (c.equals("\"")) {
                inQuote = !inQuote;
            } else if (c.equals(" ") && para <= 0 && !inQuote && hasChar) {
                return i;
            }
            if (para < 0) {
                return i - dir;
            }
            if (!c.equals(" "))
                hasChar = true;
        }
        return i;
    }

    public static String formatEqExpr(String expr) {
        return expr;
    }

    public static void compile(File flowFile, boolean binary) throws FlowCompilerException {
        System.out.println("Compiling flow: " + flowFile.getAbsolutePath());
        FlowCompiler fcompiler = new FlowCompiler(flowFile);
        File srcFile = new File(flowFile.getAbsolutePath().replaceFirst("\\.[A-Za-z]+$", "\\.java"));
        fcompiler.compileToFile(srcFile);
        System.out.println("Compiled to source code: " + srcFile.getAbsolutePath());
        if (fcompiler.flowXml.hasPublicStates()) {
            FlowSchemaCompiler.compile(flowFile.getAbsolutePath(), System.getProperty("user.dir"));
        }
        if (binary) {
            compileJavaFlow(srcFile);
            System.out.println("Compiled to byte code");
            /*
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
                
            if (compiler == null) {
               throw new RuntimeException("Could not find Java Compiler");
            }
            String binDir = flowFile.getAbsolutePath().replaceFirst("([\\\\/])src[\\\\/].*", "$1") + "bin";
            if (new File(binDir).exists()) {
               if (compiler.run(null, null, null, srcFile.getPath(), "-d", binDir) == 0) {
                  System.out.println("Compiled to byte code folder: " + binDir);
               }
            } else {
               throw new RuntimeException("Directory " + binDir + " does not exist");
            }
             */
        }
    }

    public static void compile(String flowFileName, String dir, boolean binary) throws FlowCompilerException {
        File fdir = new File(dir);
        File flowFile = null;
        if (new File(flowFileName).exists()) {
            flowFile = new File(flowFileName);
        } else if (new File(fdir, flowFileName).exists()) {
            flowFile = new File(fdir, flowFileName);
        } else {
            String f = FileFinder.findFirst(dir + "/src", flowFileName);
            if (f != null) {
                flowFile = new File(f);
            }
        }
        if (flowFile != null) {
            compile(flowFile, binary);
        }
    }

    public static void compileJavaFlow(File srcFile) throws FlowCompilerException {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (ToolProvider.getSystemJavaCompiler() == null) {
            throw new FlowCompilerException("Could not find Java Compiler");
        }
        if (!srcFile.exists()) {
            throw new FlowCompilerException(srcFile.getAbsolutePath() + " does not exist");
        }
        File outputDir = new File(srcFile.getAbsolutePath().replaceFirst("([\\\\/])src[\\\\/].*", "$1") + "bin");
        if (!outputDir.exists()) {
            throw new FlowCompilerException("Directory " + outputDir.getAbsolutePath() + " does not exist");
        }
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, Locale.US,
                StandardCharsets.UTF_8);
        Iterable<? extends JavaFileObject> compilationUnits = fileManager
                .getJavaFileObjectsFromStrings(Arrays.asList(srcFile.getAbsolutePath()));
        Iterable<String> args = Arrays.asList("-d", outputDir.getAbsolutePath());
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, args, null,
                compilationUnits);
        boolean success = task.call();
        try {
            fileManager.close();
        } catch (IOException e) {
        }
        for (Diagnostic<? extends JavaFileObject> diag : diagnostics.getDiagnostics()) {
            if (diag.getKind() == Kind.ERROR) {
                int javaLine = (int) diag.getLineNumber();
                int flowLine = mapLine(javaLine, srcFile);
                String message = diag.getMessage(Locale.US);
                message = message.replace("line " + javaLine, "line " + flowLine);
                throw new FlowCompilerException(message, flowLine);
            }
        }
        if (!success) {
            throw new FlowCompilerException("Compilation failed for unknown reason");
        }
    }

    private static int mapLine(int line, File file) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(file));
            String l;
            int lnn = 0;
            int lcount = 0;
            while ((l = br.readLine()) != null) {
                String ln = RegExp.getGroup(l, "// Line: (\\d+)", 1);
                if (ln != null) {
                    lnn = Integer.parseInt(ln);
                }
                if (lcount >= line)
                    return lnn;
                lcount++;
            }

            br.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null)
                try {
                    br.close();
                } catch (IOException e) {
                }
        }
        return 0;
    }

    public static void main(String[] args) {
        try {
            boolean compileToBinary = false;
            //boolean compileToXSD = false;
            String file = null;
            for (String arg : args) {
                if (arg.equals("-b"))
                    compileToBinary = true;
                //else if (arg.equals("-x")) 
                //   compileToXSD = true;
                else
                    file = arg;
            }
            if (file != null) {
                compile(file, System.getProperty("user.dir"), compileToBinary);

                //if (compileToXSD)
                //   FlowSchemaCompiler.compile(file, System.getProperty("user.dir"));
            } else {
                System.out.println("Compiles flow XML to Java source.\n");
                System.out.println("Usage:");
                System.out.println("iristk cflow [OPTIONS] XML\n");
                System.out.println("Options:");
                System.out.println("-b  Also compile Java source to binary");
                //System.out.println("-x  Also compile templates to XSD Schema");
            }
        } catch (FlowCompilerException e) {
            System.err.println("Error on line " + e.getLineNumber() + ": " + e.getMessage());
        }
    }

    public void useUniqueNames(boolean b) {
        this.useUniqueNames = b;
    }

    public String getFlowName() {
        return flowXml.getPackage() + "." + getLocalFlowName();
    }

    public String getLocalFlowName() {
        if (useUniqueNames)
            return flowXml.getName() + uniqueNameSuffix;
        else
            return flowXml.getName();
    }

}