org.graphwalker.machines.ExtendedFiniteStateMachine.java Source code

Java tutorial

Introduction

Here is the source code for org.graphwalker.machines.ExtendedFiniteStateMachine.java

Source

// This file is part of the GraphWalker java package
// The MIT License
//
// Copyright (c) 2010 graphwalker.org
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package org.graphwalker.machines;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.apache.log4j.Logger;
import org.graphwalker.Util;
import org.graphwalker.exceptions.FoundNoEdgeException;
import org.graphwalker.exceptions.InvalidDataException;
import org.graphwalker.filters.AccessableEdgeFilter;
import org.graphwalker.graph.Edge;
import org.json.simple.JSONObject;

import bsh.EvalError;
import bsh.Interpreter;
import bsh.NameSpace;
import bsh.Primitive;
import bsh.UtilEvalError;

public class ExtendedFiniteStateMachine extends FiniteStateMachine {

    private PrintStream Void;

    private static Logger logger = Util.setupLogger(ExtendedFiniteStateMachine.class);

    private Interpreter beanShellEngine = null;
    private AccessableEdgeFilter accessableFilter;

    private ScriptEngineManager mgr = null;
    private ScriptEngine jsEngine = null;

    private Stack<CannedNameSpace> namespaceStack;

    private PrintStream oldPrintStream;

    public ExtendedFiniteStateMachine(boolean usingJsEngine) {
        super();
        namespaceStack = new Stack<CannedNameSpace>();
        if (usingJsEngine) {
            mgr = new ScriptEngineManager();
            jsEngine = mgr.getEngineByExtension("js");
            accessableFilter = new AccessableEdgeFilter(jsEngine);
        } else {
            beanShellEngine = new Interpreter();
            accessableFilter = new AccessableEdgeFilter(beanShellEngine);
        }
        Void = new VoidPrintStream();
    }

    public void eval(String script) {
        if (jsEngine != null) {
            try {
                jsEngine.eval(script);
            } catch (ScriptException e) {
                logger.error("Problem when running: '" + script + "' in BeanShell");
                logger.error("EvalError: " + e);
                logger.error(e.getCause());
                throw new RuntimeException("Execution of startup script generated an error.", e);
            }
        } else if (beanShellEngine != null) {
            try {
                beanShellEngine.eval(script);
            } catch (EvalError e) {
                logger.error("Problem when running: '" + script + "' in BeanShell");
                logger.error("EvalError: " + e);
                logger.error(e.getCause());
                throw new RuntimeException("Execution of startup script generated an error.", e);
            }
        }
    }

    @Override
    public String getCurrentVertexName() {
        return super.getCurrentVertexName() + (hasInternalVariables() ? "/" + getCurrentDataString() : "");
    }

    @Override
    public Set<Edge> getCurrentOutEdges() throws FoundNoEdgeException {
        Set<Edge> retur = super.getCurrentOutEdges();
        for (Iterator<Edge> i = retur.iterator(); i.hasNext();) {
            Edge e = i.next();
            if (!accessableFilter.acceptEdge(getModel(), e)) {
                logger.debug("Not accessable: " + e + " from " + getCurrentVertexName());
                i.remove();
            } else {
                logger.debug("Accessable: " + e + " from " + getCurrentVertexName());
            }
        }
        if (retur.size() == 0) {
            throw new FoundNoEdgeException("Cul-De-Sac, dead end found in '" + getCurrentVertex() + "'");
        }
        return retur;
    }

    @Override
    public boolean hasInternalVariables() {
        if (jsEngine != null) {
            return !jsEngine.getBindings(ScriptContext.ENGINE_SCOPE).isEmpty();
        } else if (beanShellEngine != null) {
            return beanShellEngine.getNameSpace().getVariableNames().length > 1;
        }
        return false;
    }

    public Set<Entry<String, Object>> getCurrentJsEngineData() {
        Bindings bindings = jsEngine.getBindings(ScriptContext.ENGINE_SCOPE);
        return bindings.entrySet();
    }

    public Hashtable<String, Object> getCurrentBeanShellData() {
        Hashtable<String, Object> retur = new Hashtable<String, Object>();
        if (!hasInternalVariables())
            return retur;

        int i = 0;
        NameSpace ns = beanShellEngine.getNameSpace();
        String[] variableNames = beanShellEngine.getNameSpace().getVariableNames();
        try {
            for (; i < variableNames.length; i++) {
                if (!variableNames[i].equals("bsh")) {
                    retur.put(variableNames[i], Primitive.unwrap(ns.getVariable(variableNames[i])));
                }
            }
        } catch (UtilEvalError e) {
            throw new RuntimeException("Malformed model data: " + variableNames[i] + "\nBeanShell error message: '"
                    + e.getMessage() + "'");
        } catch (NullPointerException e) {
            logger.warn("Did not get data/value for: " + variableNames[i]);
            return retur;
        }
        return retur;
    }

    /**
     * Walks the data space, and returns all data as an json object.
     * 
     * @param dataName
     * @return json object of the data space
     */
    @SuppressWarnings("unchecked")
    public ArrayList<JSONObject> getDataAsJSON() {
        ArrayList<JSONObject> data = new ArrayList<JSONObject>();

        if (beanShellEngine != null) {
            Hashtable<String, Object> dataTable = getCurrentBeanShellData();
            Enumeration<String> enumKey = dataTable.keys();
            while (enumKey.hasMoreElements()) {
                JSONObject jsonObj = new JSONObject();
                String key = enumKey.nextElement();
                String value = null;
                if (dataTable.get(key) instanceof Object[]) {
                    value = Arrays.deepToString((Object[]) dataTable.get(key));
                } else {
                    value = dataTable.get(key).toString();
                }
                jsonObj.put("name", key);
                jsonObj.put("value", value);
                data.add(jsonObj);
            }
        }
        return data;
    }

    /**
     * Walks the data space, and return the value of the data, if found.
     * 
     * @param dataName
     * @return
     * @throws InvalidDataException is thrown if the data is not found in the data space
     */
    public String getDataValue(String dataName) {
        if (jsEngine != null) {
            return jsEngine.get(dataName).toString();
        } else if (beanShellEngine != null) {
            Hashtable<String, Object> dataTable = getCurrentBeanShellData();
            if (dataTable.containsKey(dataName)) {
                if (dataTable.get(dataName) instanceof Object[]) {
                    return Arrays.deepToString((Object[]) dataTable.get(dataName));
                } else {
                    return dataTable.get(dataName).toString();
                }
            }
        }
        return "";
    }

    /**
     * Executes an action, and returns any outcome as a string.
     * 
     * @param action
     * @return
     * @throws InvalidDataException is thrown if the data is not found in the data space
     */
    public String execAction(String action) throws InvalidDataException {
        logger.debug("Will try to execute: " + action);
        Object res = null;
        if (jsEngine != null) {
            try {
                res = jsEngine.eval(action);
            } catch (ScriptException e) {
                throw new InvalidDataException(
                        "The action: '" + action + "', does not evaluate correctly. Detail: " + e.getMessage());
            }
        } else if (beanShellEngine != null) {
            try {
                res = beanShellEngine.eval(action);
            } catch (EvalError e) {
                throw new InvalidDataException(
                        "The action: '" + action + "', does not evaluate correctly. Detail: " + e.getMessage());
            }
        }
        return res.toString();
    }

    @Override
    public String getCurrentDataString() {
        String retur = "";

        if (jsEngine != null) {
            Set<Entry<String, Object>> dataTable = getCurrentJsEngineData();
            for (Entry<String, Object> entry : dataTable) {
                if (!entry.getKey().equals("println") && !entry.getKey().equals("print")
                        && !entry.getKey().equals("context"))
                    retur += entry.getKey() + "=" + entry.getValue() + ";";
            }
        } else if (beanShellEngine != null) {
            Hashtable<String, Object> dataTable = getCurrentBeanShellData();
            Enumeration<String> e = dataTable.keys();
            while (e.hasMoreElements()) {
                String key = e.nextElement();
                String data = "";
                if (dataTable.get(key) instanceof Object[]) {
                    data = Arrays.deepToString((Object[]) dataTable.get(key));
                } else {
                    data = dataTable.get(key).toString();
                }
                retur += key + "=" + data + ";";
            }
        }
        return retur;
    }

    @Override
    public boolean walkEdge(Edge edge) {
        boolean hasWalkedEdge = super.walkEdge(edge);
        if (hasWalkedEdge) {
            if (hasAction(edge)) {
                PrintStream ps = System.out;
                System.setOut(Void);

                if (jsEngine != null) {
                    try {
                        jsEngine.eval(getAction(edge));
                    } catch (ScriptException e) {
                        logger.error("Problem when running: '" + getAction(edge) + "' in Java Script engine");
                        logger.error("EvalError: " + e);
                        logger.error(e.getCause());
                        throw new RuntimeException("Malformed action sequence\n\t" + edge + "\n\tAction sequence: "
                                + edge.getActionsKey() + "\n\tJava Script error message: '" + e.getMessage()
                                + "'\nDetails: " + e.getCause());
                    } finally {
                        System.setOut(ps);
                    }
                } else if (beanShellEngine != null) {
                    try {
                        beanShellEngine.eval(getAction(edge));
                    } catch (EvalError e) {
                        logger.error("Problem when running: '" + getAction(edge) + "' in BeanShell");
                        logger.error("EvalError: " + e);
                        logger.error(e.getCause());
                        throw new RuntimeException("Malformed action sequence\n\t" + edge + "\n\tAction sequence: "
                                + edge.getActionsKey() + "\n\tBeanShell error message: '" + e.getMessage()
                                + "'\nDetails: " + e.getCause());
                    } finally {
                        System.setOut(ps);
                    }
                }
            }
        }
        return hasWalkedEdge;
    }

    private String getAction(Edge edge) {
        return (edge == null ? "" : edge.getActionsKey());
    }

    private boolean hasAction(Edge edge) {
        return (edge == null ? false : !edge.getActionsKey().isEmpty());
    }

    @Override
    protected void track() {
        super.track();
        if (jsEngine != null) {
        } else if (beanShellEngine != null) {
            namespaceStack.push(new CannedNameSpace(beanShellEngine.getNameSpace()));
        }
    }

    @Override
    protected void popVertex() {
        super.popVertex();
        if (jsEngine != null) {
        } else if (beanShellEngine != null) {
            beanShellEngine.setNameSpace((namespaceStack.pop()).unpack());
        }
    }

    private class CannedNameSpace {
        byte[] store;

        public CannedNameSpace(NameSpace objNameSpace) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos;
            try {
                oos = new ObjectOutputStream(baos);
                oos.writeObject(objNameSpace);
            } catch (IOException e) {
                throw new RuntimeException("Unable to store backtrack information due to a IOException.", e);
            }
            store = baos.toByteArray();
        }

        public NameSpace unpack() {
            ByteArrayInputStream bais = new ByteArrayInputStream(store);
            ObjectInputStream ois;
            try {
                ois = new ObjectInputStream(bais);
                return (NameSpace) ois.readObject();
            } catch (IOException e) {
                throw new RuntimeException("Unable to restore backtrack information due to a IOException.", e);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(
                        "Unable to restore backtrack information as the NameSpace Class could not be found.", e);
            }
        }
    }

    @Override
    public void setCalculatingPath(boolean calculatingPath) {
        super.setCalculatingPath(calculatingPath);
        if (calculatingPath && this.oldPrintStream != System.out) {
            this.oldPrintStream = System.out;
            System.setOut(new VoidPrintStream());
        } else if (!calculatingPath && this.oldPrintStream != System.out) {
            System.setOut(this.oldPrintStream);
        }
    }

    private class VoidPrintStream extends PrintStream {
        public VoidPrintStream() {
            super(System.out);
        }

        @Override
        public void write(byte[] buf, int off, int len) {
        }
    }

    public boolean isJsEnabled() {
        return jsEngine != null;
    }

    public boolean isBeanShellEnabled() {
        return beanShellEngine != null;
    }
}